From c201c15f8c19d0056f27b8a7e1fc675bbdfca9e8 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Sun, 24 Jan 2021 01:32:10 +0100 Subject: [PATCH 001/339] Improve documentation for PayloadConfig (#1923) --- src/types/payload.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/types/payload.rs b/src/types/payload.rs index ad0bfd14f..ce4a9cb1b 100644 --- a/src/types/payload.rs +++ b/src/types/payload.rs @@ -191,6 +191,9 @@ fn bytes_to_string(body: Bytes, encoding: &'static Encoding) -> Result Self { Self { limit, @@ -206,7 +209,7 @@ impl PayloadConfig { } } - /// Set maximum accepted payload size. The default limit is 256kB. + /// Set maximum accepted payload size in bytes. The default limit is 256kB. pub fn limit(mut self, limit: usize) -> Self { self.limit = limit; self From 51e54dac8b18ea0ac0b1d55f6a9d8af903e0e811 Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Wed, 27 Jan 2021 02:49:57 -0800 Subject: [PATCH 002/339] fix limit not working on HttpMessageBody::limit (#1938) --- src/types/payload.rs | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/types/payload.rs b/src/types/payload.rs index ce4a9cb1b..e8fba0632 100644 --- a/src/types/payload.rs +++ b/src/types/payload.rs @@ -289,10 +289,12 @@ impl HttpMessageBody { if let Some(l) = req.headers().get(&header::CONTENT_LENGTH) { match l.to_str() { Ok(s) => match s.parse::() { - Ok(l) if l > DEFAULT_CONFIG_LIMIT => { - err = Some(PayloadError::Overflow) + Ok(l) => { + if l > DEFAULT_CONFIG_LIMIT { + err = Some(PayloadError::Overflow); + } + length = Some(l) } - Ok(l) => length = Some(l), Err(_) => err = Some(PayloadError::UnknownLength), }, Err(_) => err = Some(PayloadError::UnknownLength), @@ -316,9 +318,11 @@ impl HttpMessageBody { /// Change max size of payload. By default max size is 256kB pub fn limit(mut self, limit: usize) -> Self { if let Some(l) = self.length { - if l > limit { - self.err = Some(PayloadError::Overflow); - } + self.err = if l > limit { + Some(PayloadError::Overflow) + } else { + None + }; } self.limit = limit; self From 83fb4978ad9c7758449d9fb71dc9eab5af442eb7 Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Sat, 6 Feb 2021 08:05:33 -0800 Subject: [PATCH 003/339] fix awc test_client test (#1960) --- awc/tests/test_client.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/awc/tests/test_client.rs b/awc/tests/test_client.rs index 88987e639..8ca94ec91 100644 --- a/awc/tests/test_client.rs +++ b/awc/tests/test_client.rs @@ -722,9 +722,9 @@ async fn test_client_cookie_handling() { async fn client_unread_response() { let addr = test::unused_addr(); - std::thread::spawn(move || { - let lst = std::net::TcpListener::bind(addr).unwrap(); + 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]; From 20cf0094e591f53c31aa87987c0875bcf3595a05 Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Sat, 6 Feb 2021 08:23:59 -0800 Subject: [PATCH 004/339] fix master branch build. change web::block output type. (#1957) --- CHANGES.md | 2 + Cargo.toml | 6 +- actix-files/Cargo.toml | 2 +- actix-files/src/chunked.rs | 106 +++++++++++++++++------------ actix-files/src/named.rs | 16 +---- actix-http-test/Cargo.toml | 2 +- actix-http/CHANGES.md | 2 + actix-http/Cargo.toml | 2 +- actix-http/src/encoding/decoder.rs | 11 +-- actix-http/src/encoding/encoder.rs | 13 +--- actix-http/src/error.rs | 27 +++----- actix-multipart/Cargo.toml | 2 +- actix-web-actors/Cargo.toml | 2 +- actix-web-codegen/Cargo.toml | 2 +- awc/Cargo.toml | 2 +- src/test.rs | 2 +- src/web.rs | 13 ++-- 17 files changed, 98 insertions(+), 114 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index a6bcd56cc..3f6b09d7a 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -11,6 +11,7 @@ * `ServiceRequest::into_parts` and `ServiceRequest::from_parts` would not fail. `ServiceRequest::from_request` would not fail and no payload would be generated [#1893] * Our `Either` type now uses `Left`/`Right` variants (instead of `A`/`B`) [#1894] +* `web::block` accept any closure that has an output bound to `Send` and `'static`. [#1957] ### Fixed * Multiple calls `App::data` with the same type now keeps the latest call's data. [#1906] @@ -28,6 +29,7 @@ [#1894]: https://github.com/actix/actix-web/pull/1894 [#1869]: https://github.com/actix/actix-web/pull/1869 [#1906]: https://github.com/actix/actix-web/pull/1906 +[#1957]: https://github.com/actix/actix-web/pull/1957 ## 4.0.0-beta.1 - 2021-01-07 diff --git a/Cargo.toml b/Cargo.toml index bae6cb6cb..28cee0dad 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -74,11 +74,11 @@ required-features = ["rustls"] [dependencies] actix-codec = "0.4.0-beta.1" -actix-macros = "0.1.0" +actix-macros = "=0.2.0-beta.1" actix-router = "0.2.4" -actix-rt = "2.0.0-beta.2" +actix-rt = "=2.0.0-beta.2" actix-server = "2.0.0-beta.2" -actix-service = "2.0.0-beta.3" +actix-service = "=2.0.0-beta.3" actix-utils = "3.0.0-beta.1" actix-tls = { version = "3.0.0-beta.2", default-features = false, optional = true } diff --git a/actix-files/Cargo.toml b/actix-files/Cargo.toml index bde2cb717..b72631eb1 100644 --- a/actix-files/Cargo.toml +++ b/actix-files/Cargo.toml @@ -31,5 +31,5 @@ percent-encoding = "2.1" v_htmlescape = "0.12" [dev-dependencies] -actix-rt = "2.0.0-beta.2" +actix-rt = "=2.0.0-beta.2" actix-web = "4.0.0-beta.1" diff --git a/actix-files/src/chunked.rs b/actix-files/src/chunked.rs index 5b7b17dc4..2a62b1d26 100644 --- a/actix-files/src/chunked.rs +++ b/actix-files/src/chunked.rs @@ -8,7 +8,7 @@ use std::{ }; use actix_web::{ - error::{Error, ErrorInternalServerError}, + error::{BlockingError, Error}, rt::task::{spawn_blocking, JoinHandle}, }; use bytes::Bytes; @@ -18,11 +18,26 @@ use futures_core::{ready, Stream}; /// A helper created from a `std::fs::File` which reads the file /// chunk-by-chunk on a `ThreadPool`. pub struct ChunkedReadFile { - pub(crate) size: u64, - pub(crate) offset: u64, - pub(crate) file: Option, - pub(crate) fut: Option>>, - pub(crate) counter: u64, + size: u64, + offset: u64, + state: ChunkedReadFileState, + counter: u64, +} + +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, + } + } } impl fmt::Debug for ChunkedReadFile { @@ -38,49 +53,52 @@ impl Stream for ChunkedReadFile { mut self: Pin<&mut Self>, cx: &mut Context<'_>, ) -> Poll> { - if let Some(ref mut fut) = self.fut { - let res = match ready!(Pin::new(fut).poll(cx)) { - Ok(Ok((file, bytes))) => { - self.fut.take(); - self.file = Some(file); + 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; - self.offset += bytes.len() as u64; - self.counter += bytes.len() as u64; + if size == counter { + Poll::Ready(None) + } else { + let mut file = file + .take() + .expect("ChunkedReadFile polled after completion"); - Ok(bytes) + let fut = spawn_blocking(move || { + let max_bytes = + cmp::min(size.saturating_sub(counter), 65_536) as usize; + + 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 { + return Err(io::ErrorKind::UnexpectedEof.into()); + } + + Ok((file, Bytes::from(buf))) + }); + this.state = ChunkedReadFileState::Future(fut); + self.poll_next(cx) } - Ok(Err(e)) => Err(e.into()), - Err(_) => Err(ErrorInternalServerError("Unexpected error")), - }; - return Poll::Ready(Some(res)); - } + } + ChunkedReadFileState::Future(ref mut fut) => { + let (file, bytes) = + ready!(Pin::new(fut).poll(cx)).map_err(|_| BlockingError)??; + this.state = ChunkedReadFileState::File(Some(file)); - let size = self.size; - let offset = self.offset; - let counter = self.counter; + this.offset += bytes.len() as u64; + this.counter += bytes.len() as u64; - if size == counter { - Poll::Ready(None) - } else { - let mut file = self.file.take().expect("Use after completion"); - - self.fut = Some(spawn_blocking(move || { - let max_bytes = cmp::min(size.saturating_sub(counter), 65_536) as usize; - - 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 { - return Err(io::ErrorKind::UnexpectedEof.into()); - } - - Ok((file, Bytes::from(buf))) - })); - - self.poll_next(cx) + Poll::Ready(Some(Ok(bytes))) + } } } } diff --git a/actix-files/src/named.rs b/actix-files/src/named.rs index 8cd2a23f9..6fa3f7c6c 100644 --- a/actix-files/src/named.rs +++ b/actix-files/src/named.rs @@ -298,13 +298,7 @@ impl NamedFile { res.encoding(current_encoding); } - let reader = ChunkedReadFile { - size: self.md.len(), - offset: 0, - file: Some(self.file), - fut: None, - counter: 0, - }; + let reader = ChunkedReadFile::new(self.md.len(), 0, self.file); return res.streaming(reader); } @@ -426,13 +420,7 @@ impl NamedFile { return resp.status(StatusCode::NOT_MODIFIED).finish(); } - let reader = ChunkedReadFile { - offset, - size: length, - file: Some(self.file), - fut: None, - counter: 0, - }; + let reader = ChunkedReadFile::new(length, offset, self.file); if offset != 0 || length != self.md.len() { resp.status(StatusCode::PARTIAL_CONTENT); diff --git a/actix-http-test/Cargo.toml b/actix-http-test/Cargo.toml index 772b60f76..bc71f967a 100644 --- a/actix-http-test/Cargo.toml +++ b/actix-http-test/Cargo.toml @@ -33,7 +33,7 @@ actix-service = "2.0.0-beta.3" actix-codec = "0.4.0-beta.1" actix-tls = "3.0.0-beta.2" actix-utils = "3.0.0-beta.1" -actix-rt = "2.0.0-beta.2" +actix-rt = "=2.0.0-beta.2" actix-server = "2.0.0-beta.2" awc = "3.0.0-beta.1" diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 34de7727a..73339974d 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -16,6 +16,7 @@ * `Extensions::insert` returns Option of replaced item. [#1904] * Remove `HttpResponseBuilder::json2()` and make `HttpResponseBuilder::json()` take a value by reference. [#1903] +* Simplify `BlockingError` type to a struct. It's only triggered with blocking thread pool is dead. [#1957] ### Removed * `ResponseBuilder::set`; use `ResponseBuilder::insert_header`. [#1869] @@ -29,6 +30,7 @@ [#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 +[#1957]: https://github.com/actix/actix-web/pull/1957 ## 3.0.0-beta.1 - 2021-01-07 diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index af6209248..8f638c1d7 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -43,7 +43,7 @@ actors = ["actix"] actix-service = "2.0.0-beta.3" actix-codec = "0.4.0-beta.1" actix-utils = "3.0.0-beta.1" -actix-rt = "2.0.0-beta.2" +actix-rt = "=2.0.0-beta.2" actix-tls = "3.0.0-beta.2" actix = { version = "0.11.0-beta.1", optional = true } diff --git a/actix-http/src/encoding/decoder.rs b/actix-http/src/encoding/decoder.rs index b26609911..2cf2f6e03 100644 --- a/actix-http/src/encoding/decoder.rs +++ b/actix-http/src/encoding/decoder.rs @@ -79,15 +79,8 @@ where ) -> Poll> { loop { if let Some(ref mut fut) = self.fut { - let (chunk, decoder) = match ready!(Pin::new(fut).poll(cx)) { - Ok(Ok(item)) => item, - Ok(Err(e)) => { - return Poll::Ready(Some(Err(BlockingError::Error(e).into()))) - } - Err(_) => { - return Poll::Ready(Some(Err(BlockingError::Canceled.into()))) - } - }; + let (chunk, decoder) = + ready!(Pin::new(fut).poll(cx)).map_err(|_| BlockingError)??; self.decoder = Some(decoder); self.fut.take(); if let Some(chunk) = chunk { diff --git a/actix-http/src/encoding/encoder.rs b/actix-http/src/encoding/encoder.rs index 28c757076..1d4a8e933 100644 --- a/actix-http/src/encoding/encoder.rs +++ b/actix-http/src/encoding/encoder.rs @@ -136,17 +136,8 @@ impl MessageBody for Encoder { } if let Some(ref mut fut) = this.fut { - let mut encoder = match ready!(Pin::new(fut).poll(cx)) { - Ok(Ok(item)) => item, - Ok(Err(e)) => { - return Poll::Ready(Some(Err(BlockingError::Error(e).into()))) - } - Err(_) => { - return Poll::Ready(Some(Err( - BlockingError::::Canceled.into(), - ))) - } - }; + let mut encoder = + ready!(Pin::new(fut).poll(cx)).map_err(|_| BlockingError)??; let chunk = encoder.take(); *this.encoder = Some(encoder); this.fut.take(); diff --git a/actix-http/src/error.rs b/actix-http/src/error.rs index 9ff154240..28697cbbf 100644 --- a/actix-http/src/error.rs +++ b/actix-http/src/error.rs @@ -297,17 +297,13 @@ impl From for ParseError { /// A set of errors that can occur running blocking tasks in thread pool. #[derive(Debug, Display)] -pub enum BlockingError { - #[display(fmt = "{:?}", _0)] - Error(E), - #[display(fmt = "Thread pool is gone")] - Canceled, -} +#[display(fmt = "Blocking thread pool is gone")] +pub struct BlockingError; -impl std::error::Error for BlockingError {} +impl std::error::Error for BlockingError {} /// `InternalServerError` for `BlockingError` -impl ResponseError for BlockingError {} +impl ResponseError for BlockingError {} #[derive(Display, Debug)] /// A set of errors that can occur during payload parsing @@ -372,15 +368,12 @@ impl From for PayloadError { } } -impl From> for PayloadError { - fn from(err: BlockingError) -> Self { - match err { - BlockingError::Error(e) => PayloadError::Io(e), - BlockingError::Canceled => PayloadError::Io(io::Error::new( - io::ErrorKind::Other, - "Operation is canceled", - )), - } +impl From for PayloadError { + fn from(_: BlockingError) -> Self { + PayloadError::Io(io::Error::new( + io::ErrorKind::Other, + "Operation is canceled", + )) } } diff --git a/actix-multipart/Cargo.toml b/actix-multipart/Cargo.toml index 44a7e8d16..60620bce7 100644 --- a/actix-multipart/Cargo.toml +++ b/actix-multipart/Cargo.toml @@ -28,5 +28,5 @@ mime = "0.3" twoway = "0.2" [dev-dependencies] -actix-rt = "2.0.0-beta.2" +actix-rt = "=2.0.0-beta.2" actix-http = "3.0.0-beta.1" diff --git a/actix-web-actors/Cargo.toml b/actix-web-actors/Cargo.toml index 0f90edb07..34bf713ae 100644 --- a/actix-web-actors/Cargo.toml +++ b/actix-web-actors/Cargo.toml @@ -28,6 +28,6 @@ pin-project = "1.0.0" tokio = { version = "1", features = ["sync"] } [dev-dependencies] -actix-rt = "2.0.0-beta.2" +actix-rt = "=2.0.0-beta.2" env_logger = "0.7" futures-util = { version = "0.3.7", default-features = false } diff --git a/actix-web-codegen/Cargo.toml b/actix-web-codegen/Cargo.toml index 00875cf1b..70ab55681 100644 --- a/actix-web-codegen/Cargo.toml +++ b/actix-web-codegen/Cargo.toml @@ -19,7 +19,7 @@ syn = { version = "1", features = ["full", "parsing"] } proc-macro2 = "1" [dev-dependencies] -actix-rt = "2.0.0-beta.2" +actix-rt = "=2.0.0-beta.2" actix-web = "4.0.0-beta.1" futures-util = { version = "0.3.7", default-features = false } trybuild = "1" diff --git a/awc/Cargo.toml b/awc/Cargo.toml index 90f33c9ba..675110e5a 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -40,7 +40,7 @@ compress = ["actix-http/compress"] actix-codec = "0.4.0-beta.1" actix-service = "2.0.0-beta.3" actix-http = "3.0.0-beta.1" -actix-rt = "2.0.0-beta.2" +actix-rt = "=2.0.0-beta.2" base64 = "0.13" bytes = "1" diff --git a/src/test.rs b/src/test.rs index 769cece55..f3347bbbf 100644 --- a/src/test.rs +++ b/src/test.rs @@ -1213,7 +1213,7 @@ mod tests { match res { Ok(value) => Ok(HttpResponse::Ok() .content_type("text/plain") - .body(format!("Async with block value: {}", value))), + .body(format!("Async with block value: {:?}", value))), Err(_) => panic!("Unexpected"), } } diff --git a/src/web.rs b/src/web.rs index 88071f551..3b4475b63 100644 --- a/src/web.rs +++ b/src/web.rs @@ -274,14 +274,11 @@ pub fn service(path: T) -> WebService { /// Execute blocking function on a thread pool, returns future that resolves /// to result of the function execution. -pub async fn block(f: F) -> Result> +pub fn block(f: F) -> impl Future> where - F: FnOnce() -> Result + Send + 'static, - I: Send + 'static, - E: Send + std::fmt::Debug + 'static, + F: FnOnce() -> R + Send + 'static, + R: Send + 'static, { - match actix_rt::task::spawn_blocking(f).await { - Ok(res) => res.map_err(BlockingError::Error), - Err(_) => Err(BlockingError::Canceled), - } + let fut = actix_rt::task::spawn_blocking(f); + async { fut.await.map_err(|_| BlockingError) } } From 41bc04b1c43c860225b5914d97cf9f7741f1d74d Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Sat, 6 Feb 2021 17:00:40 -0800 Subject: [PATCH 005/339] Use immutable reference of service state. Update awc dns resolver. (#1905) --- CHANGES.md | 7 +- Cargo.toml | 16 +-- actix-files/Cargo.toml | 4 +- actix-files/src/lib.rs | 146 +++++++++++++------------- actix-files/src/service.rs | 17 +-- actix-files/tests/encoding.rs | 9 +- actix-http-test/Cargo.toml | 10 +- actix-http-test/src/lib.rs | 5 +- actix-http/CHANGES.md | 5 +- actix-http/Cargo.toml | 16 +-- actix-http/src/client/config.rs | 2 +- actix-http/src/client/connection.rs | 3 +- actix-http/src/client/connector.rs | 14 +-- actix-http/src/client/error.rs | 3 +- actix-http/src/client/pool.rs | 23 ++-- actix-http/src/h1/dispatcher.rs | 22 ++-- actix-http/src/h1/expect.rs | 8 +- actix-http/src/h1/service.rs | 16 +-- actix-http/src/h1/upgrade.rs | 8 +- actix-http/src/h2/dispatcher.rs | 9 +- actix-http/src/h2/service.rs | 11 +- actix-http/src/header/into_value.rs | 2 +- actix-http/src/service.rs | 24 ++--- actix-http/tests/test_ws.rs | 9 +- actix-multipart/Cargo.toml | 4 +- actix-web-actors/Cargo.toml | 4 +- actix-web-actors/src/context.rs | 11 +- actix-web-codegen/Cargo.toml | 2 +- actix-web-codegen/src/lib.rs | 3 +- actix-web-codegen/tests/test_macro.rs | 10 +- awc/CHANGES.md | 4 + awc/Cargo.toml | 11 +- awc/README.md | 2 +- awc/src/builder.rs | 9 +- awc/src/connect.rs | 16 +-- awc/src/lib.rs | 7 +- awc/src/sender.rs | 10 +- awc/src/ws.rs | 6 +- awc/tests/test_client.rs | 4 +- benches/responder.rs | 4 +- benches/server.rs | 2 +- benches/service.rs | 4 +- src/app.rs | 32 +++--- src/app_service.rs | 8 +- src/config.rs | 16 +-- src/data.rs | 16 +-- src/handler.rs | 4 +- src/middleware/compat.rs | 16 +-- src/middleware/compress.rs | 2 +- src/middleware/condition.rs | 12 +-- src/middleware/default_headers.rs | 8 +- src/middleware/err_handlers.rs | 10 +- src/middleware/logger.rs | 8 +- src/middleware/normalize.rs | 56 +++++----- src/request.rs | 34 +++--- src/request_data.rs | 4 +- src/resource.rs | 52 ++++----- src/responder.rs | 2 +- src/route.rs | 22 ++-- src/scope.rs | 129 +++++++++++------------ src/server.rs | 2 +- src/service.rs | 6 +- src/test.rs | 72 ++++++------- src/types/payload.rs | 18 ++-- tests/test_httpserver.rs | 4 +- 65 files changed, 497 insertions(+), 538 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 3f6b09d7a..e9a788950 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -11,8 +11,12 @@ * `ServiceRequest::into_parts` and `ServiceRequest::from_parts` would not fail. `ServiceRequest::from_request` would not fail and no payload would be generated [#1893] * Our `Either` type now uses `Left`/`Right` variants (instead of `A`/`B`) [#1894] +* `test::{call_service, read_response, read_response_json, send_request}` take `&Service` + in argument [#1905] +* `App::wrap_fn`, `Resource::wrap_fn` and `Scope::wrap_fn` would give `&Service` in closure + argument [#1905] * `web::block` accept any closure that has an output bound to `Send` and `'static`. [#1957] - + ### Fixed * Multiple calls `App::data` with the same type now keeps the latest call's data. [#1906] @@ -28,6 +32,7 @@ [#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 [#1957]: https://github.com/actix/actix-web/pull/1957 diff --git a/Cargo.toml b/Cargo.toml index 28cee0dad..1e2c35d6a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -74,13 +74,13 @@ required-features = ["rustls"] [dependencies] actix-codec = "0.4.0-beta.1" -actix-macros = "=0.2.0-beta.1" -actix-router = "0.2.4" -actix-rt = "=2.0.0-beta.2" -actix-server = "2.0.0-beta.2" -actix-service = "=2.0.0-beta.3" -actix-utils = "3.0.0-beta.1" -actix-tls = { version = "3.0.0-beta.2", default-features = false, optional = true } +actix-macros = "0.2.0" +actix-router = "0.2.7" +actix-rt = "2" +actix-server = "2.0.0-beta.3" +actix-service = "2.0.0-beta.4" +actix-utils = "3.0.0-beta.2" +actix-tls = { version = "3.0.0-beta.3", default-features = false, optional = true } actix-web-codegen = "0.4.0" actix-http = "3.0.0-beta.1" @@ -108,7 +108,7 @@ rust-tls = { package = "rustls", version = "0.19.0", optional = true } smallvec = "1.6" [dev-dependencies] -actix = "0.11.0-beta.1" +actix = { version = "0.11.0-beta.2", default-features = false } actix-http = { version = "3.0.0-beta.1", features = ["actors"] } rand = "0.8" env_logger = "0.8" diff --git a/actix-files/Cargo.toml b/actix-files/Cargo.toml index b72631eb1..3371ab060 100644 --- a/actix-files/Cargo.toml +++ b/actix-files/Cargo.toml @@ -18,7 +18,7 @@ path = "src/lib.rs" [dependencies] actix-web = { version = "4.0.0-beta.1", default-features = false } -actix-service = "2.0.0-beta.3" +actix-service = "2.0.0-beta.4" bitflags = "1" bytes = "1" futures-core = { version = "0.3.7", default-features = false } @@ -31,5 +31,5 @@ percent-encoding = "2.1" v_htmlescape = "0.12" [dev-dependencies] -actix-rt = "=2.0.0-beta.2" +actix-rt = "2" actix-web = "4.0.0-beta.1" diff --git a/actix-files/src/lib.rs b/actix-files/src/lib.rs index 7b3e9adb5..b2f6384a8 100644 --- a/actix-files/src/lib.rs +++ b/actix-files/src/lib.rs @@ -73,7 +73,8 @@ mod tests { }, middleware::Compress, test::{self, TestRequest}, - web, App, HttpResponse, Responder, + web::{self, Bytes}, + App, HttpResponse, Responder, }; use futures_util::future::ok; @@ -365,7 +366,7 @@ mod tests { DispositionType::Attachment } - let mut srv = test::init_service( + let srv = test::init_service( App::new().service( Files::new("/", ".") .mime_override(all_attachment) @@ -375,7 +376,7 @@ mod tests { .await; let request = TestRequest::get().uri("/").to_request(); - let response = test::call_service(&mut srv, request).await; + let response = test::call_service(&srv, request).await; assert_eq!(response.status(), StatusCode::OK); let content_disposition = response @@ -390,7 +391,7 @@ mod tests { #[actix_rt::test] async fn test_named_file_ranges_status_code() { - let mut srv = test::init_service( + let srv = test::init_service( App::new().service(Files::new("/test", ".").index_file("Cargo.toml")), ) .await; @@ -400,7 +401,7 @@ mod tests { .uri("/t%65st/Cargo.toml") .insert_header((header::RANGE, "bytes=10-20")) .to_request(); - let response = test::call_service(&mut srv, request).await; + let response = test::call_service(&srv, request).await; assert_eq!(response.status(), StatusCode::PARTIAL_CONTENT); // Invalid range header @@ -408,7 +409,7 @@ mod tests { .uri("/t%65st/Cargo.toml") .insert_header((header::RANGE, "bytes=1-0")) .to_request(); - let response = test::call_service(&mut srv, request).await; + let response = test::call_service(&srv, request).await; assert_eq!(response.status(), StatusCode::RANGE_NOT_SATISFIABLE); } @@ -495,14 +496,14 @@ mod tests { #[actix_rt::test] async fn test_static_files_with_spaces() { - let mut srv = test::init_service( + let srv = test::init_service( App::new().service(Files::new("/", ".").index_file("Cargo.toml")), ) .await; let request = TestRequest::get() .uri("/tests/test%20space.binary") .to_request(); - let response = test::call_service(&mut srv, request).await; + let response = test::call_service(&srv, request).await; assert_eq!(response.status(), StatusCode::OK); let bytes = test::read_body(response).await; @@ -512,28 +513,28 @@ mod tests { #[actix_rt::test] async fn test_files_not_allowed() { - let mut srv = test::init_service(App::new().service(Files::new("/", "."))).await; + let srv = test::init_service(App::new().service(Files::new("/", "."))).await; let req = TestRequest::default() .uri("/Cargo.toml") .method(Method::POST) .to_request(); - let resp = test::call_service(&mut srv, req).await; + let resp = test::call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED); - let mut srv = test::init_service(App::new().service(Files::new("/", "."))).await; + let srv = test::init_service(App::new().service(Files::new("/", "."))).await; let req = TestRequest::default() .method(Method::PUT) .uri("/Cargo.toml") .to_request(); - let resp = test::call_service(&mut srv, req).await; + let resp = test::call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED); } #[actix_rt::test] async fn test_files_guards() { - let mut srv = test::init_service( + let srv = test::init_service( App::new().service(Files::new("/", ".").use_guards(guard::Post())), ) .await; @@ -543,13 +544,13 @@ mod tests { .method(Method::POST) .to_request(); - let resp = test::call_service(&mut srv, req).await; + let resp = test::call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::OK); } #[actix_rt::test] async fn test_named_file_content_encoding() { - let mut srv = test::init_service(App::new().wrap(Compress::default()).service( + let srv = test::init_service(App::new().wrap(Compress::default()).service( web::resource("/").to(|| async { NamedFile::open("Cargo.toml") .unwrap() @@ -562,14 +563,14 @@ mod tests { .uri("/") .insert_header((header::ACCEPT_ENCODING, "gzip")) .to_request(); - let res = test::call_service(&mut srv, request).await; + let res = test::call_service(&srv, request).await; assert_eq!(res.status(), StatusCode::OK); assert!(!res.headers().contains_key(header::CONTENT_ENCODING)); } #[actix_rt::test] async fn test_named_file_content_encoding_gzip() { - let mut srv = test::init_service(App::new().wrap(Compress::default()).service( + let srv = test::init_service(App::new().wrap(Compress::default()).service( web::resource("/").to(|| async { NamedFile::open("Cargo.toml") .unwrap() @@ -582,7 +583,7 @@ mod tests { .uri("/") .insert_header((header::ACCEPT_ENCODING, "gzip")) .to_request(); - let res = test::call_service(&mut srv, request).await; + let res = test::call_service(&srv, request).await; assert_eq!(res.status(), StatusCode::OK); assert_eq!( res.headers() @@ -604,27 +605,27 @@ mod tests { #[actix_rt::test] async fn test_static_files() { - let mut srv = test::init_service( + let srv = test::init_service( App::new().service(Files::new("/", ".").show_files_listing()), ) .await; let req = TestRequest::with_uri("/missing").to_request(); - let resp = test::call_service(&mut srv, req).await; + let resp = test::call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::NOT_FOUND); - let mut srv = test::init_service(App::new().service(Files::new("/", "."))).await; + let srv = test::init_service(App::new().service(Files::new("/", "."))).await; let req = TestRequest::default().to_request(); - let resp = test::call_service(&mut srv, req).await; + let resp = test::call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::NOT_FOUND); - let mut srv = test::init_service( + let srv = test::init_service( App::new().service(Files::new("/", ".").show_files_listing()), ) .await; let req = TestRequest::with_uri("/tests").to_request(); - let resp = test::call_service(&mut srv, req).await; + let resp = test::call_service(&srv, req).await; assert_eq!( resp.headers().get(header::CONTENT_TYPE).unwrap(), "text/html; charset=utf-8" @@ -637,16 +638,16 @@ mod tests { #[actix_rt::test] async fn test_redirect_to_slash_directory() { // should not redirect if no index - let mut srv = test::init_service( + let srv = test::init_service( App::new().service(Files::new("/", ".").redirect_to_slash_directory()), ) .await; let req = TestRequest::with_uri("/tests").to_request(); - let resp = test::call_service(&mut srv, req).await; + let resp = test::call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::NOT_FOUND); // should redirect if index present - let mut srv = test::init_service( + let srv = test::init_service( App::new().service( Files::new("/", ".") .index_file("test.png") @@ -655,12 +656,12 @@ mod tests { ) .await; let req = TestRequest::with_uri("/tests").to_request(); - let resp = test::call_service(&mut srv, req).await; + let resp = test::call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::FOUND); // should not redirect if the path is wrong let req = TestRequest::with_uri("/not_existing").to_request(); - let resp = test::call_service(&mut srv, req).await; + let resp = test::call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::NOT_FOUND); } @@ -672,7 +673,7 @@ mod tests { #[actix_rt::test] async fn test_default_handler_file_missing() { - let mut st = Files::new("/", ".") + let st = Files::new("/", ".") .default_handler(|req: ServiceRequest| { ok(req.into_response(HttpResponse::Ok().body("default content"))) }) @@ -681,7 +682,7 @@ mod tests { .unwrap(); let req = TestRequest::with_uri("/missing").to_srv_request(); - let resp = test::call_service(&mut st, req).await; + let resp = test::call_service(&st, req).await; assert_eq!(resp.status(), StatusCode::OK); let bytes = test::read_body(resp).await; assert_eq!(bytes, web::Bytes::from_static(b"default content")); @@ -750,54 +751,49 @@ mod tests { // ); // } - // #[actix_rt::test] - // fn integration_serve_index() { - // let mut srv = test::TestServer::with_factory(|| { - // App::new().handler( - // "test", - // Files::new(".").index_file("Cargo.toml"), - // ) - // }); + #[actix_rt::test] + async fn integration_serve_index() { + let srv = test::init_service( + App::new().service(Files::new("test", ".").index_file("Cargo.toml")), + ) + .await; - // let request = srv.get().uri(srv.url("/test")).finish().unwrap(); - // let response = srv.execute(request.send()).unwrap(); - // assert_eq!(response.status(), StatusCode::OK); - // let bytes = srv.execute(response.body()).unwrap(); - // let data = Bytes::from(fs::read("Cargo.toml").unwrap()); - // assert_eq!(bytes, data); + let req = TestRequest::get().uri("/test").to_request(); + let res = test::call_service(&srv, req).await; + assert_eq!(res.status(), StatusCode::OK); - // let request = srv.get().uri(srv.url("/test/")).finish().unwrap(); - // let response = srv.execute(request.send()).unwrap(); - // assert_eq!(response.status(), StatusCode::OK); - // let bytes = srv.execute(response.body()).unwrap(); - // let data = Bytes::from(fs::read("Cargo.toml").unwrap()); - // assert_eq!(bytes, data); + let bytes = test::read_body(res).await; - // // nonexistent index file - // let request = srv.get().uri(srv.url("/test/unknown")).finish().unwrap(); - // let response = srv.execute(request.send()).unwrap(); - // assert_eq!(response.status(), StatusCode::NOT_FOUND); + let data = Bytes::from(fs::read("Cargo.toml").unwrap()); + assert_eq!(bytes, data); - // let request = srv.get().uri(srv.url("/test/unknown/")).finish().unwrap(); - // let response = srv.execute(request.send()).unwrap(); - // assert_eq!(response.status(), StatusCode::NOT_FOUND); - // } + let req = TestRequest::get().uri("/test/").to_request(); + let res = test::call_service(&srv, req).await; + assert_eq!(res.status(), StatusCode::OK); - // #[actix_rt::test] - // fn integration_percent_encoded() { - // let mut srv = test::TestServer::with_factory(|| { - // App::new().handler( - // "test", - // Files::new(".").index_file("Cargo.toml"), - // ) - // }); + let bytes = test::read_body(res).await; + let data = Bytes::from(fs::read("Cargo.toml").unwrap()); + assert_eq!(bytes, data); - // let request = srv - // .get() - // .uri(srv.url("/test/%43argo.toml")) - // .finish() - // .unwrap(); - // let response = srv.execute(request.send()).unwrap(); - // assert_eq!(response.status(), StatusCode::OK); - // } + // nonexistent index file + let req = TestRequest::get().uri("/test/unknown").to_request(); + let res = test::call_service(&srv, req).await; + assert_eq!(res.status(), StatusCode::NOT_FOUND); + + let req = TestRequest::get().uri("/test/unknown/").to_request(); + let res = test::call_service(&srv, req).await; + assert_eq!(res.status(), StatusCode::NOT_FOUND); + } + + #[actix_rt::test] + async fn integration_percent_encoded() { + let srv = test::init_service( + App::new().service(Files::new("test", ".").index_file("Cargo.toml")), + ) + .await; + + let req = TestRequest::get().uri("/test/%43argo.toml").to_request(); + let res = test::call_service(&srv, req).await; + assert_eq!(res.status(), StatusCode::OK); + } } diff --git a/actix-files/src/service.rs b/actix-files/src/service.rs index 25e285efc..a9822f486 100644 --- a/actix-files/src/service.rs +++ b/actix-files/src/service.rs @@ -1,9 +1,4 @@ -use std::{ - fmt, io, - path::PathBuf, - rc::Rc, - task::{Context, Poll}, -}; +use std::{fmt, io, path::PathBuf, rc::Rc, task::Poll}; use actix_service::Service; use actix_web::{ @@ -40,10 +35,10 @@ type FilesServiceFuture = Either< >; impl FilesService { - fn handle_err(&mut self, e: io::Error, req: ServiceRequest) -> FilesServiceFuture { + fn handle_err(&self, e: io::Error, req: ServiceRequest) -> FilesServiceFuture { log::debug!("Failed to handle {}: {}", req.path(), e); - if let Some(ref mut default) = self.default { + if let Some(ref default) = self.default { Either::Right(default.call(req)) } else { Either::Left(ok(req.error_response(e))) @@ -62,11 +57,9 @@ impl Service for FilesService { type Error = Error; type Future = FilesServiceFuture; - fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll> { - Poll::Ready(Ok(())) - } + actix_service::always_ready!(); - fn call(&mut self, req: ServiceRequest) -> Self::Future { + fn call(&self, req: ServiceRequest) -> Self::Future { let is_method_valid = if let Some(guard) = &self.guards { // execute user defined guards (**guard).check(req.head()) diff --git a/actix-files/tests/encoding.rs b/actix-files/tests/encoding.rs index d7e01b305..6cfa3a7f7 100644 --- a/actix-files/tests/encoding.rs +++ b/actix-files/tests/encoding.rs @@ -11,11 +11,10 @@ use actix_web::{ #[actix_rt::test] async fn test_utf8_file_contents() { // use default ISO-8859-1 encoding - let mut srv = - test::init_service(App::new().service(Files::new("/", "./tests"))).await; + let srv = test::init_service(App::new().service(Files::new("/", "./tests"))).await; let req = TestRequest::with_uri("/utf8.txt").to_request(); - let res = test::call_service(&mut srv, req).await; + let res = test::call_service(&srv, req).await; assert_eq!(res.status(), StatusCode::OK); assert_eq!( @@ -24,13 +23,13 @@ async fn test_utf8_file_contents() { ); // prefer UTF-8 encoding - let mut srv = test::init_service( + let srv = test::init_service( App::new().service(Files::new("/", "./tests").prefer_utf8(true)), ) .await; let req = TestRequest::with_uri("/utf8.txt").to_request(); - let res = test::call_service(&mut srv, req).await; + let res = test::call_service(&srv, req).await; assert_eq!(res.status(), StatusCode::OK); assert_eq!( diff --git a/actix-http-test/Cargo.toml b/actix-http-test/Cargo.toml index bc71f967a..0a804e109 100644 --- a/actix-http-test/Cargo.toml +++ b/actix-http-test/Cargo.toml @@ -29,12 +29,12 @@ default = [] openssl = ["open-ssl", "awc/openssl"] [dependencies] -actix-service = "2.0.0-beta.3" +actix-service = "2.0.0-beta.4" actix-codec = "0.4.0-beta.1" -actix-tls = "3.0.0-beta.2" -actix-utils = "3.0.0-beta.1" -actix-rt = "=2.0.0-beta.2" -actix-server = "2.0.0-beta.2" +actix-tls = "3.0.0-beta.3" +actix-utils = "3.0.0-beta.2" +actix-rt = "2" +actix-server = "2.0.0-beta.3" awc = "3.0.0-beta.1" base64 = "0.13" diff --git a/actix-http-test/src/lib.rs b/actix-http-test/src/lib.rs index 4fd74d6eb..2958b7f59 100644 --- a/actix-http-test/src/lib.rs +++ b/actix-http-test/src/lib.rs @@ -60,7 +60,7 @@ pub async fn test_server_with_addr>( // run server in separate thread thread::spawn(move || { - let sys = System::new("actix-test-server"); + let sys = System::new(); let local_addr = tcp.local_addr().unwrap(); let srv = Server::build() @@ -69,7 +69,7 @@ pub async fn test_server_with_addr>( .disable_signals(); sys.block_on(async { - srv.start(); + srv.run(); tx.send((System::current(), local_addr)).unwrap(); }); @@ -106,7 +106,6 @@ pub async fn test_server_with_addr>( Client::builder().connector(connector).finish() }; - actix_tls::connect::start_default_resolver().await.unwrap(); TestServer { addr, diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 73339974d..52980fb5d 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -16,7 +16,9 @@ * `Extensions::insert` returns Option of replaced item. [#1904] * Remove `HttpResponseBuilder::json2()` and make `HttpResponseBuilder::json()` take a value by reference. [#1903] -* Simplify `BlockingError` type to a struct. It's only triggered with blocking thread pool is dead. [#1957] +* `client::error::ConnectError` Resolver variant contains `Box` type [#1905] +* `client::ConnectorConfig` default timeout changed to 5 seconds. [#1905] +* Simplify `BlockingError` type to a struct. It's only triggered with blocking thread pool is dead. [#1957] ### Removed * `ResponseBuilder::set`; use `ResponseBuilder::insert_header`. [#1869] @@ -29,6 +31,7 @@ [#1894]: https://github.com/actix/actix-web/pull/1894 [#1903]: https://github.com/actix/actix-web/pull/1903 [#1904]: https://github.com/actix/actix-web/pull/1904 +[#1905]: https://github.com/actix/actix-web/pull/1905 [#1912]: https://github.com/actix/actix-web/pull/1912 [#1957]: https://github.com/actix/actix-web/pull/1957 diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 8f638c1d7..f55dc3b69 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -40,12 +40,12 @@ secure-cookies = ["cookie/secure"] actors = ["actix"] [dependencies] -actix-service = "2.0.0-beta.3" +actix-service = "2.0.0-beta.4" actix-codec = "0.4.0-beta.1" -actix-utils = "3.0.0-beta.1" -actix-rt = "=2.0.0-beta.2" +actix-utils = "3.0.0-beta.2" +actix-rt = "2" actix-tls = "3.0.0-beta.2" -actix = { version = "0.11.0-beta.1", optional = true } +actix = { version = "0.11.0-beta.2", default-features = false, optional = true } base64 = "0.13" bitflags = "1.2" @@ -55,9 +55,9 @@ cookie = { version = "0.14.1", features = ["percent-encode"] } derive_more = "0.99.5" either = "1.5.3" encoding_rs = "0.8" -futures-channel = { version = "0.3.7", default-features = false } -futures-core = { version = "0.3.7", default-features = false } -futures-util = { version = "0.3.7", default-features = false, features = ["sink"] } +futures-channel = { version = "0.3.7", default-features = false, features = ["alloc"] } +futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] } +futures-util = { version = "0.3.7", default-features = false, features = ["alloc", "sink"] } ahash = "0.6" h2 = "0.3.0" http = "0.2.2" @@ -85,7 +85,7 @@ brotli2 = { version="0.3.2", optional = true } flate2 = { version = "1.0.13", optional = true } [dev-dependencies] -actix-server = "2.0.0-beta.2" +actix-server = "2.0.0-beta.3" actix-http-test = { version = "3.0.0-beta.1", features = ["openssl"] } actix-tls = { version = "3.0.0-beta.2", features = ["openssl"] } criterion = "0.3" diff --git a/actix-http/src/client/config.rs b/actix-http/src/client/config.rs index 369732964..fad902d04 100644 --- a/actix-http/src/client/config.rs +++ b/actix-http/src/client/config.rs @@ -18,7 +18,7 @@ pub(crate) struct ConnectorConfig { impl Default for ConnectorConfig { fn default() -> Self { Self { - timeout: Duration::from_secs(1), + timeout: Duration::from_secs(5), conn_lifetime: Duration::from_secs(75), conn_keep_alive: Duration::from_secs(15), disconnect_timeout: Some(Duration::from_millis(3000)), diff --git a/actix-http/src/client/connection.rs b/actix-http/src/client/connection.rs index d22f2c7ac..26d392120 100644 --- a/actix-http/src/client/connection.rs +++ b/actix-http/src/client/connection.rs @@ -5,7 +5,8 @@ use std::{fmt, io, time}; use actix_codec::{AsyncRead, AsyncWrite, Framed, ReadBuf}; use bytes::Bytes; -use futures_util::future::{err, Either, FutureExt, LocalBoxFuture, Ready}; +use futures_core::future::LocalBoxFuture; +use futures_util::future::{err, Either, FutureExt, Ready}; use h2::client::SendRequest; use pin_project::pin_project; diff --git a/actix-http/src/client/connector.rs b/actix-http/src/client/connector.rs index 326a2fc60..425ee0f70 100644 --- a/actix-http/src/client/connector.rs +++ b/actix-http/src/client/connector.rs @@ -100,9 +100,9 @@ impl Connector<(), ()> { fn build_ssl(protocols: Vec>) -> SslConnector { let mut config = ClientConfig::new(); config.set_protocols(&protocols); - config - .root_store - .add_server_trust_anchors(&actix_tls::accept::rustls::TLS_SERVER_ROOTS); + config.root_store.add_server_trust_anchors( + &actix_tls::connect::ssl::rustls::TLS_SERVER_ROOTS, + ); SslConnector::Rustls(Arc::new(config)) } @@ -392,11 +392,11 @@ mod connect_impl { Ready, ConnectError>>, >; - fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { + fn poll_ready(&self, cx: &mut Context<'_>) -> Poll> { self.tcp_pool.poll_ready(cx) } - fn call(&mut self, req: Connect) -> Self::Future { + fn call(&self, req: Connect) -> Self::Future { match req.uri.scheme_str() { Some("https") | Some("wss") => { Either::Right(err(ConnectError::SslIsNotSupported)) @@ -460,11 +460,11 @@ mod connect_impl { InnerConnectorResponseB, >; - fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { + fn poll_ready(&self, cx: &mut Context<'_>) -> Poll> { self.tcp_pool.poll_ready(cx) } - fn call(&mut self, req: Connect) -> Self::Future { + fn call(&self, req: Connect) -> Self::Future { match req.uri.scheme_str() { Some("https") | Some("wss") => Either::Right(InnerConnectorResponseB { fut: self.ssl_pool.call(req), diff --git a/actix-http/src/client/error.rs b/actix-http/src/client/error.rs index a5f1b2e8e..8d609f546 100644 --- a/actix-http/src/client/error.rs +++ b/actix-http/src/client/error.rs @@ -1,6 +1,5 @@ use std::io; -use actix_tls::connect::resolver::ResolveError; use derive_more::{Display, From}; #[cfg(feature = "openssl")] @@ -23,7 +22,7 @@ pub enum ConnectError { /// Failed to resolve the hostname #[display(fmt = "Failed resolving hostname: {}", _0)] - Resolver(ResolveError), + Resolver(Box), /// No dns records #[display(fmt = "No dns records found for the input")] diff --git a/actix-http/src/client/pool.rs b/actix-http/src/client/pool.rs index af862cdd7..1eebef53b 100644 --- a/actix-http/src/client/pool.rs +++ b/actix-http/src/client/pool.rs @@ -13,7 +13,8 @@ use actix_utils::task::LocalWaker; use ahash::AHashMap; use bytes::Bytes; use futures_channel::oneshot; -use futures_util::future::{poll_fn, FutureExt, LocalBoxFuture}; +use futures_core::future::LocalBoxFuture; +use futures_util::future::{poll_fn, FutureExt}; use h2::client::{Connection, SendRequest}; use http::uri::Authority; use indexmap::IndexSet; @@ -45,7 +46,7 @@ impl From for Key { } /// Connections pool -pub(crate) struct ConnectionPool(Rc>, Rc>>); +pub(crate) struct ConnectionPool(Rc, Rc>>); impl ConnectionPool where @@ -53,7 +54,7 @@ where T: Service + 'static, { pub(crate) fn new(connector: T, config: ConnectorConfig) -> Self { - let connector_rc = Rc::new(RefCell::new(connector)); + let connector_rc = Rc::new(connector); let inner_rc = Rc::new(RefCell::new(Inner { config, acquired: 0, @@ -98,12 +99,12 @@ where type Error = ConnectError; type Future = LocalBoxFuture<'static, Result, ConnectError>>; - fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { + fn poll_ready(&self, cx: &mut Context<'_>) -> Poll> { self.0.poll_ready(cx) } - fn call(&mut self, req: Connect) -> Self::Future { - let mut connector = self.0.clone(); + fn call(&self, req: Connect) -> Self::Future { + let connector = self.0.clone(); let inner = self.1.clone(); let fut = async move { @@ -325,7 +326,7 @@ where { if let Some(timeout) = self.config.disconnect_timeout { if let ConnectionType::H1(io) = conn.io { - actix_rt::spawn(CloseConnection::new(io, timeout)) + actix_rt::spawn(CloseConnection::new(io, timeout)); } } } else { @@ -340,7 +341,7 @@ where if let ConnectionType::H1(io) = io { actix_rt::spawn(CloseConnection::new( io, timeout, - )) + )); } } continue; @@ -372,7 +373,7 @@ where self.acquired -= 1; if let Some(timeout) = self.config.disconnect_timeout { if let ConnectionType::H1(io) = io { - actix_rt::spawn(CloseConnection::new(io, timeout)) + actix_rt::spawn(CloseConnection::new(io, timeout)); } } self.check_availability(); @@ -428,7 +429,7 @@ struct ConnectorPoolSupport where Io: AsyncRead + AsyncWrite + Unpin + 'static, { - connector: T, + connector: Rc, inner: Rc>>, } @@ -535,7 +536,7 @@ where rx: Some(rx), inner: Some(inner), config, - }) + }); } } diff --git a/actix-http/src/h1/dispatcher.rs b/actix-http/src/h1/dispatcher.rs index eb5f48105..90a5f0e06 100644 --- a/actix-http/src/h1/dispatcher.rs +++ b/actix-http/src/h1/dispatcher.rs @@ -1,5 +1,4 @@ use std::{ - cell::RefCell, collections::VecDeque, fmt, future::Future, @@ -91,7 +90,7 @@ where U: Service<(Request, Framed), Response = ()>, U::Error: fmt::Display, { - flow: Rc>>, + flow: Rc>, on_connect_data: OnConnectData, flags: Flags, peer_addr: Option, @@ -177,7 +176,7 @@ where pub(crate) fn new( stream: T, config: ServiceConfig, - services: Rc>>, + services: Rc>, on_connect_data: OnConnectData, peer_addr: Option, ) -> Self { @@ -200,7 +199,7 @@ where config: ServiceConfig, read_buf: BytesMut, timeout: Option, - services: Rc>>, + services: Rc>, on_connect_data: OnConnectData, peer_addr: Option, ) -> Self { @@ -377,7 +376,7 @@ where Poll::Ready(Ok(req)) => { self.as_mut().send_continue(); this = self.as_mut().project(); - let fut = this.flow.borrow_mut().service.call(req); + let fut = this.flow.service.call(req); this.state.set(State::ServiceCall(fut)); continue; } @@ -467,12 +466,12 @@ where if req.head().expect() { // set dispatcher state so the future is pinned. let mut this = self.as_mut().project(); - let task = this.flow.borrow_mut().expect.call(req); + let task = this.flow.expect.call(req); this.state.set(State::ExpectCall(task)); } else { // the same as above. let mut this = self.as_mut().project(); - let task = this.flow.borrow_mut().service.call(req); + let task = this.flow.service.call(req); this.state.set(State::ServiceCall(task)); }; @@ -485,7 +484,7 @@ where Poll::Ready(Ok(req)) => { self.as_mut().send_continue(); let mut this = self.as_mut().project(); - let task = this.flow.borrow_mut().service.call(req); + let task = this.flow.service.call(req); this.state.set(State::ServiceCall(task)); continue; } @@ -556,9 +555,7 @@ where // merge on_connect_ext data into request extensions this.on_connect_data.merge_into(&mut req); - if pl == MessageType::Stream - && this.flow.borrow().upgrade.is_some() - { + if pl == MessageType::Stream && this.flow.upgrade.is_some() { this.messages.push_back(DispatcherMessage::Upgrade(req)); break; } @@ -811,9 +808,8 @@ where let framed = Framed::from_parts(parts); let upgrade = inner_p .flow - .borrow_mut() .upgrade - .take() + .as_ref() .unwrap() .call((req, framed)); self.as_mut() diff --git a/actix-http/src/h1/expect.rs b/actix-http/src/h1/expect.rs index c3e4ccdaa..65856edf6 100644 --- a/actix-http/src/h1/expect.rs +++ b/actix-http/src/h1/expect.rs @@ -1,4 +1,4 @@ -use std::task::{Context, Poll}; +use std::task::Poll; use actix_service::{Service, ServiceFactory}; use futures_util::future::{ready, Ready}; @@ -26,11 +26,9 @@ impl Service for ExpectHandler { type Error = Error; type Future = Ready>; - fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll> { - Poll::Ready(Ok(())) - } + actix_service::always_ready!(); - fn call(&mut self, req: Request) -> Self::Future { + fn call(&self, req: Request) -> Self::Future { ready(Ok(req)) // TODO: add some way to trigger error // Err(error::ErrorExpectationFailed("test")) diff --git a/actix-http/src/h1/service.rs b/actix-http/src/h1/service.rs index aed700eed..b79453ebd 100644 --- a/actix-http/src/h1/service.rs +++ b/actix-http/src/h1/service.rs @@ -1,4 +1,3 @@ -use std::cell::RefCell; use std::future::Future; use std::marker::PhantomData; use std::pin::Pin; @@ -367,7 +366,7 @@ where X: Service, U: Service<(Request, Framed)>, { - flow: Rc>>, + flow: Rc>, on_connect_ext: Option>>, cfg: ServiceConfig, _phantom: PhantomData, @@ -417,9 +416,9 @@ where type Error = DispatchError; type Future = Dispatcher; - fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { - let mut flow = self.flow.borrow_mut(); - let ready = flow + fn poll_ready(&self, cx: &mut Context<'_>) -> Poll> { + let ready = self + .flow .expect .poll_ready(cx) .map_err(|e| { @@ -429,7 +428,8 @@ where })? .is_ready(); - let ready = flow + let ready = self + .flow .service .poll_ready(cx) .map_err(|e| { @@ -440,7 +440,7 @@ where .is_ready() && ready; - let ready = if let Some(ref mut upg) = flow.upgrade { + let ready = if let Some(ref upg) = self.flow.upgrade { upg.poll_ready(cx) .map_err(|e| { let e = e.into(); @@ -460,7 +460,7 @@ where } } - fn call(&mut self, (io, addr): (T, Option)) -> Self::Future { + fn call(&self, (io, addr): (T, Option)) -> Self::Future { let on_connect_data = OnConnectData::from_io(&io, self.on_connect_ext.as_deref()); diff --git a/actix-http/src/h1/upgrade.rs b/actix-http/src/h1/upgrade.rs index 007aff1bf..5e24d84e3 100644 --- a/actix-http/src/h1/upgrade.rs +++ b/actix-http/src/h1/upgrade.rs @@ -1,4 +1,4 @@ -use std::task::{Context, Poll}; +use std::task::Poll; use actix_codec::Framed; use actix_service::{Service, ServiceFactory}; @@ -28,11 +28,9 @@ impl Service<(Request, Framed)> for UpgradeHandler { type Error = Error; type Future = Ready>; - fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll> { - Poll::Ready(Ok(())) - } + actix_service::always_ready!(); - fn call(&mut self, _: (Request, Framed)) -> Self::Future { + fn call(&self, _: (Request, Framed)) -> Self::Future { ready(Ok(())) } } diff --git a/actix-http/src/h2/dispatcher.rs b/actix-http/src/h2/dispatcher.rs index 959c34f13..5ccd2a9d1 100644 --- a/actix-http/src/h2/dispatcher.rs +++ b/actix-http/src/h2/dispatcher.rs @@ -1,4 +1,3 @@ -use std::cell::RefCell; use std::future::Future; use std::marker::PhantomData; use std::net; @@ -37,7 +36,7 @@ where S: Service, B: MessageBody, { - flow: Rc>>, + flow: Rc>, connection: Connection, on_connect_data: OnConnectData, config: ServiceConfig, @@ -56,7 +55,7 @@ where B: MessageBody, { pub(crate) fn new( - services: Rc>>, + flow: Rc>, connection: Connection, on_connect_data: OnConnectData, config: ServiceConfig, @@ -80,7 +79,7 @@ where }; Dispatcher { - flow: services, + flow, config, peer_addr, connection, @@ -138,7 +137,7 @@ where let svc = ServiceResponse:: { state: ServiceResponseState::ServiceCall( - this.flow.borrow_mut().service.call(req), + this.flow.service.call(req), Some(res), ), config: this.config.clone(), diff --git a/actix-http/src/h2/service.rs b/actix-http/src/h2/service.rs index 36f7dc311..36c76b17c 100644 --- a/actix-http/src/h2/service.rs +++ b/actix-http/src/h2/service.rs @@ -1,4 +1,3 @@ -use std::cell::RefCell; use std::future::Future; use std::marker::PhantomData; use std::pin::Pin; @@ -249,7 +248,7 @@ pub struct H2ServiceHandler where S: Service, { - flow: Rc>>, + flow: Rc>, cfg: ServiceConfig, on_connect_ext: Option>>, _phantom: PhantomData, @@ -290,15 +289,15 @@ where type Error = DispatchError; type Future = H2ServiceHandlerResponse; - fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { - self.flow.borrow_mut().service.poll_ready(cx).map_err(|e| { + fn poll_ready(&self, cx: &mut Context<'_>) -> Poll> { + self.flow.service.poll_ready(cx).map_err(|e| { let e = e.into(); error!("Service readiness error: {:?}", e); DispatchError::Service(e) }) } - fn call(&mut self, (io, addr): (T, Option)) -> Self::Future { + fn call(&self, (io, addr): (T, Option)) -> Self::Future { let on_connect_data = OnConnectData::from_io(&io, self.on_connect_ext.as_deref()); @@ -321,7 +320,7 @@ where { Incoming(Dispatcher), Handshake( - Option>>>, + Option>>, Option, Option, OnConnectData, diff --git a/actix-http/src/header/into_value.rs b/actix-http/src/header/into_value.rs index 4b1e6cbbd..4ba58e726 100644 --- a/actix-http/src/header/into_value.rs +++ b/actix-http/src/header/into_value.rs @@ -126,6 +126,6 @@ impl IntoHeaderValue for Mime { #[inline] fn try_into_value(self) -> Result { - HeaderValue::try_from(format!("{}", self)) + HeaderValue::from_str(self.as_ref()) } } diff --git a/actix-http/src/service.rs b/actix-http/src/service.rs index 357ac4c53..f0121db97 100644 --- a/actix-http/src/service.rs +++ b/actix-http/src/service.rs @@ -1,4 +1,3 @@ -use std::cell::RefCell; use std::marker::PhantomData; use std::pin::Pin; use std::task::{Context, Poll}; @@ -440,7 +439,7 @@ where X: Service, U: Service<(Request, Framed)>, { - flow: Rc>>, + flow: Rc>, cfg: ServiceConfig, on_connect_ext: Option>>, _phantom: PhantomData, @@ -454,12 +453,12 @@ pub(super) struct HttpFlow { } impl HttpFlow { - pub(super) fn new(service: S, expect: X, upgrade: Option) -> Rc> { - Rc::new(RefCell::new(Self { + pub(super) fn new(service: S, expect: X, upgrade: Option) -> Rc { + Rc::new(Self { service, expect, upgrade, - })) + }) } } @@ -509,9 +508,9 @@ where type Error = DispatchError; type Future = HttpServiceHandlerResponse; - fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { - let mut flow = self.flow.borrow_mut(); - let ready = flow + fn poll_ready(&self, cx: &mut Context<'_>) -> Poll> { + let ready = self + .flow .expect .poll_ready(cx) .map_err(|e| { @@ -521,7 +520,8 @@ where })? .is_ready(); - let ready = flow + let ready = self + .flow .service .poll_ready(cx) .map_err(|e| { @@ -532,7 +532,7 @@ where .is_ready() && ready; - let ready = if let Some(ref mut upg) = flow.upgrade { + let ready = if let Some(ref upg) = self.flow.upgrade { upg.poll_ready(cx) .map_err(|e| { let e = e.into(); @@ -553,7 +553,7 @@ where } fn call( - &mut self, + &self, (io, proto, peer_addr): (T, Protocol, Option), ) -> Self::Future { let on_connect_data = @@ -604,7 +604,7 @@ where Option<( Handshake, ServiceConfig, - Rc>>, + Rc>, OnConnectData, Option, )>, diff --git a/actix-http/tests/test_ws.rs b/actix-http/tests/test_ws.rs index 976fc9164..7ed9b0df1 100644 --- a/actix-http/tests/test_ws.rs +++ b/actix-http/tests/test_ws.rs @@ -21,7 +21,7 @@ impl WsService { WsService(Arc::new(Mutex::new((PhantomData, Cell::new(false))))) } - fn set_polled(&mut self) { + fn set_polled(&self) { *self.0.lock().unwrap().1.get_mut() = true; } @@ -44,15 +44,12 @@ where type Error = Error; type Future = Pin>>>; - fn poll_ready(&mut self, _ctx: &mut Context<'_>) -> Poll> { + fn poll_ready(&self, _ctx: &mut Context<'_>) -> Poll> { self.set_polled(); Poll::Ready(Ok(())) } - fn call( - &mut self, - (req, mut framed): (Request, Framed), - ) -> Self::Future { + fn call(&self, (req, mut framed): (Request, Framed)) -> Self::Future { let fut = async move { let res = ws::handshake(req.head()).unwrap().message_body(()); diff --git a/actix-multipart/Cargo.toml b/actix-multipart/Cargo.toml index 60620bce7..beecd2e9d 100644 --- a/actix-multipart/Cargo.toml +++ b/actix-multipart/Cargo.toml @@ -17,7 +17,7 @@ path = "src/lib.rs" [dependencies] actix-web = { version = "4.0.0-beta.1", default-features = false } -actix-utils = "3.0.0-beta.1" +actix-utils = "3.0.0-beta.2" bytes = "1" derive_more = "0.99.5" @@ -28,5 +28,5 @@ mime = "0.3" twoway = "0.2" [dev-dependencies] -actix-rt = "=2.0.0-beta.2" +actix-rt = "2" actix-http = "3.0.0-beta.1" diff --git a/actix-web-actors/Cargo.toml b/actix-web-actors/Cargo.toml index 34bf713ae..c6fd1c233 100644 --- a/actix-web-actors/Cargo.toml +++ b/actix-web-actors/Cargo.toml @@ -16,7 +16,7 @@ name = "actix_web_actors" path = "src/lib.rs" [dependencies] -actix = "0.11.0-beta.1" +actix = { version = "0.11.0-beta.2", default-features = false } actix-codec = "0.4.0-beta.1" actix-http = "3.0.0-beta.1" actix-web = { version = "4.0.0-beta.1", default-features = false } @@ -28,6 +28,6 @@ pin-project = "1.0.0" tokio = { version = "1", features = ["sync"] } [dev-dependencies] -actix-rt = "=2.0.0-beta.2" +actix-rt = "2" env_logger = "0.7" futures-util = { version = "0.3.7", default-features = false } diff --git a/actix-web-actors/src/context.rs b/actix-web-actors/src/context.rs index 2dd93c727..afe17cd21 100644 --- a/actix-web-actors/src/context.rs +++ b/actix-web-actors/src/context.rs @@ -233,14 +233,13 @@ mod tests { #[actix_rt::test] async fn test_default_resource() { - let mut srv = - init_service(App::new().service(web::resource("/test").to(|| { - HttpResponse::Ok().streaming(HttpContext::create(MyActor { count: 0 })) - }))) - .await; + let srv = init_service(App::new().service(web::resource("/test").to(|| { + HttpResponse::Ok().streaming(HttpContext::create(MyActor { count: 0 })) + }))) + .await; let req = TestRequest::with_uri("/test").to_request(); - let resp = call_service(&mut srv, req).await; + let resp = call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::OK); let body = read_body(resp).await; diff --git a/actix-web-codegen/Cargo.toml b/actix-web-codegen/Cargo.toml index 70ab55681..04bd10421 100644 --- a/actix-web-codegen/Cargo.toml +++ b/actix-web-codegen/Cargo.toml @@ -19,7 +19,7 @@ syn = { version = "1", features = ["full", "parsing"] } proc-macro2 = "1" [dev-dependencies] -actix-rt = "=2.0.0-beta.2" +actix-rt = "2" actix-web = "4.0.0-beta.1" futures-util = { version = "0.3.7", default-features = false } trybuild = "1" diff --git a/actix-web-codegen/src/lib.rs b/actix-web-codegen/src/lib.rs index 50e5be712..ede1e0005 100644 --- a/actix-web-codegen/src/lib.rs +++ b/actix-web-codegen/src/lib.rs @@ -175,7 +175,6 @@ pub fn main(_: TokenStream, item: TokenStream) -> TokenStream { let vis = &input.vis; let sig = &mut input.sig; let body = &input.block; - let name = &sig.ident; if sig.asyncness.is_none() { return syn::Error::new_spanned(sig.fn_token, "only async fn is supported") @@ -188,7 +187,7 @@ pub fn main(_: TokenStream, item: TokenStream) -> TokenStream { (quote! { #(#attrs)* #vis #sig { - actix_web::rt::System::new(stringify!(#name)) + actix_web::rt::System::new() .block_on(async move { #body }) } }) diff --git a/actix-web-codegen/tests/test_macro.rs b/actix-web-codegen/tests/test_macro.rs index 389d09c82..252be1b95 100644 --- a/actix-web-codegen/tests/test_macro.rs +++ b/actix-web-codegen/tests/test_macro.rs @@ -1,5 +1,4 @@ use std::future::Future; -use std::pin::Pin; use std::task::{Context, Poll}; use actix_web::dev::{Service, ServiceRequest, ServiceResponse, Transform}; @@ -8,7 +7,7 @@ use actix_web::{http, test, web::Path, App, Error, HttpResponse, Responder}; use actix_web_codegen::{ connect, delete, get, head, options, patch, post, put, route, trace, }; -use futures_util::future; +use futures_util::future::{self, LocalBoxFuture}; // Make sure that we can name function as 'config' #[get("/config")] @@ -117,14 +116,13 @@ where { type Response = ServiceResponse; type Error = Error; - #[allow(clippy::type_complexity)] - type Future = Pin>>>; + type Future = LocalBoxFuture<'static, Result>; - fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { + fn poll_ready(&self, cx: &mut Context<'_>) -> Poll> { self.service.poll_ready(cx) } - fn call(&mut self, req: ServiceRequest) -> Self::Future { + fn call(&self, req: ServiceRequest) -> Self::Future { let fut = self.service.call(req); Box::pin(async move { diff --git a/awc/CHANGES.md b/awc/CHANGES.md index c5c1185be..16ec7ad1a 100644 --- a/awc/CHANGES.md +++ b/awc/CHANGES.md @@ -5,6 +5,9 @@ * `ClientRequest::insert_header` method which allows using typed headers. [#1869] * `ClientRequest::append_header` method which allows using typed headers. [#1869] +### Changed +* Relax default timeout for `Connector` to 5 seconds(original 1 second). [#1905] + ### Removed * `ClientRequest::set`; use `ClientRequest::insert_header`. [#1869] * `ClientRequest::set_header`; use `ClientRequest::insert_header`. [#1869] @@ -12,6 +15,7 @@ * `ClientRequest::header`; use `ClientRequest::append_header`. [#1869] [#1869]: https://github.com/actix/actix-web/pull/1869 +[#1905]: https://github.com/actix/actix-web/pull/1905 ## 3.0.0-beta.1 - 2021-01-07 diff --git a/awc/Cargo.toml b/awc/Cargo.toml index 675110e5a..24275b6f5 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -38,9 +38,9 @@ compress = ["actix-http/compress"] [dependencies] actix-codec = "0.4.0-beta.1" -actix-service = "2.0.0-beta.3" +actix-service = "2.0.0-beta.4" actix-http = "3.0.0-beta.1" -actix-rt = "=2.0.0-beta.2" +actix-rt = "2" base64 = "0.13" bytes = "1" @@ -58,15 +58,12 @@ open-ssl = { version = "0.10", package = "openssl", optional = true } rust-tls = { version = "0.19.0", package = "rustls", optional = true, features = ["dangerous_configuration"] } [dev-dependencies] -# TODO: actix is temporary added as dev dep for actix-macro reason. -# Can be removed when it does not impact tests. -actix = "0.11.0-beta.1" actix-web = { version = "4.0.0-beta.1", features = ["openssl"] } actix-http = { version = "3.0.0-beta.1", features = ["openssl"] } actix-http-test = { version = "3.0.0-beta.1", features = ["openssl"] } actix-utils = "3.0.0-beta.1" -actix-server = "2.0.0-beta.2" -actix-tls = { version = "3.0.0-beta.2", features = ["openssl", "rustls"] } +actix-server = "2.0.0-beta.3" +actix-tls = { version = "3.0.0-beta.3", features = ["openssl", "rustls"] } brotli2 = "0.3.2" flate2 = "1.0.13" diff --git a/awc/README.md b/awc/README.md index 972a80140..3d18a07c5 100644 --- a/awc/README.md +++ b/awc/README.md @@ -21,7 +21,7 @@ use actix_rt::System; use awc::Client; fn main() { - System::new("test").block_on(async { + System::new().block_on(async { let client = Client::default(); let res = client diff --git a/awc/src/builder.rs b/awc/src/builder.rs index 351a493af..39eb24c39 100644 --- a/awc/src/builder.rs +++ b/awc/src/builder.rs @@ -1,4 +1,3 @@ -use std::cell::RefCell; use std::convert::TryFrom; use std::fmt; use std::rc::Rc; @@ -24,7 +23,7 @@ pub struct ClientBuilder { conn_window_size: Option, headers: HeaderMap, timeout: Option, - connector: Option>>, + connector: Option>, } impl Default for ClientBuilder { @@ -56,7 +55,7 @@ impl ClientBuilder { ::Future: 'static, T::Future: 'static, { - self.connector = Some(RefCell::new(Box::new(ConnectorWrapper(connector)))); + self.connector = Some(Box::new(ConnectorWrapper(connector))); self } @@ -182,9 +181,7 @@ impl ClientBuilder { if let Some(val) = self.stream_window_size { connector = connector.initial_window_size(val) }; - RefCell::new( - Box::new(ConnectorWrapper(connector.finish())) as Box - ) + Box::new(ConnectorWrapper(connector.finish())) as _ }; let config = ClientConfig { headers: self.headers, diff --git a/awc/src/connect.rs b/awc/src/connect.rs index 8ee239f76..2ffb8ec37 100644 --- a/awc/src/connect.rs +++ b/awc/src/connect.rs @@ -20,14 +20,14 @@ pub(crate) struct ConnectorWrapper(pub T); pub(crate) trait Connect { fn send_request( - &mut self, + &self, head: RequestHead, body: Body, addr: Option, ) -> Pin>>>; fn send_request_extra( - &mut self, + &self, head: Rc, extra_headers: Option, body: Body, @@ -36,7 +36,7 @@ pub(crate) trait Connect { /// Send request, returns Response and Framed fn open_tunnel( - &mut self, + &self, head: RequestHead, addr: Option, ) -> Pin< @@ -52,7 +52,7 @@ pub(crate) trait Connect { /// Send request and extra headers, returns Response and Framed fn open_tunnel_extra( - &mut self, + &self, head: Rc, extra_headers: Option, addr: Option, @@ -78,7 +78,7 @@ where T::Future: 'static, { fn send_request( - &mut self, + &self, head: RequestHead, body: Body, addr: Option, @@ -101,7 +101,7 @@ where } fn send_request_extra( - &mut self, + &self, head: Rc, extra_headers: Option, body: Body, @@ -126,7 +126,7 @@ where } fn open_tunnel( - &mut self, + &self, head: RequestHead, addr: Option, ) -> Pin< @@ -158,7 +158,7 @@ where } fn open_tunnel_extra( - &mut self, + &self, head: Rc, extra_headers: Option, addr: Option, diff --git a/awc/src/lib.rs b/awc/src/lib.rs index f750a3df2..a92125b5a 100644 --- a/awc/src/lib.rs +++ b/awc/src/lib.rs @@ -93,7 +93,6 @@ #![doc(html_logo_url = "https://actix.rs/img/logo.png")] #![doc(html_favicon_url = "https://actix.rs/favicon.ico")] -use std::cell::RefCell; use std::convert::TryFrom; use std::rc::Rc; use std::time::Duration; @@ -145,7 +144,7 @@ use self::connect::{Connect, ConnectorWrapper}; pub struct Client(Rc); pub(crate) struct ClientConfig { - pub(crate) connector: RefCell>, + pub(crate) connector: Box, pub(crate) headers: HeaderMap, pub(crate) timeout: Option, } @@ -153,9 +152,7 @@ pub(crate) struct ClientConfig { impl Default for Client { fn default() -> Self { Client(Rc::new(ClientConfig { - connector: RefCell::new(Box::new(ConnectorWrapper( - Connector::new().finish(), - ))), + connector: Box::new(ConnectorWrapper(Connector::new().finish())), headers: HeaderMap::new(), timeout: Some(Duration::from_secs(5)), })) diff --git a/awc/src/sender.rs b/awc/src/sender.rs index 9fb821a0e..5f790a038 100644 --- a/awc/src/sender.rs +++ b/awc/src/sender.rs @@ -183,15 +183,13 @@ impl RequestSender { where B: Into, { - let mut connector = config.connector.borrow_mut(); - let fut = match self { RequestSender::Owned(head) => { - connector.send_request(head, body.into(), addr) - } - RequestSender::Rc(head, extra_headers) => { - connector.send_request_extra(head, extra_headers, body.into(), addr) + config.connector.send_request(head, body.into(), addr) } + RequestSender::Rc(head, extra_headers) => config + .connector + .send_request_extra(head, extra_headers, body.into(), addr), }; SendClientRequest::new(fut, response_decompress, timeout.or(config.timeout)) diff --git a/awc/src/ws.rs b/awc/src/ws.rs index 17086cf2a..7c795226b 100644 --- a/awc/src/ws.rs +++ b/awc/src/ws.rs @@ -325,11 +325,7 @@ impl WebsocketsRequest { let max_size = self.max_size; let server_mode = self.server_mode; - let fut = self - .config - .connector - .borrow_mut() - .open_tunnel(head, self.addr); + let fut = self.config.connector.open_tunnel(head, self.addr); // set request timeout let (head, framed) = if let Some(to) = self.config.timeout { diff --git a/awc/tests/test_client.rs b/awc/tests/test_client.rs index 8ca94ec91..a9f5dc370 100644 --- a/awc/tests/test_client.rs +++ b/awc/tests/test_client.rs @@ -120,9 +120,7 @@ async fn test_timeout() { }); let connector = awc::Connector::new() - .connector(actix_tls::connect::new_connector( - actix_tls::connect::start_default_resolver().await.unwrap(), - )) + .connector(actix_tls::connect::default_connector()) .timeout(Duration::from_secs(15)) .finish(); diff --git a/benches/responder.rs b/benches/responder.rs index 61180d575..8cfdbd3ea 100644 --- a/benches/responder.rs +++ b/benches/responder.rs @@ -68,7 +68,7 @@ impl Responder for OptionResponder { } fn future_responder(c: &mut Criterion) { - let rt = actix_rt::System::new("test"); + let rt = actix_rt::System::new(); let req = TestRequest::default().to_http_request(); c.bench_function("future_responder", move |b| { @@ -91,7 +91,7 @@ fn future_responder(c: &mut Criterion) { } fn responder(c: &mut Criterion) { - let rt = actix_rt::System::new("test"); + let rt = actix_rt::System::new(); let req = TestRequest::default().to_http_request(); c.bench_function("responder", move |b| { b.iter_custom(|_| { diff --git a/benches/server.rs b/benches/server.rs index 117b6136e..ce79f077d 100644 --- a/benches/server.rs +++ b/benches/server.rs @@ -29,7 +29,7 @@ const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ fn bench_async_burst(c: &mut Criterion) { // We are using System here, since Runtime requires preinitialized tokio // Maybe add to actix_rt docs - let rt = actix_rt::System::new("test"); + let rt = actix_rt::System::new(); let srv = rt.block_on(async { test::start(|| { diff --git a/benches/service.rs b/benches/service.rs index 8ca6cbe28..0d3264857 100644 --- a/benches/service.rs +++ b/benches/service.rs @@ -25,7 +25,7 @@ pub fn bench_async_service(c: &mut Criterion, srv: S, name: &str) where S: Service + 'static, { - let rt = actix_rt::System::new("test"); + let rt = actix_rt::System::new(); let srv = Rc::new(RefCell::new(srv)); let req = TestRequest::default().to_srv_request(); @@ -67,7 +67,7 @@ async fn index(req: ServiceRequest) -> Result { // Sample results on MacBook Pro '14 // time: [2.0724 us 2.1345 us 2.2074 us] fn async_web_service(c: &mut Criterion) { - let rt = actix_rt::System::new("test"); + let rt = actix_rt::System::new(); let srv = Rc::new(RefCell::new(rt.block_on(init_service( App::new().service(web::service("/").finish(index)), )))); diff --git a/src/app.rs b/src/app.rs index 9c5b806ea..1660c4b94 100644 --- a/src/app.rs +++ b/src/app.rs @@ -422,7 +422,7 @@ where > where B1: MessageBody, - F: FnMut(ServiceRequest, &mut T::Service) -> R + Clone, + F: Fn(ServiceRequest, &T::Service) -> R + Clone, R: Future, Error>>, { App { @@ -480,7 +480,7 @@ mod tests { #[actix_rt::test] async fn test_default_resource() { - let mut srv = init_service( + let srv = init_service( App::new().service(web::resource("/test").to(HttpResponse::Ok)), ) .await; @@ -492,7 +492,7 @@ mod tests { let resp = srv.call(req).await.unwrap(); assert_eq!(resp.status(), StatusCode::NOT_FOUND); - let mut srv = init_service( + let srv = init_service( App::new() .service(web::resource("/test").to(HttpResponse::Ok)) .service( @@ -525,7 +525,7 @@ mod tests { #[actix_rt::test] async fn test_data_factory() { - let mut srv = + let srv = init_service(App::new().data_factory(|| ok::<_, ()>(10usize)).service( web::resource("/").to(|_: web::Data| HttpResponse::Ok()), )) @@ -534,7 +534,7 @@ mod tests { let resp = srv.call(req).await.unwrap(); assert_eq!(resp.status(), StatusCode::OK); - let mut srv = + let srv = init_service(App::new().data_factory(|| ok::<_, ()>(10u32)).service( web::resource("/").to(|_: web::Data| HttpResponse::Ok()), )) @@ -557,7 +557,7 @@ mod tests { #[actix_rt::test] async fn test_extension() { - let mut srv = init_service(App::new().app_data(10usize).service( + let srv = init_service(App::new().app_data(10usize).service( web::resource("/").to(|req: HttpRequest| { assert_eq!(*req.app_data::().unwrap(), 10); HttpResponse::Ok() @@ -571,7 +571,7 @@ mod tests { #[actix_rt::test] async fn test_wrap() { - let mut srv = init_service( + let srv = init_service( App::new() .wrap( DefaultHeaders::new() @@ -581,7 +581,7 @@ mod tests { ) .await; let req = TestRequest::with_uri("/test").to_request(); - let resp = call_service(&mut srv, req).await; + let resp = call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::OK); assert_eq!( resp.headers().get(header::CONTENT_TYPE).unwrap(), @@ -591,7 +591,7 @@ mod tests { #[actix_rt::test] async fn test_router_wrap() { - let mut srv = init_service( + let srv = init_service( App::new() .route("/test", web::get().to(HttpResponse::Ok)) .wrap( @@ -601,7 +601,7 @@ mod tests { ) .await; let req = TestRequest::with_uri("/test").to_request(); - let resp = call_service(&mut srv, req).await; + let resp = call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::OK); assert_eq!( resp.headers().get(header::CONTENT_TYPE).unwrap(), @@ -611,7 +611,7 @@ mod tests { #[actix_rt::test] async fn test_wrap_fn() { - let mut srv = init_service( + let srv = init_service( App::new() .wrap_fn(|req, srv| { let fut = srv.call(req); @@ -628,7 +628,7 @@ mod tests { ) .await; let req = TestRequest::with_uri("/test").to_request(); - let resp = call_service(&mut srv, req).await; + let resp = call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::OK); assert_eq!( resp.headers().get(header::CONTENT_TYPE).unwrap(), @@ -638,7 +638,7 @@ mod tests { #[actix_rt::test] async fn test_router_wrap_fn() { - let mut srv = init_service( + let srv = init_service( App::new() .route("/test", web::get().to(HttpResponse::Ok)) .wrap_fn(|req, srv| { @@ -655,7 +655,7 @@ mod tests { ) .await; let req = TestRequest::with_uri("/test").to_request(); - let resp = call_service(&mut srv, req).await; + let resp = call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::OK); assert_eq!( resp.headers().get(header::CONTENT_TYPE).unwrap(), @@ -665,7 +665,7 @@ mod tests { #[actix_rt::test] async fn test_external_resource() { - let mut srv = init_service( + let srv = init_service( App::new() .external_resource("youtube", "https://youtube.com/watch/{video_id}") .route( @@ -679,7 +679,7 @@ mod tests { ) .await; let req = TestRequest::with_uri("/test").to_request(); - let resp = call_service(&mut srv, req).await; + let resp = call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::OK); let body = read_body(resp).await; assert_eq!(body, Bytes::from_static(b"https://youtube.com/watch/12345")); diff --git a/src/app_service.rs b/src/app_service.rs index 8a00f59eb..a38f1d652 100644 --- a/src/app_service.rs +++ b/src/app_service.rs @@ -196,7 +196,7 @@ where actix_service::forward_ready!(service); - fn call(&mut self, req: Request) -> Self::Future { + fn call(&self, req: Request) -> Self::Future { let (head, payload) = req.into_parts(); let req = if let Some(mut req) = self.app_state.pool().pop() { @@ -287,8 +287,8 @@ impl Service for AppRouting { actix_service::always_ready!(); - fn call(&mut self, mut req: ServiceRequest) -> Self::Future { - let res = self.router.recognize_mut_checked(&mut req, |req, guards| { + fn call(&self, mut req: ServiceRequest) -> Self::Future { + let res = self.router.recognize_checked(&mut req, |req, guards| { if let Some(ref guards) = guards { for f in guards { if !f.check(req.head()) { @@ -354,7 +354,7 @@ mod tests { let data = Arc::new(AtomicBool::new(false)); { - let mut app = init_service( + let app = init_service( App::new() .data(DropData(data.clone())) .service(web::resource("/test").to(HttpResponse::Ok)), diff --git a/src/config.rs b/src/config.rs index 8e22dc90d..24afca295 100644 --- a/src/config.rs +++ b/src/config.rs @@ -263,7 +263,7 @@ mod tests { cfg.app_data(15u8); }; - let mut srv = init_service(App::new().configure(cfg).service( + let srv = init_service(App::new().configure(cfg).service( web::resource("/").to(|_: web::Data, req: HttpRequest| { assert_eq!(*req.app_data::().unwrap(), 15u8); HttpResponse::Ok() @@ -286,7 +286,7 @@ mod tests { // }); // }; - // let mut srv = + // let srv = // init_service(App::new().configure(cfg).service( // web::resource("/").to(|_: web::Data| HttpResponse::Ok()), // )); @@ -297,7 +297,7 @@ mod tests { // let cfg2 = |cfg: &mut ServiceConfig| { // cfg.data_factory(|| Ok::<_, ()>(10u32)); // }; - // let mut srv = init_service( + // let srv = init_service( // App::new() // .service(web::resource("/").to(|_: web::Data| HttpResponse::Ok())) // .configure(cfg2), @@ -309,7 +309,7 @@ mod tests { #[actix_rt::test] async fn test_external_resource() { - let mut srv = init_service( + let srv = init_service( App::new() .configure(|cfg| { cfg.external_resource( @@ -328,7 +328,7 @@ mod tests { ) .await; let req = TestRequest::with_uri("/test").to_request(); - let resp = call_service(&mut srv, req).await; + let resp = call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::OK); let body = read_body(resp).await; assert_eq!(body, Bytes::from_static(b"https://youtube.com/watch/12345")); @@ -336,7 +336,7 @@ mod tests { #[actix_rt::test] async fn test_service() { - let mut srv = init_service(App::new().configure(|cfg| { + let srv = init_service(App::new().configure(|cfg| { cfg.service( web::resource("/test").route(web::get().to(HttpResponse::Created)), ) @@ -347,13 +347,13 @@ mod tests { let req = TestRequest::with_uri("/test") .method(Method::GET) .to_request(); - let resp = call_service(&mut srv, req).await; + let resp = call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::CREATED); let req = TestRequest::with_uri("/index.html") .method(Method::GET) .to_request(); - let resp = call_service(&mut srv, req).await; + let resp = call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::OK); } } diff --git a/src/data.rs b/src/data.rs index dd0fbed0e..12a1f5cf8 100644 --- a/src/data.rs +++ b/src/data.rs @@ -144,7 +144,7 @@ mod tests { #[actix_rt::test] async fn test_data_extractor() { - let mut srv = init_service(App::new().data("TEST".to_string()).service( + let srv = init_service(App::new().data("TEST".to_string()).service( web::resource("/").to(|data: web::Data| { assert_eq!(data.to_lowercase(), "test"); HttpResponse::Ok() @@ -156,7 +156,7 @@ mod tests { let resp = srv.call(req).await.unwrap(); assert_eq!(resp.status(), StatusCode::OK); - let mut srv = + let srv = init_service(App::new().data(10u32).service( web::resource("/").to(|_: web::Data| HttpResponse::Ok()), )) @@ -165,7 +165,7 @@ mod tests { let resp = srv.call(req).await.unwrap(); assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR); - let mut srv = init_service( + let srv = init_service( App::new() .data(10u32) .data(13u32) @@ -186,7 +186,7 @@ mod tests { #[actix_rt::test] async fn test_app_data_extractor() { - let mut srv = + let srv = init_service(App::new().app_data(Data::new(10usize)).service( web::resource("/").to(|_: web::Data| HttpResponse::Ok()), )) @@ -196,7 +196,7 @@ mod tests { let resp = srv.call(req).await.unwrap(); assert_eq!(resp.status(), StatusCode::OK); - let mut srv = + let srv = init_service(App::new().app_data(Data::new(10u32)).service( web::resource("/").to(|_: web::Data| HttpResponse::Ok()), )) @@ -208,7 +208,7 @@ mod tests { #[actix_rt::test] async fn test_route_data_extractor() { - let mut srv = init_service( + let srv = init_service( App::new().service( web::resource("/") .data(10usize) @@ -222,7 +222,7 @@ mod tests { assert_eq!(resp.status(), StatusCode::OK); // different type - let mut srv = init_service( + let srv = init_service( App::new().service( web::resource("/") .data(10u32) @@ -237,7 +237,7 @@ mod tests { #[actix_rt::test] async fn test_override_data() { - let mut srv = init_service(App::new().data(1usize).service( + let srv = init_service(App::new().data(1usize).service( web::resource("/").data(10usize).route(web::get().to( |data: web::Data| { assert_eq!(**data, 10); diff --git a/src/handler.rs b/src/handler.rs index 1526f050d..0016b741e 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -113,11 +113,11 @@ where type Error = Error; type Future = HandlerServiceFuture; - fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll> { + fn poll_ready(&self, _: &mut Context<'_>) -> Poll> { Poll::Ready(Ok(())) } - fn call(&mut self, req: ServiceRequest) -> Self::Future { + fn call(&self, req: ServiceRequest) -> Self::Future { let (req, mut payload) = req.into_parts(); let fut = T::from_request(&req, &mut payload); HandlerServiceFuture::Extract(fut, Some(req), self.hnd.clone()) diff --git a/src/middleware/compat.rs b/src/middleware/compat.rs index d5661fc77..2df535280 100644 --- a/src/middleware/compat.rs +++ b/src/middleware/compat.rs @@ -80,11 +80,11 @@ where type Error = Error; type Future = CompatMiddlewareFuture; - fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { + fn poll_ready(&self, cx: &mut Context<'_>) -> Poll> { self.service.poll_ready(cx).map_err(From::from) } - fn call(&mut self, req: Req) -> Self::Future { + fn call(&self, req: Req) -> Self::Future { let fut = self.service.call(req); CompatMiddlewareFuture { fut } } @@ -138,7 +138,7 @@ mod tests { let logger = Logger::default(); let compress = Compress::default(); - let mut srv = init_service( + let srv = init_service( App::new().service( web::scope("app") .wrap(Compat::new(logger)) @@ -151,7 +151,7 @@ mod tests { .await; let req = TestRequest::with_uri("/app/test").to_request(); - let resp = call_service(&mut srv, req).await; + let resp = call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::OK); } @@ -160,7 +160,7 @@ mod tests { let logger = Logger::default(); let compress = Compress::default(); - let mut srv = init_service( + let srv = init_service( App::new().service( web::resource("app/test") .wrap(Compat::new(logger)) @@ -171,7 +171,7 @@ mod tests { .await; let req = TestRequest::with_uri("/app/test").to_request(); - let resp = call_service(&mut srv, req).await; + let resp = call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::OK); } @@ -185,11 +185,11 @@ mod tests { let logger = Logger::default(); - let mut mw = Condition::new(true, Compat::new(logger)) + let mw = Condition::new(true, Compat::new(logger)) .new_transform(srv.into_service()) .await .unwrap(); - let resp = call_service(&mut mw, TestRequest::default().to_srv_request()).await; + let resp = call_service(&mw, TestRequest::default().to_srv_request()).await; assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR); } } diff --git a/src/middleware/compress.rs b/src/middleware/compress.rs index 1f05b7ae3..7a45e4c8d 100644 --- a/src/middleware/compress.rs +++ b/src/middleware/compress.rs @@ -90,7 +90,7 @@ where actix_service::forward_ready!(service); #[allow(clippy::borrow_interior_mutable_const)] - fn call(&mut self, req: ServiceRequest) -> Self::Future { + fn call(&self, req: ServiceRequest) -> Self::Future { // negotiate content-encoding let encoding = if let Some(val) = req.headers().get(&ACCEPT_ENCODING) { if let Ok(enc) = val.to_str() { diff --git a/src/middleware/condition.rs b/src/middleware/condition.rs index d61e7d576..85eba3fb8 100644 --- a/src/middleware/condition.rs +++ b/src/middleware/condition.rs @@ -76,14 +76,14 @@ where type Error = E::Error; type Future = Either; - fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { + fn poll_ready(&self, cx: &mut Context<'_>) -> Poll> { match self { ConditionMiddleware::Enable(service) => service.poll_ready(cx), ConditionMiddleware::Disable(service) => service.poll_ready(cx), } } - fn call(&mut self, req: Req) -> Self::Future { + fn call(&self, req: Req) -> Self::Future { match self { ConditionMiddleware::Enable(service) => Either::Left(service.call(req)), ConditionMiddleware::Disable(service) => Either::Right(service.call(req)), @@ -123,12 +123,12 @@ mod tests { let mw = ErrorHandlers::new().handler(StatusCode::INTERNAL_SERVER_ERROR, render_500); - let mut mw = Condition::new(true, mw) + let mw = Condition::new(true, mw) .new_transform(srv.into_service()) .await .unwrap(); let resp = - test::call_service(&mut mw, TestRequest::default().to_srv_request()).await; + test::call_service(&mw, TestRequest::default().to_srv_request()).await; assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0001"); } @@ -141,13 +141,13 @@ mod tests { let mw = ErrorHandlers::new().handler(StatusCode::INTERNAL_SERVER_ERROR, render_500); - let mut mw = Condition::new(false, mw) + let mw = Condition::new(false, mw) .new_transform(srv.into_service()) .await .unwrap(); let resp = - test::call_service(&mut mw, TestRequest::default().to_srv_request()).await; + test::call_service(&mw, TestRequest::default().to_srv_request()).await; assert_eq!(resp.headers().get(CONTENT_TYPE), None); } } diff --git a/src/middleware/default_headers.rs b/src/middleware/default_headers.rs index 5d55fd830..a9a50ec2c 100644 --- a/src/middleware/default_headers.rs +++ b/src/middleware/default_headers.rs @@ -143,7 +143,7 @@ where actix_service::forward_ready!(service); - fn call(&mut self, req: ServiceRequest) -> Self::Future { + fn call(&self, req: ServiceRequest) -> Self::Future { let inner = self.inner.clone(); let fut = self.service.call(req); @@ -200,7 +200,7 @@ mod tests { #[actix_rt::test] async fn test_default_headers() { - let mut mw = DefaultHeaders::new() + let mw = DefaultHeaders::new() .header(CONTENT_TYPE, "0001") .new_transform(ok_service()) .await @@ -218,7 +218,7 @@ mod tests { .finish(), )) }; - let mut mw = DefaultHeaders::new() + let mw = DefaultHeaders::new() .header(CONTENT_TYPE, "0001") .new_transform(srv.into_service()) .await @@ -231,7 +231,7 @@ mod tests { async fn test_content_type() { let srv = |req: ServiceRequest| ok(req.into_response(HttpResponse::Ok().finish())); - let mut mw = DefaultHeaders::new() + let mw = DefaultHeaders::new() .add_content_type() .new_transform(srv.into_service()) .await diff --git a/src/middleware/err_handlers.rs b/src/middleware/err_handlers.rs index 44962aa98..70933241d 100644 --- a/src/middleware/err_handlers.rs +++ b/src/middleware/err_handlers.rs @@ -123,7 +123,7 @@ where actix_service::forward_ready!(service); - fn call(&mut self, req: ServiceRequest) -> Self::Future { + fn call(&self, req: ServiceRequest) -> Self::Future { let handlers = self.handlers.clone(); let fut = self.service.call(req); ErrorHandlersFuture::ServiceFuture { fut, handlers } @@ -196,14 +196,14 @@ mod tests { ok(req.into_response(HttpResponse::InternalServerError().finish())) }; - let mut mw = ErrorHandlers::new() + let mw = ErrorHandlers::new() .handler(StatusCode::INTERNAL_SERVER_ERROR, render_500) .new_transform(srv.into_service()) .await .unwrap(); let resp = - test::call_service(&mut mw, TestRequest::default().to_srv_request()).await; + test::call_service(&mw, TestRequest::default().to_srv_request()).await; assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0001"); } @@ -223,14 +223,14 @@ mod tests { ok(req.into_response(HttpResponse::InternalServerError().finish())) }; - let mut mw = ErrorHandlers::new() + let mw = ErrorHandlers::new() .handler(StatusCode::INTERNAL_SERVER_ERROR, render_500_async) .new_transform(srv.into_service()) .await .unwrap(); let resp = - test::call_service(&mut mw, TestRequest::default().to_srv_request()).await; + test::call_service(&mw, TestRequest::default().to_srv_request()).await; assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0001"); } } diff --git a/src/middleware/logger.rs b/src/middleware/logger.rs index 66bd7e464..969aa9be5 100644 --- a/src/middleware/logger.rs +++ b/src/middleware/logger.rs @@ -219,7 +219,7 @@ where actix_service::forward_ready!(service); - fn call(&mut self, req: ServiceRequest) -> Self::Future { + fn call(&self, req: ServiceRequest) -> Self::Future { if self.inner.exclude.contains(req.path()) || self.inner.exclude_regex.is_match(req.path()) { @@ -609,7 +609,7 @@ mod tests { }; let logger = Logger::new("%% %{User-Agent}i %{X-Test}o %{HOME}e %D test"); - let mut srv = logger.new_transform(srv.into_service()).await.unwrap(); + let srv = logger.new_transform(srv.into_service()).await.unwrap(); let req = TestRequest::default() .insert_header(( @@ -632,7 +632,7 @@ mod tests { let logger = Logger::new("%% %{User-Agent}i %{X-Test}o %{HOME}e %D test") .exclude_regex("\\w"); - let mut srv = logger.new_transform(srv.into_service()).await.unwrap(); + let srv = logger.new_transform(srv.into_service()).await.unwrap(); let req = TestRequest::default() .insert_header(( @@ -806,7 +806,7 @@ mod tests { captured.to_owned() }); - let mut srv = logger.new_transform(test::ok_service()).await.unwrap(); + let srv = logger.new_transform(test::ok_service()).await.unwrap(); let req = TestRequest::default().to_srv_request(); srv.call(req).await.unwrap(); diff --git a/src/middleware/normalize.rs b/src/middleware/normalize.rs index 89cab9073..8519f041a 100644 --- a/src/middleware/normalize.rs +++ b/src/middleware/normalize.rs @@ -57,7 +57,7 @@ impl Default for TrailingSlash { /// ```rust /// use actix_web::{web, middleware, App}; /// -/// # actix_web::rt::System::new("doctest").block_on(async { +/// # actix_web::rt::System::new().block_on(async { /// let app = App::new() /// .wrap(middleware::NormalizePath::default()) /// .route("/test", web::get().to(|| async { "test" })) @@ -66,22 +66,22 @@ impl Default for TrailingSlash { /// use actix_web::http::StatusCode; /// use actix_web::test::{call_service, init_service, TestRequest}; /// -/// let mut app = init_service(app).await; +/// let app = init_service(app).await; /// /// let req = TestRequest::with_uri("/test").to_request(); -/// let res = call_service(&mut app, req).await; +/// let res = call_service(&app, req).await; /// assert_eq!(res.status(), StatusCode::OK); /// /// let req = TestRequest::with_uri("/test/").to_request(); -/// let res = call_service(&mut app, req).await; +/// let res = call_service(&app, req).await; /// assert_eq!(res.status(), StatusCode::OK); /// /// let req = TestRequest::with_uri("/unmatchable").to_request(); -/// let res = call_service(&mut app, req).await; +/// let res = call_service(&app, req).await; /// assert_eq!(res.status(), StatusCode::NOT_FOUND); /// /// let req = TestRequest::with_uri("/unmatchable/").to_request(); -/// let res = call_service(&mut app, req).await; +/// let res = call_service(&app, req).await; /// assert_eq!(res.status(), StatusCode::NOT_FOUND); /// # }) /// ``` @@ -132,7 +132,7 @@ where actix_service::forward_ready!(service); - fn call(&mut self, mut req: ServiceRequest) -> Self::Future { + fn call(&self, mut req: ServiceRequest) -> Self::Future { let head = req.head_mut(); let original_path = head.uri.path(); @@ -195,7 +195,7 @@ mod tests { #[actix_rt::test] async fn test_wrap() { - let mut app = init_service( + let app = init_service( App::new() .wrap(NormalizePath::default()) .service(web::resource("/").to(HttpResponse::Ok)) @@ -204,37 +204,37 @@ mod tests { .await; let req = TestRequest::with_uri("/").to_request(); - let res = call_service(&mut app, req).await; + let res = call_service(&app, req).await; assert!(res.status().is_success()); let req = TestRequest::with_uri("/?query=test").to_request(); - let res = call_service(&mut app, req).await; + let res = call_service(&app, req).await; assert!(res.status().is_success()); let req = TestRequest::with_uri("///").to_request(); - let res = call_service(&mut app, req).await; + let res = call_service(&app, req).await; assert!(res.status().is_success()); let req = TestRequest::with_uri("/v1//something////").to_request(); - let res = call_service(&mut app, req).await; + let res = call_service(&app, req).await; assert!(res.status().is_success()); let req2 = TestRequest::with_uri("//v1/something").to_request(); - let res2 = call_service(&mut app, req2).await; + let res2 = call_service(&app, req2).await; assert!(res2.status().is_success()); let req3 = TestRequest::with_uri("//v1//////something").to_request(); - let res3 = call_service(&mut app, req3).await; + let res3 = call_service(&app, req3).await; assert!(res3.status().is_success()); let req4 = TestRequest::with_uri("/v1//something").to_request(); - let res4 = call_service(&mut app, req4).await; + let res4 = call_service(&app, req4).await; assert!(res4.status().is_success()); } #[actix_rt::test] async fn trim_trailing_slashes() { - let mut app = init_service( + let app = init_service( App::new() .wrap(NormalizePath(TrailingSlash::Trim)) .service(web::resource("/").to(HttpResponse::Ok)) @@ -244,37 +244,37 @@ mod tests { // root paths should still work let req = TestRequest::with_uri("/").to_request(); - let res = call_service(&mut app, req).await; + let res = call_service(&app, req).await; assert!(res.status().is_success()); let req = TestRequest::with_uri("/?query=test").to_request(); - let res = call_service(&mut app, req).await; + let res = call_service(&app, req).await; assert!(res.status().is_success()); let req = TestRequest::with_uri("///").to_request(); - let res = call_service(&mut app, req).await; + let res = call_service(&app, req).await; assert!(res.status().is_success()); let req = TestRequest::with_uri("/v1/something////").to_request(); - let res = call_service(&mut app, req).await; + let res = call_service(&app, req).await; assert!(res.status().is_success()); let req2 = TestRequest::with_uri("/v1/something/").to_request(); - let res2 = call_service(&mut app, req2).await; + let res2 = call_service(&app, req2).await; assert!(res2.status().is_success()); let req3 = TestRequest::with_uri("//v1//something//").to_request(); - let res3 = call_service(&mut app, req3).await; + let res3 = call_service(&app, req3).await; assert!(res3.status().is_success()); let req4 = TestRequest::with_uri("//v1//something").to_request(); - let res4 = call_service(&mut app, req4).await; + let res4 = call_service(&app, req4).await; assert!(res4.status().is_success()); } #[actix_rt::test] async fn keep_trailing_slash_unchanged() { - let mut app = init_service( + let app = init_service( App::new() .wrap(NormalizePath(TrailingSlash::MergeOnly)) .service(web::resource("/").to(HttpResponse::Ok)) @@ -299,7 +299,7 @@ mod tests { for (path, success) in tests { let req = TestRequest::with_uri(path).to_request(); - let res = call_service(&mut app, req).await; + let res = call_service(&app, req).await; assert_eq!(res.status().is_success(), success); } } @@ -311,7 +311,7 @@ mod tests { ready(Ok(req.into_response(HttpResponse::Ok().finish()))) }; - let mut normalize = NormalizePath::default() + let normalize = NormalizePath::default() .new_transform(srv.into_service()) .await .unwrap(); @@ -342,7 +342,7 @@ mod tests { ready(Ok(req.into_response(HttpResponse::Ok().finish()))) }; - let mut normalize = NormalizePath::default() + let normalize = NormalizePath::default() .new_transform(srv.into_service()) .await .unwrap(); @@ -359,7 +359,7 @@ mod tests { ready(Ok(req.into_response(HttpResponse::Ok().finish()))) }; - let mut normalize = NormalizePath::default() + let normalize = NormalizePath::default() .new_transform(srv.into_service()) .await .unwrap(); diff --git a/src/request.rs b/src/request.rs index fe60cb169..a563518e0 100644 --- a/src/request.rs +++ b/src/request.rs @@ -556,7 +556,7 @@ mod tests { #[actix_rt::test] async fn test_drop_http_request_pool() { - let mut srv = init_service(App::new().service(web::resource("/").to( + let srv = init_service(App::new().service(web::resource("/").to( |req: HttpRequest| { HttpResponse::Ok() .insert_header(("pool_cap", req.app_state().pool().cap)) @@ -566,7 +566,7 @@ mod tests { .await; let req = TestRequest::default().to_request(); - let resp = call_service(&mut srv, req).await; + let resp = call_service(&srv, req).await; drop(srv); @@ -575,7 +575,7 @@ mod tests { #[actix_rt::test] async fn test_data() { - let mut srv = init_service(App::new().app_data(10usize).service( + let srv = init_service(App::new().app_data(10usize).service( web::resource("/").to(|req: HttpRequest| { if req.app_data::().is_some() { HttpResponse::Ok() @@ -587,10 +587,10 @@ mod tests { .await; let req = TestRequest::default().to_request(); - let resp = call_service(&mut srv, req).await; + let resp = call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::OK); - let mut srv = init_service(App::new().app_data(10u32).service( + let srv = init_service(App::new().app_data(10u32).service( web::resource("/").to(|req: HttpRequest| { if req.app_data::().is_some() { HttpResponse::Ok() @@ -602,7 +602,7 @@ mod tests { .await; let req = TestRequest::default().to_request(); - let resp = call_service(&mut srv, req).await; + let resp = call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::BAD_REQUEST); } @@ -614,7 +614,7 @@ mod tests { HttpResponse::Ok().body(num.to_string()) } - let mut srv = init_service( + let srv = init_service( App::new() .app_data(88usize) .service(web::resource("/").route(web::get().to(echo_usize))) @@ -645,7 +645,7 @@ mod tests { HttpResponse::Ok().body(num.to_string()) } - let mut srv = init_service( + let srv = init_service( App::new() .app_data(88usize) .service(web::resource("/").route(web::get().to(echo_usize))) @@ -685,7 +685,7 @@ mod tests { let tracker = Rc::new(RefCell::new(Tracker { dropped: false })); { let tracker2 = Rc::clone(&tracker); - let mut srv = init_service(App::new().data(10u32).service( + let srv = init_service(App::new().data(10u32).service( web::resource("/").to(move |req: HttpRequest| { req.extensions_mut().insert(Foo { tracker: Rc::clone(&tracker2), @@ -696,7 +696,7 @@ mod tests { .await; let req = TestRequest::default().to_request(); - let resp = call_service(&mut srv, req).await; + let resp = call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::OK); } @@ -705,7 +705,7 @@ mod tests { #[actix_rt::test] async fn extract_path_pattern() { - let mut srv = init_service( + let srv = init_service( App::new().service( web::scope("/user/{id}") .service(web::resource("/profile").route(web::get().to( @@ -727,17 +727,17 @@ mod tests { .await; let req = TestRequest::get().uri("/user/22/profile").to_request(); - let res = call_service(&mut srv, req).await; + let res = call_service(&srv, req).await; assert_eq!(res.status(), StatusCode::OK); let req = TestRequest::get().uri("/user/22/not-exist").to_request(); - let res = call_service(&mut srv, req).await; + let res = call_service(&srv, req).await; assert_eq!(res.status(), StatusCode::OK); } #[actix_rt::test] async fn extract_path_pattern_complex() { - let mut srv = init_service( + let srv = init_service( App::new() .service(web::scope("/user").service(web::scope("/{id}").service( web::resource("").to(move |req: HttpRequest| { @@ -759,15 +759,15 @@ mod tests { .await; let req = TestRequest::get().uri("/user/test").to_request(); - let res = call_service(&mut srv, req).await; + let res = call_service(&srv, req).await; assert_eq!(res.status(), StatusCode::OK); let req = TestRequest::get().uri("/").to_request(); - let res = call_service(&mut srv, req).await; + let res = call_service(&srv, req).await; assert_eq!(res.status(), StatusCode::OK); let req = TestRequest::get().uri("/not-exist").to_request(); - let res = call_service(&mut srv, req).await; + let res = call_service(&srv, req).await; assert_eq!(res.status(), StatusCode::OK); } } diff --git a/src/request_data.rs b/src/request_data.rs index 285154884..beee8ac12 100644 --- a/src/request_data.rs +++ b/src/request_data.rs @@ -102,7 +102,7 @@ mod tests { #[actix_rt::test] async fn req_data_extractor() { - let mut srv = init_service( + let srv = init_service( App::new() .wrap_fn(|req, srv| { if req.method() == Method::POST { @@ -142,7 +142,7 @@ mod tests { #[actix_rt::test] async fn req_data_internal_mutability() { - let mut srv = init_service( + let srv = init_service( App::new() .wrap_fn(|req, srv| { let data_before = Rc::new(RefCell::new(42u32)); diff --git a/src/resource.rs b/src/resource.rs index 3844b429d..188e6fa43 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -328,7 +328,7 @@ where >, > where - F: FnMut(ServiceRequest, &mut T::Service) -> R + Clone, + F: Fn(ServiceRequest, &T::Service) -> R + Clone, R: Future>, { Resource { @@ -471,8 +471,8 @@ impl Service for ResourceService { actix_service::always_ready!(); - fn call(&mut self, mut req: ServiceRequest) -> Self::Future { - for route in self.routes.iter_mut() { + fn call(&self, mut req: ServiceRequest) -> Self::Future { + for route in self.routes.iter() { if route.check(&mut req) { if let Some(ref app_data) = self.app_data { req.add_data_container(app_data.clone()); @@ -530,7 +530,7 @@ mod tests { #[actix_rt::test] async fn test_middleware() { - let mut srv = + let srv = init_service( App::new().service( web::resource("/test") @@ -544,7 +544,7 @@ mod tests { ) .await; let req = TestRequest::with_uri("/test").to_request(); - let resp = call_service(&mut srv, req).await; + let resp = call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::OK); assert_eq!( resp.headers().get(header::CONTENT_TYPE).unwrap(), @@ -554,7 +554,7 @@ mod tests { #[actix_rt::test] async fn test_middleware_fn() { - let mut srv = init_service( + let srv = init_service( App::new().service( web::resource("/test") .wrap_fn(|req, srv| { @@ -574,7 +574,7 @@ mod tests { ) .await; let req = TestRequest::with_uri("/test").to_request(); - let resp = call_service(&mut srv, req).await; + let resp = call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::OK); assert_eq!( resp.headers().get(header::CONTENT_TYPE).unwrap(), @@ -584,20 +584,20 @@ mod tests { #[actix_rt::test] async fn test_to() { - let mut srv = + let srv = init_service(App::new().service(web::resource("/test").to(|| async { sleep(Duration::from_millis(100)).await; Ok::<_, Error>(HttpResponse::Ok()) }))) .await; let req = TestRequest::with_uri("/test").to_request(); - let resp = call_service(&mut srv, req).await; + let resp = call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::OK); } #[actix_rt::test] async fn test_pattern() { - let mut srv = init_service( + let srv = init_service( App::new().service( web::resource(["/test", "/test2"]) .to(|| async { Ok::<_, Error>(HttpResponse::Ok()) }), @@ -605,16 +605,16 @@ mod tests { ) .await; let req = TestRequest::with_uri("/test").to_request(); - let resp = call_service(&mut srv, req).await; + let resp = call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::OK); let req = TestRequest::with_uri("/test2").to_request(); - let resp = call_service(&mut srv, req).await; + let resp = call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::OK); } #[actix_rt::test] async fn test_default_resource() { - let mut srv = init_service( + let srv = init_service( App::new() .service(web::resource("/test").route(web::get().to(HttpResponse::Ok))) .default_service(|r: ServiceRequest| { @@ -623,16 +623,16 @@ mod tests { ) .await; let req = TestRequest::with_uri("/test").to_request(); - let resp = call_service(&mut srv, req).await; + let resp = call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::OK); let req = TestRequest::with_uri("/test") .method(Method::POST) .to_request(); - let resp = call_service(&mut srv, req).await; + let resp = call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED); - let mut srv = init_service( + let srv = init_service( App::new().service( web::resource("/test") .route(web::get().to(HttpResponse::Ok)) @@ -644,19 +644,19 @@ mod tests { .await; let req = TestRequest::with_uri("/test").to_request(); - let resp = call_service(&mut srv, req).await; + let resp = call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::OK); let req = TestRequest::with_uri("/test") .method(Method::POST) .to_request(); - let resp = call_service(&mut srv, req).await; + let resp = call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::BAD_REQUEST); } #[actix_rt::test] async fn test_resource_guards() { - let mut srv = init_service( + let srv = init_service( App::new() .service( web::resource("/test/{p}") @@ -679,25 +679,25 @@ mod tests { let req = TestRequest::with_uri("/test/it") .method(Method::GET) .to_request(); - let resp = call_service(&mut srv, req).await; + let resp = call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::OK); let req = TestRequest::with_uri("/test/it") .method(Method::PUT) .to_request(); - let resp = call_service(&mut srv, req).await; + let resp = call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::CREATED); let req = TestRequest::with_uri("/test/it") .method(Method::DELETE) .to_request(); - let resp = call_service(&mut srv, req).await; + let resp = call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::NO_CONTENT); } #[actix_rt::test] async fn test_data() { - let mut srv = init_service( + let srv = init_service( App::new() .data(1.0f64) .data(1usize) @@ -723,13 +723,13 @@ mod tests { .await; let req = TestRequest::get().uri("/test").to_request(); - let resp = call_service(&mut srv, req).await; + let resp = call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::OK); } #[actix_rt::test] async fn test_data_default_service() { - let mut srv = init_service( + let srv = init_service( App::new().data(1usize).service( web::resource("/test") .data(10usize) @@ -742,7 +742,7 @@ mod tests { .await; let req = TestRequest::get().uri("/test").to_request(); - let resp = call_service(&mut srv, req).await; + let resp = call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::OK); } } diff --git a/src/responder.rs b/src/responder.rs index d5771d520..dcad45e0f 100644 --- a/src/responder.rs +++ b/src/responder.rs @@ -260,7 +260,7 @@ pub(crate) mod tests { #[actix_rt::test] async fn test_option_responder() { - let mut srv = init_service( + let srv = init_service( App::new() .service( web::resource("/none").to(|| async { Option::<&'static str>::None }), diff --git a/src/route.rs b/src/route.rs index 8a3d1da9f..c5e297411 100644 --- a/src/route.rs +++ b/src/route.rs @@ -121,11 +121,11 @@ impl Service for RouteService { type Error = Error; type Future = LocalBoxFuture<'static, Result>; - fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { + fn poll_ready(&self, cx: &mut Context<'_>) -> Poll> { self.service.poll_ready(cx) } - fn call(&mut self, req: ServiceRequest) -> Self::Future { + fn call(&self, req: ServiceRequest) -> Self::Future { self.service.call(req) } } @@ -299,11 +299,11 @@ where type Error = Error; type Future = LocalBoxFuture<'static, Result>; - fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { + fn poll_ready(&self, cx: &mut Context<'_>) -> Poll> { self.service.poll_ready(cx) } - fn call(&mut self, req: ServiceRequest) -> Self::Future { + fn call(&self, req: ServiceRequest) -> Self::Future { Box::pin(self.service.call(req)) } } @@ -327,7 +327,7 @@ mod tests { #[actix_rt::test] async fn test_route() { - let mut srv = init_service( + let srv = init_service( App::new() .service( web::resource("/test") @@ -356,35 +356,35 @@ mod tests { let req = TestRequest::with_uri("/test") .method(Method::GET) .to_request(); - let resp = call_service(&mut srv, req).await; + let resp = call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::OK); let req = TestRequest::with_uri("/test") .method(Method::POST) .to_request(); - let resp = call_service(&mut srv, req).await; + let resp = call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::CREATED); let req = TestRequest::with_uri("/test") .method(Method::PUT) .to_request(); - let resp = call_service(&mut srv, req).await; + let resp = call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::BAD_REQUEST); let req = TestRequest::with_uri("/test") .method(Method::DELETE) .to_request(); - let resp = call_service(&mut srv, req).await; + let resp = call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::BAD_REQUEST); let req = TestRequest::with_uri("/test") .method(Method::HEAD) .to_request(); - let resp = call_service(&mut srv, req).await; + let resp = call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED); let req = TestRequest::with_uri("/json").to_request(); - let resp = call_service(&mut srv, req).await; + let resp = call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::OK); let body = read_body(resp).await; diff --git a/src/scope.rs b/src/scope.rs index 290a35eb1..d17acd843 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -380,7 +380,7 @@ where >, > where - F: FnMut(ServiceRequest, &mut T::Service) -> R + Clone, + F: Fn(ServiceRequest, &T::Service) -> R + Clone, R: Future>, { Scope { @@ -526,8 +526,8 @@ impl Service for ScopeService { actix_service::always_ready!(); - fn call(&mut self, mut req: ServiceRequest) -> Self::Future { - let res = self.router.recognize_mut_checked(&mut req, |req, guards| { + fn call(&self, mut req: ServiceRequest) -> Self::Future { + let res = self.router.recognize_checked(&mut req, |req, guards| { if let Some(ref guards) = guards { for f in guards { if !f.check(req.head()) { @@ -589,7 +589,7 @@ mod tests { #[actix_rt::test] async fn test_scope() { - let mut srv = init_service(App::new().service( + let srv = init_service(App::new().service( web::scope("/app").service(web::resource("/path1").to(HttpResponse::Ok)), )) .await; @@ -601,7 +601,7 @@ mod tests { #[actix_rt::test] async fn test_scope_root() { - let mut srv = init_service( + let srv = init_service( App::new().service( web::scope("/app") .service(web::resource("").to(HttpResponse::Ok)) @@ -621,7 +621,7 @@ mod tests { #[actix_rt::test] async fn test_scope_root2() { - let mut srv = init_service(App::new().service( + let srv = init_service(App::new().service( web::scope("/app/").service(web::resource("").to(HttpResponse::Ok)), )) .await; @@ -637,7 +637,7 @@ mod tests { #[actix_rt::test] async fn test_scope_root3() { - let mut srv = init_service(App::new().service( + let srv = init_service(App::new().service( web::scope("/app/").service(web::resource("/").to(HttpResponse::Ok)), )) .await; @@ -653,7 +653,7 @@ mod tests { #[actix_rt::test] async fn test_scope_route() { - let mut srv = init_service( + let srv = init_service( App::new().service( web::scope("app") .route("/path1", web::get().to(HttpResponse::Ok)) @@ -681,7 +681,7 @@ mod tests { #[actix_rt::test] async fn test_scope_route_without_leading_slash() { - let mut srv = init_service( + let srv = init_service( App::new().service( web::scope("app").service( web::resource("path1") @@ -711,7 +711,7 @@ mod tests { #[actix_rt::test] async fn test_scope_guard() { - let mut srv = init_service( + let srv = init_service( App::new().service( web::scope("/app") .guard(guard::Get()) @@ -735,14 +735,13 @@ mod tests { #[actix_rt::test] async fn test_scope_variable_segment() { - let mut srv = - init_service(App::new().service(web::scope("/ab-{project}").service( - web::resource("/path1").to(|r: HttpRequest| { - HttpResponse::Ok() - .body(format!("project: {}", &r.match_info()["project"])) - }), - ))) - .await; + let srv = init_service(App::new().service(web::scope("/ab-{project}").service( + web::resource("/path1").to(|r: HttpRequest| { + HttpResponse::Ok() + .body(format!("project: {}", &r.match_info()["project"])) + }), + ))) + .await; let req = TestRequest::with_uri("/ab-project1/path1").to_request(); let resp = srv.call(req).await.unwrap(); @@ -763,7 +762,7 @@ mod tests { #[actix_rt::test] async fn test_nested_scope() { - let mut srv = init_service(App::new().service(web::scope("/app").service( + let srv = init_service(App::new().service(web::scope("/app").service( web::scope("/t1").service(web::resource("/path1").to(HttpResponse::Created)), ))) .await; @@ -775,7 +774,7 @@ mod tests { #[actix_rt::test] async fn test_nested_scope_no_slash() { - let mut srv = init_service(App::new().service(web::scope("/app").service( + let srv = init_service(App::new().service(web::scope("/app").service( web::scope("t1").service(web::resource("/path1").to(HttpResponse::Created)), ))) .await; @@ -787,7 +786,7 @@ mod tests { #[actix_rt::test] async fn test_nested_scope_root() { - let mut srv = init_service( + let srv = init_service( App::new().service( web::scope("/app").service( web::scope("/t1") @@ -809,7 +808,7 @@ mod tests { #[actix_rt::test] async fn test_nested_scope_filter() { - let mut srv = init_service( + let srv = init_service( App::new().service( web::scope("/app").service( web::scope("/t1") @@ -835,7 +834,7 @@ mod tests { #[actix_rt::test] async fn test_nested_scope_with_variable_segment() { - let mut srv = init_service(App::new().service(web::scope("/app").service( + let srv = init_service(App::new().service(web::scope("/app").service( web::scope("/{project_id}").service(web::resource("/path1").to( |r: HttpRequest| { HttpResponse::Created() @@ -860,7 +859,7 @@ mod tests { #[actix_rt::test] async fn test_nested2_scope_with_variable_segment() { - let mut srv = init_service(App::new().service(web::scope("/app").service( + let srv = init_service(App::new().service(web::scope("/app").service( web::scope("/{project}").service(web::scope("/{id}").service( web::resource("/path1").to(|r: HttpRequest| { HttpResponse::Created().body(format!( @@ -892,7 +891,7 @@ mod tests { #[actix_rt::test] async fn test_default_resource() { - let mut srv = init_service( + let srv = init_service( App::new().service( web::scope("/app") .service(web::resource("/path1").to(HttpResponse::Ok)) @@ -914,7 +913,7 @@ mod tests { #[actix_rt::test] async fn test_default_resource_propagation() { - let mut srv = init_service( + let srv = init_service( App::new() .service( web::scope("/app1") @@ -942,7 +941,7 @@ mod tests { #[actix_rt::test] async fn test_middleware() { - let mut srv = init_service( + let srv = init_service( App::new().service( web::scope("app") .wrap( @@ -959,7 +958,7 @@ mod tests { .await; let req = TestRequest::with_uri("/app/test").to_request(); - let resp = call_service(&mut srv, req).await; + let resp = call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::OK); assert_eq!( resp.headers().get(header::CONTENT_TYPE).unwrap(), @@ -969,7 +968,7 @@ mod tests { #[actix_rt::test] async fn test_middleware_fn() { - let mut srv = init_service( + let srv = init_service( App::new().service( web::scope("app") .wrap_fn(|req, srv| { @@ -989,7 +988,7 @@ mod tests { .await; let req = TestRequest::with_uri("/app/test").to_request(); - let resp = call_service(&mut srv, req).await; + let resp = call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::OK); assert_eq!( resp.headers().get(header::CONTENT_TYPE).unwrap(), @@ -999,7 +998,7 @@ mod tests { #[actix_rt::test] async fn test_override_data() { - let mut srv = init_service(App::new().data(1usize).service( + let srv = init_service(App::new().data(1usize).service( web::scope("app").data(10usize).route( "/t", web::get().to(|data: web::Data| { @@ -1011,13 +1010,13 @@ mod tests { .await; let req = TestRequest::with_uri("/app/t").to_request(); - let resp = call_service(&mut srv, req).await; + let resp = call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::OK); } #[actix_rt::test] async fn test_override_data_default_service() { - let mut srv = init_service(App::new().data(1usize).service( + let srv = init_service(App::new().data(1usize).service( web::scope("app").data(10usize).default_service(web::to( |data: web::Data| { assert_eq!(**data, 10); @@ -1028,13 +1027,13 @@ mod tests { .await; let req = TestRequest::with_uri("/app/t").to_request(); - let resp = call_service(&mut srv, req).await; + let resp = call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::OK); } #[actix_rt::test] async fn test_override_app_data() { - let mut srv = init_service(App::new().app_data(web::Data::new(1usize)).service( + let srv = init_service(App::new().app_data(web::Data::new(1usize)).service( web::scope("app").app_data(web::Data::new(10usize)).route( "/t", web::get().to(|data: web::Data| { @@ -1046,17 +1045,16 @@ mod tests { .await; let req = TestRequest::with_uri("/app/t").to_request(); - let resp = call_service(&mut srv, req).await; + let resp = call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::OK); } #[actix_rt::test] async fn test_scope_config() { - let mut srv = - init_service(App::new().service(web::scope("/app").configure(|s| { - s.route("/path1", web::get().to(HttpResponse::Ok)); - }))) - .await; + let srv = init_service(App::new().service(web::scope("/app").configure(|s| { + s.route("/path1", web::get().to(HttpResponse::Ok)); + }))) + .await; let req = TestRequest::with_uri("/app/path1").to_request(); let resp = srv.call(req).await.unwrap(); @@ -1065,13 +1063,12 @@ mod tests { #[actix_rt::test] async fn test_scope_config_2() { - let mut srv = - init_service(App::new().service(web::scope("/app").configure(|s| { - s.service(web::scope("/v1").configure(|s| { - s.route("/", web::get().to(HttpResponse::Ok)); - })); - }))) - .await; + let srv = init_service(App::new().service(web::scope("/app").configure(|s| { + s.service(web::scope("/v1").configure(|s| { + s.route("/", web::get().to(HttpResponse::Ok)); + })); + }))) + .await; let req = TestRequest::with_uri("/app/v1/").to_request(); let resp = srv.call(req).await.unwrap(); @@ -1080,24 +1077,20 @@ mod tests { #[actix_rt::test] async fn test_url_for_external() { - let mut srv = - init_service(App::new().service(web::scope("/app").configure(|s| { - s.service(web::scope("/v1").configure(|s| { - s.external_resource( - "youtube", - "https://youtube.com/watch/{video_id}", - ); - s.route( - "/", - web::get().to(|req: HttpRequest| { - HttpResponse::Ok().body( - req.url_for("youtube", &["xxxxxx"]).unwrap().to_string(), - ) - }), - ); - })); - }))) - .await; + let srv = init_service(App::new().service(web::scope("/app").configure(|s| { + s.service(web::scope("/v1").configure(|s| { + s.external_resource("youtube", "https://youtube.com/watch/{video_id}"); + s.route( + "/", + web::get().to(|req: HttpRequest| { + HttpResponse::Ok().body( + req.url_for("youtube", &["xxxxxx"]).unwrap().to_string(), + ) + }), + ); + })); + }))) + .await; let req = TestRequest::with_uri("/app/v1/").to_request(); let resp = srv.call(req).await.unwrap(); @@ -1108,7 +1101,7 @@ mod tests { #[actix_rt::test] async fn test_url_for_nested() { - let mut srv = init_service(App::new().service(web::scope("/a").service( + let srv = init_service(App::new().service(web::scope("/a").service( web::scope("/b").service(web::resource("/c/{stuff}").name("c").route( web::get().to(|req: HttpRequest| { HttpResponse::Ok() @@ -1119,7 +1112,7 @@ mod tests { .await; let req = TestRequest::with_uri("/a/b/c/test").to_request(); - let resp = call_service(&mut srv, req).await; + let resp = call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::OK); let body = read_body(resp).await; assert_eq!( diff --git a/src/server.rs b/src/server.rs index 8bfb27b77..59d589439 100644 --- a/src/server.rs +++ b/src/server.rs @@ -630,7 +630,7 @@ where /// } /// ``` pub fn run(self) -> Server { - self.builder.start() + self.builder.run() } } diff --git a/src/service.rs b/src/service.rs index 596eedd7f..86949dca6 100644 --- a/src/service.rs +++ b/src/service.rs @@ -542,7 +542,7 @@ mod tests { #[actix_rt::test] async fn test_service() { - let mut srv = init_service( + let srv = init_service( App::new().service(web::service("/test").name("test").finish( |req: ServiceRequest| ok(req.into_response(HttpResponse::Ok().finish())), )), @@ -552,7 +552,7 @@ mod tests { let resp = srv.call(req).await.unwrap(); assert_eq!(resp.status(), http::StatusCode::OK); - let mut srv = init_service( + let srv = init_service( App::new().service(web::service("/test").guard(guard::Get()).finish( |req: ServiceRequest| ok(req.into_response(HttpResponse::Ok().finish())), )), @@ -567,7 +567,7 @@ mod tests { #[actix_rt::test] async fn test_service_data() { - let mut srv = init_service( + let srv = init_service( App::new() .data(42u32) .service(web::service("/test").name("test").finish( diff --git a/src/test.rs b/src/test.rs index f3347bbbf..3acb520ff 100644 --- a/src/test.rs +++ b/src/test.rs @@ -60,7 +60,7 @@ pub fn default_service( /// /// #[actix_rt::test] /// async fn test_init_service() { -/// let mut app = test::init_service( +/// let app = test::init_service( /// App::new() /// .service(web::resource("/test").to(|| async { HttpResponse::Ok() })) /// ).await; @@ -116,7 +116,7 @@ where /// /// #[actix_rt::test] /// async fn test_response() { -/// let mut app = test::init_service( +/// let app = test::init_service( /// App::new() /// .service(web::resource("/test").to(|| async { /// HttpResponse::Ok() @@ -127,11 +127,11 @@ where /// let req = test::TestRequest::with_uri("/test").to_request(); /// /// // Call application -/// let resp = test::call_service(&mut app, req).await; +/// let resp = test::call_service(&app, req).await; /// assert_eq!(resp.status(), StatusCode::OK); /// } /// ``` -pub async fn call_service(app: &mut S, req: R) -> S::Response +pub async fn call_service(app: &S, req: R) -> S::Response where S: Service, Error = E>, E: std::fmt::Debug, @@ -147,7 +147,7 @@ where /// /// #[actix_rt::test] /// async fn test_index() { -/// let mut app = test::init_service( +/// let app = test::init_service( /// App::new().service( /// web::resource("/index.html") /// .route(web::post().to(|| async { @@ -160,11 +160,11 @@ where /// .header(header::CONTENT_TYPE, "application/json") /// .to_request(); /// -/// let result = test::read_response(&mut app, req).await; +/// let result = test::read_response(&app, req).await; /// assert_eq!(result, Bytes::from_static(b"welcome!")); /// } /// ``` -pub async fn read_response(app: &mut S, req: Request) -> Bytes +pub async fn read_response(app: &S, req: Request) -> Bytes where S: Service, Error = Error>, B: MessageBody + Unpin, @@ -190,7 +190,7 @@ where /// /// #[actix_rt::test] /// async fn test_index() { -/// let mut app = test::init_service( +/// let app = test::init_service( /// App::new().service( /// web::resource("/index.html") /// .route(web::post().to(|| async { @@ -203,7 +203,7 @@ where /// .header(header::CONTENT_TYPE, "application/json") /// .to_request(); /// -/// let resp = test::call_service(&mut app, req).await; +/// let resp = test::call_service(&app, req).await; /// let result = test::read_body(resp).await; /// assert_eq!(result, Bytes::from_static(b"welcome!")); /// } @@ -234,7 +234,7 @@ where /// /// #[actix_rt::test] /// async fn test_post_person() { -/// let mut app = test::init_service( +/// let app = test::init_service( /// App::new().service( /// web::resource("/people") /// .route(web::post().to(|person: web::Json| async { @@ -294,7 +294,7 @@ where /// /// #[actix_rt::test] /// async fn test_add_person() { -/// let mut app = test::init_service( +/// let app = test::init_service( /// App::new().service( /// web::resource("/people") /// .route(web::post().to(|person: web::Json| async { @@ -314,7 +314,7 @@ where /// let result: Person = test::read_response_json(&mut app, req).await; /// } /// ``` -pub async fn read_response_json(app: &mut S, req: Request) -> T +pub async fn read_response_json(app: &S, req: Request) -> T where S: Service, Error = Error>, B: MessageBody + Unpin, @@ -569,7 +569,7 @@ impl TestRequest { } /// Complete request creation, calls service and waits for response future completion. - pub async fn send_request(self, app: &mut S) -> S::Response + pub async fn send_request(self, app: &S) -> S::Response where S: Service, Error = E>, E: std::fmt::Debug, @@ -595,7 +595,7 @@ impl TestRequest { /// /// #[actix_rt::test] /// async fn test_example() { -/// let mut srv = test::start( +/// let srv = test::start( /// || App::new().service( /// web::resource("/").to(my_handler)) /// ); @@ -635,7 +635,7 @@ where /// /// #[actix_rt::test] /// async fn test_example() { -/// let mut srv = test::start_with(test::config().h1(), || +/// let srv = test::start_with(test::config().h1(), || /// App::new().service(web::resource("/").to(my_handler)) /// ); /// @@ -667,7 +667,7 @@ where // run server in separate thread thread::spawn(move || { - let sys = System::new("actix-test-server"); + let sys = System::new(); let tcp = net::TcpListener::bind("127.0.0.1:0").unwrap(); let local_addr = tcp.local_addr().unwrap(); let factory = factory.clone(); @@ -760,7 +760,7 @@ where .unwrap(); sys.block_on(async { - let srv = srv.start(); + let srv = srv.run(); tx.send((System::current(), srv, local_addr)).unwrap(); }); @@ -1043,7 +1043,7 @@ mod tests { #[actix_rt::test] async fn test_request_methods() { - let mut app = init_service( + let app = init_service( App::new().service( web::resource("/index.html") .route(web::put().to(|| HttpResponse::Ok().body("put!"))) @@ -1058,7 +1058,7 @@ mod tests { .insert_header((header::CONTENT_TYPE, "application/json")) .to_request(); - let result = read_response(&mut app, put_req).await; + let result = read_response(&app, put_req).await; assert_eq!(result, Bytes::from_static(b"put!")); let patch_req = TestRequest::patch() @@ -1066,17 +1066,17 @@ mod tests { .insert_header((header::CONTENT_TYPE, "application/json")) .to_request(); - let result = read_response(&mut app, patch_req).await; + let result = read_response(&app, patch_req).await; assert_eq!(result, Bytes::from_static(b"patch!")); let delete_req = TestRequest::delete().uri("/index.html").to_request(); - let result = read_response(&mut app, delete_req).await; + let result = read_response(&app, delete_req).await; assert_eq!(result, Bytes::from_static(b"delete!")); } #[actix_rt::test] async fn test_response() { - let mut app = init_service( + let app = init_service( App::new().service( web::resource("/index.html") .route(web::post().to(|| HttpResponse::Ok().body("welcome!"))), @@ -1089,13 +1089,13 @@ mod tests { .insert_header((header::CONTENT_TYPE, "application/json")) .to_request(); - let result = read_response(&mut app, req).await; + let result = read_response(&app, req).await; assert_eq!(result, Bytes::from_static(b"welcome!")); } #[actix_rt::test] async fn test_send_request() { - let mut app = init_service( + let app = init_service( App::new().service( web::resource("/index.html") .route(web::get().to(|| HttpResponse::Ok().body("welcome!"))), @@ -1105,7 +1105,7 @@ mod tests { let resp = TestRequest::get() .uri("/index.html") - .send_request(&mut app) + .send_request(&app) .await; let result = read_body(resp).await; @@ -1120,7 +1120,7 @@ mod tests { #[actix_rt::test] async fn test_response_json() { - let mut app = init_service(App::new().service(web::resource("/people").route( + let app = init_service(App::new().service(web::resource("/people").route( web::post().to(|person: web::Json| HttpResponse::Ok().json(person)), ))) .await; @@ -1133,13 +1133,13 @@ mod tests { .set_payload(payload) .to_request(); - let result: Person = read_response_json(&mut app, req).await; + let result: Person = read_response_json(&app, req).await; assert_eq!(&result.id, "12345"); } #[actix_rt::test] async fn test_body_json() { - let mut app = init_service(App::new().service(web::resource("/people").route( + let app = init_service(App::new().service(web::resource("/people").route( web::post().to(|person: web::Json| HttpResponse::Ok().json(person)), ))) .await; @@ -1150,7 +1150,7 @@ mod tests { .uri("/people") .insert_header((header::CONTENT_TYPE, "application/json")) .set_payload(payload) - .send_request(&mut app) + .send_request(&app) .await; let result: Person = read_body_json(resp).await; @@ -1159,7 +1159,7 @@ mod tests { #[actix_rt::test] async fn test_request_response_form() { - let mut app = init_service(App::new().service(web::resource("/people").route( + let app = init_service(App::new().service(web::resource("/people").route( web::post().to(|person: web::Form| HttpResponse::Ok().json(person)), ))) .await; @@ -1176,14 +1176,14 @@ mod tests { assert_eq!(req.content_type(), "application/x-www-form-urlencoded"); - let result: Person = read_response_json(&mut app, req).await; + let result: Person = read_response_json(&app, req).await; assert_eq!(&result.id, "12345"); assert_eq!(&result.name, "User name"); } #[actix_rt::test] async fn test_request_response_json() { - let mut app = init_service(App::new().service(web::resource("/people").route( + let app = init_service(App::new().service(web::resource("/people").route( web::post().to(|person: web::Json| HttpResponse::Ok().json(person)), ))) .await; @@ -1200,7 +1200,7 @@ mod tests { assert_eq!(req.content_type(), "application/json"); - let result: Person = read_response_json(&mut app, req).await; + let result: Person = read_response_json(&app, req).await; assert_eq!(&result.id, "12345"); assert_eq!(&result.name, "User name"); } @@ -1218,7 +1218,7 @@ mod tests { } } - let mut app = init_service( + let app = init_service( App::new().service(web::resource("/index.html").to(async_with_block)), ) .await; @@ -1235,7 +1235,7 @@ mod tests { HttpResponse::Ok() } - let mut app = init_service( + let app = init_service( App::new() .data(10usize) .service(web::resource("/index.html").to(handler)), @@ -1291,7 +1291,7 @@ mod tests { .data(addr.clone()) .service(web::resource("/").to(actor_handler)); - let mut app = init_service(srv).await; + let app = init_service(srv).await; let req = TestRequest::post().uri("/").to_request(); let res = app.call(req).await.unwrap(); diff --git a/src/types/payload.rs b/src/types/payload.rs index e8fba0632..724034522 100644 --- a/src/types/payload.rs +++ b/src/types/payload.rs @@ -392,7 +392,7 @@ mod tests { "payload is probably json string" } - let mut srv = init_service( + let srv = init_service( App::new() .service( web::resource("/bytes-app-data") @@ -422,43 +422,43 @@ mod tests { .await; let req = TestRequest::with_uri("/bytes-app-data").to_request(); - let resp = call_service(&mut srv, req).await; + let resp = call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::BAD_REQUEST); let req = TestRequest::with_uri("/bytes-data").to_request(); - let resp = call_service(&mut srv, req).await; + let resp = call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::BAD_REQUEST); let req = TestRequest::with_uri("/string-app-data").to_request(); - let resp = call_service(&mut srv, req).await; + let resp = call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::BAD_REQUEST); let req = TestRequest::with_uri("/string-data").to_request(); - let resp = call_service(&mut srv, req).await; + let resp = call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::BAD_REQUEST); let req = TestRequest::with_uri("/bytes-app-data") .insert_header(header::ContentType(mime::APPLICATION_JSON)) .to_request(); - let resp = call_service(&mut srv, req).await; + let resp = call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::OK); let req = TestRequest::with_uri("/bytes-data") .insert_header(header::ContentType(mime::APPLICATION_JSON)) .to_request(); - let resp = call_service(&mut srv, req).await; + let resp = call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::OK); let req = TestRequest::with_uri("/string-app-data") .insert_header(header::ContentType(mime::APPLICATION_JSON)) .to_request(); - let resp = call_service(&mut srv, req).await; + let resp = call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::OK); let req = TestRequest::with_uri("/string-data") .insert_header(header::ContentType(mime::APPLICATION_JSON)) .to_request(); - let resp = call_service(&mut srv, req).await; + let resp = call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::OK); } diff --git a/tests/test_httpserver.rs b/tests/test_httpserver.rs index 5eca14931..78d4ef685 100644 --- a/tests/test_httpserver.rs +++ b/tests/test_httpserver.rs @@ -13,7 +13,7 @@ async fn test_start() { let (tx, rx) = mpsc::channel(); thread::spawn(move || { - let sys = actix_rt::System::new("test"); + let sys = actix_rt::System::new(); sys.block_on(async { let srv = HttpServer::new(|| { @@ -91,7 +91,7 @@ async fn test_start_ssl() { let (tx, rx) = mpsc::channel(); thread::spawn(move || { - let sys = actix_rt::System::new("test"); + let sys = actix_rt::System::new(); let builder = ssl_acceptor().unwrap(); let srv = HttpServer::new(|| { From 7cfed73be8d4349d0e2795e4fde1b3beba5e77c0 Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Sat, 6 Feb 2021 19:20:35 -0800 Subject: [PATCH 006/339] fix memory usage for h1 and read bug on buffer size. (#1929) --- actix-http/src/h1/decoder.rs | 27 ++++++++++++++++-------- actix-http/src/h1/dispatcher.rs | 37 +++++++++++++++++++++++++-------- 2 files changed, 46 insertions(+), 18 deletions(-) diff --git a/actix-http/src/h1/decoder.rs b/actix-http/src/h1/decoder.rs index 85379b084..9da958563 100644 --- a/actix-http/src/h1/decoder.rs +++ b/actix-http/src/h1/decoder.rs @@ -14,7 +14,7 @@ use crate::header::HeaderMap; use crate::message::{ConnectionType, ResponseHead}; use crate::request::Request; -const MAX_BUFFER_SIZE: usize = 131_072; +pub(crate) const MAX_BUFFER_SIZE: usize = 131_072; const MAX_HEADERS: usize = 96; /// Incoming message decoder @@ -203,7 +203,15 @@ impl MessageType for Request { (len, method, uri, version, req.headers.len()) } - httparse::Status::Partial => return Ok(None), + httparse::Status::Partial => { + return if src.len() >= MAX_BUFFER_SIZE { + trace!("MAX_BUFFER_SIZE unprocessed data reached, closing"); + Err(ParseError::TooLarge) + } else { + // Return None to notify more read are needed for parsing request + Ok(None) + }; + } } }; @@ -222,9 +230,6 @@ impl MessageType for Request { PayloadLength::None => { if method == Method::CONNECT { PayloadType::Stream(PayloadDecoder::eof()) - } else if src.len() >= MAX_BUFFER_SIZE { - trace!("MAX_BUFFER_SIZE unprocessed data reached, closing"); - return Err(ParseError::TooLarge); } else { PayloadType::None } @@ -273,7 +278,14 @@ impl MessageType for ResponseHead { (len, version, status, res.headers.len()) } - httparse::Status::Partial => return Ok(None), + httparse::Status::Partial => { + return if src.len() >= MAX_BUFFER_SIZE { + error!("MAX_BUFFER_SIZE unprocessed data reached, closing"); + Err(ParseError::TooLarge) + } else { + Ok(None) + } + } } }; @@ -289,9 +301,6 @@ impl MessageType for ResponseHead { } else if status == StatusCode::SWITCHING_PROTOCOLS { // switching protocol or connect PayloadType::Stream(PayloadDecoder::eof()) - } else if src.len() >= MAX_BUFFER_SIZE { - error!("MAX_BUFFER_SIZE unprocessed data reached, closing"); - return Err(ParseError::TooLarge); } else { // for HTTP/1.0 read to eof and close connection if msg.version == Version::HTTP_10 { diff --git a/actix-http/src/h1/dispatcher.rs b/actix-http/src/h1/dispatcher.rs index 90a5f0e06..e65a1bd56 100644 --- a/actix-http/src/h1/dispatcher.rs +++ b/actix-http/src/h1/dispatcher.rs @@ -29,8 +29,8 @@ use super::codec::Codec; use super::payload::{Payload, PayloadSender, PayloadStatus}; use super::{Message, MessageType}; -const LW_BUFFER_SIZE: usize = 4096; -const HW_BUFFER_SIZE: usize = 32_768; +const LW_BUFFER_SIZE: usize = 1024; +const HW_BUFFER_SIZE: usize = 1024 * 8; const MAX_PIPELINED_MESSAGES: usize = 16; bitflags! { @@ -404,7 +404,7 @@ where }, StateProj::SendPayload(mut stream) => { loop { - if this.write_buf.len() < HW_BUFFER_SIZE { + if this.write_buf.len() < super::payload::MAX_BUFFER_SIZE { match stream.as_mut().poll_next(cx) { Poll::Ready(Some(Ok(item))) => { this.codec.encode( @@ -605,6 +605,8 @@ where } } } + // decode is partial and buffer is not full yet. + // break and wait for more read. Ok(None) => break, Err(ParseError::Io(e)) => { self.as_mut().client_disconnected(); @@ -612,6 +614,18 @@ where *this.error = Some(DispatchError::Io(e)); break; } + Err(ParseError::TooLarge) => { + if let Some(mut payload) = this.payload.take() { + payload.set_error(PayloadError::Overflow); + } + // Requests overflow buffer size should be responded with 413 + this.messages.push_back(DispatcherMessage::Error( + Response::PayloadTooLarge().finish().drop_body(), + )); + this.flags.insert(Flags::READ_DISCONNECT); + *this.error = Some(ParseError::TooLarge.into()); + break; + } Err(e) => { if let Some(mut payload) = this.payload.take() { payload.set_error(PayloadError::EncodingCorrupted); @@ -890,12 +904,7 @@ where let mut read_some = false; loop { - // If buf is full return but do not disconnect since - // there is more reading to be done - if buf.len() >= HW_BUFFER_SIZE { - return Ok(Some(false)); - } - + // reserve capacity for buffer let remaining = buf.capacity() - buf.len(); if remaining < LW_BUFFER_SIZE { buf.reserve(HW_BUFFER_SIZE - remaining); @@ -909,6 +918,16 @@ where if n == 0 { return Ok(Some(true)); } else { + // If buf is full return but do not disconnect since + // there is more reading to be done + if buf.len() >= super::decoder::MAX_BUFFER_SIZE { + // at this point it's not known io is still scheduled to + // be waked up. so force wake up dispatcher just in case. + // TODO: figure out the overhead. + cx.waker().wake_by_ref(); + return Ok(Some(false)); + } + read_some = true; } } From 830fb2cdb2e52c97ffc7a53e1469aaf5d5e39f1a Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Sat, 6 Feb 2021 19:51:36 -0800 Subject: [PATCH 007/339] properly drop h2 connection (#1926) --- actix-http/src/client/connection.rs | 82 ++++++++++++++++++++++++++++- actix-http/src/client/h2proto.rs | 5 +- actix-http/src/client/pool.rs | 11 ++-- 3 files changed, 89 insertions(+), 9 deletions(-) diff --git a/actix-http/src/client/connection.rs b/actix-http/src/client/connection.rs index 26d392120..4c6a6dcb8 100644 --- a/actix-http/src/client/connection.rs +++ b/actix-http/src/client/connection.rs @@ -1,9 +1,11 @@ use std::future::Future; +use std::ops::{Deref, DerefMut}; use std::pin::Pin; use std::task::{Context, Poll}; use std::{fmt, io, time}; use actix_codec::{AsyncRead, AsyncWrite, Framed, ReadBuf}; +use actix_rt::task::JoinHandle; use bytes::Bytes; use futures_core::future::LocalBoxFuture; use futures_util::future::{err, Either, FutureExt, Ready}; @@ -21,7 +23,53 @@ use super::{h1proto, h2proto}; pub(crate) enum ConnectionType { H1(Io), - H2(SendRequest), + H2(H2Connection), +} + +// h2 connection has two parts: SendRequest and Connection. +// Connection is spawned as async task on runtime and H2Connection would hold a handle for +// this task. So it can wake up and quit the task when SendRequest is dropped. +pub(crate) struct H2Connection { + handle: JoinHandle<()>, + sender: SendRequest, +} + +impl H2Connection { + pub(crate) fn new( + sender: SendRequest, + connection: h2::client::Connection, + ) -> Self + where + Io: AsyncRead + AsyncWrite + Unpin + 'static, + { + let handle = actix_rt::spawn(async move { + let _ = connection.await; + }); + + Self { handle, sender } + } +} + +// wake up waker when drop +impl Drop for H2Connection { + fn drop(&mut self) { + self.handle.abort(); + } +} + +// only expose sender type to public. +impl Deref for H2Connection { + type Target = SendRequest; + + fn deref(&self) -> &Self::Target { + &self.sender + } +} + +impl DerefMut for H2Connection { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.sender + } } pub trait Connection { @@ -266,3 +314,35 @@ where } } } + +#[cfg(test)] +mod test { + use std::net; + + use actix_rt::net::TcpStream; + + use super::*; + + #[actix_rt::test] + async fn test_h2_connection_drop() { + let addr = "127.0.0.1:0".parse::().unwrap(); + let listener = net::TcpListener::bind(addr).unwrap(); + let local = listener.local_addr().unwrap(); + + std::thread::spawn(move || while listener.accept().is_ok() {}); + + let tcp = TcpStream::connect(local).await.unwrap(); + let (sender, connection) = h2::client::handshake(tcp).await.unwrap(); + let conn = H2Connection::new(sender.clone(), connection); + + assert!(sender.clone().ready().await.is_ok()); + assert!(h2::client::SendRequest::clone(&*conn).ready().await.is_ok()); + + drop(conn); + + match sender.ready().await { + Ok(_) => panic!("connection should be gone and can not be ready"), + Err(e) => assert!(e.is_io()), + }; + } +} diff --git a/actix-http/src/client/h2proto.rs b/actix-http/src/client/h2proto.rs index 76915f214..a70bc1738 100644 --- a/actix-http/src/client/h2proto.rs +++ b/actix-http/src/client/h2proto.rs @@ -22,9 +22,10 @@ use super::config::ConnectorConfig; use super::connection::{ConnectionType, IoConnection}; use super::error::SendRequestError; use super::pool::Acquired; +use crate::client::connection::H2Connection; pub(crate) async fn send_request( - mut io: SendRequest, + mut io: H2Connection, head: RequestHeadType, body: B, created: time::Instant, @@ -173,7 +174,7 @@ async fn send_body( /// release SendRequest object fn release( - io: SendRequest, + io: H2Connection, pool: Option>, created: time::Instant, close: bool, diff --git a/actix-http/src/client/pool.rs b/actix-http/src/client/pool.rs index 1eebef53b..867ba5c0c 100644 --- a/actix-http/src/client/pool.rs +++ b/actix-http/src/client/pool.rs @@ -26,6 +26,7 @@ use super::connection::{ConnectionType, IoConnection}; use super::error::ConnectError; use super::h2proto::handshake; use super::Connect; +use crate::client::connection::H2Connection; #[derive(Clone, Copy, PartialEq)] /// Protocol version @@ -139,10 +140,9 @@ where Some(guard.consume()), )) } else { - let (snd, connection) = handshake(io, &config).await?; - actix_rt::spawn(connection.map(|_| ())); + let (sender, connection) = handshake(io, &config).await?; Ok(IoConnection::new( - ConnectionType::H2(snd), + ConnectionType::H2(H2Connection::new(sender, connection)), Instant::now(), Some(guard.consume()), )) @@ -566,11 +566,10 @@ where if let Some(ref mut h2) = this.h2 { return match Pin::new(h2).poll(cx) { - Poll::Ready(Ok((snd, connection))) => { - actix_rt::spawn(connection.map(|_| ())); + Poll::Ready(Ok((sender, connection))) => { let rx = this.rx.take().unwrap(); let _ = rx.send(Ok(IoConnection::new( - ConnectionType::H2(snd), + ConnectionType::H2(H2Connection::new(sender, connection)), Instant::now(), Some(Acquired(this.key.clone(), this.inner.take())), ))); From 9eaea6a2fd2c9005e7e64887569dcc27fec98596 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sun, 7 Feb 2021 03:54:58 +0000 Subject: [PATCH 008/339] tweak feature flags --- Cargo.toml | 12 ++++++------ README.md | 9 ++++----- actix-http-test/Cargo.toml | 6 +++--- actix-http-test/src/lib.rs | 5 ++++- actix-http/Cargo.toml | 10 +++++----- actix-http/tests/test_openssl.rs | 5 ++++- actix-http/tests/test_rustls.rs | 5 ++++- actix-web-actors/Cargo.toml | 2 +- awc/Cargo.toml | 10 +++++----- awc/tests/test_connector.rs | 7 +++++-- awc/tests/test_rustls_client.rs | 2 +- awc/tests/test_ssl_client.rs | 7 +++++-- src/lib.rs | 5 +++++ src/test.rs | 10 +++++----- tests/test_httpserver.rs | 11 ++++++++--- tests/test_server.rs | 11 ++++++++--- 16 files changed, 73 insertions(+), 44 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 1e2c35d6a..479a89f87 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -47,10 +47,10 @@ compress = ["actix-http/compress", "awc/compress"] secure-cookies = ["actix-http/secure-cookies"] # openssl -openssl = ["actix-tls/accept", "actix-tls/openssl", "awc/openssl", "open-ssl"] +openssl = ["tls_openssl", "actix-tls/accept", "actix-tls/openssl", "awc/openssl"] # rustls -rustls = ["actix-tls/accept", "actix-tls/rustls", "awc/rustls", "rust-tls"] +rustls = ["tls_rustls", "actix-tls/accept", "actix-tls/rustls", "awc/rustls"] [[example]] name = "basic" @@ -86,7 +86,7 @@ actix-web-codegen = "0.4.0" actix-http = "3.0.0-beta.1" awc = { version = "3.0.0-beta.1", default-features = false } -ahash = "0.6" +ahash = "0.7" bytes = "1" derive_more = "0.99.5" either = "1.5.3" @@ -101,10 +101,10 @@ regex = "1.4" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" serde_urlencoded = "0.7" -time = { version = "0.2.7", default-features = false, features = ["std"] } +time = { version = "0.2.23", default-features = false, features = ["std"] } url = "2.1" -open-ssl = { package = "openssl", version = "0.10", optional = true } -rust-tls = { package = "rustls", version = "0.19.0", optional = true } +tls_openssl = { package = "openssl", version = "0.10.9", optional = true } +tls_rustls = { package = "rustls", version = "0.19.0", optional = true } smallvec = "1.6" [dev-dependencies] diff --git a/README.md b/README.md index 956bb3741..8a703117f 100644 --- a/README.md +++ b/README.md @@ -11,10 +11,9 @@ ![License](https://img.shields.io/crates/l/actix-web.svg) [![Dependency Status](https://deps.rs/crate/actix-web/3.3.2/status.svg)](https://deps.rs/crate/actix-web/3.3.2)
-[![Build Status](https://travis-ci.org/actix/actix-web.svg?branch=master)](https://travis-ci.org/actix/actix-web) +[![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) -[![Download](https://img.shields.io/crates/d/actix-web.svg)](https://crates.io/crates/actix-web) -[![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) +![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)

@@ -99,9 +98,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 - [http://www.apache.org/licenses/LICENSE-2.0](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)) + [http://opensource.org/licenses/MIT]) at your option. diff --git a/actix-http-test/Cargo.toml b/actix-http-test/Cargo.toml index 0a804e109..25fd74a10 100644 --- a/actix-http-test/Cargo.toml +++ b/actix-http-test/Cargo.toml @@ -26,7 +26,7 @@ path = "src/lib.rs" default = [] # openssl -openssl = ["open-ssl", "awc/openssl"] +openssl = ["tls-openssl", "awc/openssl"] [dependencies] actix-service = "2.0.0-beta.4" @@ -47,8 +47,8 @@ serde = "1.0" serde_json = "1.0" slab = "0.4" serde_urlencoded = "0.7" -time = { version = "0.2.7", default-features = false, features = ["std"] } -open-ssl = { version = "0.10", package = "openssl", optional = true } +time = { version = "0.2.23", default-features = false, features = ["std"] } +tls-openssl = { version = "0.10.9", package = "openssl", optional = true } [dev-dependencies] actix-web = "4.0.0-beta.1" diff --git a/actix-http-test/src/lib.rs b/actix-http-test/src/lib.rs index 2958b7f59..fcf8b3476 100644 --- a/actix-http-test/src/lib.rs +++ b/actix-http-test/src/lib.rs @@ -4,6 +4,9 @@ #![doc(html_logo_url = "https://actix.rs/img/logo.png")] #![doc(html_favicon_url = "https://actix.rs/favicon.ico")] +#[cfg(feature = "openssl")] +extern crate tls_openssl as openssl; + use std::sync::mpsc; use std::{net, thread, time}; @@ -82,7 +85,7 @@ pub async fn test_server_with_addr>( let connector = { #[cfg(feature = "openssl")] { - use open_ssl::ssl::{SslConnector, SslMethod, SslVerifyMode}; + use openssl::ssl::{SslConnector, SslMethod, SslVerifyMode}; let mut builder = SslConnector::builder(SslMethod::tls()).unwrap(); builder.set_verify(SslVerifyMode::NONE); diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index f55dc3b69..22a54f569 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -58,7 +58,7 @@ encoding_rs = "0.8" futures-channel = { version = "0.3.7", default-features = false, features = ["alloc"] } futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] } futures-util = { version = "0.3.7", default-features = false, features = ["alloc", "sink"] } -ahash = "0.6" +ahash = "0.7" h2 = "0.3.0" http = "0.2.2" httparse = "1.3" @@ -78,7 +78,7 @@ sha-1 = "0.9" smallvec = "1.6" slab = "0.4" serde_urlencoded = "0.7" -time = { version = "0.2.7", default-features = false, features = ["std"] } +time = { version = "0.2.23", default-features = false, features = ["std"] } # compression brotli2 = { version="0.3.2", optional = true } @@ -89,10 +89,10 @@ actix-server = "2.0.0-beta.3" actix-http-test = { version = "3.0.0-beta.1", features = ["openssl"] } actix-tls = { version = "3.0.0-beta.2", features = ["openssl"] } criterion = "0.3" -env_logger = "0.7" +env_logger = "0.8" serde_derive = "1.0" -open-ssl = { version="0.10", package = "openssl" } -rust-tls = { version="0.19", package = "rustls" } +tls-openssl = { version = "0.10", package = "openssl" } +tls-rustls = { version = "0.19", package = "rustls" } [[bench]] name = "write-camel-case" diff --git a/actix-http/tests/test_openssl.rs b/actix-http/tests/test_openssl.rs index f20cfd70c..73bbe6208 100644 --- a/actix-http/tests/test_openssl.rs +++ b/actix-http/tests/test_openssl.rs @@ -1,4 +1,7 @@ #![cfg(feature = "openssl")] + +extern crate tls_openssl as openssl; + use std::io; use actix_http::error::{ErrorBadRequest, PayloadError}; @@ -11,7 +14,7 @@ use actix_service::{fn_service, ServiceFactoryExt}; use bytes::{Bytes, BytesMut}; use futures_util::future::{err, ok, ready}; use futures_util::stream::{once, Stream, StreamExt}; -use open_ssl::ssl::{AlpnError, SslAcceptor, SslFiletype, SslMethod}; +use openssl::ssl::{AlpnError, SslAcceptor, SslFiletype, SslMethod}; async fn load_body(stream: S) -> Result where diff --git a/actix-http/tests/test_rustls.rs b/actix-http/tests/test_rustls.rs index 2f6b31f49..59ffcfeb0 100644 --- a/actix-http/tests/test_rustls.rs +++ b/actix-http/tests/test_rustls.rs @@ -1,4 +1,7 @@ #![cfg(feature = "rustls")] + +extern crate tls_rustls as rustls; + use actix_http::error::PayloadError; use actix_http::http::header::{self, HeaderName, HeaderValue}; use actix_http::http::{Method, StatusCode, Version}; @@ -9,7 +12,7 @@ use actix_service::{fn_factory_with_config, fn_service}; use bytes::{Bytes, BytesMut}; use futures_util::future::{self, err, ok}; use futures_util::stream::{once, Stream, StreamExt}; -use rust_tls::{ +use rustls::{ internal::pemfile::{certs, pkcs8_private_keys}, NoClientAuth, ServerConfig as RustlsServerConfig, }; diff --git a/actix-web-actors/Cargo.toml b/actix-web-actors/Cargo.toml index c6fd1c233..522279fd0 100644 --- a/actix-web-actors/Cargo.toml +++ b/actix-web-actors/Cargo.toml @@ -29,5 +29,5 @@ tokio = { version = "1", features = ["sync"] } [dev-dependencies] actix-rt = "2" -env_logger = "0.7" +env_logger = "0.8" futures-util = { version = "0.3.7", default-features = false } diff --git a/awc/Cargo.toml b/awc/Cargo.toml index 24275b6f5..7a9130780 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -28,10 +28,10 @@ features = ["openssl", "rustls", "compress"] default = ["compress"] # openssl -openssl = ["open-ssl", "actix-http/openssl"] +openssl = ["tls-openssl", "actix-http/openssl"] # rustls -rustls = ["rust-tls", "actix-http/rustls"] +rustls = ["tls-rustls", "actix-http/rustls"] # content-encoding support compress = ["actix-http/compress"] @@ -54,8 +54,8 @@ rand = "0.8" serde = "1.0" serde_json = "1.0" serde_urlencoded = "0.7" -open-ssl = { version = "0.10", package = "openssl", optional = true } -rust-tls = { version = "0.19.0", package = "rustls", optional = true, features = ["dangerous_configuration"] } +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.1", features = ["openssl"] } @@ -68,6 +68,6 @@ actix-tls = { version = "3.0.0-beta.3", features = ["openssl", "rustls"] } brotli2 = "0.3.2" flate2 = "1.0.13" futures-util = { version = "0.3.7", default-features = false } -env_logger = "0.7" +env_logger = "0.8" rcgen = "0.8" webpki = "0.21" diff --git a/awc/tests/test_connector.rs b/awc/tests/test_connector.rs index e500801c4..4e4fa5833 100644 --- a/awc/tests/test_connector.rs +++ b/awc/tests/test_connector.rs @@ -1,10 +1,13 @@ #![cfg(feature = "openssl")] + +extern crate tls_openssl as openssl; + use actix_http::HttpService; use actix_http_test::test_server; use actix_service::{map_config, ServiceFactoryExt}; use actix_web::http::Version; use actix_web::{dev::AppConfig, web, App, HttpResponse}; -use open_ssl::ssl::{SslAcceptor, SslConnector, SslFiletype, SslMethod, SslVerifyMode}; +use openssl::ssl::{SslAcceptor, SslConnector, SslFiletype, SslMethod, SslVerifyMode}; fn ssl_acceptor() -> SslAcceptor { // load ssl keys @@ -20,7 +23,7 @@ fn ssl_acceptor() -> SslAcceptor { if protos.windows(3).any(|window| window == H2) { Ok(b"h2") } else { - Err(open_ssl::ssl::AlpnError::NOACK) + Err(openssl::ssl::AlpnError::NOACK) } }); builder.set_alpn_protos(b"\x02h2").unwrap(); diff --git a/awc/tests/test_rustls_client.rs b/awc/tests/test_rustls_client.rs index 2da3d9696..b0f3b71be 100644 --- a/awc/tests/test_rustls_client.rs +++ b/awc/tests/test_rustls_client.rs @@ -1,6 +1,6 @@ #![cfg(feature = "rustls")] -extern crate rust_tls as rustls; +extern crate tls_rustls as rustls; use std::{ io::BufReader, diff --git a/awc/tests/test_ssl_client.rs b/awc/tests/test_ssl_client.rs index de1514042..064824d59 100644 --- a/awc/tests/test_ssl_client.rs +++ b/awc/tests/test_ssl_client.rs @@ -1,4 +1,7 @@ #![cfg(feature = "openssl")] + +extern crate tls_openssl as openssl; + use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::Arc; @@ -8,7 +11,7 @@ use actix_service::{map_config, pipeline_factory, ServiceFactoryExt}; use actix_web::http::Version; use actix_web::{dev::AppConfig, web, App, HttpResponse}; use futures_util::future::ok; -use open_ssl::ssl::{SslAcceptor, SslConnector, SslFiletype, SslMethod, SslVerifyMode}; +use openssl::ssl::{SslAcceptor, SslConnector, SslFiletype, SslMethod, SslVerifyMode}; fn ssl_acceptor() -> SslAcceptor { // load ssl keys @@ -24,7 +27,7 @@ fn ssl_acceptor() -> SslAcceptor { if protos.windows(3).any(|window| window == H2) { Ok(b"h2") } else { - Err(open_ssl::ssl::AlpnError::NOACK) + Err(openssl::ssl::AlpnError::NOACK) } }); builder.set_alpn_protos(b"\x02h2").unwrap(); diff --git a/src/lib.rs b/src/lib.rs index 52471f4b3..46fbc2937 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -71,6 +71,11 @@ #![doc(html_logo_url = "https://actix.rs/img/logo.png")] #![doc(html_favicon_url = "https://actix.rs/favicon.ico")] +#[cfg(feature = "openssl")] +extern crate tls_openssl as openssl; +#[cfg(feature = "rustls")] +extern crate tls_rustls as rustls; + mod app; mod app_service; mod config; diff --git a/src/test.rs b/src/test.rs index 3acb520ff..62c329c91 100644 --- a/src/test.rs +++ b/src/test.rs @@ -773,7 +773,7 @@ where let connector = { #[cfg(feature = "openssl")] { - use open_ssl::ssl::{SslConnector, SslMethod, SslVerifyMode}; + use openssl::ssl::{SslConnector, SslMethod, SslVerifyMode}; let mut builder = SslConnector::builder(SslMethod::tls()).unwrap(); builder.set_verify(SslVerifyMode::NONE); @@ -825,9 +825,9 @@ enum HttpVer { enum StreamType { Tcp, #[cfg(feature = "openssl")] - Openssl(open_ssl::ssl::SslAcceptor), + Openssl(openssl::ssl::SslAcceptor), #[cfg(feature = "rustls")] - Rustls(rust_tls::ServerConfig), + Rustls(rustls::ServerConfig), } impl Default for TestServerConfig { @@ -865,14 +865,14 @@ impl TestServerConfig { /// Start openssl server #[cfg(feature = "openssl")] - pub fn openssl(mut self, acceptor: open_ssl::ssl::SslAcceptor) -> Self { + pub fn openssl(mut self, acceptor: openssl::ssl::SslAcceptor) -> Self { self.stream = StreamType::Openssl(acceptor); self } /// Start rustls server #[cfg(feature = "rustls")] - pub fn rustls(mut self, config: rust_tls::ServerConfig) -> Self { + pub fn rustls(mut self, config: rustls::ServerConfig) -> Self { self.stream = StreamType::Rustls(config); self } diff --git a/tests/test_httpserver.rs b/tests/test_httpserver.rs index 78d4ef685..cd5a75d49 100644 --- a/tests/test_httpserver.rs +++ b/tests/test_httpserver.rs @@ -2,7 +2,12 @@ use std::sync::mpsc; use std::{thread, time::Duration}; #[cfg(feature = "openssl")] -use open_ssl::ssl::SslAcceptorBuilder; +extern crate tls_openssl as openssl; +#[cfg(feature = "rustls")] +extern crate tls_rustls as rustls; + +#[cfg(feature = "openssl")] +use openssl::ssl::SslAcceptorBuilder; use actix_web::{test, web, App, HttpResponse, HttpServer}; @@ -70,7 +75,7 @@ async fn test_start() { #[allow(clippy::unnecessary_wraps)] #[cfg(feature = "openssl")] fn ssl_acceptor() -> std::io::Result { - use open_ssl::ssl::{SslAcceptor, SslFiletype, SslMethod}; + use openssl::ssl::{SslAcceptor, SslFiletype, SslMethod}; // load ssl keys let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap(); builder @@ -116,7 +121,7 @@ async fn test_start_ssl() { }); let (srv, sys) = rx.recv().unwrap(); - use open_ssl::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 diff --git a/tests/test_server.rs b/tests/test_server.rs index 0f51a2fdb..c626474cf 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -1,3 +1,8 @@ +#[cfg(feature = "openssl")] +extern crate tls_openssl as openssl; +#[cfg(feature = "rustls")] +extern crate tls_rustls as rustls; + use std::{ future::Future, io::{Read, Write}, @@ -713,7 +718,7 @@ async fn test_brotli_encoding_large() { #[actix_rt::test] async fn test_brotli_encoding_large_openssl() { // load ssl keys - use open_ssl::ssl::{SslAcceptor, SslFiletype, SslMethod}; + use openssl::ssl::{SslAcceptor, SslFiletype, SslMethod}; let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap(); builder .set_private_key_file("tests/key.pem", SslFiletype::PEM) @@ -753,8 +758,8 @@ async fn test_brotli_encoding_large_openssl() { #[cfg(all(feature = "rustls", feature = "openssl"))] #[actix_rt::test] async fn test_reading_deflate_encoding_large_random_rustls() { - use rust_tls::internal::pemfile::{certs, pkcs8_private_keys}; - use rust_tls::{NoClientAuth, ServerConfig}; + use rustls::internal::pemfile::{certs, pkcs8_private_keys}; + use rustls::{NoClientAuth, ServerConfig}; use std::fs::File; use std::io::BufReader; From 50309aa295ba38eb74998092850734b8c8bfa981 Mon Sep 17 00:00:00 2001 From: Jens Reidel Date: Sun, 7 Feb 2021 05:50:23 +0100 Subject: [PATCH 009/339] Use askama-escape for html escaping (#1953) --- actix-files/CHANGES.md | 2 ++ actix-files/Cargo.toml | 3 ++- actix-files/src/directory.rs | 4 ++-- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/actix-files/CHANGES.md b/actix-files/CHANGES.md index 9f606dfcd..8e5566b61 100644 --- a/actix-files/CHANGES.md +++ b/actix-files/CHANGES.md @@ -2,8 +2,10 @@ ## Unreleased - 2021-xx-xx * 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. diff --git a/actix-files/Cargo.toml b/actix-files/Cargo.toml index 3371ab060..b4317596c 100644 --- a/actix-files/Cargo.toml +++ b/actix-files/Cargo.toml @@ -19,6 +19,8 @@ path = "src/lib.rs" [dependencies] actix-web = { version = "4.0.0-beta.1", default-features = false } actix-service = "2.0.0-beta.4" + +askama_escape = "0.10" bitflags = "1" bytes = "1" futures-core = { version = "0.3.7", default-features = false } @@ -28,7 +30,6 @@ log = "0.4" mime = "0.3" mime_guess = "2.0.1" percent-encoding = "2.1" -v_htmlescape = "0.12" [dev-dependencies] actix-rt = "2" diff --git a/actix-files/src/directory.rs b/actix-files/src/directory.rs index 3717985d3..1103dd6a7 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)] @@ -50,7 +50,7 @@ macro_rules! encode_file_url { // " -- " & -- & ' -- ' < -- < > -- > / -- / macro_rules! encode_file_name { ($entry:ident) => { - escape_html_entity(&$entry.file_name().to_string_lossy()) + escape_html_entity(&$entry.file_name().to_string_lossy(), Html) }; } From deafb7c8b8038b3ab4bb80cf9b465a30453c7d44 Mon Sep 17 00:00:00 2001 From: Alexander Jackson Date: Sun, 7 Feb 2021 04:54:41 +0000 Subject: [PATCH 010/339] Improve `impl ResponseError` documentation (#1939) Co-authored-by: Rob Ede --- actix-http/src/error.rs | 35 +++++++++++++++++++++-------------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/actix-http/src/error.rs b/actix-http/src/error.rs index 28697cbbf..7beedc3ba 100644 --- a/actix-http/src/error.rs +++ b/actix-http/src/error.rs @@ -147,7 +147,10 @@ impl From for Error { } } -/// Return `GATEWAY_TIMEOUT` for `TimeoutError` +/// Inspects the underlying enum and returns an appropriate status code. +/// +/// If the variant is [`TimeoutError::Service`], the error code of the service is returned. +/// Otherwise, [`StatusCode::GATEWAY_TIMEOUT`] is returned. impl ResponseError for TimeoutError { fn status_code(&self) -> StatusCode { match self { @@ -161,41 +164,44 @@ impl ResponseError for TimeoutError { #[display(fmt = "UnknownError")] struct UnitError; -/// `InternalServerError` for `UnitError` +/// Returns [`StatusCode::INTERNAL_SERVER_ERROR`] for [`UnitError`]. impl ResponseError for UnitError {} -/// `InternalServerError` for `JsonError` +/// Returns [`StatusCode::INTERNAL_SERVER_ERROR`] for [`JsonError`]. impl ResponseError for JsonError {} -/// `InternalServerError` for `FormError` +/// Returns [`StatusCode::INTERNAL_SERVER_ERROR`] for [`FormError`]. impl ResponseError for FormError {} #[cfg(feature = "openssl")] -/// `InternalServerError` for `openssl::ssl::Error` +/// Returns [`StatusCode::INTERNAL_SERVER_ERROR`] for [`actix_tls::accept::openssl::SslError`]. impl ResponseError for actix_tls::accept::openssl::SslError {} -/// Return `BAD_REQUEST` for `de::value::Error` +/// Returns [`StatusCode::BAD_REQUEST`] for [`DeError`]. impl ResponseError for DeError { fn status_code(&self) -> StatusCode { StatusCode::BAD_REQUEST } } -/// `InternalServerError` for `Canceled` +/// Returns [`StatusCode::INTERNAL_SERVER_ERROR`] for [`Canceled`]. impl ResponseError for Canceled {} -/// Return `BAD_REQUEST` for `Utf8Error` +/// Returns [`StatusCode::BAD_REQUEST`] for [`Utf8Error`]. impl ResponseError for Utf8Error { fn status_code(&self) -> StatusCode { StatusCode::BAD_REQUEST } } -/// Return `InternalServerError` for `HttpError`, -/// Response generation can return `HttpError`, so it is internal error +/// Returns [`StatusCode::INTERNAL_SERVER_ERROR`] for [`HttpError`]. impl ResponseError for HttpError {} -/// Return `InternalServerError` for `io::Error` +/// 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() { @@ -206,7 +212,7 @@ impl ResponseError for io::Error { } } -/// `BadRequest` for `InvalidHeaderValue` +/// Returns [`StatusCode::BAD_REQUEST`] for [`header::InvalidHeaderValue`]. impl ResponseError for header::InvalidHeaderValue { fn status_code(&self) -> StatusCode { StatusCode::BAD_REQUEST @@ -963,8 +969,9 @@ where } #[cfg(feature = "actors")] -/// `InternalServerError` for `actix::MailboxError` -/// This is supported on feature=`actors` only +/// Returns [`StatusCode::INTERNAL_SERVER_ERROR`] for [`actix::MailboxError`]. +/// +/// This is only supported when the feature `actors` is enabled. impl ResponseError for actix::MailboxError {} #[cfg(test)] From 4c243cbf89e680772ed8ec7dcdde143e026e2a6c Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Sun, 7 Feb 2021 10:56:39 -0800 Subject: [PATCH 011/339] simplify methods of awc::connect::Connect trait (#1941) --- actix-http/src/h1/client.rs | 12 --- actix-http/src/h1/encoder.rs | 16 +--- awc/src/connect.rs | 143 +++++------------------------------ awc/src/sender.rs | 43 ++++++----- 4 files changed, 49 insertions(+), 165 deletions(-) diff --git a/actix-http/src/h1/client.rs b/actix-http/src/h1/client.rs index 2e0103409..4a6104688 100644 --- a/actix-http/src/h1/client.rs +++ b/actix-http/src/h1/client.rs @@ -223,15 +223,3 @@ impl Encoder> for ClientCodec { Ok(()) } } - -pub struct Writer<'a>(pub &'a mut BytesMut); - -impl<'a> io::Write for Writer<'a> { - fn write(&mut self, buf: &[u8]) -> io::Result { - self.0.extend_from_slice(buf); - Ok(buf.len()) - } - fn flush(&mut self) -> io::Result<()> { - Ok(()) - } -} diff --git a/actix-http/src/h1/encoder.rs b/actix-http/src/h1/encoder.rs index bb89905fb..bd8287b26 100644 --- a/actix-http/src/h1/encoder.rs +++ b/actix-http/src/h1/encoder.rs @@ -337,7 +337,7 @@ impl MessageType for RequestHeadType { let head = self.as_ref(); dst.reserve(256 + head.headers.len() * AVERAGE_HEADER_SIZE); write!( - Writer(dst), + helpers::Writer(dst), "{} {} {}", head.method, head.uri.path_and_query().map(|u| u.as_str()).unwrap_or("/"), @@ -470,7 +470,7 @@ impl TransferEncoding { *eof = true; buf.extend_from_slice(b"0\r\n\r\n"); } else { - writeln!(Writer(buf), "{:X}\r", msg.len()) + writeln!(helpers::Writer(buf), "{:X}\r", msg.len()) .map_err(|e| io::Error::new(io::ErrorKind::Other, e))?; buf.reserve(msg.len() + 2); @@ -520,18 +520,6 @@ impl TransferEncoding { } } -struct Writer<'a>(pub &'a mut BytesMut); - -impl<'a> io::Write for Writer<'a> { - fn write(&mut self, buf: &[u8]) -> io::Result { - self.0.extend_from_slice(buf); - Ok(buf.len()) - } - fn flush(&mut self) -> io::Result<()> { - Ok(()) - } -} - /// # Safety /// Callers must ensure that the given length matches given value length. unsafe fn write_data(value: &[u8], buf: *mut u8, len: usize) { diff --git a/awc/src/connect.rs b/awc/src/connect.rs index 2ffb8ec37..5450550a7 100644 --- a/awc/src/connect.rs +++ b/awc/src/connect.rs @@ -1,71 +1,39 @@ -use std::future::Future; -use std::pin::Pin; -use std::rc::Rc; -use std::task::{Context, Poll}; -use std::{fmt, io, net}; +use std::{ + fmt, io, net, + pin::Pin, + task::{Context, Poll}, +}; use actix_codec::{AsyncRead, AsyncWrite, Framed, ReadBuf}; -use actix_http::body::Body; -use actix_http::client::{ - Connect as ClientConnect, ConnectError, Connection, SendRequestError, +use actix_http::{ + body::Body, + client::{Connect as ClientConnect, ConnectError, Connection, SendRequestError}, + h1::ClientCodec, + RequestHead, RequestHeadType, ResponseHead, }; -use actix_http::h1::ClientCodec; -use actix_http::http::HeaderMap; -use actix_http::{RequestHead, RequestHeadType, ResponseHead}; use actix_service::Service; +use futures_core::future::LocalBoxFuture; use crate::response::ClientResponse; pub(crate) struct ConnectorWrapper(pub T); +type TunnelResponse = (ResponseHead, Framed); + pub(crate) trait Connect { fn send_request( &self, - head: RequestHead, + head: RequestHeadType, body: Body, addr: Option, - ) -> Pin>>>; - - fn send_request_extra( - &self, - head: Rc, - extra_headers: Option, - body: Body, - addr: Option, - ) -> Pin>>>; + ) -> LocalBoxFuture<'static, Result>; /// Send request, returns Response and Framed fn open_tunnel( &self, head: RequestHead, addr: Option, - ) -> Pin< - Box< - dyn Future< - Output = Result< - (ResponseHead, Framed), - SendRequestError, - >, - >, - >, - >; - - /// Send request and extra headers, returns Response and Framed - fn open_tunnel_extra( - &self, - head: Rc, - extra_headers: Option, - addr: Option, - ) -> Pin< - Box< - dyn Future< - Output = Result< - (ResponseHead, Framed), - SendRequestError, - >, - >, - >, - >; + ) -> LocalBoxFuture<'static, Result>; } impl Connect for ConnectorWrapper @@ -79,13 +47,13 @@ where { fn send_request( &self, - head: RequestHead, + head: RequestHeadType, body: Body, addr: Option, - ) -> Pin>>> { + ) -> LocalBoxFuture<'static, Result> { // connect to the host let fut = self.0.call(ClientConnect { - uri: head.uri.clone(), + uri: head.as_ref().uri.clone(), addr, }); @@ -93,33 +61,7 @@ where let connection = fut.await?; // send request - connection - .send_request(RequestHeadType::from(head), body) - .await - .map(|(head, payload)| ClientResponse::new(head, payload)) - }) - } - - fn send_request_extra( - &self, - head: Rc, - extra_headers: Option, - body: Body, - addr: Option, - ) -> Pin>>> { - // connect to the host - let fut = self.0.call(ClientConnect { - uri: head.uri.clone(), - addr, - }); - - Box::pin(async move { - let connection = fut.await?; - - // send request - let (head, payload) = connection - .send_request(RequestHeadType::Rc(head, extra_headers), body) - .await?; + let (head, payload) = connection.send_request(head, body).await?; Ok(ClientResponse::new(head, payload)) }) @@ -129,16 +71,7 @@ where &self, head: RequestHead, addr: Option, - ) -> Pin< - Box< - dyn Future< - Output = Result< - (ResponseHead, Framed), - SendRequestError, - >, - >, - >, - > { + ) -> LocalBoxFuture<'static, Result> { // connect to the host let fut = self.0.call(ClientConnect { uri: head.uri.clone(), @@ -156,40 +89,6 @@ where Ok((head, framed)) }) } - - fn open_tunnel_extra( - &self, - head: Rc, - extra_headers: Option, - addr: Option, - ) -> Pin< - Box< - dyn Future< - Output = Result< - (ResponseHead, Framed), - SendRequestError, - >, - >, - >, - > { - // connect to the host - let fut = self.0.call(ClientConnect { - uri: head.uri.clone(), - addr, - }); - - Box::pin(async move { - let connection = fut.await?; - - // send request - let (head, framed) = connection - .open_tunnel(RequestHeadType::Rc(head, extra_headers)) - .await?; - - let framed = framed.into_map_io(|io| BoxedSocket(Box::new(Socket(io)))); - Ok((head, framed)) - }) - } } trait AsyncSocket { diff --git a/awc/src/sender.rs b/awc/src/sender.rs index 5f790a038..1cf863d96 100644 --- a/awc/src/sender.rs +++ b/awc/src/sender.rs @@ -1,21 +1,26 @@ -use std::future::Future; -use std::net; -use std::pin::Pin; -use std::rc::Rc; -use std::task::{Context, Poll}; -use std::time::Duration; +use std::{ + future::Future, + net, + pin::Pin, + rc::Rc, + task::{Context, Poll}, + time::Duration, +}; +use actix_http::{ + body::{Body, BodyStream}, + http::{ + header::{self, HeaderMap, HeaderName, IntoHeaderValue}, + Error as HttpError, + }, + Error, RequestHead, RequestHeadType, +}; use actix_rt::time::{sleep, Sleep}; use bytes::Bytes; use derive_more::From; use futures_core::Stream; use serde::Serialize; -use actix_http::body::{Body, BodyStream}; -use actix_http::http::header::{self, IntoHeaderValue}; -use actix_http::http::{Error as HttpError, HeaderMap, HeaderName}; -use actix_http::{Error, RequestHead}; - #[cfg(feature = "compress")] use actix_http::encoding::Decoder; #[cfg(feature = "compress")] @@ -184,12 +189,16 @@ impl RequestSender { B: Into, { let fut = match self { - RequestSender::Owned(head) => { - config.connector.send_request(head, body.into(), addr) - } - RequestSender::Rc(head, extra_headers) => config - .connector - .send_request_extra(head, extra_headers, body.into(), addr), + RequestSender::Owned(head) => config.connector.send_request( + RequestHeadType::Owned(head), + body.into(), + addr, + ), + RequestSender::Rc(head, extra_headers) => config.connector.send_request( + RequestHeadType::Rc(head, extra_headers), + body.into(), + addr, + ), }; SendClientRequest::new(fut, response_decompress, timeout.or(config.timeout)) From dbc47c9122b5731a11a5e1dc388efd0a13d65754 Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Sun, 7 Feb 2021 12:19:10 -0800 Subject: [PATCH 012/339] optimize actix-http messages (#1914) --- actix-http/src/message.rs | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/actix-http/src/message.rs b/actix-http/src/message.rs index bccb4d53e..736f35ee1 100644 --- a/actix-http/src/message.rs +++ b/actix-http/src/message.rs @@ -343,6 +343,8 @@ impl ResponseHead { } 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. head: Rc, } @@ -353,14 +355,6 @@ impl Message { } } -impl Clone for Message { - fn clone(&self) -> Self { - Message { - head: self.head.clone(), - } - } -} - impl std::ops::Deref for Message { type Target = T; @@ -377,9 +371,7 @@ impl std::ops::DerefMut for Message { impl Drop for Message { fn drop(&mut self) { - if Rc::strong_count(&self.head) == 1 { - T::with_pool(|p| p.release(self.head.clone())) - } + T::with_pool(|p| p.release(self.head.clone())) } } From 9604e249c9c840decd22ac6d66252063690e987c Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sun, 7 Feb 2021 20:33:53 +0000 Subject: [PATCH 013/339] use stable clippy (#1963) --- .github/workflows/bench.yml | 2 +- .github/workflows/clippy-fmt.yml | 21 ++++++++++++++------- .github/workflows/upload-doc.yml | 2 +- src/middleware/condition.rs | 1 - src/middleware/err_handlers.rs | 2 -- tests/test_httpserver.rs | 1 - 6 files changed, 16 insertions(+), 13 deletions(-) diff --git a/.github/workflows/bench.yml b/.github/workflows/bench.yml index d19471a16..828d62561 100644 --- a/.github/workflows/bench.yml +++ b/.github/workflows/bench.yml @@ -1,4 +1,4 @@ -name: Benchmark (Linux) +name: Benchmark on: pull_request: diff --git a/.github/workflows/clippy-fmt.yml b/.github/workflows/clippy-fmt.yml index fb1ed7f32..e966fa4ab 100644 --- a/.github/workflows/clippy-fmt.yml +++ b/.github/workflows/clippy-fmt.yml @@ -1,32 +1,39 @@ +name: Lint + on: pull_request: types: [opened, synchronize, reopened] -name: Clippy and rustfmt Check jobs: - clippy_check: + fmt: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - - uses: actions-rs/toolchain@v1 + - name: Install Rust + uses: actions-rs/toolchain@v1 with: toolchain: stable components: rustfmt - override: true - name: Check with rustfmt uses: actions-rs/cargo@v1 with: command: fmt args: --all -- --check - - uses: actions-rs/toolchain@v1 + clippy: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + + - name: Install Rust + uses: actions-rs/toolchain@v1 with: - toolchain: nightly + toolchain: stable components: clippy override: true - name: Check with Clippy uses: actions-rs/clippy-check@v1 with: token: ${{ secrets.GITHUB_TOKEN }} - args: --all-features --all --tests + args: --workspace --tests --all-features diff --git a/.github/workflows/upload-doc.yml b/.github/workflows/upload-doc.yml index ba87a5637..c080dd8c3 100644 --- a/.github/workflows/upload-doc.yml +++ b/.github/workflows/upload-doc.yml @@ -24,7 +24,7 @@ jobs: uses: actions-rs/cargo@v1 with: command: doc - args: --no-deps --workspace --all-features + args: --workspace --all-features --no-deps - name: Tweak HTML run: echo "" > target/doc/index.html diff --git a/src/middleware/condition.rs b/src/middleware/condition.rs index 85eba3fb8..9263292b9 100644 --- a/src/middleware/condition.rs +++ b/src/middleware/condition.rs @@ -106,7 +106,6 @@ mod tests { HttpResponse, }; - #[allow(clippy::unnecessary_wraps)] fn render_500(mut res: ServiceResponse) -> Result> { res.response_mut() .headers_mut() diff --git a/src/middleware/err_handlers.rs b/src/middleware/err_handlers.rs index 70933241d..b55959460 100644 --- a/src/middleware/err_handlers.rs +++ b/src/middleware/err_handlers.rs @@ -182,7 +182,6 @@ mod tests { use crate::test::{self, TestRequest}; use crate::HttpResponse; - #[allow(clippy::unnecessary_wraps)] fn render_500(mut res: ServiceResponse) -> Result> { res.response_mut() .headers_mut() @@ -207,7 +206,6 @@ mod tests { assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0001"); } - #[allow(clippy::unnecessary_wraps)] fn render_500_async( mut res: ServiceResponse, ) -> Result> { diff --git a/tests/test_httpserver.rs b/tests/test_httpserver.rs index cd5a75d49..07c171674 100644 --- a/tests/test_httpserver.rs +++ b/tests/test_httpserver.rs @@ -72,7 +72,6 @@ async fn test_start() { let _ = sys.stop(); } -#[allow(clippy::unnecessary_wraps)] #[cfg(feature = "openssl")] fn ssl_acceptor() -> std::io::Result { use openssl::ssl::{SslAcceptor, SslFiletype, SslMethod}; From 266cf0622c18fe569f6af3f0de19f0a226c98bc0 Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Sun, 7 Feb 2021 14:48:27 -0800 Subject: [PATCH 014/339] reduce branch.remove deadcode for h1 dispatcher (#1962) --- actix-http/src/h1/dispatcher.rs | 202 +++++++++++++++----------------- 1 file changed, 95 insertions(+), 107 deletions(-) diff --git a/actix-http/src/h1/dispatcher.rs b/actix-http/src/h1/dispatcher.rs index e65a1bd56..f8fc95921 100644 --- a/actix-http/src/h1/dispatcher.rs +++ b/actix-http/src/h1/dispatcher.rs @@ -734,6 +734,78 @@ where } Ok(()) } + + /// 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)] + fn read_available( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + ) -> Result { + let this = self.project(); + + if this.flags.contains(Flags::READ_DISCONNECT) { + return Ok(false); + }; + + let mut io = Pin::new(this.io.as_mut().unwrap()); + + let mut read_some = false; + + loop { + // grow buffer if necessary. + let remaining = this.read_buf.capacity() - this.read_buf.len(); + if remaining < LW_BUFFER_SIZE { + this.read_buf.reserve(HW_BUFFER_SIZE - remaining); + } + + match actix_codec::poll_read_buf(io.as_mut(), cx, this.read_buf) { + Poll::Pending => return Ok(false), + Poll::Ready(Ok(n)) => { + if n == 0 { + return Ok(true); + } else { + // Return early when read buf exceed decoder's max buffer size. + if this.read_buf.len() >= super::decoder::MAX_BUFFER_SIZE { + // at this point it's not known io is still scheduled to + // be waked up. so force wake up dispatcher just in case. + // TODO: figure out the overhead. + cx.waker().wake_by_ref(); + return Ok(false); + } + + read_some = true; + } + } + Poll::Ready(Err(err)) => { + return if err.kind() == io::ErrorKind::WouldBlock { + Ok(false) + } else if err.kind() == io::ErrorKind::ConnectionReset && read_some { + Ok(true) + } else { + Err(DispatchError::Io(err)) + } + } + } + } + } + + /// call upgrade service with request. + fn upgrade(self: Pin<&mut Self>, req: Request) -> U::Future { + let this = self.project(); + let mut parts = FramedParts::with_read_buf( + this.io.take().unwrap(), + mem::take(this.codec), + mem::take(this.read_buf), + ); + parts.write_buf = mem::take(this.write_buf); + let framed = Framed::from_parts(parts); + this.flow.upgrade.as_ref().unwrap().call((req, framed)) + } } impl Future for Dispatcher @@ -778,60 +850,36 @@ where } } } else { - // read socket into a buf - let should_disconnect = - if !inner.flags.contains(Flags::READ_DISCONNECT) { - let mut inner_p = inner.as_mut().project(); - read_available( - cx, - inner_p.io.as_mut().unwrap(), - &mut inner_p.read_buf, - )? - } else { - None - }; + // read from io stream and fill read buffer. + let should_disconnect = inner.as_mut().read_available(cx)?; inner.as_mut().poll_request(cx)?; - if let Some(true) = should_disconnect { - let inner_p = inner.as_mut().project(); - inner_p.flags.insert(Flags::READ_DISCONNECT); - if let Some(mut payload) = inner_p.payload.take() { + + // io stream should to be closed. + if should_disconnect { + let inner = inner.as_mut().project(); + inner.flags.insert(Flags::READ_DISCONNECT); + if let Some(mut payload) = inner.payload.take() { payload.feed_eof(); } }; loop { - let inner_p = inner.as_mut().project(); - let remaining = - inner_p.write_buf.capacity() - inner_p.write_buf.len(); - if remaining < LW_BUFFER_SIZE { - inner_p.write_buf.reserve(HW_BUFFER_SIZE - remaining); - } - let result = inner.as_mut().poll_response(cx)?; - let drain = result == PollResponse::DrainWriteBuf; - - // switch to upgrade handler - if let PollResponse::Upgrade(req) = result { - let inner_p = inner.as_mut().project(); - let mut parts = FramedParts::with_read_buf( - inner_p.io.take().unwrap(), - mem::take(inner_p.codec), - mem::take(inner_p.read_buf), - ); - parts.write_buf = mem::take(inner_p.write_buf); - let framed = Framed::from_parts(parts); - let upgrade = inner_p - .flow - .upgrade - .as_ref() - .unwrap() - .call((req, framed)); - self.as_mut() - .project() - .inner - .set(DispatcherState::Upgrade(upgrade)); - return self.poll(cx); - } + // poll_response and populate write buffer. + // drain indicate if write buffer should be emptied before next run. + let drain = match inner.as_mut().poll_response(cx)? { + PollResponse::DrainWriteBuf => true, + PollResponse::DoNothing => false, + // upgrade request and goes Upgrade variant of DispatcherState. + PollResponse::Upgrade(req) => { + let upgrade = inner.upgrade(req); + self.as_mut() + .project() + .inner + .set(DispatcherState::Upgrade(upgrade)); + return self.poll(cx); + } + }; // we didn't get WouldBlock from write operation, // so data get written to kernel completely (macOS) @@ -888,66 +936,6 @@ where } } -/// Returns either: -/// - `Ok(Some(true))` - data was read and done reading all data. -/// - `Ok(Some(false))` - data was read but there should be more to read. -/// - `Ok(None)` - no data was read but there should be more to read later. -/// - Unhandled Errors -fn read_available( - cx: &mut Context<'_>, - io: &mut T, - buf: &mut BytesMut, -) -> Result, io::Error> -where - T: AsyncRead + Unpin, -{ - let mut read_some = false; - - loop { - // reserve capacity for buffer - let remaining = buf.capacity() - buf.len(); - if remaining < LW_BUFFER_SIZE { - buf.reserve(HW_BUFFER_SIZE - remaining); - } - - match actix_codec::poll_read_buf(Pin::new(io), cx, buf) { - Poll::Pending => { - return if read_some { Ok(Some(false)) } else { Ok(None) }; - } - Poll::Ready(Ok(n)) => { - if n == 0 { - return Ok(Some(true)); - } else { - // If buf is full return but do not disconnect since - // there is more reading to be done - if buf.len() >= super::decoder::MAX_BUFFER_SIZE { - // at this point it's not known io is still scheduled to - // be waked up. so force wake up dispatcher just in case. - // TODO: figure out the overhead. - cx.waker().wake_by_ref(); - return Ok(Some(false)); - } - - read_some = true; - } - } - Poll::Ready(Err(err)) => { - return if err.kind() == io::ErrorKind::WouldBlock { - if read_some { - Ok(Some(false)) - } else { - Ok(None) - } - } else if err.kind() == io::ErrorKind::ConnectionReset && read_some { - Ok(Some(true)) - } else { - Err(err) - } - } - } - } -} - #[cfg(test)] mod tests { use std::str; From dddb623a11f23c96af45e295ac479d66374e3897 Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Sun, 7 Feb 2021 15:47:51 -0800 Subject: [PATCH 015/339] add services register for tuple and vec of services (#1933) --- CHANGES.md | 3 ++ src/service.rs | 142 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 145 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index e9a788950..9a3b7fe3e 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -4,6 +4,8 @@ ### 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 vector of same type of `HttpServiceFactory` to `App` [#1933] ### Changed * Rework `Responder` trait to be sync and returns `Response`/`HttpResponse` directly. @@ -34,6 +36,7 @@ [#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 diff --git a/src/service.rs b/src/service.rs index 86949dca6..db0ec602a 100644 --- a/src/service.rs +++ b/src/service.rs @@ -22,6 +22,13 @@ pub trait HttpServiceFactory { fn register(self, config: &mut AppService); } +impl HttpServiceFactory for Vec { + fn register(self, config: &mut AppService) { + self.into_iter() + .for_each(|factory| factory.register(config)); + } +} + pub(crate) trait AppServiceFactory { fn register(&mut self, config: &mut AppService); } @@ -532,6 +539,65 @@ where } } +/// Macro helping register different types of services at the sametime. +/// +/// The service type must be implementing [`HttpServiceFactory`](self::HttpServiceFactory) trait. +/// +/// The max number of services can be grouped together is 12. +/// +/// # Examples +/// +/// ``` +/// use actix_web::{services, web, App}; +/// +/// let services = services![ +/// web::resource("/test2").to(|| async { "test2" }), +/// web::scope("/test3").route("/", web::get().to(|| async { "test3" })) +/// ]; +/// +/// let app = App::new().service(services); +/// +/// // services macro just convert multiple services to a tuple. +/// // below would also work without importing the macro. +/// let app = App::new().service(( +/// web::resource("/test2").to(|| async { "test2" }), +/// web::scope("/test3").route("/", web::get().to(|| async { "test3" })) +/// )); +/// ``` +#[macro_export] +macro_rules! services { + ($($x:expr),+ $(,)?) => { + ($($x,)+) + } +} + +/// HttpServiceFactory trait impl for tuples +macro_rules! service_tuple ({ $(($n:tt, $T:ident)),+} => { + impl<$($T: HttpServiceFactory),+> HttpServiceFactory for ($($T,)+) { + fn register(self, config: &mut AppService) { + $(self.$n.register(config);)+ + } + } +}); + +#[rustfmt::skip] +mod m { + use super::*; + + service_tuple!((0, A)); + service_tuple!((0, A), (1, B)); + service_tuple!((0, A), (1, B), (2, C)); + service_tuple!((0, A), (1, B), (2, C), (3, D)); + service_tuple!((0, A), (1, B), (2, C), (3, D), (4, E)); + service_tuple!((0, A), (1, B), (2, C), (3, D), (4, E), (5, F)); + service_tuple!((0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G)); + service_tuple!((0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G), (7, H)); + service_tuple!((0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G), (7, H), (8, I)); + service_tuple!((0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G), (7, H), (8, I), (9, J)); + service_tuple!((0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G), (7, H), (8, I), (9, J), (10, K)); + service_tuple!((0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G), (7, H), (8, I), (9, J), (10, K), (11, L)); +} + #[cfg(test)] mod tests { use super::*; @@ -606,4 +672,80 @@ mod tests { assert!(s.contains("ServiceResponse")); assert!(s.contains("x-test")); } + + #[actix_rt::test] + async fn test_services_macro() { + let scoped = services![ + web::service("/scoped_test1").name("scoped_test1").finish( + |req: ServiceRequest| async { + Ok(req.into_response(HttpResponse::Ok().finish())) + } + ), + web::resource("/scoped_test2").to(|| async { "test2" }), + ]; + + let services = services![ + web::service("/test1") + .name("test") + .finish(|req: ServiceRequest| async { + Ok(req.into_response(HttpResponse::Ok().finish())) + }), + web::resource("/test2").to(|| async { "test2" }), + web::scope("/test3").service(scoped) + ]; + + let srv = init_service(App::new().service(services)).await; + + let req = TestRequest::with_uri("/test1").to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), http::StatusCode::OK); + + let req = TestRequest::with_uri("/test2").to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), http::StatusCode::OK); + + let req = TestRequest::with_uri("/test3/scoped_test1").to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), http::StatusCode::OK); + + let req = TestRequest::with_uri("/test3/scoped_test2").to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), http::StatusCode::OK); + } + + #[actix_rt::test] + async fn test_services_vec() { + let services = vec![ + web::resource("/test1").to(|| async { "test1" }), + web::resource("/test2").to(|| async { "test2" }), + ]; + + let scoped = vec![ + web::resource("/scoped_test1").to(|| async { "test1" }), + web::resource("/scoped_test2").to(|| async { "test2" }), + ]; + + let srv = init_service( + App::new() + .service(services) + .service(web::scope("/test3").service(scoped)), + ) + .await; + + let req = TestRequest::with_uri("/test1").to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), http::StatusCode::OK); + + let req = TestRequest::with_uri("/test2").to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), http::StatusCode::OK); + + let req = TestRequest::with_uri("/test3/scoped_test1").to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), http::StatusCode::OK); + + let req = TestRequest::with_uri("/test3/scoped_test2").to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), http::StatusCode::OK); + } } From 519d7f2b8ab323b34071c8eedce03f89e4fde6d0 Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Tue, 9 Feb 2021 02:41:20 -0800 Subject: [PATCH 016/339] add trust-dns optional feature for actix-http and awc (#1969) --- Cargo.toml | 1 - actix-http/CHANGES.md | 4 ++ actix-http/Cargo.toml | 7 +-- actix-http/src/client/connector.rs | 84 ++++++++++++++++++++++++++++-- actix-http/src/error.rs | 6 --- awc/CHANGES.md | 2 + awc/Cargo.toml | 3 ++ src/test.rs | 6 ++- 8 files changed, 98 insertions(+), 15 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 479a89f87..80187c8a5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -109,7 +109,6 @@ smallvec = "1.6" [dev-dependencies] actix = { version = "0.11.0-beta.2", default-features = false } -actix-http = { version = "3.0.0-beta.1", features = ["actors"] } rand = "0.8" env_logger = "0.8" serde_derive = "1.0" diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 52980fb5d..ba8cc6e6d 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -7,6 +7,7 @@ * `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] +* `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 @@ -26,6 +27,8 @@ * `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] [#1869]: https://github.com/actix/actix-web/pull/1869 [#1894]: https://github.com/actix/actix-web/pull/1894 @@ -34,6 +37,7 @@ [#1905]: https://github.com/actix/actix-web/pull/1905 [#1912]: https://github.com/actix/actix-web/pull/1912 [#1957]: https://github.com/actix/actix-web/pull/1957 +[#1969]: https://github.com/actix/actix-web/pull/1969 ## 3.0.0-beta.1 - 2021-01-07 diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 22a54f569..9f62639f8 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -36,8 +36,8 @@ compress = ["flate2", "brotli2"] # support for secure cookies secure-cookies = ["cookie/secure"] -# support for actix Actor messages -actors = ["actix"] +# trust-dns as client dns resolver +trust-dns = ["trust-dns-resolver"] [dependencies] actix-service = "2.0.0-beta.4" @@ -45,7 +45,6 @@ actix-codec = "0.4.0-beta.1" actix-utils = "3.0.0-beta.2" actix-rt = "2" actix-tls = "3.0.0-beta.2" -actix = { version = "0.11.0-beta.2", default-features = false, optional = true } base64 = "0.13" bitflags = "1.2" @@ -84,6 +83,8 @@ time = { version = "0.2.23", default-features = false, features = ["std"] } brotli2 = { version="0.3.2", optional = true } flate2 = { version = "1.0.13", 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.1", features = ["openssl"] } diff --git a/actix-http/src/client/connector.rs b/actix-http/src/client/connector.rs index 425ee0f70..7c8e2b2a0 100644 --- a/actix-http/src/client/connector.rs +++ b/actix-http/src/client/connector.rs @@ -6,7 +6,7 @@ use actix_codec::{AsyncRead, AsyncWrite}; use actix_rt::net::TcpStream; use actix_service::{apply_fn, Service, ServiceExt}; use actix_tls::connect::{ - default_connector, Connect as TcpConnect, Connection as TcpConnection, + new_connector, Connect as TcpConnect, Connection as TcpConnection, Resolver, }; use actix_utils::timeout::{TimeoutError, TimeoutService}; use http::Uri; @@ -19,7 +19,6 @@ use super::Connect; #[cfg(feature = "openssl")] use actix_tls::connect::ssl::openssl::SslConnector as OpensslConnector; - #[cfg(feature = "rustls")] use actix_tls::connect::ssl::rustls::ClientConfig; #[cfg(feature = "rustls")] @@ -70,7 +69,7 @@ impl Connector<(), ()> { > { Connector { ssl: Self::build_ssl(vec![b"h2".to_vec(), b"http/1.1".to_vec()]), - connector: default_connector(), + connector: new_connector(resolver::resolver()), config: ConnectorConfig::default(), _phantom: PhantomData, } @@ -532,3 +531,82 @@ mod connect_impl { } } } + +#[cfg(not(feature = "trust-dns"))] +mod resolver { + use super::*; + + pub(super) fn resolver() -> Resolver { + Resolver::Default + } +} + +#[cfg(feature = "trust-dns")] +mod resolver { + use std::{cell::RefCell, net::SocketAddr}; + + use actix_tls::connect::Resolve; + use futures_core::future::LocalBoxFuture; + use trust_dns_resolver::{ + config::{ResolverConfig, ResolverOpts}, + system_conf::read_system_conf, + TokioAsyncResolver, + }; + + use super::*; + + pub(super) fn resolver() -> Resolver { + // new type for impl Resolve trait for TokioAsyncResolver. + struct TrustDnsResolver(TokioAsyncResolver); + + impl Resolve for TrustDnsResolver { + fn lookup<'a>( + &'a self, + host: &'a str, + port: u16, + ) -> LocalBoxFuture<'a, Result, Box>> + { + Box::pin(async move { + let res = self + .0 + .lookup_ip(host) + .await? + .iter() + .map(|ip| SocketAddr::new(ip, port)) + .collect(); + Ok(res) + }) + } + } + + // dns struct is cached in thread local. + // so new client constructor can reuse the existing dns resolver. + thread_local! { + static TRUST_DNS_RESOLVER: RefCell> = RefCell::new(None); + } + + // 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), + Err(e) => { + log::error!("TRust-DNS can not load system config: {}", e); + (ResolverConfig::default(), ResolverOpts::default()) + } + }; + + let resolver = TokioAsyncResolver::tokio(cfg, opts).unwrap(); + + // box trust dns resolver and put it in thread local. + let resolver = Resolver::new_custom(TrustDnsResolver(resolver)); + *local.borrow_mut() = Some(resolver.clone()); + resolver + } + } + }) + } +} diff --git a/actix-http/src/error.rs b/actix-http/src/error.rs index 7beedc3ba..cea1f5d4f 100644 --- a/actix-http/src/error.rs +++ b/actix-http/src/error.rs @@ -968,12 +968,6 @@ where InternalError::new(err, StatusCode::NETWORK_AUTHENTICATION_REQUIRED).into() } -#[cfg(feature = "actors")] -/// Returns [`StatusCode::INTERNAL_SERVER_ERROR`] for [`actix::MailboxError`]. -/// -/// This is only supported when the feature `actors` is enabled. -impl ResponseError for actix::MailboxError {} - #[cfg(test)] mod tests { use super::*; diff --git a/awc/CHANGES.md b/awc/CHANGES.md index 16ec7ad1a..80895f46d 100644 --- a/awc/CHANGES.md +++ b/awc/CHANGES.md @@ -4,6 +4,7 @@ ### 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] ### Changed * Relax default timeout for `Connector` to 5 seconds(original 1 second). [#1905] @@ -16,6 +17,7 @@ [#1869]: https://github.com/actix/actix-web/pull/1869 [#1905]: https://github.com/actix/actix-web/pull/1905 +[#1969]: https://github.com/actix/actix-web/pull/1969 ## 3.0.0-beta.1 - 2021-01-07 diff --git a/awc/Cargo.toml b/awc/Cargo.toml index 7a9130780..483e04968 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -36,6 +36,9 @@ rustls = ["tls-rustls", "actix-http/rustls"] # content-encoding support compress = ["actix-http/compress"] +# trust-dns as dns resolver +trust-dns = ["actix-http/trust-dns"] + [dependencies] actix-codec = "0.4.0-beta.1" actix-service = "2.0.0-beta.4" diff --git a/src/test.rs b/src/test.rs index 62c329c91..5da100b81 100644 --- a/src/test.rs +++ b/src/test.rs @@ -1277,8 +1277,10 @@ mod tests { async fn actor_handler( addr: Data>, ) -> Result { - // `?` operator tests "actors" feature flag on actix-http - let res = addr.send(Num(1)).await?; + let res = addr + .send(Num(1)) + .await + .map_err(crate::error::ErrorInternalServerError)?; if res == 1 { Ok(HttpResponse::Ok()) From a6ed4aee842858b3afb78a5d6830c77fa438d694 Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Tue, 9 Feb 2021 14:32:46 -0800 Subject: [PATCH 017/339] add poll_flush after a non blocked write to h1 dispatcher (#1971) --- actix-http/src/h1/dispatcher.rs | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/actix-http/src/h1/dispatcher.rs b/actix-http/src/h1/dispatcher.rs index f8fc95921..8fbf3ede0 100644 --- a/actix-http/src/h1/dispatcher.rs +++ b/actix-http/src/h1/dispatcher.rs @@ -286,15 +286,12 @@ where self: Pin<&mut Self>, cx: &mut Context<'_>, ) -> Result { - let len = self.write_buf.len(); - if len == 0 { - return Ok(false); - } - let InnerDispatcherProj { io, write_buf, .. } = self.project(); let mut io = Pin::new(io.as_mut().unwrap()); + let len = write_buf.len(); let mut written = 0; + while written < len { match io.as_mut().poll_write(cx, &write_buf[written..]) { Poll::Ready(Ok(0)) => { @@ -314,9 +311,14 @@ where // SAFETY: setting length to 0 is safe // skips one length check vs truncate - unsafe { write_buf.set_len(0) } + unsafe { + write_buf.set_len(0); + } - Ok(false) + // flush the io and check if get blocked. + let blocked = io.poll_flush(cx)?.is_pending(); + + Ok(blocked) } fn send_response( From 949d14ae2b78ca7d9d81af27d216b0cb4d3bcd7e Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Tue, 9 Feb 2021 22:59:17 +0000 Subject: [PATCH 018/339] clean up header map (#1964) --- actix-http/CHANGES.md | 10 + actix-http/Cargo.toml | 1 - actix-http/src/client/h1proto.rs | 4 +- actix-http/src/h1/decoder.rs | 4 +- actix-http/src/h1/encoder.rs | 130 ++-- actix-http/src/header/as_name.rs | 46 ++ actix-http/src/header/map.rs | 1110 ++++++++++++++++++++++-------- actix-http/src/header/mod.rs | 18 +- actix-http/src/request.rs | 6 +- actix-http/src/response.rs | 22 +- awc/src/frozen.rs | 4 +- awc/src/request.rs | 8 +- awc/src/sender.rs | 4 +- src/request.rs | 8 +- 14 files changed, 982 insertions(+), 393 deletions(-) create mode 100644 actix-http/src/header/as_name.rs diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index ba8cc6e6d..bef17fb08 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -7,6 +7,9 @@ * `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 @@ -20,6 +23,9 @@ * `client::error::ConnectError` Resolver variant contains `Box` type [#1905] * `client::ConnectorConfig` default timeout changed to 5 seconds. [#1905] * Simplify `BlockingError` type to a struct. It's only triggered with 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] ### Removed * `ResponseBuilder::set`; use `ResponseBuilder::insert_header`. [#1869] @@ -30,6 +36,9 @@ * `actors` optional feature. [#1969] * `ResponseError` impl for `actix::MailboxError`. [#1969] +### Documentation +* 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 [#1903]: https://github.com/actix/actix-web/pull/1903 @@ -37,6 +46,7 @@ [#1905]: https://github.com/actix/actix-web/pull/1905 [#1912]: https://github.com/actix/actix-web/pull/1912 [#1957]: https://github.com/actix/actix-web/pull/1957 +[#1964]: https://github.com/actix/actix-web/pull/1964 [#1969]: https://github.com/actix/actix-web/pull/1969 diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 9f62639f8..e974e61df 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -52,7 +52,6 @@ bytes = "1" bytestring = "1" cookie = { version = "0.14.1", features = ["percent-encode"] } derive_more = "0.99.5" -either = "1.5.3" encoding_rs = "0.8" futures-channel = { version = "0.3.7", default-features = false, features = ["alloc"] } futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] } diff --git a/actix-http/src/client/h1proto.rs b/actix-http/src/client/h1proto.rs index 24f4207e8..92c3c0e1b 100644 --- a/actix-http/src/client/h1proto.rs +++ b/actix-http/src/client/h1proto.rs @@ -48,11 +48,11 @@ where match wrt.get_mut().split().freeze().try_into_value() { Ok(value) => match head { RequestHeadType::Owned(ref mut head) => { - head.headers.insert(HOST, value) + head.headers.insert(HOST, value); } RequestHeadType::Rc(_, ref mut extra_headers) => { let headers = extra_headers.get_or_insert(HeaderMap::new()); - headers.insert(HOST, value) + headers.insert(HOST, value); } }, Err(e) => log::error!("Can not set HOST header {}", e), diff --git a/actix-http/src/h1/decoder.rs b/actix-http/src/h1/decoder.rs index 9da958563..122a815d5 100644 --- a/actix-http/src/h1/decoder.rs +++ b/actix-http/src/h1/decoder.rs @@ -830,8 +830,8 @@ mod tests { .get_all(SET_COOKIE) .map(|v| v.to_str().unwrap().to_owned()) .collect(); - assert_eq!(val[1], "c1=cookie1"); - assert_eq!(val[0], "c2=cookie2"); + assert_eq!(val[0], "c1=cookie1"); + assert_eq!(val[1], "c2=cookie2"); } #[test] diff --git a/actix-http/src/h1/encoder.rs b/actix-http/src/h1/encoder.rs index bd8287b26..2932a7dce 100644 --- a/actix-http/src/h1/encoder.rs +++ b/actix-http/src/h1/encoder.rs @@ -144,104 +144,54 @@ pub(crate) trait MessageType: Sized { let k = key.as_str().as_bytes(); let k_len = k.len(); - match value { - Value::One(ref val) => { - let v = val.as_ref(); - let v_len = v.len(); + // TODO: drain? + for val in value.iter() { + let v = val.as_ref(); + let v_len = v.len(); - // key length + value length + colon + space + \r\n - let len = k_len + v_len + 4; + // key length + value length + colon + space + \r\n + let len = k_len + v_len + 4; - if len > remaining { - // not enough room in buffer for this header; reserve more space - - // SAFETY: all the bytes written up to position "pos" are initialized - // the written byte count and pointer advancement are kept in sync - unsafe { - dst.advance_mut(pos); - } - - pos = 0; - dst.reserve(len * 2); - remaining = dst.capacity() - dst.len(); - - // re-assign buf raw pointer since it's possible that the buffer was - // reallocated and/or resized - buf = dst.chunk_mut().as_mut_ptr(); - } - - // SAFETY: on each write, it is enough to ensure that the advancement of the - // cursor matches the number of bytes written + if len > remaining { + // SAFETY: all the bytes written up to position "pos" are initialized + // the written byte count and pointer advancement are kept in sync unsafe { - // use upper Camel-Case - if camel_case { - write_camel_case(k, from_raw_parts_mut(buf, k_len)) - } else { - write_data(k, buf, k_len) - } - - buf = buf.add(k_len); - - write_data(b": ", buf, 2); - buf = buf.add(2); - - write_data(v, buf, v_len); - buf = buf.add(v_len); - - write_data(b"\r\n", buf, 2); - buf = buf.add(2); + dst.advance_mut(pos); } - pos += len; - remaining -= len; + pos = 0; + dst.reserve(len * 2); + remaining = dst.capacity() - dst.len(); + + // re-assign buf raw pointer since it's possible that the buffer was + // reallocated and/or resized + buf = dst.chunk_mut().as_mut_ptr(); } - Value::Multi(ref vec) => { - for val in vec { - let v = val.as_ref(); - let v_len = v.len(); - let len = k_len + v_len + 4; - - if len > remaining { - // SAFETY: all the bytes written up to position "pos" are initialized - // the written byte count and pointer advancement are kept in sync - unsafe { - dst.advance_mut(pos); - } - pos = 0; - dst.reserve(len * 2); - remaining = dst.capacity() - dst.len(); - - // re-assign buf raw pointer since it's possible that the buffer was - // reallocated and/or resized - buf = dst.chunk_mut().as_mut_ptr(); - } - - // SAFETY: on each write, it is enough to ensure that the advancement of - // the cursor matches the number of bytes written - unsafe { - if camel_case { - write_camel_case(k, from_raw_parts_mut(buf, k_len)); - } else { - write_data(k, buf, k_len); - } - - buf = buf.add(k_len); - - write_data(b": ", buf, 2); - buf = buf.add(2); - - write_data(v, buf, v_len); - buf = buf.add(v_len); - - write_data(b"\r\n", buf, 2); - buf = buf.add(2); - }; - - pos += len; - remaining -= len; + // SAFETY: on each write, it is enough to ensure that the advancement of + // the cursor matches the number of bytes written + unsafe { + if camel_case { + // use Camel-Case headers + write_camel_case(k, from_raw_parts_mut(buf, k_len)); + } else { + write_data(k, buf, k_len); } - } + + buf = buf.add(k_len); + + write_data(b": ", buf, 2); + buf = buf.add(2); + + write_data(v, buf, v_len); + buf = buf.add(v_len); + + write_data(b"\r\n", buf, 2); + buf = buf.add(2); + }; + + pos += len; + remaining -= len; } }); diff --git a/actix-http/src/header/as_name.rs b/actix-http/src/header/as_name.rs new file mode 100644 index 000000000..68456e3f7 --- /dev/null +++ b/actix-http/src/header/as_name.rs @@ -0,0 +1,46 @@ +//! Helper trait for types that can be effectively borrowed as a [HeaderValue]. + +use std::{borrow::Cow, str::FromStr}; + +use http::header::{HeaderName, InvalidHeaderName}; + +pub trait AsHeaderName: Sealed {} + +pub trait Sealed { + fn try_as_name(&self) -> Result, InvalidHeaderName>; +} + +impl Sealed for HeaderName { + fn try_as_name(&self) -> Result, InvalidHeaderName> { + Ok(Cow::Borrowed(self)) + } +} +impl AsHeaderName for HeaderName {} + +impl Sealed for &HeaderName { + fn try_as_name(&self) -> Result, InvalidHeaderName> { + Ok(Cow::Borrowed(*self)) + } +} +impl AsHeaderName for &HeaderName {} + +impl Sealed for &str { + fn try_as_name(&self) -> Result, InvalidHeaderName> { + HeaderName::from_str(self).map(Cow::Owned) + } +} +impl AsHeaderName for &str {} + +impl Sealed for String { + fn try_as_name(&self) -> Result, InvalidHeaderName> { + HeaderName::from_str(self).map(Cow::Owned) + } +} +impl AsHeaderName for String {} + +impl Sealed for &String { + fn try_as_name(&self) -> Result, InvalidHeaderName> { + HeaderName::from_str(self).map(Cow::Owned) + } +} +impl AsHeaderName for &String {} diff --git a/actix-http/src/header/map.rs b/actix-http/src/header/map.rs index 8f20f3e6f..106e44edb 100644 --- a/actix-http/src/header/map.rs +++ b/actix-http/src/header/map.rs @@ -1,332 +1,563 @@ -use std::{ - collections::hash_map::{self, Entry}, - convert::TryFrom, -}; +//! A multi-value [`HeaderMap`] and its iterators. + +use std::{borrow::Cow, collections::hash_map, ops}; use ahash::AHashMap; -use either::Either; use http::header::{HeaderName, HeaderValue}; use smallvec::{smallvec, SmallVec}; +use crate::header::AsHeaderName; + /// A multi-map of HTTP headers. /// -/// `HeaderMap` is a "multi-map" of [`HeaderName`] to one or more values. +/// `HeaderMap` is a "multi-map" of [`HeaderName`] to one or more [`HeaderValue`]s. +/// +/// # Examples +/// ``` +/// use actix_http::http::{header, HeaderMap, HeaderValue}; +/// +/// let mut map = HeaderMap::new(); +/// +/// map.insert(header::CONTENT_TYPE, HeaderValue::from_static("text/plain")); +/// map.insert(header::ORIGIN, HeaderValue::from_static("example.com")); +/// +/// assert!(map.contains_key(header::CONTENT_TYPE)); +/// assert!(map.contains_key(header::ORIGIN)); +/// +/// let mut removed = map.remove(header::ORIGIN); +/// assert_eq!(removed.next().unwrap(), "example.com"); +/// +/// assert!(!map.contains_key(header::ORIGIN)); +/// ``` #[derive(Debug, Clone, Default)] pub struct HeaderMap { pub(crate) inner: AHashMap, } +/// A bespoke non-empty list for HeaderMap values. #[derive(Debug, Clone)] -pub(crate) enum Value { - One(HeaderValue), - Multi(SmallVec<[HeaderValue; 4]>), +pub(crate) struct Value { + inner: SmallVec<[HeaderValue; 4]>, } impl Value { - fn first(&self) -> &HeaderValue { - match self { - Value::One(ref val) => val, - Value::Multi(ref val) => &val[0], + fn one(val: HeaderValue) -> Self { + Self { + inner: smallvec![val], } } + fn first(&self) -> &HeaderValue { + &self.inner[0] + } + fn first_mut(&mut self) -> &mut HeaderValue { - match self { - Value::One(ref mut val) => val, - Value::Multi(ref mut val) => &mut val[0], - } + &mut self.inner[0] } - fn append(&mut self, val: HeaderValue) { - match self { - Value::One(_) => { - let data = std::mem::replace(self, Value::Multi(smallvec![val])); - match data { - Value::One(val) => self.append(val), - Value::Multi(_) => unreachable!(), - } - } - Value::Multi(ref mut vec) => vec.push(val), - } + fn append(&mut self, new_val: HeaderValue) { + self.inner.push(new_val) + } +} + +impl ops::Deref for Value { + type Target = SmallVec<[HeaderValue; 4]>; + + fn deref(&self) -> &Self::Target { + &self.inner } } impl HeaderMap { /// Create an empty `HeaderMap`. /// - /// The map will be created without any capacity. This function will not - /// allocate. + /// The map will be created without any capacity; this function will not allocate. + /// + /// # Examples + /// ``` + /// # use actix_http::http::HeaderMap; + /// let map = HeaderMap::new(); + /// + /// assert!(map.is_empty()); + /// assert_eq!(0, map.capacity()); + /// ``` pub fn new() -> Self { - HeaderMap { - inner: AHashMap::default(), - } + HeaderMap::default() } /// Create an empty `HeaderMap` with the specified capacity. /// - /// The returned map will allocate internal storage in order to hold about - /// `capacity` elements without reallocating. However, this is a "best - /// effort" as there are usage patterns that could cause additional - /// allocations before `capacity` headers are stored in the map. + /// The map will be able to hold at least `capacity` elements without needing to reallocate. + /// If `capacity` is 0, the map will be created without allocating. /// - /// More capacity than requested may be allocated. - pub fn with_capacity(capacity: usize) -> HeaderMap { + /// # Examples + /// ``` + /// # use actix_http::http::HeaderMap; + /// let map = HeaderMap::with_capacity(16); + /// + /// assert!(map.is_empty()); + /// assert!(map.capacity() >= 16); + /// ``` + pub fn with_capacity(capacity: usize) -> Self { HeaderMap { - inner: AHashMap::with_capacity_and_hasher(capacity, Default::default()), + inner: AHashMap::with_capacity(capacity), } } - /// Returns the number of keys stored in the map. + /// Create new `HeaderMap` from a `http::HeaderMap`-like drain. + pub(crate) fn from_drain(mut drain: I) -> Self + where + I: Iterator, HeaderValue)>, + { + let (first_name, first_value) = match drain.next() { + None => return HeaderMap::new(), + Some((name, val)) => { + let name = name.expect("drained first item had no name"); + (name, val) + } + }; + + let (lb, ub) = drain.size_hint(); + let capacity = ub.unwrap_or(lb); + + 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) + }); + + map + } + + /// Returns the number of values stored in the map. /// - /// This number could be be less than or equal to actual headers stored in - /// the map. + /// See also: [`len_keys`](Self::len_keys). + /// + /// # Examples + /// ``` + /// # use actix_http::http::{header, HeaderMap, HeaderValue}; + /// let mut map = HeaderMap::new(); + /// assert_eq!(map.len(), 0); + /// + /// map.insert(header::ACCEPT, HeaderValue::from_static("text/plain")); + /// map.insert(header::SET_COOKIE, HeaderValue::from_static("one=1")); + /// assert_eq!(map.len(), 2); + /// + /// map.append(header::SET_COOKIE, HeaderValue::from_static("two=2")); + /// assert_eq!(map.len(), 3); + /// ``` pub fn len(&self) -> usize { + self.inner + .iter() + .fold(0, |acc, (_, values)| acc + values.len()) + } + + /// Returns the number of _keys_ stored in the map. + /// + /// The number of values stored will be at least this number. See also: [`Self::len`]. + /// + /// # Examples + /// ``` + /// # use actix_http::http::{header, HeaderMap, HeaderValue}; + /// let mut map = HeaderMap::new(); + /// assert_eq!(map.len_keys(), 0); + /// + /// map.insert(header::ACCEPT, HeaderValue::from_static("text/plain")); + /// map.insert(header::SET_COOKIE, HeaderValue::from_static("one=1")); + /// assert_eq!(map.len_keys(), 2); + /// + /// map.append(header::SET_COOKIE, HeaderValue::from_static("two=2")); + /// assert_eq!(map.len_keys(), 2); + /// ``` + pub fn len_keys(&self) -> usize { self.inner.len() } /// Returns true if the map contains no elements. + /// + /// # Examples + /// ``` + /// # use actix_http::http::{header, HeaderMap, HeaderValue}; + /// let mut map = HeaderMap::new(); + /// assert!(map.is_empty()); + /// + /// map.insert(header::ACCEPT, HeaderValue::from_static("text/plain")); + /// assert!(!map.is_empty()); + /// ``` pub fn is_empty(&self) -> bool { self.inner.len() == 0 } - /// Clears the map, removing all key-value pairs. Keeps the allocated memory - /// for reuse. + /// Clears the map, removing all name-value pairs. + /// + /// Keeps the allocated memory for reuse. + /// + /// # Examples + /// ``` + /// # use actix_http::http::{header, HeaderMap, HeaderValue}; + /// let mut map = HeaderMap::new(); + /// + /// map.insert(header::ACCEPT, HeaderValue::from_static("text/plain")); + /// map.insert(header::SET_COOKIE, HeaderValue::from_static("one=1")); + /// assert_eq!(map.len(), 2); + /// + /// map.clear(); + /// assert!(map.is_empty()); + /// ``` pub fn clear(&mut self) { self.inner.clear(); } - /// Returns the number of headers the map can hold without reallocating. + fn get_value(&self, key: impl AsHeaderName) -> Option<&Value> { + match key.try_as_name().ok()? { + Cow::Borrowed(name) => self.inner.get(name), + Cow::Owned(name) => self.inner.get(&name), + } + } + + /// Returns a reference to the _first_ value associated with a header name. /// - /// This number is an approximation as certain usage patterns could cause - /// additional allocations before the returned capacity is filled. + /// Returns `None` if there is no value associated with the key. + /// + /// Even when multiple values are associated with the key, the "first" one is returned but is + /// not guaranteed to be chosen with any particular order; though, the returned item will be + /// consistent for each call to `get` if the map has not changed. + /// + /// See also: [`get_all`](Self::get_all). + /// + /// # Examples + /// ``` + /// # use actix_http::http::{header, HeaderMap, HeaderValue}; + /// let mut map = HeaderMap::new(); + /// + /// map.insert(header::SET_COOKIE, HeaderValue::from_static("one=1")); + /// + /// let cookie = map.get(header::SET_COOKIE).unwrap(); + /// assert_eq!(cookie, "one=1"); + /// + /// map.append(header::SET_COOKIE, HeaderValue::from_static("two=2")); + /// assert_eq!(map.get(header::SET_COOKIE).unwrap(), "one=1"); + /// + /// assert_eq!(map.get(header::SET_COOKIE), map.get("set-cookie")); + /// assert_eq!(map.get(header::SET_COOKIE), map.get("Set-Cookie")); + /// + /// assert!(map.get(header::HOST).is_none()); + /// 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()) + } + + /// Returns a mutable reference to the _first_ value associated a header name. + /// + /// Returns `None` if there is no value associated with the key. + /// + /// Even when multiple values are associated with the key, the "first" one is returned but is + /// not guaranteed to be chosen with any particular order; though, the returned item will be + /// consistent for each call to `get_mut` if the map has not changed. + /// + /// See also: [`get_all`](Self::get_all). + /// + /// # Examples + /// ``` + /// # use actix_http::http::{header, HeaderMap, HeaderValue}; + /// let mut map = HeaderMap::new(); + /// + /// map.insert(header::SET_COOKIE, HeaderValue::from_static("one=1")); + /// + /// let mut cookie = map.get_mut(header::SET_COOKIE).unwrap(); + /// assert_eq!(cookie, "one=1"); + /// + /// *cookie = HeaderValue::from_static("three=3"); + /// assert_eq!(map.get(header::SET_COOKIE).unwrap(), "three=3"); + /// + /// assert!(map.get(header::HOST).is_none()); + /// 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()? { + 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()), + } + } + + /// 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 + /// insertion order. + /// + /// # Examples + /// ``` + /// # use actix_http::http::{header, HeaderMap, HeaderValue}; + /// let mut map = HeaderMap::new(); + /// + /// let mut none_iter = map.get_all(header::ORIGIN); + /// assert!(none_iter.next().is_none()); + /// + /// map.insert(header::SET_COOKIE, HeaderValue::from_static("one=1")); + /// map.append(header::SET_COOKIE, HeaderValue::from_static("two=2")); + /// + /// let mut set_cookies_iter = map.get_all(header::SET_COOKIE); + /// assert_eq!(set_cookies_iter.next().unwrap(), "one=1"); + /// 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)) + } + + // TODO: get_all_mut ? + + /// Returns `true` if the map contains a value for the specified key. + /// + /// Invalid header names will simply return false. + /// + /// # Examples + /// ``` + /// # use actix_http::http::{header, HeaderMap, HeaderValue}; + /// let mut map = HeaderMap::new(); + /// assert!(!map.contains_key(header::ACCEPT)); + /// + /// map.insert(header::ACCEPT, HeaderValue::from_static("text/plain")); + /// assert!(map.contains_key(header::ACCEPT)); + /// ``` + pub fn contains_key(&self, key: impl AsHeaderName) -> bool { + match key.try_as_name() { + Ok(Cow::Borrowed(name)) => self.inner.contains_key(name), + Ok(Cow::Owned(name)) => self.inner.contains_key(&name), + Err(_) => false, + } + } + + /// Inserts a name-value pair into 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; + /// this matters for types that can be `==` without being identical. + /// + /// # Examples + /// ``` + /// # use actix_http::http::{header, HeaderMap, HeaderValue}; + /// let mut map = HeaderMap::new(); + /// + /// map.insert(header::ACCEPT, HeaderValue::from_static("text/plain")); + /// assert!(map.contains_key(header::ACCEPT)); + /// assert_eq!(map.len(), 1); + /// + /// let mut removed = map.insert(header::ACCEPT, HeaderValue::from_static("text/csv")); + /// assert_eq!(removed.next().unwrap(), "text/plain"); + /// assert!(removed.next().is_none()); + /// + /// assert_eq!(map.len(), 1); + /// ``` + pub fn insert(&mut self, key: HeaderName, val: HeaderValue) -> Removed { + let value = self.inner.insert(key, Value::one(val)); + Removed::new(value) + } + + /// Inserts a name-value pair into 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 + /// be `==` without being identical. + /// + /// # Examples + /// ``` + /// # use actix_http::http::{header, HeaderMap, HeaderValue}; + /// let mut map = HeaderMap::new(); + /// + /// map.append(header::HOST, HeaderValue::from_static("example.com")); + /// assert_eq!(map.len(), 1); + /// + /// map.append(header::ACCEPT, HeaderValue::from_static("text/csv")); + /// assert_eq!(map.len(), 2); + /// + /// map.append(header::ACCEPT, HeaderValue::from_static("text/html")); + /// assert_eq!(map.len(), 3); + /// ``` + pub fn append(&mut self, key: HeaderName, value: HeaderValue) { + match self.inner.entry(key) { + hash_map::Entry::Occupied(mut entry) => { + entry.get_mut().append(value); + } + hash_map::Entry::Vacant(entry) => { + entry.insert(Value::one(value)); + } + }; + } + + /// Removes all headers for a particular header name from the map. + /// + /// # Examples + /// ``` + /// # use actix_http::http::{header, HeaderMap, HeaderValue}; + /// let mut map = HeaderMap::new(); + /// + /// map.append(header::SET_COOKIE, HeaderValue::from_static("one=1")); + /// map.append(header::SET_COOKIE, HeaderValue::from_static("one=2")); + /// + /// assert_eq!(map.len(), 2); + /// + /// let mut removed = map.remove(header::SET_COOKIE); + /// assert_eq!(removed.next().unwrap(), "one=1"); + /// assert_eq!(removed.next().unwrap(), "one=2"); + /// assert!(removed.next().is_none()); + /// + /// assert!(map.is_empty()); + pub fn remove(&mut self, key: impl AsHeaderName) -> Removed { + let value = match key.try_as_name() { + Ok(Cow::Borrowed(name)) => self.inner.remove(name), + Ok(Cow::Owned(name)) => self.inner.remove(&name), + Err(_) => None, + }; + + Removed::new(value) + } + + /// Returns the number of single-value headers the map can hold without needing to reallocate. + /// + /// Since this is a multi-value map, the actual capacity is much larger when considering + /// each header name can be associated with an arbitrary number of values. The effect is that + /// the size of `len` may be greater than `capacity` since it counts all the values. + /// Conversely, [`len_keys`](Self::len_keys) will never be larger than capacity. + /// + /// # Examples + /// ``` + /// # use actix_http::http::HeaderMap; + /// let map = HeaderMap::with_capacity(16); + /// + /// assert!(map.is_empty()); + /// assert!(map.capacity() >= 16); + /// ``` pub fn capacity(&self) -> usize { self.inner.capacity() } - /// Reserves capacity for at least `additional` more headers to be inserted - /// into the `HeaderMap`. + /// Reserves capacity for at least `additional` more headers to be inserted in the map. /// - /// The header map may reserve more space to avoid frequent reallocations. - /// Like with `with_capacity`, this will be a "best effort" to avoid - /// allocations until `additional` more headers are inserted. Certain usage - /// patterns could cause additional allocations before the number is - /// reached. + /// The header map may reserve more space to avoid frequent reallocations. Additional capacity + /// only considers single-value headers. + /// + /// # Panics + /// Panics if the new allocation size overflows usize. + /// + /// # Examples + /// ``` + /// # use actix_http::http::HeaderMap; + /// let mut map = HeaderMap::with_capacity(2); + /// assert!(map.capacity() >= 2); + /// + /// map.reserve(100); + /// assert!(map.capacity() >= 102); + /// + /// assert!(map.is_empty()); + /// ``` pub fn reserve(&mut self, additional: usize) { self.inner.reserve(additional) } - /// Returns a reference to the value associated with the key. + /// An iterator over all name-value pairs. /// - /// If there are multiple values associated with the key, then the first one - /// is returned. Use `get_all` to get all values associated with a given - /// key. Returns `None` if there are no values associated with the key. - pub fn get(&self, name: N) -> Option<&HeaderValue> { - self.get2(name).map(|v| v.first()) - } - - fn get2(&self, name: N) -> Option<&Value> { - match name.as_name() { - Either::Left(name) => self.inner.get(name), - Either::Right(s) => { - if let Ok(name) = HeaderName::try_from(s) { - self.inner.get(&name) - } else { - None - } - } - } - } - - /// Returns an iterator of all values associated with a key. + /// Names will be yielded for each associated value. So, if a key has 3 associated values, it + /// will be yielded 3 times. The iteration order should be considered arbitrary. /// - /// The returned view does not incur any allocations and allows iterating the values associated - /// with the key. Returns `None` if there are no values associated with the key. Iteration order - /// is not guaranteed to be the same as insertion order. - pub fn get_all(&self, name: N) -> GetAll<'_> { - GetAll { - idx: 0, - item: self.get2(name), - } - } - - /// Returns a mutable reference to the value associated with the key. + /// # Examples + /// ``` + /// # use actix_http::http::{header, HeaderMap, HeaderValue}; + /// let mut map = HeaderMap::new(); /// - /// If there are multiple values associated with the key, then the first one - /// is returned. Use `entry` to get all values associated with a given - /// key. Returns `None` if there are no values associated with the key. - pub fn get_mut(&mut self, name: N) -> Option<&mut HeaderValue> { - match name.as_name() { - Either::Left(name) => self.inner.get_mut(name).map(|v| v.first_mut()), - Either::Right(s) => { - if let Ok(name) = HeaderName::try_from(s) { - self.inner.get_mut(&name).map(|v| v.first_mut()) - } else { - None - } - } - } - } - - /// Returns true if the map contains a value for the specified key. - pub fn contains_key(&self, key: N) -> bool { - match key.as_name() { - Either::Left(name) => self.inner.contains_key(name), - Either::Right(s) => { - if let Ok(name) = HeaderName::try_from(s) { - self.inner.contains_key(&name) - } else { - false - } - } - } - } - - /// An iterator visiting all key-value pairs. + /// let mut iter = map.iter(); + /// assert!(iter.next().is_none()); /// - /// The iteration order is arbitrary, but consistent across platforms for - /// the same crate version. Each key will be yielded once per associated - /// value. So, if a key has 3 associated values, it will be yielded 3 times. + /// map.append(header::HOST, HeaderValue::from_static("duck.com")); + /// map.append(header::SET_COOKIE, HeaderValue::from_static("one=1")); + /// map.append(header::SET_COOKIE, HeaderValue::from_static("two=2")); + /// + /// let mut iter = map.iter(); + /// assert!(iter.next().is_some()); + /// assert!(iter.next().is_some()); + /// assert!(iter.next().is_some()); + /// assert!(iter.next().is_none()); + /// + /// let pairs = map.iter().collect::>(); + /// assert!(pairs.contains(&(&header::HOST, &HeaderValue::from_static("duck.com")))); + /// assert!(pairs.contains(&(&header::SET_COOKIE, &HeaderValue::from_static("one=1")))); + /// assert!(pairs.contains(&(&header::SET_COOKIE, &HeaderValue::from_static("two=2")))); + /// ``` pub fn iter(&self) -> Iter<'_> { Iter::new(self.inner.iter()) } - /// An iterator visiting all keys. + /// An iterator over all contained header names. /// - /// The iteration order is arbitrary, but consistent across platforms for - /// the same crate version. Each key will be yielded only once even if it - /// has multiple associated values. + /// Each name will only be yielded once even if it has multiple associated values. The iteration + /// order should be considered arbitrary. + /// + /// # Examples + /// ``` + /// # use actix_http::http::{header, HeaderMap, HeaderValue}; + /// let mut map = HeaderMap::new(); + /// + /// let mut iter = map.keys(); + /// assert!(iter.next().is_none()); + /// + /// map.append(header::HOST, HeaderValue::from_static("duck.com")); + /// map.append(header::SET_COOKIE, HeaderValue::from_static("one=1")); + /// map.append(header::SET_COOKIE, HeaderValue::from_static("two=2")); + /// + /// let keys = map.keys().cloned().collect::>(); + /// assert_eq!(keys.len(), 2); + /// assert!(keys.contains(&header::HOST)); + /// assert!(keys.contains(&header::SET_COOKIE)); + /// ``` pub fn keys(&self) -> Keys<'_> { Keys(self.inner.keys()) } - /// Inserts a key-value pair into the map. + /// Clears the map, returning all name-value sets as an iterator. /// - /// If the map did not previously have this key present, then `None` is - /// returned. + /// Header names will only be yielded for the first value in each set. All items that are + /// yielded without a name and after an item with a name are associated with that same name. + /// The first item will always contain a name. /// - /// If the map did have this key present, the new value is associated with - /// the key and all previous values are removed. **Note** that only a single - /// one of the previous values is returned. If there are multiple values - /// that have been previously associated with the key, then the first one is - /// returned. See `insert_mult` on `OccupiedEntry` for an API that returns - /// all values. + /// Keeps the allocated memory for reuse. + /// # Examples + /// ``` + /// # use actix_http::http::{header, HeaderMap, HeaderValue}; + /// let mut map = HeaderMap::new(); /// - /// The key is not updated, though; this matters for types that can be `==` - /// without being identical. - pub fn insert(&mut self, key: HeaderName, val: HeaderValue) { - let _ = self.inner.insert(key, Value::One(val)); - } - - /// Inserts a key-value pair into the map. + /// let mut iter = map.drain(); + /// assert!(iter.next().is_none()); + /// drop(iter); /// - /// If the map did not previously have this key present, then `false` is - /// returned. + /// map.append(header::SET_COOKIE, HeaderValue::from_static("one=1")); + /// map.append(header::SET_COOKIE, HeaderValue::from_static("two=2")); /// - /// If the map did have this key present, the new value is pushed to the end - /// of the list of values currently associated with the key. The key is not - /// updated, though; this matters for types that can be `==` without being - /// identical. - pub fn append(&mut self, key: HeaderName, value: HeaderValue) { - match self.inner.entry(key) { - Entry::Occupied(mut entry) => entry.get_mut().append(value), - Entry::Vacant(entry) => { - entry.insert(Value::One(value)); - } - } - } - - /// Removes all headers for a particular header name from the map. - pub fn remove(&mut self, key: N) { - match key.as_name() { - Either::Left(name) => { - let _ = self.inner.remove(name); - } - Either::Right(s) => { - if let Ok(name) = HeaderName::try_from(s) { - let _ = self.inner.remove(&name); - } - } - } + /// let mut iter = map.drain(); + /// assert_eq!(iter.next().unwrap(), (Some(header::SET_COOKIE), HeaderValue::from_static("one=1"))); + /// assert_eq!(iter.next().unwrap(), (None, HeaderValue::from_static("two=2"))); + /// drop(iter); + /// + /// assert!(map.is_empty()); + /// ``` + pub fn drain(&mut self) -> Drain<'_> { + Drain::new(self.inner.drain()) } } -#[doc(hidden)] -pub trait AsName { - fn as_name(&self) -> Either<&HeaderName, &str>; -} - -impl AsName for HeaderName { - fn as_name(&self) -> Either<&HeaderName, &str> { - Either::Left(self) - } -} - -impl<'a> AsName for &'a HeaderName { - fn as_name(&self) -> Either<&HeaderName, &str> { - Either::Left(self) - } -} - -impl<'a> AsName for &'a str { - fn as_name(&self) -> Either<&HeaderName, &str> { - Either::Right(self) - } -} - -impl AsName for String { - fn as_name(&self) -> Either<&HeaderName, &str> { - Either::Right(self.as_str()) - } -} - -impl<'a> AsName for &'a String { - fn as_name(&self) -> Either<&HeaderName, &str> { - Either::Right(self.as_str()) - } -} - -/// Iterator for all values in a `HeaderMap` with the same name. -pub struct GetAll<'a> { - idx: usize, - item: Option<&'a Value>, -} - -impl<'a> Iterator for GetAll<'a> { - type Item = &'a HeaderValue; +/// Note that this implementation will clone a [HeaderName] for each value. +impl IntoIterator for HeaderMap { + type Item = (HeaderName, HeaderValue); + type IntoIter = IntoIter; #[inline] - fn next(&mut self) -> Option<&'a HeaderValue> { - if let Some(ref val) = self.item { - match val { - Value::One(ref val) => { - self.item.take(); - Some(val) - } - Value::Multi(ref vec) => { - if self.idx < vec.len() { - let item = Some(&vec[self.idx]); - self.idx += 1; - item - } else { - self.item.take(); - None - } - } - } - } else { - None - } - } -} - -pub struct Keys<'a>(hash_map::Keys<'a, HeaderName, Value>); - -impl<'a> Iterator for Keys<'a> { - type Item = &'a HeaderName; - - #[inline] - fn next(&mut self) -> Option<&'a HeaderName> { - self.0.next() + fn into_iter(self) -> Self::IntoIter { + IntoIter::new(self.inner.into_iter()) } } @@ -334,23 +565,116 @@ impl<'a> IntoIterator for &'a HeaderMap { type Item = (&'a HeaderName, &'a HeaderValue); type IntoIter = Iter<'a>; + #[inline] fn into_iter(self) -> Self::IntoIter { - self.iter() + Iter::new(self.inner.iter()) } } -pub struct Iter<'a> { +/// Iterator for all values with the same header name. +/// +/// See [`HeaderMap::get_all`]. +#[derive(Debug)] +pub struct GetAll<'a> { idx: usize, - current: Option<(&'a HeaderName, &'a SmallVec<[HeaderValue; 4]>)>, - iter: hash_map::Iter<'a, HeaderName, Value>, + 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)), + } + } +} + +/// Iterator for owned [`HeaderValue`]s with the same associated [`HeaderName`] returned from methods +/// on [`HeaderMap`] that remove or replace items. +#[derive(Debug)] +pub struct Removed { + inner: Option>, +} + +impl<'a> Removed { + fn new(value: Option) -> Self { + let inner = value.map(|value| value.inner.into_iter()); + Self { inner } + } +} + +impl Iterator for Removed { + type Item = HeaderValue; + + #[inline] + fn next(&mut self) -> Option { + self.inner.as_mut()?.next() + } + + #[inline] + fn size_hint(&self) -> (usize, Option) { + match self.inner { + Some(ref iter) => iter.size_hint(), + None => (0, None), + } + } +} + +/// Iterator over all [`HeaderName`]s in the map. +#[derive(Debug)] +pub struct Keys<'a>(hash_map::Keys<'a, HeaderName, Value>); + +impl<'a> Iterator for Keys<'a> { + type Item = &'a HeaderName; + + #[inline] + fn next(&mut self) -> Option { + self.0.next() + } + + #[inline] + fn size_hint(&self) -> (usize, Option) { + self.0.size_hint() + } +} + +#[derive(Debug)] +pub struct Iter<'a> { + inner: hash_map::Iter<'a, HeaderName, Value>, + multi_inner: Option<(&'a HeaderName, &'a SmallVec<[HeaderValue; 4]>)>, + multi_idx: usize, } impl<'a> Iter<'a> { fn new(iter: hash_map::Iter<'a, HeaderName, Value>) -> Self { Self { - iter, - idx: 0, - current: None, + inner: iter, + multi_idx: 0, + multi_inner: None, } } } @@ -358,28 +682,272 @@ impl<'a> Iter<'a> { impl<'a> Iterator for Iter<'a> { type Item = (&'a HeaderName, &'a HeaderValue); - #[inline] - fn next(&mut self) -> Option<(&'a HeaderName, &'a HeaderValue)> { - if let Some(ref mut item) = self.current { - if self.idx < item.1.len() { - let item = (item.0, &item.1[self.idx]); - self.idx += 1; - return Some(item); - } else { - self.idx = 0; - self.current.take(); - } - } - if let Some(item) = self.iter.next() { - match item.1 { - Value::One(ref value) => Some((item.0, value)), - Value::Multi(ref vec) => { - self.current = Some((item.0, vec)); - self.next() + fn next(&mut self) -> Option { + // handle in-progress multi value lists first + if let Some((ref name, ref mut vals)) = self.multi_inner { + match vals.get(self.multi_idx) { + Some(val) => { + self.multi_idx += 1; + return Some((name, val)); + } + None => { + // no more items in value list; reset state + self.multi_idx = 0; + self.multi_inner = None; } } - } else { - None + } + + let (name, value) = self.inner.next()?; + + // set up new inner iter and recurse into it + self.multi_inner = Some((name, &value.inner)); + self.next() + } + + #[inline] + fn size_hint(&self) -> (usize, Option) { + // take inner lower bound + // make no attempt at an upper bound + (self.inner.size_hint().0, None) + } +} + +/// Iterator over drained name-value pairs. +/// +/// Iterator items are `(Option, HeaderValue)` to avoid cloning. +#[derive(Debug)] +pub struct Drain<'a> { + inner: hash_map::Drain<'a, HeaderName, Value>, + multi_inner: Option<(Option, SmallVec<[HeaderValue; 4]>)>, + multi_idx: usize, +} + +impl<'a> Drain<'a> { + fn new(iter: hash_map::Drain<'a, HeaderName, Value>) -> Self { + Self { + inner: iter, + multi_inner: None, + multi_idx: 0, } } } + +impl<'a> Iterator for Drain<'a> { + type Item = (Option, HeaderValue); + + fn next(&mut self) -> Option { + // handle in-progress multi value iterators first + if let Some((ref mut name, ref mut vals)) = self.multi_inner { + if !vals.is_empty() { + // OPTIMIZE: array removals + return Some((name.take(), vals.remove(0))); + } else { + // no more items in value iterator; reset state + self.multi_inner = None; + self.multi_idx = 0; + } + } + + let (name, value) = self.inner.next()?; + + // set up new inner iter and recurse into it + self.multi_inner = Some((Some(name), value.inner)); + self.next() + } + + #[inline] + fn size_hint(&self) -> (usize, Option) { + // take inner lower bound + // make no attempt at an upper bound + (self.inner.size_hint().0, None) + } +} + +/// Iterator over owned name-value pairs. +/// +/// Implementation necessarily clones header names for each value. +#[derive(Debug)] +pub struct IntoIter { + inner: hash_map::IntoIter, + multi_inner: Option<(HeaderName, smallvec::IntoIter<[HeaderValue; 4]>)>, +} + +impl IntoIter { + fn new(inner: hash_map::IntoIter) -> Self { + Self { + inner, + multi_inner: None, + } + } +} + +impl Iterator for IntoIter { + type Item = (HeaderName, HeaderValue); + + fn next(&mut self) -> Option { + // handle in-progress multi value iterators first + if let Some((ref name, ref mut vals)) = self.multi_inner { + match vals.next() { + Some(val) => { + return Some((name.clone(), val)); + } + None => { + // no more items in value iterator; reset state + self.multi_inner = None; + } + } + } + + let (name, value) = self.inner.next()?; + + // set up new inner iter and recurse into it + self.multi_inner = Some((name, value.inner.into_iter())); + self.next() + } + + #[inline] + fn size_hint(&self) -> (usize, Option) { + // take inner lower bound + // make no attempt at an upper bound + (self.inner.size_hint().0, None) + } +} + +#[cfg(test)] +mod tests { + use http::header; + + use super::*; + + #[test] + fn create() { + let map = HeaderMap::new(); + assert_eq!(map.len(), 0); + assert_eq!(map.capacity(), 0); + + let map = HeaderMap::with_capacity(16); + assert_eq!(map.len(), 0); + assert!(map.capacity() >= 16); + } + + #[test] + fn insert() { + let mut map = HeaderMap::new(); + + map.insert(header::LOCATION, HeaderValue::from_static("/test")); + assert_eq!(map.len(), 1); + } + + #[test] + fn contains() { + let mut map = HeaderMap::new(); + assert!(!map.contains_key(header::LOCATION)); + + map.insert(header::LOCATION, HeaderValue::from_static("/test")); + assert!(map.contains_key(header::LOCATION)); + assert!(map.contains_key("Location")); + assert!(map.contains_key("Location".to_owned())); + assert!(map.contains_key("location")); + } + + #[test] + fn entries_iter() { + let mut map = HeaderMap::new(); + + map.append(header::HOST, HeaderValue::from_static("duck.com")); + map.append(header::COOKIE, HeaderValue::from_static("one=1")); + map.append(header::COOKIE, HeaderValue::from_static("two=2")); + + let mut iter = map.iter(); + assert!(iter.next().is_some()); + assert!(iter.next().is_some()); + assert!(iter.next().is_some()); + assert!(iter.next().is_none()); + + let pairs = map.iter().collect::>(); + assert!(pairs.contains(&(&header::HOST, &HeaderValue::from_static("duck.com")))); + assert!(pairs.contains(&(&header::COOKIE, &HeaderValue::from_static("one=1")))); + assert!(pairs.contains(&(&header::COOKIE, &HeaderValue::from_static("two=2")))); + } + + #[test] + fn drain_iter() { + let mut map = HeaderMap::new(); + + map.append(header::COOKIE, HeaderValue::from_static("one=1")); + map.append(header::COOKIE, HeaderValue::from_static("two=2")); + + let mut vals = vec![]; + let mut iter = map.drain(); + + let (name, val) = iter.next().unwrap(); + assert_eq!(name, Some(header::COOKIE)); + vals.push(val); + + let (name, val) = iter.next().unwrap(); + assert!(name.is_none()); + vals.push(val); + + assert!(vals.contains(&HeaderValue::from_static("one=1"))); + assert!(vals.contains(&HeaderValue::from_static("two=2"))); + + assert!(iter.next().is_none()); + drop(iter); + + assert!(map.is_empty()); + } + + #[test] + fn entries_into_iter() { + let mut map = HeaderMap::new(); + + map.append(header::HOST, HeaderValue::from_static("duck.com")); + map.append(header::COOKIE, HeaderValue::from_static("one=1")); + map.append(header::COOKIE, HeaderValue::from_static("two=2")); + + let mut iter = map.into_iter(); + assert!(iter.next().is_some()); + assert!(iter.next().is_some()); + assert!(iter.next().is_some()); + assert!(iter.next().is_none()); + } + + #[test] + fn iter_and_into_iter_same_order() { + let mut map = HeaderMap::new(); + + map.append(header::HOST, HeaderValue::from_static("duck.com")); + map.append(header::COOKIE, HeaderValue::from_static("one=1")); + map.append(header::COOKIE, HeaderValue::from_static("two=2")); + + let mut iter = map.iter(); + let mut into_iter = map.clone().into_iter(); + + assert_eq!(iter.next().map(owned_pair), into_iter.next()); + assert_eq!(iter.next().map(owned_pair), into_iter.next()); + assert_eq!(iter.next().map(owned_pair), into_iter.next()); + assert_eq!(iter.next().map(owned_pair), into_iter.next()); + } + + #[test] + fn get_all_and_remove_same_order() { + let mut map = HeaderMap::new(); + + map.append(header::COOKIE, HeaderValue::from_static("one=1")); + map.append(header::COOKIE, HeaderValue::from_static("two=2")); + + let mut vals = map.get_all(header::COOKIE); + let mut removed = map.clone().remove(header::COOKIE); + + assert_eq!(vals.next(), removed.next().as_ref()); + assert_eq!(vals.next(), removed.next().as_ref()); + assert_eq!(vals.next(), removed.next().as_ref()); + } + + fn owned_pair<'a>( + (name, val): (&'a HeaderName, &'a HeaderValue), + ) -> (HeaderName, HeaderValue) { + (name.clone(), val.clone()) + } +} diff --git a/actix-http/src/header/mod.rs b/actix-http/src/header/mod.rs index dc97bf5ff..9543d43b6 100644 --- a/actix-http/src/header/mod.rs +++ b/actix-http/src/header/mod.rs @@ -1,4 +1,4 @@ -//! Typed HTTP headers, pre-defined `HeaderName`s, traits for parsing/conversion and other +//! Typed HTTP headers, pre-defined `HeaderName`s, traits for parsing and conversion, and other //! header utility methods. use std::fmt; @@ -11,6 +11,7 @@ pub use http::header::*; use crate::error::ParseError; use crate::httpmessage::HttpMessage; +mod as_name; mod into_pair; mod into_value; mod utils; @@ -23,6 +24,7 @@ pub use self::common::*; #[doc(hidden)] pub use self::shared::*; +pub use self::as_name::AsHeaderName; pub use self::into_pair::IntoHeaderPair; pub use self::into_value::IntoHeaderValue; #[doc(hidden)] @@ -39,16 +41,14 @@ pub trait Header: IntoHeaderValue { fn parse(msg: &T) -> Result; } -#[doc(hidden)] +#[derive(Debug, Default)] pub(crate) struct Writer { buf: BytesMut, } impl Writer { fn new() -> Writer { - Writer { - buf: BytesMut::new(), - } + Writer::default() } fn take(&mut self) -> Bytes { @@ -71,12 +71,8 @@ impl fmt::Write for Writer { /// Convert `http::HeaderMap` to our `HeaderMap`. impl From for HeaderMap { - fn from(map: http::HeaderMap) -> HeaderMap { - let mut new_map = HeaderMap::with_capacity(map.capacity()); - for (h, v) in map.iter() { - new_map.append(h.clone(), v.clone()); - } - new_map + fn from(mut map: http::HeaderMap) -> HeaderMap { + HeaderMap::from_drain(map.drain()) } } diff --git a/actix-http/src/request.rs b/actix-http/src/request.rs index 1031d7dce..fbf4d3c23 100644 --- a/actix-http/src/request.rs +++ b/actix-http/src/request.rs @@ -177,13 +177,17 @@ impl

fmt::Debug for Request

{ self.method(), self.path() )?; + if let Some(q) = self.uri().query().as_ref() { writeln!(f, " query: ?{:?}", q)?; } + writeln!(f, " headers:")?; - for (key, val) in self.headers() { + + for (key, val) in self.headers().iter() { writeln!(f, " {:?}: {:?}", key, val)?; } + Ok(()) } } diff --git a/actix-http/src/response.rs b/actix-http/src/response.rs index 110514e05..113ace221 100644 --- a/actix-http/src/response.rs +++ b/actix-http/src/response.rs @@ -363,7 +363,9 @@ impl ResponseBuilder { { if let Some(parts) = parts(&mut self.head, &self.err) { match header.try_into_header_pair() { - Ok((key, value)) => parts.headers.insert(key, value), + Ok((key, value)) => { + parts.headers.insert(key, value); + } Err(e) => self.err = Some(e.into()), }; } @@ -752,9 +754,11 @@ impl<'a> From<&'a ResponseHead> for ResponseBuilder { let mut msg = BoxedResponseHead::new(head.status); msg.version = head.version; msg.reason = head.reason; - for (k, v) in &head.headers { + + for (k, v) in head.headers.iter() { msg.headers.append(k.clone(), v.clone()); } + msg.no_chunking(!head.chunked()); ResponseBuilder { @@ -893,16 +897,20 @@ mod tests { .max_age(time::Duration::days(1)) .finish(), ) - .del_cookie(&cookies[1]) + .del_cookie(&cookies[0]) .finish(); - let mut val: Vec<_> = resp + let mut val = resp .headers() .get_all(SET_COOKIE) .map(|v| v.to_str().unwrap().to_owned()) - .collect(); + .collect::>(); val.sort(); + + // the .del_cookie call assert!(val[0].starts_with("cookie1=; Max-Age=0;")); + + // the .cookie call assert_eq!( val[1], "name=value; HttpOnly; Path=/test; Domain=www.rust-lang.org; Max-Age=86400" @@ -927,9 +935,9 @@ mod tests { let mut iter = r.cookies(); let v = iter.next().unwrap(); - assert_eq!((v.name(), v.value()), ("cookie3", "val300")); - let v = iter.next().unwrap(); assert_eq!((v.name(), v.value()), ("original", "val100")); + let v = iter.next().unwrap(); + assert_eq!((v.name(), v.value()), ("cookie3", "val300")); } #[test] diff --git a/awc/src/frozen.rs b/awc/src/frozen.rs index 878f404c6..46b4063a0 100644 --- a/awc/src/frozen.rs +++ b/awc/src/frozen.rs @@ -145,7 +145,9 @@ impl FrozenSendBuilder { { match HeaderName::try_from(key) { Ok(key) => match value.try_into_value() { - Ok(value) => self.extra_headers.insert(key, value), + Ok(value) => { + self.extra_headers.insert(key, value); + } Err(e) => self.err = Some(e.into()), }, Err(e) => self.err = Some(e.into()), diff --git a/awc/src/request.rs b/awc/src/request.rs index b9a333b18..c87df9b3b 100644 --- a/awc/src/request.rs +++ b/awc/src/request.rs @@ -159,7 +159,9 @@ impl ClientRequest { H: IntoHeaderPair, { match header.try_into_header_pair() { - Ok((key, value)) => self.head.headers.insert(key, value), + Ok((key, value)) => { + self.head.headers.insert(key, value); + } Err(e) => self.err = Some(e.into()), }; @@ -232,7 +234,9 @@ impl ClientRequest { >::Error: Into, { match HeaderValue::try_from(value) { - Ok(value) => self.head.headers.insert(header::CONTENT_TYPE, value), + Ok(value) => { + self.head.headers.insert(header::CONTENT_TYPE, value); + } Err(e) => self.err = Some(e.into()), } self diff --git a/awc/src/sender.rs b/awc/src/sender.rs index 1cf863d96..ca220be71 100644 --- a/awc/src/sender.rs +++ b/awc/src/sender.rs @@ -304,7 +304,9 @@ impl RequestSender { RequestSender::Owned(head) => { if !head.headers.contains_key(&key) { match value.try_into_value() { - Ok(value) => head.headers.insert(key, value), + Ok(value) => { + head.headers.insert(key, value); + } Err(e) => return Err(e.into()), } } diff --git a/src/request.rs b/src/request.rs index a563518e0..9a5e43185 100644 --- a/src/request.rs +++ b/src/request.rs @@ -445,10 +445,10 @@ mod tests { { let cookies = req.cookies().unwrap(); assert_eq!(cookies.len(), 2); - assert_eq!(cookies[0].name(), "cookie2"); - assert_eq!(cookies[0].value(), "value2"); - assert_eq!(cookies[1].name(), "cookie1"); - assert_eq!(cookies[1].value(), "value1"); + assert_eq!(cookies[0].name(), "cookie1"); + assert_eq!(cookies[0].value(), "value1"); + assert_eq!(cookies[1].name(), "cookie2"); + assert_eq!(cookies[1].value(), "value2"); } let cookie = req.cookie("cookie1"); From dcad9724bc1a056417818f89359476889438074c Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Wed, 10 Feb 2021 02:11:53 -0800 Subject: [PATCH 019/339] ensure poll_flush on h1 connection disconnect (#1974) --- actix-http/src/h1/dispatcher.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/actix-http/src/h1/dispatcher.rs b/actix-http/src/h1/dispatcher.rs index 8fbf3ede0..1d28a0a39 100644 --- a/actix-http/src/h1/dispatcher.rs +++ b/actix-http/src/h1/dispatcher.rs @@ -841,9 +841,8 @@ where if inner.flags.contains(Flags::WRITE_DISCONNECT) { Poll::Ready(Ok(())) } else { - // flush buffer - inner.as_mut().poll_flush(cx)?; - if !inner.write_buf.is_empty() { + // flush buffer and wait on block. + if inner.as_mut().poll_flush(cx)? { Poll::Pending } else { Pin::new(inner.project().io.as_mut().unwrap()) From a290e589823163d24880fc37f678b34b6dcbf38e Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Wed, 10 Feb 2021 12:10:03 +0000 Subject: [PATCH 020/339] prepare beta 2 release set (#1975) --- CHANGES.md | 23 ++-- Cargo.toml | 6 +- README.md | 6 +- actix-files/CHANGES.md | 4 + actix-files/Cargo.toml | 6 +- actix-http-test/CHANGES.md | 4 + actix-http-test/Cargo.toml | 8 +- actix-http-test/README.md | 2 +- actix-http/CHANGES.md | 219 +++++++++--------------------- actix-http/Cargo.toml | 4 +- actix-http/README.md | 11 +- actix-multipart/CHANGES.md | 4 + actix-multipart/Cargo.toml | 8 +- actix-multipart/README.md | 22 ++- actix-multipart/src/lib.rs | 2 +- actix-web-actors/CHANGES.md | 4 + actix-web-actors/Cargo.toml | 8 +- actix-web-actors/README.md | 22 ++- actix-web-actors/src/lib.rs | 2 +- actix-web-codegen/Cargo.toml | 2 +- actix-web-codegen/src/lib.rs | 2 +- awc/CHANGES.md | 3 + awc/Cargo.toml | 10 +- awc/README.md | 6 +- docs/graphs/web-focus.dot | 7 +- docs/graphs/web-only.dot | 1 - src/middleware/compat.rs | 2 +- src/middleware/compress.rs | 2 +- src/middleware/condition.rs | 2 +- src/middleware/default_headers.rs | 2 +- src/middleware/err_handlers.rs | 2 +- src/middleware/logger.rs | 2 +- src/middleware/normalize.rs | 2 +- src/types/json.rs | 2 +- src/types/path.rs | 2 +- src/types/payload.rs | 6 +- src/types/query.rs | 4 +- src/web.rs | 1 + 38 files changed, 191 insertions(+), 234 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 9a3b7fe3e..5eac57fe6 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,26 +1,29 @@ # Changes ## Unreleased - 2021-xx-xx + + +## 4.0.0-beta.2 - 2021-xx-xx ### 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 vector of same type of `HttpServiceFactory` 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 more simple and performant. [#1891] -* `ServiceRequest::into_parts` and `ServiceRequest::from_parts` would not fail. - `ServiceRequest::from_request` would not fail and no payload would be generated [#1893] + 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` +* `test::{call_service, read_response, read_response_json, send_request}` take `&Service` in argument [#1905] -* `App::wrap_fn`, `Resource::wrap_fn` and `Scope::wrap_fn` would give `&Service` in closure - argument [#1905] -* `web::block` accept any closure that has an output bound to `Send` and `'static`. [#1957] - +* `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 `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] diff --git a/Cargo.toml b/Cargo.toml index 80187c8a5..217594a48 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-web" -version = "4.0.0-beta.1" +version = "4.0.0-beta.2" authors = ["Nikolay Kim "] description = "Actix Web is a powerful, pragmatic, and extremely fast web framework for Rust" readme = "README.md" @@ -83,8 +83,8 @@ actix-utils = "3.0.0-beta.2" actix-tls = { version = "3.0.0-beta.3", default-features = false, optional = true } actix-web-codegen = "0.4.0" -actix-http = "3.0.0-beta.1" -awc = { version = "3.0.0-beta.1", default-features = false } +actix-http = "3.0.0-beta.2" +awc = { version = "3.0.0-beta.2", default-features = false } ahash = "0.7" bytes = "1" diff --git a/README.md b/README.md index 8a703117f..684310961 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=3.3.2)](https://docs.rs/actix-web/3.3.2) +[![Documentation](https://docs.rs/actix-web/badge.svg?version=4.0.0-beta.2)](https://docs.rs/actix-web/4.0.0-beta.2) [![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.svg) -[![Dependency Status](https://deps.rs/crate/actix-web/3.3.2/status.svg)](https://deps.rs/crate/actix-web/3.3.2) +![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.2/status.svg)](https://deps.rs/crate/actix-web/4.0.0-beta.2)
[![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 8e5566b61..6e2a241ac 100644 --- a/actix-files/CHANGES.md +++ b/actix-files/CHANGES.md @@ -1,12 +1,16 @@ # Changes ## Unreleased - 2021-xx-xx + + +## 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] [#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] diff --git a/actix-files/Cargo.toml b/actix-files/Cargo.toml index b4317596c..f7e1a6c0d 100644 --- a/actix-files/Cargo.toml +++ b/actix-files/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-files" -version = "0.6.0-beta.1" +version = "0.6.0-beta.2" authors = ["Nikolay Kim "] description = "Static file serving for Actix Web" readme = "README.md" @@ -17,7 +17,7 @@ name = "actix_files" path = "src/lib.rs" [dependencies] -actix-web = { version = "4.0.0-beta.1", default-features = false } +actix-web = { version = "4.0.0-beta.2", default-features = false } actix-service = "2.0.0-beta.4" askama_escape = "0.10" @@ -33,4 +33,4 @@ percent-encoding = "2.1" [dev-dependencies] actix-rt = "2" -actix-web = "4.0.0-beta.1" +actix-web = "4.0.0-beta.2" diff --git a/actix-http-test/CHANGES.md b/actix-http-test/CHANGES.md index ed6d87a60..2f47d700d 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.2 - 2021-02-10 +* No notable changes. + + ## 3.0.0-beta.1 - 2021-01-07 * Update `bytes` to `1.0`. [#1813] diff --git a/actix-http-test/Cargo.toml b/actix-http-test/Cargo.toml index 25fd74a10..3eef4e727 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.1" +version = "3.0.0-beta.2" authors = ["Nikolay Kim "] description = "Various helpers for Actix applications to use during testing" readme = "README.md" @@ -35,7 +35,7 @@ actix-tls = "3.0.0-beta.3" actix-utils = "3.0.0-beta.2" actix-rt = "2" actix-server = "2.0.0-beta.3" -awc = "3.0.0-beta.1" +awc = "3.0.0-beta.2" 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 = "4.0.0-beta.1" -actix-http = "3.0.0-beta.1" +actix-web = "4.0.0-beta.2" +actix-http = "3.0.0-beta.2" diff --git a/actix-http-test/README.md b/actix-http-test/README.md index bca9a7976..66f15979d 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=2.1.0)](https://docs.rs/actix-http-test/2.1.0) -![Apache 2.0 or MIT licensed](https://img.shields.io/crates/l/actix-http-test) +![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-http-test) [![Dependency Status](https://deps.rs/crate/actix-http-test/2.1.0/status.svg)](https://deps.rs/crate/actix-http-test/2.1.0) [![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) diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index bef17fb08..07a64701e 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -1,6 +1,9 @@ # Changes ## Unreleased - 2021-xx-xx + + +## 3.0.0-beta.2 - 2021-02-19 ### Added * `IntoHeaderPair` trait that allows using typed and untyped headers in the same methods. [#1869] * `ResponseBuilder::insert_header` method which allows using typed headers. [#1869] @@ -18,11 +21,12 @@ * Renamed `IntoHeaderValue::{try_into => try_into_value}` to avoid ambiguity with std `TryInto` trait. [#1894] * `Extensions::insert` returns Option of replaced item. [#1904] -* Remove `HttpResponseBuilder::json2()` and make `HttpResponseBuilder::json()` take a value by - reference. [#1903] -* `client::error::ConnectError` Resolver variant contains `Box` type [#1905] +* 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 struct. It's only triggered with blocking thread pool is dead. [#1957] +* 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] @@ -123,15 +127,14 @@ * Update actix-connect and actix-tls dependencies. -## [2.0.0-beta.3] - 2020-08-14 - +## 2.0.0-beta.3 - 2020-08-14 ### Fixed * Memory leak of `client::pool::ConnectorPoolSupport`. [#1626] [#1626]: https://github.com/actix/actix-web/pull/1626 -## [2.0.0-beta.2] - 2020-07-21 +## 2.0.0-beta.2 - 2020-07-21 ### Fixed * Potential UB in h1 decoder using uninitialized memory. [#1614] @@ -142,10 +145,8 @@ [#1615]: https://github.com/actix/actix-web/pull/1615 -## [2.0.0-beta.1] - 2020-07-11 - +## 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] @@ -155,33 +156,30 @@ [#1586]: https://github.com/actix/actix-web/pull/1586 [#1580]: https://github.com/actix/actix-web/pull/1580 -## [2.0.0-alpha.4] - 2020-05-21 +## 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 no_chunking function [#1439] +* 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 `u64` instead of a `usize`. * Update `base64` dependency to 0.12 ### Fixed - * Support parsing of `SameSite=None` [#1503] [#1439]: https://github.com/actix/actix-web/pull/1439 [#1503]: https://github.com/actix/actix-web/pull/1503 -## [2.0.0-alpha.3] - 2020-05-08 +## 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 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 will be deprecated in the near future. @@ -189,338 +187,247 @@ [#1422]: https://github.com/actix/actix-web/pull/1422 [#1487]: https://github.com/actix/actix-web/pull/1487 -## [2.0.0-alpha.2] - 2020-03-07 +## 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 respectively - to improve download speed for awc when downloading large objects. [#1394] - -* client::Connector accepts initial_window_size and initial_connection_window_size HTTP2 configuration. [#1394] - +* 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 + HTTP2 configuration. [#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 -## [2.0.0-alpha.1] - 2020-02-27 +## 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 &mut self in the poll_next(). +* 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. ### Fixed - * Allow `SameSite=None` cookies to be sent in a response. -## [1.0.1] - 2019-12-20 +## 1.0.1 - 2019-12-20 ### Fixed - * Poll upgrade service's readiness from HTTP service handlers - * Replace brotli with brotli2 #1224 -## [1.0.0] - 2019-12-13 +## 1.0.0 - 2019-12-13 ### Added - * Add websockets continuation frame support ### Changed - * Replace `flate2-xxx` features with `compress` -## [1.0.0-alpha.5] - 2019-12-09 +## 1.0.0-alpha.5 - 2019-12-09 ### Fixed - * Check `Upgrade` service readiness before calling it - -* Fix buffer remaining capacity calcualtion +* Fix buffer remaining capacity calculation ### Changed - * Websockets: Ping and Pong should have binary data #1049 -## [1.0.0-alpha.4] - 2019-12-08 +## 1.0.0-alpha.4 - 2019-12-08 ### Added - * Add impl ResponseBuilder for Error ### Changed - * Use rust based brotli compression library -## [1.0.0-alpha.3] - 2019-12-07 - +## 1.0.0-alpha.3 - 2019-12-07 ### Changed - * Migrate to tokio 0.2 - * Migrate to `std::future` -## [0.2.11] - 2019-11-06 - +## 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 `actix_files::NamedFile` to be more compatible. (#1151) - +* 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` ### Fixed +* To be compatible with non-English error responses, `ResponseError` rendered with `text/plain; + charset=utf-8` header [#1118] -* 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 -## [0.2.10] - 2019-09-11 - +## 0.2.10 - 2019-09-11 ### Added - -* Add support for sending HTTP requests with `Rc` in addition to sending HTTP requests with `RequestHead` +* 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 -## [0.2.9] - 2019-08-13 - +## 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 ### Fixed - * Fixed a panic in the HTTP2 handshake in client HTTP requests (#1031) -## [0.2.8] - 2019-08-01 - +## 0.2.8 - 2019-08-01 ### Added - * Add `rustls` support - * Add `Clone` impl for `HeaderMap` ### Fixed - * awc client panic #1016 - -* Invalid response with compression middleware enabled, but compression-related features disabled #997 +* Invalid response with compression middleware enabled, but compression-related features + disabled #997 -## [0.2.7] - 2019-07-18 - +## 0.2.7 - 2019-07-18 ### Added - * Add support for downcasting response errors #986 -## [0.2.6] - 2019-07-17 - +## 0.2.6 - 2019-07-17 ### Changed - * Replace `ClonableService` with local copy - * Upgrade `rand` dependency version to 0.7 -## [0.2.5] - 2019-06-28 - +## 0.2.5 - 2019-06-28 ### Added - * 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` -## [0.2.4] - 2019-06-16 - +## 0.2.4 - 2019-06-16 ### Fixed - * Do not compress NoContent (204) responses #918 -## [0.2.3] - 2019-06-02 - +## 0.2.3 - 2019-06-02 ### Added - * Debug impl for ResponseBuilder - * From SizedStream and BodyStream for Body ### Changed - * SizedStream uses u64 -## [0.2.2] - 2019-05-29 - +## 0.2.2 - 2019-05-29 ### Fixed - * Parse incoming stream before closing stream on disconnect #868 -## [0.2.1] - 2019-05-25 - +## 0.2.1 - 2019-05-25 ### Fixed - * Handle socket read disconnect -## [0.2.0] - 2019-05-12 - +## 0.2.0 - 2019-05-12 ### Changed - * Update actix-service to 0.4 - * Expect and upgrade services accept `ServerConfig` config. ### Deleted - * `OneRequest` service -## [0.1.5] - 2019-05-04 - +## 0.1.5 - 2019-05-04 ### Fixed - * Clean up response extensions in response pool #817 -## [0.1.4] - 2019-04-24 - +## 0.1.4 - 2019-04-24 ### Added - * Allow to render h1 request headers in `Camel-Case` ### Fixed - * Read until eof for http/1.0 responses #771 -## [0.1.3] - 2019-04-23 - +## 0.1.3 - 2019-04-23 ### Fixed - * Fix http client pool management - * Fix http client wait queue management #794 -## [0.1.2] - 2019-04-23 - +## 0.1.2 - 2019-04-23 ### Fixed - * Fix BorrowMutError panic in client connector #793 -## [0.1.1] - 2019-04-19 - +## 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 -## [0.1.0] - 2019-04-16 - +## 0.1.0 - 2019-04-16 ### Added - * Expose peer addr via `Request::peer_addr()` and `RequestHead::peer_addr` ### Changed - * `actix_http::encoding` always available - * use trust-dns-resolver 0.11.0 -## [0.1.0-alpha.5] - 2019-04-12 - +## 0.1.0-alpha.5 - 2019-04-12 ### Added - * 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 -## [0.1.0-alpha.4] - 2019-04-08 - +## 0.1.0-alpha.4 - 2019-04-08 ### Added - * 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 comression +* Use thread pool for response body compression ### Deleted - * Removed PayloadBuffer -## [0.1.0-alpha.3] - 2019-04-02 - +## 0.1.0-alpha.3 - 2019-04-02 ### Added - * 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 -## [0.1.0-alpha.2] - 2019-03-29 - +## 0.1.0-alpha.2 - 2019-03-29 ### Added - * Added ws::Message::Nop, no-op websockets message ### Changed - -* Do not use thread pool for decomression 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 - +## 0.1.0-alpha.1 - 2019-03-28 * Initial impl diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index e974e61df..67ea0a12d 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-http" -version = "3.0.0-beta.1" +version = "3.0.0-beta.2" authors = ["Nikolay Kim "] description = "HTTP primitives for the Actix ecosystem" readme = "README.md" @@ -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.1", features = ["openssl"] } +actix-http-test = { version = "3.0.0-beta.2", features = ["openssl"] } actix-tls = { version = "3.0.0-beta.2", features = ["openssl"] } criterion = "0.3" env_logger = "0.8" diff --git a/actix-http/README.md b/actix-http/README.md index 9dfb85e24..4ec5b97af 100644 --- a/actix-http/README.md +++ b/actix-http/README.md @@ -3,10 +3,13 @@ > 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=2.2.0)](https://docs.rs/actix-http/2.2.0) -![Apache 2.0 or MIT licensed](https://img.shields.io/crates/l/actix-http) -[![Dependency Status](https://deps.rs/crate/actix-http/2.2.0/status.svg)](https://deps.rs/crate/actix-http/2.2.0) -[![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](https://docs.rs/actix-http/badge.svg?version=3.0.0-beta.2)](https://docs.rs/actix-http/3.0.0-beta.2) +[![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.2/status.svg)](https://deps.rs/crate/actix-http/3.0.0-beta.2) +[![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) ## Documentation & Resources diff --git a/actix-multipart/CHANGES.md b/actix-multipart/CHANGES.md index e1fe9c4af..2142ebf4b 100644 --- a/actix-multipart/CHANGES.md +++ b/actix-multipart/CHANGES.md @@ -3,6 +3,10 @@ ## Unreleased - 2021-xx-xx +## 0.4.0-beta.2 - 2021-02-10 +* 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] diff --git a/actix-multipart/Cargo.toml b/actix-multipart/Cargo.toml index beecd2e9d..6a6b4ac14 100644 --- a/actix-multipart/Cargo.toml +++ b/actix-multipart/Cargo.toml @@ -1,8 +1,8 @@ [package] name = "actix-multipart" -version = "0.4.0-beta.1" +version = "0.4.0-beta.2" authors = ["Nikolay Kim "] -description = "Multipart support for actix web framework." +description = "Multipart form support for Actix Web" readme = "README.md" keywords = ["http", "web", "framework", "async", "futures"] homepage = "https://actix.rs" @@ -16,7 +16,7 @@ name = "actix_multipart" path = "src/lib.rs" [dependencies] -actix-web = { version = "4.0.0-beta.1", default-features = false } +actix-web = { version = "4.0.0-beta.2", default-features = false } actix-utils = "3.0.0-beta.2" bytes = "1" @@ -29,4 +29,4 @@ twoway = "0.2" [dev-dependencies] actix-rt = "2" -actix-http = "3.0.0-beta.1" +actix-http = "3.0.0-beta.2" diff --git a/actix-multipart/README.md b/actix-multipart/README.md index edb2e0020..defcf7828 100644 --- a/actix-multipart/README.md +++ b/actix-multipart/README.md @@ -1,8 +1,18 @@ -# Multipart support for actix web framework [![Build Status](https://travis-ci.org/actix/actix-web.svg?branch=master)](https://travis-ci.org/actix/actix-web) [![codecov](https://codecov.io/gh/actix/actix-web/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-web) [![crates.io](https://meritbadge.herokuapp.com/actix-multipart)](https://crates.io/crates/actix-multipart) [![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) +# actix-multipart -## Documentation & community resources +> Multipart form support for Actix Web. -* [API Documentation](https://docs.rs/actix-multipart/) -* [Chat on gitter](https://gitter.im/actix/actix) -* Cargo package: [actix-multipart](https://crates.io/crates/actix-multipart) -* Minimum supported Rust version: 1.40 or later +[![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.2)](https://docs.rs/actix-multipart/0.4.0-beta.2) +[![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.2/status.svg)](https://deps.rs/crate/actix-multipart/0.4.0-beta.2) +[![Download](https://img.shields.io/crates/d/actix-multipart.svg)](https://crates.io/crates/actix-multipart) +[![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) + +## 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-multipart/src/lib.rs b/actix-multipart/src/lib.rs index 46dd0ee9b..38a24e28f 100644 --- a/actix-multipart/src/lib.rs +++ b/actix-multipart/src/lib.rs @@ -1,4 +1,4 @@ -//! Multipart form support for Actix web. +//! Multipart form support for Actix Web. #![deny(rust_2018_idioms)] #![allow(clippy::borrow_interior_mutable_const)] diff --git a/actix-web-actors/CHANGES.md b/actix-web-actors/CHANGES.md index f75c6805f..acd9ceada 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.2 - 2021-02-10 +* No notable changes. + + ## 4.0.0-beta.1 - 2021-01-07 * Update `pin-project` to `1.0`. * Update `bytes` to `1.0`. [#1813] diff --git a/actix-web-actors/Cargo.toml b/actix-web-actors/Cargo.toml index 522279fd0..5f689ddb7 100644 --- a/actix-web-actors/Cargo.toml +++ b/actix-web-actors/Cargo.toml @@ -1,8 +1,8 @@ [package] name = "actix-web-actors" -version = "4.0.0-beta.1" +version = "4.0.0-beta.2" authors = ["Nikolay Kim "] -description = "Actix actors support for actix web framework." +description = "Actix actors support for Actix Web" readme = "README.md" keywords = ["actix", "http", "web", "framework", "async"] homepage = "https://actix.rs" @@ -18,8 +18,8 @@ path = "src/lib.rs" [dependencies] actix = { version = "0.11.0-beta.2", default-features = false } actix-codec = "0.4.0-beta.1" -actix-http = "3.0.0-beta.1" -actix-web = { version = "4.0.0-beta.1", default-features = false } +actix-http = "3.0.0-beta.2" +actix-web = { version = "4.0.0-beta.2", default-features = false } bytes = "1" bytestring = "1" diff --git a/actix-web-actors/README.md b/actix-web-actors/README.md index fb8c3a621..c9b588153 100644 --- a/actix-web-actors/README.md +++ b/actix-web-actors/README.md @@ -1,8 +1,18 @@ -Actix actors support for actix web framework [![Build Status](https://travis-ci.org/actix/actix-web.svg?branch=master)](https://travis-ci.org/actix/actix-web) [![codecov](https://codecov.io/gh/actix/actix-web/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-web) [![crates.io](https://meritbadge.herokuapp.com/actix-web-actors)](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) +# actix-web-actors -## Documentation & community resources +> Actix actors support for Actix Web. -* [API Documentation](https://docs.rs/actix-web-actors/) -* [Chat on gitter](https://gitter.im/actix/actix) -* Cargo package: [actix-web-actors](https://crates.io/crates/actix-web-actors) -* Minimum supported Rust version: 1.40 or later +[![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=0.5.0)](https://docs.rs/actix-web-actors/0.5.0) +[![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/0.5.0/status.svg)](https://deps.rs/crate/actix-web-actors/0.5.0) +[![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) + +## 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-actors/src/lib.rs b/actix-web-actors/src/lib.rs index 0421f05fb..7a4823d91 100644 --- a/actix-web-actors/src/lib.rs +++ b/actix-web-actors/src/lib.rs @@ -1,4 +1,4 @@ -//! Actix actors integration for Actix web framework +//! Actix actors support for Actix Web. #![deny(rust_2018_idioms)] #![allow(clippy::borrow_interior_mutable_const)] diff --git a/actix-web-codegen/Cargo.toml b/actix-web-codegen/Cargo.toml index 04bd10421..377427fd1 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" -actix-web = "4.0.0-beta.1" +actix-web = "4.0.0-beta.2" futures-util = { version = "0.3.7", default-features = false } trybuild = "1" rustversion = "1" diff --git a/actix-web-codegen/src/lib.rs b/actix-web-codegen/src/lib.rs index ede1e0005..1248fe757 100644 --- a/actix-web-codegen/src/lib.rs +++ b/actix-web-codegen/src/lib.rs @@ -159,7 +159,7 @@ method_macro! { /// # Actix Web Re-export /// This macro can be applied with `#[actix_web::main]` when used in Actix Web applications. /// -/// # Usage +/// # Examples /// ```rust /// #[actix_web_codegen::main] /// async fn main() { diff --git a/awc/CHANGES.md b/awc/CHANGES.md index 80895f46d..20e8af6df 100644 --- a/awc/CHANGES.md +++ b/awc/CHANGES.md @@ -1,6 +1,9 @@ # Changes ## Unreleased - 2021-xx-xx + + +## 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] diff --git a/awc/Cargo.toml b/awc/Cargo.toml index 483e04968..e4178d45e 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "awc" -version = "3.0.0-beta.1" +version = "3.0.0-beta.2" authors = ["Nikolay Kim "] description = "Async HTTP and WebSocket client library built on the Actix ecosystem" readme = "README.md" @@ -42,7 +42,7 @@ trust-dns = ["actix-http/trust-dns"] [dependencies] actix-codec = "0.4.0-beta.1" actix-service = "2.0.0-beta.4" -actix-http = "3.0.0-beta.1" +actix-http = "3.0.0-beta.2" actix-rt = "2" base64 = "0.13" @@ -61,9 +61,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.1", features = ["openssl"] } -actix-http = { version = "3.0.0-beta.1", features = ["openssl"] } -actix-http-test = { version = "3.0.0-beta.1", features = ["openssl"] } +actix-web = { version = "4.0.0-beta.2", features = ["openssl"] } +actix-http = { version = "3.0.0-beta.2", features = ["openssl"] } +actix-http-test = { version = "3.0.0-beta.2", features = ["openssl"] } actix-utils = "3.0.0-beta.1" actix-server = "2.0.0-beta.3" actix-tls = { version = "3.0.0-beta.3", features = ["openssl", "rustls"] } diff --git a/awc/README.md b/awc/README.md index 3d18a07c5..043ae6a41 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=2.0.3)](https://docs.rs/awc/2.0.3) -![Apache 2.0 or MIT licensed](https://img.shields.io/crates/l/awc) -[![Dependency Status](https://deps.rs/crate/awc/2.0.3/status.svg)](https://deps.rs/crate/awc/2.0.3) +[![Documentation](https://docs.rs/awc/badge.svg?version=3.0.0-beta.2)](https://docs.rs/awc/3.0.0-beta.2) +![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/awc) +[![Dependency Status](https://deps.rs/crate/awc/3.0.0-beta.2/status.svg)](https://deps.rs/crate/awc/3.0.0-beta.2) [![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 diff --git a/docs/graphs/web-focus.dot b/docs/graphs/web-focus.dot index 55a82bb41..ec0f7a946 100644 --- a/docs/graphs/web-focus.dot +++ b/docs/graphs/web-focus.dot @@ -1,6 +1,7 @@ digraph { subgraph cluster_web { label="actix/actix-web" + "awc" "actix-web" "actix-files" @@ -16,7 +17,7 @@ digraph { "actix-web-actors" -> { "actix" "actix-web" "actix-http" "actix-codec" } "actix-multipart" -> { "actix-web" "actix-service" "actix-utils" } "actix-http" -> { "actix-service" "actix-codec" "actix-tls" "actix-utils" "actix-rt" "threadpool" } - "actix-http" -> { "actix" "actix-tls" }[color=blue] // optional + "actix-http" -> { "actix-tls" }[color=blue] // optional "actix-files" -> { "actix-web" } "actix-http-test" -> { "actix-service" "actix-codec" "actix-tls" "actix-utils" "actix-rt" "actix-server" "awc" } @@ -27,4 +28,8 @@ digraph { "actix-tls" -> { "actix-service" "actix-codec" "actix-utils" } "actix-server" -> { "actix-service" "actix-rt" "actix-codec" "actix-utils" } "actix-rt" -> { "macros" "threadpool" } + + // actix + + "actix" -> { "actix-rt" } } diff --git a/docs/graphs/web-only.dot b/docs/graphs/web-only.dot index 9e1bb2805..6f8292a3a 100644 --- a/docs/graphs/web-only.dot +++ b/docs/graphs/web-only.dot @@ -15,7 +15,6 @@ digraph { "awc" -> { "actix-http" } "actix-web-actors" -> { "actix" "actix-web" "actix-http" } "actix-multipart" -> { "actix-web" } - "actix-http" -> { "actix" }[color=blue] // optional "actix-files" -> { "actix-web" } "actix-http-test" -> { "awc" } } diff --git a/src/middleware/compat.rs b/src/middleware/compat.rs index 2df535280..7326c5824 100644 --- a/src/middleware/compat.rs +++ b/src/middleware/compat.rs @@ -15,7 +15,7 @@ use crate::{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). /// -/// # Usage +/// # Examples /// ```rust /// use actix_web::middleware::{Logger, Compat}; /// use actix_web::{App, web}; diff --git a/src/middleware/compress.rs b/src/middleware/compress.rs index 7a45e4c8d..698ba768e 100644 --- a/src/middleware/compress.rs +++ b/src/middleware/compress.rs @@ -30,7 +30,7 @@ use crate::{ /// Use `BodyEncoding` trait for overriding response compression. To disable compression set /// encoding to `ContentEncoding::Identity`. /// -/// # Usage +/// # Examples /// ```rust /// use actix_web::{web, middleware, App, HttpResponse}; /// diff --git a/src/middleware/condition.rs b/src/middleware/condition.rs index 9263292b9..be6f6764d 100644 --- a/src/middleware/condition.rs +++ b/src/middleware/condition.rs @@ -11,7 +11,7 @@ use futures_util::future::{Either, FutureExt, LocalBoxFuture}; /// control such middlewares like `Logger` or `Compress` directly. See the [`Compat`](super::Compat) /// middleware for a workaround. /// -/// # Usage +/// # Examples /// ```rust /// use actix_web::middleware::{Condition, NormalizePath}; /// use actix_web::App; diff --git a/src/middleware/default_headers.rs b/src/middleware/default_headers.rs index a9a50ec2c..d98403502 100644 --- a/src/middleware/default_headers.rs +++ b/src/middleware/default_headers.rs @@ -28,7 +28,7 @@ use crate::{ /// /// Headers with the same key that are already set in a response will *not* be overwritten. /// -/// # Usage +/// # Examples /// ```rust /// use actix_web::{web, http, middleware, App, HttpResponse}; /// diff --git a/src/middleware/err_handlers.rs b/src/middleware/err_handlers.rs index b55959460..07150f905 100644 --- a/src/middleware/err_handlers.rs +++ b/src/middleware/err_handlers.rs @@ -33,7 +33,7 @@ type ErrorHandler = dyn Fn(ServiceResponse) -> Result Date: Wed, 10 Feb 2021 12:12:03 +0000 Subject: [PATCH 021/339] consistent case s/web/Web --- README.md | 2 +- src/app.rs | 2 +- src/lib.rs | 4 ++-- src/scope.rs | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 684310961..ff3755062 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@

-

Actix web

+

Actix Web

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

diff --git a/src/app.rs b/src/app.rs index 1660c4b94..40362e0d3 100644 --- a/src/app.rs +++ b/src/app.rs @@ -211,7 +211,7 @@ where /// /// Http service is any type that implements `HttpServiceFactory` trait. /// - /// Actix web provides several services implementations: + /// Actix Web provides several services implementations: /// /// * *Resource* is an entry in resource table which corresponds to requested URL. /// * *Scope* is a set of resources with common root path. diff --git a/src/lib.rs b/src/lib.rs index 46fbc2937..7f096584a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -30,7 +30,7 @@ //! //! To get started navigating the API docs, you may consider looking at the following pages first: //! -//! * [App]: This struct represents an Actix web application and is used to +//! * [App]: This struct represents an Actix Web application and is used to //! configure routes and other common application settings. //! //! * [HttpServer]: This struct represents an HTTP server instance and is @@ -205,7 +205,7 @@ pub mod dev { } pub mod client { - //! Actix web async HTTP client. + //! Actix Web async HTTP client. //! //! ```rust //! use actix_web::client::Client; diff --git a/src/scope.rs b/src/scope.rs index d17acd843..d08c78133 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -206,11 +206,11 @@ where self } - /// Register http service. + /// Register HTTP service. /// /// This is similar to `App's` service registration. /// - /// Actix web provides several services implementations: + /// Actix Web provides several services implementations: /// /// * *Resource* is an entry in resource table which corresponds to requested URL. /// * *Scope* is a set of resources with common root path. From bd26083f333ecf63e3eb444748250364ce124f5e Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Wed, 10 Feb 2021 12:45:46 +0000 Subject: [PATCH 022/339] prepare codegen 0.5.0-beta.1 release --- CHANGES.md | 6 +++++- Cargo.toml | 4 ++-- actix-web-codegen/CHANGES.md | 4 ++++ actix-web-codegen/Cargo.toml | 4 ++-- actix-web-codegen/README.md | 14 ++++++++------ actix-web-codegen/src/lib.rs | 10 +++++----- 6 files changed, 26 insertions(+), 16 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 5eac57fe6..954410b30 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -3,7 +3,11 @@ ## Unreleased - 2021-xx-xx -## 4.0.0-beta.2 - 2021-xx-xx +## 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] diff --git a/Cargo.toml b/Cargo.toml index 217594a48..708075b90 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-web" -version = "4.0.0-beta.2" +version = "4.0.0-beta.3" authors = ["Nikolay Kim "] description = "Actix Web is a powerful, pragmatic, and extremely fast web framework for Rust" readme = "README.md" @@ -82,7 +82,7 @@ actix-service = "2.0.0-beta.4" actix-utils = "3.0.0-beta.2" actix-tls = { version = "3.0.0-beta.3", default-features = false, optional = true } -actix-web-codegen = "0.4.0" +actix-web-codegen = "0.5.0-beta.1" actix-http = "3.0.0-beta.2" awc = { version = "3.0.0-beta.2", default-features = false } diff --git a/actix-web-codegen/CHANGES.md b/actix-web-codegen/CHANGES.md index a7675d9dd..2ce728aad 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.1 - 2021-02-10 +* 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] diff --git a/actix-web-codegen/Cargo.toml b/actix-web-codegen/Cargo.toml index 377427fd1..a54c684a2 100644 --- a/actix-web-codegen/Cargo.toml +++ b/actix-web-codegen/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "actix-web-codegen" -version = "0.4.0" -description = "Actix web proc macros" +version = "0.5.0-beta.1" +description = "Routing and runtime macros for Actix Web" readme = "README.md" homepage = "https://actix.rs" repository = "https://github.com/actix/actix-web" diff --git a/actix-web-codegen/README.md b/actix-web-codegen/README.md index 887502075..5820bb443 100644 --- a/actix-web-codegen/README.md +++ b/actix-web-codegen/README.md @@ -1,22 +1,24 @@ # actix-web-codegen -> Helper and convenience macros for Actix Web +> Routing and runtime macros for Actix Web. -[![crates.io](https://meritbadge.herokuapp.com/actix-web-codegen)](https://crates.io/crates/actix-web-codegen) -[![Documentation](https://docs.rs/actix-web-codegen/badge.svg)](https://docs.rs/actix-web-codegen/0.4.0/actix_web_codegen/) +[![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.1)](https://docs.rs/actix-web-codegen/0.5.0-beta.1) [![Version](https://img.shields.io/badge/rustc-1.46+-ab6000.svg)](https://blog.rust-lang.org/2020/03/12/Rust-1.46.html) -[![Build Status](https://travis-ci.org/actix/actix-web.svg?branch=master)](https://travis-ci.org/actix/actix-web) -[![codecov](https://codecov.io/gh/actix/actix-web/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-web) +![License](https://img.shields.io/crates/l/actix-web-codegen.svg) +
+[![dependency status](https://deps.rs/crate/actix-web-codegen/0.5.0-beta.1/status.svg)](https://deps.rs/crate/actix-web-codegen/0.5.0-beta.1) +[![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) ## Documentation & Resources - [API Documentation](https://docs.rs/actix-web-codegen) - [Chat on Gitter](https://gitter.im/actix/actix-web) -- Cargo package: [actix-web-codegen](https://crates.io/crates/actix-web-codegen) - Minimum supported Rust version: 1.46 or later. ## Compile Testing + Uses the [`trybuild`] crate. All compile fail tests should include a stderr file generated by `trybuild`. See the [workflow section](https://github.com/dtolnay/trybuild#workflow) of the trybuild docs for info on how to do this. [`trybuild`]: https://github.com/dtolnay/trybuild diff --git a/actix-web-codegen/src/lib.rs b/actix-web-codegen/src/lib.rs index 1248fe757..670d82ce9 100644 --- a/actix-web-codegen/src/lib.rs +++ b/actix-web-codegen/src/lib.rs @@ -1,6 +1,6 @@ -//! Macros for reducing boilerplate code in Actix Web applications. +//! Routing and runtime macros for Actix Web. //! -//! ## Actix Web Re-exports +//! # Actix Web Re-exports //! Actix Web re-exports a version of this crate in it's entirety so you usually don't have to //! specify a dependency on this crate explicitly. Sometimes, however, updates are made to this //! crate before the actix-web dependency is updated. Therefore, code examples here will show @@ -10,7 +10,7 @@ //! # Runtime Setup //! Used for setting up the actix async runtime. See [macro@main] macro docs. //! -//! ```rust +//! ``` //! #[actix_web_codegen::main] // or `#[actix_web::main]` in Actix Web apps //! async fn main() { //! async { println!("Hello world"); }.await @@ -23,7 +23,7 @@ //! //! See docs for: [GET], [POST], [PATCH], [PUT], [DELETE], [HEAD], [CONNECT], [OPTIONS], [TRACE] //! -//! ```rust +//! ``` //! # use actix_web::HttpResponse; //! # use actix_web_codegen::get; //! #[get("/test")] @@ -36,7 +36,7 @@ //! Similar to the single method handler macro but takes one or more arguments for the HTTP methods //! it should respond to. See [macro@route] macro docs. //! -//! ```rust +//! ``` //! # use actix_web::HttpResponse; //! # use actix_web_codegen::route; //! #[route("/test", method="GET", method="HEAD")] From e18464b274b7d80b756b317f9cac5773dc33bf14 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Wed, 10 Feb 2021 12:57:13 +0000 Subject: [PATCH 023/339] bump actix web versions in deps --- actix-files/Cargo.toml | 4 ++-- actix-http-test/Cargo.toml | 2 +- actix-multipart/Cargo.toml | 2 +- actix-web-actors/Cargo.toml | 2 +- actix-web-codegen/Cargo.toml | 2 +- awc/Cargo.toml | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/actix-files/Cargo.toml b/actix-files/Cargo.toml index f7e1a6c0d..45fa18a69 100644 --- a/actix-files/Cargo.toml +++ b/actix-files/Cargo.toml @@ -17,7 +17,7 @@ name = "actix_files" path = "src/lib.rs" [dependencies] -actix-web = { version = "4.0.0-beta.2", default-features = false } +actix-web = { version = "4.0.0-beta.3", default-features = false } actix-service = "2.0.0-beta.4" askama_escape = "0.10" @@ -33,4 +33,4 @@ percent-encoding = "2.1" [dev-dependencies] actix-rt = "2" -actix-web = "4.0.0-beta.2" +actix-web = "4.0.0-beta.3" diff --git a/actix-http-test/Cargo.toml b/actix-http-test/Cargo.toml index 3eef4e727..cb3b82eda 100644 --- a/actix-http-test/Cargo.toml +++ b/actix-http-test/Cargo.toml @@ -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 = "4.0.0-beta.2" +actix-web = "4.0.0-beta.3" actix-http = "3.0.0-beta.2" diff --git a/actix-multipart/Cargo.toml b/actix-multipart/Cargo.toml index 6a6b4ac14..fc59f30c7 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.2", default-features = false } +actix-web = { version = "4.0.0-beta.3", default-features = false } actix-utils = "3.0.0-beta.2" bytes = "1" diff --git a/actix-web-actors/Cargo.toml b/actix-web-actors/Cargo.toml index 5f689ddb7..c96e0465f 100644 --- a/actix-web-actors/Cargo.toml +++ b/actix-web-actors/Cargo.toml @@ -19,7 +19,7 @@ path = "src/lib.rs" actix = { version = "0.11.0-beta.2", default-features = false } actix-codec = "0.4.0-beta.1" actix-http = "3.0.0-beta.2" -actix-web = { version = "4.0.0-beta.2", default-features = false } +actix-web = { version = "4.0.0-beta.3", default-features = false } bytes = "1" bytestring = "1" diff --git a/actix-web-codegen/Cargo.toml b/actix-web-codegen/Cargo.toml index a54c684a2..886d9ac3e 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" -actix-web = "4.0.0-beta.2" +actix-web = "4.0.0-beta.3" futures-util = { version = "0.3.7", default-features = false } trybuild = "1" rustversion = "1" diff --git a/awc/Cargo.toml b/awc/Cargo.toml index e4178d45e..8cab8e3ba 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -61,7 +61,7 @@ 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.2", features = ["openssl"] } +actix-web = { version = "4.0.0-beta.3", features = ["openssl"] } actix-http = { version = "3.0.0-beta.2", features = ["openssl"] } actix-http-test = { version = "3.0.0-beta.2", features = ["openssl"] } actix-utils = "3.0.0-beta.1" From ea5ce3befb806aac28df3019320d0b4619d96198 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Wed, 10 Feb 2021 18:36:14 +0000 Subject: [PATCH 024/339] prepare actix-http 3.0.0-beta.3 release --- Cargo.toml | 2 +- actix-http-test/Cargo.toml | 2 +- actix-http/CHANGES.md | 6 +++++- actix-http/Cargo.toml | 4 ++-- actix-http/README.md | 4 ++-- actix-multipart/Cargo.toml | 2 +- actix-web-actors/Cargo.toml | 2 +- awc/Cargo.toml | 4 ++-- 8 files changed, 15 insertions(+), 11 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 708075b90..3cbd1f358 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -83,7 +83,7 @@ actix-utils = "3.0.0-beta.2" actix-tls = { version = "3.0.0-beta.3", default-features = false, optional = true } actix-web-codegen = "0.5.0-beta.1" -actix-http = "3.0.0-beta.2" +actix-http = "3.0.0-beta.3" awc = { version = "3.0.0-beta.2", default-features = false } ahash = "0.7" diff --git a/actix-http-test/Cargo.toml b/actix-http-test/Cargo.toml index cb3b82eda..6622e04e0 100644 --- a/actix-http-test/Cargo.toml +++ b/actix-http-test/Cargo.toml @@ -52,4 +52,4 @@ tls-openssl = { version = "0.10.9", package = "openssl", optional = true } [dev-dependencies] actix-web = "4.0.0-beta.3" -actix-http = "3.0.0-beta.2" +actix-http = "3.0.0-beta.3" diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 07a64701e..b781fe50e 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -3,7 +3,11 @@ ## Unreleased - 2021-xx-xx -## 3.0.0-beta.2 - 2021-02-19 +## 3.0.0-beta.3 - 2021-02-10 +* No notable changes. + + +## 3.0.0-beta.2 - 2021-02-10 ### Added * `IntoHeaderPair` trait that allows using typed and untyped headers in the same methods. [#1869] * `ResponseBuilder::insert_header` method which allows using typed headers. [#1869] diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 67ea0a12d..c789c3ae5 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-http" -version = "3.0.0-beta.2" +version = "3.0.0-beta.3" authors = ["Nikolay Kim "] description = "HTTP primitives for the Actix ecosystem" readme = "README.md" @@ -15,7 +15,7 @@ license = "MIT OR Apache-2.0" edition = "2018" [package.metadata.docs.rs] -features = ["openssl", "rustls", "compress", "secure-cookies", "actors"] +features = ["openssl", "rustls", "compress", "secure-cookies"] [lib] name = "actix_http" diff --git a/actix-http/README.md b/actix-http/README.md index 4ec5b97af..881fbc8c5 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.2)](https://docs.rs/actix-http/3.0.0-beta.2) +[![Documentation](https://docs.rs/actix-http/badge.svg?version=3.0.0-beta.3)](https://docs.rs/actix-http/3.0.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) ![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.2/status.svg)](https://deps.rs/crate/actix-http/3.0.0-beta.2) +[![dependency status](https://deps.rs/crate/actix-http/3.0.0-beta.3/status.svg)](https://deps.rs/crate/actix-http/3.0.0-beta.3) [![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/Cargo.toml b/actix-multipart/Cargo.toml index fc59f30c7..67b2698bd 100644 --- a/actix-multipart/Cargo.toml +++ b/actix-multipart/Cargo.toml @@ -29,4 +29,4 @@ twoway = "0.2" [dev-dependencies] actix-rt = "2" -actix-http = "3.0.0-beta.2" +actix-http = "3.0.0-beta.3" diff --git a/actix-web-actors/Cargo.toml b/actix-web-actors/Cargo.toml index c96e0465f..698ff9420 100644 --- a/actix-web-actors/Cargo.toml +++ b/actix-web-actors/Cargo.toml @@ -18,7 +18,7 @@ path = "src/lib.rs" [dependencies] actix = { version = "0.11.0-beta.2", default-features = false } actix-codec = "0.4.0-beta.1" -actix-http = "3.0.0-beta.2" +actix-http = "3.0.0-beta.3" actix-web = { version = "4.0.0-beta.3", default-features = false } bytes = "1" diff --git a/awc/Cargo.toml b/awc/Cargo.toml index 8cab8e3ba..a6da53664 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -42,7 +42,7 @@ trust-dns = ["actix-http/trust-dns"] [dependencies] actix-codec = "0.4.0-beta.1" actix-service = "2.0.0-beta.4" -actix-http = "3.0.0-beta.2" +actix-http = "3.0.0-beta.3" actix-rt = "2" base64 = "0.13" @@ -62,7 +62,7 @@ tls-rustls = { version = "0.19.0", package = "rustls", optional = true, features [dev-dependencies] actix-web = { version = "4.0.0-beta.3", features = ["openssl"] } -actix-http = { version = "3.0.0-beta.2", features = ["openssl"] } +actix-http = { version = "3.0.0-beta.3", features = ["openssl"] } actix-http-test = { version = "3.0.0-beta.2", features = ["openssl"] } actix-utils = "3.0.0-beta.1" actix-server = "2.0.0-beta.3" From d9d0d1d1a2e7b1380cb5ea05268f9c1cf6e2b13d Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Wed, 10 Feb 2021 15:11:12 -0800 Subject: [PATCH 025/339] reduce unsafe (#1972) --- actix-http/src/h1/dispatcher.rs | 7 +- actix-http/src/ws/frame.rs | 12 +- actix-http/src/ws/mask.rs | 189 ++++++++++++-------------------- 3 files changed, 77 insertions(+), 131 deletions(-) diff --git a/actix-http/src/h1/dispatcher.rs b/actix-http/src/h1/dispatcher.rs index 1d28a0a39..3882c2d52 100644 --- a/actix-http/src/h1/dispatcher.rs +++ b/actix-http/src/h1/dispatcher.rs @@ -309,11 +309,8 @@ where } } - // SAFETY: setting length to 0 is safe - // skips one length check vs truncate - unsafe { - write_buf.set_len(0); - } + // everything has written to io. clear buffer. + write_buf.clear(); // flush the io and check if get blocked. let blocked = io.poll_flush(cx)?.is_pending(); diff --git a/actix-http/src/ws/frame.rs b/actix-http/src/ws/frame.rs index 8c49524e6..78d487dd2 100644 --- a/actix-http/src/ws/frame.rs +++ b/actix-http/src/ws/frame.rs @@ -16,7 +16,8 @@ impl Parser { src: &[u8], server: bool, max_size: usize, - ) -> Result)>, ProtocolError> { + ) -> Result)>, ProtocolError> + { let chunk_len = src.len(); let mut idx = 2; @@ -77,9 +78,10 @@ impl Parser { return Ok(None); } - let mask = - u32::from_le_bytes(TryFrom::try_from(&src[idx..idx + 4]).unwrap()); + let mask = TryFrom::try_from(&src[idx..idx + 4]).unwrap(); + idx += 4; + Some(mask) } else { None @@ -187,8 +189,8 @@ impl Parser { }; if mask { - let mask = rand::random::(); - dst.put_u32_le(mask); + let mask = rand::random::<[u8; 4]>(); + dst.put_slice(mask.as_ref()); dst.put_slice(payload.as_ref()); let pos = dst.len() - payload_len; apply_mask(&mut dst[pos..], mask); diff --git a/actix-http/src/ws/mask.rs b/actix-http/src/ws/mask.rs index 79e015f79..276ca4a85 100644 --- a/actix-http/src/ws/mask.rs +++ b/actix-http/src/ws/mask.rs @@ -1,136 +1,57 @@ //! This is code from [Tungstenite project](https://github.com/snapview/tungstenite-rs) -#![allow(clippy::cast_ptr_alignment)] -use std::ptr::copy_nonoverlapping; -use std::slice; -/// Holds a slice guaranteed to be shorter than 8 bytes. -struct ShortSlice<'a> { - inner: &'a mut [u8], -} - -impl<'a> ShortSlice<'a> { - /// # Safety - /// Given slice must be shorter than 8 bytes. - unsafe fn new(slice: &'a mut [u8]) -> Self { - // Sanity check for debug builds - debug_assert!(slice.len() < 8); - ShortSlice { inner: slice } - } - - fn len(&self) -> usize { - self.inner.len() - } -} - -/// Faster version of `apply_mask()` which operates on 8-byte blocks. +/// Mask/unmask a frame. #[inline] -#[allow(clippy::cast_lossless)] -pub(crate) fn apply_mask(buf: &mut [u8], mask_u32: u32) { - // Extend the mask to 64 bits - let mut mask_u64 = ((mask_u32 as u64) << 32) | (mask_u32 as u64); - // Split the buffer into three segments - let (head, mid, tail) = align_buf(buf); +pub fn apply_mask(buf: &mut [u8], mask: [u8; 4]) { + apply_mask_fast32(buf, mask) +} - // Initial unaligned segment - let head_len = head.len(); - if head_len > 0 { - xor_short(head, mask_u64); +/// A safe unoptimized mask application. +#[inline] +fn apply_mask_fallback(buf: &mut [u8], mask: [u8; 4]) { + for (i, byte) in buf.iter_mut().enumerate() { + *byte ^= mask[i & 3]; + } +} + +/// Faster version of `apply_mask()` which operates on 4-byte blocks. +#[inline] +pub fn apply_mask_fast32(buf: &mut [u8], mask: [u8; 4]) { + let mask_u32 = u32::from_ne_bytes(mask); + + // SAFETY: + // + // buf is a valid slice borrowed mutably from bytes::BytesMut. + // + // 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 head = prefix.len() & 3; + let mask_u32 = if head > 0 { if cfg!(target_endian = "big") { - mask_u64 = mask_u64.rotate_left(8 * head_len as u32); + mask_u32.rotate_left(8 * head as u32) } else { - mask_u64 = mask_u64.rotate_right(8 * head_len as u32); - } - } - // Aligned segment - for v in mid { - *v ^= mask_u64; - } - // Final unaligned segment - if tail.len() > 0 { - xor_short(tail, mask_u64); - } -} - -// TODO: copy_nonoverlapping here compiles to call memcpy. While it is not so -// inefficient, it could be done better. The compiler does not understand that -// a `ShortSlice` must be smaller than a u64. -#[inline] -#[allow(clippy::needless_pass_by_value)] -fn xor_short(buf: ShortSlice<'_>, mask: u64) { - // SAFETY: we know that a `ShortSlice` fits in a u64 - unsafe { - let (ptr, len) = (buf.inner.as_mut_ptr(), buf.len()); - let mut b: u64 = 0; - #[allow(trivial_casts)] - copy_nonoverlapping(ptr, &mut b as *mut _ as *mut u8, len); - b ^= mask; - #[allow(trivial_casts)] - copy_nonoverlapping(&b as *const _ as *const u8, ptr, len); - } -} - -/// # Safety -/// Caller must ensure the buffer has the correct size and alignment. -#[inline] -unsafe fn cast_slice(buf: &mut [u8]) -> &mut [u64] { - // Assert correct size and alignment in debug builds - debug_assert!(buf.len().trailing_zeros() >= 3); - debug_assert!((buf.as_ptr() as usize).trailing_zeros() >= 3); - - slice::from_raw_parts_mut(buf.as_mut_ptr() as *mut u64, buf.len() >> 3) -} - -/// Splits a slice into three parts: -/// - an unaligned short head -/// - an aligned `u64` slice mid section -/// - an unaligned short tail -#[inline] -fn align_buf(buf: &mut [u8]) -> (ShortSlice<'_>, &mut [u64], ShortSlice<'_>) { - let start_ptr = buf.as_ptr() as usize; - let end_ptr = start_ptr + buf.len(); - - // Round *up* to next aligned boundary for start - let start_aligned = (start_ptr + 7) & !0x7; - // Round *down* to last aligned boundary for end - let end_aligned = end_ptr & !0x7; - - if end_aligned >= start_aligned { - // We have our three segments (head, mid, tail) - let (tmp, tail) = buf.split_at_mut(end_aligned - start_ptr); - let (head, mid) = tmp.split_at_mut(start_aligned - start_ptr); - - // SAFETY: we know the middle section is correctly aligned, and the outer - // sections are smaller than 8 bytes - unsafe { - ( - ShortSlice::new(head), - cast_slice(mid), - ShortSlice::new(tail), - ) + mask_u32.rotate_right(8 * head as u32) } } else { - // We didn't cross even one aligned boundary! - - // SAFETY: The outer sections are smaller than 8 bytes - unsafe { (ShortSlice::new(buf), &mut [], ShortSlice::new(&mut [])) } + mask_u32 + }; + for word in words.iter_mut() { + *word ^= mask_u32; } + apply_mask_fallback(&mut suffix, mask_u32.to_ne_bytes()); } #[cfg(test)] mod tests { - use super::apply_mask; - - /// A safe unoptimized mask application. - fn apply_mask_fallback(buf: &mut [u8], mask: &[u8; 4]) { - for (i, byte) in buf.iter_mut().enumerate() { - *byte ^= mask[i & 3]; - } - } + 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() { + fn test_apply_mask_legacy() { let mask = [0x6d, 0xb6, 0xb2, 0x80]; - let mask_u32 = u32::from_le_bytes(mask); let unmasked = vec![ 0xf3, 0x00, 0x01, 0x02, 0x03, 0x80, 0x81, 0x82, 0xff, 0xfe, 0x00, 0x17, @@ -140,10 +61,10 @@ mod tests { // Check masking with proper alignment. { let mut masked = unmasked.clone(); - apply_mask_fallback(&mut masked, &mask); + apply_mask_fallback(&mut masked, mask); let mut masked_fast = unmasked.clone(); - apply_mask(&mut masked_fast, mask_u32); + apply_mask(&mut masked_fast, mask); assert_eq!(masked, masked_fast); } @@ -151,12 +72,38 @@ mod tests { // Check masking without alignment. { let mut masked = unmasked.clone(); - apply_mask_fallback(&mut masked[1..], &mask); + apply_mask_fallback(&mut masked[1..], mask); let mut masked_fast = unmasked; - apply_mask(&mut masked_fast[1..], mask_u32); + apply_mask(&mut masked_fast[1..], mask); assert_eq!(masked, masked_fast); } } + + #[test] + 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, + ]; + + for data_len in 0..=unmasked.len() { + let unmasked = &unmasked[0..data_len]; + // Check masking with different alignment. + for off in 0..=3 { + if unmasked.len() < off { + continue; + } + let mut masked = unmasked.to_vec(); + apply_mask_fallback(&mut masked[off..], mask); + + let mut masked_fast = unmasked.to_vec(); + apply_mask_fast32(&mut masked_fast[off..], mask); + + assert_eq!(masked, masked_fast); + } + } + } } From 75a9a72e78b5dad1cf599c05c3ada50525b8284e Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Thu, 11 Feb 2021 06:54:42 -0800 Subject: [PATCH 026/339] clean up poll_response. add comments (#1978) --- actix-http/src/h1/dispatcher.rs | 166 +++++++++++++++----------------- 1 file changed, 78 insertions(+), 88 deletions(-) diff --git a/actix-http/src/h1/dispatcher.rs b/actix-http/src/h1/dispatcher.rs index 3882c2d52..0389d1f8e 100644 --- a/actix-http/src/h1/dispatcher.rs +++ b/actix-http/src/h1/dispatcher.rs @@ -139,27 +139,14 @@ where fn is_empty(&self) -> bool { matches!(self, State::None) } - - fn is_call(&self) -> bool { - matches!(self, State::ServiceCall(_)) - } } + enum PollResponse { Upgrade(Request), DoNothing, DrainWriteBuf, } -impl PartialEq for PollResponse { - fn eq(&self, other: &PollResponse) -> bool { - match self { - PollResponse::DrainWriteBuf => matches!(other, PollResponse::DrainWriteBuf), - PollResponse::DoNothing => matches!(other, PollResponse::DoNothing), - _ => false, - } - } -} - impl Dispatcher where T: AsyncRead + AsyncWrite + Unpin, @@ -323,9 +310,10 @@ where message: Response<()>, body: ResponseBody, ) -> Result<(), DispatchError> { + let size = body.size(); let mut this = self.project(); this.codec - .encode(Message::Item((message, body.size())), &mut this.write_buf) + .encode(Message::Item((message, size)), &mut this.write_buf) .map_err(|err| { if let Some(mut payload) = this.payload.take() { payload.set_error(PayloadError::Incomplete(None)); @@ -334,7 +322,7 @@ where })?; this.flags.set(Flags::KEEPALIVE, this.codec.keepalive()); - match body.size() { + match size { BodySize::None | BodySize::Empty => this.state.set(State::None), _ => this.state.set(State::SendPayload(body)), }; @@ -351,109 +339,111 @@ where mut self: Pin<&mut Self>, cx: &mut Context<'_>, ) -> Result { - loop { + 'res: loop { let mut this = self.as_mut().project(); - // state is not changed on Poll::Pending. - // other variant and conditions always trigger a state change(or an error). - let state_change = match this.state.project() { + match this.state.as_mut().project() { + // no future is in InnerDispatcher state. pop next message. StateProj::None => match this.messages.pop_front() { + // handle request message. Some(DispatcherMessage::Item(req)) => { - self.as_mut().handle_request(req, cx)?; - true + // Handle `EXPECT: 100-Continue` header + if req.head().expect() { + // set InnerDispatcher state and continue loop to poll it. + let task = this.flow.expect.call(req); + this.state.set(State::ExpectCall(task)); + } else { + // the same as expect call. + let task = this.flow.service.call(req); + this.state.set(State::ServiceCall(task)); + }; } + // handle error message. Some(DispatcherMessage::Error(res)) => { + // send_response would update InnerDispatcher state to SendPayload or + // None(If response body is empty). + // continue loop to poll it. self.as_mut() .send_response(res, ResponseBody::Other(Body::Empty))?; - true } + // return with upgrade request and poll it exclusively. Some(DispatcherMessage::Upgrade(req)) => { return Ok(PollResponse::Upgrade(req)); } - None => false, - }, - StateProj::ExpectCall(fut) => match fut.poll(cx) { - Poll::Ready(Ok(req)) => { - self.as_mut().send_continue(); - this = self.as_mut().project(); - let fut = this.flow.service.call(req); - this.state.set(State::ServiceCall(fut)); - continue; - } - Poll::Ready(Err(e)) => { - let res: Response = e.into().into(); - let (res, body) = res.replace_body(()); - self.as_mut().send_response(res, body.into_body())?; - true - } - Poll::Pending => false, + // all messages are dealt with. + None => return Ok(PollResponse::DoNothing), }, StateProj::ServiceCall(fut) => match fut.poll(cx) { + // service call resolved. send response. Poll::Ready(Ok(res)) => { let (res, body) = res.into().replace_body(()); self.as_mut().send_response(res, body)?; - continue; } + // send service call error as response Poll::Ready(Err(e)) => { let res: Response = e.into().into(); let (res, body) = res.replace_body(()); self.as_mut().send_response(res, body.into_body())?; - true } - Poll::Pending => false, + // service call pending and could be waiting for more chunk messages. + // (pipeline message limit and/or payload can_read limit) + Poll::Pending => { + // no new message is decoded and no new payload is feed. + // nothing to do except waiting for new incoming data from client. + if !self.as_mut().poll_request(cx)? { + return Ok(PollResponse::DoNothing); + } + // otherwise keep loop. + } }, StateProj::SendPayload(mut stream) => { - loop { - if 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)), - &mut this.write_buf, - )?; - continue; - } - Poll::Ready(None) => { - this.codec.encode( - Message::Chunk(None), - &mut this.write_buf, - )?; - this = self.as_mut().project(); - this.state.set(State::None); - } - Poll::Ready(Some(Err(_))) => { - return Err(DispatchError::Unknown) - } - Poll::Pending => return Ok(PollResponse::DoNothing), + // 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) { + Poll::Ready(Some(Ok(item))) => { + this.codec.encode( + Message::Chunk(Some(item)), + &mut this.write_buf, + )?; } - } else { - return Ok(PollResponse::DrainWriteBuf); + Poll::Ready(None) => { + this.codec + .encode(Message::Chunk(None), &mut this.write_buf)?; + // payload stream finished. + // set state to None and handle next message + this.state.set(State::None); + continue 'res; + } + Poll::Ready(Some(Err(e))) => { + return Err(DispatchError::Service(e)) + } + Poll::Pending => return Ok(PollResponse::DoNothing), } - break; } - continue; + // buffer is beyond max size. + // return and try to write the whole buffer to io stream. + return Ok(PollResponse::DrainWriteBuf); } - }; - - // state is changed and continue when the state is not Empty - if state_change { - if !self.state.is_empty() { - continue; - } - } else { - // if read-backpressure is enabled and we consumed some data. - // we may read more data and retry - if self.state.is_call() { - if self.as_mut().poll_request(cx)? { - continue; + StateProj::ExpectCall(fut) => match fut.poll(cx) { + // expect resolved. write continue to buffer and set InnerDispatcher state + // to service call. + Poll::Ready(Ok(req)) => { + this.write_buf + .extend_from_slice(b"HTTP/1.1 100 Continue\r\n\r\n"); + let fut = this.flow.service.call(req); + this.state.set(State::ServiceCall(fut)); } - } else if !self.messages.is_empty() { - continue; - } + // send expect error as response + Poll::Ready(Err(e)) => { + let res: Response = e.into().into(); + let (res, body) = res.replace_body(()); + self.as_mut().send_response(res, body.into_body())?; + } + // expect must be solved before progress can be made. + Poll::Pending => return Ok(PollResponse::DoNothing), + }, } - break; } - - Ok(PollResponse::DoNothing) } fn handle_request( From ceace26ed4cf1fd753178fbe7726e6f18d8070d1 Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Thu, 11 Feb 2021 14:19:14 -0800 Subject: [PATCH 027/339] remove unused flag POLLED (#1980) --- actix-http/src/h1/dispatcher.rs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/actix-http/src/h1/dispatcher.rs b/actix-http/src/h1/dispatcher.rs index 0389d1f8e..c4ccf72c3 100644 --- a/actix-http/src/h1/dispatcher.rs +++ b/actix-http/src/h1/dispatcher.rs @@ -37,15 +37,14 @@ bitflags! { pub struct Flags: u8 { const STARTED = 0b0000_0001; const KEEPALIVE = 0b0000_0010; - const POLLED = 0b0000_0100; - const SHUTDOWN = 0b0000_1000; - const READ_DISCONNECT = 0b0001_0000; - const WRITE_DISCONNECT = 0b0010_0000; - const UPGRADE = 0b0100_0000; + const SHUTDOWN = 0b0000_0100; + const READ_DISCONNECT = 0b0000_1000; + const WRITE_DISCONNECT = 0b0001_0000; + const UPGRADE = 0b0010_0000; } } -#[pin_project::pin_project] +#[pin_project] /// Dispatcher for HTTP/1.1 protocol pub struct Dispatcher where From 871ca5e4ae2bdc22d1ea02701c2992fa8d04aed7 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Thu, 11 Feb 2021 21:44:22 +0000 Subject: [PATCH 028/339] stop claiming actor support --- README.md | 1 - actix-http/src/h1/dispatcher.rs | 10 +++++++ src/lib.rs | 27 ----------------- src/test.rs | 53 --------------------------------- 4 files changed, 10 insertions(+), 81 deletions(-) diff --git a/README.md b/README.md index ff3755062..cc7c4cd52 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,6 @@ * SSL support using OpenSSL or Rustls * Middlewares ([Logger, Session, CORS, etc](https://actix.rs/docs/middleware/)) * Includes an async [HTTP client](https://actix.rs/actix-web/actix_web/client/index.html) -* Supports [Actix actor framework](https://github.com/actix/actix) * Runs on stable Rust 1.46+ ## Documentation diff --git a/actix-http/src/h1/dispatcher.rs b/actix-http/src/h1/dispatcher.rs index c4ccf72c3..94bc5c442 100644 --- a/actix-http/src/h1/dispatcher.rs +++ b/actix-http/src/h1/dispatcher.rs @@ -356,6 +356,7 @@ where this.state.set(State::ServiceCall(task)); }; } + // handle error message. Some(DispatcherMessage::Error(res)) => { // send_response would update InnerDispatcher state to SendPayload or @@ -364,10 +365,12 @@ where self.as_mut() .send_response(res, ResponseBody::Other(Body::Empty))?; } + // return with upgrade request and poll it exclusively. Some(DispatcherMessage::Upgrade(req)) => { return Ok(PollResponse::Upgrade(req)); } + // all messages are dealt with. None => return Ok(PollResponse::DoNothing), }, @@ -377,12 +380,14 @@ where let (res, body) = res.into().replace_body(()); self.as_mut().send_response(res, body)?; } + // send service call error as response Poll::Ready(Err(e)) => { let res: Response = e.into().into(); let (res, body) = res.replace_body(()); self.as_mut().send_response(res, body.into_body())?; } + // service call pending and could be waiting for more chunk messages. // (pipeline message limit and/or payload can_read limit) Poll::Pending => { @@ -394,6 +399,7 @@ where // otherwise keep loop. } }, + StateProj::SendPayload(mut stream) => { // keep populate writer buffer until buffer size limit hit, // get blocked or finished. @@ -405,6 +411,7 @@ where &mut this.write_buf, )?; } + Poll::Ready(None) => { this.codec .encode(Message::Chunk(None), &mut this.write_buf)?; @@ -413,9 +420,11 @@ where this.state.set(State::None); continue 'res; } + Poll::Ready(Some(Err(e))) => { return Err(DispatchError::Service(e)) } + Poll::Pending => return Ok(PollResponse::DoNothing), } } @@ -423,6 +432,7 @@ where // return and try to write the whole buffer to io 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. diff --git a/src/lib.rs b/src/lib.rs index 7f096584a..271efa0de 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -56,7 +56,6 @@ //! * SSL support using OpenSSL or Rustls //! * Middlewares ([Logger, Session, CORS, etc](https://actix.rs/docs/middleware/)) //! * Includes an async [HTTP client](https://actix.rs/actix-web/actix_web/client/index.html) -//! * Supports [Actix actor framework](https://github.com/actix/actix) //! * Runs on stable Rust 1.46+ //! //! ## Crate Features @@ -203,29 +202,3 @@ pub mod dev { } } } - -pub mod client { - //! Actix Web async HTTP client. - //! - //! ```rust - //! use actix_web::client::Client; - //! - //! #[actix_web::main] - //! async fn main() { - //! let mut client = Client::default(); - //! - //! // Create request builder and send request - //! let response = client.get("http://www.rust-lang.org") - //! .insert_header(("User-Agent", "actix-web/3.0")) - //! .send() // <- Send request - //! .await; // <- Wait for response - //! - //! println!("Response: {:?}", response); - //! } - //! ``` - - pub use awc::error::*; - pub use awc::{ - test, Client, ClientBuilder, ClientRequest, ClientResponse, Connector, - }; -} diff --git a/src/test.rs b/src/test.rs index 5da100b81..03113bd4a 100644 --- a/src/test.rs +++ b/src/test.rs @@ -1246,57 +1246,4 @@ mod tests { let res = app.call(req).await.unwrap(); assert!(res.status().is_success()); } - - #[actix_rt::test] - async fn test_actor() { - use crate::Error; - use actix::prelude::*; - - struct MyActor; - - impl Actor for MyActor { - type Context = Context; - } - - struct Num(usize); - - impl Message for Num { - type Result = usize; - } - - impl Handler for MyActor { - type Result = usize; - - fn handle(&mut self, msg: Num, _: &mut Self::Context) -> Self::Result { - msg.0 - } - } - - let addr = MyActor.start(); - - async fn actor_handler( - addr: Data>, - ) -> Result { - let res = addr - .send(Num(1)) - .await - .map_err(crate::error::ErrorInternalServerError)?; - - if res == 1 { - Ok(HttpResponse::Ok()) - } else { - Ok(HttpResponse::BadRequest()) - } - } - - let srv = App::new() - .data(addr.clone()) - .service(web::resource("/").to(actor_handler)); - - let app = init_service(srv).await; - - let req = TestRequest::post().uri("/").to_request(); - let res = app.call(req).await.unwrap(); - assert!(res.status().is_success()); - } } From 77efc09362a2eb7517bd47c29dea4a92a3de3d97 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Thu, 11 Feb 2021 22:39:54 +0000 Subject: [PATCH 029/339] hide httpmessage mod --- actix-http-test/src/lib.rs | 20 ++++++++--------- actix-http/src/body.rs | 18 +++++++++------ actix-http/src/client/connector.rs | 8 ++++--- actix-http/src/client/error.rs | 22 ++++++++++++++----- actix-http/src/client/mod.rs | 3 ++- actix-http/src/clinu/mod.rs | 0 actix-http/src/config.rs | 10 ++++----- actix-http/src/encoding/decoder.rs | 6 ++--- actix-http/src/encoding/encoder.rs | 7 +++--- actix-http/src/encoding/mod.rs | 3 ++- actix-http/src/error.rs | 4 ++-- actix-http/src/h1/dispatcher.rs | 2 +- actix-http/src/h1/mod.rs | 2 +- actix-http/src/h2/mod.rs | 2 +- actix-http/src/h2/service.rs | 2 +- actix-http/src/header/as_name.rs | 2 ++ .../src/{httpcodes.rs => http_codes.rs} | 0 actix-http/src/lib.rs | 4 ++-- actix-http/src/request.rs | 10 +++++---- actix-http/src/response.rs | 6 ++--- actix-http/src/service.rs | 2 +- actix-http/tests/test_openssl.rs | 2 +- actix-http/tests/test_server.rs | 2 +- actix-web-actors/src/context.rs | 2 +- awc/src/builder.rs | 5 +++-- awc/src/lib.rs | 2 +- awc/src/request.rs | 6 ++--- awc/src/response.rs | 4 ++-- src/app.rs | 4 ++-- src/data.rs | 2 +- src/guard.rs | 22 +++++++++---------- src/request.rs | 10 +++++---- src/server.rs | 11 +++++----- src/service.rs | 8 ++++--- src/test.rs | 10 ++++----- 35 files changed, 125 insertions(+), 98 deletions(-) delete mode 100644 actix-http/src/clinu/mod.rs rename actix-http/src/{httpcodes.rs => http_codes.rs} (100%) diff --git a/actix-http-test/src/lib.rs b/actix-http-test/src/lib.rs index fcf8b3476..f5c97262a 100644 --- a/actix-http-test/src/lib.rs +++ b/actix-http-test/src/lib.rs @@ -150,7 +150,7 @@ impl TestServer { } } - /// Construct test https server url + /// Construct test HTTPS server URL. pub fn surl(&self, uri: &str) -> String { if uri.starts_with('/') { format!("https://localhost:{}{}", self.addr.port(), uri) @@ -164,7 +164,7 @@ impl TestServer { self.client.get(self.url(path.as_ref()).as_str()) } - /// Create https `GET` request + /// Create HTTPS `GET` request pub fn sget>(&self, path: S) -> ClientRequest { self.client.get(self.surl(path.as_ref()).as_str()) } @@ -174,7 +174,7 @@ impl TestServer { self.client.post(self.url(path.as_ref()).as_str()) } - /// Create https `POST` request + /// Create HTTPS `POST` request pub fn spost>(&self, path: S) -> ClientRequest { self.client.post(self.surl(path.as_ref()).as_str()) } @@ -184,7 +184,7 @@ impl TestServer { self.client.head(self.url(path.as_ref()).as_str()) } - /// Create https `HEAD` request + /// Create HTTPS `HEAD` request pub fn shead>(&self, path: S) -> ClientRequest { self.client.head(self.surl(path.as_ref()).as_str()) } @@ -194,7 +194,7 @@ impl TestServer { self.client.put(self.url(path.as_ref()).as_str()) } - /// Create https `PUT` request + /// Create HTTPS `PUT` request pub fn sput>(&self, path: S) -> ClientRequest { self.client.put(self.surl(path.as_ref()).as_str()) } @@ -204,7 +204,7 @@ impl TestServer { self.client.patch(self.url(path.as_ref()).as_str()) } - /// Create https `PATCH` request + /// Create HTTPS `PATCH` request pub fn spatch>(&self, path: S) -> ClientRequest { self.client.patch(self.surl(path.as_ref()).as_str()) } @@ -214,7 +214,7 @@ impl TestServer { self.client.delete(self.url(path.as_ref()).as_str()) } - /// Create https `DELETE` request + /// Create HTTPS `DELETE` request pub fn sdelete>(&self, path: S) -> ClientRequest { self.client.delete(self.surl(path.as_ref()).as_str()) } @@ -224,12 +224,12 @@ impl TestServer { self.client.options(self.url(path.as_ref()).as_str()) } - /// Create https `OPTIONS` request + /// Create HTTPS `OPTIONS` request pub fn soptions>(&self, path: S) -> ClientRequest { self.client.options(self.surl(path.as_ref()).as_str()) } - /// Connect to test http server + /// Connect to test HTTP server pub fn request>(&self, method: Method, path: S) -> ClientRequest { self.client.request(method, path.as_ref()) } @@ -263,7 +263,7 @@ impl TestServer { self.ws_at("/").await } - /// Stop http server + /// Stop HTTP server fn stop(&mut self) { self.system.stop(); } diff --git a/actix-http/src/body.rs b/actix-http/src/body.rs index d5d1989d4..0dbe93a4a 100644 --- a/actix-http/src/body.rs +++ b/actix-http/src/body.rs @@ -1,6 +1,10 @@ -use std::pin::Pin; -use std::task::{Context, Poll}; -use std::{fmt, mem}; +//! Traits and structures to aid consuming and writing HTTP payloads. + +use std::{ + fmt, mem, + pin::Pin, + task::{Context, Poll}, +}; use bytes::{Bytes, BytesMut}; use futures_core::{ready, Stream}; @@ -8,8 +12,8 @@ use pin_project::pin_project; use crate::error::Error; +/// Body size hint. #[derive(Debug, PartialEq, Copy, Clone)] -/// Body size hint pub enum BodySize { None, Empty, @@ -23,7 +27,7 @@ impl BodySize { } } -/// Type that provides this trait can be streamed to a peer. +/// Type that implement this trait can be streamed to a peer. pub trait MessageBody { fn size(&self) -> BodySize; @@ -80,7 +84,7 @@ impl ResponseBody { impl ResponseBody { pub fn take_body(&mut self) -> ResponseBody { - std::mem::replace(self, ResponseBody::Other(Body::None)) + mem::replace(self, ResponseBody::Other(Body::None)) } } @@ -127,7 +131,7 @@ impl Stream for ResponseBody { } } -/// Represents various types of http message body. +/// Represents various types of HTTP message body. pub enum Body { /// Empty response. `Content-Length` header is not set. None, diff --git a/actix-http/src/client/connector.rs b/actix-http/src/client/connector.rs index 7c8e2b2a0..3bf424d49 100644 --- a/actix-http/src/client/connector.rs +++ b/actix-http/src/client/connector.rs @@ -34,7 +34,8 @@ enum SslConnector { #[cfg(not(any(feature = "openssl", feature = "rustls")))] type SslConnector = (); -/// Manages http client network connectivity +/// Manages HTTP client network connectivity. +/// /// The `Connector` type uses a builder-like combinator pattern for service /// construction that finishes by calling the `.finish()` method. /// @@ -160,8 +161,9 @@ where self } - /// Maximum supported http major version - /// Supported versions http/1.1, http/2 + /// 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 { let versions = match val { http::Version::HTTP_11 => vec![b"http/1.1".to_vec()], diff --git a/actix-http/src/client/error.rs b/actix-http/src/client/error.rs index 8d609f546..7768462b8 100644 --- a/actix-http/src/client/error.rs +++ b/actix-http/src/client/error.rs @@ -65,13 +65,16 @@ impl From for ConnectError { #[derive(Debug, Display, From)] pub enum InvalidUrl { - #[display(fmt = "Missing url scheme")] + #[display(fmt = "Missing URL scheme")] MissingScheme, - #[display(fmt = "Unknown url scheme")] + + #[display(fmt = "Unknown URL scheme")] UnknownScheme, + #[display(fmt = "Missing host name")] MissingHost, - #[display(fmt = "Url parse error: {}", _0)] + + #[display(fmt = "URL parse error: {}", _0)] HttpError(http::Error), } @@ -83,25 +86,33 @@ pub enum SendRequestError { /// Invalid URL #[display(fmt = "Invalid URL: {}", _0)] Url(InvalidUrl), + /// Failed to connect to host #[display(fmt = "Failed to connect to host: {}", _0)] Connect(ConnectError), + /// Error sending request Send(io::Error), + /// Error parsing response Response(ParseError), + /// Http error #[display(fmt = "{}", _0)] Http(HttpError), + /// Http2 error #[display(fmt = "{}", _0)] H2(h2::Error), + /// Response took too long #[display(fmt = "Timeout while waiting for response")] Timeout, - /// Tunnels are not supported for http2 connection + + /// Tunnels are not supported for HTTP/2 connection #[display(fmt = "Tunnels are not supported for http2 connection")] TunnelNotSupported, + /// Error sending request body Body(Error), } @@ -127,7 +138,8 @@ pub enum FreezeRequestError { /// Invalid URL #[display(fmt = "Invalid URL: {}", _0)] Url(InvalidUrl), - /// Http error + + /// HTTP error #[display(fmt = "{}", _0)] Http(HttpError), } diff --git a/actix-http/src/client/mod.rs b/actix-http/src/client/mod.rs index dd1e9b25a..9c7f632ea 100644 --- a/actix-http/src/client/mod.rs +++ b/actix-http/src/client/mod.rs @@ -1,4 +1,5 @@ -//! Http client api +//! HTTP client. + use http::Uri; mod config; diff --git a/actix-http/src/clinu/mod.rs b/actix-http/src/clinu/mod.rs deleted file mode 100644 index e69de29bb..000000000 diff --git a/actix-http/src/config.rs b/actix-http/src/config.rs index f178db274..61708cfc3 100644 --- a/actix-http/src/config.rs +++ b/actix-http/src/config.rs @@ -95,32 +95,32 @@ impl ServiceConfig { })) } + /// Returns true if connection is secure (HTTPS) #[inline] - /// Returns true if connection is secure(https) pub fn secure(&self) -> bool { self.0.secure } - #[inline] /// Returns the local address that this server is bound to. + #[inline] pub fn local_addr(&self) -> Option { self.0.local_addr } - #[inline] /// Keep alive duration if configured. + #[inline] pub fn keep_alive(&self) -> Option { self.0.keep_alive } - #[inline] /// Return state of connection keep-alive functionality + #[inline] pub fn keep_alive_enabled(&self) -> bool { self.0.ka_enabled } - #[inline] /// Client timeout for first request. + #[inline] pub fn client_timer(&self) -> Option { let delay_time = self.0.client_timeout; if delay_time != 0 { diff --git a/actix-http/src/encoding/decoder.rs b/actix-http/src/encoding/decoder.rs index 2cf2f6e03..e16d42288 100644 --- a/actix-http/src/encoding/decoder.rs +++ b/actix-http/src/encoding/decoder.rs @@ -1,7 +1,7 @@ -use std::future::Future; -use std::io::{self, Write}; -use std::pin::Pin; +//! Stream decoders. + use std::task::{Context, Poll}; +use std::{future::Future, io, io::Write as _, pin::Pin}; use actix_rt::task::{spawn_blocking, JoinHandle}; use brotli2::write::BrotliDecoder; diff --git a/actix-http/src/encoding/encoder.rs b/actix-http/src/encoding/encoder.rs index 1d4a8e933..3a413a150 100644 --- a/actix-http/src/encoding/encoder.rs +++ b/actix-http/src/encoding/encoder.rs @@ -1,8 +1,7 @@ -//! Stream encoder -use std::future::Future; -use std::io::{self, Write}; -use std::pin::Pin; +//! Stream encoders. + use std::task::{Context, Poll}; +use std::{future::Future, io, io::Write as _, pin::Pin}; use actix_rt::task::{spawn_blocking, JoinHandle}; use brotli2::write::BrotliEncoder; diff --git a/actix-http/src/encoding/mod.rs b/actix-http/src/encoding/mod.rs index 9eaf4104e..cb271c638 100644 --- a/actix-http/src/encoding/mod.rs +++ b/actix-http/src/encoding/mod.rs @@ -1,4 +1,5 @@ -//! Content-Encoding support +//! Content-Encoding support. + use std::io; use bytes::{Bytes, BytesMut}; diff --git a/actix-http/src/error.rs b/actix-http/src/error.rs index cea1f5d4f..5eb3c157a 100644 --- a/actix-http/src/error.rs +++ b/actix-http/src/error.rs @@ -38,7 +38,7 @@ pub type Result = result::Result; /// 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 +/// 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 { @@ -404,7 +404,7 @@ impl ResponseError for crate::cookie::ParseError { } #[derive(Debug, Display, From)] -/// A set of errors that can occur during dispatching http requests +/// A set of errors that can occur during dispatching HTTP requests pub enum DispatchError { /// Service error Service(Error), diff --git a/actix-http/src/h1/dispatcher.rs b/actix-http/src/h1/dispatcher.rs index 94bc5c442..8d451fdad 100644 --- a/actix-http/src/h1/dispatcher.rs +++ b/actix-http/src/h1/dispatcher.rs @@ -178,7 +178,7 @@ where ) } - /// Create http/1 dispatcher with slow request timeout. + /// Create HTTP/1 dispatcher with slow request timeout. pub(crate) fn with_timeout( io: T, codec: Codec, diff --git a/actix-http/src/h1/mod.rs b/actix-http/src/h1/mod.rs index 3d5dea5d6..7e6df6ceb 100644 --- a/actix-http/src/h1/mod.rs +++ b/actix-http/src/h1/mod.rs @@ -1,4 +1,4 @@ -//! HTTP/1 implementation +//! HTTP/1 protocol implementation. use bytes::{Bytes, BytesMut}; mod client; diff --git a/actix-http/src/h2/mod.rs b/actix-http/src/h2/mod.rs index c05ee609d..7eff44ac1 100644 --- a/actix-http/src/h2/mod.rs +++ b/actix-http/src/h2/mod.rs @@ -1,4 +1,4 @@ -//! HTTP/2 implementation. +//! HTTP/2 protocol. use std::{ pin::Pin, diff --git a/actix-http/src/h2/service.rs b/actix-http/src/h2/service.rs index 36c76b17c..e00c8d968 100644 --- a/actix-http/src/h2/service.rs +++ b/actix-http/src/h2/service.rs @@ -243,7 +243,7 @@ where } } -/// `Service` implementation for http/2 transport +/// `Service` implementation for HTTP/2 transport pub struct H2ServiceHandler where S: Service, diff --git a/actix-http/src/header/as_name.rs b/actix-http/src/header/as_name.rs index 68456e3f7..af81ff7f2 100644 --- a/actix-http/src/header/as_name.rs +++ b/actix-http/src/header/as_name.rs @@ -1,4 +1,6 @@ //! Helper trait for types that can be effectively borrowed as a [HeaderValue]. +//! +//! [HeaderValue]: crate::http::HeaderValue use std::{borrow::Cow, str::FromStr}; diff --git a/actix-http/src/httpcodes.rs b/actix-http/src/http_codes.rs similarity index 100% rename from actix-http/src/httpcodes.rs rename to actix-http/src/http_codes.rs diff --git a/actix-http/src/lib.rs b/actix-http/src/lib.rs index cc99130eb..15866a3d8 100644 --- a/actix-http/src/lib.rs +++ b/actix-http/src/lib.rs @@ -25,8 +25,8 @@ pub mod encoding; mod extensions; mod header; mod helpers; -mod httpcodes; -pub mod httpmessage; +mod http_codes; +mod httpmessage; mod message; mod payload; mod request; diff --git a/actix-http/src/request.rs b/actix-http/src/request.rs index fbf4d3c23..1ac2f33d6 100644 --- a/actix-http/src/request.rs +++ b/actix-http/src/request.rs @@ -107,7 +107,7 @@ impl

Request

{ #[inline] #[doc(hidden)] - /// Mutable reference to a http message part of the request + /// Mutable reference to a HTTP message part of the request pub fn head_mut(&mut self) -> &mut RequestHead { &mut *self.head } @@ -158,10 +158,12 @@ impl

Request

{ self.head().method == Method::CONNECT } - /// Peer socket address + /// Peer socket address. /// - /// Peer address is actual socket address, if proxy is used in front of - /// actix http server, then peer address would be address of this proxy. + /// 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. + /// + /// Will only return None when called in unit tests. #[inline] pub fn peer_addr(&self) -> Option { self.head().peer_addr diff --git a/actix-http/src/response.rs b/actix-http/src/response.rs index 113ace221..16d4a03d7 100644 --- a/actix-http/src/response.rs +++ b/actix-http/src/response.rs @@ -32,13 +32,13 @@ pub struct Response { } impl Response { - /// Create http response builder with specific status. + /// Create HTTP response builder with specific status. #[inline] pub fn build(status: StatusCode) -> ResponseBuilder { ResponseBuilder::new(status) } - /// Create http response builder + /// Create HTTP response builder #[inline] pub fn build_from>(source: T) -> ResponseBuilder { source.into() @@ -97,7 +97,7 @@ impl Response { } #[inline] - /// Mutable reference to a http message part of the response + /// Mutable reference to a HTTP message part of the response pub fn head_mut(&mut self) -> &mut ResponseHead { &mut *self.head } diff --git a/actix-http/src/service.rs b/actix-http/src/service.rs index f0121db97..fee26dcc3 100644 --- a/actix-http/src/service.rs +++ b/actix-http/src/service.rs @@ -432,7 +432,7 @@ where } } -/// `Service` implementation for http transport +/// `Service` implementation for HTTP transport pub struct HttpServiceHandler where S: Service, diff --git a/actix-http/tests/test_openssl.rs b/actix-http/tests/test_openssl.rs index 73bbe6208..188516f83 100644 --- a/actix-http/tests/test_openssl.rs +++ b/actix-http/tests/test_openssl.rs @@ -7,7 +7,7 @@ use std::io; use actix_http::error::{ErrorBadRequest, PayloadError}; use actix_http::http::header::{self, HeaderName, HeaderValue}; use actix_http::http::{Method, StatusCode, Version}; -use actix_http::httpmessage::HttpMessage; +use actix_http::HttpMessage; use actix_http::{body, Error, HttpService, Request, Response}; use actix_http_test::test_server; use actix_service::{fn_service, ServiceFactoryExt}; diff --git a/actix-http/tests/test_server.rs b/actix-http/tests/test_server.rs index b4ef74406..910fa81f2 100644 --- a/actix-http/tests/test_server.rs +++ b/actix-http/tests/test_server.rs @@ -10,7 +10,7 @@ use futures_util::future::{self, err, ok, ready, FutureExt}; use futures_util::stream::{once, StreamExt}; use regex::Regex; -use actix_http::httpmessage::HttpMessage; +use actix_http::HttpMessage; use actix_http::{ body, error, http, http::header, Error, HttpService, KeepAlive, Request, Response, }; diff --git a/actix-web-actors/src/context.rs b/actix-web-actors/src/context.rs index afe17cd21..c452025af 100644 --- a/actix-web-actors/src/context.rs +++ b/actix-web-actors/src/context.rs @@ -15,7 +15,7 @@ use bytes::Bytes; use futures_core::Stream; use tokio::sync::oneshot::Sender; -/// Execution context for http actors +/// Execution context for HTTP actors pub struct HttpContext where A: Actor>, diff --git a/awc/src/builder.rs b/awc/src/builder.rs index 39eb24c39..94ffb8a71 100644 --- a/awc/src/builder.rs +++ b/awc/src/builder.rs @@ -82,8 +82,9 @@ impl ClientBuilder { self } - /// Maximum supported http major version - /// Supported versions http/1.1, http/2 + /// 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 { self.max_http_version = Some(val); self diff --git a/awc/src/lib.rs b/awc/src/lib.rs index a92125b5a..bd52f7ab1 100644 --- a/awc/src/lib.rs +++ b/awc/src/lib.rs @@ -134,7 +134,7 @@ use self::connect::{Connect, ConnectorWrapper}; /// /// let res = client.get("http://www.rust-lang.org") // <- Create request builder /// .insert_header(("User-Agent", "Actix-web")) -/// .send() // <- Send http request +/// .send() // <- Send HTTP request /// .await; // <- send request and wait for response /// /// println!("Response: {:?}", res); diff --git a/awc/src/request.rs b/awc/src/request.rs index c87df9b3b..3ec43f3e5 100644 --- a/awc/src/request.rs +++ b/awc/src/request.rs @@ -42,10 +42,10 @@ cfg_if::cfg_if! { /// let response = awc::Client::new() /// .get("http://www.rust-lang.org") // <- Create request builder /// .insert_header(("User-Agent", "Actix-web")) -/// .send() // <- Send http request +/// .send() // <- Send HTTP request /// .await; /// -/// response.and_then(|response| { // <- server http response +/// response.and_then(|response| { // <- server HTTP response /// println!("Response: {:?}", response); /// Ok(()) /// }); @@ -219,7 +219,7 @@ impl ClientRequest { } /// Force close connection instead of returning it back to connections pool. - /// This setting affect only http/1 connections. + /// This setting affect only HTTP/1 connections. #[inline] pub fn force_close(mut self) -> Self { self.head.set_connection_type(ConnectionType::Close); diff --git a/awc/src/response.rs b/awc/src/response.rs index c3e7d71ce..f5161f9c1 100644 --- a/awc/src/response.rs +++ b/awc/src/response.rs @@ -109,7 +109,7 @@ impl ClientResponse where S: Stream>, { - /// Loads http response's body. + /// Loads HTTP response's body. pub fn body(&mut self) -> MessageBody { MessageBody::new(self) } @@ -151,7 +151,7 @@ impl fmt::Debug for ClientResponse { } } -/// Future that resolves to a complete http message body. +/// Future that resolves to a complete HTTP message body. pub struct MessageBody { length: Option, err: Option, diff --git a/src/app.rs b/src/app.rs index 40362e0d3..123076fc8 100644 --- a/src/app.rs +++ b/src/app.rs @@ -72,7 +72,7 @@ where /// Set application data. Application data could be accessed /// by using `Data` extractor where `T` is data type. /// - /// **Note**: http server accepts an application factory rather than + /// **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 @@ -207,7 +207,7 @@ where ) } - /// Register http service. + /// Register HTTP service. /// /// Http service is any type that implements `HttpServiceFactory` trait. /// diff --git a/src/data.rs b/src/data.rs index 12a1f5cf8..b3f3d643c 100644 --- a/src/data.rs +++ b/src/data.rs @@ -27,7 +27,7 @@ pub(crate) type FnDataFactory = /// /// Application data can be accessed by using `Data` extractor where `T` is data type. /// -/// **Note**: http server accepts an application factory rather than an application instance. HTTP +/// **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` diff --git a/src/guard.rs b/src/guard.rs index ba0cbea85..5d0de58c2 100644 --- a/src/guard.rs +++ b/src/guard.rs @@ -176,7 +176,7 @@ impl Guard for NotGuard { } } -/// Http method guard +/// HTTP method guard. #[doc(hidden)] pub struct MethodGuard(http::Method); @@ -186,52 +186,52 @@ impl Guard for MethodGuard { } } -/// Guard to match *GET* http method +/// Guard to match *GET* HTTP method. pub fn Get() -> MethodGuard { MethodGuard(http::Method::GET) } -/// Predicate to match *POST* http method +/// Predicate to match *POST* HTTP method. pub fn Post() -> MethodGuard { MethodGuard(http::Method::POST) } -/// Predicate to match *PUT* http method +/// Predicate to match *PUT* HTTP method. pub fn Put() -> MethodGuard { MethodGuard(http::Method::PUT) } -/// Predicate to match *DELETE* http method +/// Predicate to match *DELETE* HTTP method. pub fn Delete() -> MethodGuard { MethodGuard(http::Method::DELETE) } -/// Predicate to match *HEAD* http method +/// Predicate to match *HEAD* HTTP method. pub fn Head() -> MethodGuard { MethodGuard(http::Method::HEAD) } -/// Predicate to match *OPTIONS* http method +/// Predicate to match *OPTIONS* HTTP method. pub fn Options() -> MethodGuard { MethodGuard(http::Method::OPTIONS) } -/// Predicate to match *CONNECT* http method +/// Predicate to match *CONNECT* HTTP method. pub fn Connect() -> MethodGuard { MethodGuard(http::Method::CONNECT) } -/// Predicate to match *PATCH* http method +/// Predicate to match *PATCH* HTTP method. pub fn Patch() -> MethodGuard { MethodGuard(http::Method::PATCH) } -/// Predicate to match *TRACE* http method +/// Predicate to match *TRACE* HTTP method. pub fn Trace() -> MethodGuard { MethodGuard(http::Method::TRACE) } -/// Predicate to match specified http method +/// Predicate to match specified HTTP method. pub fn Method(method: http::Method) -> MethodGuard { MethodGuard(method) } diff --git a/src/request.rs b/src/request.rs index 9a5e43185..3f4eb1038 100644 --- a/src/request.rs +++ b/src/request.rs @@ -62,7 +62,7 @@ impl HttpRequest { } /// This method returns mutable reference to the request head. - /// panics if multiple references of http request exists. + /// panics if multiple references of HTTP request exists. #[inline] pub(crate) fn head_mut(&mut self) -> &mut RequestHead { &mut Rc::get_mut(&mut self.inner).unwrap().head @@ -202,12 +202,14 @@ impl HttpRequest { &self.app_state().rmap() } - /// Peer socket address + /// Peer socket address. /// - /// Peer address is actual socket address, if proxy is used in front of - /// actix http server, then peer address would be address of this proxy. + /// 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. + /// + /// Will only return None when called in unit tests. #[inline] pub fn peer_addr(&self) -> Option { self.head().peer_addr diff --git a/src/server.rs b/src/server.rs index 59d589439..69318e08d 100644 --- a/src/server.rs +++ b/src/server.rs @@ -40,7 +40,7 @@ struct Config { /// An HTTP Server. /// -/// Create new http server with application factory. +/// Create new HTTP server with application factory. /// /// ```rust,no_run /// use actix_web::{web, App, HttpResponse, HttpServer}; @@ -86,7 +86,7 @@ where S::Service: 'static, B: MessageBody + 'static, { - /// Create new http server with application factory + /// Create new HTTP server with application factory pub fn new(factory: F) -> Self { HttpServer { factory, @@ -131,8 +131,7 @@ where /// Set number of workers to start. /// - /// By default http server uses number of available logical cpu as threads - /// count. + /// By default, server uses number of available logical CPU as thread count. pub fn workers(mut self, num: usize) -> Self { self.builder = self.builder.workers(num); self @@ -257,7 +256,7 @@ where /// Get 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 + /// 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)> { @@ -610,7 +609,7 @@ where { /// Start listening for incoming connections. /// - /// This method starts number of http workers in separate threads. + /// This method starts number of HTTP workers in separate threads. /// For each address this method starts separate thread which does /// `accept()` in a loop. /// diff --git a/src/service.rs b/src/service.rs index db0ec602a..a7fb12848 100644 --- a/src/service.rs +++ b/src/service.rs @@ -164,12 +164,14 @@ impl ServiceRequest { } } - /// Peer socket address + /// Peer socket address. /// - /// Peer address is actual socket address, if proxy is used in front of - /// actix http server, then peer address would be address of this proxy. + /// 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 `ConnectionInfo` should be used. + /// + /// Will only return None when called in unit tests. #[inline] pub fn peer_addr(&self) -> Option { self.head().peer_addr diff --git a/src/test.rs b/src/test.rs index 03113bd4a..c03f078ef 100644 --- a/src/test.rs +++ b/src/test.rs @@ -851,13 +851,13 @@ impl TestServerConfig { } } - /// Start http/1.1 server only + /// Start HTTP/1.1 server only pub fn h1(mut self) -> Self { self.tp = HttpVer::Http1; self } - /// Start http/2 server only + /// Start HTTP/2 server only pub fn h2(mut self) -> Self { self.tp = HttpVer::Http2; self @@ -956,7 +956,7 @@ impl TestServer { self.client.options(self.url(path.as_ref()).as_str()) } - /// Connect to test http server + /// Connect to test HTTP server pub fn request>(&self, method: Method, path: S) -> ClientRequest { self.client.request(method, path.as_ref()) } @@ -990,7 +990,7 @@ impl TestServer { self.ws_at("/").await } - /// Gracefully stop http server + /// Gracefully stop HTTP server pub async fn stop(self) { self.server.stop(true).await; self.system.stop(); @@ -1006,7 +1006,7 @@ impl Drop for TestServer { #[cfg(test)] mod tests { - use actix_http::httpmessage::HttpMessage; + use actix_http::HttpMessage; use serde::{Deserialize, Serialize}; use std::time::SystemTime; From c1af5089b92ade8df4b105e5e34ec18576a7417a Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Thu, 11 Feb 2021 22:58:35 +0000 Subject: [PATCH 030/339] add 431 and 451 status codes --- actix-http/src/h1/codec.rs | 2 +- actix-http/src/h1/decoder.rs | 2 +- actix-http/src/h1/dispatcher.rs | 36 +++++++++---------- actix-http/src/header/common/if_range.rs | 2 +- actix-http/src/header/mod.rs | 2 +- actix-http/src/http_codes.rs | 8 +++++ .../src/{httpmessage.rs => http_message.rs} | 0 actix-http/src/lib.rs | 6 ++-- actix-http/src/request.rs | 2 +- actix-http/src/response.rs | 3 +- 10 files changed, 35 insertions(+), 28 deletions(-) rename actix-http/src/{httpmessage.rs => http_message.rs} (100%) diff --git a/actix-http/src/h1/codec.rs b/actix-http/src/h1/codec.rs index d5035df26..4aeb9f120 100644 --- a/actix-http/src/h1/codec.rs +++ b/actix-http/src/h1/codec.rs @@ -199,7 +199,7 @@ mod tests { use http::Method; use super::*; - use crate::httpmessage::HttpMessage; + use crate::HttpMessage; #[test] 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 122a815d5..074616929 100644 --- a/actix-http/src/h1/decoder.rs +++ b/actix-http/src/h1/decoder.rs @@ -652,7 +652,7 @@ mod tests { use super::*; use crate::error::ParseError; use crate::http::header::{HeaderName, SET_COOKIE}; - use crate::httpmessage::HttpMessage; + use crate::HttpMessage; impl PayloadType { fn unwrap(self) -> PayloadDecoder { diff --git a/actix-http/src/h1/dispatcher.rs b/actix-http/src/h1/dispatcher.rs index 8d451fdad..7ee2ee258 100644 --- a/actix-http/src/h1/dispatcher.rs +++ b/actix-http/src/h1/dispatcher.rs @@ -382,8 +382,8 @@ where } // send service call error as response - Poll::Ready(Err(e)) => { - let res: Response = e.into().into(); + Poll::Ready(Err(err)) => { + let res: Response = err.into().into(); let (res, body) = res.replace_body(()); self.as_mut().send_response(res, body.into_body())?; } @@ -421,8 +421,8 @@ where continue 'res; } - Poll::Ready(Some(Err(e))) => { - return Err(DispatchError::Service(e)) + Poll::Ready(Some(Err(err))) => { + return Err(DispatchError::Service(err)) } Poll::Pending => return Ok(PollResponse::DoNothing), @@ -443,8 +443,8 @@ where this.state.set(State::ServiceCall(fut)); } // send expect error as response - Poll::Ready(Err(e)) => { - let res: Response = e.into().into(); + Poll::Ready(Err(err)) => { + let res: Response = err.into().into(); let (res, body) = res.replace_body(()); self.as_mut().send_response(res, body.into_body())?; } @@ -492,9 +492,9 @@ where // future is error. send response and return a result. On success // to notify the dispatcher a new state is set and the outer loop // should be continue. - Poll::Ready(Err(e)) => { - let e = e.into(); - let res: Response = e.into(); + Poll::Ready(Err(err)) => { + let err = err.into(); + let res: Response = err.into(); let (res, body) = res.replace_body(()); return self.send_response(res, body.into_body()); } @@ -512,9 +512,9 @@ where } // see the comment on ExpectCall state branch's Pending. Poll::Pending => Ok(()), - // see the comment on ExpectCall state branch's Ready(Err(e)). - Poll::Ready(Err(e)) => { - let res: Response = e.into().into(); + // see the comment on ExpectCall state branch's Ready(Err(err)). + Poll::Ready(Err(err)) => { + let res: Response = err.into().into(); let (res, body) = res.replace_body(()); self.send_response(res, body.into_body()) } @@ -606,25 +606,25 @@ where // decode is partial and buffer is not full yet. // break and wait for more read. Ok(None) => break, - Err(ParseError::Io(e)) => { + Err(ParseError::Io(err)) => { self.as_mut().client_disconnected(); this = self.as_mut().project(); - *this.error = Some(DispatchError::Io(e)); + *this.error = Some(DispatchError::Io(err)); break; } Err(ParseError::TooLarge) => { if let Some(mut payload) = this.payload.take() { payload.set_error(PayloadError::Overflow); } - // Requests overflow buffer size should be responded with 413 + // Requests overflow buffer size should be responded with 431 this.messages.push_back(DispatcherMessage::Error( - Response::PayloadTooLarge().finish().drop_body(), + Response::RequestHeaderFieldsTooLarge().finish().drop_body(), )); this.flags.insert(Flags::READ_DISCONNECT); *this.error = Some(ParseError::TooLarge.into()); break; } - Err(e) => { + Err(err) => { if let Some(mut payload) = this.payload.take() { payload.set_error(PayloadError::EncodingCorrupted); } @@ -634,7 +634,7 @@ where Response::BadRequest().finish().drop_body(), )); this.flags.insert(Flags::READ_DISCONNECT); - *this.error = Some(e.into()); + *this.error = Some(err.into()); break; } } diff --git a/actix-http/src/header/common/if_range.rs b/actix-http/src/header/common/if_range.rs index 1513b7a44..0a5749505 100644 --- a/actix-http/src/header/common/if_range.rs +++ b/actix-http/src/header/common/if_range.rs @@ -5,7 +5,7 @@ use crate::header::{ self, from_one_raw_str, EntityTag, Header, HeaderName, HeaderValue, HttpDate, IntoHeaderValue, InvalidHeaderValue, Writer, }; -use crate::httpmessage::HttpMessage; +use crate::HttpMessage; /// `If-Range` header, defined in [RFC7233](http://tools.ietf.org/html/rfc7233#section-3.2) /// diff --git a/actix-http/src/header/mod.rs b/actix-http/src/header/mod.rs index 9543d43b6..1100a959d 100644 --- a/actix-http/src/header/mod.rs +++ b/actix-http/src/header/mod.rs @@ -9,7 +9,7 @@ use percent_encoding::{AsciiSet, CONTROLS}; pub use http::header::*; use crate::error::ParseError; -use crate::httpmessage::HttpMessage; +use crate::HttpMessage; mod as_name; mod into_pair; diff --git a/actix-http/src/http_codes.rs b/actix-http/src/http_codes.rs index f421d8e23..688a08be5 100644 --- a/actix-http/src/http_codes.rs +++ b/actix-http/src/http_codes.rs @@ -67,6 +67,14 @@ impl Response { static_resp!(ExpectationFailed, StatusCode::EXPECTATION_FAILED); static_resp!(UnprocessableEntity, StatusCode::UNPROCESSABLE_ENTITY); static_resp!(TooManyRequests, StatusCode::TOO_MANY_REQUESTS); + static_resp!( + RequestHeaderFieldsTooLarge, + StatusCode::REQUEST_HEADER_FIELDS_TOO_LARGE + ); + static_resp!( + UnavailableForLegalReasons, + StatusCode::UNAVAILABLE_FOR_LEGAL_REASONS + ); static_resp!(InternalServerError, StatusCode::INTERNAL_SERVER_ERROR); static_resp!(NotImplemented, StatusCode::NOT_IMPLEMENTED); diff --git a/actix-http/src/httpmessage.rs b/actix-http/src/http_message.rs similarity index 100% rename from actix-http/src/httpmessage.rs rename to actix-http/src/http_message.rs diff --git a/actix-http/src/lib.rs b/actix-http/src/lib.rs index 15866a3d8..a55aaadbf 100644 --- a/actix-http/src/lib.rs +++ b/actix-http/src/lib.rs @@ -1,6 +1,6 @@ //! HTTP primitives for the Actix ecosystem. -#![deny(rust_2018_idioms)] +#![deny(rust_2018_idioms, nonstandard_style)] #![allow( clippy::type_complexity, clippy::too_many_arguments, @@ -26,7 +26,7 @@ mod extensions; mod header; mod helpers; mod http_codes; -mod httpmessage; +mod http_message; mod message; mod payload; mod request; @@ -45,7 +45,7 @@ pub use self::builder::HttpServiceBuilder; pub use self::config::{KeepAlive, ServiceConfig}; pub use self::error::{Error, ResponseError, Result}; pub use self::extensions::Extensions; -pub use self::httpmessage::HttpMessage; +pub use self::http_message::HttpMessage; pub use self::message::{Message, RequestHead, RequestHeadType, ResponseHead}; pub use self::payload::{Payload, PayloadStream}; pub use self::request::Request; diff --git a/actix-http/src/request.rs b/actix-http/src/request.rs index 1ac2f33d6..18c6c8889 100644 --- a/actix-http/src/request.rs +++ b/actix-http/src/request.rs @@ -9,7 +9,7 @@ use http::{header, Method, Uri, Version}; use crate::extensions::Extensions; use crate::header::HeaderMap; -use crate::httpmessage::HttpMessage; +use crate::HttpMessage; use crate::message::{Message, RequestHead}; use crate::payload::{Payload, PayloadStream}; diff --git a/actix-http/src/response.rs b/actix-http/src/response.rs index 16d4a03d7..763243a63 100644 --- a/actix-http/src/response.rs +++ b/actix-http/src/response.rs @@ -867,6 +867,7 @@ mod tests { use super::*; use crate::body::Body; use crate::http::header::{HeaderValue, CONTENT_TYPE, COOKIE, SET_COOKIE}; + use crate::HttpMessage; #[test] fn test_debug() { @@ -880,8 +881,6 @@ mod tests { #[test] fn test_response_cookies() { - use crate::httpmessage::HttpMessage; - let req = crate::test::TestRequest::default() .append_header((COOKIE, "cookie1=value1")) .append_header((COOKIE, "cookie2=value2")) From 31d9ed81c5bdd7af96b2648851485426fd481c75 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Thu, 11 Feb 2021 23:03:17 +0000 Subject: [PATCH 031/339] change rustfmt line width to 96 --- actix-files/src/chunked.rs | 14 +- actix-files/src/directory.rs | 4 +- actix-files/src/files.rs | 12 +- actix-files/src/lib.rs | 23 ++- actix-files/src/named.rs | 15 +- actix-files/src/range.rs | 6 +- actix-files/src/service.rs | 7 +- actix-files/tests/encoding.rs | 7 +- actix-http-test/src/lib.rs | 9 +- actix-http/src/request.rs | 2 +- actix-multipart/src/server.rs | 53 ++----- actix-web-actors/src/context.rs | 18 +-- actix-web-actors/src/ws.rs | 38 ++--- actix-web-actors/tests/test_ws.rs | 10 +- actix-web-codegen/tests/test_macro.rs | 4 +- awc/src/connect.rs | 8 +- awc/src/error.rs | 4 +- awc/src/request.rs | 11 +- awc/src/response.rs | 15 +- awc/src/sender.rs | 39 ++--- awc/src/ws.rs | 8 +- awc/tests/test_client.rs | 8 +- awc/tests/test_rustls_client.rs | 3 +- awc/tests/test_ssl_client.rs | 3 +- benches/server.rs | 5 +- examples/basic.rs | 8 +- examples/uds.rs | 8 +- rustfmt.toml | 2 +- src/app.rs | 71 +++++---- src/app_service.rs | 19 ++- src/config.rs | 35 ++--- src/data.rs | 52 +++---- src/error.rs | 4 +- src/lib.rs | 8 +- src/middleware/compat.rs | 4 +- src/middleware/condition.rs | 12 +- src/middleware/default_headers.rs | 3 +- src/middleware/err_handlers.rs | 6 +- src/middleware/logger.rs | 36 ++--- src/request.rs | 30 ++-- src/resource.rs | 46 +++--- src/responder.rs | 12 +- src/route.rs | 14 +- src/scope.rs | 70 +++++---- src/server.rs | 200 +++++++++++--------------- src/service.rs | 29 ++-- src/test.rs | 78 ++++------ src/types/either.rs | 30 ++-- src/types/form.rs | 10 +- src/types/json.rs | 4 +- src/types/path.rs | 15 +- src/types/payload.rs | 13 +- src/types/query.rs | 3 +- src/types/readlines.rs | 5 +- tests/test_httpserver.rs | 3 +- tests/test_server.rs | 63 +++----- 56 files changed, 463 insertions(+), 756 deletions(-) diff --git a/actix-files/src/chunked.rs b/actix-files/src/chunked.rs index 2a62b1d26..f639848c9 100644 --- a/actix-files/src/chunked.rs +++ b/actix-files/src/chunked.rs @@ -49,10 +49,7 @@ impl fmt::Debug for ChunkedReadFile { impl Stream for ChunkedReadFile { 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> { let this = self.as_mut().get_mut(); match this.state { ChunkedReadFileState::File(ref mut file) => { @@ -68,16 +65,13 @@ impl Stream for ChunkedReadFile { .expect("ChunkedReadFile polled after completion"); let fut = spawn_blocking(move || { - let max_bytes = - cmp::min(size.saturating_sub(counter), 65_536) as usize; + let max_bytes = cmp::min(size.saturating_sub(counter), 65_536) as usize; let mut buf = Vec::with_capacity(max_bytes); file.seek(io::SeekFrom::Start(offset))?; - let n_bytes = file - .by_ref() - .take(max_bytes as u64) - .read_to_end(&mut buf)?; + 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()); diff --git a/actix-files/src/directory.rs b/actix-files/src/directory.rs index 1103dd6a7..80e0c98d0 100644 --- a/actix-files/src/directory.rs +++ b/actix-files/src/directory.rs @@ -66,9 +66,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-files/src/files.rs b/actix-files/src/files.rs index 0cf0a91ba..6f8b28bbf 100644 --- a/actix-files/src/files.rs +++ b/actix-files/src/files.rs @@ -2,9 +2,7 @@ use std::{cell::RefCell, fmt, io, path::PathBuf, rc::Rc}; use actix_service::{boxed, IntoServiceFactory, ServiceFactory, ServiceFactoryExt}; use actix_web::{ - dev::{ - AppService, HttpServiceFactory, ResourceDef, ServiceRequest, ServiceResponse, - }, + dev::{AppService, HttpServiceFactory, ResourceDef, ServiceRequest, ServiceResponse}, error::Error, guard::Guard, http::header::DispositionType, @@ -13,8 +11,8 @@ use actix_web::{ use futures_util::future::{ok, FutureExt, LocalBoxFuture}; use crate::{ - directory_listing, named, Directory, DirectoryRenderer, FilesService, - HttpNewService, MimeOverride, + directory_listing, named, Directory, DirectoryRenderer, FilesService, HttpNewService, + MimeOverride, }; /// Static files handling service. @@ -129,8 +127,8 @@ impl Files { /// Set custom directory renderer pub fn files_listing_renderer(mut self, f: F) -> Self where - for<'r, 's> F: Fn(&'r Directory, &'s HttpRequest) -> Result - + 'static, + for<'r, 's> F: + Fn(&'r Directory, &'s HttpRequest) -> Result + 'static, { self.renderer = Rc::new(f); self diff --git a/actix-files/src/lib.rs b/actix-files/src/lib.rs index b2f6384a8..04dd9f07f 100644 --- a/actix-files/src/lib.rs +++ b/actix-files/src/lib.rs @@ -98,8 +98,7 @@ mod tests { #[actix_rt::test] async fn test_if_modified_since_without_if_none_match() { let file = NamedFile::open("Cargo.toml").unwrap(); - let since = - header::HttpDate::from(SystemTime::now().add(Duration::from_secs(60))); + let since = header::HttpDate::from(SystemTime::now().add(Duration::from_secs(60))); let req = TestRequest::default() .insert_header((header::IF_MODIFIED_SINCE, since)) @@ -123,8 +122,7 @@ mod tests { #[actix_rt::test] async fn test_if_modified_since_with_if_none_match() { let file = NamedFile::open("Cargo.toml").unwrap(); - let since = - header::HttpDate::from(SystemTime::now().add(Duration::from_secs(60))); + let since = header::HttpDate::from(SystemTime::now().add(Duration::from_secs(60))); let req = TestRequest::default() .insert_header((header::IF_NONE_MATCH, "miss_etag")) @@ -212,8 +210,7 @@ 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(); + NamedFile::from_file(File::open("Cargo.toml").unwrap(), "貨物.toml").unwrap(); { file.file(); let _f: &File = &file; @@ -605,10 +602,9 @@ mod tests { #[actix_rt::test] async fn test_static_files() { - let srv = test::init_service( - App::new().service(Files::new("/", ".").show_files_listing()), - ) - .await; + let srv = + test::init_service(App::new().service(Files::new("/", ".").show_files_listing())) + .await; let req = TestRequest::with_uri("/missing").to_request(); let resp = test::call_service(&srv, req).await; @@ -620,10 +616,9 @@ mod tests { let resp = test::call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::NOT_FOUND); - let srv = test::init_service( - App::new().service(Files::new("/", ".").show_files_listing()), - ) - .await; + let srv = + test::init_service(App::new().service(Files::new("/", ".").show_files_listing())) + .await; let req = TestRequest::with_uri("/tests").to_request(); let resp = test::call_service(&srv, req).await; assert_eq!( diff --git a/actix-files/src/named.rs b/actix-files/src/named.rs index 6fa3f7c6c..a688b2e6c 100644 --- a/actix-files/src/named.rs +++ b/actix-files/src/named.rs @@ -11,8 +11,7 @@ use actix_web::{ dev::{BodyEncoding, SizedStream}, http::{ header::{ - self, Charset, ContentDisposition, DispositionParam, DispositionType, - ExtendedValue, + self, Charset, ContentDisposition, DispositionParam, DispositionType, ExtendedValue, }, ContentEncoding, StatusCode, }, @@ -395,18 +394,10 @@ impl NamedFile { resp.encoding(ContentEncoding::Identity); resp.insert_header(( header::CONTENT_RANGE, - format!( - "bytes {}-{}/{}", - offset, - offset + length - 1, - self.md.len() - ), + format!("bytes {}-{}/{}", offset, offset + length - 1, self.md.len()), )); } else { - resp.insert_header(( - header::CONTENT_RANGE, - format!("bytes */{}", length), - )); + resp.insert_header((header::CONTENT_RANGE, format!("bytes */{}", length))); return resp.status(StatusCode::RANGE_NOT_SATISFIABLE).finish(); }; } else { diff --git a/actix-files/src/range.rs b/actix-files/src/range.rs index e420ce414..6718980cb 100644 --- a/actix-files/src/range.rs +++ b/actix-files/src/range.rs @@ -46,8 +46,7 @@ impl HttpRange { if start_str.is_empty() { // If no start is specified, end specifies the // range start relative to the end of the file. - let mut length: i64 = - end_str.parse().map_err(|_| ParseRangeErr(()))?; + let mut length: i64 = end_str.parse().map_err(|_| ParseRangeErr(()))?; if length > size_sig { length = size_sig; @@ -72,8 +71,7 @@ impl HttpRange { // If no end is specified, range extends to end of the file. size_sig - start } else { - let mut end: i64 = - end_str.parse().map_err(|_| ParseRangeErr(()))?; + let mut end: i64 = end_str.parse().map_err(|_| ParseRangeErr(()))?; if start > end { return Err(ParseRangeErr(())); diff --git a/actix-files/src/service.rs b/actix-files/src/service.rs index a9822f486..14eea6ebc 100644 --- a/actix-files/src/service.rs +++ b/actix-files/src/service.rs @@ -11,8 +11,8 @@ use actix_web::{ use futures_util::future::{ok, Either, LocalBoxFuture, Ready}; use crate::{ - named, Directory, DirectoryRenderer, FilesError, HttpService, MimeOverride, - NamedFile, PathBufWrap, + named, Directory, DirectoryRenderer, FilesError, HttpService, MimeOverride, NamedFile, + PathBufWrap, }; /// Assembled file serving service. @@ -138,8 +138,7 @@ impl Service for FilesService { 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_()); + let new_disposition = mime_override(&named_file.content_type.type_()); named_file.content_disposition.disposition = new_disposition; } named_file.flags = self.file_flags; diff --git a/actix-files/tests/encoding.rs b/actix-files/tests/encoding.rs index 6cfa3a7f7..d21d4f8fd 100644 --- a/actix-files/tests/encoding.rs +++ b/actix-files/tests/encoding.rs @@ -23,10 +23,9 @@ async fn test_utf8_file_contents() { ); // prefer UTF-8 encoding - let srv = test::init_service( - App::new().service(Files::new("/", "./tests").prefer_utf8(true)), - ) - .await; + let srv = + test::init_service(App::new().service(Files::new("/", "./tests").prefer_utf8(true))) + .await; let req = TestRequest::with_uri("/utf8.txt").to_request(); let res = test::call_service(&srv, req).await; diff --git a/actix-http-test/src/lib.rs b/actix-http-test/src/lib.rs index f5c97262a..89cb0777f 100644 --- a/actix-http-test/src/lib.rs +++ b/actix-http-test/src/lib.rs @@ -120,8 +120,7 @@ pub async fn test_server_with_addr>( /// Get first available unused address pub fn unused_addr() -> net::SocketAddr { let addr: net::SocketAddr = "127.0.0.1:0".parse().unwrap(); - let socket = - Socket::new(Domain::ipv4(), Type::stream(), Some(Protocol::tcp())).unwrap(); + let socket = Socket::new(Domain::ipv4(), Type::stream(), Some(Protocol::tcp())).unwrap(); socket.bind(&addr.into()).unwrap(); socket.set_reuse_address(true).unwrap(); let tcp = socket.into_tcp_listener(); @@ -248,8 +247,7 @@ impl TestServer { pub async fn ws_at( &mut self, path: &str, - ) -> Result, awc::error::WsClientError> - { + ) -> Result, awc::error::WsClientError> { let url = self.url(path); let connect = self.client.ws(url).connect(); connect.await.map(|(_, framed)| framed) @@ -258,8 +256,7 @@ impl TestServer { /// Connect to a websocket server pub async fn ws( &mut self, - ) -> Result, awc::error::WsClientError> - { + ) -> Result, awc::error::WsClientError> { self.ws_at("/").await } diff --git a/actix-http/src/request.rs b/actix-http/src/request.rs index 18c6c8889..197ec11c6 100644 --- a/actix-http/src/request.rs +++ b/actix-http/src/request.rs @@ -9,9 +9,9 @@ use http::{header, Method, Uri, Version}; use crate::extensions::Extensions; use crate::header::HeaderMap; -use crate::HttpMessage; use crate::message::{Message, RequestHead}; use crate::payload::{Payload, PayloadStream}; +use crate::HttpMessage; /// Request pub struct Request

{ diff --git a/actix-multipart/src/server.rs b/actix-multipart/src/server.rs index 2cde5e8cf..8cd1c8e0c 100644 --- a/actix-multipart/src/server.rs +++ b/actix-multipart/src/server.rs @@ -13,9 +13,7 @@ use futures_util::stream::{LocalBoxStream, Stream, StreamExt}; use actix_utils::task::LocalWaker; use actix_web::error::{ParseError, PayloadError}; -use actix_web::http::header::{ - self, ContentDisposition, HeaderMap, HeaderName, HeaderValue, -}; +use actix_web::http::header::{self, ContentDisposition, HeaderMap, HeaderName, HeaderValue}; use crate::error::MultipartError; @@ -120,10 +118,7 @@ impl Multipart { impl Stream for Multipart { 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> { if let Some(err) = self.error.take() { Poll::Ready(Some(Err(err))) } else if self.safety.current() { @@ -142,9 +137,7 @@ impl Stream for Multipart { } impl InnerMultipart { - fn read_headers( - payload: &mut PayloadBuffer, - ) -> Result, MultipartError> { + fn read_headers(payload: &mut PayloadBuffer) -> Result, MultipartError> { match payload.read_until(b"\r\n\r\n")? { None => { if payload.eof { @@ -226,8 +219,7 @@ impl InnerMultipart { if chunk.len() < boundary.len() { continue; } - if &chunk[..2] == b"--" - && &chunk[2..chunk.len() - 2] == boundary.as_bytes() + if &chunk[..2] == b"--" && &chunk[2..chunk.len() - 2] == boundary.as_bytes() { break; } else { @@ -273,9 +265,7 @@ impl InnerMultipart { match field.borrow_mut().poll(safety) { Poll::Pending => return Poll::Pending, Poll::Ready(Some(Ok(_))) => continue, - Poll::Ready(Some(Err(e))) => { - return Poll::Ready(Some(Err(e))) - } + Poll::Ready(Some(Err(e))) => return Poll::Ready(Some(Err(e))), Poll::Ready(None) => true, } } @@ -311,10 +301,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 { @@ -418,8 +405,7 @@ impl Field { 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) - { + if let Some(content_disposition) = self.headers.get(&header::CONTENT_DISPOSITION) { ContentDisposition::from_raw(content_disposition).ok() } else { None @@ -430,15 +416,10 @@ impl Field { impl Stream for Field { 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> { 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) - { + if let Some(mut payload) = inner.payload.as_ref().unwrap().get_mut(&self.safety) { payload.poll_stream(cx)?; } inner.poll(&self.safety) @@ -607,8 +588,7 @@ impl InnerField { return Poll::Ready(None); } - let result = if let Some(mut payload) = self.payload.as_ref().unwrap().get_mut(s) - { + 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) @@ -628,7 +608,9 @@ impl InnerField { Ok(None) => Poll::Pending, Ok(Some(line)) => { if line.as_ref() != b"\r\n" { - log::warn!("multipart field did not read all the data or it is malformed"); + log::warn!( + "multipart field did not read all the data or it is malformed" + ); } Poll::Ready(None) } @@ -804,9 +786,7 @@ impl PayloadBuffer { /// Read bytes until new line delimiter or eof pub fn readline_or_eof(&mut self) -> Result, MultipartError> { match self.readline() { - Err(MultipartError::Incomplete) if self.eof => { - Ok(Some(self.buf.split().freeze())) - } + Err(MultipartError::Incomplete) if self.eof => Ok(Some(self.buf.split().freeze())), line => line, } } @@ -902,10 +882,7 @@ mod tests { impl Stream for SlowStream { 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(); if !this.ready { this.ready = true; diff --git a/actix-web-actors/src/context.rs b/actix-web-actors/src/context.rs index c452025af..bd6635cba 100644 --- a/actix-web-actors/src/context.rs +++ b/actix-web-actors/src/context.rs @@ -3,9 +3,7 @@ use std::future::Future; use std::pin::Pin; use std::task::{Context, Poll}; -use actix::dev::{ - AsyncContextParts, ContextFut, ContextParts, Envelope, Mailbox, ToEnvelope, -}; +use actix::dev::{AsyncContextParts, ContextFut, ContextParts, Envelope, Mailbox, ToEnvelope}; use actix::fut::ActorFuture; use actix::{ Actor, ActorContext, ActorState, Addr, AsyncContext, Handler, Message, SpawnHandle, @@ -165,10 +163,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> { if self.fut.alive() { let _ = Pin::new(&mut self.fut).poll(cx); } @@ -233,10 +228,11 @@ 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(|| { + 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/actix-web-actors/src/ws.rs b/actix-web-actors/src/ws.rs index 10113665b..3eb8cc1e7 100644 --- a/actix-web-actors/src/ws.rs +++ b/actix-web-actors/src/ws.rs @@ -7,13 +7,12 @@ use std::task::{Context, Poll}; use std::{collections::VecDeque, convert::TryFrom}; use actix::dev::{ - AsyncContextParts, ContextFut, ContextParts, Envelope, Mailbox, StreamHandler, - ToEnvelope, + AsyncContextParts, ContextFut, ContextParts, Envelope, Mailbox, StreamHandler, ToEnvelope, }; use actix::fut::ActorFuture; use actix::{ - Actor, ActorContext, ActorState, Addr, AsyncContext, Handler, - Message as ActixMessage, SpawnHandle, + Actor, ActorContext, ActorState, Addr, AsyncContext, Handler, Message as ActixMessage, + SpawnHandle, }; use actix_codec::{Decoder, Encoder}; use actix_http::ws::{hash_key, Codec}; @@ -32,8 +31,7 @@ use tokio::sync::oneshot::Sender; /// Perform WebSocket handshake and start actor. pub fn start(actor: A, req: &HttpRequest, stream: T) -> Result where - A: Actor> - + StreamHandler>, + A: Actor> + StreamHandler>, T: Stream> + 'static, { let mut res = handshake(req)?; @@ -57,8 +55,7 @@ pub fn start_with_addr( stream: T, ) -> Result<(Addr, HttpResponse), Error> where - A: Actor> - + StreamHandler>, + A: Actor> + StreamHandler>, T: Stream> + 'static, { let mut res = handshake(req)?; @@ -76,8 +73,7 @@ pub fn start_with_protocols( stream: T, ) -> Result where - A: Actor> - + StreamHandler>, + A: Actor> + StreamHandler>, T: Stream> + 'static, { let mut res = handshake_with_protocols(req, protocols)?; @@ -301,10 +297,7 @@ where } /// Create a new Websocket context - pub fn with_factory( - stream: S, - f: F, - ) -> impl Stream> + pub fn with_factory(stream: S, f: F) -> impl Stream> where F: FnOnce(&mut Self) -> A + 'static, A: StreamHandler>, @@ -423,10 +416,7 @@ where { 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(); if this.fut.alive() { @@ -493,10 +483,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> { let mut this = self.as_mut().project(); if !*this.closed { @@ -512,9 +499,10 @@ where } Poll::Pending => break, Poll::Ready(Some(Err(e))) => { - return Poll::Ready(Some(Err(ProtocolError::Io( - io::Error::new(io::ErrorKind::Other, format!("{}", e)), - )))); + return Poll::Ready(Some(Err(ProtocolError::Io(io::Error::new( + io::ErrorKind::Other, + format!("{}", e), + ))))); } } } diff --git a/actix-web-actors/tests/test_ws.rs b/actix-web-actors/tests/test_ws.rs index 7fd59a4a7..912480ae4 100644 --- a/actix-web-actors/tests/test_ws.rs +++ b/actix-web-actors/tests/test_ws.rs @@ -11,11 +11,7 @@ impl Actor for Ws { } impl StreamHandler> for Ws { - fn handle( - &mut self, - msg: Result, - ctx: &mut Self::Context, - ) { + 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), @@ -30,9 +26,7 @@ impl StreamHandler> for Ws { async fn test_simple() { let mut srv = test::start(|| { App::new().service(web::resource("/").to( - |req: HttpRequest, stream: web::Payload| async move { - ws::start(Ws, &req, stream) - }, + |req: HttpRequest, stream: web::Payload| async move { ws::start(Ws, &req, stream) }, )) }); diff --git a/actix-web-codegen/tests/test_macro.rs b/actix-web-codegen/tests/test_macro.rs index 252be1b95..34cdad649 100644 --- a/actix-web-codegen/tests/test_macro.rs +++ b/actix-web-codegen/tests/test_macro.rs @@ -4,9 +4,7 @@ use std::task::{Context, Poll}; use actix_web::dev::{Service, ServiceRequest, ServiceResponse, Transform}; use actix_web::http::header::{HeaderName, HeaderValue}; use actix_web::{http, test, web::Path, App, Error, HttpResponse, Responder}; -use actix_web_codegen::{ - connect, delete, get, head, options, patch, post, put, route, trace, -}; +use actix_web_codegen::{connect, delete, get, head, options, patch, post, put, route, trace}; use futures_util::future::{self, LocalBoxFuture}; // Make sure that we can name function as 'config' diff --git a/awc/src/connect.rs b/awc/src/connect.rs index 5450550a7..a9b8f9f83 100644 --- a/awc/src/connect.rs +++ b/awc/src/connect.rs @@ -82,8 +82,7 @@ where let connection = fut.await?; // send request - let (head, framed) = - connection.open_tunnel(RequestHeadType::from(head)).await?; + let (head, framed) = connection.open_tunnel(RequestHeadType::from(head)).await?; let framed = framed.into_map_io(|io| BoxedSocket(Box::new(Socket(io)))); Ok((head, framed)) @@ -142,10 +141,7 @@ impl AsyncWrite for BoxedSocket { Pin::new(self.get_mut().0.as_write()).poll_flush(cx) } - fn poll_shutdown( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll> { + fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { Pin::new(self.get_mut().0.as_write()).poll_shutdown(cx) } } diff --git a/awc/src/error.rs b/awc/src/error.rs index d008166d9..c60339f76 100644 --- a/awc/src/error.rs +++ b/awc/src/error.rs @@ -1,7 +1,5 @@ //! Http client errors -pub use actix_http::client::{ - ConnectError, FreezeRequestError, InvalidUrl, SendRequestError, -}; +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; diff --git a/awc/src/request.rs b/awc/src/request.rs index 3ec43f3e5..db8196c5b 100644 --- a/awc/src/request.rs +++ b/awc/src/request.rs @@ -11,8 +11,7 @@ use actix_http::body::Body; use actix_http::cookie::{Cookie, CookieJar}; use actix_http::http::header::{self, IntoHeaderPair}; use actix_http::http::{ - uri, ConnectionType, Error as HttpError, HeaderMap, HeaderValue, Method, Uri, - Version, + uri, ConnectionType, Error as HttpError, HeaderMap, HeaderValue, Method, Uri, Version, }; use actix_http::{Error, RequestHead}; @@ -520,15 +519,11 @@ impl ClientRequest { .unwrap_or(true); if https { - slf = - slf.insert_header_if_none((header::ACCEPT_ENCODING, HTTPS_ENCODING)) + slf = slf.insert_header_if_none((header::ACCEPT_ENCODING, HTTPS_ENCODING)) } else { #[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))] { - slf = slf.insert_header_if_none(( - header::ACCEPT_ENCODING, - "gzip, deflate", - )) + slf = slf.insert_header_if_none((header::ACCEPT_ENCODING, "gzip, deflate")) } }; } diff --git a/awc/src/response.rs b/awc/src/response.rs index f5161f9c1..1c887ee4e 100644 --- a/awc/src/response.rs +++ b/awc/src/response.rs @@ -50,8 +50,7 @@ impl HttpMessage for ClientResponse { if self.extensions().get::().is_none() { let mut cookies = Vec::new(); for hdr in self.headers().get_all(&SET_COOKIE) { - let s = std::str::from_utf8(hdr.as_bytes()) - .map_err(CookieParseError::from)?; + 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)); @@ -132,10 +131,7 @@ where { 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> { Pin::new(&mut self.get_mut().payload).poll_next(cx) } } @@ -306,9 +302,7 @@ where if let Some(len) = self.length.take() { if len > self.fut.as_ref().unwrap().limit { - return Poll::Ready(Err(JsonPayloadError::Payload( - PayloadError::Overflow, - ))); + return Poll::Ready(Err(JsonPayloadError::Payload(PayloadError::Overflow))); } } @@ -374,8 +368,7 @@ mod tests { _ => unreachable!("error"), } - let mut req = - TestResponse::with_header(header::CONTENT_LENGTH, "1000000").finish(); + let mut req = TestResponse::with_header(header::CONTENT_LENGTH, "1000000").finish(); match req.body().await.err().unwrap() { PayloadError::Overflow => {} _ => unreachable!("error"), diff --git a/awc/src/sender.rs b/awc/src/sender.rs index ca220be71..a72b129f8 100644 --- a/awc/src/sender.rs +++ b/awc/src/sender.rs @@ -81,8 +81,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(); @@ -99,15 +98,9 @@ impl Future for SendClientRequest { let res = futures_core::ready!(Pin::new(send).poll(cx)).map(|res| { res.map_body(|head, payload| { if *response_decompress { - Payload::Stream(Decoder::from_headers( - payload, - &head.headers, - )) + Payload::Stream(Decoder::from_headers(payload, &head.headers)) } else { - Payload::Stream(Decoder::new( - payload, - ContentEncoding::Identity, - )) + Payload::Stream(Decoder::new(payload, ContentEncoding::Identity)) } }) }); @@ -189,11 +182,11 @@ impl RequestSender { B: Into, { let fut = match self { - RequestSender::Owned(head) => config.connector.send_request( - RequestHeadType::Owned(head), - body.into(), - addr, - ), + RequestSender::Owned(head) => { + config + .connector + .send_request(RequestHeadType::Owned(head), body.into(), addr) + } RequestSender::Rc(head, extra_headers) => config.connector.send_request( RequestHeadType::Rc(head, extra_headers), body.into(), @@ -217,8 +210,7 @@ impl RequestSender { Err(e) => return Error::from(e).into(), }; - if let Err(e) = self.set_header_if_none(header::CONTENT_TYPE, "application/json") - { + if let Err(e) = self.set_header_if_none(header::CONTENT_TYPE, "application/json") { return e.into(); } @@ -245,10 +237,9 @@ impl RequestSender { }; // set content-type - if let Err(e) = self.set_header_if_none( - header::CONTENT_TYPE, - "application/x-www-form-urlencoded", - ) { + if let Err(e) = + self.set_header_if_none(header::CONTENT_TYPE, "application/x-www-form-urlencoded") + { return e.into(); } @@ -292,11 +283,7 @@ impl RequestSender { self.send_body(addr, response_decompress, timeout, config, Body::Empty) } - fn set_header_if_none( - &mut self, - key: HeaderName, - value: V, - ) -> Result<(), HttpError> + fn set_header_if_none(&mut self, key: HeaderName, value: V) -> Result<(), HttpError> where V: IntoHeaderValue, { diff --git a/awc/src/ws.rs b/awc/src/ws.rs index 7c795226b..8994af7ed 100644 --- a/awc/src/ws.rs +++ b/awc/src/ws.rs @@ -40,12 +40,8 @@ pub use actix_http::ws::{CloseCode, CloseReason, Codec, Frame, Message}; use crate::connect::BoxedSocket; 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::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; diff --git a/awc/tests/test_client.rs b/awc/tests/test_client.rs index a9f5dc370..7e74d226e 100644 --- a/awc/tests/test_client.rs +++ b/awc/tests/test_client.rs @@ -51,8 +51,7 @@ const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ #[actix_rt::test] async fn test_simple() { let srv = test::start(|| { - App::new() - .service(web::resource("/").route(web::to(|| HttpResponse::Ok().body(STR)))) + App::new().service(web::resource("/").route(web::to(|| HttpResponse::Ok().body(STR)))) }); let request = srv.get("/").insert_header(("x-test", "111")).send(); @@ -612,9 +611,8 @@ async fn test_client_streaming_explicit() { })) }); - let body = stream::once(async { - Ok::<_, actix_http::Error>(Bytes::from_static(STR.as_bytes())) - }); + 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 mut res = req.await.unwrap(); diff --git a/awc/tests/test_rustls_client.rs b/awc/tests/test_rustls_client.rs index b0f3b71be..a928715a8 100644 --- a/awc/tests/test_rustls_client.rs +++ b/awc/tests/test_rustls_client.rs @@ -64,8 +64,7 @@ async fn test_connection_reuse_h2() { .and_then( HttpService::build() .h2(map_config( - App::new() - .service(web::resource("/").route(web::to(HttpResponse::Ok))), + App::new().service(web::resource("/").route(web::to(HttpResponse::Ok))), |_| AppConfig::default(), )) .rustls(tls_config()) diff --git a/awc/tests/test_ssl_client.rs b/awc/tests/test_ssl_client.rs index 064824d59..b93c729e5 100644 --- a/awc/tests/test_ssl_client.rs +++ b/awc/tests/test_ssl_client.rs @@ -48,8 +48,7 @@ async fn test_connection_reuse_h2() { .and_then( HttpService::build() .h2(map_config( - App::new() - .service(web::resource("/").route(web::to(HttpResponse::Ok))), + App::new().service(web::resource("/").route(web::to(HttpResponse::Ok))), |_| AppConfig::default(), )) .openssl(ssl_acceptor()) diff --git a/benches/server.rs b/benches/server.rs index ce79f077d..2471dd74c 100644 --- a/benches/server.rs +++ b/benches/server.rs @@ -33,9 +33,8 @@ fn bench_async_burst(c: &mut Criterion) { let srv = rt.block_on(async { test::start(|| { - App::new().service( - web::resource("/").route(web::to(|| HttpResponse::Ok().body(STR))), - ) + App::new() + .service(web::resource("/").route(web::to(|| HttpResponse::Ok().body(STR)))) }) }); diff --git a/examples/basic.rs b/examples/basic.rs index e8ad5fcdb..99eef3ee1 100644 --- a/examples/basic.rs +++ b/examples/basic.rs @@ -29,12 +29,8 @@ async fn main() -> std::io::Result<()> { .service(no_params) .service( web::resource("/resource2/index.html") - .wrap( - middleware::DefaultHeaders::new().header("X-Version-R2", "0.3"), - ) - .default_service( - web::route().to(|| HttpResponse::MethodNotAllowed()), - ) + .wrap(middleware::DefaultHeaders::new().header("X-Version-R2", "0.3")) + .default_service(web::route().to(|| HttpResponse::MethodNotAllowed())) .route(web::get().to(index_async)), ) .service(web::resource("/test1.html").to(|| async { "Test\r\n" })) diff --git a/examples/uds.rs b/examples/uds.rs index c0a5d76a6..096781984 100644 --- a/examples/uds.rs +++ b/examples/uds.rs @@ -33,12 +33,8 @@ async fn main() -> std::io::Result<()> { .service(no_params) .service( web::resource("/resource2/index.html") - .wrap( - middleware::DefaultHeaders::new().header("X-Version-R2", "0.3"), - ) - .default_service( - web::route().to(|| HttpResponse::MethodNotAllowed()), - ) + .wrap(middleware::DefaultHeaders::new().header("X-Version-R2", "0.3")) + .default_service(web::route().to(|| HttpResponse::MethodNotAllowed())) .route(web::get().to(index_async)), ) .service(web::resource("/test1.html").to(|| async { "Test\r\n" })) diff --git a/rustfmt.toml b/rustfmt.toml index 94bd11d51..973e002c0 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -1,2 +1,2 @@ -max_width = 89 +max_width = 96 reorder_imports = true diff --git a/src/app.rs b/src/app.rs index 123076fc8..7a26a3a89 100644 --- a/src/app.rs +++ b/src/app.rs @@ -8,8 +8,7 @@ use actix_http::body::{Body, MessageBody}; use actix_http::{Extensions, Request}; use actix_service::boxed::{self, BoxServiceFactory}; use actix_service::{ - apply, apply_fn_factory, IntoServiceFactory, ServiceFactory, ServiceFactoryExt, - Transform, + apply, apply_fn_factory, IntoServiceFactory, ServiceFactory, ServiceFactoryExt, Transform, }; use futures_util::future::FutureExt; @@ -473,17 +472,13 @@ mod tests { use crate::http::{header, HeaderValue, Method, StatusCode}; use crate::middleware::DefaultHeaders; use crate::service::ServiceRequest; - use crate::test::{ - call_service, init_service, read_body, try_init_service, TestRequest, - }; + 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() { - let srv = init_service( - App::new().service(web::resource("/test").to(HttpResponse::Ok)), - ) - .await; + let srv = + init_service(App::new().service(web::resource("/test").to(HttpResponse::Ok))).await; let req = TestRequest::with_uri("/test").to_request(); let resp = srv.call(req).await.unwrap(); assert_eq!(resp.status(), StatusCode::OK); @@ -525,20 +520,22 @@ mod tests { #[actix_rt::test] async fn test_data_factory() { - let srv = - init_service(App::new().data_factory(|| ok::<_, ()>(10usize)).service( - web::resource("/").to(|_: web::Data| HttpResponse::Ok()), - )) - .await; + let srv = init_service( + App::new() + .data_factory(|| ok::<_, ()>(10usize)) + .service(web::resource("/").to(|_: web::Data| HttpResponse::Ok())), + ) + .await; let req = TestRequest::default().to_request(); let resp = srv.call(req).await.unwrap(); assert_eq!(resp.status(), StatusCode::OK); - let srv = - init_service(App::new().data_factory(|| ok::<_, ()>(10u32)).service( - web::resource("/").to(|_: web::Data| HttpResponse::Ok()), - )) - .await; + let srv = init_service( + App::new() + .data_factory(|| ok::<_, ()>(10u32)) + .service(web::resource("/").to(|_: web::Data| HttpResponse::Ok())), + ) + .await; let req = TestRequest::default().to_request(); let resp = srv.call(req).await.unwrap(); assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR); @@ -546,23 +543,24 @@ mod tests { #[actix_rt::test] async fn test_data_factory_errors() { - let srv = - try_init_service(App::new().data_factory(|| err::(())).service( - web::resource("/").to(|_: web::Data| HttpResponse::Ok()), - )) - .await; + let srv = try_init_service( + App::new() + .data_factory(|| err::(())) + .service(web::resource("/").to(|_: web::Data| HttpResponse::Ok())), + ) + .await; assert!(srv.is_err()); } #[actix_rt::test] async fn test_extension() { - let srv = init_service(App::new().app_data(10usize).service( - web::resource("/").to(|req: HttpRequest| { + let srv = init_service(App::new().app_data(10usize).service(web::resource("/").to( + |req: HttpRequest| { assert_eq!(*req.app_data::().unwrap(), 10); HttpResponse::Ok() - }), - )) + }, + ))) .await; let req = TestRequest::default().to_request(); let resp = srv.call(req).await.unwrap(); @@ -617,10 +615,8 @@ mod tests { let fut = srv.call(req); async move { let mut res = fut.await?; - res.headers_mut().insert( - header::CONTENT_TYPE, - HeaderValue::from_static("0001"), - ); + res.headers_mut() + .insert(header::CONTENT_TYPE, HeaderValue::from_static("0001")); Ok(res) } }) @@ -645,10 +641,8 @@ mod tests { let fut = srv.call(req); async { let mut res = fut.await?; - res.headers_mut().insert( - header::CONTENT_TYPE, - HeaderValue::from_static("0001"), - ); + res.headers_mut() + .insert(header::CONTENT_TYPE, HeaderValue::from_static("0001")); Ok(res) } }), @@ -671,9 +665,8 @@ mod tests { .route( "/test", web::get().to(|req: HttpRequest| { - HttpResponse::Ok().body( - req.url_for("youtube", &["12345"]).unwrap().to_string(), - ) + HttpResponse::Ok() + .body(req.url_for("youtube", &["12345"]).unwrap().to_string()) }), ), ) diff --git a/src/app_service.rs b/src/app_service.rs index a38f1d652..9b4ae3354 100644 --- a/src/app_service.rs +++ b/src/app_service.rs @@ -241,16 +241,15 @@ impl ServiceFactory for AppRoutingFactory { fn new_service(&self, _: ()) -> Self::Future { // 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 factory_fut = factory.new_service(()); - async move { - let service = factory_fut.await?; - Ok((path, guards, service)) - } - })); + let factory_fut = join_all(self.services.iter().map(|(path, factory, guards)| { + let path = path.clone(); + let guards = guards.borrow_mut().take(); + let factory_fut = factory.new_service(()); + async move { + let service = factory_fut.await?; + Ok((path, guards, service)) + } + })); // construct default service factory future let default_fut = self.default.new_service(()); diff --git a/src/config.rs b/src/config.rs index 24afca295..bd9a25c6f 100644 --- a/src/config.rs +++ b/src/config.rs @@ -17,8 +17,7 @@ use crate::service::{ }; type Guards = Vec>; -type HttpNewService = - boxed::BoxServiceFactory<(), ServiceRequest, ServiceResponse, Error, ()>; +type HttpNewService = boxed::BoxServiceFactory<(), ServiceRequest, ServiceResponse, Error, ()>; /// Application configuration pub struct AppService { @@ -99,12 +98,8 @@ impl AppService { InitError = (), > + 'static, { - self.services.push(( - rdef, - boxed::factory(factory.into_factory()), - guards, - nested, - )); + self.services + .push((rdef, boxed::factory(factory.into_factory()), guards, nested)); } } @@ -263,12 +258,12 @@ mod tests { cfg.app_data(15u8); }; - let srv = init_service(App::new().configure(cfg).service( - web::resource("/").to(|_: web::Data, req: HttpRequest| { + let srv = init_service(App::new().configure(cfg).service(web::resource("/").to( + |_: web::Data, req: HttpRequest| { assert_eq!(*req.app_data::().unwrap(), 15u8); HttpResponse::Ok() - }), - )) + }, + ))) .await; let req = TestRequest::default().to_request(); let resp = srv.call(req).await.unwrap(); @@ -312,17 +307,13 @@ mod tests { let srv = init_service( App::new() .configure(|cfg| { - cfg.external_resource( - "youtube", - "https://youtube.com/watch/{video_id}", - ); + cfg.external_resource("youtube", "https://youtube.com/watch/{video_id}"); }) .route( "/test", web::get().to(|req: HttpRequest| { - HttpResponse::Ok().body( - req.url_for("youtube", &["12345"]).unwrap().to_string(), - ) + HttpResponse::Ok() + .body(req.url_for("youtube", &["12345"]).unwrap().to_string()) }), ), ) @@ -337,10 +328,8 @@ mod tests { #[actix_rt::test] async fn test_service() { let srv = init_service(App::new().configure(|cfg| { - cfg.service( - web::resource("/test").route(web::get().to(HttpResponse::Created)), - ) - .route("/index.html", web::get().to(HttpResponse::Ok)); + cfg.service(web::resource("/test").route(web::get().to(HttpResponse::Created))) + .route("/index.html", web::get().to(HttpResponse::Ok)); })) .await; diff --git a/src/data.rs b/src/data.rs index b3f3d643c..133248212 100644 --- a/src/data.rs +++ b/src/data.rs @@ -156,11 +156,12 @@ mod tests { let resp = srv.call(req).await.unwrap(); assert_eq!(resp.status(), StatusCode::OK); - let srv = - init_service(App::new().data(10u32).service( - web::resource("/").to(|_: web::Data| HttpResponse::Ok()), - )) - .await; + let srv = init_service( + App::new() + .data(10u32) + .service(web::resource("/").to(|_: web::Data| HttpResponse::Ok())), + ) + .await; let req = TestRequest::default().to_request(); let resp = srv.call(req).await.unwrap(); assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR); @@ -186,21 +187,23 @@ mod tests { #[actix_rt::test] async fn test_app_data_extractor() { - let srv = - init_service(App::new().app_data(Data::new(10usize)).service( - web::resource("/").to(|_: web::Data| HttpResponse::Ok()), - )) - .await; + let srv = init_service( + App::new() + .app_data(Data::new(10usize)) + .service(web::resource("/").to(|_: web::Data| HttpResponse::Ok())), + ) + .await; let req = TestRequest::default().to_request(); let resp = srv.call(req).await.unwrap(); assert_eq!(resp.status(), StatusCode::OK); - let srv = - init_service(App::new().app_data(Data::new(10u32)).service( - web::resource("/").to(|_: web::Data| HttpResponse::Ok()), - )) - .await; + let srv = init_service( + App::new() + .app_data(Data::new(10u32)) + .service(web::resource("/").to(|_: web::Data| HttpResponse::Ok())), + ) + .await; let req = TestRequest::default().to_request(); let resp = srv.call(req).await.unwrap(); assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR); @@ -237,15 +240,16 @@ mod tests { #[actix_rt::test] async fn test_override_data() { - let srv = init_service(App::new().data(1usize).service( - web::resource("/").data(10usize).route(web::get().to( - |data: web::Data| { - assert_eq!(**data, 10); - HttpResponse::Ok() - }, - )), - )) - .await; + let srv = + init_service(App::new().data(1usize).service( + web::resource("/").data(10usize).route(web::get().to( + |data: web::Data| { + assert_eq!(**data, 10); + HttpResponse::Ok() + }, + )), + )) + .await; let req = TestRequest::default().to_request(); let resp = srv.call(req).await.unwrap(); diff --git a/src/error.rs b/src/error.rs index c0d6f8af9..1d7c781d8 100644 --- a/src/error.rs +++ b/src/error.rs @@ -92,9 +92,7 @@ impl std::error::Error for JsonPayloadError {} impl ResponseError for JsonPayloadError { fn error_response(&self) -> HttpResponse { match *self { - JsonPayloadError::Overflow => { - HttpResponse::new(StatusCode::PAYLOAD_TOO_LARGE) - } + JsonPayloadError::Overflow => HttpResponse::new(StatusCode::PAYLOAD_TOO_LARGE), _ => HttpResponse::new(StatusCode::BAD_REQUEST), } } diff --git a/src/lib.rs b/src/lib.rs index 271efa0de..39cfaf197 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -130,9 +130,7 @@ pub mod dev { pub use crate::handler::Handler; pub use crate::info::ConnectionInfo; pub use crate::rmap::ResourceMap; - pub use crate::service::{ - HttpServiceFactory, ServiceRequest, ServiceResponse, WebService, - }; + pub use crate::service::{HttpServiceFactory, ServiceRequest, ServiceResponse, WebService}; pub use crate::types::form::UrlEncoded; pub use crate::types::json::JsonBody; @@ -142,9 +140,7 @@ pub mod dev { #[cfg(feature = "compress")] pub use actix_http::encoding::Decoder as Decompress; pub use actix_http::ResponseBuilder as HttpResponseBuilder; - pub use actix_http::{ - Extensions, Payload, PayloadStream, RequestHead, ResponseHead, - }; + 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::{Service, Transform}; diff --git a/src/middleware/compat.rs b/src/middleware/compat.rs index 7326c5824..97742e92c 100644 --- a/src/middleware/compat.rs +++ b/src/middleware/compat.rs @@ -143,9 +143,7 @@ mod tests { web::scope("app") .wrap(Compat::new(logger)) .wrap(Compat::new(compress)) - .service( - web::resource("/test").route(web::get().to(HttpResponse::Ok)), - ), + .service(web::resource("/test").route(web::get().to(HttpResponse::Ok))), ), ) .await; diff --git a/src/middleware/condition.rs b/src/middleware/condition.rs index be6f6764d..f0c344062 100644 --- a/src/middleware/condition.rs +++ b/src/middleware/condition.rs @@ -119,15 +119,13 @@ mod tests { ok(req.into_response(HttpResponse::InternalServerError().finish())) }; - let mw = - 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 = test::call_service(&mw, TestRequest::default().to_srv_request()).await; assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0001"); } @@ -137,16 +135,14 @@ mod tests { ok(req.into_response(HttpResponse::InternalServerError().finish())) }; - let mw = - 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 = test::call_service(&mw, TestRequest::default().to_srv_request()).await; assert_eq!(resp.headers().get(CONTENT_TYPE), None); } } diff --git a/src/middleware/default_headers.rs b/src/middleware/default_headers.rs index d98403502..a36cc2f29 100644 --- a/src/middleware/default_headers.rs +++ b/src/middleware/default_headers.rs @@ -229,8 +229,7 @@ mod tests { #[actix_rt::test] async fn test_content_type() { - let srv = - |req: ServiceRequest| ok(req.into_response(HttpResponse::Ok().finish())); + let srv = |req: ServiceRequest| ok(req.into_response(HttpResponse::Ok().finish())); let mw = DefaultHeaders::new() .add_content_type() .new_transform(srv.into_service()) diff --git a/src/middleware/err_handlers.rs b/src/middleware/err_handlers.rs index 07150f905..4673ed4ce 100644 --- a/src/middleware/err_handlers.rs +++ b/src/middleware/err_handlers.rs @@ -201,8 +201,7 @@ mod tests { .await .unwrap(); - let resp = - test::call_service(&mw, TestRequest::default().to_srv_request()).await; + let resp = test::call_service(&mw, TestRequest::default().to_srv_request()).await; assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0001"); } @@ -227,8 +226,7 @@ mod tests { .await .unwrap(); - let resp = - test::call_service(&mw, TestRequest::default().to_srv_request()).await; + let resp = test::call_service(&mw, TestRequest::default().to_srv_request()).await; assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0001"); } } diff --git a/src/middleware/logger.rs b/src/middleware/logger.rs index d4880b562..5b5b5577c 100644 --- a/src/middleware/logger.rs +++ b/src/middleware/logger.rs @@ -137,9 +137,9 @@ impl Logger { ) -> Self { let inner = Rc::get_mut(&mut self.0).unwrap(); - let ft = inner.format.0.iter_mut().find(|ft| { - matches!(ft, FormatText::CustomRequest(unit_label, _) if label == unit_label) - }); + let ft = inner.format.0.iter_mut().find( + |ft| matches!(ft, FormatText::CustomRequest(unit_label, _) if label == unit_label), + ); if let Some(FormatText::CustomRequest(_, request_fn)) = ft { // replace into None or previously registered fn using same label @@ -363,8 +363,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]|xi)|[atPrUsbTD]?)").unwrap(); let mut idx = 0; let mut results = Vec::new(); @@ -385,12 +384,12 @@ impl Format { unreachable!() } } - "i" => FormatText::RequestHeader( - HeaderName::try_from(key.as_str()).unwrap(), - ), - "o" => FormatText::ResponseHeader( - HeaderName::try_from(key.as_str()).unwrap(), - ), + "i" => { + FormatText::RequestHeader(HeaderName::try_from(key.as_str()).unwrap()) + } + "o" => { + FormatText::ResponseHeader(HeaderName::try_from(key.as_str()).unwrap()) + } "e" => FormatText::EnvironHeader(key.as_str().to_owned()), "xi" => FormatText::CustomRequest(key.as_str().to_owned(), None), _ => unreachable!(), @@ -533,9 +532,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("%Y-%m-%dT%H:%M:%S")), FormatText::RequestHeader(ref name) => { let s = if let Some(val) = req.headers().get(name) { if let Ok(s) = val.to_str() { @@ -557,8 +554,7 @@ impl FormatText { *self = s; } FormatText::RealIPRemoteAddr => { - let s = if let Some(remote) = req.connection_info().realip_remote_addr() - { + let s = if let Some(remote) = req.connection_info().realip_remote_addr() { FormatText::Str(remote.to_string()) } else { FormatText::Str("-".to_string()) @@ -629,8 +625,8 @@ mod tests { .finish(), )) }; - let logger = Logger::new("%% %{User-Agent}i %{X-Test}o %{HOME}e %D test") - .exclude_regex("\\w"); + let logger = + Logger::new("%% %{User-Agent}i %{X-Test}o %{HOME}e %D test").exclude_regex("\\w"); let srv = logger.new_transform(srv.into_service()).await.unwrap(); @@ -743,9 +739,7 @@ mod tests { let req = TestRequest::default() .insert_header(( header::FORWARDED, - header::HeaderValue::from_static( - "for=192.0.2.60;proto=http;by=203.0.113.43", - ), + header::HeaderValue::from_static("for=192.0.2.60;proto=http;by=203.0.113.43"), )) .to_srv_request(); diff --git a/src/request.rs b/src/request.rs index 3f4eb1038..d62fca300 100644 --- a/src/request.rs +++ b/src/request.rs @@ -175,11 +175,7 @@ impl HttpRequest { /// ); /// } /// ``` - pub fn url_for( - &self, - name: &str, - elements: U, - ) -> Result + pub fn url_for(&self, name: &str, elements: U) -> Result where U: IntoIterator, I: AsRef, @@ -577,30 +573,30 @@ mod tests { #[actix_rt::test] async fn test_data() { - let srv = init_service(App::new().app_data(10usize).service( - web::resource("/").to(|req: HttpRequest| { + let srv = init_service(App::new().app_data(10usize).service(web::resource("/").to( + |req: HttpRequest| { if req.app_data::().is_some() { HttpResponse::Ok() } else { HttpResponse::BadRequest() } - }), - )) + }, + ))) .await; let req = TestRequest::default().to_request(); let resp = call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::OK); - let srv = init_service(App::new().app_data(10u32).service( - web::resource("/").to(|req: HttpRequest| { + let srv = init_service(App::new().app_data(10u32).service(web::resource("/").to( + |req: HttpRequest| { if req.app_data::().is_some() { HttpResponse::Ok() } else { HttpResponse::BadRequest() } - }), - )) + }, + ))) .await; let req = TestRequest::default().to_request(); @@ -687,14 +683,14 @@ 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(move |req: HttpRequest| { + let srv = init_service(App::new().data(10u32).service(web::resource("/").to( + move |req: HttpRequest| { req.extensions_mut().insert(Foo { tracker: Rc::clone(&tracker2), }); HttpResponse::Ok() - }), - )) + }, + ))) .await; let req = TestRequest::default().to_request(); diff --git a/src/resource.rs b/src/resource.rs index 188e6fa43..944beeefa 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -358,9 +358,10 @@ where U::InitError: fmt::Debug, { // create and configure default resource - self.default = boxed::factory(f.into_factory().map_init_err(|e| { - log::error!("Can not construct default service: {:?}", e) - })); + self.default = boxed::factory( + f.into_factory() + .map_init_err(|e| log::error!("Can not construct default service: {:?}", e)), + ); self } @@ -437,8 +438,7 @@ impl ServiceFactory for ResourceFactory { let default_fut = self.default.new_service(()); // construct route service factory futures - let factory_fut = - join_all(self.routes.iter().map(|route| route.new_service(()))); + let factory_fut = join_all(self.routes.iter().map(|route| route.new_service(()))); let app_data = self.app_data.clone(); @@ -530,19 +530,18 @@ mod tests { #[actix_rt::test] async fn test_middleware() { - let srv = - init_service( - App::new().service( - web::resource("/test") - .name("test") - .wrap(DefaultHeaders::new().header( - header::CONTENT_TYPE, - HeaderValue::from_static("0001"), - )) - .route(web::get().to(HttpResponse::Ok)), - ), - ) - .await; + let srv = init_service( + App::new().service( + web::resource("/test") + .name("test") + .wrap( + DefaultHeaders::new() + .header(header::CONTENT_TYPE, HeaderValue::from_static("0001")), + ) + .route(web::get().to(HttpResponse::Ok)), + ), + ) + .await; let req = TestRequest::with_uri("/test").to_request(); let resp = call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::OK); @@ -584,12 +583,11 @@ mod tests { #[actix_rt::test] async fn test_to() { - let srv = - init_service(App::new().service(web::resource("/test").to(|| async { - sleep(Duration::from_millis(100)).await; - Ok::<_, Error>(HttpResponse::Ok()) - }))) - .await; + let srv = init_service(App::new().service(web::resource("/test").to(|| async { + sleep(Duration::from_millis(100)).await; + Ok::<_, Error>(HttpResponse::Ok()) + }))) + .await; let req = TestRequest::with_uri("/test").to_request(); let resp = call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::OK); diff --git a/src/responder.rs b/src/responder.rs index dcad45e0f..92945cdaa 100644 --- a/src/responder.rs +++ b/src/responder.rs @@ -262,9 +262,7 @@ pub(crate) mod tests { async fn test_option_responder() { let srv = init_service( App::new() - .service( - web::resource("/none").to(|| async { Option::<&'static str>::None }), - ) + .service(web::resource("/none").to(|| async { Option::<&'static str>::None })) .service(web::resource("/some").to(|| async { Some("some") })), ) .await; @@ -364,8 +362,7 @@ pub(crate) mod tests { ); // InternalError - let resp = - error::InternalError::new("err", StatusCode::BAD_REQUEST).respond_to(&req); + let resp = error::InternalError::new("err", StatusCode::BAD_REQUEST).respond_to(&req); assert_eq!(resp.status(), StatusCode::BAD_REQUEST); } @@ -382,9 +379,8 @@ pub(crate) mod tests { HeaderValue::from_static("text/plain; charset=utf-8") ); - let res = - Err::(error::InternalError::new("err", StatusCode::BAD_REQUEST)) - .respond_to(&req); + let res = Err::(error::InternalError::new("err", StatusCode::BAD_REQUEST)) + .respond_to(&req); assert_eq!(res.status(), StatusCode::BAD_REQUEST); } diff --git a/src/route.rs b/src/route.rs index c5e297411..b6b2482cd 100644 --- a/src/route.rs +++ b/src/route.rs @@ -238,12 +238,7 @@ where impl RouteNewService where - T: ServiceFactory< - ServiceRequest, - Config = (), - Response = ServiceResponse, - Error = Error, - >, + T: ServiceFactory, T::Future: 'static, T::Service: 'static, >::Future: 'static, @@ -255,12 +250,7 @@ where impl ServiceFactory for RouteNewService where - T: ServiceFactory< - ServiceRequest, - Config = (), - Response = ServiceResponse, - Error = Error, - >, + T: ServiceFactory, T::Future: 'static, T::Service: 'static, >::Future: 'static, diff --git a/src/scope.rs b/src/scope.rs index d08c78133..dd02501b0 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -8,8 +8,8 @@ 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, IntoServiceFactory, Service, ServiceFactory, ServiceFactoryExt, + Transform, }; use futures_core::future::LocalBoxFuture; use futures_util::future::join_all; @@ -476,16 +476,15 @@ impl ServiceFactory for ScopeFactory { let default_fut = self.default.new_service(()); // 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 factory_fut = factory.new_service(()); - async move { - let service = factory_fut.await?; - Ok((path, guards, service)) - } - })); + let factory_fut = join_all(self.services.iter().map(|(path, factory, guards)| { + let path = path.clone(); + let guards = guards.borrow_mut().take(); + let factory_fut = factory.new_service(()); + async move { + let service = factory_fut.await?; + Ok((path, guards, service)) + } + })); let app_data = self.app_data.clone(); @@ -589,10 +588,11 @@ mod tests { #[actix_rt::test] async fn test_scope() { - let srv = init_service(App::new().service( - web::scope("/app").service(web::resource("/path1").to(HttpResponse::Ok)), - )) - .await; + let srv = + init_service(App::new().service( + web::scope("/app").service(web::resource("/path1").to(HttpResponse::Ok)), + )) + .await; let req = TestRequest::with_uri("/app/path1").to_request(); let resp = srv.call(req).await.unwrap(); @@ -621,9 +621,10 @@ mod tests { #[actix_rt::test] async fn test_scope_root2() { - let srv = init_service(App::new().service( - web::scope("/app/").service(web::resource("").to(HttpResponse::Ok)), - )) + let srv = init_service( + App::new() + .service(web::scope("/app/").service(web::resource("").to(HttpResponse::Ok))), + ) .await; let req = TestRequest::with_uri("/app").to_request(); @@ -637,9 +638,10 @@ mod tests { #[actix_rt::test] async fn test_scope_root3() { - let srv = init_service(App::new().service( - web::scope("/app/").service(web::resource("/").to(HttpResponse::Ok)), - )) + let srv = init_service( + App::new() + .service(web::scope("/app/").service(web::resource("/").to(HttpResponse::Ok))), + ) .await; let req = TestRequest::with_uri("/app").to_request(); @@ -737,8 +739,7 @@ mod tests { async fn test_scope_variable_segment() { let srv = init_service(App::new().service(web::scope("/ab-{project}").service( web::resource("/path1").to(|r: HttpRequest| { - HttpResponse::Ok() - .body(format!("project: {}", &r.match_info()["project"])) + HttpResponse::Ok().body(format!("project: {}", &r.match_info()["project"])) }), ))) .await; @@ -945,14 +946,10 @@ mod tests { App::new().service( web::scope("app") .wrap( - DefaultHeaders::new().header( - header::CONTENT_TYPE, - HeaderValue::from_static("0001"), - ), + DefaultHeaders::new() + .header(header::CONTENT_TYPE, HeaderValue::from_static("0001")), ) - .service( - web::resource("/test").route(web::get().to(HttpResponse::Ok)), - ), + .service(web::resource("/test").route(web::get().to(HttpResponse::Ok))), ), ) .await; @@ -975,10 +972,8 @@ mod tests { let fut = srv.call(req); async move { let mut res = fut.await?; - res.headers_mut().insert( - header::CONTENT_TYPE, - HeaderValue::from_static("0001"), - ); + res.headers_mut() + .insert(header::CONTENT_TYPE, HeaderValue::from_static("0001")); Ok(res) } }) @@ -1083,9 +1078,8 @@ mod tests { s.route( "/", web::get().to(|req: HttpRequest| { - HttpResponse::Ok().body( - req.url_for("youtube", &["xxxxxx"]).unwrap().to_string(), - ) + HttpResponse::Ok() + .body(req.url_for("youtube", &["xxxxxx"]).unwrap().to_string()) }), ); })); diff --git a/src/server.rs b/src/server.rs index 69318e08d..d69d6570d 100644 --- a/src/server.rs +++ b/src/server.rs @@ -277,32 +277,28 @@ where }); 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)); + 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_timeout(c.client_timeout) - .local_addr(addr); + let 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 - }; + 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 + }; - svc.finish(map_config(factory(), move |_| { - AppConfig::new(false, addr, host.clone()) - })) - .tcp() - }, - )?; + svc.finish(map_config(factory(), move |_| { + AppConfig::new(false, addr, host.clone()) + })) + .tcp() + })?; Ok(self) } @@ -334,32 +330,30 @@ where 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)); + 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_timeout(c.client_timeout) - .client_disconnect(c.client_shutdown); + let svc = HttpService::build() + .keep_alive(c.keep_alive) + .client_timeout(c.client_timeout) + .client_disconnect(c.client_shutdown); - 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 svc = if let Some(handler) = on_connect_fn.clone() { + svc.on_connect_ext(move |io: &_, ext: _| { + (&*handler)(io as &dyn Any, ext) + }) + } else { + svc + }; - svc.finish(map_config(factory(), move |_| { - AppConfig::new(true, addr, host.clone()) - })) - .openssl(acceptor.clone()) - }, - )?; + svc.finish(map_config(factory(), move |_| { + AppConfig::new(true, addr, host.clone()) + })) + .openssl(acceptor.clone()) + })?; Ok(self) } @@ -391,32 +385,28 @@ where 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)); + 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_timeout(c.client_timeout) - .client_disconnect(c.client_shutdown); + let svc = HttpService::build() + .keep_alive(c.keep_alive) + .client_timeout(c.client_timeout) + .client_disconnect(c.client_shutdown); - 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 svc = if let Some(handler) = on_connect_fn.clone() { + svc.on_connect_ext(move |io: &_, ext: _| (handler)(io as &dyn Any, ext)) + } else { + svc + }; - svc.finish(map_config(factory(), move |_| { - AppConfig::new(true, addr, host.clone()) - })) - .rustls(config.clone()) - }, - )?; + svc.finish(map_config(factory(), move |_| { + AppConfig::new(true, addr, host.clone()) + })) + .rustls(config.clone()) + })?; Ok(self) } @@ -433,10 +423,7 @@ where Ok(self) } - fn bind2( - &self, - addr: A, - ) -> io::Result> { + fn bind2(&self, addr: A) -> io::Result> { let mut err = None; let mut success = false; let mut sockets = Vec::new(); @@ -469,11 +456,7 @@ where /// Start listening for incoming tls connections. /// /// This method sets alpn protocols to "h2" and "http/1.1" - pub fn bind_openssl( - mut self, - addr: A, - builder: SslAcceptorBuilder, - ) -> io::Result + pub fn bind_openssl(mut self, addr: A, builder: SslAcceptorBuilder) -> io::Result where A: net::ToSocketAddrs, { @@ -505,18 +488,13 @@ where #[cfg(unix)] /// Start listening for unix domain (UDS) connections on existing listener. - pub fn listen_uds( - mut self, - lst: std::os::unix::net::UnixListener, - ) -> io::Result { + pub fn listen_uds(mut self, lst: std::os::unix::net::UnixListener) -> io::Result { use actix_rt::net::UnixStream; 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, - ); + 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, @@ -533,23 +511,19 @@ where c.host.clone().unwrap_or_else(|| format!("{}", socket_addr)), ); - pipeline_factory(|io: UnixStream| ok((io, Protocol::Http1, None))).and_then( - { - let svc = HttpService::build() - .keep_alive(c.keep_alive) - .client_timeout(c.client_timeout); + pipeline_factory(|io: UnixStream| ok((io, Protocol::Http1, None))).and_then({ + let 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 - }; + 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 + }; - svc.finish(map_config(factory(), move |_| config.clone())) - }, - ) + svc.finish(map_config(factory(), move |_| config.clone())) + }) })?; Ok(self) } @@ -564,10 +538,8 @@ where 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, - ); + 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, @@ -583,13 +555,12 @@ where socket_addr, c.host.clone().unwrap_or_else(|| format!("{}", socket_addr)), ); - pipeline_factory(|io: UnixStream| 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())), - ) + pipeline_factory(|io: UnixStream| 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())), + ) }, )?; Ok(self) @@ -633,10 +604,7 @@ where } } -fn create_tcp_listener( - addr: net::SocketAddr, - backlog: u32, -) -> io::Result { +fn create_tcp_listener(addr: net::SocketAddr, backlog: u32) -> io::Result { use socket2::{Domain, Protocol, Socket, Type}; let domain = match addr { net::SocketAddr::V4(_) => Domain::ipv4(), diff --git a/src/service.rs b/src/service.rs index a7fb12848..fcbe61a02 100644 --- a/src/service.rs +++ b/src/service.rs @@ -5,8 +5,7 @@ use std::{fmt, net}; use actix_http::body::{Body, MessageBody, ResponseBody}; use actix_http::http::{HeaderMap, Method, StatusCode, Uri, Version}; use actix_http::{ - Error, Extensions, HttpMessage, Payload, PayloadStream, RequestHead, Response, - ResponseHead, + Error, Extensions, HttpMessage, Payload, PayloadStream, RequestHead, Response, ResponseHead, }; use actix_router::{IntoPattern, Path, Resource, ResourceDef, Url}; use actix_service::{IntoServiceFactory, ServiceFactory}; @@ -635,20 +634,18 @@ mod tests { #[actix_rt::test] async fn test_service_data() { - let srv = init_service( - App::new() - .data(42u32) - .service(web::service("/test").name("test").finish( - |req: ServiceRequest| { - assert_eq!( - req.app_data::>().unwrap().as_ref(), - &42 - ); - ok(req.into_response(HttpResponse::Ok().finish())) - }, - )), - ) - .await; + let srv = + init_service( + App::new() + .data(42u32) + .service(web::service("/test").name("test").finish( + |req: ServiceRequest| { + assert_eq!(req.app_data::>().unwrap().as_ref(), &42); + ok(req.into_response(HttpResponse::Ok().finish())) + }, + )), + ) + .await; let req = TestRequest::with_uri("/test").to_request(); let resp = srv.call(req).await.unwrap(); assert_eq!(resp.status(), http::StatusCode::OK); diff --git a/src/test.rs b/src/test.rs index c03f078ef..99602eb11 100644 --- a/src/test.rs +++ b/src/test.rs @@ -12,9 +12,7 @@ use actix_http::test::TestRequest as HttpTestRequest; use actix_http::{cookie::Cookie, ws, Extensions, HttpService, Request}; use actix_router::{Path, ResourceDef, Url}; use actix_rt::{time::sleep, System}; -use actix_service::{ - map_config, IntoService, IntoServiceFactory, Service, ServiceFactory, -}; +use actix_service::{map_config, IntoService, IntoServiceFactory, Service, ServiceFactory}; use awc::error::PayloadError; use awc::{Client, ClientRequest, ClientResponse, Connector}; use bytes::{Bytes, BytesMut}; @@ -78,12 +76,7 @@ pub async fn init_service( ) -> impl Service, Error = E> where R: IntoServiceFactory, - S: ServiceFactory< - Request, - Config = AppConfig, - Response = ServiceResponse, - Error = E, - >, + S: ServiceFactory, Error = E>, S::InitError: std::fmt::Debug, { try_init_service(app) @@ -97,12 +90,7 @@ pub(crate) async fn try_init_service( ) -> Result, Error = E>, S::InitError> where R: IntoServiceFactory, - S: ServiceFactory< - Request, - Config = AppConfig, - Response = ServiceResponse, - Error = E, - >, + S: ServiceFactory, Error = E>, S::InitError: std::fmt::Debug, { let srv = app.into_factory(); @@ -264,9 +252,8 @@ where { let body = read_body(res).await; - serde_json::from_slice(&body).unwrap_or_else(|e| { - panic!("read_response_json failed during deserialization: {}", e) - }) + serde_json::from_slice(&body) + .unwrap_or_else(|e| panic!("read_response_json failed during deserialization: {}", e)) } pub async fn load_stream(mut stream: S) -> Result @@ -487,8 +474,7 @@ impl TestRequest { /// 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"); + 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 @@ -528,8 +514,7 @@ impl TestRequest { 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 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)), @@ -548,8 +533,7 @@ impl TestRequest { 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 app_state = AppInitServiceState::new(Rc::new(self.rmap), self.config.clone()); HttpRequest::new(self.path, head, app_state, Rc::new(self.app_data)) } @@ -560,8 +544,7 @@ impl TestRequest { 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 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)); @@ -678,24 +661,21 @@ where let srv = match cfg.stream { StreamType::Tcp => match cfg.tp { HttpVer::Http1 => builder.listen("test", tcp, move || { - let cfg = - AppConfig::new(false, local_addr, format!("{}", local_addr)); + let cfg = AppConfig::new(false, local_addr, format!("{}", local_addr)); HttpService::build() .client_timeout(ctimeout) .h1(map_config(factory(), move |_| cfg.clone())) .tcp() }), HttpVer::Http2 => builder.listen("test", tcp, move || { - let cfg = - AppConfig::new(false, local_addr, format!("{}", local_addr)); + let cfg = AppConfig::new(false, local_addr, format!("{}", local_addr)); HttpService::build() .client_timeout(ctimeout) .h2(map_config(factory(), move |_| cfg.clone())) .tcp() }), HttpVer::Both => builder.listen("test", tcp, move || { - let cfg = - AppConfig::new(false, local_addr, format!("{}", local_addr)); + let cfg = AppConfig::new(false, local_addr, format!("{}", local_addr)); HttpService::build() .client_timeout(ctimeout) .finish(map_config(factory(), move |_| cfg.clone())) @@ -705,24 +685,21 @@ where #[cfg(feature = "openssl")] StreamType::Openssl(acceptor) => match cfg.tp { HttpVer::Http1 => builder.listen("test", tcp, move || { - let cfg = - AppConfig::new(true, local_addr, format!("{}", local_addr)); + let cfg = AppConfig::new(true, local_addr, format!("{}", local_addr)); HttpService::build() .client_timeout(ctimeout) .h1(map_config(factory(), move |_| cfg.clone())) .openssl(acceptor.clone()) }), HttpVer::Http2 => builder.listen("test", tcp, move || { - let cfg = - AppConfig::new(true, local_addr, format!("{}", local_addr)); + let cfg = AppConfig::new(true, local_addr, format!("{}", local_addr)); HttpService::build() .client_timeout(ctimeout) .h2(map_config(factory(), move |_| cfg.clone())) .openssl(acceptor.clone()) }), HttpVer::Both => builder.listen("test", tcp, move || { - let cfg = - AppConfig::new(true, local_addr, format!("{}", local_addr)); + let cfg = AppConfig::new(true, local_addr, format!("{}", local_addr)); HttpService::build() .client_timeout(ctimeout) .finish(map_config(factory(), move |_| cfg.clone())) @@ -732,24 +709,21 @@ where #[cfg(feature = "rustls")] StreamType::Rustls(config) => match cfg.tp { HttpVer::Http1 => builder.listen("test", tcp, move || { - let cfg = - AppConfig::new(true, local_addr, format!("{}", local_addr)); + let cfg = AppConfig::new(true, local_addr, format!("{}", local_addr)); HttpService::build() .client_timeout(ctimeout) .h1(map_config(factory(), move |_| cfg.clone())) .rustls(config.clone()) }), HttpVer::Http2 => builder.listen("test", tcp, move || { - let cfg = - AppConfig::new(true, local_addr, format!("{}", local_addr)); + let cfg = AppConfig::new(true, local_addr, format!("{}", local_addr)); HttpService::build() .client_timeout(ctimeout) .h2(map_config(factory(), move |_| cfg.clone())) .rustls(config.clone()) }), HttpVer::Both => builder.listen("test", tcp, move || { - let cfg = - AppConfig::new(true, local_addr, format!("{}", local_addr)); + let cfg = AppConfig::new(true, local_addr, format!("{}", local_addr)); HttpService::build() .client_timeout(ctimeout) .finish(map_config(factory(), move |_| cfg.clone())) @@ -887,8 +861,7 @@ impl TestServerConfig { /// Get first available unused address pub fn unused_addr() -> net::SocketAddr { let addr: net::SocketAddr = "127.0.0.1:0".parse().unwrap(); - let socket = - Socket::new(Domain::ipv4(), Type::stream(), Some(Protocol::tcp())).unwrap(); + let socket = Socket::new(Domain::ipv4(), Type::stream(), Some(Protocol::tcp())).unwrap(); socket.bind(&addr.into()).unwrap(); socket.set_reuse_address(true).unwrap(); let tcp = socket.into_tcp_listener(); @@ -975,8 +948,7 @@ impl TestServer { pub async fn ws_at( &mut self, path: &str, - ) -> Result, awc::error::WsClientError> - { + ) -> Result, awc::error::WsClientError> { let url = self.url(path); let connect = self.client.ws(url).connect(); connect.await.map(|(_, framed)| framed) @@ -985,8 +957,7 @@ impl TestServer { /// Connect to a websocket server pub async fn ws( &mut self, - ) -> Result, awc::error::WsClientError> - { + ) -> Result, awc::error::WsClientError> { self.ws_at("/").await } @@ -1218,10 +1189,9 @@ mod tests { } } - let app = init_service( - App::new().service(web::resource("/index.html").to(async_with_block)), - ) - .await; + 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(); diff --git a/src/types/either.rs b/src/types/either.rs index d72a14fd0..bbab48dec 100644 --- a/src/types/either.rs +++ b/src/types/either.rs @@ -276,13 +276,12 @@ mod tests { .set_payload(Bytes::from_static(b"!@$%^&*()")) .to_http_parts(); - let payload = - Either::, Json>, Bytes>::from_request( - &req, &mut pl, - ) - .await - .unwrap() - .unwrap_right(); + let payload = Either::, Json>, Bytes>::from_request( + &req, &mut pl, + ) + .await + .unwrap() + .unwrap_right(); assert_eq!(&payload.as_ref(), &b"!@$%^&*()"); } @@ -294,15 +293,14 @@ mod tests { }) .to_http_parts(); - let form = - Either::, Json>, Bytes>::from_request( - &req, &mut pl, - ) - .await - .unwrap() - .unwrap_left() - .unwrap_right() - .into_inner(); + let form = Either::, Json>, Bytes>::from_request( + &req, &mut pl, + ) + .await + .unwrap() + .unwrap_left() + .unwrap_right() + .into_inner(); assert_eq!(&form.hello, "world"); } } diff --git a/src/types/form.rs b/src/types/form.rs index 96e09ee1c..0b5c3c1b4 100644 --- a/src/types/form.rs +++ b/src/types/form.rs @@ -21,8 +21,8 @@ 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, + error::UrlencodedError, extract::FromRequest, http::header::CONTENT_LENGTH, web, Error, + HttpMessage, HttpRequest, HttpResponse, Responder, }; /// URL encoded payload extractor and responder. @@ -342,16 +342,14 @@ where } if encoding == UTF_8 { - serde_urlencoded::from_bytes::(&body) - .map_err(|_| UrlencodedError::Parse) + serde_urlencoded::from_bytes::(&body).map_err(|_| UrlencodedError::Parse) } else { let body = encoding .decode_without_bom_handling_and_without_replacement(&body) .map(|s| s.into_owned()) .ok_or(UrlencodedError::Parse)?; - serde_urlencoded::from_str::(&body) - .map_err(|_| UrlencodedError::Parse) + serde_urlencoded::from_str::(&body).map_err(|_| UrlencodedError::Parse) } } .boxed_local(), diff --git a/src/types/json.rs b/src/types/json.rs index 95d8fd4bb..28960402a 100644 --- a/src/types/json.rs +++ b/src/types/json.rs @@ -482,8 +482,8 @@ mod tests { let msg = MyObject { name: "invalid request".to_string(), }; - let resp = HttpResponse::BadRequest() - .body(serde_json::to_string(&msg).unwrap()); + let resp = + HttpResponse::BadRequest().body(serde_json::to_string(&msg).unwrap()); InternalError::from_response(err, resp).into() })) .to_http_parts(); diff --git a/src/types/path.rs b/src/types/path.rs index a44a3ca9e..4ab124d53 100644 --- a/src/types/path.rs +++ b/src/types/path.rs @@ -235,11 +235,9 @@ mod tests { assert_eq!(res.1, "user1"); let (Path(a), Path(b)) = - <(Path<(String, String)>, Path<(String, String)>)>::from_request( - &req, &mut pl, - ) - .await - .unwrap(); + <(Path<(String, String)>, Path<(String, String)>)>::from_request(&req, &mut pl) + .await + .unwrap(); assert_eq!(a.0, "name"); assert_eq!(a.1, "user1"); assert_eq!(b.0, "name"); @@ -300,11 +298,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() + error::InternalError::from_response(err, HttpResponse::Conflict().finish()) + .into() })) .to_http_parts(); diff --git a/src/types/payload.rs b/src/types/payload.rs index ec91eb44c..781347b84 100644 --- a/src/types/payload.rs +++ b/src/types/payload.rs @@ -55,10 +55,7 @@ impl Stream for Payload { type Item = Result; #[inline] - fn poll_next( - mut self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll> { + fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { Pin::new(&mut self.0).poll_next(cx) } } @@ -396,9 +393,7 @@ mod tests { App::new() .service( web::resource("/bytes-app-data") - .app_data( - PayloadConfig::default().mimetype(mime::APPLICATION_JSON), - ) + .app_data(PayloadConfig::default().mimetype(mime::APPLICATION_JSON)) .route(web::get().to(bytes_handler)), ) .service( @@ -408,9 +403,7 @@ mod tests { ) .service( web::resource("/string-app-data") - .app_data( - PayloadConfig::default().mimetype(mime::APPLICATION_JSON), - ) + .app_data(PayloadConfig::default().mimetype(mime::APPLICATION_JSON)) .route(web::get().to(string_handler)), ) .service( diff --git a/src/types/query.rs b/src/types/query.rs index 13b331845..691a4792b 100644 --- a/src/types/query.rs +++ b/src/types/query.rs @@ -167,8 +167,7 @@ where /// ``` #[derive(Clone)] pub struct QueryConfig { - err_handler: - Option Error + Send + Sync>>, + err_handler: Option Error + Send + Sync>>, } impl QueryConfig { diff --git a/src/types/readlines.rs b/src/types/readlines.rs index 01aab64ab..b8bdcc504 100644 --- a/src/types/readlines.rs +++ b/src/types/readlines.rs @@ -74,10 +74,7 @@ where { 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(); if let Some(err) = this.err.take() { diff --git a/tests/test_httpserver.rs b/tests/test_httpserver.rs index 07c171674..34d57008d 100644 --- a/tests/test_httpserver.rs +++ b/tests/test_httpserver.rs @@ -23,8 +23,7 @@ async fn test_start() { sys.block_on(async { let srv = HttpServer::new(|| { App::new().service( - web::resource("/") - .route(web::to(|| HttpResponse::Ok().body("test"))), + web::resource("/").route(web::to(|| HttpResponse::Ok().body("test"))), ) }) .workers(1) diff --git a/tests/test_server.rs b/tests/test_server.rs index c626474cf..c2caa9eb2 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -11,8 +11,7 @@ use std::{ }; use actix_http::http::header::{ - ContentEncoding, ACCEPT_ENCODING, CONTENT_ENCODING, CONTENT_LENGTH, - TRANSFER_ENCODING, + ContentEncoding, ACCEPT_ENCODING, CONTENT_ENCODING, CONTENT_LENGTH, TRANSFER_ENCODING, }; use brotli2::write::{BrotliDecoder, BrotliEncoder}; use bytes::Bytes; @@ -69,14 +68,10 @@ impl TestBody { impl futures_core::stream::Stream for TestBody { 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> { ready!(Pin::new(&mut self.delay).poll(cx)); - self.delay = - Box::pin(actix_rt::time::sleep(std::time::Duration::from_millis(10))); + self.delay = Box::pin(actix_rt::time::sleep(std::time::Duration::from_millis(10))); let chunk_size = std::cmp::min(self.chunk_size, self.data.len()); let chunk = self.data.split_to(chunk_size); if chunk.is_empty() { @@ -90,8 +85,7 @@ impl futures_core::stream::Stream for TestBody { #[actix_rt::test] async fn test_body() { let srv = test::start(|| { - App::new() - .service(web::resource("/").route(web::to(|| HttpResponse::Ok().body(STR)))) + App::new().service(web::resource("/").route(web::to(|| HttpResponse::Ok().body(STR)))) }); let mut response = srv.get("/").send().await.unwrap(); @@ -328,12 +322,12 @@ async fn test_body_chunked_implicit() { #[actix_rt::test] async fn test_body_br_streaming() { let srv = test::start_with(test::config().h1(), || { - App::new().wrap(Compress::new(ContentEncoding::Br)).service( - web::resource("/").route(web::to(move || { + App::new() + .wrap(Compress::new(ContentEncoding::Br)) + .service(web::resource("/").route(web::to(move || { HttpResponse::Ok() .streaming(TestBody::new(Bytes::from_static(STR.as_ref()), 24)) - })), - ) + }))) }); let mut response = srv @@ -361,8 +355,7 @@ async fn test_body_br_streaming() { async fn test_head_binary() { let srv = test::start_with(test::config().h1(), || { App::new().service( - web::resource("/") - .route(web::head().to(move || HttpResponse::Ok().body(STR))), + web::resource("/").route(web::head().to(move || HttpResponse::Ok().body(STR))), ) }); @@ -403,9 +396,7 @@ async fn test_body_deflate() { let srv = test::start_with(test::config().h1(), || { App::new() .wrap(Compress::new(ContentEncoding::Deflate)) - .service( - web::resource("/").route(web::to(move || HttpResponse::Ok().body(STR))), - ) + .service(web::resource("/").route(web::to(move || HttpResponse::Ok().body(STR)))) }); // client request @@ -430,9 +421,9 @@ async fn test_body_deflate() { #[actix_rt::test] async fn test_body_brotli() { let srv = test::start_with(test::config().h1(), || { - App::new().wrap(Compress::new(ContentEncoding::Br)).service( - web::resource("/").route(web::to(move || HttpResponse::Ok().body(STR))), - ) + App::new() + .wrap(Compress::new(ContentEncoding::Br)) + .service(web::resource("/").route(web::to(move || HttpResponse::Ok().body(STR)))) }); // client request @@ -459,8 +450,7 @@ async fn test_body_brotli() { async fn test_encoding() { let srv = test::start_with(test::config().h1(), || { App::new().wrap(Compress::default()).service( - web::resource("/") - .route(web::to(move |body: Bytes| HttpResponse::Ok().body(body))), + web::resource("/").route(web::to(move |body: Bytes| HttpResponse::Ok().body(body))), ) }); @@ -485,8 +475,7 @@ async fn test_encoding() { async fn test_gzip_encoding() { let srv = test::start_with(test::config().h1(), || { App::new().service( - web::resource("/") - .route(web::to(move |body: Bytes| HttpResponse::Ok().body(body))), + web::resource("/").route(web::to(move |body: Bytes| HttpResponse::Ok().body(body))), ) }); @@ -512,8 +501,7 @@ async fn test_gzip_encoding_large() { let data = STR.repeat(10); let srv = test::start_with(test::config().h1(), || { App::new().service( - web::resource("/") - .route(web::to(move |body: Bytes| HttpResponse::Ok().body(body))), + web::resource("/").route(web::to(move |body: Bytes| HttpResponse::Ok().body(body))), ) }); @@ -544,8 +532,7 @@ async fn test_reading_gzip_encoding_large_random() { let srv = test::start_with(test::config().h1(), || { App::new().service( - web::resource("/") - .route(web::to(move |body: Bytes| HttpResponse::Ok().body(body))), + web::resource("/").route(web::to(move |body: Bytes| HttpResponse::Ok().body(body))), ) }); @@ -571,8 +558,7 @@ async fn test_reading_gzip_encoding_large_random() { async fn test_reading_deflate_encoding() { let srv = test::start_with(test::config().h1(), || { App::new().service( - web::resource("/") - .route(web::to(move |body: Bytes| HttpResponse::Ok().body(body))), + web::resource("/").route(web::to(move |body: Bytes| HttpResponse::Ok().body(body))), ) }); @@ -598,8 +584,7 @@ async fn test_reading_deflate_encoding_large() { let data = STR.repeat(10); let srv = test::start_with(test::config().h1(), || { App::new().service( - web::resource("/") - .route(web::to(move |body: Bytes| HttpResponse::Ok().body(body))), + web::resource("/").route(web::to(move |body: Bytes| HttpResponse::Ok().body(body))), ) }); @@ -630,8 +615,7 @@ async fn test_reading_deflate_encoding_large_random() { let srv = test::start_with(test::config().h1(), || { App::new().service( - web::resource("/") - .route(web::to(move |body: Bytes| HttpResponse::Ok().body(body))), + web::resource("/").route(web::to(move |body: Bytes| HttpResponse::Ok().body(body))), ) }); @@ -657,8 +641,7 @@ async fn test_reading_deflate_encoding_large_random() { async fn test_brotli_encoding() { let srv = test::start_with(test::config().h1(), || { App::new().service( - web::resource("/") - .route(web::to(move |body: Bytes| HttpResponse::Ok().body(body))), + web::resource("/").route(web::to(move |body: Bytes| HttpResponse::Ok().body(body))), ) }); @@ -883,9 +866,7 @@ async fn test_normalize() { let srv = test::start_with(test::config().h1(), || { App::new() .wrap(NormalizePath::new(TrailingSlash::Trim)) - .service( - web::resource("/one").route(web::to(|| HttpResponse::Ok().finish())), - ) + .service(web::resource("/one").route(web::to(|| HttpResponse::Ok().finish()))) }); let response = srv.get("/one/").send().await.unwrap(); From 81bef93e5ee26547681c2c20599648ee2abfb25d Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Fri, 12 Feb 2021 00:15:25 +0000 Subject: [PATCH 032/339] add time parser year shift tests --- Cargo.toml | 3 ++ actix-http/src/encoding/decoder.rs | 61 ++++++++++++++++++---------- actix-http/src/encoding/encoder.rs | 37 +++++++++++------ actix-http/src/message.rs | 2 + actix-http/src/time_parser.rs | 64 ++++++++++++++++++++++-------- 5 files changed, 116 insertions(+), 51 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 3cbd1f358..569d1f8c2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -116,6 +116,9 @@ brotli2 = "0.3.2" flate2 = "1.0.13" criterion = "0.3" +[profile.dev] +debug = false + [profile.release] lto = true opt-level = 3 diff --git a/actix-http/src/encoding/decoder.rs b/actix-http/src/encoding/decoder.rs index e16d42288..f0abae865 100644 --- a/actix-http/src/encoding/decoder.rs +++ b/actix-http/src/encoding/decoder.rs @@ -1,7 +1,11 @@ //! Stream decoders. -use std::task::{Context, Poll}; -use std::{future::Future, io, io::Write as _, pin::Pin}; +use std::{ + future::Future, + io::{self, Write as _}, + pin::Pin, + task::{Context, Poll}, +}; use actix_rt::task::{spawn_blocking, JoinHandle}; use brotli2::write::BrotliDecoder; @@ -9,11 +13,13 @@ use bytes::Bytes; use flate2::write::{GzDecoder, ZlibDecoder}; use futures_core::{ready, Stream}; -use super::Writer; -use crate::error::{BlockingError, PayloadError}; -use crate::http::header::{ContentEncoding, HeaderMap, CONTENT_ENCODING}; +use crate::{ + encoding::Writer, + error::{BlockingError, PayloadError}, + http::header::{ContentEncoding, HeaderMap, CONTENT_ENCODING}, +}; -const INPLACE: usize = 2049; +const MAX_CHUNK_SIZE_DECODE_IN_PLACE: usize = 2049; pub struct Decoder { decoder: Option, @@ -41,6 +47,7 @@ where ))), _ => None, }; + Decoder { decoder, stream, @@ -53,15 +60,11 @@ where #[inline] pub fn from_headers(stream: S, headers: &HeaderMap) -> Decoder { // check content-encoding - let encoding = if let Some(enc) = headers.get(&CONTENT_ENCODING) { - if let Ok(enc) = enc.to_str() { - ContentEncoding::from(enc) - } else { - ContentEncoding::Identity - } - } else { - ContentEncoding::Identity - }; + let encoding = headers + .get(&CONTENT_ENCODING) + .and_then(|val| val.to_str().ok()) + .map(ContentEncoding::from) + .unwrap_or(ContentEncoding::Identity); Self::new(stream, encoding) } @@ -81,8 +84,10 @@ where if let Some(ref mut fut) = self.fut { let (chunk, decoder) = ready!(Pin::new(fut).poll(cx)).map_err(|_| BlockingError)??; + self.decoder = Some(decoder); self.fut.take(); + if let Some(chunk) = chunk { return Poll::Ready(Some(Ok(chunk))); } @@ -92,13 +97,15 @@ where return Poll::Ready(None); } - match Pin::new(&mut self.stream).poll_next(cx) { - Poll::Ready(Some(Err(err))) => return Poll::Ready(Some(Err(err))), - Poll::Ready(Some(Ok(chunk))) => { + match ready!(Pin::new(&mut self.stream).poll_next(cx)) { + Some(Err(err)) => return Poll::Ready(Some(Err(err))), + + Some(Ok(chunk)) => { if let Some(mut decoder) = self.decoder.take() { - if chunk.len() < INPLACE { + if chunk.len() < MAX_CHUNK_SIZE_DECODE_IN_PLACE { let chunk = decoder.feed_data(chunk)?; self.decoder = Some(decoder); + if let Some(chunk) = chunk { return Poll::Ready(Some(Ok(chunk))); } @@ -108,13 +115,16 @@ where Ok((chunk, decoder)) })); } + continue; } else { return Poll::Ready(Some(Ok(chunk))); } } - Poll::Ready(None) => { + + None => { self.eof = true; + return if let Some(mut decoder) = self.decoder.take() { match decoder.feed_eof() { Ok(Some(res)) => Poll::Ready(Some(Ok(res))), @@ -125,10 +135,8 @@ where Poll::Ready(None) }; } - Poll::Pending => break, } } - Poll::Pending } } @@ -144,6 +152,7 @@ impl ContentDecoder { ContentDecoder::Br(ref mut decoder) => match decoder.flush() { Ok(()) => { let b = decoder.get_mut().take(); + if !b.is_empty() { Ok(Some(b)) } else { @@ -152,9 +161,11 @@ impl ContentDecoder { } Err(e) => Err(e), }, + ContentDecoder::Gzip(ref mut decoder) => match decoder.try_finish() { Ok(_) => { let b = decoder.get_mut().take(); + if !b.is_empty() { Ok(Some(b)) } else { @@ -163,6 +174,7 @@ impl ContentDecoder { } Err(e) => Err(e), }, + ContentDecoder::Deflate(ref mut decoder) => match decoder.try_finish() { Ok(_) => { let b = decoder.get_mut().take(); @@ -183,6 +195,7 @@ impl ContentDecoder { Ok(_) => { decoder.flush()?; let b = decoder.get_mut().take(); + if !b.is_empty() { Ok(Some(b)) } else { @@ -191,10 +204,12 @@ impl ContentDecoder { } Err(e) => Err(e), }, + ContentDecoder::Gzip(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 { @@ -203,9 +218,11 @@ impl ContentDecoder { } Err(e) => Err(e), }, + ContentDecoder::Deflate(ref mut decoder) => match decoder.write_all(&data) { Ok(_) => { decoder.flush()?; + let b = decoder.get_mut().take(); if !b.is_empty() { Ok(Some(b)) diff --git a/actix-http/src/encoding/encoder.rs b/actix-http/src/encoding/encoder.rs index 3a413a150..366ecb8c4 100644 --- a/actix-http/src/encoding/encoder.rs +++ b/actix-http/src/encoding/encoder.rs @@ -1,7 +1,11 @@ //! Stream encoders. -use std::task::{Context, Poll}; -use std::{future::Future, io, io::Write as _, pin::Pin}; +use std::{ + future::Future, + io::{self, Write as _}, + pin::Pin, + task::{Context, Poll}, +}; use actix_rt::task::{spawn_blocking, JoinHandle}; use brotli2::write::BrotliEncoder; @@ -10,15 +14,19 @@ use flate2::write::{GzEncoder, ZlibEncoder}; use futures_core::ready; use pin_project::pin_project; -use crate::body::{Body, BodySize, MessageBody, ResponseBody}; -use crate::http::header::{ContentEncoding, CONTENT_ENCODING}; -use crate::http::{HeaderValue, StatusCode}; -use crate::{Error, ResponseHead}; +use crate::{ + body::{Body, BodySize, MessageBody, ResponseBody}, + http::{ + header::{ContentEncoding, CONTENT_ENCODING}, + HeaderValue, StatusCode, + }, + Error, ResponseHead, +}; use super::Writer; use crate::error::BlockingError; -const INPLACE: usize = 1024; +const MAX_CHUNK_SIZE_ENCODE_IN_PLACE: usize = 1024; #[pin_project] pub struct Encoder { @@ -137,23 +145,28 @@ impl MessageBody for Encoder { if let Some(ref mut fut) = this.fut { let mut encoder = ready!(Pin::new(fut).poll(cx)).map_err(|_| BlockingError)??; + let chunk = encoder.take(); *this.encoder = Some(encoder); this.fut.take(); + if !chunk.is_empty() { return Poll::Ready(Some(Ok(chunk))); } } - let result = this.body.as_mut().poll_next(cx); + let result = ready!(this.body.as_mut().poll_next(cx)); match result { - Poll::Ready(Some(Ok(chunk))) => { + Some(Err(err)) => return Poll::Ready(Some(Err(err))), + + Some(Ok(chunk)) => { if let Some(mut encoder) = this.encoder.take() { - if chunk.len() < INPLACE { + if chunk.len() < MAX_CHUNK_SIZE_ENCODE_IN_PLACE { encoder.write(&chunk)?; let chunk = encoder.take(); *this.encoder = Some(encoder); + if !chunk.is_empty() { return Poll::Ready(Some(Ok(chunk))); } @@ -167,7 +180,8 @@ impl MessageBody for Encoder { return Poll::Ready(Some(Ok(chunk))); } } - Poll::Ready(None) => { + + None => { if let Some(encoder) = this.encoder.take() { let chunk = encoder.finish()?; if chunk.is_empty() { @@ -180,7 +194,6 @@ impl MessageBody for Encoder { return Poll::Ready(None); } } - val => return val, } } } diff --git a/actix-http/src/message.rs b/actix-http/src/message.rs index 736f35ee1..6438ccba0 100644 --- a/actix-http/src/message.rs +++ b/actix-http/src/message.rs @@ -13,8 +13,10 @@ use crate::http::{header, Method, StatusCode, Uri, Version}; pub enum ConnectionType { /// Close connection after response Close, + /// Keep connection alive after response KeepAlive, + /// Connection is upgraded to different type Upgrade, } diff --git a/actix-http/src/time_parser.rs b/actix-http/src/time_parser.rs index 0d06a5867..46bf73037 100644 --- a/actix-http/src/time_parser.rs +++ b/actix-http/src/time_parser.rs @@ -8,35 +8,65 @@ pub fn parse_http_date(time: &str) -> Option { } /// 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 { - match PrimitiveDateTime::parse(time, "%A, %d-%b-%y %H:%M:%S") { - Ok(dt) => { - // 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(); + let dt = PrimitiveDateTime::parse(time, "%A, %d-%b-%y %H:%M:%S").ok()?; - if expanded_year > now.year() + 50 { - expanded_year -= 100; - } + // 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. - match Date::try_from_ymd(expanded_year, dt.month(), dt.day()) { - Ok(date) => Some(PrimitiveDateTime::new(date, dt.time())), - Err(_) => None, - } - } - Err(_) => None, + 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))); + } +} From 4fc7d7675964b8451ab17aea3a8998e3db64e87e Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Fri, 12 Feb 2021 00:27:20 +0000 Subject: [PATCH 033/339] s/websocket/WebSocket in docs --- actix-http-test/src/lib.rs | 4 ++-- actix-http/src/h1/decoder.rs | 2 +- actix-http/src/ws/codec.rs | 4 ++-- actix-http/src/ws/frame.rs | 2 +- actix-http/src/ws/mod.rs | 16 ++++++++-------- actix-http/src/ws/proto.rs | 3 +-- actix-web-actors/src/ws.rs | 10 +++++----- awc/src/ws.rs | 10 +++++----- awc/tests/test_ws.rs | 2 +- src/test.rs | 4 ++-- 10 files changed, 28 insertions(+), 29 deletions(-) diff --git a/actix-http-test/src/lib.rs b/actix-http-test/src/lib.rs index 89cb0777f..df5774998 100644 --- a/actix-http-test/src/lib.rs +++ b/actix-http-test/src/lib.rs @@ -243,7 +243,7 @@ impl TestServer { response.body().limit(10_485_760).await } - /// Connect to websocket server at a given path + /// Connect to WebSocket server at a given path. pub async fn ws_at( &mut self, path: &str, @@ -253,7 +253,7 @@ impl TestServer { connect.await.map(|(_, framed)| framed) } - /// Connect to a websocket server + /// Connect to a WebSocket server. pub async fn ws( &mut self, ) -> Result, awc::error::WsClientError> { diff --git a/actix-http/src/h1/decoder.rs b/actix-http/src/h1/decoder.rs index 074616929..93a4b13d2 100644 --- a/actix-http/src/h1/decoder.rs +++ b/actix-http/src/h1/decoder.rs @@ -224,7 +224,7 @@ impl MessageType for Request { let decoder = match length { PayloadLength::Payload(pl) => pl, PayloadLength::UpgradeWebSocket => { - // upgrade(websocket) + // upgrade (WebSocket) PayloadType::Stream(PayloadDecoder::eof()) } PayloadLength::None => { diff --git a/actix-http/src/ws/codec.rs b/actix-http/src/ws/codec.rs index d01e8dab9..54d850854 100644 --- a/actix-http/src/ws/codec.rs +++ b/actix-http/src/ws/codec.rs @@ -54,7 +54,7 @@ pub enum Frame { Close(Option), } -/// A `WebSocket` continuation item. +/// A WebSocket continuation item. #[derive(Debug, PartialEq)] pub enum Item { FirstText(Bytes), @@ -79,7 +79,7 @@ bitflags! { } impl Codec { - /// Create new websocket frames decoder. + /// Create new WebSocket frames decoder. pub fn new() -> Codec { Codec { max_size: 65_536, diff --git a/actix-http/src/ws/frame.rs b/actix-http/src/ws/frame.rs index 78d487dd2..46edf5d85 100644 --- a/actix-http/src/ws/frame.rs +++ b/actix-http/src/ws/frame.rs @@ -7,7 +7,7 @@ use crate::ws::mask::apply_mask; use crate::ws::proto::{CloseCode, CloseReason, OpCode}; use crate::ws::ProtocolError; -/// A struct representing a `WebSocket` frame. +/// A struct representing a WebSocket frame. #[derive(Debug)] pub struct Parser; diff --git a/actix-http/src/ws/mod.rs b/actix-http/src/ws/mod.rs index dad2646c1..0490163d5 100644 --- a/actix-http/src/ws/mod.rs +++ b/actix-http/src/ws/mod.rs @@ -1,6 +1,6 @@ -//! WebSocket protocol support. +//! WebSocket protocol. //! -//! To setup a WebSocket, first do web socket handshake then on success convert `Payload` into a +//! To setup a WebSocket, first perform the WebSocket handshake then on success convert `Payload` into a //! `WsStream` stream and then use `WsWriter` to communicate with the peer. use std::io; @@ -76,7 +76,7 @@ pub enum HandshakeError { #[display(fmt = "Method not allowed.")] GetMethodRequired, - /// Upgrade header if not set to websocket. + /// Upgrade header if not set to WebSocket. #[display(fmt = "WebSocket upgrade is expected.")] NoWebsocketUpgrade, @@ -88,7 +88,7 @@ pub enum HandshakeError { #[display(fmt = "WebSocket version header is required.")] NoVersionHeader, - /// Unsupported websocket version. + /// Unsupported WebSocket version. #[display(fmt = "Unsupported version.")] UnsupportedVersion, @@ -127,20 +127,20 @@ impl ResponseError for HandshakeError { } } -/// Verify `WebSocket` handshake request and create handshake response. +/// Verify WebSocket handshake request and create handshake response. pub fn handshake(req: &RequestHead) -> Result { verify_handshake(req)?; Ok(handshake_response(req)) } -/// Verify `WebSocket` handshake request. +/// Verify WebSocket handshake request. pub fn verify_handshake(req: &RequestHead) -> Result<(), HandshakeError> { // WebSocket accepts only GET if req.method != Method::GET { return Err(HandshakeError::GetMethodRequired); } - // Check for "UPGRADE" to websocket header + // Check for "UPGRADE" to WebSocket header let has_hdr = if let Some(hdr) = req.headers().get(header::UPGRADE) { if let Ok(s) = hdr.to_str() { s.to_ascii_lowercase().contains("websocket") @@ -181,7 +181,7 @@ pub fn verify_handshake(req: &RequestHead) -> Result<(), HandshakeError> { Ok(()) } -/// Create websocket handshake response +/// Create WebSocket handshake response. /// /// This function returns handshake `Response`, ready to send to peer. pub fn handshake_response(req: &RequestHead) -> ResponseBuilder { diff --git a/actix-http/src/ws/proto.rs b/actix-http/src/ws/proto.rs index 93468d232..1e8bf7af3 100644 --- a/actix-http/src/ws/proto.rs +++ b/actix-http/src/ws/proto.rs @@ -74,8 +74,7 @@ impl From for OpCode { } } -/// Status code used to indicate why an endpoint is closing the `WebSocket` -/// connection. +/// Status code used to indicate why an endpoint is closing the WebSocket connection. #[derive(Debug, Eq, PartialEq, Clone, Copy)] pub enum CloseCode { /// Indicates a normal closure, meaning that the purpose for diff --git a/actix-web-actors/src/ws.rs b/actix-web-actors/src/ws.rs index 3eb8cc1e7..1ab4cfce5 100644 --- a/actix-web-actors/src/ws.rs +++ b/actix-web-actors/src/ws.rs @@ -48,7 +48,7 @@ where /// /// 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. +/// from the WebSocket request. pub fn start_with_addr( actor: A, req: &HttpRequest, @@ -63,7 +63,7 @@ where Ok((addr, res.streaming(out_stream))) } -/// Do websocket handshake and start ws actor. +/// Do WebSocket handshake and start ws actor. /// /// `protocols` is a sequence of known protocols. pub fn start_with_protocols( @@ -80,7 +80,7 @@ where Ok(res.streaming(WebsocketContext::create(actor, stream))) } -/// Prepare `WebSocket` handshake response. +/// Prepare WebSocket handshake response. /// /// This function returns handshake `HttpResponse`, ready to send to peer. /// It does not perform any IO. @@ -88,7 +88,7 @@ pub fn handshake(req: &HttpRequest) -> Result, @@ -59,7 +59,7 @@ pub struct WebsocketsRequest { } impl WebsocketsRequest { - /// Create new websocket connection + /// Create new WebSocket connection pub(crate) fn new(uri: U, config: Rc) -> Self where Uri: TryFrom, @@ -102,7 +102,7 @@ impl WebsocketsRequest { self } - /// Set supported websocket protocols + /// Set supported WebSocket protocols pub fn protocols(mut self, protos: U) -> Self where U: IntoIterator, @@ -239,7 +239,7 @@ impl WebsocketsRequest { self.header(AUTHORIZATION, format!("Bearer {}", token)) } - /// Complete request construction and connect to a websockets server. + /// Complete request construction and connect to a WebSocket server. pub async fn connect( mut self, ) -> Result<(ClientResponse, Framed), WsClientError> { @@ -338,7 +338,7 @@ impl WebsocketsRequest { return Err(WsClientError::InvalidResponseStatus(head.status)); } - // Check for "UPGRADE" to websocket header + // check for "UPGRADE" to WebSocket header let has_hdr = if let Some(hdr) = head.headers.get(&header::UPGRADE) { if let Ok(s) = hdr.to_str() { s.to_ascii_lowercase().contains("websocket") diff --git a/awc/tests/test_ws.rs b/awc/tests/test_ws.rs index 8eb912dac..1b3f780dc 100644 --- a/awc/tests/test_ws.rs +++ b/awc/tests/test_ws.rs @@ -31,7 +31,7 @@ async fn test_simple() { .send(h1::Message::Item((res.drop_body(), BodySize::None))) .await?; - // start websocket service + // start WebSocket service let framed = framed.replace_codec(ws::Codec::new()); ws::Dispatcher::with(framed, ws_service).await } diff --git a/src/test.rs b/src/test.rs index 99602eb11..d576838de 100644 --- a/src/test.rs +++ b/src/test.rs @@ -944,7 +944,7 @@ impl TestServer { response.body().limit(10_485_760).await } - /// Connect to websocket server at a given path + /// Connect to WebSocket server at a given path. pub async fn ws_at( &mut self, path: &str, @@ -954,7 +954,7 @@ impl TestServer { connect.await.map(|(_, framed)| framed) } - /// Connect to a websocket server + /// Connect to a WebSocket server. pub async fn ws( &mut self, ) -> Result, awc::error::WsClientError> { From ce9b2770e2c7931cd29b574101cfbcbcba3c399b Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Fri, 12 Feb 2021 02:37:28 -0800 Subject: [PATCH 034/339] remove unused Dispatcher::new_timeout (#1985) --- actix-http/src/h1/dispatcher.rs | 43 ++++++--------------------------- 1 file changed, 8 insertions(+), 35 deletions(-) diff --git a/actix-http/src/h1/dispatcher.rs b/actix-http/src/h1/dispatcher.rs index 7ee2ee258..e6533b7c5 100644 --- a/actix-http/src/h1/dispatcher.rs +++ b/actix-http/src/h1/dispatcher.rs @@ -160,62 +160,35 @@ where { /// Create HTTP/1 dispatcher. pub(crate) fn new( - stream: T, - config: ServiceConfig, - services: Rc>, - on_connect_data: OnConnectData, - peer_addr: Option, - ) -> Self { - Dispatcher::with_timeout( - stream, - Codec::new(config.clone()), - config, - BytesMut::with_capacity(HW_BUFFER_SIZE), - None, - services, - on_connect_data, - peer_addr, - ) - } - - /// Create HTTP/1 dispatcher with slow request timeout. - pub(crate) fn with_timeout( io: T, - codec: Codec, config: ServiceConfig, - read_buf: BytesMut, - timeout: Option, - services: Rc>, + flow: Rc>, on_connect_data: OnConnectData, peer_addr: Option, ) -> Self { - let keepalive = config.keep_alive_enabled(); - let flags = if keepalive { + let flags = if config.keep_alive_enabled() { Flags::KEEPALIVE } else { Flags::empty() }; // keep-alive timer - let (ka_expire, ka_timer) = if let Some(delay) = timeout { - (delay.deadline(), Some(delay)) - } else if let Some(delay) = config.keep_alive_timer() { - (delay.deadline(), Some(delay)) - } else { - (config.now(), None) + let (ka_expire, ka_timer) = match config.keep_alive_timer() { + Some(delay) => (delay.deadline(), Some(delay)), + None => (config.now(), None), }; 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, - read_buf, - flow: services, + codec: Codec::new(config), + flow, on_connect_data, flags, peer_addr, From 95113ad12f4aa34b6173019753fee335804fcbe1 Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Fri, 12 Feb 2021 12:33:13 -0800 Subject: [PATCH 035/339] do not self wake up when have a payload (#1984) --- actix-http/src/h1/dispatcher.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/actix-http/src/h1/dispatcher.rs b/actix-http/src/h1/dispatcher.rs index e6533b7c5..2e729b78d 100644 --- a/actix-http/src/h1/dispatcher.rs +++ b/actix-http/src/h1/dispatcher.rs @@ -745,7 +745,12 @@ where // at this point it's not known io is still scheduled to // be waked up. so force wake up dispatcher just in case. // TODO: figure out the overhead. - cx.waker().wake_by_ref(); + if this.payload.is_none() { + // When dispatcher has a payload. The responsibility of + // wake up stream would be shift to PayloadSender. + // Therefore no self wake up is needed. + cx.waker().wake_by_ref(); + } return Ok(false); } From 366c032c36390bd1527970fe90cf2f7217736c65 Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Fri, 12 Feb 2021 13:52:58 -0800 Subject: [PATCH 036/339] refactor DateService (#1983) --- actix-http/src/config.rs | 171 +++++++++++++++++++++++------------ actix-http/src/h1/codec.rs | 4 +- actix-http/src/h1/encoder.rs | 12 +-- benches/server.rs | 29 +++--- 4 files changed, 138 insertions(+), 78 deletions(-) diff --git a/actix-http/src/config.rs b/actix-http/src/config.rs index 61708cfc3..f2e71799a 100644 --- a/actix-http/src/config.rs +++ b/actix-http/src/config.rs @@ -4,9 +4,11 @@ use std::rc::Rc; use std::time::Duration; use std::{fmt, net}; -use actix_rt::time::{sleep, sleep_until, Instant, Sleep}; +use actix_rt::{ + task::JoinHandle, + time::{interval, sleep_until, Instant, Sleep}, +}; use bytes::BytesMut; -use futures_util::{future, FutureExt}; use time::OffsetDateTime; /// "Sun, 06 Nov 1994 08:49:37 GMT".len() @@ -49,7 +51,7 @@ struct Inner { ka_enabled: bool, secure: bool, local_addr: Option, - timer: DateService, + date_service: DateService, } impl Clone for ServiceConfig { @@ -91,7 +93,7 @@ impl ServiceConfig { client_disconnect, secure, local_addr, - timer: DateService::new(), + date_service: DateService::new(), })) } @@ -125,7 +127,7 @@ impl ServiceConfig { let delay_time = self.0.client_timeout; if delay_time != 0 { Some(sleep_until( - self.0.timer.now() + Duration::from_millis(delay_time), + self.0.date_service.now() + Duration::from_millis(delay_time), )) } else { None @@ -136,7 +138,7 @@ impl ServiceConfig { pub fn client_timer_expire(&self) -> Option { let delay = self.0.client_timeout; if delay != 0 { - Some(self.0.timer.now() + Duration::from_millis(delay)) + Some(self.0.date_service.now() + Duration::from_millis(delay)) } else { None } @@ -146,7 +148,7 @@ impl ServiceConfig { pub fn client_disconnect_timer(&self) -> Option { let delay = self.0.client_disconnect; if delay != 0 { - Some(self.0.timer.now() + Duration::from_millis(delay)) + Some(self.0.date_service.now() + Duration::from_millis(delay)) } else { None } @@ -156,7 +158,7 @@ impl ServiceConfig { /// Return keep-alive timer delay is configured. pub fn keep_alive_timer(&self) -> Option { if let Some(ka) = self.0.keep_alive { - Some(sleep_until(self.0.timer.now() + ka)) + Some(sleep_until(self.0.date_service.now() + ka)) } else { None } @@ -165,7 +167,7 @@ impl ServiceConfig { /// Keep-alive expire time pub fn keep_alive_expire(&self) -> Option { if let Some(ka) = self.0.keep_alive { - Some(self.0.timer.now() + ka) + Some(self.0.date_service.now() + ka) } else { None } @@ -173,7 +175,7 @@ impl ServiceConfig { #[inline] pub(crate) fn now(&self) -> Instant { - self.0.timer.now() + self.0.date_service.now() } #[doc(hidden)] @@ -181,7 +183,7 @@ impl ServiceConfig { let mut buf: [u8; 39] = [0; 39]; buf[..6].copy_from_slice(b"date: "); self.0 - .timer + .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); @@ -189,7 +191,7 @@ impl ServiceConfig { pub(crate) fn set_date_header(&self, dst: &mut BytesMut) { self.0 - .timer + .date_service .set_date(|date| dst.extend_from_slice(&date.bytes)); } } @@ -230,57 +232,75 @@ impl fmt::Write for Date { } } -#[derive(Clone)] -struct DateService(Rc); - -struct DateServiceInner { - current: Cell>, +/// Service for update Date and Instant periodically at 500 millis interval. +struct DateService { + current: Rc>, + handle: JoinHandle<()>, } -impl DateServiceInner { - fn new() -> Self { - DateServiceInner { - current: Cell::new(None), - } - } - - fn reset(&self) { - self.current.take(); - } - - fn update(&self) { - let now = Instant::now(); - let date = Date::new(); - self.current.set(Some((date, now))); +impl Drop for DateService { + fn drop(&mut self) { + // stop the timer update async task on drop. + self.handle.abort(); } } impl DateService { fn new() -> Self { - DateService(Rc::new(DateServiceInner::new())) - } + // 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(); - fn check_date(&self) { - if self.0.current.get().is_none() { - self.0.update(); + let mut interval = interval(Duration::from_millis(500)); + loop { + let now = interval.tick().await; + let date = Date::new(); + current_clone.set((date, now)); + } + }); - // periodic date update - let s = self.clone(); - actix_rt::spawn(sleep(Duration::from_millis(500)).then(move |_| { - s.0.reset(); - future::ready(()) - })); - } + DateService { current, handle } } fn now(&self) -> Instant { - self.check_date(); - self.0.current.get().unwrap().1 + self.current.get().1 } fn set_date(&self, mut f: F) { - self.check_date(); - f(&self.0.current.get().unwrap().0); + f(&self.current.get().0); + } +} + +// test drop behavior of DateService. only enabled in tests. +// TODO: move to a util module for testing all spawn handle drop style tasks. +#[cfg(test)] +mod notify_on_drop { + use std::sync::atomic::{AtomicBool, Ordering::SeqCst}; + + static NOTIFY_DROPPED: AtomicBool = AtomicBool::new(false); + + pub(crate) fn is_dropped() -> bool { + NOTIFY_DROPPED.load(SeqCst) + } + + pub(crate) struct NotifyOnDrop; + + impl NotifyOnDrop { + pub(crate) fn new() -> Self { + NOTIFY_DROPPED.store(false, SeqCst); + NotifyOnDrop + } + } + + impl Drop for NotifyOnDrop { + fn drop(&mut self) { + NOTIFY_DROPPED.store(true, SeqCst); + } } } @@ -288,14 +308,53 @@ impl DateService { mod tests { use super::*; - // Test modifying the date from within the closure - // passed to `set_date` - #[test] - fn test_evil_date() { - let service = DateService::new(); - // Make sure that `check_date` doesn't try to spawn a task - service.0.update(); - service.set_date(|_| service.0.reset()); + use actix_rt::task::yield_now; + + #[actix_rt::test] + async fn test_date_service_update() { + let settings = ServiceConfig::new(KeepAlive::Os, 0, 0, false, None); + + yield_now().await; + + let mut buf1 = BytesMut::with_capacity(DATE_VALUE_LENGTH + 10); + settings.set_date(&mut buf1); + let now1 = settings.now(); + + sleep_until(Instant::now() + Duration::from_secs(2)).await; + yield_now().await; + + let now2 = settings.now(); + let mut buf2 = BytesMut::with_capacity(DATE_VALUE_LENGTH + 10); + settings.set_date(&mut buf2); + + assert_ne!(now1, now2); + + assert_ne!(buf1, buf2); + + drop(settings); + assert!(notify_on_drop::is_dropped()); + } + + #[actix_rt::test] + async fn test_date_service_drop() { + let service = Rc::new(DateService::new()); + + // yield so date service have a chance to register the spawned timer update task. + yield_now().await; + + let clone1 = service.clone(); + let clone2 = service.clone(); + let clone3 = service.clone(); + + drop(clone1); + assert_eq!(false, notify_on_drop::is_dropped()); + drop(clone2); + assert_eq!(false, notify_on_drop::is_dropped()); + drop(clone3); + assert_eq!(false, notify_on_drop::is_dropped()); + + drop(service); + assert!(notify_on_drop::is_dropped()); } #[test] diff --git a/actix-http/src/h1/codec.rs b/actix-http/src/h1/codec.rs index 4aeb9f120..634ca25e8 100644 --- a/actix-http/src/h1/codec.rs +++ b/actix-http/src/h1/codec.rs @@ -201,8 +201,8 @@ mod tests { use super::*; use crate::HttpMessage; - #[test] - fn test_http_request_chunked_payload_and_next_message() { + #[actix_rt::test] + async fn test_http_request_chunked_payload_and_next_message() { let mut codec = Codec::default(); let mut buf = BytesMut::from( diff --git a/actix-http/src/h1/encoder.rs b/actix-http/src/h1/encoder.rs index 2932a7dce..69e69de42 100644 --- a/actix-http/src/h1/encoder.rs +++ b/actix-http/src/h1/encoder.rs @@ -529,8 +529,8 @@ mod tests { ); } - #[test] - fn test_camel_case() { + #[actix_rt::test] + async fn test_camel_case() { let mut bytes = BytesMut::with_capacity(2048); let mut head = RequestHead::default(); head.set_camel_case_headers(true); @@ -593,8 +593,8 @@ mod tests { assert!(data.contains("date: date\r\n")); } - #[test] - fn test_extra_headers() { + #[actix_rt::test] + async fn test_extra_headers() { let mut bytes = BytesMut::with_capacity(2048); let mut head = RequestHead::default(); @@ -627,8 +627,8 @@ mod tests { assert!(data.contains("date: date\r\n")); } - #[test] - fn test_no_content_length() { + #[actix_rt::test] + async fn test_no_content_length() { let mut bytes = BytesMut::with_capacity(2048); let mut res: Response<()> = diff --git a/benches/server.rs b/benches/server.rs index 2471dd74c..9dd540a73 100644 --- a/benches/server.rs +++ b/benches/server.rs @@ -42,24 +42,25 @@ fn bench_async_burst(c: &mut Criterion) { c.bench_function("get_body_async_burst", move |b| { b.iter_custom(|iters| { - let client = - rt.block_on(async { Client::new().get(url.clone()).freeze().unwrap() }); + rt.block_on(async { + let client = Client::new().get(url.clone()).freeze().unwrap(); + + let start = std::time::Instant::now(); + // benchmark body - let start = std::time::Instant::now(); - // benchmark body - let resps = rt.block_on(async move { let burst = (0..iters).map(|_| client.send()); - join_all(burst).await - }); - let elapsed = start.elapsed(); + let resps = join_all(burst).await; - // if there are failed requests that might be an issue - let failed = resps.iter().filter(|r| r.is_err()).count(); - if failed > 0 { - eprintln!("failed {} requests (might be bench timeout)", failed); - }; + let elapsed = start.elapsed(); - elapsed + // if there are failed requests that might be an issue + let failed = resps.iter().filter(|r| r.is_err()).count(); + if failed > 0 { + eprintln!("failed {} requests (might be bench timeout)", failed); + }; + + elapsed + }) }) }); } From 1e538bf73efe5bebdfd920559ece136a23c911bf Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Fri, 12 Feb 2021 21:53:21 +0000 Subject: [PATCH 037/339] rework ci (#1982) --- .github/workflows/{linux.yml => ci.yml} | 40 +++++++++---- .github/workflows/macos.yml | 56 ------------------ .github/workflows/windows.yml | 76 ------------------------- Cargo.toml | 14 +++-- actix-http-test/Cargo.toml | 6 ++ actix-http/Cargo.toml | 5 ++ awc/Cargo.toml | 6 ++ 7 files changed, 55 insertions(+), 148 deletions(-) rename .github/workflows/{linux.yml => ci.yml} (58%) delete mode 100644 .github/workflows/macos.yml delete mode 100644 .github/workflows/windows.yml diff --git a/.github/workflows/linux.yml b/.github/workflows/ci.yml similarity index 58% rename from .github/workflows/linux.yml rename to .github/workflows/ci.yml index 53f22df63..55af5a380 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/ci.yml @@ -1,24 +1,27 @@ -name: CI (Linux) +name: CI on: pull_request: types: [opened, synchronize, reopened] push: - branches: - - master + branches: [master] jobs: build_and_test: 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-latest, triple: x86_64-pc-windows-msvc } version: - 1.46.0 # MSRV - stable - nightly - name: ${{ matrix.version }} - x86_64-unknown-linux-gnu - runs-on: ubuntu-latest + name: ${{ matrix.target.name }} / ${{ matrix.version }} + runs-on: ${{ matrix.target.os }} steps: - uses: actions/checkout@v2 @@ -26,7 +29,7 @@ jobs: - name: Install ${{ matrix.version }} uses: actions-rs/toolchain@v1 with: - toolchain: ${{ matrix.version }}-x86_64-unknown-linux-gnu + toolchain: ${{ matrix.version }}-${{ matrix.target.triple }} profile: minimal override: true @@ -37,18 +40,25 @@ jobs: - name: Cache Dependencies uses: Swatinem/rust-cache@v1.0.1 - - name: check build + - name: check minimal uses: actions-rs/cargo@v1 with: command: check - args: --all --bins --examples --tests + args: --workspace --no-default-features --tests + + - name: check full + uses: actions-rs/cargo@v1 + with: + command: check + args: --workspace --bins --examples --tests - name: tests uses: actions-rs/cargo@v1 - timeout-minutes: 40 with: command: test - args: --all --all-features --no-fail-fast -- --nocapture + args: -v --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 @@ -65,12 +75,18 @@ jobs: args: --package=awc --no-default-features --features=rustls -- --nocapture - name: Generate coverage file - if: matrix.version == 'stable' && github.ref == 'refs/heads/master' + 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 - name: Upload to Codecov - if: matrix.version == 'stable' && github.ref == 'refs/heads/master' + if: > + matrix.target.os == 'ubuntu-latest' + && matrix.version == 'stable' + && github.ref == 'refs/heads/master' uses: codecov/codecov-action@v1 with: file: cobertura.xml diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml deleted file mode 100644 index 6b5366faf..000000000 --- a/.github/workflows/macos.yml +++ /dev/null @@ -1,56 +0,0 @@ -name: CI (macOS) - -on: - pull_request: - types: [opened, synchronize, reopened] - push: - branches: - - master - -jobs: - build_and_test: - strategy: - fail-fast: false - matrix: - version: - - stable - - nightly - - name: ${{ matrix.version }} - x86_64-apple-darwin - runs-on: macOS-latest - - steps: - - uses: actions/checkout@v2 - - - name: Install ${{ matrix.version }} - uses: actions-rs/toolchain@v1 - with: - toolchain: ${{ matrix.version }}-x86_64-apple-darwin - 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.0.1 - - - name: check build - uses: actions-rs/cargo@v1 - with: - command: check - args: --all --bins --examples --tests - - - name: tests - uses: actions-rs/cargo@v1 - with: - command: test - args: --all --all-features --no-fail-fast -- --nocapture - --skip=test_h2_content_length - --skip=test_reading_deflate_encoding_large_random_rustls - - - name: Clear the cargo caches - run: | - cargo install cargo-cache --no-default-features --features ci-autoclean - cargo-cache diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml deleted file mode 100644 index d3de72a61..000000000 --- a/.github/workflows/windows.yml +++ /dev/null @@ -1,76 +0,0 @@ -name: CI (Windows) - -on: - pull_request: - types: [opened, synchronize, reopened] - push: - branches: - - master - -env: - VCPKGRS_DYNAMIC: 1 - -jobs: - build_and_test: - strategy: - fail-fast: false - matrix: - version: - - stable - - nightly - - name: ${{ matrix.version }} - x86_64-pc-windows-msvc - runs-on: windows-latest - - steps: - - uses: actions/checkout@v2 - - - name: Install ${{ matrix.version }} - uses: actions-rs/toolchain@v1 - with: - toolchain: ${{ matrix.version }}-x86_64-pc-windows-msvc - profile: minimal - override: true - - - name: Install OpenSSL - run: | - vcpkg integrate install - vcpkg install openssl:x64-windows - Copy-Item C:\vcpkg\installed\x64-windows\bin\libcrypto-1_1-x64.dll C:\vcpkg\installed\x64-windows\bin\libcrypto.dll - Copy-Item C:\vcpkg\installed\x64-windows\bin\libssl-1_1-x64.dll C:\vcpkg\installed\x64-windows\bin\libssl.dll - Get-ChildItem C:\vcpkg\installed\x64-windows\bin - Get-ChildItem C:\vcpkg\installed\x64-windows\lib - - - name: Generate Cargo.lock - uses: actions-rs/cargo@v1 - with: - command: generate-lockfile - - name: Cache Dependencies - uses: Swatinem/rust-cache@v1.0.1 - - - name: check build - uses: actions-rs/cargo@v1 - with: - command: check - args: --all --bins --examples --tests - - - name: tests - uses: actions-rs/cargo@v1 - with: - command: test - args: --all --all-features --no-fail-fast -- --nocapture - --skip=test_h2_content_length - --skip=test_reading_deflate_encoding_large_random_rustls - --skip=test_params - --skip=test_simple - --skip=test_expect_continue - --skip=test_http10_keepalive - --skip=test_slow_request - --skip=test_connection_force_close - --skip=test_connection_server_close - --skip=test_connection_wait_queue_force_close - - - name: Clear the cargo caches - run: | - cargo install cargo-cache --no-default-features --features ci-autoclean - cargo-cache diff --git a/Cargo.toml b/Cargo.toml index 569d1f8c2..b0302b352 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -47,10 +47,10 @@ compress = ["actix-http/compress", "awc/compress"] secure-cookies = ["actix-http/secure-cookies"] # openssl -openssl = ["tls_openssl", "actix-tls/accept", "actix-tls/openssl", "awc/openssl"] +openssl = ["tls-openssl", "actix-tls/accept", "actix-tls/openssl", "awc/openssl"] # rustls -rustls = ["tls_rustls", "actix-tls/accept", "actix-tls/rustls", "awc/rustls"] +rustls = ["tls-rustls", "actix-tls/accept", "actix-tls/rustls", "awc/rustls"] [[example]] name = "basic" @@ -103,10 +103,16 @@ serde_json = "1.0" serde_urlencoded = "0.7" time = { version = "0.2.23", default-features = false, features = ["std"] } url = "2.1" -tls_openssl = { package = "openssl", version = "0.10.9", optional = true } -tls_rustls = { package = "rustls", version = "0.19.0", optional = true } +tls-openssl = { package = "openssl", version = "0.10.9", optional = true } +tls-rustls = { package = "rustls", version = "0.19.0", optional = true } smallvec = "1.6" +[target.'cfg(windows)'.dependencies.tls-openssl] +version = "0.10.9" +package = "openssl" +features = ["vendored"] +optional = true + [dev-dependencies] actix = { version = "0.11.0-beta.2", default-features = false } rand = "0.8" diff --git a/actix-http-test/Cargo.toml b/actix-http-test/Cargo.toml index 6622e04e0..6dcf73637 100644 --- a/actix-http-test/Cargo.toml +++ b/actix-http-test/Cargo.toml @@ -50,6 +50,12 @@ serde_urlencoded = "0.7" time = { version = "0.2.23", default-features = false, features = ["std"] } tls-openssl = { version = "0.10.9", package = "openssl", optional = true } +[target.'cfg(windows)'.dependencies.tls-openssl] +version = "0.10.9" +package = "openssl" +features = ["vendored"] +optional = true + [dev-dependencies] actix-web = "4.0.0-beta.3" actix-http = "3.0.0-beta.3" diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index c789c3ae5..2c4775898 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -94,6 +94,11 @@ serde_derive = "1.0" tls-openssl = { version = "0.10", package = "openssl" } tls-rustls = { version = "0.19", package = "rustls" } +[target.'cfg(windows)'.dev-dependencies.tls-openssl] +version = "0.10.9" +package = "openssl" +features = ["vendored"] + [[bench]] name = "write-camel-case" harness = false diff --git a/awc/Cargo.toml b/awc/Cargo.toml index a6da53664..615a76e09 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -60,6 +60,12 @@ 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"] } +[target.'cfg(windows)'.dependencies.tls-openssl] +version = "0.10.9" +package = "openssl" +features = ["vendored"] +optional = true + [dev-dependencies] actix-web = { version = "4.0.0-beta.3", features = ["openssl"] } actix-http = { version = "3.0.0-beta.3", features = ["openssl"] } From b37669cb3b47eeb60c161ff12156d1c877af91a2 Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Fri, 12 Feb 2021 20:23:37 -0800 Subject: [PATCH 038/339] fix notify on drop (#1987) --- actix-http/src/config.rs | 40 ++++++++++++++++++++++++++++++++++------ 1 file changed, 34 insertions(+), 6 deletions(-) diff --git a/actix-http/src/config.rs b/actix-http/src/config.rs index f2e71799a..9f84b8694 100644 --- a/actix-http/src/config.rs +++ b/actix-http/src/config.rs @@ -276,30 +276,58 @@ impl DateService { } } -// test drop behavior of DateService. only enabled in tests. // 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 mod notify_on_drop { - use std::sync::atomic::{AtomicBool, Ordering::SeqCst}; + use std::cell::RefCell; - static NOTIFY_DROPPED: AtomicBool = AtomicBool::new(false); + thread_local! { + static NOTIFY_DROPPED: RefCell> = RefCell::new(None); + } + /// Check if the spawned task is dropped. + /// + /// # Panic: + /// + /// When there was no `NotifyOnDrop` instance on current thread pub(crate) fn is_dropped() -> bool { - NOTIFY_DROPPED.load(SeqCst) + 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.store(false, SeqCst); + 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.store(true, SeqCst); + NOTIFY_DROPPED.with(|bool| { + if let Some(b) = bool.borrow_mut().as_mut() { + *b = true; + } + }); } } } From 3279070f9feb1983709626bae3bde431e287ce88 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sat, 13 Feb 2021 15:08:43 +0000 Subject: [PATCH 039/339] optional cookies features (#1981) --- CHANGES.md | 4 ++ Cargo.toml | 17 ++++--- actix-http/CHANGES.md | 4 ++ actix-http/Cargo.toml | 23 +++++---- actix-http/src/error.rs | 5 +- actix-http/src/h1/encoder.rs | 1 - actix-http/src/http_message.rs | 10 ++-- actix-http/src/lib.rs | 20 +++++++- actix-http/src/response.rs | 92 ++++++++++++++++++++++------------ actix-http/src/test.rs | 35 +++++++------ awc/CHANGES.md | 4 ++ awc/Cargo.toml | 8 ++- awc/src/lib.rs | 4 +- awc/src/request.rs | 11 +++- awc/src/response.rs | 23 +++++---- awc/src/test.rs | 14 +++++- awc/src/ws.rs | 8 ++- src/error.rs | 2 + src/lib.rs | 1 + tests/test_server.rs | 8 +-- 20 files changed, 205 insertions(+), 89 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 954410b30..743f5066b 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,10 @@ # Changes ## Unreleased - 2021-xx-xx +### Changed +* Feature `cookies` is now optional and enabled by default. [#1981] + +[#1981]: https://github.com/actix/actix-web/pull/1981 ## 4.0.0-beta.3 - 2021-02-10 diff --git a/Cargo.toml b/Cargo.toml index b0302b352..64b447e14 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,6 +15,7 @@ license = "MIT OR Apache-2.0" edition = "2018" [package.metadata.docs.rs] +# features that docs.rs will build with features = ["openssl", "rustls", "compress", "secure-cookies"] [badges] @@ -38,12 +39,15 @@ members = [ ] [features] -default = ["compress"] +default = ["compress", "cookies"] # content-encoding support compress = ["actix-http/compress", "awc/compress"] -# sessions feature +# support for cookies +cookies = ["actix-http/cookies", "awc/cookies"] + +# secure cookies feature secure-cookies = ["actix-http/secure-cookies"] # openssl @@ -95,17 +99,17 @@ futures-core = { version = "0.3.7", default-features = false } futures-util = { version = "0.3.7", default-features = false } log = "0.4" mime = "0.3" -socket2 = "0.3.16" pin-project = "1.0.0" regex = "1.4" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" serde_urlencoded = "0.7" +smallvec = "1.6" +socket2 = "0.3.16" time = { version = "0.2.23", default-features = false, features = ["std"] } -url = "2.1" tls-openssl = { package = "openssl", version = "0.10.9", optional = true } tls-rustls = { package = "rustls", version = "0.19.0", optional = true } -smallvec = "1.6" +url = "2.1" [target.'cfg(windows)'.dependencies.tls-openssl] version = "0.10.9" @@ -122,9 +126,6 @@ brotli2 = "0.3.2" flate2 = "1.0.13" criterion = "0.3" -[profile.dev] -debug = false - [profile.release] lto = true opt-level = 3 diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index b781fe50e..54f7357f1 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -1,6 +1,10 @@ # Changes ## Unreleased - 2021-xx-xx +### Changed +* Feature `cookies` is now optional and disabled by default. [#1981] + +[#1981]: https://github.com/actix/actix-web/pull/1981 ## 3.0.0-beta.3 - 2021-02-10 diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 2c4775898..0e6916a0b 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -15,7 +15,8 @@ license = "MIT OR Apache-2.0" edition = "2018" [package.metadata.docs.rs] -features = ["openssl", "rustls", "compress", "secure-cookies"] +# features that docs.rs will build with +features = ["openssl", "rustls", "compress", "cookies", "secure-cookies"] [lib] name = "actix_http" @@ -30,11 +31,14 @@ openssl = ["actix-tls/openssl"] # rustls support rustls = ["actix-tls/rustls"] -# enable compressison support +# enable compression support compress = ["flate2", "brotli2"] +# support for cookies +cookies = ["cookie"] + # support for secure cookies -secure-cookies = ["cookie/secure"] +secure-cookies = ["cookies", "cookie/secure"] # trust-dns as client dns resolver trust-dns = ["trust-dns-resolver"] @@ -46,24 +50,25 @@ actix-utils = "3.0.0-beta.2" actix-rt = "2" actix-tls = "3.0.0-beta.2" +ahash = "0.7" base64 = "0.13" bitflags = "1.2" bytes = "1" bytestring = "1" -cookie = { version = "0.14.1", features = ["percent-encode"] } +cfg-if = "1" +cookie = { version = "0.14.1", features = ["percent-encode"], optional = true } derive_more = "0.99.5" encoding_rs = "0.8" futures-channel = { version = "0.3.7", default-features = false, features = ["alloc"] } futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] } futures-util = { version = "0.3.7", default-features = false, features = ["alloc", "sink"] } -ahash = "0.7" h2 = "0.3.0" http = "0.2.2" httparse = "1.3" indexmap = "1.3" itoa = "0.4" -lazy_static = "1.4" language-tags = "0.2" +lazy_static = "1.4" log = "0.4" mime = "0.3" percent-encoding = "2.1" @@ -72,10 +77,10 @@ rand = "0.8" regex = "1.3" serde = "1.0" serde_json = "1.0" -sha-1 = "0.9" -smallvec = "1.6" -slab = "0.4" serde_urlencoded = "0.7" +sha-1 = "0.9" +slab = "0.4" +smallvec = "1.6" time = { version = "0.2.23", default-features = false, features = ["std"] } # compression diff --git a/actix-http/src/error.rs b/actix-http/src/error.rs index 5eb3c157a..6a9de2d3e 100644 --- a/actix-http/src/error.rs +++ b/actix-http/src/error.rs @@ -19,10 +19,12 @@ use serde_json::error::Error as JsonError; use serde_urlencoded::ser::Error as FormError; use crate::body::Body; -pub use crate::cookie::ParseError as CookieParseError; use crate::helpers::Writer; use crate::response::{Response, ResponseBuilder}; +#[cfg(feature = "cookies")] +pub use crate::cookie::ParseError as CookieParseError; + /// A specialized [`std::result::Result`] /// for actix web operations /// @@ -397,6 +399,7 @@ impl ResponseError for PayloadError { } /// Return `BadRequest` for `cookie::ParseError` +#[cfg(feature = "cookies")] impl ResponseError for crate::cookie::ParseError { fn status_code(&self) -> StatusCode { StatusCode::BAD_REQUEST diff --git a/actix-http/src/h1/encoder.rs b/actix-http/src/h1/encoder.rs index 69e69de42..97916e7db 100644 --- a/actix-http/src/h1/encoder.rs +++ b/actix-http/src/h1/encoder.rs @@ -549,7 +549,6 @@ mod tests { ); let data = String::from_utf8(Vec::from(bytes.split().freeze().as_ref())).unwrap(); - eprintln!("{}", &data); assert!(data.contains("Content-Length: 0\r\n")); assert!(data.contains("Connection: close\r\n")); diff --git a/actix-http/src/http_message.rs b/actix-http/src/http_message.rs index 2610b8784..b1f04e50d 100644 --- a/actix-http/src/http_message.rs +++ b/actix-http/src/http_message.rs @@ -5,12 +5,14 @@ use encoding_rs::{Encoding, UTF_8}; use http::header; use mime::Mime; -use crate::cookie::Cookie; -use crate::error::{ContentTypeError, CookieParseError, ParseError}; +use crate::error::{ContentTypeError, ParseError}; use crate::extensions::Extensions; use crate::header::{Header, HeaderMap}; use crate::payload::Payload; +#[cfg(feature = "cookies")] +use crate::{cookie::Cookie, error::CookieParseError}; +#[cfg(feature = "cookies")] struct Cookies(Vec>); /// Trait that implements general purpose operations on HTTP messages. @@ -104,7 +106,7 @@ pub trait HttpMessage: Sized { } /// Load request cookies. - #[inline] + #[cfg(feature = "cookies")] fn cookies(&self) -> Result>>, CookieParseError> { if self.extensions().get::().is_none() { let mut cookies = Vec::new(); @@ -119,12 +121,14 @@ pub trait HttpMessage: Sized { } self.extensions_mut().insert(Cookies(cookies)); } + Ok(Ref::map(self.extensions(), |ext| { &ext.get::().unwrap().0 })) } /// Return request cookie. + #[cfg(feature = "cookies")] fn cookie(&self, name: &str) -> Option> { if let Ok(cookies) = self.cookies() { for cookie in cookies.iter() { diff --git a/actix-http/src/lib.rs b/actix-http/src/lib.rs index a55aaadbf..574d4ef68 100644 --- a/actix-http/src/lib.rs +++ b/actix-http/src/lib.rs @@ -1,4 +1,19 @@ //! 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) | +//! | `cookies` | Support for cookies backed by the [cookie] crate. | +//! | `secure-cookies` | Adds for secure cookies. Enables `cookies` feature. | +//! | `trust-dns` | Use [trust-dns] as the client DNS resolver. | +//! +//! [OpenSSL]: https://crates.io/crates/openssl +//! [rustls]: https://crates.io/crates/rustls +//! [cookie]: https://crates.io/crates/cookie +//! [trust-dns]: https://crates.io/crates/trust-dns #![deny(rust_2018_idioms, nonstandard_style)] #![allow( @@ -34,13 +49,15 @@ mod response; mod service; mod time_parser; -pub use cookie; pub mod error; pub mod h1; pub mod h2; pub mod test; pub mod ws; +#[cfg(feature = "cookies")] +pub use cookie; + pub use self::builder::HttpServiceBuilder; pub use self::config::{KeepAlive, ServiceConfig}; pub use self::error::{Error, ResponseError, Result}; @@ -61,6 +78,7 @@ pub mod http { pub use http::{uri, Error, Uri}; pub use http::{Method, StatusCode, Version}; + #[cfg(feature = "cookies")] pub use crate::cookie::{Cookie, CookieBuilder}; pub use crate::header::HeaderMap; diff --git a/actix-http/src/response.rs b/actix-http/src/response.rs index 763243a63..471dacd28 100644 --- a/actix-http/src/response.rs +++ b/actix-http/src/response.rs @@ -16,13 +16,17 @@ use futures_core::Stream; use serde::Serialize; use crate::body::{Body, BodyStream, MessageBody, ResponseBody}; -use crate::cookie::{Cookie, CookieJar}; use crate::error::Error; use crate::extensions::Extensions; use crate::header::{IntoHeaderPair, IntoHeaderValue}; -use crate::http::header::{self, HeaderName, HeaderValue}; +use crate::http::header::{self, HeaderName}; use crate::http::{Error as HttpError, HeaderMap, StatusCode}; use crate::message::{BoxedResponseHead, ConnectionType, ResponseHead}; +#[cfg(feature = "cookies")] +use crate::{ + cookie::{Cookie, CookieJar}, + http::header::HeaderValue, +}; /// An HTTP Response pub struct Response { @@ -133,6 +137,7 @@ impl Response { } /// Get an iterator for the cookies set by this response + #[cfg(feature = "cookies")] #[inline] pub fn cookies(&self) -> CookieIter<'_> { CookieIter { @@ -141,6 +146,7 @@ impl Response { } /// Add a cookie to this response + #[cfg(feature = "cookies")] #[inline] pub fn add_cookie(&mut self, cookie: &Cookie<'_>) -> Result<(), HttpError> { let h = &mut self.head.headers; @@ -153,6 +159,7 @@ impl Response { /// Remove all cookies with the given name from this response. Returns /// the number of cookies removed. + #[cfg(feature = "cookies")] #[inline] pub fn del_cookie(&mut self, name: &str) -> usize { let h = &mut self.head.headers; @@ -298,10 +305,12 @@ impl Future for Response { } } +#[cfg(feature = "cookies")] pub struct CookieIter<'a> { iter: header::GetAll<'a>, } +#[cfg(feature = "cookies")] impl<'a> Iterator for CookieIter<'a> { type Item = Cookie<'a>; @@ -316,13 +325,13 @@ impl<'a> Iterator for CookieIter<'a> { } } -/// An HTTP response builder +/// An HTTP response builder. /// -/// This type can be used to construct an instance of `Response` through a -/// builder-like pattern. +/// This type can be used to construct an instance of `Response` through a builder-like pattern. pub struct ResponseBuilder { head: Option, err: Option, + #[cfg(feature = "cookies")] cookies: Option, } @@ -333,6 +342,7 @@ impl ResponseBuilder { ResponseBuilder { head: Some(BoxedResponseHead::new(status)), err: None, + #[cfg(feature = "cookies")] cookies: None, } } @@ -531,6 +541,7 @@ impl ResponseBuilder { /// .finish() /// } /// ``` + #[cfg(feature = "cookies")] pub fn cookie<'c>(&mut self, cookie: Cookie<'c>) -> &mut Self { if self.cookies.is_none() { let mut jar = CookieJar::new(); @@ -557,6 +568,7 @@ impl ResponseBuilder { /// builder.finish() /// } /// ``` + #[cfg(feature = "cookies")] pub fn del_cookie<'a>(&mut self, cookie: &Cookie<'a>) -> &mut Self { if self.cookies.is_none() { self.cookies = Some(CookieJar::new()) @@ -624,8 +636,11 @@ impl ResponseBuilder { return Response::from(Error::from(e)).into_body(); } + // allow unused mut when cookies feature is disabled + #[allow(unused_mut)] let mut response = self.head.take().expect("cannot reuse response builder"); + #[cfg(feature = "cookies")] if let Some(ref jar) = self.cookies { for cookie in jar.delta() { match HeaderValue::from_str(&cookie.to_string()) { @@ -693,6 +708,7 @@ impl ResponseBuilder { ResponseBuilder { head: self.head.take(), err: self.err.take(), + #[cfg(feature = "cookies")] cookies: self.cookies.take(), } } @@ -712,21 +728,28 @@ fn parts<'a>( /// Convert `Response` to a `ResponseBuilder`. Body get dropped. impl From> for ResponseBuilder { fn from(res: Response) -> ResponseBuilder { - // If this response has cookies, load them into a jar - let mut jar: Option = None; - for c in res.cookies() { - if let Some(ref mut j) = jar { - j.add_original(c.into_owned()); - } else { - let mut j = CookieJar::new(); - j.add_original(c.into_owned()); - jar = Some(j); + #[cfg(feature = "cookies")] + let jar = { + // If this response has cookies, load them into a jar + let mut jar: Option = None; + + for c in res.cookies() { + if let Some(ref mut j) = jar { + j.add_original(c.into_owned()); + } else { + let mut j = CookieJar::new(); + j.add_original(c.into_owned()); + jar = Some(j); + } } - } + + jar + }; ResponseBuilder { head: Some(res.head), err: None, + #[cfg(feature = "cookies")] cookies: jar, } } @@ -735,22 +758,6 @@ impl From> for ResponseBuilder { /// Convert `ResponseHead` to a `ResponseBuilder` impl<'a> From<&'a ResponseHead> for ResponseBuilder { fn from(head: &'a ResponseHead) -> ResponseBuilder { - // If this response has cookies, load them into a jar - let mut jar: Option = None; - - let cookies = CookieIter { - iter: head.headers.get_all(header::SET_COOKIE), - }; - for c in cookies { - if let Some(ref mut j) = jar { - j.add_original(c.into_owned()); - } else { - let mut j = CookieJar::new(); - j.add_original(c.into_owned()); - jar = Some(j); - } - } - let mut msg = BoxedResponseHead::new(head.status); msg.version = head.version; msg.reason = head.reason; @@ -761,9 +768,32 @@ impl<'a> From<&'a ResponseHead> for ResponseBuilder { msg.no_chunking(!head.chunked()); + #[cfg(feature = "cookies")] + let jar = { + // If this response has cookies, load them into a jar + let mut jar: Option = None; + + let cookies = CookieIter { + iter: head.headers.get_all(header::SET_COOKIE), + }; + + for c in cookies { + if let Some(ref mut j) = jar { + j.add_original(c.into_owned()); + } else { + let mut j = CookieJar::new(); + j.add_original(c.into_owned()); + jar = Some(j); + } + } + + jar + }; + ResponseBuilder { head: Some(msg), err: None, + #[cfg(feature = "cookies")] cookies: jar, } } diff --git a/actix-http/src/test.rs b/actix-http/src/test.rs index 8f0a7d21a..870a656df 100644 --- a/actix-http/src/test.rs +++ b/actix-http/src/test.rs @@ -11,13 +11,14 @@ use std::{ use actix_codec::{AsyncRead, AsyncWrite, ReadBuf}; use bytes::{Bytes, BytesMut}; -use http::{ - header::{self, HeaderValue}, - Method, Uri, Version, -}; +use http::{Method, Uri, Version}; +#[cfg(feature = "cookies")] use crate::{ cookie::{Cookie, CookieJar}, + header::{self, HeaderValue}, +}; +use crate::{ header::{HeaderMap, IntoHeaderPair}, payload::Payload, Request, @@ -53,6 +54,7 @@ struct Inner { method: Method, uri: Uri, headers: HeaderMap, + #[cfg(feature = "cookies")] cookies: CookieJar, payload: Option, } @@ -64,6 +66,7 @@ impl Default for TestRequest { uri: Uri::from_str("/").unwrap(), version: Version::HTTP_11, headers: HeaderMap::new(), + #[cfg(feature = "cookies")] cookies: CookieJar::new(), payload: None, })) @@ -132,6 +135,7 @@ impl TestRequest { } /// Set cookie for this request. + #[cfg(feature = "cookies")] pub fn cookie<'a>(&mut self, cookie: Cookie<'a>) -> &mut Self { parts(&mut self.0).cookies.add(cookie.into_owned()); self @@ -165,17 +169,20 @@ impl TestRequest { head.version = inner.version; head.headers = inner.headers; - let cookie: String = inner - .cookies - .delta() - // ensure only name=value is written to cookie header - .map(|c| Cookie::new(c.name(), c.value()).encoded().to_string()) - .collect::>() - .join("; "); + #[cfg(feature = "cookies")] + { + let cookie: String = inner + .cookies + .delta() + // ensure only name=value is written to cookie header + .map(|c| Cookie::new(c.name(), c.value()).encoded().to_string()) + .collect::>() + .join("; "); - if !cookie.is_empty() { - head.headers - .insert(header::COOKIE, HeaderValue::from_str(&cookie).unwrap()); + if !cookie.is_empty() { + head.headers + .insert(header::COOKIE, HeaderValue::from_str(&cookie).unwrap()); + } } req diff --git a/awc/CHANGES.md b/awc/CHANGES.md index 20e8af6df..9224f414d 100644 --- a/awc/CHANGES.md +++ b/awc/CHANGES.md @@ -1,6 +1,10 @@ # Changes ## Unreleased - 2021-xx-xx +### Changed +* Feature `cookies` is now optional and enabled by default. [#1981] + +[#1981]: https://github.com/actix/actix-web/pull/1981 ## 3.0.0-beta.2 - 2021-02-10 diff --git a/awc/Cargo.toml b/awc/Cargo.toml index 615a76e09..9beecc6d4 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -22,10 +22,11 @@ name = "awc" path = "src/lib.rs" [package.metadata.docs.rs] -features = ["openssl", "rustls", "compress"] +# features that docs.rs will build with +features = ["openssl", "rustls", "compress", "cookies"] [features] -default = ["compress"] +default = ["compress", "cookies"] # openssl openssl = ["tls-openssl", "actix-http/openssl"] @@ -36,6 +37,9 @@ rustls = ["tls-rustls", "actix-http/rustls"] # content-encoding support compress = ["actix-http/compress"] +# cookie parsing and cookie jar +cookies = ["actix-http/cookies"] + # trust-dns as dns resolver trust-dns = ["actix-http/trust-dns"] diff --git a/awc/src/lib.rs b/awc/src/lib.rs index bd52f7ab1..e50c19c8c 100644 --- a/awc/src/lib.rs +++ b/awc/src/lib.rs @@ -97,7 +97,9 @@ use std::convert::TryFrom; use std::rc::Rc; use std::time::Duration; -pub use actix_http::{client::Connector, cookie, http}; +#[cfg(feature = "cookies")] +pub use actix_http::cookie; +pub use actix_http::{client::Connector, http}; use actix_http::http::{Error as HttpError, HeaderMap, Method, Uri}; use actix_http::RequestHead; diff --git a/awc/src/request.rs b/awc/src/request.rs index db8196c5b..812c0e805 100644 --- a/awc/src/request.rs +++ b/awc/src/request.rs @@ -8,6 +8,7 @@ use futures_core::Stream; use serde::Serialize; use actix_http::body::Body; +#[cfg(feature = "cookies")] use actix_http::cookie::{Cookie, CookieJar}; use actix_http::http::header::{self, IntoHeaderPair}; use actix_http::http::{ @@ -54,10 +55,12 @@ pub struct ClientRequest { pub(crate) head: RequestHead, err: Option, addr: Option, - cookies: Option, response_decompress: bool, timeout: Option, config: Rc, + + #[cfg(feature = "cookies")] + cookies: Option, } impl ClientRequest { @@ -72,6 +75,7 @@ impl ClientRequest { head: RequestHead::default(), err: None, addr: None, + #[cfg(feature = "cookies")] cookies: None, timeout: None, response_decompress: true, @@ -290,6 +294,7 @@ impl ClientRequest { /// println!("Response: {:?}", resp); /// } /// ``` + #[cfg(feature = "cookies")] pub fn cookie(mut self, cookie: Cookie<'_>) -> Self { if self.cookies.is_none() { let mut jar = CookieJar::new(); @@ -472,7 +477,8 @@ impl ClientRequest { ) } - fn prep_for_sending(mut self) -> Result { + // allow unused mut when cookies feature is disabled + fn prep_for_sending(#[allow(unused_mut)] mut self) -> Result { if let Some(e) = self.err { return Err(e.into()); } @@ -493,6 +499,7 @@ impl ClientRequest { } // set cookies + #[cfg(feature = "cookies")] if let Some(ref mut jar) = self.cookies { let cookie: String = jar .delta() diff --git a/awc/src/response.rs b/awc/src/response.rs index 1c887ee4e..cf687329d 100644 --- a/awc/src/response.rs +++ b/awc/src/response.rs @@ -1,20 +1,25 @@ -use std::cell::{Ref, RefMut}; use std::fmt; use std::future::Future; use std::marker::PhantomData; use std::pin::Pin; use std::task::{Context, Poll}; +use std::{ + cell::{Ref, RefMut}, + mem, +}; use bytes::{Bytes, BytesMut}; use futures_core::{ready, Stream}; -use actix_http::cookie::Cookie; -use actix_http::error::{CookieParseError, PayloadError}; -use actix_http::http::header::{CONTENT_LENGTH, SET_COOKIE}; +use actix_http::error::PayloadError; +use actix_http::http::header; use actix_http::http::{HeaderMap, StatusCode, Version}; use actix_http::{Extensions, HttpMessage, Payload, PayloadStream, ResponseHead}; use serde::de::DeserializeOwned; +#[cfg(feature = "cookies")] +use actix_http::{cookie::Cookie, error::CookieParseError}; + use crate::error::JsonPayloadError; /// Client Response @@ -39,17 +44,17 @@ impl HttpMessage for ClientResponse { } fn take_payload(&mut self) -> Payload { - std::mem::replace(&mut self.payload, Payload::None) + mem::replace(&mut self.payload, Payload::None) } /// Load request cookies. - #[inline] + #[cfg(feature = "cookies")] 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(&SET_COOKIE) { + 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()); } @@ -161,7 +166,7 @@ where /// Create `MessageBody` for request. pub fn new(res: &mut ClientResponse) -> MessageBody { let mut len = None; - if let Some(l) = res.headers().get(&CONTENT_LENGTH) { + 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) @@ -256,7 +261,7 @@ where } let mut len = None; - if let Some(l) = req.headers().get(&CONTENT_LENGTH) { + if let Some(l) = req.headers().get(&header::CONTENT_LENGTH) { if let Ok(s) = l.to_str() { if let Ok(l) = s.parse::() { len = Some(l) diff --git a/awc/src/test.rs b/awc/src/test.rs index 84646b9f7..97bbb9c3d 100644 --- a/awc/src/test.rs +++ b/awc/src/test.rs @@ -1,9 +1,13 @@ //! Test helpers for actix http client to use during testing. use std::convert::TryFrom; -use actix_http::cookie::{Cookie, CookieJar}; -use actix_http::http::header::{self, Header, HeaderValue, IntoHeaderValue}; +use actix_http::http::header::{Header, IntoHeaderValue}; use actix_http::http::{Error as HttpError, HeaderName, StatusCode, Version}; +#[cfg(feature = "cookies")] +use actix_http::{ + cookie::{Cookie, CookieJar}, + http::header::{self, HeaderValue}, +}; use actix_http::{h1, Payload, ResponseHead}; use bytes::Bytes; @@ -12,6 +16,7 @@ use crate::ClientResponse; /// Test `ClientResponse` builder pub struct TestResponse { head: ResponseHead, + #[cfg(feature = "cookies")] cookies: CookieJar, payload: Option, } @@ -20,6 +25,7 @@ impl Default for TestResponse { fn default() -> TestResponse { TestResponse { head: ResponseHead::new(StatusCode::OK), + #[cfg(feature = "cookies")] cookies: CookieJar::new(), payload: None, } @@ -69,6 +75,7 @@ impl TestResponse { } /// Set cookie for this response + #[cfg(feature = "cookies")] pub fn cookie(mut self, cookie: Cookie<'_>) -> Self { self.cookies.add(cookie.into_owned()); self @@ -84,8 +91,11 @@ impl TestResponse { /// Complete response creation and generate `ClientResponse` instance pub fn finish(self) -> ClientResponse { + // allow unused mut when cookies feature is disabled + #[allow(unused_mut)] let mut head = self.head; + #[cfg(feature = "cookies")] for cookie in self.cookies.delta() { head.headers.insert( header::SET_COOKIE, diff --git a/awc/src/ws.rs b/awc/src/ws.rs index 8db002836..d5528595d 100644 --- a/awc/src/ws.rs +++ b/awc/src/ws.rs @@ -32,6 +32,7 @@ use std::rc::Rc; use std::{fmt, str}; use actix_codec::Framed; +#[cfg(feature = "cookies")] use actix_http::cookie::{Cookie, CookieJar}; use actix_http::{ws, Payload, RequestHead}; use actix_rt::time::timeout; @@ -54,8 +55,10 @@ pub struct WebsocketsRequest { addr: Option, max_size: usize, server_mode: bool, - cookies: Option, config: Rc, + + #[cfg(feature = "cookies")] + cookies: Option, } impl WebsocketsRequest { @@ -89,6 +92,7 @@ impl WebsocketsRequest { protocols: None, max_size: 65_536, server_mode: false, + #[cfg(feature = "cookies")] cookies: None, } } @@ -117,6 +121,7 @@ impl WebsocketsRequest { } /// Set a cookie + #[cfg(feature = "cookies")] pub fn cookie(mut self, cookie: Cookie<'_>) -> Self { if self.cookies.is_none() { let mut jar = CookieJar::new(); @@ -270,6 +275,7 @@ impl WebsocketsRequest { } // set cookies + #[cfg(feature = "cookies")] if let Some(ref mut jar) = self.cookies { let cookie: String = jar .delta() diff --git a/src/error.rs b/src/error.rs index 1d7c781d8..0865257d3 100644 --- a/src/error.rs +++ b/src/error.rs @@ -13,9 +13,11 @@ pub enum UrlGenerationError { /// Resource not found #[display(fmt = "Resource not found")] ResourceNotFound, + /// Not all path pattern covered #[display(fmt = "Not all path pattern covered")] NotEnoughElements, + /// URL parse error #[display(fmt = "{}", _0)] ParseError(UrlParseError), diff --git a/src/lib.rs b/src/lib.rs index 39cfaf197..73390a55e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -61,6 +61,7 @@ //! ## 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` //! * `rustls` - HTTPS support via `rustls` crate, supports `HTTP/2` //! * `secure-cookies` - secure cookies support diff --git a/tests/test_server.rs b/tests/test_server.rs index c2caa9eb2..c2e627a3f 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -806,15 +806,15 @@ async fn test_server_cookies() { })) }); + let req = srv.get("/"); + let res = req.send().await.unwrap(); + assert!(res.status().is_success()); + let first_cookie = http::CookieBuilder::new("first", "first_value") .http_only(true) .finish(); let second_cookie = http::Cookie::new("second", "second_value"); - let req = srv.get("/"); - let res = req.send().await.unwrap(); - assert!(res.status().is_success()); - let cookies = res.cookies().expect("To have cookies"); assert_eq!(cookies.len(), 2); if cookies[0] == first_cookie { From 7fa6333a0cdc6b7786b767e93ff73042ac365680 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sat, 13 Feb 2021 17:16:36 +0000 Subject: [PATCH 040/339] use rcgen for tls key generation (#1989) --- .github/workflows/ci.yml | 10 ++- Cargo.toml | 10 +-- actix-http/Cargo.toml | 1 + actix-http/tests/test_openssl.rs | 61 +++++++------- actix-http/tests/test_rustls.rs | 45 +++++----- awc/tests/test_connector.rs | 27 +++--- awc/tests/test_ssl_client.rs | 27 +++--- src/lib.rs | 4 +- src/middleware/compat.rs | 11 ++- src/request.rs | 2 + src/test.rs | 5 +- tests/cert.pem | 19 ----- tests/key.pem | 28 ------- tests/test_httpserver.rs | 23 ++++-- tests/test_server.rs | 138 ++++++++++++++++++++----------- 15 files changed, 226 insertions(+), 185 deletions(-) delete mode 100644 tests/cert.pem delete mode 100644 tests/key.pem diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 55af5a380..0126238ef 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -40,11 +40,17 @@ jobs: - name: Cache Dependencies uses: Swatinem/rust-cache@v1.0.1 + - 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: check - args: --workspace --no-default-features --tests + command: hack + args: --clean-per-run check --workspace --no-default-features --tests - name: check full uses: actions-rs/cargo@v1 diff --git a/Cargo.toml b/Cargo.toml index 64b447e14..1a1b8645c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -118,13 +118,13 @@ features = ["vendored"] optional = true [dev-dependencies] -actix = { version = "0.11.0-beta.2", default-features = false } -rand = "0.8" -env_logger = "0.8" -serde_derive = "1.0" brotli2 = "0.3.2" -flate2 = "1.0.13" criterion = "0.3" +env_logger = "0.8" +flate2 = "1.0.13" +rand = "0.8" +rcgen = "0.8" +serde_derive = "1.0" [profile.release] lto = true diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 0e6916a0b..69a344d4a 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -95,6 +95,7 @@ actix-http-test = { version = "3.0.0-beta.2", features = ["openssl"] } actix-tls = { version = "3.0.0-beta.2", features = ["openssl"] } criterion = "0.3" env_logger = "0.8" +rcgen = "0.8" serde_derive = "1.0" tls-openssl = { version = "0.10", package = "openssl" } tls-rustls = { version = "0.19", package = "rustls" } diff --git a/actix-http/tests/test_openssl.rs b/actix-http/tests/test_openssl.rs index 188516f83..f44968baa 100644 --- a/actix-http/tests/test_openssl.rs +++ b/actix-http/tests/test_openssl.rs @@ -14,7 +14,11 @@ use actix_service::{fn_service, ServiceFactoryExt}; use bytes::{Bytes, BytesMut}; use futures_util::future::{err, ok, ready}; use futures_util::stream::{once, Stream, StreamExt}; -use openssl::ssl::{AlpnError, SslAcceptor, SslFiletype, SslMethod}; +use openssl::{ + pkey::PKey, + ssl::{SslAcceptor, SslMethod}, + x509::X509, +}; async fn load_body(stream: S) -> Result where @@ -34,29 +38,26 @@ where Ok(body) } -fn ssl_acceptor() -> SslAcceptor { - // load ssl keys +fn tls_config() -> SslAcceptor { + 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 cert = X509::from_pem(cert_file.as_bytes()).unwrap(); + let key = PKey::private_key_from_pem(key_file.as_bytes()).unwrap(); + let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap(); - builder - .set_private_key_file("../tests/key.pem", SslFiletype::PEM) - .unwrap(); - builder - .set_certificate_chain_file("../tests/cert.pem") - .unwrap(); + builder.set_certificate(&cert).unwrap(); + builder.set_private_key(&key).unwrap(); + builder.set_alpn_select_callback(|_, protos| { const H2: &[u8] = b"\x02h2"; - const H11: &[u8] = b"\x08http/1.1"; if protos.windows(3).any(|window| window == H2) { Ok(b"h2") - } else if protos.windows(9).any(|window| window == H11) { - Ok(b"http/1.1") } else { - Err(AlpnError::NOACK) + Err(openssl::ssl::AlpnError::NOACK) } }); - builder - .set_alpn_protos(b"\x08http/1.1\x02h2") - .expect("Can not contrust SslAcceptor"); + builder.set_alpn_protos(b"\x02h2").unwrap(); builder.build() } @@ -66,7 +67,7 @@ async fn test_h2() -> io::Result<()> { let srv = test_server(move || { HttpService::build() .h2(|_| ok::<_, Error>(Response::Ok().finish())) - .openssl(ssl_acceptor()) + .openssl(tls_config()) .map_err(|_| ()) }) .await; @@ -85,7 +86,7 @@ async fn test_h2_1() -> io::Result<()> { assert_eq!(req.version(), Version::HTTP_2); ok::<_, Error>(Response::Ok().finish()) }) - .openssl(ssl_acceptor()) + .openssl(tls_config()) .map_err(|_| ()) }) .await; @@ -104,7 +105,7 @@ async fn test_h2_body() -> io::Result<()> { let body = load_body(req.take_payload()).await?; Ok::<_, Error>(Response::Ok().body(body)) }) - .openssl(ssl_acceptor()) + .openssl(tls_config()) .map_err(|_| ()) }) .await; @@ -133,7 +134,7 @@ async fn test_h2_content_length() { ]; ok::<_, ()>(Response::new(statuses[indx])) }) - .openssl(ssl_acceptor()) + .openssl(tls_config()) .map_err(|_| ()) }) .await; @@ -195,7 +196,7 @@ async fn test_h2_headers() { } ok::<_, ()>(builder.body(data.clone())) }) - .openssl(ssl_acceptor()) + .openssl(tls_config()) .map_err(|_| ()) }).await; @@ -234,7 +235,7 @@ async fn test_h2_body2() { let mut srv = test_server(move || { HttpService::build() .h2(|_| ok::<_, ()>(Response::Ok().body(STR))) - .openssl(ssl_acceptor()) + .openssl(tls_config()) .map_err(|_| ()) }) .await; @@ -252,7 +253,7 @@ async fn test_h2_head_empty() { let mut srv = test_server(move || { HttpService::build() .finish(|_| ok::<_, ()>(Response::Ok().body(STR))) - .openssl(ssl_acceptor()) + .openssl(tls_config()) .map_err(|_| ()) }) .await; @@ -276,7 +277,7 @@ async fn test_h2_head_binary() { let mut srv = test_server(move || { HttpService::build() .h2(|_| ok::<_, ()>(Response::Ok().body(STR))) - .openssl(ssl_acceptor()) + .openssl(tls_config()) .map_err(|_| ()) }) .await; @@ -299,7 +300,7 @@ async fn test_h2_head_binary2() { let srv = test_server(move || { HttpService::build() .h2(|_| ok::<_, ()>(Response::Ok().body(STR))) - .openssl(ssl_acceptor()) + .openssl(tls_config()) .map_err(|_| ()) }) .await; @@ -323,7 +324,7 @@ async fn test_h2_body_length() { Response::Ok().body(body::SizedStream::new(STR.len() as u64, body)), ) }) - .openssl(ssl_acceptor()) + .openssl(tls_config()) .map_err(|_| ()) }) .await; @@ -348,7 +349,7 @@ async fn test_h2_body_chunked_explicit() { .streaming(body), ) }) - .openssl(ssl_acceptor()) + .openssl(tls_config()) .map_err(|_| ()) }) .await; @@ -376,7 +377,7 @@ async fn test_h2_response_http_error_handling() { .body(STR), ) })) - .openssl(ssl_acceptor()) + .openssl(tls_config()) .map_err(|_| ()) }) .await; @@ -394,7 +395,7 @@ async fn test_h2_service_error() { let mut srv = test_server(move || { HttpService::build() .h2(|_| err::(ErrorBadRequest("error"))) - .openssl(ssl_acceptor()) + .openssl(tls_config()) .map_err(|_| ()) }) .await; @@ -418,7 +419,7 @@ async fn test_h2_on_connect() { assert!(req.extensions().contains::()); ok::<_, ()>(Response::Ok().finish()) }) - .openssl(ssl_acceptor()) + .openssl(tls_config()) .map_err(|_| ()) }) .await; diff --git a/actix-http/tests/test_rustls.rs b/actix-http/tests/test_rustls.rs index 59ffcfeb0..a36400910 100644 --- a/actix-http/tests/test_rustls.rs +++ b/actix-http/tests/test_rustls.rs @@ -31,14 +31,19 @@ where Ok(body) } -fn ssl_acceptor() -> RustlsServerConfig { - // load ssl keys +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(File::open("../tests/cert.pem").unwrap()); - let key_file = &mut BufReader::new(File::open("../tests/key.pem").unwrap()); + let cert_file = &mut BufReader::new(cert_file.as_bytes()); + let key_file = &mut BufReader::new(key_file.as_bytes()); + let cert_chain = certs(cert_file).unwrap(); let mut keys = pkcs8_private_keys(key_file).unwrap(); config.set_single_cert(cert_chain, keys.remove(0)).unwrap(); + config } @@ -47,7 +52,7 @@ async fn test_h1() -> io::Result<()> { let srv = test_server(move || { HttpService::build() .h1(|_| future::ok::<_, Error>(Response::Ok().finish())) - .rustls(ssl_acceptor()) + .rustls(tls_config()) }) .await; @@ -61,7 +66,7 @@ async fn test_h2() -> io::Result<()> { let srv = test_server(move || { HttpService::build() .h2(|_| future::ok::<_, Error>(Response::Ok().finish())) - .rustls(ssl_acceptor()) + .rustls(tls_config()) }) .await; @@ -79,7 +84,7 @@ async fn test_h1_1() -> io::Result<()> { assert_eq!(req.version(), Version::HTTP_11); future::ok::<_, Error>(Response::Ok().finish()) }) - .rustls(ssl_acceptor()) + .rustls(tls_config()) }) .await; @@ -97,7 +102,7 @@ async fn test_h2_1() -> io::Result<()> { assert_eq!(req.version(), Version::HTTP_2); future::ok::<_, Error>(Response::Ok().finish()) }) - .rustls(ssl_acceptor()) + .rustls(tls_config()) }) .await; @@ -115,7 +120,7 @@ async fn test_h2_body1() -> io::Result<()> { let body = load_body(req.take_payload()).await?; Ok::<_, Error>(Response::Ok().body(body)) }) - .rustls(ssl_acceptor()) + .rustls(tls_config()) }) .await; @@ -143,7 +148,7 @@ async fn test_h2_content_length() { ]; future::ok::<_, ()>(Response::new(statuses[indx])) }) - .rustls(ssl_acceptor()) + .rustls(tls_config()) }) .await; @@ -203,7 +208,7 @@ async fn test_h2_headers() { } future::ok::<_, ()>(config.body(data.clone())) }) - .rustls(ssl_acceptor()) + .rustls(tls_config()) }).await; let response = srv.sget("/").send().await.unwrap(); @@ -241,7 +246,7 @@ async fn test_h2_body2() { let mut srv = test_server(move || { HttpService::build() .h2(|_| future::ok::<_, ()>(Response::Ok().body(STR))) - .rustls(ssl_acceptor()) + .rustls(tls_config()) }) .await; @@ -258,7 +263,7 @@ async fn test_h2_head_empty() { let mut srv = test_server(move || { HttpService::build() .finish(|_| ok::<_, ()>(Response::Ok().body(STR))) - .rustls(ssl_acceptor()) + .rustls(tls_config()) }) .await; @@ -284,7 +289,7 @@ async fn test_h2_head_binary() { let mut srv = test_server(move || { HttpService::build() .h2(|_| ok::<_, ()>(Response::Ok().body(STR))) - .rustls(ssl_acceptor()) + .rustls(tls_config()) }) .await; @@ -309,7 +314,7 @@ async fn test_h2_head_binary2() { let srv = test_server(move || { HttpService::build() .h2(|_| ok::<_, ()>(Response::Ok().body(STR))) - .rustls(ssl_acceptor()) + .rustls(tls_config()) }) .await; @@ -335,7 +340,7 @@ async fn test_h2_body_length() { Response::Ok().body(body::SizedStream::new(STR.len() as u64, body)), ) }) - .rustls(ssl_acceptor()) + .rustls(tls_config()) }) .await; @@ -359,7 +364,7 @@ async fn test_h2_body_chunked_explicit() { .streaming(body), ) }) - .rustls(ssl_acceptor()) + .rustls(tls_config()) }) .await; @@ -388,7 +393,7 @@ async fn test_h2_response_http_error_handling() { ) })) })) - .rustls(ssl_acceptor()) + .rustls(tls_config()) }) .await; @@ -405,7 +410,7 @@ async fn test_h2_service_error() { let mut srv = test_server(move || { HttpService::build() .h2(|_| err::(error::ErrorBadRequest("error"))) - .rustls(ssl_acceptor()) + .rustls(tls_config()) }) .await; @@ -422,7 +427,7 @@ async fn test_h1_service_error() { let mut srv = test_server(move || { HttpService::build() .h1(|_| err::(error::ErrorBadRequest("error"))) - .rustls(ssl_acceptor()) + .rustls(tls_config()) }) .await; diff --git a/awc/tests/test_connector.rs b/awc/tests/test_connector.rs index 4e4fa5833..fd725506d 100644 --- a/awc/tests/test_connector.rs +++ b/awc/tests/test_connector.rs @@ -7,17 +7,23 @@ use actix_http_test::test_server; use actix_service::{map_config, ServiceFactoryExt}; use actix_web::http::Version; use actix_web::{dev::AppConfig, web, App, HttpResponse}; -use openssl::ssl::{SslAcceptor, SslConnector, SslFiletype, SslMethod, SslVerifyMode}; +use openssl::{ + pkey::PKey, + ssl::{SslAcceptor, SslConnector, SslMethod, SslVerifyMode}, + x509::X509, +}; + +fn tls_config() -> SslAcceptor { + 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 cert = X509::from_pem(cert_file.as_bytes()).unwrap(); + let key = PKey::private_key_from_pem(key_file.as_bytes()).unwrap(); -fn ssl_acceptor() -> SslAcceptor { - // load ssl keys let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap(); - builder - .set_private_key_file("../tests/key.pem", SslFiletype::PEM) - .unwrap(); - builder - .set_certificate_chain_file("../tests/cert.pem") - .unwrap(); + builder.set_certificate(&cert).unwrap(); + builder.set_private_key(&key).unwrap(); + builder.set_alpn_select_callback(|_, protos| { const H2: &[u8] = b"\x02h2"; if protos.windows(3).any(|window| window == H2) { @@ -27,6 +33,7 @@ fn ssl_acceptor() -> SslAcceptor { } }); builder.set_alpn_protos(b"\x02h2").unwrap(); + builder.build() } @@ -38,7 +45,7 @@ async fn test_connection_window_size() { App::new().service(web::resource("/").route(web::to(HttpResponse::Ok))), |_| AppConfig::default(), )) - .openssl(ssl_acceptor()) + .openssl(tls_config()) .map_err(|_| ()) }) .await; diff --git a/awc/tests/test_ssl_client.rs b/awc/tests/test_ssl_client.rs index b93c729e5..08aa125cd 100644 --- a/awc/tests/test_ssl_client.rs +++ b/awc/tests/test_ssl_client.rs @@ -11,17 +11,23 @@ use actix_service::{map_config, pipeline_factory, ServiceFactoryExt}; use actix_web::http::Version; use actix_web::{dev::AppConfig, web, App, HttpResponse}; use futures_util::future::ok; -use openssl::ssl::{SslAcceptor, SslConnector, SslFiletype, SslMethod, SslVerifyMode}; +use openssl::{ + pkey::PKey, + ssl::{SslAcceptor, SslConnector, SslMethod, SslVerifyMode}, + x509::X509, +}; + +fn tls_config() -> SslAcceptor { + 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 cert = X509::from_pem(cert_file.as_bytes()).unwrap(); + let key = PKey::private_key_from_pem(key_file.as_bytes()).unwrap(); -fn ssl_acceptor() -> SslAcceptor { - // load ssl keys let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap(); - builder - .set_private_key_file("../tests/key.pem", SslFiletype::PEM) - .unwrap(); - builder - .set_certificate_chain_file("../tests/cert.pem") - .unwrap(); + builder.set_certificate(&cert).unwrap(); + builder.set_private_key(&key).unwrap(); + builder.set_alpn_select_callback(|_, protos| { const H2: &[u8] = b"\x02h2"; if protos.windows(3).any(|window| window == H2) { @@ -31,6 +37,7 @@ fn ssl_acceptor() -> SslAcceptor { } }); builder.set_alpn_protos(b"\x02h2").unwrap(); + builder.build() } @@ -51,7 +58,7 @@ async fn test_connection_reuse_h2() { App::new().service(web::resource("/").route(web::to(HttpResponse::Ok))), |_| AppConfig::default(), )) - .openssl(ssl_acceptor()) + .openssl(tls_config()) .map_err(|_| ()), ) }) diff --git a/src/lib.rs b/src/lib.rs index 73390a55e..16b2ab186 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -99,8 +99,10 @@ pub mod test; pub(crate) mod types; pub mod web; +#[cfg(feature = "cookies")] +pub use actix_http::cookie; pub use actix_http::Response as HttpResponse; -pub use actix_http::{body, cookie, http, Error, HttpMessage, ResponseError, Result}; +pub use actix_http::{body, http, Error, HttpMessage, ResponseError, Result}; pub use actix_rt as rt; pub use actix_web_codegen::*; diff --git a/src/middleware/compat.rs b/src/middleware/compat.rs index 97742e92c..6f60264b1 100644 --- a/src/middleware/compat.rs +++ b/src/middleware/compat.rs @@ -123,18 +123,24 @@ impl MapServiceResponseBody for ServiceRespons #[cfg(test)] mod tests { + // easier to code when cookies feature is disabled + #![allow(unused_imports)] + use super::*; use actix_service::IntoService; use crate::dev::ServiceRequest; use crate::http::StatusCode; - use crate::middleware::{Compress, Condition, Logger}; + use crate::middleware::{self, Condition, Logger}; use crate::test::{call_service, init_service, TestRequest}; use crate::{web, App, HttpResponse}; #[actix_rt::test] + #[cfg(feature = "cookies")] async fn test_scope_middleware() { + use crate::middleware::Compress; + let logger = Logger::default(); let compress = Compress::default(); @@ -154,7 +160,10 @@ mod tests { } #[actix_rt::test] + #[cfg(feature = "cookies")] async fn test_resource_scope_middleware() { + use crate::middleware::Compress; + let logger = Logger::default(); let compress = Compress::default(); diff --git a/src/request.rs b/src/request.rs index d62fca300..514b7466e 100644 --- a/src/request.rs +++ b/src/request.rs @@ -429,12 +429,14 @@ mod tests { } #[test] + #[cfg(feature = "cookies")] fn test_no_request_cookies() { let req = TestRequest::default().to_http_request(); assert!(req.cookies().unwrap().is_empty()); } #[test] + #[cfg(feature = "cookies")] fn test_request_cookies() { let req = TestRequest::default() .append_header((header::COOKIE, "cookie1=value1")) diff --git a/src/test.rs b/src/test.rs index d576838de..2ec4252b1 100644 --- a/src/test.rs +++ b/src/test.rs @@ -6,10 +6,12 @@ use std::sync::mpsc; use std::{fmt, net, thread, time}; use actix_codec::{AsyncRead, AsyncWrite, Framed}; +#[cfg(feature = "cookies")] +use actix_http::cookie::Cookie; use actix_http::http::header::{ContentType, IntoHeaderPair}; use actix_http::http::{Method, StatusCode, Uri, Version}; use actix_http::test::TestRequest as HttpTestRequest; -use actix_http::{cookie::Cookie, ws, Extensions, HttpService, Request}; +use actix_http::{ws, Extensions, HttpService, Request}; use actix_router::{Path, ResourceDef, Url}; use actix_rt::{time::sleep, System}; use actix_service::{map_config, IntoService, IntoServiceFactory, Service, ServiceFactory}; @@ -438,6 +440,7 @@ impl TestRequest { } /// Set cookie for this request. + #[cfg(feature = "cookies")] pub fn cookie(mut self, cookie: Cookie<'_>) -> Self { self.req.cookie(cookie); self diff --git a/tests/cert.pem b/tests/cert.pem deleted file mode 100644 index 0eeb6721d..000000000 --- a/tests/cert.pem +++ /dev/null @@ -1,19 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIDEDCCAfgCCQCQdmIZc/Ib/jANBgkqhkiG9w0BAQsFADBKMQswCQYDVQQGEwJ1 -czELMAkGA1UECAwCY2ExCzAJBgNVBAcMAnNmMSEwHwYJKoZIhvcNAQkBFhJmYWZo -cmQ5MUBnbWFpbC5jb20wHhcNMTkxMTE5MTEwNjU1WhcNMjkxMTE2MTEwNjU1WjBK -MQswCQYDVQQGEwJ1czELMAkGA1UECAwCY2ExCzAJBgNVBAcMAnNmMSEwHwYJKoZI -hvcNAQkBFhJmYWZocmQ5MUBnbWFpbC5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IB -DwAwggEKAoIBAQDcnaz12CKzUL7248V7Axhms/O9UQXfAdw0yolEfC3P5jADa/1C -+kLWKjAc2coqDSbGsrsR6KiH2g06Kunx+tSGqUO+Sct7HEehmxndiSwx/hfMWezy -XRe/olcHFTeCk/Tllz4xGEplhPua6GLhJygLOhAMiV8cwCYrgyPqsDduExLDFCqc -K2xntIPreumXpiE3QY4+MWyteiJko4IWDFf/UwwsdCY5MlFfw1F/Uv9vz7FfOfvu -GccHd/ex8cOwotUqd6emZb+0bVE24Sv8U+yLnHIVx/tOkxgMAnJEpAnf2G3Wp3zU -b2GJosbmfGaf+xTfnGGhTLLL7kCtva+NvZr5AgMBAAEwDQYJKoZIhvcNAQELBQAD -ggEBANftoL8zDGrjCwWvct8kOOqset2ukK8vjIGwfm88CKsy0IfSochNz2qeIu9R -ZuO7c0pfjmRkir9ZQdq9vXgG3ccL9UstFsferPH9W3YJ83kgXg3fa0EmCiN/0hwz -6Ij1ZBiN1j3+d6+PJPgyYFNu2nGwox5mJ9+aRAGe0/9c63PEOY8P2TI4HsiPmYSl -fFR8k/03vr6e+rTKW85BgctjvYKe/TnFxeCQ7dZ+na7vlEtch4tNmy6O/vEk2kCt -5jW0DUxhmRsv2wGmfFRI0+LotHjoXQQZi6nN5aGL3odaGF3gYwIVlZNd3AdkwDQz -BzG0ZwXuDDV9bSs3MfWEWcy4xuU= ------END CERTIFICATE----- diff --git a/tests/key.pem b/tests/key.pem deleted file mode 100644 index a6d308168..000000000 --- a/tests/key.pem +++ /dev/null @@ -1,28 +0,0 @@ ------BEGIN PRIVATE KEY----- -MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDcnaz12CKzUL72 -48V7Axhms/O9UQXfAdw0yolEfC3P5jADa/1C+kLWKjAc2coqDSbGsrsR6KiH2g06 -Kunx+tSGqUO+Sct7HEehmxndiSwx/hfMWezyXRe/olcHFTeCk/Tllz4xGEplhPua -6GLhJygLOhAMiV8cwCYrgyPqsDduExLDFCqcK2xntIPreumXpiE3QY4+MWyteiJk -o4IWDFf/UwwsdCY5MlFfw1F/Uv9vz7FfOfvuGccHd/ex8cOwotUqd6emZb+0bVE2 -4Sv8U+yLnHIVx/tOkxgMAnJEpAnf2G3Wp3zUb2GJosbmfGaf+xTfnGGhTLLL7kCt -va+NvZr5AgMBAAECggEBAKoU0UwzVgVCQgca8Jt2dnBvWYDhnxIfYAI/BvaKedMm -1ms87OKfB7oOiksjyI0E2JklH72dzZf2jm4CuZt5UjGC+xwPzlTaJ4s6hQVbBHyC -NRyxU1BCXtW5tThbrhD4OjxqjmLRJEIB9OunLtwAEQoeuFLB8Va7+HFhR+Zd9k3f -7aVA93pC5A50NRbZlke4miJ3Q8n7ZF0+UmxkBfm3fbqLk7aMWkoEKwLLTadjRlu1 -bBp0YDStX66I/p1kujqBOdh6VpPvxFOa1sV9pq0jeiGc9YfSkzRSKzIn8GoyviFB -fHeszQdNlcnrSDSNnMABAw+ZpxUO7SCaftjwejEmKZUCgYEA+TY43VpmV95eY7eo -WKwGepiHE0fwQLuKGELmZdZI80tFi73oZMuiB5WzwmkaKGcJmm7KGE9KEvHQCo9j -xvmktBR0VEZH8pmVfun+4h6+0H7m/NKMBBeOyv/IK8jBgHjkkB6e6nmeR7CqTxCw -tf9tbajl1QN8gNzXZSjBDT/lanMCgYEA4qANOKOSiEARtgwyXQeeSJcM2uPv6zF3 -ffM7vjSedtuEOHUSVeyBP/W8KDt7zyPppO/WNbURHS+HV0maS9yyj6zpVS2HGmbs -3fetswsQ+zYVdokW89x4oc2z4XOGHd1LcSlyhRwPt0u2g1E9L0irwTQLWU0npFmG -PRf7sN9+LeMCgYAGkDUDL2ROoB6gRa/7Vdx90hKMoXJkYgwLA4gJ2pDlR3A3c/Lw -5KQJyxmG3zm/IqeQF6be6QesZA30mT4peV2rGHbP2WH/s6fKReNelSy1VQJEWk8x -tGUgV4gwDwN5nLV4TjYlOrq+bJqvpmLhCC8bmj0jVQosYqSRl3cuICasnQKBgGlV -VO/Xb1su1EyWPK5qxRIeSxZOTYw2sMB01nbgxCqge0M2fvA6/hQ5ZlwY0cIEgits -YlcSMsMq/TAAANxz1vbaupUhlSMbZcsBvNV0Nk9c4vr2Wxm7hsJF9u66IEMvQUp2 -pkjiMxfR9CHzF4orr9EcHI5EQ0Grbq5kwFKEfoRbAoGAcWoFPILeJOlp2yW/Ds3E -g2fQdI9BAamtEZEaslJmZMmsDTg5ACPcDkOSFEQIaJ7wLPXeZy74FVk/NrY5F8Gz -bjX9OD/xzwp852yW5L9r62vYJakAlXef5jI6CFdYKDDCcarU0S7W5k6kq9n+wrBR -i1NklYmUAMr2q59uJA5zsic= ------END PRIVATE KEY----- diff --git a/tests/test_httpserver.rs b/tests/test_httpserver.rs index 34d57008d..3aa1d36b0 100644 --- a/tests/test_httpserver.rs +++ b/tests/test_httpserver.rs @@ -73,15 +73,22 @@ async fn test_start() { #[cfg(feature = "openssl")] fn ssl_acceptor() -> std::io::Result { - use openssl::ssl::{SslAcceptor, SslFiletype, SslMethod}; - // load ssl keys + use openssl::{ + pkey::PKey, + ssl::{SslAcceptor, SslMethod}, + x509::X509, + }; + + 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 cert = X509::from_pem(cert_file.as_bytes()).unwrap(); + let key = PKey::private_key_from_pem(key_file.as_bytes()).unwrap(); + let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap(); - builder - .set_private_key_file("tests/key.pem", SslFiletype::PEM) - .unwrap(); - builder - .set_certificate_chain_file("tests/cert.pem") - .unwrap(); + builder.set_certificate(&cert).unwrap(); + builder.set_private_key(&key).unwrap(); + Ok(builder) } diff --git a/tests/test_server.rs b/tests/test_server.rs index c2e627a3f..2466730f9 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -21,6 +21,11 @@ use flate2::{ Compression, }; use futures_util::ready; +use openssl::{ + pkey::PKey, + ssl::{SslAcceptor, SslMethod}, + x509::X509, +}; use rand::{distributions::Alphanumeric, Rng}; use actix_web::dev::BodyEncoding; @@ -49,6 +54,30 @@ 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"; +fn openssl_config() -> SslAcceptor { + 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 cert = X509::from_pem(cert_file.as_bytes()).unwrap(); + let key = PKey::private_key_from_pem(key_file.as_bytes()).unwrap(); + + let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap(); + builder.set_certificate(&cert).unwrap(); + builder.set_private_key(&key).unwrap(); + + builder.set_alpn_select_callback(|_, protos| { + const H2: &[u8] = b"\x02h2"; + if protos.windows(3).any(|window| window == H2) { + Ok(b"h2") + } else { + Err(openssl::ssl::AlpnError::NOACK) + } + }); + builder.set_alpn_protos(b"\x02h2").unwrap(); + + builder.build() +} + struct TestBody { data: Bytes, chunk_size: usize, @@ -700,18 +729,8 @@ async fn test_brotli_encoding_large() { #[cfg(feature = "openssl")] #[actix_rt::test] async fn test_brotli_encoding_large_openssl() { - // load ssl keys - use openssl::ssl::{SslAcceptor, SslFiletype, SslMethod}; - let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap(); - builder - .set_private_key_file("tests/key.pem", SslFiletype::PEM) - .unwrap(); - builder - .set_certificate_chain_file("tests/cert.pem") - .unwrap(); - let data = STR.repeat(10); - let srv = test::start_with(test::config().openssl(builder.build()), move || { + let srv = test::start_with(test::config().openssl(openssl_config()), move || { App::new().service(web::resource("/").route(web::to(|bytes: Bytes| { HttpResponse::Ok() .encoding(actix_web::http::ContentEncoding::Identity) @@ -739,53 +758,72 @@ async fn test_brotli_encoding_large_openssl() { } #[cfg(all(feature = "rustls", feature = "openssl"))] -#[actix_rt::test] -async fn test_reading_deflate_encoding_large_random_rustls() { - use rustls::internal::pemfile::{certs, pkcs8_private_keys}; - use rustls::{NoClientAuth, ServerConfig}; - use std::fs::File; +mod plus_rustls { use std::io::BufReader; - let data = rand::thread_rng() - .sample_iter(&Alphanumeric) - .take(160_000) - .map(char::from) - .collect::(); + use rustls::{ + internal::pemfile::{certs, pkcs8_private_keys}, + NoClientAuth, ServerConfig as RustlsServerConfig, + }; - // load ssl keys - let mut config = ServerConfig::new(NoClientAuth::new()); - let cert_file = &mut BufReader::new(File::open("tests/cert.pem").unwrap()); - let key_file = &mut BufReader::new(File::open("tests/key.pem").unwrap()); - let cert_chain = certs(cert_file).unwrap(); - let mut keys = pkcs8_private_keys(key_file).unwrap(); - config.set_single_cert(cert_chain, keys.remove(0)).unwrap(); + use super::*; - let srv = test::start_with(test::config().rustls(config), || { - App::new().service(web::resource("/").route(web::to(|bytes: Bytes| { - HttpResponse::Ok() - .encoding(actix_web::http::ContentEncoding::Identity) - .body(bytes) - }))) - }); + fn rustls_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(); - // encode data - let mut e = ZlibEncoder::new(Vec::new(), Compression::default()); - e.write_all(data.as_ref()).unwrap(); - let enc = e.finish().unwrap(); + let mut 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()); - // client request - let req = srv - .post("/") - .insert_header((actix_web::http::header::CONTENT_ENCODING, "deflate")) - .send_stream(TestBody::new(Bytes::from(enc), 1024)); + let cert_chain = certs(cert_file).unwrap(); + let mut keys = pkcs8_private_keys(key_file).unwrap(); + config.set_single_cert(cert_chain, keys.remove(0)).unwrap(); - let mut response = req.await.unwrap(); - assert!(response.status().is_success()); + config + } - // read response - let bytes = response.body().await.unwrap(); - assert_eq!(bytes.len(), data.len()); - assert_eq!(bytes, Bytes::from(data)); + #[actix_rt::test] + async fn test_reading_deflate_encoding_large_random_rustls() { + use rustls::internal::pemfile::{certs, pkcs8_private_keys}; + use rustls::{NoClientAuth, ServerConfig}; + use std::fs::File; + use std::io::BufReader; + + let data = rand::thread_rng() + .sample_iter(&Alphanumeric) + .take(160_000) + .map(char::from) + .collect::(); + + let srv = test::start_with(test::config().rustls(rustls_config()), || { + App::new().service(web::resource("/").route(web::to(|bytes: Bytes| { + HttpResponse::Ok() + .encoding(actix_web::http::ContentEncoding::Identity) + .body(bytes) + }))) + }); + + // encode data + let mut e = ZlibEncoder::new(Vec::new(), Compression::default()); + e.write_all(data.as_ref()).unwrap(); + let enc = e.finish().unwrap(); + + // client request + let req = srv + .post("/") + .insert_header((actix_web::http::header::CONTENT_ENCODING, "deflate")) + .send_stream(TestBody::new(Bytes::from(enc), 1024)); + + let mut response = req.await.unwrap(); + assert!(response.status().is_success()); + + // read response + let bytes = response.body().await.unwrap(); + assert_eq!(bytes.len(), data.len()); + assert_eq!(bytes, Bytes::from(data)); + } } #[actix_rt::test] From 308b70b03917aa505781a2cc551612279abee74c Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Sun, 14 Feb 2021 09:36:18 -0800 Subject: [PATCH 041/339] fix potential over read (#1991) --- actix-http/src/h1/dispatcher.rs | 88 ++++++++++++++++++++++----------- 1 file changed, 59 insertions(+), 29 deletions(-) diff --git a/actix-http/src/h1/dispatcher.rs b/actix-http/src/h1/dispatcher.rs index 2e729b78d..cb15bc1e9 100644 --- a/actix-http/src/h1/dispatcher.rs +++ b/actix-http/src/h1/dispatcher.rs @@ -728,6 +728,53 @@ where let mut read_some = false; 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. + + 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. + + */ + cx.waker().wake_by_ref(); + } + + return Ok(false); + } + // grow buffer if necessary. let remaining = this.read_buf.capacity() - this.read_buf.len(); if remaining < LW_BUFFER_SIZE { @@ -735,35 +782,18 @@ where } match actix_codec::poll_read_buf(io.as_mut(), cx, this.read_buf) { - Poll::Pending => return Ok(false), Poll::Ready(Ok(n)) => { if n == 0 { return Ok(true); - } else { - // Return early when read buf exceed decoder's max buffer size. - if this.read_buf.len() >= super::decoder::MAX_BUFFER_SIZE { - // at this point it's not known io is still scheduled to - // be waked up. so force wake up dispatcher just in case. - // TODO: figure out the overhead. - if this.payload.is_none() { - // When dispatcher has a payload. The responsibility of - // wake up stream would be shift to PayloadSender. - // Therefore no self wake up is needed. - cx.waker().wake_by_ref(); - } - return Ok(false); - } - - read_some = true; } + read_some = true; } + Poll::Pending => return Ok(false), Poll::Ready(Err(err)) => { - return if err.kind() == io::ErrorKind::WouldBlock { - Ok(false) - } else if err.kind() == io::ErrorKind::ConnectionReset && read_some { - Ok(true) - } else { - Err(DispatchError::Io(err)) + return match err.kind() { + io::ErrorKind::WouldBlock => Ok(false), + io::ErrorKind::ConnectionReset if read_some => Ok(true), + _ => Err(DispatchError::Io(err)), } } } @@ -985,7 +1015,7 @@ mod tests { None, ); - futures_util::pin_mut!(h1); + actix_rt::pin!(h1); match h1.as_mut().poll(cx) { Poll::Pending => panic!(), @@ -1025,7 +1055,7 @@ mod tests { None, ); - futures_util::pin_mut!(h1); + actix_rt::pin!(h1); assert!(matches!(&h1.inner, DispatcherState::Normal(_))); @@ -1079,7 +1109,7 @@ mod tests { None, ); - futures_util::pin_mut!(h1); + actix_rt::pin!(h1); assert!(matches!(&h1.inner, DispatcherState::Normal(_))); @@ -1138,7 +1168,7 @@ mod tests { ", ); - futures_util::pin_mut!(h1); + actix_rt::pin!(h1); assert!(h1.as_mut().poll(cx).is_pending()); assert!(matches!(&h1.inner, DispatcherState::Normal(_))); @@ -1210,7 +1240,7 @@ mod tests { ", ); - futures_util::pin_mut!(h1); + actix_rt::pin!(h1); assert!(h1.as_mut().poll(cx).is_ready()); assert!(matches!(&h1.inner, DispatcherState::Normal(_))); @@ -1271,7 +1301,7 @@ mod tests { ", ); - futures_util::pin_mut!(h1); + actix_rt::pin!(h1); assert!(h1.as_mut().poll(cx).is_ready()); assert!(matches!(&h1.inner, DispatcherState::Upgrade(_))); From 68d1bd88b13836e2b0ad785b84dcf36d1f96861c Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Sun, 14 Feb 2021 10:13:05 -0800 Subject: [PATCH 042/339] remove unused flag upgrade (#1992) --- actix-http/src/h1/dispatcher.rs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/actix-http/src/h1/dispatcher.rs b/actix-http/src/h1/dispatcher.rs index cb15bc1e9..f7d7f32c3 100644 --- a/actix-http/src/h1/dispatcher.rs +++ b/actix-http/src/h1/dispatcher.rs @@ -40,7 +40,6 @@ bitflags! { const SHUTDOWN = 0b0000_0100; const READ_DISCONNECT = 0b0000_1000; const WRITE_DISCONNECT = 0b0001_0000; - const UPGRADE = 0b0010_0000; } } @@ -215,10 +214,7 @@ where U::Error: fmt::Display, { fn can_read(&self, cx: &mut Context<'_>) -> bool { - if self - .flags - .intersects(Flags::READ_DISCONNECT | Flags::UPGRADE) - { + if self.flags.contains(Flags::READ_DISCONNECT) { false } else if let Some(ref info) = self.payload { info.need_read(cx) == PayloadStatus::Read @@ -501,7 +497,7 @@ where } /// Process one incoming request. - pub(self) fn poll_request( + fn poll_request( mut self: Pin<&mut Self>, cx: &mut Context<'_>, ) -> Result { From 0404b78b54bc80658630b1af588562aa7245d217 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Mon, 15 Feb 2021 11:24:46 +0000 Subject: [PATCH 043/339] improve body size docs --- actix-http/src/body.rs | 26 ++++++++++++++++++++++++++ actix-http/src/client/h2proto.rs | 1 + actix-http/src/encoding/encoder.rs | 1 + 3 files changed, 28 insertions(+) diff --git a/actix-http/src/body.rs b/actix-http/src/body.rs index 0dbe93a4a..4fd578c87 100644 --- a/actix-http/src/body.rs +++ b/actix-http/src/body.rs @@ -15,13 +15,39 @@ use crate::error::Error; /// Body size hint. #[derive(Debug, PartialEq, Copy, Clone)] pub enum BodySize { + /// Absence of body can be assumed from method or status code. + /// + /// 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`. Sized(u64), + + /// Unknown size body. + /// + /// Will not write Content-Length header. Can be used with chunked Transfer-Encoding. Stream, } impl BodySize { + /// Returns true if size hint indicates no or empty body. + /// + /// ``` + /// # 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)) } diff --git a/actix-http/src/client/h2proto.rs b/actix-http/src/client/h2proto.rs index a70bc1738..105e04c73 100644 --- a/actix-http/src/client/h2proto.rs +++ b/actix-http/src/client/h2proto.rs @@ -36,6 +36,7 @@ where B: MessageBody, { trace!("Sending client request: {:?} {:?}", head, body.size()); + let head_req = head.as_ref().method == Method::HEAD; let length = body.size(); let eof = matches!( diff --git a/actix-http/src/encoding/encoder.rs b/actix-http/src/encoding/encoder.rs index 366ecb8c4..ee0587fbd 100644 --- a/actix-http/src/encoding/encoder.rs +++ b/actix-http/src/encoding/encoder.rs @@ -78,6 +78,7 @@ impl Encoder { }); } } + ResponseBody::Body(Encoder { body, eof: false, From 55db3ec65c7bae11a29b9b088d924f2b6bd065c1 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Mon, 15 Feb 2021 12:20:43 +0000 Subject: [PATCH 044/339] split up http body module --- .github/workflows/ci.yml | 2 +- actix-http/src/body.rs | 740 --------------------------- actix-http/src/body/body.rs | 158 ++++++ actix-http/src/body/body_stream.rs | 59 +++ actix-http/src/body/message_body.rs | 142 +++++ actix-http/src/body/mod.rs | 251 +++++++++ actix-http/src/body/response_body.rs | 77 +++ actix-http/src/body/size.rs | 40 ++ actix-http/src/body/sized_stream.rs | 59 +++ awc/src/error.rs | 3 +- 10 files changed, 789 insertions(+), 742 deletions(-) delete mode 100644 actix-http/src/body.rs create mode 100644 actix-http/src/body/body.rs create mode 100644 actix-http/src/body/body_stream.rs create mode 100644 actix-http/src/body/message_body.rs create mode 100644 actix-http/src/body/mod.rs create mode 100644 actix-http/src/body/response_body.rs create mode 100644 actix-http/src/body/size.rs create mode 100644 actix-http/src/body/sized_stream.rs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0126238ef..d61c96963 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -87,7 +87,7 @@ jobs: && github.ref == 'refs/heads/master' run: | cargo install cargo-tarpaulin --vers "^0.13" - cargo tarpaulin --out Xml + cargo tarpaulin --out Xml --verbose - name: Upload to Codecov if: > matrix.target.os == 'ubuntu-latest' diff --git a/actix-http/src/body.rs b/actix-http/src/body.rs deleted file mode 100644 index 4fd578c87..000000000 --- a/actix-http/src/body.rs +++ /dev/null @@ -1,740 +0,0 @@ -//! Traits and structures to aid consuming and writing HTTP payloads. - -use std::{ - fmt, mem, - pin::Pin, - task::{Context, Poll}, -}; - -use bytes::{Bytes, BytesMut}; -use futures_core::{ready, Stream}; -use pin_project::pin_project; - -use crate::error::Error; - -/// Body size hint. -#[derive(Debug, PartialEq, Copy, Clone)] -pub enum BodySize { - /// Absence of body can be assumed from method or status code. - /// - /// 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`. - Sized(u64), - - /// Unknown size body. - /// - /// Will not write Content-Length header. Can be used with chunked Transfer-Encoding. - Stream, -} - -impl BodySize { - /// Returns true if size hint indicates no or empty body. - /// - /// ``` - /// # 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)) - } -} - -/// Type that implement this trait can be streamed to a peer. -pub trait MessageBody { - fn size(&self) -> BodySize; - - fn poll_next( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll>>; - - downcast_get_type_id!(); -} - -downcast!(MessageBody); - -impl MessageBody for () { - fn size(&self) -> BodySize { - BodySize::Empty - } - - fn poll_next( - self: Pin<&mut Self>, - _: &mut Context<'_>, - ) -> Poll>> { - Poll::Ready(None) - } -} - -impl MessageBody for Box { - 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) - } -} - -#[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 { - 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>> { - match self.project() { - ResponseBodyProj::Body(body) => body.poll_next(cx), - ResponseBodyProj::Other(body) => Pin::new(body).poll_next(cx), - } - } -} - -impl Stream for ResponseBody { - type Item = Result; - - fn poll_next( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll> { - match self.project() { - ResponseBodyProj::Body(body) => body.poll_next(cx), - ResponseBodyProj::Other(body) => Pin::new(body).poll_next(cx), - } - } -} - -/// Represents various types of HTTP message body. -pub enum Body { - /// 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(Box), -} - -impl Body { - /// Create body from slice (copy) - pub fn from_slice(s: &[u8]) -> Body { - Body::Bytes(Bytes::copy_from_slice(s)) - } - - /// Create body from generic message body. - pub fn from_message(body: B) -> Body { - Body::Message(Box::new(body)) - } -} - -impl MessageBody for Body { - fn size(&self) -> BodySize { - match self { - Body::None => BodySize::None, - Body::Empty => BodySize::Empty, - Body::Bytes(ref bin) => BodySize::Sized(bin.len() as u64), - Body::Message(ref body) => body.size(), - } - } - - fn poll_next( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll>> { - match self.get_mut() { - Body::None => Poll::Ready(None), - Body::Empty => Poll::Ready(None), - Body::Bytes(ref mut bin) => { - let len = bin.len(); - if len == 0 { - Poll::Ready(None) - } else { - Poll::Ready(Some(Ok(mem::take(bin)))) - } - } - Body::Message(body) => Pin::new(&mut **body).poll_next(cx), - } - } -} - -impl PartialEq for Body { - fn eq(&self, other: &Body) -> bool { - match *self { - Body::None => matches!(*other, Body::None), - Body::Empty => matches!(*other, Body::Empty), - Body::Bytes(ref b) => match *other { - Body::Bytes(ref b2) => b == b2, - _ => false, - }, - Body::Message(_) => false, - } - } -} - -impl fmt::Debug for Body { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match *self { - Body::None => write!(f, "Body::None"), - Body::Empty => write!(f, "Body::Empty"), - Body::Bytes(ref b) => write!(f, "Body::Bytes({:?})", b), - Body::Message(_) => write!(f, "Body::Message(_)"), - } - } -} - -impl From<&'static str> for Body { - fn from(s: &'static str) -> Body { - Body::Bytes(Bytes::from_static(s.as_ref())) - } -} - -impl From<&'static [u8]> for Body { - fn from(s: &'static [u8]) -> Body { - Body::Bytes(Bytes::from_static(s)) - } -} - -impl From> for Body { - fn from(vec: Vec) -> Body { - Body::Bytes(Bytes::from(vec)) - } -} - -impl From for Body { - fn from(s: String) -> Body { - s.into_bytes().into() - } -} - -impl<'a> From<&'a String> for Body { - fn from(s: &'a String) -> Body { - Body::Bytes(Bytes::copy_from_slice(AsRef::<[u8]>::as_ref(&s))) - } -} - -impl From for Body { - fn from(s: Bytes) -> Body { - Body::Bytes(s) - } -} - -impl From for Body { - fn from(s: BytesMut) -> Body { - Body::Bytes(s.freeze()) - } -} - -impl From for Body { - fn from(v: serde_json::Value) -> Body { - Body::Bytes(v.to_string().into()) - } -} - -impl From> for Body -where - S: Stream> + Unpin + 'static, -{ - fn from(s: SizedStream) -> Body { - Body::from_message(s) - } -} - -impl From> for Body -where - S: Stream> + Unpin + 'static, - E: Into + 'static, -{ - fn from(s: BodyStream) -> Body { - Body::from_message(s) - } -} - -impl MessageBody for Bytes { - 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())))) - } - } -} - -impl MessageBody for BytesMut { - 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()).freeze()))) - } - } -} - -impl MessageBody for &'static str { - 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(Bytes::from_static( - mem::take(self.get_mut()).as_ref(), - )))) - } - } -} - -impl MessageBody for Vec { - 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(Bytes::from(mem::take(self.get_mut()))))) - } - } -} - -impl MessageBody for String { - 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(Bytes::from( - mem::take(self.get_mut()).into_bytes(), - )))) - } - } -} - -/// Type represent streaming body. -/// Response does not contain `content-length` header and appropriate transfer encoding is used. -pub struct BodyStream { - stream: S, -} - -impl BodyStream -where - S: Stream> + Unpin, - E: Into, -{ - pub fn new(stream: S) -> Self { - BodyStream { stream } - } -} - -impl MessageBody for BodyStream -where - S: Stream> + Unpin, - E: Into, -{ - fn size(&self) -> BodySize { - BodySize::Stream - } - - /// Attempts to pull out the next value of the underlying [`Stream`]. - /// - /// Empty values are skipped to prevent [`BodyStream`]'s transmission being - /// ended on a zero-length chunk, but rather proceed until the underlying - /// [`Stream`] ends. - fn poll_next( - mut self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll>> { - loop { - let stream = &mut self.as_mut().stream; - return Poll::Ready(match ready!(Pin::new(stream).poll_next(cx)) { - Some(Ok(ref bytes)) if bytes.is_empty() => continue, - opt => opt.map(|res| res.map_err(Into::into)), - }); - } - } -} - -/// Type represent streaming body. This body implementation should be used -/// if total size of stream is known. Data get sent as is without using transfer encoding. -pub struct SizedStream { - size: u64, - stream: S, -} - -impl SizedStream -where - S: Stream> + Unpin, -{ - pub fn new(size: u64, stream: S) -> Self { - SizedStream { size, stream } - } -} - -impl MessageBody for SizedStream -where - S: Stream> + Unpin, -{ - fn size(&self) -> BodySize { - BodySize::Sized(self.size as u64) - } - - /// Attempts to pull out the next value of the underlying [`Stream`]. - /// - /// Empty values are skipped to prevent [`SizedStream`]'s transmission being - /// ended on a zero-length chunk, but rather proceed until the underlying - /// [`Stream`] ends. - fn poll_next( - mut self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll>> { - loop { - let stream = &mut self.as_mut().stream; - return Poll::Ready(match ready!(Pin::new(stream).poll_next(cx)) { - Some(Ok(ref bytes)) if bytes.is_empty() => continue, - val => val, - }); - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use futures_util::future::poll_fn; - use futures_util::pin_mut; - use futures_util::stream; - - impl Body { - pub(crate) fn get_ref(&self) -> &[u8] { - match *self { - Body::Bytes(ref bin) => &bin, - _ => panic!(), - } - } - } - - impl ResponseBody { - pub(crate) fn get_ref(&self) -> &[u8] { - match *self { - ResponseBody::Body(ref b) => b.get_ref(), - ResponseBody::Other(ref b) => b.get_ref(), - } - } - } - - #[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!("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!(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(), - BodySize::Sized(4) - ); - assert_eq!(Body::from_slice(b"test".as_ref()).get_ref(), b"test"); - let sb = Bytes::from(&b"test"[..]); - pin_mut!(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!(Body::from(Vec::from("test")).size(), BodySize::Sized(4)); - assert_eq!(Body::from(Vec::from("test")).get_ref(), b"test"); - let test_vec = Vec::from("test"); - pin_mut!(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!(Body::from(b.clone()).size(), BodySize::Sized(4)); - assert_eq!(Body::from(b.clone()).get_ref(), b"test"); - pin_mut!(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!(Body::from(b.clone()).size(), BodySize::Sized(4)); - assert_eq!(Body::from(b.clone()).get_ref(), b"test"); - pin_mut!(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!(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"); - pin_mut!(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::Empty); - assert!(poll_fn(|cx| Pin::new(&mut ()).poll_next(cx)) - .await - .is_none()); - } - - #[actix_rt::test] - async fn test_box() { - let val = Box::new(()); - pin_mut!(val); - assert_eq!(val.size(), BodySize::Empty); - assert!(poll_fn(|cx| val.as_mut().poll_next(cx)).await.is_none()); - } - - #[actix_rt::test] - async fn test_body_eq() { - assert!( - Body::Bytes(Bytes::from_static(b"1")) - == Body::Bytes(Bytes::from_static(b"1")) - ); - assert!(Body::Bytes(Bytes::from_static(b"1")) != Body::None); - } - - #[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')); - } - - #[actix_rt::test] - async fn test_serde_json() { - use serde_json::json; - assert_eq!( - Body::from(serde_json::Value::String("test".into())).size(), - BodySize::Sized(6) - ); - assert_eq!( - Body::from(json!({"test-key":"test-value"})).size(), - BodySize::Sized(25) - ); - } - - mod body_stream { - use super::*; - //use futures::task::noop_waker; - //use futures::stream::once; - - #[actix_rt::test] - async fn skips_empty_chunks() { - let body = BodyStream::new(stream::iter( - ["1", "", "2"] - .iter() - .map(|&v| Ok(Bytes::from(v)) as Result), - )); - pin_mut!(body); - - assert_eq!( - poll_fn(|cx| body.as_mut().poll_next(cx)) - .await - .unwrap() - .ok(), - Some(Bytes::from("1")), - ); - assert_eq!( - poll_fn(|cx| body.as_mut().poll_next(cx)) - .await - .unwrap() - .ok(), - Some(Bytes::from("2")), - ); - } - - /* Now it does not compile as it should - #[actix_rt::test] - async fn move_pinned_pointer() { - let (sender, receiver) = futures::channel::oneshot::channel(); - let mut body_stream = Ok(BodyStream::new(once(async { - let x = Box::new(0i32); - let y = &x; - receiver.await.unwrap(); - let _z = **y; - Ok::<_, ()>(Bytes::new()) - }))); - - let waker = noop_waker(); - let mut context = Context::from_waker(&waker); - pin_mut!(body_stream); - - let _ = body_stream.as_mut().unwrap().poll_next(&mut context); - sender.send(()).unwrap(); - let _ = std::mem::replace(&mut body_stream, Err([0; 32])).unwrap().poll_next(&mut context); - }*/ - } - - mod sized_stream { - use super::*; - - #[actix_rt::test] - async fn skips_empty_chunks() { - let body = SizedStream::new( - 2, - stream::iter(["1", "", "2"].iter().map(|&v| Ok(Bytes::from(v)))), - ); - pin_mut!(body); - assert_eq!( - poll_fn(|cx| body.as_mut().poll_next(cx)) - .await - .unwrap() - .ok(), - Some(Bytes::from("1")), - ); - assert_eq!( - poll_fn(|cx| body.as_mut().poll_next(cx)) - .await - .unwrap() - .ok(), - Some(Bytes::from("2")), - ); - } - } - - #[actix_rt::test] - async fn test_body_casting() { - let mut body = String::from("hello cast"); - let resp_body: &mut dyn MessageBody = &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/body.rs b/actix-http/src/body/body.rs new file mode 100644 index 000000000..a3fd7d41c --- /dev/null +++ b/actix-http/src/body/body.rs @@ -0,0 +1,158 @@ +use std::{ + fmt, mem, + pin::Pin, + task::{Context, Poll}, +}; + +use bytes::{Bytes, BytesMut}; +use futures_core::Stream; + +use crate::error::Error; + +use super::{BodySize, BodyStream, MessageBody, SizedStream}; + +/// Represents various types of HTTP message body. +pub enum Body { + /// 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(Box), +} + +impl Body { + /// Create body from slice (copy) + pub fn from_slice(s: &[u8]) -> Body { + Body::Bytes(Bytes::copy_from_slice(s)) + } + + /// Create body from generic message body. + pub fn from_message(body: B) -> Body { + Body::Message(Box::new(body)) + } +} + +impl MessageBody for Body { + fn size(&self) -> BodySize { + match self { + Body::None => BodySize::None, + Body::Empty => BodySize::Empty, + Body::Bytes(ref bin) => BodySize::Sized(bin.len() as u64), + Body::Message(ref body) => body.size(), + } + } + + fn poll_next( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + ) -> Poll>> { + match self.get_mut() { + Body::None => Poll::Ready(None), + Body::Empty => Poll::Ready(None), + Body::Bytes(ref mut bin) => { + let len = bin.len(); + if len == 0 { + Poll::Ready(None) + } else { + Poll::Ready(Some(Ok(mem::take(bin)))) + } + } + Body::Message(body) => Pin::new(&mut **body).poll_next(cx), + } + } +} + +impl PartialEq for Body { + fn eq(&self, other: &Body) -> bool { + match *self { + Body::None => matches!(*other, Body::None), + Body::Empty => matches!(*other, Body::Empty), + Body::Bytes(ref b) => match *other { + Body::Bytes(ref b2) => b == b2, + _ => false, + }, + Body::Message(_) => false, + } + } +} + +impl fmt::Debug for Body { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match *self { + Body::None => write!(f, "Body::None"), + Body::Empty => write!(f, "Body::Empty"), + Body::Bytes(ref b) => write!(f, "Body::Bytes({:?})", b), + Body::Message(_) => write!(f, "Body::Message(_)"), + } + } +} + +impl From<&'static str> for Body { + fn from(s: &'static str) -> Body { + Body::Bytes(Bytes::from_static(s.as_ref())) + } +} + +impl From<&'static [u8]> for Body { + fn from(s: &'static [u8]) -> Body { + Body::Bytes(Bytes::from_static(s)) + } +} + +impl From> for Body { + fn from(vec: Vec) -> Body { + Body::Bytes(Bytes::from(vec)) + } +} + +impl From for Body { + fn from(s: String) -> Body { + s.into_bytes().into() + } +} + +impl<'a> From<&'a String> for Body { + fn from(s: &'a String) -> Body { + Body::Bytes(Bytes::copy_from_slice(AsRef::<[u8]>::as_ref(&s))) + } +} + +impl From for Body { + fn from(s: Bytes) -> Body { + Body::Bytes(s) + } +} + +impl From for Body { + fn from(s: BytesMut) -> Body { + Body::Bytes(s.freeze()) + } +} + +impl From for Body { + fn from(v: serde_json::Value) -> Body { + Body::Bytes(v.to_string().into()) + } +} + +impl From> for Body +where + S: Stream> + Unpin + 'static, +{ + fn from(s: SizedStream) -> Body { + Body::from_message(s) + } +} + +impl From> for Body +where + S: Stream> + Unpin + 'static, + E: Into + 'static, +{ + fn from(s: BodyStream) -> Body { + Body::from_message(s) + } +} diff --git a/actix-http/src/body/body_stream.rs b/actix-http/src/body/body_stream.rs new file mode 100644 index 000000000..60e33b161 --- /dev/null +++ b/actix-http/src/body/body_stream.rs @@ -0,0 +1,59 @@ +use std::{ + pin::Pin, + task::{Context, Poll}, +}; + +use bytes::Bytes; +use futures_core::{ready, Stream}; + +use crate::error::Error; + +use super::{BodySize, MessageBody}; + +/// Streaming response wrapper. +/// +/// Response does not contain `Content-Length` header and appropriate transfer encoding is used. +pub struct BodyStream { + stream: S, +} + +impl BodyStream +where + S: Stream> + Unpin, + E: Into, +{ + pub fn new(stream: S) -> Self { + BodyStream { stream } + } +} + +impl MessageBody for BodyStream +where + S: Stream> + Unpin, + E: Into, +{ + fn size(&self) -> BodySize { + BodySize::Stream + } + + /// Attempts to pull out the next value of the underlying [`Stream`]. + /// + /// Empty values are skipped to prevent [`BodyStream`]'s transmission being + /// ended on a zero-length chunk, but rather proceed until the underlying + /// [`Stream`] ends. + fn poll_next( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + ) -> Poll>> { + loop { + let stream = &mut self.as_mut().stream; + + let chunk = match ready!(Pin::new(stream).poll_next(cx)) { + Some(Ok(ref bytes)) if bytes.is_empty() => continue, + opt => opt.map(|res| res.map_err(Into::into)), + }; + + return Poll::Ready(chunk); + } + } +} diff --git a/actix-http/src/body/message_body.rs b/actix-http/src/body/message_body.rs new file mode 100644 index 000000000..012329146 --- /dev/null +++ b/actix-http/src/body/message_body.rs @@ -0,0 +1,142 @@ +//! [`MessageBody`] trait and foreign implementations. + +use std::{ + mem, + pin::Pin, + task::{Context, Poll}, +}; + +use bytes::{Bytes, BytesMut}; + +use crate::error::Error; + +use super::BodySize; + +/// Type that implement this trait can be streamed to a peer. +pub trait MessageBody { + fn size(&self) -> BodySize; + + fn poll_next( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + ) -> Poll>>; + + downcast_get_type_id!(); +} + +downcast!(MessageBody); + +impl MessageBody for () { + fn size(&self) -> BodySize { + BodySize::Empty + } + + fn poll_next( + self: Pin<&mut Self>, + _: &mut Context<'_>, + ) -> Poll>> { + Poll::Ready(None) + } +} + +impl MessageBody for Box { + 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 Bytes { + 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())))) + } + } +} + +impl MessageBody for BytesMut { + 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()).freeze()))) + } + } +} + +impl MessageBody for &'static str { + 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(Bytes::from_static( + mem::take(self.get_mut()).as_ref(), + )))) + } + } +} + +impl MessageBody for Vec { + 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(Bytes::from(mem::take(self.get_mut()))))) + } + } +} + +impl MessageBody for String { + 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(Bytes::from( + mem::take(self.get_mut()).into_bytes(), + )))) + } + } +} diff --git a/actix-http/src/body/mod.rs b/actix-http/src/body/mod.rs new file mode 100644 index 000000000..1080c67e2 --- /dev/null +++ b/actix-http/src/body/mod.rs @@ -0,0 +1,251 @@ +//! Traits and structures to aid consuming and writing HTTP payloads. + +mod body; +mod body_stream; +mod message_body; +mod response_body; +mod size; +mod sized_stream; + +pub use self::body::Body; +pub use self::body_stream::BodyStream; +pub use self::message_body::MessageBody; +pub use self::response_body::ResponseBody; +pub use self::size::BodySize; +pub use self::sized_stream::SizedStream; + +#[cfg(test)] +mod tests { + use std::pin::Pin; + + use actix_rt::pin; + use bytes::{Bytes, BytesMut}; + use futures_util::{future::poll_fn, stream}; + + use super::*; + + impl Body { + pub(crate) fn get_ref(&self) -> &[u8] { + match *self { + Body::Bytes(ref bin) => &bin, + _ => panic!(), + } + } + } + + impl ResponseBody { + pub(crate) fn get_ref(&self) -> &[u8] { + match *self { + ResponseBody::Body(ref b) => b.get_ref(), + ResponseBody::Other(ref b) => b.get_ref(), + } + } + } + + #[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!("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!(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(), + BodySize::Sized(4) + ); + assert_eq!(Body::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!(Body::from(Vec::from("test")).size(), BodySize::Sized(4)); + assert_eq!(Body::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!(Body::from(b.clone()).size(), BodySize::Sized(4)); + assert_eq!(Body::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!(Body::from(b.clone()).size(), BodySize::Sized(4)); + assert_eq!(Body::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!(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"); + 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::Empty); + assert!(poll_fn(|cx| Pin::new(&mut ()).poll_next(cx)) + .await + .is_none()); + } + + #[actix_rt::test] + async fn test_box() { + 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()); + } + + #[actix_rt::test] + async fn test_body_eq() { + assert!( + Body::Bytes(Bytes::from_static(b"1")) + == Body::Bytes(Bytes::from_static(b"1")) + ); + assert!(Body::Bytes(Bytes::from_static(b"1")) != Body::None); + } + + #[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')); + } + + #[actix_rt::test] + async fn test_serde_json() { + use serde_json::json; + assert_eq!( + Body::from(serde_json::Value::String("test".into())).size(), + BodySize::Sized(6) + ); + assert_eq!( + Body::from(json!({"test-key":"test-value"})).size(), + BodySize::Sized(25) + ); + } + + #[actix_rt::test] + async fn body_stream_skips_empty_chunks() { + let body = BodyStream::new(stream::iter( + ["1", "", "2"] + .iter() + .map(|&v| Ok(Bytes::from(v)) as Result), + )); + pin!(body); + + assert_eq!( + poll_fn(|cx| body.as_mut().poll_next(cx)) + .await + .unwrap() + .ok(), + Some(Bytes::from("1")), + ); + assert_eq!( + poll_fn(|cx| body.as_mut().poll_next(cx)) + .await + .unwrap() + .ok(), + Some(Bytes::from("2")), + ); + } + + mod sized_stream { + use super::*; + + #[actix_rt::test] + async fn skips_empty_chunks() { + let body = SizedStream::new( + 2, + stream::iter(["1", "", "2"].iter().map(|&v| Ok(Bytes::from(v)))), + ); + pin!(body); + assert_eq!( + poll_fn(|cx| body.as_mut().poll_next(cx)) + .await + .unwrap() + .ok(), + Some(Bytes::from("1")), + ); + assert_eq!( + poll_fn(|cx| body.as_mut().poll_next(cx)) + .await + .unwrap() + .ok(), + Some(Bytes::from("2")), + ); + } + } + + #[actix_rt::test] + async fn test_body_casting() { + let mut body = String::from("hello cast"); + let resp_body: &mut dyn MessageBody = &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/response_body.rs b/actix-http/src/body/response_body.rs new file mode 100644 index 000000000..97141e11e --- /dev/null +++ b/actix-http/src/body/response_body.rs @@ -0,0 +1,77 @@ +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 { + 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>> { + match self.project() { + ResponseBodyProj::Body(body) => body.poll_next(cx), + ResponseBodyProj::Other(body) => Pin::new(body).poll_next(cx), + } + } +} + +impl Stream for ResponseBody { + type Item = Result; + + fn poll_next( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + ) -> Poll> { + match self.project() { + ResponseBodyProj::Body(body) => body.poll_next(cx), + ResponseBodyProj::Other(body) => Pin::new(body).poll_next(cx), + } + } +} diff --git a/actix-http/src/body/size.rs b/actix-http/src/body/size.rs new file mode 100644 index 000000000..775d5b8f1 --- /dev/null +++ b/actix-http/src/body/size.rs @@ -0,0 +1,40 @@ +/// Body size hint. +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum BodySize { + /// Absence of body can be assumed from method or status code. + /// + /// 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`. + Sized(u64), + + /// Unknown size body. + /// + /// Will not write Content-Length header. Can be used with chunked Transfer-Encoding. + Stream, +} + +impl BodySize { + /// Returns true if size hint indicates no or empty body. + /// + /// ``` + /// # 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)) + } +} diff --git a/actix-http/src/body/sized_stream.rs b/actix-http/src/body/sized_stream.rs new file mode 100644 index 000000000..af995a0fb --- /dev/null +++ b/actix-http/src/body/sized_stream.rs @@ -0,0 +1,59 @@ +use std::{ + pin::Pin, + task::{Context, Poll}, +}; + +use bytes::Bytes; +use futures_core::{ready, Stream}; + +use crate::error::Error; + +use super::{BodySize, MessageBody}; + +/// 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. +pub struct SizedStream { + size: u64, + stream: S, +} + +impl SizedStream +where + S: Stream> + Unpin, +{ + pub fn new(size: u64, stream: S) -> Self { + SizedStream { size, stream } + } +} + +impl MessageBody for SizedStream +where + S: Stream> + Unpin, +{ + fn size(&self) -> BodySize { + BodySize::Sized(self.size as u64) + } + + /// Attempts to pull out the next value of the underlying [`Stream`]. + /// + /// Empty values are skipped to prevent [`SizedStream`]'s transmission being + /// ended on a zero-length chunk, but rather proceed until the underlying + /// [`Stream`] ends. + fn poll_next( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + ) -> Poll>> { + loop { + let stream = &mut self.as_mut().stream; + + let chunk = match ready!(Pin::new(stream).poll_next(cx)) { + Some(Ok(ref bytes)) if bytes.is_empty() => continue, + val => val, + }; + + return Poll::Ready(chunk); + } + } +} diff --git a/awc/src/error.rs b/awc/src/error.rs index c60339f76..f86224e62 100644 --- a/awc/src/error.rs +++ b/awc/src/error.rs @@ -1,4 +1,5 @@ -//! Http client errors +//! 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; From c06572946855e0ac9fdd95d09f83f904374a9f07 Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Tue, 16 Feb 2021 00:27:14 -0800 Subject: [PATCH 045/339] rework client connection pool (#1994) --- actix-http/CHANGES.md | 5 + actix-http/Cargo.toml | 4 +- actix-http/src/client/connection.rs | 18 +- actix-http/src/client/h1proto.rs | 5 +- actix-http/src/client/pool.rs | 1052 ++++++++++++++------------- actix-http/src/error.rs | 4 - 6 files changed, 564 insertions(+), 524 deletions(-) diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 54f7357f1..6ba111eb3 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -4,7 +4,12 @@ ### Changed * Feature `cookies` is now optional and disabled by default. [#1981] +### Removed +* 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 ## 3.0.0-beta.3 - 2021-02-10 diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 69a344d4a..78fb55079 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -59,13 +59,11 @@ cfg-if = "1" cookie = { version = "0.14.1", features = ["percent-encode"], optional = true } derive_more = "0.99.5" encoding_rs = "0.8" -futures-channel = { version = "0.3.7", default-features = false, features = ["alloc"] } futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] } futures-util = { version = "0.3.7", default-features = false, features = ["alloc", "sink"] } h2 = "0.3.0" http = "0.2.2" httparse = "1.3" -indexmap = "1.3" itoa = "0.4" language-tags = "0.2" lazy_static = "1.4" @@ -79,9 +77,9 @@ serde = "1.0" serde_json = "1.0" serde_urlencoded = "0.7" sha-1 = "0.9" -slab = "0.4" smallvec = "1.6" time = { version = "0.2.23", default-features = false, features = ["std"] } +tokio = { version = "1.2", features = ["sync"] } # compression brotli2 = { version="0.3.2", optional = true } diff --git a/actix-http/src/client/connection.rs b/actix-http/src/client/connection.rs index 4c6a6dcb8..0b2c6e1df 100644 --- a/actix-http/src/client/connection.rs +++ b/actix-http/src/client/connection.rs @@ -103,7 +103,10 @@ pub(crate) trait ConnectionLifetime: AsyncRead + AsyncWrite + 'static { #[doc(hidden)] /// HTTP client connection -pub struct IoConnection { +pub struct IoConnection +where + T: AsyncWrite + Unpin + 'static, +{ io: Option>, created: time::Instant, pool: Option>, @@ -111,7 +114,7 @@ pub struct IoConnection { impl fmt::Debug for IoConnection where - T: fmt::Debug, + T: AsyncWrite + Unpin + fmt::Debug + 'static, { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self.io { @@ -138,6 +141,11 @@ impl IoConnection { pub(crate) fn into_inner(self) -> (ConnectionType, time::Instant) { (self.io.unwrap(), self.created) } + + #[cfg(test)] + pub(crate) fn into_parts(self) -> (ConnectionType, time::Instant, Acquired) { + (self.io.unwrap(), self.created, self.pool.unwrap()) + } } impl Connection for IoConnection @@ -202,7 +210,11 @@ where } #[allow(dead_code)] -pub(crate) enum EitherConnection { +pub(crate) enum EitherConnection +where + A: AsyncRead + AsyncWrite + Unpin + 'static, + B: AsyncRead + AsyncWrite + Unpin + 'static, +{ A(IoConnection), B(IoConnection), } diff --git a/actix-http/src/client/h1proto.rs b/actix-http/src/client/h1proto.rs index 92c3c0e1b..1ff5c7017 100644 --- a/actix-http/src/client/h1proto.rs +++ b/actix-http/src/client/h1proto.rs @@ -165,7 +165,10 @@ where #[doc(hidden)] /// HTTP client connection -pub struct H1Connection { +pub struct H1Connection +where + T: AsyncWrite + Unpin + 'static, +{ /// T should be `Unpin` io: Option, created: time::Instant, diff --git a/actix-http/src/client/pool.rs b/actix-http/src/client/pool.rs index 867ba5c0c..b0b1613ab 100644 --- a/actix-http/src/client/pool.rs +++ b/actix-http/src/client/pool.rs @@ -1,32 +1,27 @@ use std::cell::RefCell; use std::collections::VecDeque; use std::future::Future; +use std::ops::Deref; use std::pin::Pin; use std::rc::Rc; +use std::sync::Arc; use std::task::{Context, Poll}; use std::time::{Duration, Instant}; use actix_codec::{AsyncRead, AsyncWrite, ReadBuf}; use actix_rt::time::{sleep, Sleep}; use actix_service::Service; -use actix_utils::task::LocalWaker; use ahash::AHashMap; -use bytes::Bytes; -use futures_channel::oneshot; use futures_core::future::LocalBoxFuture; -use futures_util::future::{poll_fn, FutureExt}; -use h2::client::{Connection, SendRequest}; use http::uri::Authority; -use indexmap::IndexSet; use pin_project::pin_project; -use slab::Slab; +use tokio::sync::{OwnedSemaphorePermit, Semaphore}; use super::config::ConnectorConfig; -use super::connection::{ConnectionType, IoConnection}; +use super::connection::{ConnectionType, H2Connection, IoConnection}; use super::error::ConnectError; use super::h2proto::handshake; use super::Connect; -use crate::client::connection::H2Connection; #[derive(Clone, Copy, PartialEq)] /// Protocol version @@ -46,358 +41,279 @@ impl From for Key { } } -/// Connections pool -pub(crate) struct ConnectionPool(Rc, Rc>>); - -impl ConnectionPool +/// Connections pool for reuse Io type for certain [`http::uri::Authority`] as key +pub(crate) struct ConnectionPool where - Io: AsyncRead + AsyncWrite + Unpin + 'static, - T: Service + 'static, + Io: AsyncWrite + Unpin + 'static, { - pub(crate) fn new(connector: T, config: ConnectorConfig) -> Self { - let connector_rc = Rc::new(connector); - let inner_rc = Rc::new(RefCell::new(Inner { - config, - acquired: 0, - waiters: Slab::new(), - waiters_queue: IndexSet::new(), - available: AHashMap::default(), - waker: LocalWaker::new(), - })); + connector: Rc, + inner: ConnectionPoolInner, +} - // start support future - actix_rt::spawn(ConnectorPoolSupport { - connector: Rc::clone(&connector_rc), - inner: Rc::clone(&inner_rc), - }); +/// wrapper type for check the ref count of Rc. +struct ConnectionPoolInner(Rc>) +where + Io: AsyncWrite + Unpin + 'static; - ConnectionPool(connector_rc, inner_rc) +impl ConnectionPoolInner +where + Io: AsyncWrite + Unpin + 'static, +{ + /// spawn a async for graceful shutdown h1 Io type with a timeout. + fn close(&self, conn: ConnectionType) { + if let Some(timeout) = self.config.disconnect_timeout { + if let ConnectionType::H1(io) = conn { + actix_rt::spawn(CloseConnection::new(io, timeout)); + } + } } } -impl Clone for ConnectionPool +impl Clone for ConnectionPoolInner where - Io: 'static, + Io: AsyncWrite + Unpin + 'static, { fn clone(&self) -> Self { - ConnectionPool(self.0.clone(), self.1.clone()) + Self(Rc::clone(&self.0)) } } -impl Drop for ConnectionPool { - fn drop(&mut self) { - // wake up the ConnectorPoolSupport when dropping so it can exit properly. - self.1.borrow().waker.wake(); - } -} - -impl Service for ConnectionPool +impl Deref for ConnectionPoolInner where + Io: AsyncWrite + Unpin + 'static, +{ + type Target = ConnectionPoolInnerPriv; + + fn deref(&self) -> &Self::Target { + &*self.0 + } +} + +impl Drop for ConnectionPoolInner +where + Io: AsyncWrite + Unpin + 'static, +{ + fn drop(&mut self) { + // When strong count is one it means the pool is dropped + // remove and drop all Io types. + if Rc::strong_count(&self.0) == 1 { + self.permits.close(); + std::mem::take(&mut *self.available.borrow_mut()) + .into_iter() + .for_each(|(_, conns)| { + conns.into_iter().for_each(|pooled| self.close(pooled.conn)) + }); + } + } +} + +struct ConnectionPoolInnerPriv +where + Io: AsyncWrite + Unpin + 'static, +{ + config: ConnectorConfig, + available: RefCell>>>, + permits: Arc, +} + +impl ConnectionPool +where + Io: AsyncWrite + Unpin + 'static, +{ + /// construct a new connection pool. + /// + /// [`super::config::ConnectorConfig`]'s `limit` is used as the max permits allowed + /// for on flight connections. + /// + /// The pool can only have equal to `limit` amount of requests spawning/using Io type + /// concurrently. + /// + /// Any requests beyond limit would be wait in fifo order and get notified in async + /// manner by [`tokio::sync::Semaphore`] + pub(crate) fn new(connector: S, config: ConnectorConfig) -> Self { + let permits = Arc::new(Semaphore::new(config.limit)); + let available = RefCell::new(AHashMap::default()); + let connector = Rc::new(connector); + + let inner = ConnectionPoolInner(Rc::new(ConnectionPoolInnerPriv { + config, + available, + permits, + })); + + Self { connector, inner } + } +} + +impl Clone for ConnectionPool +where + Io: AsyncWrite + Unpin + 'static, +{ + fn clone(&self) -> Self { + Self { + connector: self.connector.clone(), + inner: self.inner.clone(), + } + } +} + +impl Service for ConnectionPool +where + S: Service + 'static, Io: AsyncRead + AsyncWrite + Unpin + 'static, - T: Service + 'static, { type Response = IoConnection; type Error = ConnectError; type Future = LocalBoxFuture<'static, Result, ConnectError>>; fn poll_ready(&self, cx: &mut Context<'_>) -> Poll> { - self.0.poll_ready(cx) + self.connector.poll_ready(cx) } fn call(&self, req: Connect) -> Self::Future { - let connector = self.0.clone(); - let inner = self.1.clone(); + let connector = self.connector.clone(); + let inner = self.inner.clone(); - let fut = async move { + Box::pin(async move { let key = if let Some(authority) = req.uri.authority() { authority.clone().into() } else { return Err(ConnectError::Unresolved); }; - // acquire connection - match poll_fn(|cx| Poll::Ready(inner.borrow_mut().acquire(&key, cx))).await { - Acquire::Acquired(io, created) => { - // use existing connection - Ok(IoConnection::new( - io, - created, - Some(Acquired(key, Some(inner))), - )) + // acquire an owned permit and carry it with connection + let permit = inner + .permits + .clone() + .acquire_owned() + .await + // TODO: use specific error for semaphore acquire error + .map_err(|_| ConnectError::NoRecords)?; + + // check if there is idle connection for given key. + let mut map = inner.available.borrow_mut(); + + let mut conn = None; + if let Some(conns) = map.get_mut(&key) { + let now = Instant::now(); + while let Some(mut c) = conns.pop_front() { + // check the lifetime and drop connection that live for too long. + if (now - c.used) > inner.config.conn_keep_alive + || (now - c.created) > inner.config.conn_lifetime + { + inner.close(c.conn); + // check if the connection is still usable. + } else { + if let ConnectionType::H1(ref mut io) = c.conn { + let check = ConnectionCheckFuture { io }; + match check.await { + ConnectionState::Break => { + inner.close(c.conn); + continue; + } + ConnectionState::Skip => continue, + ConnectionState::Live => conn = Some(c), + } + } else { + conn = Some(c); + } + + break; + } } - Acquire::Available => { - // open tcp connection + }; + + // drop map early to end the borrow_mut of RefCell. + drop(map); + + // construct acquired. It's used to put Io type back to pool/ close the Io type. + // permit is carried with the whole lifecycle of Acquired. + let acquired = Some(Acquired { key, inner, permit }); + + // match the connection and spawn new one if did not get anything. + match conn { + Some(conn) => Ok(IoConnection::new(conn.conn, conn.created, acquired)), + None => { let (io, proto) = connector.call(req).await?; - let config = inner.borrow().config.clone(); - - let guard = OpenGuard::new(key, inner); - if proto == Protocol::Http1 { Ok(IoConnection::new( ConnectionType::H1(io), Instant::now(), - Some(guard.consume()), + acquired, )) } else { - let (sender, connection) = handshake(io, &config).await?; + let config = &acquired.as_ref().unwrap().inner.config; + let (sender, connection) = handshake(io, config).await?; Ok(IoConnection::new( ConnectionType::H2(H2Connection::new(sender, connection)), Instant::now(), - Some(guard.consume()), + acquired, )) } } - _ => { - // connection is not available, wait - let (rx, token) = inner.borrow_mut().wait_for(req); - - let guard = WaiterGuard::new(key, token, inner); - let res = match rx.await { - Err(_) => Err(ConnectError::Disconnected), - Ok(res) => res, - }; - guard.consume(); - res - } } + }) + } +} + +/// Type for check the connection and determine if it's usable. +struct ConnectionCheckFuture<'a, Io> { + io: &'a mut Io, +} + +enum ConnectionState { + Live, + Break, + Skip, +} + +impl Future for ConnectionCheckFuture<'_, Io> +where + Io: AsyncRead + Unpin, +{ + type Output = ConnectionState; + + // this future is only used to get access to Context. + // It should never return Poll::Pending. + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let this = self.get_mut(); + let mut buf = [0; 2]; + let mut read_buf = ReadBuf::new(&mut buf); + + let state = match Pin::new(&mut this.io).poll_read(cx, &mut read_buf) { + // io is pending and new data would wake up it. + Poll::Pending => ConnectionState::Live, + // io have data inside. drop it. + Poll::Ready(Ok(())) if !read_buf.filled().is_empty() => { + ConnectionState::Break + } + // otherwise skip to next. + _ => ConnectionState::Skip, }; - fut.boxed_local() + Poll::Ready(state) } } -struct WaiterGuard -where - Io: AsyncRead + AsyncWrite + Unpin + 'static, -{ - key: Key, - token: usize, - inner: Option>>>, -} - -impl WaiterGuard -where - Io: AsyncRead + AsyncWrite + Unpin + 'static, -{ - fn new(key: Key, token: usize, inner: Rc>>) -> Self { - Self { - key, - token, - inner: Some(inner), - } - } - - fn consume(mut self) { - let _ = self.inner.take(); - } -} - -impl Drop for WaiterGuard -where - Io: AsyncRead + AsyncWrite + Unpin + 'static, -{ - fn drop(&mut self) { - if let Some(i) = self.inner.take() { - let mut inner = i.as_ref().borrow_mut(); - inner.release_waiter(&self.key, self.token); - inner.check_availability(); - } - } -} - -struct OpenGuard -where - Io: AsyncRead + AsyncWrite + Unpin + 'static, -{ - key: Key, - inner: Option>>>, -} - -impl OpenGuard -where - Io: AsyncRead + AsyncWrite + Unpin + 'static, -{ - fn new(key: Key, inner: Rc>>) -> Self { - Self { - key, - inner: Some(inner), - } - } - - fn consume(mut self) -> Acquired { - Acquired(self.key.clone(), self.inner.take()) - } -} - -impl Drop for OpenGuard -where - Io: AsyncRead + AsyncWrite + Unpin + 'static, -{ - fn drop(&mut self) { - if let Some(i) = self.inner.take() { - let mut inner = i.as_ref().borrow_mut(); - inner.release(); - inner.check_availability(); - } - } -} - -enum Acquire { - Acquired(ConnectionType, Instant), - Available, - NotAvailable, -} - -struct AvailableConnection { - io: ConnectionType, +struct PooledConnection { + conn: ConnectionType, used: Instant, created: Instant, } -pub(crate) struct Inner { - config: ConnectorConfig, - acquired: usize, - available: AHashMap>>, - waiters: Slab< - Option<( - Connect, - oneshot::Sender, ConnectError>>, - )>, - >, - waiters_queue: IndexSet<(Key, usize)>, - waker: LocalWaker, -} - -impl Inner { - fn reserve(&mut self) { - self.acquired += 1; - } - - fn release(&mut self) { - self.acquired -= 1; - } - - fn release_waiter(&mut self, key: &Key, token: usize) { - self.waiters.remove(token); - let _ = self.waiters_queue.shift_remove(&(key.clone(), token)); - } -} - -impl Inner -where - Io: AsyncRead + AsyncWrite + Unpin + 'static, -{ - /// connection is not available, wait - fn wait_for( - &mut self, - connect: Connect, - ) -> ( - oneshot::Receiver, ConnectError>>, - usize, - ) { - let (tx, rx) = oneshot::channel(); - - let key: Key = connect.uri.authority().unwrap().clone().into(); - let entry = self.waiters.vacant_entry(); - let token = entry.key(); - entry.insert(Some((connect, tx))); - assert!(self.waiters_queue.insert((key, token))); - - (rx, token) - } - - fn acquire(&mut self, key: &Key, cx: &mut Context<'_>) -> Acquire { - // check limits - if self.config.limit > 0 && self.acquired >= self.config.limit { - return Acquire::NotAvailable; - } - - self.reserve(); - - // check if open connection is available - // cleanup stale connections at the same time - if let Some(ref mut connections) = self.available.get_mut(key) { - let now = Instant::now(); - while let Some(conn) = connections.pop_back() { - // check if it still usable - if (now - conn.used) > self.config.conn_keep_alive - || (now - conn.created) > self.config.conn_lifetime - { - if let Some(timeout) = self.config.disconnect_timeout { - if let ConnectionType::H1(io) = conn.io { - actix_rt::spawn(CloseConnection::new(io, timeout)); - } - } - } else { - let mut io = conn.io; - let mut buf = [0; 2]; - let mut read_buf = ReadBuf::new(&mut buf); - if let ConnectionType::H1(ref mut s) = io { - match Pin::new(s).poll_read(cx, &mut read_buf) { - Poll::Pending => {} - Poll::Ready(Ok(())) if !read_buf.filled().is_empty() => { - if let Some(timeout) = self.config.disconnect_timeout { - if let ConnectionType::H1(io) = io { - actix_rt::spawn(CloseConnection::new( - io, timeout, - )); - } - } - continue; - } - _ => continue, - } - } - return Acquire::Acquired(io, conn.created); - } - } - } - Acquire::Available - } - - fn release_conn(&mut self, key: &Key, io: ConnectionType, created: Instant) { - self.acquired -= 1; - self.available - .entry(key.clone()) - .or_insert_with(VecDeque::new) - .push_back(AvailableConnection { - io, - created, - used: Instant::now(), - }); - self.check_availability(); - } - - fn release_close(&mut self, io: ConnectionType) { - self.acquired -= 1; - if let Some(timeout) = self.config.disconnect_timeout { - if let ConnectionType::H1(io) = io { - actix_rt::spawn(CloseConnection::new(io, timeout)); - } - } - self.check_availability(); - } - - fn check_availability(&self) { - if !self.waiters_queue.is_empty() && self.acquired < self.config.limit { - self.waker.wake(); - } - } -} - -#[pin_project::pin_project] -struct CloseConnection { - io: T, +#[pin_project] +struct CloseConnection { + io: Io, #[pin] timeout: Sleep, } -impl CloseConnection +impl CloseConnection where - T: AsyncWrite + Unpin, + Io: AsyncWrite + Unpin, { - fn new(io: T, timeout: Duration) -> Self { + fn new(io: Io, timeout: Duration) -> Self { CloseConnection { io, timeout: sleep(timeout), @@ -405,9 +321,9 @@ where } } -impl Future for CloseConnection +impl Future for CloseConnection where - T: AsyncWrite + Unpin, + Io: AsyncWrite + Unpin, { type Output = (); @@ -416,230 +332,340 @@ where match this.timeout.poll(cx) { Poll::Ready(_) => Poll::Ready(()), - Poll::Pending => match Pin::new(this.io).poll_shutdown(cx) { - Poll::Ready(_) => Poll::Ready(()), - Poll::Pending => Poll::Pending, - }, + Poll::Pending => Pin::new(this.io).poll_shutdown(cx).map(|_| ()), } } } -#[pin_project] -struct ConnectorPoolSupport +pub(crate) struct Acquired where - Io: AsyncRead + AsyncWrite + Unpin + 'static, + Io: AsyncWrite + Unpin + 'static, { - connector: Rc, - inner: Rc>>, -} - -impl Future for ConnectorPoolSupport -where - Io: AsyncRead + AsyncWrite + Unpin + 'static, - T: Service, - T::Future: 'static, -{ - type Output = (); - - fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - let this = self.project(); - - if Rc::strong_count(this.inner) == 1 { - // If we are last copy of Inner it means the ConnectionPool is already gone - // and we are safe to exit. - return Poll::Ready(()); - } - - let mut inner = this.inner.borrow_mut(); - inner.waker.register(cx.waker()); - - // check waiters - loop { - let (key, token) = { - if let Some((key, token)) = inner.waiters_queue.get_index(0) { - (key.clone(), *token) - } else { - break; - } - }; - if inner.waiters.get(token).unwrap().is_none() { - continue; - } - - match inner.acquire(&key, cx) { - Acquire::NotAvailable => break, - Acquire::Acquired(io, created) => { - let tx = inner.waiters.get_mut(token).unwrap().take().unwrap().1; - if let Err(conn) = tx.send(Ok(IoConnection::new( - io, - created, - Some(Acquired(key.clone(), Some(this.inner.clone()))), - ))) { - let (io, created) = conn.unwrap().into_inner(); - inner.release_conn(&key, io, created); - } - } - Acquire::Available => { - let (connect, tx) = - inner.waiters.get_mut(token).unwrap().take().unwrap(); - OpenWaitingConnection::spawn( - key.clone(), - tx, - this.inner.clone(), - this.connector.call(connect), - inner.config.clone(), - ); - } - } - let _ = inner.waiters_queue.swap_remove_index(0); - } - - Poll::Pending - } -} - -#[pin_project::pin_project(PinnedDrop)] -struct OpenWaitingConnection -where - Io: AsyncRead + AsyncWrite + Unpin + 'static, -{ - #[pin] - fut: F, key: Key, - h2: Option< - LocalBoxFuture< - 'static, - Result<(SendRequest, Connection), h2::Error>, - >, - >, - rx: Option, ConnectError>>>, - inner: Option>>>, - config: ConnectorConfig, + inner: ConnectionPoolInner, + permit: OwnedSemaphorePermit, } -impl OpenWaitingConnection +impl Acquired where - F: Future> + 'static, Io: AsyncRead + AsyncWrite + Unpin + 'static, { - fn spawn( - key: Key, - rx: oneshot::Sender, ConnectError>>, - inner: Rc>>, - fut: F, - config: ConnectorConfig, - ) { - actix_rt::spawn(OpenWaitingConnection { - key, - fut, - h2: None, - rx: Some(rx), - inner: Some(inner), - config, + // close the Io type. + pub(crate) fn close(&mut self, conn: IoConnection) { + let (conn, _) = conn.into_inner(); + self.inner.close(conn); + } + + // put the Io type back to pool. + pub(crate) fn release(&mut self, conn: IoConnection) { + let (io, created) = conn.into_inner(); + let Acquired { key, inner, .. } = self; + inner + .available + .borrow_mut() + .entry(key.clone()) + .or_insert_with(VecDeque::new) + .push_back(PooledConnection { + conn: io, + created, + used: Instant::now(), + }); + + // a no op bind. used to stop clippy warning without adding allow attribute. + let _permit = &mut self.permit; + } +} + +#[cfg(test)] +mod test { + use super::*; + + use std::cell::Cell; + use std::io; + + use http::Uri; + + use crate::client::connection::IoConnection; + + // A stream type always return pending on async read. + // mock a usable tcp stream that ready to be used as client + struct TestStream(Rc>); + + impl Drop for TestStream { + fn drop(&mut self) { + self.0.set(self.0.get() - 1); + } + } + + impl AsyncRead for TestStream { + fn poll_read( + self: Pin<&mut Self>, + _: &mut Context<'_>, + _: &mut ReadBuf<'_>, + ) -> Poll> { + Poll::Pending + } + } + + impl AsyncWrite for TestStream { + fn poll_write( + self: Pin<&mut Self>, + _: &mut Context<'_>, + _: &[u8], + ) -> Poll> { + unimplemented!() + } + + fn poll_flush( + self: Pin<&mut Self>, + _: &mut Context<'_>, + ) -> Poll> { + unimplemented!() + } + + fn poll_shutdown( + self: Pin<&mut Self>, + _: &mut Context<'_>, + ) -> Poll> { + Poll::Ready(Ok(())) + } + } + + struct TestPoolConnector { + generated: Rc>, + } + + impl Service for TestPoolConnector { + type Response = (TestStream, Protocol); + type Error = ConnectError; + type Future = LocalBoxFuture<'static, Result>; + + fn poll_ready(&self, _: &mut Context<'_>) -> Poll> { + unimplemented!("poll_ready is not used in test") + } + + fn call(&self, _: Connect) -> Self::Future { + self.generated.set(self.generated.get() + 1); + let generated = self.generated.clone(); + Box::pin(async { Ok((TestStream(generated), Protocol::Http1)) }) + } + } + + fn release(conn: IoConnection) + where + T: AsyncRead + AsyncWrite + Unpin + 'static, + { + let (conn, created, mut acquired) = conn.into_parts(); + acquired.release(IoConnection::new(conn, created, None)); + } + + #[actix_rt::test] + async fn test_pool_limit() { + let connector = TestPoolConnector { + generated: Rc::new(Cell::new(0)), + }; + + let config = ConnectorConfig { + limit: 1, + ..Default::default() + }; + + let pool = super::ConnectionPool::new(connector, config); + + let req = Connect { + uri: Uri::from_static("http://localhost"), + addr: None, + }; + + let conn = pool.call(req.clone()).await.unwrap(); + + let waiting = Rc::new(Cell::new(true)); + + let waiting_clone = waiting.clone(); + actix_rt::spawn(async move { + actix_rt::time::sleep(Duration::from_millis(100)).await; + waiting_clone.set(false); + drop(conn); }); - } -} - -#[pin_project::pinned_drop] -impl PinnedDrop for OpenWaitingConnection -where - Io: AsyncRead + AsyncWrite + Unpin + 'static, -{ - fn drop(self: Pin<&mut Self>) { - if let Some(inner) = self.project().inner.take() { - let mut inner = inner.as_ref().borrow_mut(); - inner.release(); - inner.check_availability(); - } - } -} - -impl Future for OpenWaitingConnection -where - F: Future>, - Io: AsyncRead + AsyncWrite + Unpin, -{ - type Output = (); - - fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - let this = self.as_mut().project(); - - if let Some(ref mut h2) = this.h2 { - return match Pin::new(h2).poll(cx) { - Poll::Ready(Ok((sender, connection))) => { - let rx = this.rx.take().unwrap(); - let _ = rx.send(Ok(IoConnection::new( - ConnectionType::H2(H2Connection::new(sender, connection)), - Instant::now(), - Some(Acquired(this.key.clone(), this.inner.take())), - ))); - Poll::Ready(()) - } - Poll::Pending => Poll::Pending, - Poll::Ready(Err(err)) => { - let _ = this.inner.take(); - if let Some(rx) = this.rx.take() { - let _ = rx.send(Err(ConnectError::H2(err))); - } - Poll::Ready(()) - } - }; - } - - match this.fut.poll(cx) { - Poll::Ready(Err(err)) => { - let _ = this.inner.take(); - if let Some(rx) = this.rx.take() { - let _ = rx.send(Err(err)); - } - Poll::Ready(()) - } - Poll::Ready(Ok((io, proto))) => { - if proto == Protocol::Http1 { - let rx = this.rx.take().unwrap(); - let _ = rx.send(Ok(IoConnection::new( - ConnectionType::H1(io), - Instant::now(), - Some(Acquired(this.key.clone(), this.inner.take())), - ))); - Poll::Ready(()) - } else { - *this.h2 = Some(handshake(io, this.config).boxed_local()); - self.poll(cx) - } - } - Poll::Pending => Poll::Pending, - } - } -} - -pub(crate) struct Acquired(Key, Option>>>); - -impl Acquired -where - T: AsyncRead + AsyncWrite + Unpin + 'static, -{ - pub(crate) fn close(&mut self, conn: IoConnection) { - if let Some(inner) = self.1.take() { - let (io, _) = conn.into_inner(); - inner.as_ref().borrow_mut().release_close(io); - } - } - pub(crate) fn release(&mut self, conn: IoConnection) { - if let Some(inner) = self.1.take() { - let (io, created) = conn.into_inner(); - inner - .as_ref() - .borrow_mut() - .release_conn(&self.0, io, created); - } - } -} - -impl Drop for Acquired { - fn drop(&mut self) { - if let Some(inner) = self.1.take() { - inner.as_ref().borrow_mut().release(); - } + + assert!(waiting.get()); + + let now = Instant::now(); + let conn = pool.call(req).await.unwrap(); + + release(conn); + assert!(!waiting.get()); + assert!(now.elapsed() >= Duration::from_millis(100)); + } + + #[actix_rt::test] + async fn test_pool_keep_alive() { + let generated = Rc::new(Cell::new(0)); + let generated_clone = generated.clone(); + + let connector = TestPoolConnector { generated }; + + let config = ConnectorConfig { + conn_keep_alive: Duration::from_secs(1), + ..Default::default() + }; + + let pool = super::ConnectionPool::new(connector, config); + + let req = Connect { + uri: Uri::from_static("http://localhost"), + addr: None, + }; + + let conn = pool.call(req.clone()).await.unwrap(); + assert_eq!(1, generated_clone.get()); + release(conn); + + let conn = pool.call(req.clone()).await.unwrap(); + assert_eq!(1, generated_clone.get()); + release(conn); + + actix_rt::time::sleep(Duration::from_millis(1500)).await; + actix_rt::task::yield_now().await; + + let conn = pool.call(req).await.unwrap(); + // Note: spawned recycle connection is not ran yet. + // This is tokio current thread runtime specific behavior. + assert_eq!(2, generated_clone.get()); + + // yield task so the old connection is properly dropped. + actix_rt::task::yield_now().await; + assert_eq!(1, generated_clone.get()); + + release(conn); + } + + #[actix_rt::test] + async fn test_pool_lifetime() { + let generated = Rc::new(Cell::new(0)); + let generated_clone = generated.clone(); + + let connector = TestPoolConnector { generated }; + + let config = ConnectorConfig { + conn_lifetime: Duration::from_secs(1), + ..Default::default() + }; + + let pool = super::ConnectionPool::new(connector, config); + + let req = Connect { + uri: Uri::from_static("http://localhost"), + addr: None, + }; + + let conn = pool.call(req.clone()).await.unwrap(); + assert_eq!(1, generated_clone.get()); + release(conn); + + let conn = pool.call(req.clone()).await.unwrap(); + assert_eq!(1, generated_clone.get()); + release(conn); + + actix_rt::time::sleep(Duration::from_millis(1500)).await; + actix_rt::task::yield_now().await; + + let conn = pool.call(req).await.unwrap(); + // Note: spawned recycle connection is not ran yet. + // This is tokio current thread runtime specific behavior. + assert_eq!(2, generated_clone.get()); + + // yield task so the old connection is properly dropped. + actix_rt::task::yield_now().await; + assert_eq!(1, generated_clone.get()); + + release(conn); + } + + #[actix_rt::test] + async fn test_pool_authority_key() { + let generated = Rc::new(Cell::new(0)); + let generated_clone = generated.clone(); + + let connector = TestPoolConnector { generated }; + + let config = ConnectorConfig::default(); + + let pool = super::ConnectionPool::new(connector, config); + + let req = Connect { + uri: Uri::from_static("https://crates.io"), + addr: None, + }; + + let conn = pool.call(req.clone()).await.unwrap(); + assert_eq!(1, generated_clone.get()); + release(conn); + + let conn = pool.call(req).await.unwrap(); + assert_eq!(1, generated_clone.get()); + release(conn); + + let req = Connect { + uri: Uri::from_static("https://google.com"), + addr: None, + }; + + let conn = pool.call(req.clone()).await.unwrap(); + assert_eq!(2, generated_clone.get()); + release(conn); + let conn = pool.call(req).await.unwrap(); + assert_eq!(2, generated_clone.get()); + release(conn); + } + + #[actix_rt::test] + async fn test_pool_drop() { + let generated = Rc::new(Cell::new(0)); + let generated_clone = generated.clone(); + + let connector = TestPoolConnector { generated }; + + let config = ConnectorConfig::default(); + + let pool = Rc::new(super::ConnectionPool::new(connector, config)); + + let req = Connect { + uri: Uri::from_static("https://crates.io"), + addr: None, + }; + + let conn = pool.call(req.clone()).await.unwrap(); + assert_eq!(1, generated_clone.get()); + release(conn); + + let req = Connect { + uri: Uri::from_static("https://google.com"), + addr: None, + }; + let conn = pool.call(req.clone()).await.unwrap(); + assert_eq!(2, generated_clone.get()); + release(conn); + + let clone1 = pool.clone(); + let clone2 = clone1.clone(); + + drop(clone2); + for _ in 0..2 { + actix_rt::task::yield_now().await; + } + assert_eq!(2, generated_clone.get()); + + drop(clone1); + for _ in 0..2 { + actix_rt::task::yield_now().await; + } + assert_eq!(2, generated_clone.get()); + + drop(pool); + for _ in 0..2 { + actix_rt::task::yield_now().await; + } + assert_eq!(0, generated_clone.get()); } } diff --git a/actix-http/src/error.rs b/actix-http/src/error.rs index 6a9de2d3e..97f2b3eff 100644 --- a/actix-http/src/error.rs +++ b/actix-http/src/error.rs @@ -11,7 +11,6 @@ use actix_utils::dispatcher::DispatcherError as FramedDispatcherError; use actix_utils::timeout::TimeoutError; use bytes::BytesMut; use derive_more::{Display, From}; -pub use futures_channel::oneshot::Canceled; use http::uri::InvalidUri; use http::{header, Error as HttpError, StatusCode}; use serde::de::value::Error as DeError; @@ -186,9 +185,6 @@ impl ResponseError for DeError { } } -/// Returns [`StatusCode::INTERNAL_SERVER_ERROR`] for [`Canceled`]. -impl ResponseError for Canceled {} - /// Returns [`StatusCode::BAD_REQUEST`] for [`Utf8Error`]. impl ResponseError for Utf8Error { fn status_code(&self) -> StatusCode { From 17b3e7e22512f1bdb17df35369b70fdd67aa6e9d Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Tue, 16 Feb 2021 09:08:30 +0000 Subject: [PATCH 046/339] pool doc nits (#1999) --- actix-http/src/body/mod.rs | 1 + actix-http/src/client/connection.rs | 7 +- actix-http/src/client/error.rs | 2 +- actix-http/src/client/pool.rs | 141 +++++++++++++++------------- 4 files changed, 80 insertions(+), 71 deletions(-) diff --git a/actix-http/src/body/mod.rs b/actix-http/src/body/mod.rs index 1080c67e2..a4d6ba2b6 100644 --- a/actix-http/src/body/mod.rs +++ b/actix-http/src/body/mod.rs @@ -1,5 +1,6 @@ //! Traits and structures to aid consuming and writing HTTP payloads. +#[allow(clippy::module_inception)] mod body; mod body_stream; mod message_body; diff --git a/actix-http/src/client/connection.rs b/actix-http/src/client/connection.rs index 0b2c6e1df..3ab902422 100644 --- a/actix-http/src/client/connection.rs +++ b/actix-http/src/client/connection.rs @@ -26,9 +26,10 @@ pub(crate) enum ConnectionType { H2(H2Connection), } -// h2 connection has two parts: SendRequest and Connection. -// Connection is spawned as async task on runtime and H2Connection would hold a handle for -// this task. So it can wake up and quit the task when SendRequest is dropped. +/// `H2Connection` has two parts: `SendRequest` and `Connection`. +/// +/// `Connection` is spawned as an async task on runtime and `H2Connection` holds a handle for +/// this task. Therefore, it can wake up and quit the task when SendRequest is dropped. pub(crate) struct H2Connection { handle: JoinHandle<()>, sender: SendRequest, diff --git a/actix-http/src/client/error.rs b/actix-http/src/client/error.rs index 7768462b8..d27363456 100644 --- a/actix-http/src/client/error.rs +++ b/actix-http/src/client/error.rs @@ -25,7 +25,7 @@ pub enum ConnectError { Resolver(Box), /// No dns records - #[display(fmt = "No dns records found for the input")] + #[display(fmt = "No DNS records found for the input")] NoRecords, /// Http2 error diff --git a/actix-http/src/client/pool.rs b/actix-http/src/client/pool.rs index b0b1613ab..3800696fa 100644 --- a/actix-http/src/client/pool.rs +++ b/actix-http/src/client/pool.rs @@ -1,4 +1,5 @@ -use std::cell::RefCell; +//! Client connection pooling keyed on the authority part of the connection URI. + use std::collections::VecDeque; use std::future::Future; use std::ops::Deref; @@ -7,14 +8,16 @@ use std::rc::Rc; use std::sync::Arc; use std::task::{Context, Poll}; use std::time::{Duration, Instant}; +use std::{cell::RefCell, io}; -use actix_codec::{AsyncRead, AsyncWrite, ReadBuf}; +use actix_codec::{AsyncRead, AsyncWrite}; 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 tokio::io::ReadBuf; use tokio::sync::{OwnedSemaphorePermit, Semaphore}; use super::config::ConnectorConfig; @@ -41,7 +44,7 @@ impl From for Key { } } -/// Connections pool for reuse Io type for certain [`http::uri::Authority`] as key +/// Connections pool for reuse Io type for certain [`http::uri::Authority`] as key. pub(crate) struct ConnectionPool where Io: AsyncWrite + Unpin + 'static, @@ -120,16 +123,16 @@ impl ConnectionPool where Io: AsyncWrite + Unpin + 'static, { - /// construct a new connection pool. + /// Construct a new connection pool. /// - /// [`super::config::ConnectorConfig`]'s `limit` is used as the max permits allowed - /// for on flight connections. + /// [`super::config::ConnectorConfig`]'s `limit` is used as the max permits allowed for + /// in-flight connections. /// /// The pool can only have equal to `limit` amount of requests spawning/using Io type /// concurrently. /// - /// Any requests beyond limit would be wait in fifo order and get notified in async - /// manner by [`tokio::sync::Semaphore`] + /// Any requests beyond limit would be wait in fifo order and get notified in async manner + /// by [`tokio::sync::Semaphore`] pub(crate) fn new(connector: S, config: ConnectorConfig) -> Self { let permits = Arc::new(Semaphore::new(config.limit)); let available = RefCell::new(AHashMap::default()); @@ -166,9 +169,7 @@ where type Error = ConnectError; type Future = LocalBoxFuture<'static, Result, ConnectError>>; - fn poll_ready(&self, cx: &mut Context<'_>) -> Poll> { - self.connector.poll_ready(cx) - } + actix_service::forward_ready!(connector); fn call(&self, req: Connect) -> Self::Future { let connector = self.connector.clone(); @@ -182,49 +183,55 @@ where }; // acquire an owned permit and carry it with connection - let permit = inner - .permits - .clone() - .acquire_owned() - .await - // TODO: use specific error for semaphore acquire error - .map_err(|_| ConnectError::NoRecords)?; + let permit = inner.permits.clone().acquire_owned().await.map_err(|_| { + ConnectError::Io(io::Error::new( + io::ErrorKind::Other, + "failed to acquire semaphore on client connection pool", + )) + })?; - // check if there is idle connection for given key. - let mut map = inner.available.borrow_mut(); + let conn = { + let mut conn = None; - let mut conn = None; - if let Some(conns) = map.get_mut(&key) { - let now = Instant::now(); - while let Some(mut c) = conns.pop_front() { - // check the lifetime and drop connection that live for too long. - if (now - c.used) > inner.config.conn_keep_alive - || (now - c.created) > inner.config.conn_lifetime - { - inner.close(c.conn); - // check if the connection is still usable. - } else { - if let ConnectionType::H1(ref mut io) = c.conn { - let check = ConnectionCheckFuture { io }; - match check.await { - ConnectionState::Break => { - inner.close(c.conn); - continue; - } - ConnectionState::Skip => continue, - ConnectionState::Live => conn = Some(c), - } + // check if there is idle connection for given key. + let mut map = inner.available.borrow_mut(); + + if let Some(conns) = map.get_mut(&key) { + let now = Instant::now(); + + while let Some(mut c) = conns.pop_front() { + 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; + + if conn_ineligible { + // drop connections that are too old + inner.close(c.conn); } else { - conn = Some(c); + // check if the connection is still usable + if let ConnectionType::H1(ref mut io) = c.conn { + let check = ConnectionCheckFuture { io }; + match check.await { + ConnectionState::Tainted => { + inner.close(c.conn); + continue; + } + ConnectionState::Skip => continue, + ConnectionState::Live => conn = Some(c), + } + } else { + conn = Some(c); + } + + break; } - - break; } - } - }; + }; - // drop map early to end the borrow_mut of RefCell. - drop(map); + conn + }; // construct acquired. It's used to put Io type back to pool/ close the Io type. // permit is carried with the whole lifecycle of Acquired. @@ -263,8 +270,13 @@ struct ConnectionCheckFuture<'a, Io> { } enum ConnectionState { + /// IO is pending and a new request would wake it. Live, - Break, + + /// IO unexpectedly has unread data and should be dropped. + Tainted, + + /// IO should be skipped but not dropped. Skip, } @@ -282,13 +294,11 @@ where let mut read_buf = ReadBuf::new(&mut buf); let state = match Pin::new(&mut this.io).poll_read(cx, &mut read_buf) { - // io is pending and new data would wake up it. - Poll::Pending => ConnectionState::Live, - // io have data inside. drop it. Poll::Ready(Ok(())) if !read_buf.filled().is_empty() => { - ConnectionState::Break + ConnectionState::Tainted } - // otherwise skip to next. + + Poll::Pending => ConnectionState::Live, _ => ConnectionState::Skip, }; @@ -350,16 +360,17 @@ impl Acquired where Io: AsyncRead + AsyncWrite + Unpin + 'static, { - // close the Io type. + /// Close the IO. pub(crate) fn close(&mut self, conn: IoConnection) { let (conn, _) = conn.into_inner(); self.inner.close(conn); } - // put the Io type back to pool. + /// Release IO back into pool. pub(crate) fn release(&mut self, conn: IoConnection) { let (io, created) = conn.into_inner(); let Acquired { key, inner, .. } = self; + inner .available .borrow_mut() @@ -371,24 +382,22 @@ where used: Instant::now(), }); - // a no op bind. used to stop clippy warning without adding allow attribute. - let _permit = &mut self.permit; + let _ = &mut self.permit; } } #[cfg(test)] mod test { - use super::*; - - use std::cell::Cell; - use std::io; + use std::{cell::Cell, io}; use http::Uri; + use super::*; use crate::client::connection::IoConnection; - // A stream type always return pending on async read. - // mock a usable tcp stream that ready to be used as client + /// A stream type that always returns pending on async read. + /// + /// Mocks an idle TCP stream that is ready to be used for client connections. struct TestStream(Rc>); impl Drop for TestStream { @@ -440,9 +449,7 @@ mod test { type Error = ConnectError; type Future = LocalBoxFuture<'static, Result>; - fn poll_ready(&self, _: &mut Context<'_>) -> Poll> { - unimplemented!("poll_ready is not used in test") - } + actix_service::always_ready!(); fn call(&self, _: Connect) -> Self::Future { self.generated.set(self.generated.get() + 1); From 3e0a9b99ff7327bf57ba4f842242c3e703b9894a Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Tue, 16 Feb 2021 09:28:14 +0000 Subject: [PATCH 047/339] update rust-cache action --- .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 d61c96963..7d0520d52 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -38,7 +38,7 @@ jobs: with: command: generate-lockfile - name: Cache Dependencies - uses: Swatinem/rust-cache@v1.0.1 + uses: Swatinem/rust-cache@v1.2.0 - name: Install cargo-hack uses: actions-rs/cargo@v1 From 117025a96bc3b30a427551ab4305c65b73b5b39c Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Tue, 16 Feb 2021 06:10:22 -0800 Subject: [PATCH 048/339] simplify client::connection::Connection trait (#1998) --- actix-http/src/client/connection.rs | 100 +++++++++++++--------------- actix-http/src/client/h1proto.rs | 4 +- actix-http/src/client/h2proto.rs | 3 +- awc/src/builder.rs | 1 - awc/src/connect.rs | 2 - 5 files changed, 50 insertions(+), 60 deletions(-) diff --git a/actix-http/src/client/connection.rs b/actix-http/src/client/connection.rs index 3ab902422..707d5551b 100644 --- a/actix-http/src/client/connection.rs +++ b/actix-http/src/client/connection.rs @@ -1,4 +1,3 @@ -use std::future::Future; use std::ops::{Deref, DerefMut}; use std::pin::Pin; use std::task::{Context, Poll}; @@ -8,7 +7,6 @@ use actix_codec::{AsyncRead, AsyncWrite, Framed, ReadBuf}; use actix_rt::task::JoinHandle; use bytes::Bytes; use futures_core::future::LocalBoxFuture; -use futures_util::future::{err, Either, FutureExt, Ready}; use h2::client::SendRequest; use pin_project::pin_project; @@ -75,7 +73,6 @@ impl DerefMut for H2Connection { pub trait Connection { type Io: AsyncRead + AsyncWrite + Unpin; - type Future: Future>; fn protocol(&self) -> Protocol; @@ -84,14 +81,16 @@ pub trait Connection { self, head: H, body: B, - ) -> Self::Future; - - type TunnelFuture: Future< - Output = Result<(ResponseHead, Framed), SendRequestError>, - >; + ) -> LocalBoxFuture<'static, Result<(ResponseHead, Payload), SendRequestError>>; /// Send request, returns Response and Framed - fn open_tunnel>(self, head: H) -> Self::TunnelFuture; + fn open_tunnel + 'static>( + self, + head: H, + ) -> LocalBoxFuture< + 'static, + Result<(ResponseHead, Framed), SendRequestError>, + >; } pub(crate) trait ConnectionLifetime: AsyncRead + AsyncWrite + 'static { @@ -154,8 +153,6 @@ where T: AsyncRead + AsyncWrite + Unpin + 'static, { type Io = T; - type Future = - LocalBoxFuture<'static, Result<(ResponseHead, Payload), SendRequestError>>; fn protocol(&self) -> Protocol { match self.io { @@ -169,33 +166,35 @@ where mut self, head: H, body: B, - ) -> Self::Future { + ) -> LocalBoxFuture<'static, Result<(ResponseHead, Payload), SendRequestError>> { match self.io.take().unwrap() { - ConnectionType::H1(io) => { - h1proto::send_request(io, head.into(), body, self.created, self.pool) - .boxed_local() - } - ConnectionType::H2(io) => { - h2proto::send_request(io, head.into(), body, self.created, self.pool) - .boxed_local() - } + ConnectionType::H1(io) => Box::pin(h1proto::send_request( + io, + head.into(), + body, + self.created, + self.pool, + )), + ConnectionType::H2(io) => Box::pin(h2proto::send_request( + io, + head.into(), + body, + self.created, + self.pool, + )), } } - type TunnelFuture = Either< - LocalBoxFuture< - 'static, - Result<(ResponseHead, Framed), SendRequestError>, - >, - Ready), SendRequestError>>, - >; - /// Send request, returns Response and Framed - fn open_tunnel>(mut self, head: H) -> Self::TunnelFuture { + fn open_tunnel>( + mut self, + head: H, + ) -> LocalBoxFuture< + 'static, + Result<(ResponseHead, Framed), SendRequestError>, + > { match self.io.take().unwrap() { - ConnectionType::H1(io) => { - Either::Left(h1proto::open_tunnel(io, head.into()).boxed_local()) - } + ConnectionType::H1(io) => Box::pin(h1proto::open_tunnel(io, head.into())), ConnectionType::H2(io) => { if let Some(mut pool) = self.pool.take() { pool.release(IoConnection::new( @@ -204,7 +203,7 @@ where None, )); } - Either::Right(err(SendRequestError::TunnelNotSupported)) + Box::pin(async { Err(SendRequestError::TunnelNotSupported) }) } } } @@ -226,8 +225,6 @@ where B: AsyncRead + AsyncWrite + Unpin + 'static, { type Io = EitherIo; - type Future = - LocalBoxFuture<'static, Result<(ResponseHead, Payload), SendRequestError>>; fn protocol(&self) -> Protocol { match self { @@ -240,33 +237,30 @@ where self, head: H, body: RB, - ) -> Self::Future { + ) -> LocalBoxFuture<'static, Result<(ResponseHead, Payload), SendRequestError>> { match self { EitherConnection::A(con) => con.send_request(head, body), EitherConnection::B(con) => con.send_request(head, body), } } - type TunnelFuture = LocalBoxFuture< + /// Send request, returns Response and Framed + fn open_tunnel + 'static>( + self, + head: H, + ) -> LocalBoxFuture< 'static, Result<(ResponseHead, Framed), SendRequestError>, - >; - - /// Send request, returns Response and Framed - fn open_tunnel>(self, head: H) -> Self::TunnelFuture { + > { match self { - EitherConnection::A(con) => con - .open_tunnel(head) - .map(|res| { - res.map(|(head, framed)| (head, framed.into_map_io(EitherIo::A))) - }) - .boxed_local(), - EitherConnection::B(con) => con - .open_tunnel(head) - .map(|res| { - res.map(|(head, framed)| (head, framed.into_map_io(EitherIo::B))) - }) - .boxed_local(), + EitherConnection::A(con) => Box::pin(async { + let (head, framed) = con.open_tunnel(head).await?; + Ok((head, framed.into_map_io(EitherIo::A))) + }), + EitherConnection::B(con) => Box::pin(async { + let (head, framed) = con.open_tunnel(head).await?; + Ok((head, framed.into_map_io(EitherIo::B))) + }), } } } diff --git a/actix-http/src/client/h1proto.rs b/actix-http/src/client/h1proto.rs index 1ff5c7017..082c4b8e2 100644 --- a/actix-http/src/client/h1proto.rs +++ b/actix-http/src/client/h1proto.rs @@ -8,7 +8,7 @@ use bytes::buf::BufMut; use bytes::{Bytes, BytesMut}; use futures_core::Stream; use futures_util::future::poll_fn; -use futures_util::{pin_mut, SinkExt, StreamExt}; +use futures_util::{SinkExt, StreamExt}; use crate::error::PayloadError; use crate::h1; @@ -127,7 +127,7 @@ where T: ConnectionLifetime + Unpin, B: MessageBody, { - pin_mut!(body); + actix_rt::pin!(body); let mut eof = false; while !eof { diff --git a/actix-http/src/client/h2proto.rs b/actix-http/src/client/h2proto.rs index 105e04c73..0deb5c014 100644 --- a/actix-http/src/client/h2proto.rs +++ b/actix-http/src/client/h2proto.rs @@ -5,7 +5,6 @@ use std::time; use actix_codec::{AsyncRead, AsyncWrite}; use bytes::Bytes; use futures_util::future::poll_fn; -use futures_util::pin_mut; use h2::{ client::{Builder, Connection, SendRequest}, SendStream, @@ -131,7 +130,7 @@ async fn send_body( mut send: SendStream, ) -> Result<(), SendRequestError> { let mut buf = None; - pin_mut!(body); + actix_rt::pin!(body); loop { if buf.is_none() { match poll_fn(|cx| body.as_mut().poll_next(cx)).await { diff --git a/awc/src/builder.rs b/awc/src/builder.rs index 94ffb8a71..3d1613c66 100644 --- a/awc/src/builder.rs +++ b/awc/src/builder.rs @@ -52,7 +52,6 @@ impl ClientBuilder { where T: Service + 'static, T::Response: Connection, - ::Future: 'static, T::Future: 'static, { self.connector = Some(Box::new(ConnectorWrapper(connector))); diff --git a/awc/src/connect.rs b/awc/src/connect.rs index a9b8f9f83..9a2ded195 100644 --- a/awc/src/connect.rs +++ b/awc/src/connect.rs @@ -41,8 +41,6 @@ where T: Service, T::Response: Connection, ::Io: 'static, - ::Future: 'static, - ::TunnelFuture: 'static, T::Future: 'static, { fn send_request( From 2cc6b47fcf9c2768bb08e8b45208e2ecc132c18d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luka=20Zakraj=C5=A1ek?= Date: Tue, 16 Feb 2021 19:48:16 +0100 Subject: [PATCH 049/339] Use http-range library for HttpRange (#2003) --- actix-files/Cargo.toml | 1 + actix-files/src/range.rs | 87 +++++----------------------------------- 2 files changed, 10 insertions(+), 78 deletions(-) diff --git a/actix-files/Cargo.toml b/actix-files/Cargo.toml index 45fa18a69..08b7b36fc 100644 --- a/actix-files/Cargo.toml +++ b/actix-files/Cargo.toml @@ -25,6 +25,7 @@ bitflags = "1" bytes = "1" futures-core = { version = "0.3.7", default-features = false } futures-util = { version = "0.3.7", default-features = false } +http-range = "0.1.4" derive_more = "0.99.5" log = "0.4" mime = "0.3" diff --git a/actix-files/src/range.rs b/actix-files/src/range.rs index 6718980cb..8d9fe9445 100644 --- a/actix-files/src/range.rs +++ b/actix-files/src/range.rs @@ -10,9 +10,6 @@ pub struct HttpRange { pub length: u64, } -const PREFIX: &str = "bytes="; -const PREFIX_LEN: usize = 6; - #[derive(Debug, Clone, Display, Error)] #[display(fmt = "Parse HTTP Range failed")] pub struct ParseRangeErr(#[error(not(source))] ()); @@ -23,82 +20,16 @@ impl HttpRange { /// `header` is HTTP Range header (e.g. `bytes=bytes=0-9`). /// `size` is full size of response (file). pub fn parse(header: &str, size: u64) -> Result, ParseRangeErr> { - if header.is_empty() { - return Ok(Vec::new()); + match http_range::HttpRange::parse(header, size) { + Ok(ranges) => Ok(ranges + .iter() + .map(|range| HttpRange { + start: range.start, + length: range.length, + }) + .collect()), + Err(_) => Err(ParseRangeErr(())), } - if !header.starts_with(PREFIX) { - return Err(ParseRangeErr(())); - } - - let size_sig = size as i64; - let mut no_overlap = false; - - let all_ranges: Vec> = header[PREFIX_LEN..] - .split(',') - .map(|x| x.trim()) - .filter(|x| !x.is_empty()) - .map(|ra| { - let mut start_end_iter = ra.split('-'); - - let start_str = start_end_iter.next().ok_or(ParseRangeErr(()))?.trim(); - let end_str = start_end_iter.next().ok_or(ParseRangeErr(()))?.trim(); - - if start_str.is_empty() { - // If no start is specified, end specifies the - // range start relative to the end of the file. - let mut length: i64 = end_str.parse().map_err(|_| ParseRangeErr(()))?; - - if length > size_sig { - length = size_sig; - } - - Ok(Some(HttpRange { - start: (size_sig - length) as u64, - length: length as u64, - })) - } else { - let start: i64 = start_str.parse().map_err(|_| ParseRangeErr(()))?; - - if start < 0 { - return Err(ParseRangeErr(())); - } - if start >= size_sig { - no_overlap = true; - return Ok(None); - } - - let length = if end_str.is_empty() { - // If no end is specified, range extends to end of the file. - size_sig - start - } else { - let mut end: i64 = end_str.parse().map_err(|_| ParseRangeErr(()))?; - - if start > end { - return Err(ParseRangeErr(())); - } - - if end >= size_sig { - end = size_sig - 1; - } - - end - start + 1 - }; - - Ok(Some(HttpRange { - start: start as u64, - length: length as u64, - })) - } - }) - .collect::>()?; - - let ranges: Vec = all_ranges.into_iter().filter_map(|x| x).collect(); - - if no_overlap && ranges.is_empty() { - return Err(ParseRangeErr(())); - } - - Ok(ranges) } } From dfa795ff9da9e7064239b9bb4ea55a90d60bfd11 Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Wed, 17 Feb 2021 03:18:31 -0800 Subject: [PATCH 050/339] return poll in poll_flush (#2005) --- actix-http/src/h1/dispatcher.rs | 37 +++++++++++++-------------------- 1 file changed, 14 insertions(+), 23 deletions(-) diff --git a/actix-http/src/h1/dispatcher.rs b/actix-http/src/h1/dispatcher.rs index f7d7f32c3..839f75402 100644 --- a/actix-http/src/h1/dispatcher.rs +++ b/actix-http/src/h1/dispatcher.rs @@ -13,6 +13,7 @@ use actix_rt::time::{sleep_until, Instant, Sleep}; use actix_service::Service; use bitflags::bitflags; use bytes::{Buf, BytesMut}; +use futures_core::ready; use log::{error, trace}; use pin_project::pin_project; @@ -233,14 +234,10 @@ where } } - /// Flush stream - /// - /// true - got WouldBlock - /// false - didn't get WouldBlock fn poll_flush( self: Pin<&mut Self>, cx: &mut Context<'_>, - ) -> Result { + ) -> Poll> { let InnerDispatcherProj { io, write_buf, .. } = self.project(); let mut io = Pin::new(io.as_mut().unwrap()); @@ -248,19 +245,18 @@ where let mut written = 0; while written < len { - match io.as_mut().poll_write(cx, &write_buf[written..]) { - Poll::Ready(Ok(0)) => { - return Err(DispatchError::Io(io::Error::new( + match io.as_mut().poll_write(cx, &write_buf[written..])? { + Poll::Ready(0) => { + return Poll::Ready(Err(io::Error::new( io::ErrorKind::WriteZero, "", ))) } - Poll::Ready(Ok(n)) => written += n, + Poll::Ready(n) => written += n, Poll::Pending => { write_buf.advance(written); - return Ok(true); + return Poll::Pending; } - Poll::Ready(Err(err)) => return Err(DispatchError::Io(err)), } } @@ -268,9 +264,7 @@ where write_buf.clear(); // flush the io and check if get blocked. - let blocked = io.poll_flush(cx)?.is_pending(); - - Ok(blocked) + io.poll_flush(cx) } fn send_response( @@ -841,14 +835,11 @@ where if inner.flags.contains(Flags::WRITE_DISCONNECT) { Poll::Ready(Ok(())) } else { - // flush buffer and wait on block. - if inner.as_mut().poll_flush(cx)? { - Poll::Pending - } else { - Pin::new(inner.project().io.as_mut().unwrap()) - .poll_shutdown(cx) - .map_err(DispatchError::from) - } + // flush buffer and wait on blocked. + ready!(inner.as_mut().poll_flush(cx))?; + Pin::new(inner.project().io.as_mut().unwrap()) + .poll_shutdown(cx) + .map_err(DispatchError::from) } } else { // read from io stream and fill read buffer. @@ -888,7 +879,7 @@ where // // TODO: what? is WouldBlock good or bad? // want to find a reference for this macOS behavior - if inner.as_mut().poll_flush(cx)? || !drain { + if inner.as_mut().poll_flush(cx)?.is_pending() || !drain { break; } } From 5efea652e342479d6d0a20afc58bd880a691a712 Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Wed, 17 Feb 2021 03:55:11 -0800 Subject: [PATCH 051/339] add ClientResponse::timeout (#1931) --- awc/CHANGES.md | 4 ++ awc/src/response.rs | 131 ++++++++++++++++++++++++++++++++------- awc/src/sender.rs | 32 +++++----- awc/tests/test_client.rs | 75 +++++++++++++++++++++- 4 files changed, 202 insertions(+), 40 deletions(-) diff --git a/awc/CHANGES.md b/awc/CHANGES.md index 9224f414d..c67f65560 100644 --- a/awc/CHANGES.md +++ b/awc/CHANGES.md @@ -1,9 +1,13 @@ # Changes ## Unreleased - 2021-xx-xx +### Added +* `ClientResponse::timeout` for set the timeout of collecting response body. [#1931] + ### Changed * Feature `cookies` is now optional and enabled by default. [#1981] +[#1931]: https://github.com/actix/actix-web/pull/1931 [#1981]: https://github.com/actix/actix-web/pull/1981 diff --git a/awc/src/response.rs b/awc/src/response.rs index cf687329d..514b8a90b 100644 --- a/awc/src/response.rs +++ b/awc/src/response.rs @@ -1,20 +1,22 @@ -use std::fmt; -use std::future::Future; -use std::marker::PhantomData; -use std::pin::Pin; -use std::task::{Context, Poll}; use std::{ cell::{Ref, RefMut}, - mem, + fmt, + future::Future, + io, + marker::PhantomData, + pin::Pin, + task::{Context, Poll}, + time::{Duration, Instant}, }; +use actix_http::{ + error::PayloadError, + http::{header, HeaderMap, StatusCode, Version}, + Extensions, HttpMessage, Payload, PayloadStream, ResponseHead, +}; +use actix_rt::time::{sleep, Sleep}; use bytes::{Bytes, BytesMut}; use futures_core::{ready, Stream}; - -use actix_http::error::PayloadError; -use actix_http::http::header; -use actix_http::http::{HeaderMap, StatusCode, Version}; -use actix_http::{Extensions, HttpMessage, Payload, PayloadStream, ResponseHead}; use serde::de::DeserializeOwned; #[cfg(feature = "cookies")] @@ -26,6 +28,38 @@ use crate::error::JsonPayloadError; 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 { @@ -35,6 +69,10 @@ impl HttpMessage for ClientResponse { &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() } @@ -43,10 +81,6 @@ impl HttpMessage for ClientResponse { self.head.extensions_mut() } - fn take_payload(&mut self) -> Payload { - mem::replace(&mut self.payload, Payload::None) - } - /// Load request cookies. #[cfg(feature = "cookies")] fn cookies(&self) -> Result>>, CookieParseError> { @@ -69,7 +103,11 @@ impl HttpMessage for ClientResponse { impl ClientResponse { /// Create new Request instance pub(crate) fn new(head: ResponseHead, payload: Payload) -> Self { - ClientResponse { head, payload } + ClientResponse { + head, + payload, + timeout: ResponseTimeout::default(), + } } #[inline] @@ -105,8 +143,43 @@ impl ClientResponse { 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 + } } impl ClientResponse @@ -137,7 +210,10 @@ where type Item = Result; fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - Pin::new(&mut self.get_mut().payload).poll_next(cx) + let this = self.get_mut(); + this.timeout.poll_timeout(cx)?; + + Pin::new(&mut this.payload).poll_next(cx) } } @@ -156,6 +232,7 @@ impl fmt::Debug for ClientResponse { pub struct MessageBody { length: Option, err: Option, + timeout: ResponseTimeout, fut: Option>, } @@ -181,6 +258,7 @@ where MessageBody { length: len, err: None, + timeout: std::mem::take(&mut res.timeout), fut: Some(ReadBody::new(res.take_payload(), 262_144)), } } @@ -198,6 +276,7 @@ where fut: None, err: Some(e), length: None, + timeout: ResponseTimeout::default(), } } } @@ -221,6 +300,8 @@ where } } + this.timeout.poll_timeout(cx)?; + Pin::new(&mut this.fut.as_mut().unwrap()).poll(cx) } } @@ -234,6 +315,7 @@ where pub struct JsonBody { length: Option, err: Option, + timeout: ResponseTimeout, fut: Option>, _phantom: PhantomData, } @@ -244,9 +326,9 @@ where U: DeserializeOwned, { /// Create `JsonBody` for request. - pub fn new(req: &mut ClientResponse) -> Self { + pub fn new(res: &mut ClientResponse) -> Self { // check content-type - let json = if let Ok(Some(mime)) = req.mime_type() { + let json = if let Ok(Some(mime)) = res.mime_type() { mime.subtype() == mime::JSON || mime.suffix() == Some(mime::JSON) } else { false @@ -255,13 +337,15 @@ where return JsonBody { length: None, fut: None, + timeout: ResponseTimeout::default(), err: Some(JsonPayloadError::ContentType), _phantom: PhantomData, }; } let mut len = None; - if let Some(l) = req.headers().get(&header::CONTENT_LENGTH) { + + 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) @@ -272,7 +356,8 @@ where JsonBody { length: len, err: None, - fut: Some(ReadBody::new(req.take_payload(), 65536)), + timeout: std::mem::take(&mut res.timeout), + fut: Some(ReadBody::new(res.take_payload(), 65536)), _phantom: PhantomData, } } @@ -311,6 +396,10 @@ where } } + 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)) } diff --git a/awc/src/sender.rs b/awc/src/sender.rs index a72b129f8..6bac401c5 100644 --- a/awc/src/sender.rs +++ b/awc/src/sender.rs @@ -18,15 +18,11 @@ use actix_http::{ use actix_rt::time::{sleep, Sleep}; use bytes::Bytes; use derive_more::From; -use futures_core::Stream; +use futures_core::{ready, Stream}; use serde::Serialize; #[cfg(feature = "compress")] -use actix_http::encoding::Decoder; -#[cfg(feature = "compress")] -use actix_http::http::header::ContentEncoding; -#[cfg(feature = "compress")] -use actix_http::{Payload, PayloadStream}; +use actix_http::{encoding::Decoder, http::header::ContentEncoding, Payload, PayloadStream}; use crate::error::{FreezeRequestError, InvalidUrl, SendRequestError}; use crate::response::ClientResponse; @@ -61,7 +57,6 @@ impl From for SendRequestError { pub enum SendClientRequest { Fut( Pin>>>, - // FIXME: use a pinned Sleep instead of box. Option>>, bool, ), @@ -88,15 +83,14 @@ impl Future for SendClientRequest { match this { SendClientRequest::Fut(send, delay, response_decompress) => { - if delay.is_some() { - match Pin::new(delay.as_mut().unwrap()).poll(cx) { - Poll::Pending => {} - _ => return Poll::Ready(Err(SendRequestError::Timeout)), + if let Some(delay) = delay { + if delay.as_mut().poll(cx).is_ready() { + return Poll::Ready(Err(SendRequestError::Timeout)); } } - let res = futures_core::ready!(Pin::new(send).poll(cx)).map(|res| { - res.map_body(|head, payload| { + let res = ready!(send.as_mut().poll(cx)).map(|res| { + res._timeout(delay.take()).map_body(|head, payload| { if *response_decompress { Payload::Stream(Decoder::from_headers(payload, &head.headers)) } else { @@ -123,13 +117,15 @@ impl Future for SendClientRequest { let this = self.get_mut(); match this { SendClientRequest::Fut(send, delay, _) => { - if delay.is_some() { - match Pin::new(delay.as_mut().unwrap()).poll(cx) { - Poll::Pending => {} - _ => return Poll::Ready(Err(SendRequestError::Timeout)), + if let Some(delay) = delay { + if delay.as_mut().poll(cx).is_ready() { + return Poll::Ready(Err(SendRequestError::Timeout)); } } - Pin::new(send).poll(cx) + + send.as_mut() + .poll(cx) + .map_ok(|res| res._timeout(delay.take())) } SendClientRequest::Err(ref mut e) => match e.take() { Some(e) => Poll::Ready(Err(e)), diff --git a/awc/tests/test_client.rs b/awc/tests/test_client.rs index 7e74d226e..bcbaf3f41 100644 --- a/awc/tests/test_client.rs +++ b/awc/tests/test_client.rs @@ -24,7 +24,7 @@ use actix_web::{ middleware::Compress, test, web, App, Error, HttpMessage, HttpRequest, HttpResponse, }; -use awc::error::SendRequestError; +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 \ @@ -157,6 +157,79 @@ async fn test_timeout_override() { } } +#[actix_rt::test] +async fn test_response_timeout() { + use futures_util::stream::{once, StreamExt}; + + let srv = test::start(|| { + App::new().service(web::resource("/").route(web::to(|| async { + Ok::<_, Error>( + HttpResponse::Ok() + .content_type("application/json") + .streaming(Box::pin(once(async { + actix_rt::time::sleep(Duration::from_millis(200)).await; + Ok::<_, Error>(Bytes::from(STR)) + }))), + ) + }))) + }); + + let client = awc::Client::new(); + + let res = client + .get(srv.url("/")) + .send() + .await + .unwrap() + .timeout(Duration::from_millis(500)) + .body() + .await + .unwrap(); + assert_eq!(std::str::from_utf8(res.as_ref()).unwrap(), STR); + + let res = client + .get(srv.url("/")) + .send() + .await + .unwrap() + .timeout(Duration::from_millis(100)) + .next() + .await + .unwrap(); + match res { + Err(PayloadError::Io(e)) => assert_eq!(e.kind(), std::io::ErrorKind::TimedOut), + _ => panic!("Response error type is not matched"), + } + + let res = client + .get(srv.url("/")) + .send() + .await + .unwrap() + .timeout(Duration::from_millis(100)) + .body() + .await; + match res { + Err(PayloadError::Io(e)) => assert_eq!(e.kind(), std::io::ErrorKind::TimedOut), + _ => panic!("Response error type is not matched"), + } + + let res = client + .get(srv.url("/")) + .send() + .await + .unwrap() + .timeout(Duration::from_millis(100)) + .json::>() + .await; + match res { + Err(JsonPayloadError::Payload(PayloadError::Io(e))) => { + assert_eq!(e.kind(), std::io::ErrorKind::TimedOut) + } + _ => panic!("Response error type is not matched"), + } +} + #[actix_rt::test] async fn test_connection_reuse() { let num = Arc::new(AtomicUsize::new(0)); From dfd9dc40ea27da155a4464c8cc18db256ae25f64 Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Wed, 17 Feb 2021 09:10:46 -0800 Subject: [PATCH 052/339] remove awc::connect::connect trait. (#2004) --- awc/src/builder.rs | 8 +-- awc/src/connect.rs | 132 +++++++++++++++++++++++++++------------------ awc/src/lib.rs | 8 +-- awc/src/sender.rs | 42 ++++++++------- awc/src/ws.rs | 14 +++-- 5 files changed, 120 insertions(+), 84 deletions(-) diff --git a/awc/src/builder.rs b/awc/src/builder.rs index 3d1613c66..b5e8bf5bf 100644 --- a/awc/src/builder.rs +++ b/awc/src/builder.rs @@ -7,7 +7,7 @@ use actix_http::client::{Connect as HttpConnect, ConnectError, Connection, Conne use actix_http::http::{self, header, Error as HttpError, HeaderMap, HeaderName}; use actix_service::Service; -use crate::connect::{Connect, ConnectorWrapper}; +use crate::connect::{ConnectService, ConnectorWrapper}; use crate::{Client, ClientConfig}; /// An HTTP Client builder @@ -23,7 +23,7 @@ pub struct ClientBuilder { conn_window_size: Option, headers: HeaderMap, timeout: Option, - connector: Option>, + connector: Option, } impl Default for ClientBuilder { @@ -54,7 +54,7 @@ impl ClientBuilder { T::Response: Connection, T::Future: 'static, { - self.connector = Some(Box::new(ConnectorWrapper(connector))); + self.connector = Some(Box::new(ConnectorWrapper::new(connector))); self } @@ -181,7 +181,7 @@ impl ClientBuilder { if let Some(val) = self.stream_window_size { connector = connector.initial_window_size(val) }; - Box::new(ConnectorWrapper(connector.finish())) as _ + Box::new(ConnectorWrapper::new(connector.finish())) as _ }; let config = ClientConfig { headers: self.headers, diff --git a/awc/src/connect.rs b/awc/src/connect.rs index 9a2ded195..97af2d1cc 100644 --- a/awc/src/connect.rs +++ b/awc/src/connect.rs @@ -16,74 +16,100 @@ use futures_core::future::LocalBoxFuture; use crate::response::ClientResponse; -pub(crate) struct ConnectorWrapper(pub T); - -type TunnelResponse = (ResponseHead, Framed); - -pub(crate) trait Connect { - fn send_request( - &self, - head: RequestHeadType, - body: Body, - addr: Option, - ) -> LocalBoxFuture<'static, Result>; - - /// Send request, returns Response and Framed - fn open_tunnel( - &self, - head: RequestHead, - addr: Option, - ) -> LocalBoxFuture<'static, Result>; +pub(crate) struct ConnectorWrapper { + connector: T, } -impl Connect for ConnectorWrapper +impl ConnectorWrapper { + pub(crate) fn new(connector: T) -> Self { + Self { connector } + } +} + +pub type ConnectService = Box< + dyn Service< + ConnectRequest, + Response = ConnectResponse, + Error = SendRequestError, + Future = LocalBoxFuture<'static, Result>, + >, +>; + +pub enum ConnectRequest { + Client(RequestHeadType, Body, Option), + Tunnel(RequestHead, Option), +} + +pub enum ConnectResponse { + Client(ClientResponse), + Tunnel(ResponseHead, Framed), +} + +impl ConnectResponse { + pub fn into_client_response(self) -> ClientResponse { + match self { + ConnectResponse::Client(res) => res, + _ => panic!( + "ClientResponse only reachable with ConnectResponse::ClientResponse variant" + ), + } + } + + pub fn into_tunnel_response(self) -> (ResponseHead, Framed) { + match self { + ConnectResponse::Tunnel(head, framed) => (head, framed), + _ => panic!( + "TunnelResponse only reachable with ConnectResponse::TunnelResponse variant" + ), + } + } +} + +impl Service for ConnectorWrapper where T: Service, T::Response: Connection, ::Io: 'static, T::Future: 'static, { - fn send_request( - &self, - head: RequestHeadType, - body: Body, - addr: Option, - ) -> LocalBoxFuture<'static, Result> { + type Response = ConnectResponse; + type Error = SendRequestError; + type Future = LocalBoxFuture<'static, Result>; + + actix_service::forward_ready!(connector); + + fn call(&self, req: ConnectRequest) -> Self::Future { // connect to the host - let fut = self.0.call(ClientConnect { - uri: head.as_ref().uri.clone(), - addr, - }); + let fut = match req { + ConnectRequest::Client(ref head, .., addr) => self.connector.call(ClientConnect { + uri: head.as_ref().uri.clone(), + addr, + }), + ConnectRequest::Tunnel(ref head, addr) => self.connector.call(ClientConnect { + uri: head.uri.clone(), + addr, + }), + }; Box::pin(async move { let connection = fut.await?; - // send request - let (head, payload) = connection.send_request(head, body).await?; + match req { + ConnectRequest::Client(head, body, ..) => { + // send request + let (head, payload) = connection.send_request(head, body).await?; - Ok(ClientResponse::new(head, payload)) - }) - } + Ok(ConnectResponse::Client(ClientResponse::new(head, payload))) + } + ConnectRequest::Tunnel(head, ..) => { + // send request + let (head, framed) = + connection.open_tunnel(RequestHeadType::from(head)).await?; - fn open_tunnel( - &self, - head: RequestHead, - addr: Option, - ) -> LocalBoxFuture<'static, Result> { - // connect to the host - let fut = self.0.call(ClientConnect { - uri: head.uri.clone(), - addr, - }); - - Box::pin(async move { - let connection = fut.await?; - - // send request - let (head, framed) = connection.open_tunnel(RequestHeadType::from(head)).await?; - - let framed = framed.into_map_io(|io| BoxedSocket(Box::new(Socket(io)))); - Ok((head, framed)) + let framed = framed.into_map_io(|io| BoxedSocket(Box::new(Socket(io)))); + Ok(ConnectResponse::Tunnel(head, framed)) + } + } }) } } diff --git a/awc/src/lib.rs b/awc/src/lib.rs index e50c19c8c..1e27ed9ab 100644 --- a/awc/src/lib.rs +++ b/awc/src/lib.rs @@ -115,13 +115,13 @@ pub mod test; pub mod ws; pub use self::builder::ClientBuilder; -pub use self::connect::BoxedSocket; +pub use self::connect::{BoxedSocket, ConnectRequest, ConnectResponse, ConnectService}; pub use self::frozen::{FrozenClientRequest, FrozenSendBuilder}; pub use self::request::ClientRequest; pub use self::response::{ClientResponse, JsonBody, MessageBody}; pub use self::sender::SendClientRequest; -use self::connect::{Connect, ConnectorWrapper}; +use self::connect::ConnectorWrapper; /// An asynchronous HTTP and WebSocket client. /// @@ -146,7 +146,7 @@ use self::connect::{Connect, ConnectorWrapper}; pub struct Client(Rc); pub(crate) struct ClientConfig { - pub(crate) connector: Box, + pub(crate) connector: ConnectService, pub(crate) headers: HeaderMap, pub(crate) timeout: Option, } @@ -154,7 +154,7 @@ pub(crate) struct ClientConfig { impl Default for Client { fn default() -> Self { Client(Rc::new(ClientConfig { - connector: Box::new(ConnectorWrapper(Connector::new().finish())), + connector: Box::new(ConnectorWrapper::new(Connector::new().finish())), headers: HeaderMap::new(), timeout: Some(Duration::from_secs(5)), })) diff --git a/awc/src/sender.rs b/awc/src/sender.rs index 6bac401c5..1170c69a0 100644 --- a/awc/src/sender.rs +++ b/awc/src/sender.rs @@ -18,12 +18,13 @@ use actix_http::{ use actix_rt::time::{sleep, Sleep}; use bytes::Bytes; use derive_more::From; -use futures_core::{ready, Stream}; +use futures_core::Stream; 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; @@ -56,7 +57,8 @@ impl From for SendRequestError { #[must_use = "futures do nothing unless polled"] pub enum SendClientRequest { Fut( - Pin>>>, + Pin>>>, + // FIXME: use a pinned Sleep instead of box. Option>>, bool, ), @@ -65,7 +67,7 @@ pub enum SendClientRequest { impl SendClientRequest { pub(crate) fn new( - send: Pin>>>, + send: Pin>>>, response_decompress: bool, timeout: Option, ) -> SendClientRequest { @@ -89,14 +91,19 @@ impl Future for SendClientRequest { } } - let res = ready!(send.as_mut().poll(cx)).map(|res| { - res._timeout(delay.take()).map_body(|head, payload| { - if *response_decompress { - Payload::Stream(Decoder::from_headers(payload, &head.headers)) - } else { - Payload::Stream(Decoder::new(payload, ContentEncoding::Identity)) - } - }) + let res = futures_core::ready!(send.as_mut().poll(cx)).map(|res| { + res.into_client_response()._timeout(delay.take()).map_body( + |head, payload| { + if *response_decompress { + Payload::Stream(Decoder::from_headers(payload, &head.headers)) + } else { + Payload::Stream(Decoder::new( + payload, + ContentEncoding::Identity, + )) + } + }, + ) }); Poll::Ready(res) @@ -122,10 +129,9 @@ impl Future for SendClientRequest { return Poll::Ready(Err(SendRequestError::Timeout)); } } - send.as_mut() .poll(cx) - .map_ok(|res| res._timeout(delay.take())) + .map_ok(|res| res.into_client_response()._timeout(delay.take())) } SendClientRequest::Err(ref mut e) => match e.take() { Some(e) => Poll::Ready(Err(e)), @@ -177,19 +183,19 @@ impl RequestSender { where B: Into, { - let fut = match self { + let req = match self { RequestSender::Owned(head) => { - config - .connector - .send_request(RequestHeadType::Owned(head), body.into(), addr) + ConnectRequest::Client(RequestHeadType::Owned(head), body.into(), addr) } - RequestSender::Rc(head, extra_headers) => config.connector.send_request( + RequestSender::Rc(head, extra_headers) => ConnectRequest::Client( RequestHeadType::Rc(head, extra_headers), body.into(), addr, ), }; + let fut = config.connector.call(req); + SendClientRequest::new(fut, response_decompress, timeout.or(config.timeout)) } diff --git a/awc/src/ws.rs b/awc/src/ws.rs index d5528595d..5f4570963 100644 --- a/awc/src/ws.rs +++ b/awc/src/ws.rs @@ -36,10 +36,11 @@ use actix_codec::Framed; use actix_http::cookie::{Cookie, CookieJar}; use actix_http::{ws, Payload, RequestHead}; use actix_rt::time::timeout; +use actix_service::Service; pub use actix_http::ws::{CloseCode, CloseReason, Codec, Frame, Message}; -use crate::connect::BoxedSocket; +use crate::connect::{BoxedSocket, ConnectRequest}; 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}; @@ -327,18 +328,21 @@ impl WebsocketsRequest { let max_size = self.max_size; let server_mode = self.server_mode; - let fut = self.config.connector.open_tunnel(head, self.addr); + let req = ConnectRequest::Tunnel(head, self.addr); + + let fut = self.config.connector.call(req); // set request timeout - let (head, framed) = if let Some(to) = self.config.timeout { + let res = if let Some(to) = self.config.timeout { timeout(to, fut) .await - .map_err(|_| SendRequestError::Timeout) - .and_then(|res| res)? + .map_err(|_| SendRequestError::Timeout)?? } else { fut.await? }; + let (head, framed) = res.into_tunnel_response(); + // verify response if head.status != StatusCode::SWITCHING_PROTOCOLS { return Err(WsClientError::InvalidResponseStatus(head.status)); From f62a982a511151640c052bef21656c2596b5f886 Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Thu, 18 Feb 2021 02:38:27 -0800 Subject: [PATCH 053/339] simplify the match on h1 message type (#2006) --- actix-http/src/h1/dispatcher.rs | 42 +++++++++++++++++++++++---------- 1 file changed, 30 insertions(+), 12 deletions(-) diff --git a/actix-http/src/h1/dispatcher.rs b/actix-http/src/h1/dispatcher.rs index 839f75402..6df579c0a 100644 --- a/actix-http/src/h1/dispatcher.rs +++ b/actix-http/src/h1/dispatcher.rs @@ -510,25 +510,43 @@ where match msg { Message::Item(mut req) => { - let pl = this.codec.message_type(); req.head_mut().peer_addr = *this.peer_addr; // merge on_connect_ext data into request extensions this.on_connect_data.merge_into(&mut req); - if pl == MessageType::Stream && this.flow.upgrade.is_some() { - this.messages.push_back(DispatcherMessage::Upgrade(req)); - break; - } - if pl == MessageType::Payload || pl == MessageType::Stream { - let (ps, pl) = Payload::create(false); - let (req1, _) = - req.replace_payload(crate::Payload::H1(pl)); - req = req1; - *this.payload = Some(ps); + match this.codec.message_type() { + // Request is upgradable. add upgrade message and break. + // 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)); + break; + } + + // 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. + */ + let (ps, pl) = Payload::create(false); + let (req1, _) = + req.replace_payload(crate::Payload::H1(pl)); + req = req1; + *this.payload = Some(ps); + } + + // Request has no payload. + MessageType::None => {} } - // handle request early + // handle request early when no future in InnerDispatcher state. if this.state.is_empty() { self.as_mut().handle_request(req, cx)?; this = self.as_mut().project(); From 1838d9cd0f8a3e7da0aebdb022cee3907ba43d67 Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Thu, 18 Feb 2021 03:24:10 -0800 Subject: [PATCH 054/339] remove unused method. reduce leaf future type (#2009) --- actix-http/src/client/connection.rs | 33 ++------ actix-http/src/client/connector.rs | 127 ++++++++++------------------ 2 files changed, 54 insertions(+), 106 deletions(-) diff --git a/actix-http/src/client/connection.rs b/actix-http/src/client/connection.rs index 707d5551b..778083a1c 100644 --- a/actix-http/src/client/connection.rs +++ b/actix-http/src/client/connection.rs @@ -16,7 +16,7 @@ use crate::message::{RequestHeadType, ResponseHead}; use crate::payload::Payload; use super::error::SendRequestError; -use super::pool::{Acquired, Protocol}; +use super::pool::Acquired; use super::{h1proto, h2proto}; pub(crate) enum ConnectionType { @@ -49,7 +49,7 @@ impl H2Connection { } } -// wake up waker when drop +// cancel spawned connection task on drop. impl Drop for H2Connection { fn drop(&mut self) { self.handle.abort(); @@ -74,8 +74,6 @@ impl DerefMut for H2Connection { pub trait Connection { type Io: AsyncRead + AsyncWrite + Unpin; - fn protocol(&self) -> Protocol; - /// Send request and body fn send_request>( self, @@ -154,14 +152,6 @@ where { type Io = T; - fn protocol(&self) -> Protocol { - match self.io { - Some(ConnectionType::H1(_)) => Protocol::Http1, - Some(ConnectionType::H2(_)) => Protocol::Http2, - None => Protocol::Http1, - } - } - fn send_request>( mut self, head: H, @@ -210,7 +200,7 @@ where } #[allow(dead_code)] -pub(crate) enum EitherConnection +pub(crate) enum EitherIoConnection where A: AsyncRead + AsyncWrite + Unpin + 'static, B: AsyncRead + AsyncWrite + Unpin + 'static, @@ -219,28 +209,21 @@ where B(IoConnection), } -impl Connection for EitherConnection +impl Connection for EitherIoConnection where A: AsyncRead + AsyncWrite + Unpin + 'static, B: AsyncRead + AsyncWrite + Unpin + 'static, { type Io = EitherIo; - fn protocol(&self) -> Protocol { - match self { - EitherConnection::A(con) => con.protocol(), - EitherConnection::B(con) => con.protocol(), - } - } - fn send_request>( self, head: H, body: RB, ) -> LocalBoxFuture<'static, Result<(ResponseHead, Payload), SendRequestError>> { match self { - EitherConnection::A(con) => con.send_request(head, body), - EitherConnection::B(con) => con.send_request(head, body), + EitherIoConnection::A(con) => con.send_request(head, body), + EitherIoConnection::B(con) => con.send_request(head, body), } } @@ -253,11 +236,11 @@ where Result<(ResponseHead, Framed), SendRequestError>, > { match self { - EitherConnection::A(con) => Box::pin(async { + EitherIoConnection::A(con) => Box::pin(async { let (head, framed) = con.open_tunnel(head).await?; Ok((head, framed.into_map_io(EitherIo::A))) }), - EitherConnection::B(con) => Box::pin(async { + EitherIoConnection::B(con) => Box::pin(async { let (head, framed) = con.open_tunnel(head).await?; Ok((head, framed.into_map_io(EitherIo::B))) }), diff --git a/actix-http/src/client/connector.rs b/actix-http/src/client/connector.rs index 3bf424d49..65536f257 100644 --- a/actix-http/src/client/connector.rs +++ b/actix-http/src/client/connector.rs @@ -356,7 +356,7 @@ where mod connect_impl { use std::task::{Context, Poll}; - use futures_util::future::{err, Either, Ready}; + use futures_core::future::LocalBoxFuture; use super::*; use crate::client::connection::IoConnection; @@ -388,10 +388,7 @@ mod connect_impl { { type Response = IoConnection; type Error = ConnectError; - type Future = Either< - as Service>::Future, - Ready, ConnectError>>, - >; + type Future = LocalBoxFuture<'static, Result, ConnectError>>; fn poll_ready(&self, cx: &mut Context<'_>) -> Poll> { self.tcp_pool.poll_ready(cx) @@ -400,9 +397,9 @@ mod connect_impl { fn call(&self, req: Connect) -> Self::Future { match req.uri.scheme_str() { Some("https") | Some("wss") => { - Either::Right(err(ConnectError::SslIsNotSupported)) + Box::pin(async { Err(ConnectError::SslIsNotSupported) }) } - _ => Either::Left(self.tcp_pool.call(req)), + _ => self.tcp_pool.call(req), } } } @@ -411,33 +408,29 @@ mod connect_impl { #[cfg(any(feature = "openssl", feature = "rustls"))] mod connect_impl { use std::future::Future; - use std::marker::PhantomData; use std::pin::Pin; use std::task::{Context, Poll}; - use futures_core::ready; - use futures_util::future::Either; - use super::*; - use crate::client::connection::EitherConnection; + use crate::client::connection::EitherIoConnection; - pub(crate) struct InnerConnector + pub(crate) struct InnerConnector where + S1: Service + 'static, + S2: Service + 'static, Io1: AsyncRead + AsyncWrite + Unpin + 'static, Io2: AsyncRead + AsyncWrite + Unpin + 'static, - T1: Service, - T2: Service, { - pub(crate) tcp_pool: ConnectionPool, - pub(crate) ssl_pool: ConnectionPool, + pub(crate) tcp_pool: ConnectionPool, + pub(crate) ssl_pool: ConnectionPool, } - impl Clone for InnerConnector + impl Clone for InnerConnector where + S1: Service + 'static, + S2: Service + 'static, Io1: AsyncRead + AsyncWrite + Unpin + 'static, Io2: AsyncRead + AsyncWrite + Unpin + 'static, - T1: Service + 'static, - T2: Service + 'static, { fn clone(&self) -> Self { InnerConnector { @@ -447,19 +440,16 @@ mod connect_impl { } } - impl Service for InnerConnector + impl Service for InnerConnector where + S1: Service + 'static, + S2: Service + 'static, Io1: AsyncRead + AsyncWrite + Unpin + 'static, Io2: AsyncRead + AsyncWrite + Unpin + 'static, - T1: Service + 'static, - T2: Service + 'static, { - type Response = EitherConnection; + type Response = EitherIoConnection; type Error = ConnectError; - type Future = Either< - InnerConnectorResponseA, - InnerConnectorResponseB, - >; + type Future = InnerConnectorResponse; fn poll_ready(&self, cx: &mut Context<'_>) -> Poll> { self.tcp_pool.poll_ready(cx) @@ -467,69 +457,44 @@ mod connect_impl { fn call(&self, req: Connect) -> Self::Future { match req.uri.scheme_str() { - Some("https") | Some("wss") => Either::Right(InnerConnectorResponseB { - fut: self.ssl_pool.call(req), - _phantom: PhantomData, - }), - _ => Either::Left(InnerConnectorResponseA { - fut: self.tcp_pool.call(req), - _phantom: PhantomData, - }), + Some("https") | Some("wss") => { + InnerConnectorResponse::Io2(self.ssl_pool.call(req)) + } + _ => InnerConnectorResponse::Io1(self.tcp_pool.call(req)), } } } - #[pin_project::pin_project] - pub(crate) struct InnerConnectorResponseA + #[pin_project::pin_project(project = InnerConnectorProj)] + pub(crate) enum InnerConnectorResponse where - Io1: AsyncRead + AsyncWrite + Unpin + 'static, - T: Service + 'static, - { - #[pin] - fut: as Service>::Future, - _phantom: PhantomData, - } - - impl Future for InnerConnectorResponseA - where - T: Service + 'static, + S1: Service + 'static, + S2: Service + 'static, Io1: AsyncRead + AsyncWrite + Unpin + 'static, Io2: AsyncRead + AsyncWrite + Unpin + 'static, { - type Output = Result, ConnectError>; + Io1(#[pin] as Service>::Future), + Io2(#[pin] as Service>::Future), + } + + impl Future for InnerConnectorResponse + where + S1: Service + 'static, + S2: Service + 'static, + Io1: AsyncRead + AsyncWrite + Unpin + 'static, + Io2: AsyncRead + AsyncWrite + Unpin + 'static, + { + type Output = Result, ConnectError>; fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - Poll::Ready( - ready!(Pin::new(&mut self.get_mut().fut).poll(cx)) - .map(EitherConnection::A), - ) - } - } - - #[pin_project::pin_project] - pub(crate) struct InnerConnectorResponseB - where - Io2: AsyncRead + AsyncWrite + Unpin + 'static, - T: Service + 'static, - { - #[pin] - fut: as Service>::Future, - _phantom: PhantomData, - } - - impl Future for InnerConnectorResponseB - where - T: Service + 'static, - Io1: AsyncRead + AsyncWrite + Unpin + 'static, - Io2: AsyncRead + AsyncWrite + Unpin + 'static, - { - type Output = Result, ConnectError>; - - fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - Poll::Ready( - ready!(Pin::new(&mut self.get_mut().fut).poll(cx)) - .map(EitherConnection::B), - ) + match self.project() { + InnerConnectorProj::Io1(fut) => { + fut.poll(cx).map_ok(EitherIoConnection::A) + } + InnerConnectorProj::Io2(fut) => { + fut.poll(cx).map_ok(EitherIoConnection::B) + } + } } } } From 946cccaa1a2ace8d997723399da4d5e0b43457e3 Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Thu, 18 Feb 2021 04:30:09 -0800 Subject: [PATCH 055/339] refactor awc::ClientBuilder (#2008) --- actix-http-test/src/lib.rs | 2 - actix-http/src/client/mod.rs | 4 ++ awc/CHANGES.md | 7 +- awc/src/builder.rs | 115 ++++++++++++++++---------------- awc/src/lib.rs | 18 ++++- awc/tests/test_client.rs | 7 +- awc/tests/test_connector.rs | 2 +- awc/tests/test_rustls_client.rs | 2 +- awc/tests/test_ssl_client.rs | 2 +- src/test.rs | 2 - tests/test_httpserver.rs | 3 +- 11 files changed, 91 insertions(+), 73 deletions(-) diff --git a/actix-http-test/src/lib.rs b/actix-http-test/src/lib.rs index df5774998..8de07c8d3 100644 --- a/actix-http-test/src/lib.rs +++ b/actix-http-test/src/lib.rs @@ -96,14 +96,12 @@ pub async fn test_server_with_addr>( .conn_lifetime(time::Duration::from_secs(0)) .timeout(time::Duration::from_millis(30000)) .ssl(builder.build()) - .finish() } #[cfg(not(feature = "openssl"))] { Connector::new() .conn_lifetime(time::Duration::from_secs(0)) .timeout(time::Duration::from_millis(30000)) - .finish() } }; diff --git a/actix-http/src/client/mod.rs b/actix-http/src/client/mod.rs index 9c7f632ea..5f5e57edb 100644 --- a/actix-http/src/client/mod.rs +++ b/actix-http/src/client/mod.rs @@ -10,6 +10,10 @@ mod h1proto; mod h2proto; mod pool; +pub use actix_tls::connect::{ + Connect as TcpConnect, ConnectError as TcpConnectError, Connection as TcpConnection, +}; + pub use self::connection::Connection; pub use self::connector::Connector; pub use self::error::{ConnectError, FreezeRequestError, InvalidUrl, SendRequestError}; diff --git a/awc/CHANGES.md b/awc/CHANGES.md index c67f65560..e6ead2cc8 100644 --- a/awc/CHANGES.md +++ b/awc/CHANGES.md @@ -6,10 +6,15 @@ ### Changed * Feature `cookies` is now optional and enabled by default. [#1981] +* `ClientBuilder::connector` method would take `actix_http::client::Connector` type. [#2008] + +### Removed +* `ClientBuilder::default` function [#2008] +* `ClientBuilder::disable_redirects` and `ClientBuilder::max_redirects` method [#2008] [#1931]: https://github.com/actix/actix-web/pull/1931 [#1981]: https://github.com/actix/actix-web/pull/1981 - +[#2008]: https://github.com/actix/actix-web/pull/2008 ## 3.0.0-beta.2 - 2021-02-10 ### Added diff --git a/awc/src/builder.rs b/awc/src/builder.rs index b5e8bf5bf..4495b39fd 100644 --- a/awc/src/builder.rs +++ b/awc/src/builder.rs @@ -3,59 +3,80 @@ use std::fmt; use std::rc::Rc; use std::time::Duration; -use actix_http::client::{Connect as HttpConnect, ConnectError, Connection, Connector}; -use actix_http::http::{self, header, Error as HttpError, HeaderMap, HeaderName}; +use actix_codec::{AsyncRead, AsyncWrite}; +use actix_http::{ + client::{Connector, TcpConnect, TcpConnectError, TcpConnection}, + http::{self, header, Error as HttpError, HeaderMap, HeaderName, Uri}, +}; +use actix_rt::net::TcpStream; use actix_service::Service; -use crate::connect::{ConnectService, ConnectorWrapper}; +use crate::connect::ConnectorWrapper; use crate::{Client, ClientConfig}; /// An HTTP Client builder /// /// This type can be used to construct an instance of `Client` through a /// builder-like pattern. -pub struct ClientBuilder { +pub struct ClientBuilder { default_headers: bool, - allow_redirects: bool, - max_redirects: usize, max_http_version: Option, stream_window_size: Option, conn_window_size: Option, headers: HeaderMap, timeout: Option, - connector: Option, -} - -impl Default for ClientBuilder { - fn default() -> Self { - Self::new() - } + connector: Connector, } impl ClientBuilder { - pub fn new() -> Self { + #[allow(clippy::new_ret_no_self)] + pub fn new() -> ClientBuilder< + impl Service< + TcpConnect, + Response = TcpConnection, + Error = TcpConnectError, + > + Clone, + TcpStream, + > { ClientBuilder { default_headers: true, - allow_redirects: true, - max_redirects: 10, headers: HeaderMap::new(), timeout: Some(Duration::from_secs(5)), - connector: None, + connector: Connector::new(), max_http_version: None, stream_window_size: None, conn_window_size: None, } } +} +impl ClientBuilder +where + S: Service, Response = TcpConnection, Error = TcpConnectError> + + Clone + + 'static, + Io: AsyncRead + AsyncWrite + Unpin + fmt::Debug + 'static, +{ /// Use custom connector service. - pub fn connector(mut self, connector: T) -> Self + pub fn connector(self, connector: Connector) -> ClientBuilder where - T: Service + 'static, - T::Response: Connection, - T::Future: 'static, + S1: Service< + TcpConnect, + Response = TcpConnection, + Error = TcpConnectError, + > + Clone + + 'static, + Io1: AsyncRead + AsyncWrite + Unpin + fmt::Debug + 'static, { - self.connector = Some(Box::new(ConnectorWrapper::new(connector))); - self + ClientBuilder { + default_headers: self.default_headers, + headers: self.headers, + timeout: self.timeout, + connector, + max_http_version: self.max_http_version, + stream_window_size: self.stream_window_size, + conn_window_size: self.conn_window_size, + } } /// Set request timeout @@ -73,14 +94,6 @@ impl ClientBuilder { self } - /// Do not follow redirects. - /// - /// Redirects are allowed by default. - pub fn disable_redirects(mut self) -> Self { - self.allow_redirects = false; - self - } - /// Maximum supported HTTP major version. /// /// Supported versions are HTTP/1.1 and HTTP/2. @@ -107,14 +120,6 @@ impl ClientBuilder { self } - /// Set max number of redirects. - /// - /// Max redirects is set to 10 by default. - pub fn max_redirects(mut self, num: usize) -> Self { - self.max_redirects = num; - self - } - /// Do not add default request headers. /// By default `Date` and `User-Agent` headers are set. pub fn no_default_headers(mut self) -> Self { @@ -144,9 +149,9 @@ impl ClientBuilder { } /// Set client wide HTTP basic authorization header - pub fn basic_auth(self, username: U, password: Option<&str>) -> Self + pub fn basic_auth(self, username: N, password: Option<&str>) -> Self where - U: fmt::Display, + N: fmt::Display, { let auth = match password { Some(password) => format!("{}:{}", username, password), @@ -168,26 +173,24 @@ impl ClientBuilder { /// Finish build process and create `Client` instance. pub fn finish(self) -> Client { - let connector = if let Some(connector) = self.connector { - connector - } else { - let mut connector = Connector::new(); - if let Some(val) = self.max_http_version { - connector = connector.max_http_version(val) - }; - if let Some(val) = self.conn_window_size { - connector = connector.initial_connection_window_size(val) - }; - if let Some(val) = self.stream_window_size { - connector = connector.initial_window_size(val) - }; - Box::new(ConnectorWrapper::new(connector.finish())) as _ + let mut connector = self.connector; + + if let Some(val) = self.max_http_version { + connector = connector.max_http_version(val); }; + if let Some(val) = self.conn_window_size { + connector = connector.initial_connection_window_size(val) + }; + if let Some(val) = self.stream_window_size { + connector = connector.initial_window_size(val) + }; + let config = ClientConfig { headers: self.headers, timeout: self.timeout, - connector, + connector: Box::new(ConnectorWrapper::new(connector.finish())) as _, }; + Client(Rc::new(config)) } } diff --git a/awc/src/lib.rs b/awc/src/lib.rs index 1e27ed9ab..66ff55402 100644 --- a/awc/src/lib.rs +++ b/awc/src/lib.rs @@ -101,8 +101,13 @@ use std::time::Duration; pub use actix_http::cookie; pub use actix_http::{client::Connector, http}; -use actix_http::http::{Error as HttpError, HeaderMap, Method, Uri}; -use actix_http::RequestHead; +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 connect; @@ -169,7 +174,14 @@ impl Client { /// Create `Client` builder. /// This function is equivalent of `ClientBuilder::new()`. - pub fn builder() -> ClientBuilder { + pub fn builder() -> ClientBuilder< + impl Service< + TcpConnect, + Response = TcpConnection, + Error = TcpConnectError, + > + Clone, + TcpStream, + > { ClientBuilder::new() } diff --git a/awc/tests/test_client.rs b/awc/tests/test_client.rs index bcbaf3f41..a41a8dac3 100644 --- a/awc/tests/test_client.rs +++ b/awc/tests/test_client.rs @@ -120,8 +120,7 @@ async fn test_timeout() { let connector = awc::Connector::new() .connector(actix_tls::connect::default_connector()) - .timeout(Duration::from_secs(15)) - .finish(); + .timeout(Duration::from_secs(15)); let client = awc::Client::builder() .connector(connector) @@ -368,7 +367,7 @@ async fn test_connection_wait_queue() { .await; let client = awc::Client::builder() - .connector(awc::Connector::new().limit(1).finish()) + .connector(awc::Connector::new().limit(1)) .finish(); // req 1 @@ -417,7 +416,7 @@ async fn test_connection_wait_queue_force_close() { .await; let client = awc::Client::builder() - .connector(awc::Connector::new().limit(1).finish()) + .connector(awc::Connector::new().limit(1)) .finish(); // req 1 diff --git a/awc/tests/test_connector.rs b/awc/tests/test_connector.rs index fd725506d..632f68b72 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()).finish()) + .connector(awc::Connector::new().ssl(builder.build())) .initial_window_size(100) .initial_connection_window_size(100) .finish(); diff --git a/awc/tests/test_rustls_client.rs b/awc/tests/test_rustls_client.rs index a928715a8..464edfe89 100644 --- a/awc/tests/test_rustls_client.rs +++ b/awc/tests/test_rustls_client.rs @@ -82,7 +82,7 @@ async fn test_connection_reuse_h2() { .set_certificate_verifier(Arc::new(danger::NoCertificateVerification)); let client = awc::Client::builder() - .connector(awc::Connector::new().rustls(Arc::new(config)).finish()) + .connector(awc::Connector::new().rustls(Arc::new(config))) .finish(); // req 1 diff --git a/awc/tests/test_ssl_client.rs b/awc/tests/test_ssl_client.rs index 08aa125cd..3079aaf5e 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()).finish()) + .connector(awc::Connector::new().ssl(builder.build())) .finish(); // req 1 diff --git a/src/test.rs b/src/test.rs index 2ec4252b1..dd2426fec 100644 --- a/src/test.rs +++ b/src/test.rs @@ -761,14 +761,12 @@ where .conn_lifetime(time::Duration::from_secs(0)) .timeout(time::Duration::from_millis(30000)) .ssl(builder.build()) - .finish() } #[cfg(not(feature = "openssl"))] { Connector::new() .conn_lifetime(time::Duration::from_secs(0)) .timeout(time::Duration::from_millis(30000)) - .finish() } }; diff --git a/tests/test_httpserver.rs b/tests/test_httpserver.rs index 3aa1d36b0..043159376 100644 --- a/tests/test_httpserver.rs +++ b/tests/test_httpserver.rs @@ -137,8 +137,7 @@ async fn test_start_ssl() { .connector( awc::Connector::new() .ssl(builder.build()) - .timeout(Duration::from_millis(100)) - .finish(), + .timeout(Duration::from_millis(100)), ) .finish(); From 3b93c62e2309865dd8d4b9f8a0b53785c8011569 Mon Sep 17 00:00:00 2001 From: Richard Janis Goldschmidt Date: Thu, 18 Feb 2021 16:20:20 +0100 Subject: [PATCH 056/339] Fix Json extractor to be 32kB by default (#2010) --- CHANGES.md | 3 +++ src/types/json.rs | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 743f5066b..7cb03c30c 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -3,8 +3,11 @@ ## Unreleased - 2021-xx-xx ### 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 diff --git a/src/types/json.rs b/src/types/json.rs index 28960402a..31ff680f4 100644 --- a/src/types/json.rs +++ b/src/types/json.rs @@ -345,7 +345,7 @@ where let payload = payload.take(); JsonBody::Body { - limit: 262_144, + limit: 32_768, length, payload, buf: BytesMut::with_capacity(8192), @@ -353,7 +353,7 @@ where } } - /// Set maximum accepted payload size. The default limit is 256kB. + /// Set maximum accepted payload size. The default limit is 32kB. pub fn limit(self, limit: usize) -> Self { match self { JsonBody::Body { From 83365058cead13682124c769e1198419911bca22 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1vid=20Szab=C3=B3?= Date: Thu, 18 Feb 2021 22:56:24 +0100 Subject: [PATCH 057/339] Fix HTTP client link (#2011) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index cc7c4cd52..bf68e7961 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,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://actix.rs/actix-web/actix_web/client/index.html) +* Includes an async [HTTP client](https://docs.rs/actix-web/latest/actix_web/client/index.html) * Runs on stable Rust 1.46+ ## Documentation From 2dbdf61c377dccf9f755b49804e70f4661594a17 Mon Sep 17 00:00:00 2001 From: Adam Chalmers Date: Sat, 20 Feb 2021 11:59:09 -0600 Subject: [PATCH 058/339] Inner field of web::Query is public again (#2016) (#2017) Co-authored-by: Rob Ede --- src/types/query.rs | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/types/query.rs b/src/types/query.rs index 691a4792b..79af32581 100644 --- a/src/types/query.rs +++ b/src/types/query.rs @@ -29,7 +29,7 @@ use crate::{dev::Payload, error::QueryPayloadError, Error, FromRequest, HttpRequ /// Code /// } /// -/// #[derive(Deserialize)] +/// #[derive(Debug, Deserialize)] /// pub struct AuthRequest { /// id: u64, /// response_type: ResponseType, @@ -42,9 +42,23 @@ use crate::{dev::Payload, error::QueryPayloadError, Error, FromRequest, HttpRequ /// async fn index(info: web::Query) -> String { /// format!("Authorization request for id={} and type={:?}!", info.id, info.response_type) /// } +/// +/// // 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()); +/// "OK".to_string() +/// } +/// +/// // Or use `.0`, which is equivalent to `.into_inner()`. +/// #[get("/debug2")] +/// async fn debug2(info: web::Query) -> String { +/// dbg!("Authorization object={:?}", info.0); +/// "OK".to_string() +/// } /// ``` #[derive(Clone, PartialEq, Eq, PartialOrd, Ord)] -pub struct Query(T); +pub struct Query(pub T); impl Query { /// Unwrap into inner `T` value. From aacec30ad160096b9a3f03db9618fd25b0f1a3a4 Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Mon, 22 Feb 2021 03:15:12 -0800 Subject: [PATCH 059/339] reduce duplicate code (#2020) --- actix-http/src/client/connection.rs | 63 +++---- actix-http/src/client/connector.rs | 263 +++++++++++----------------- 2 files changed, 130 insertions(+), 196 deletions(-) diff --git a/actix-http/src/client/connection.rs b/actix-http/src/client/connection.rs index 778083a1c..97ecd0515 100644 --- a/actix-http/src/client/connection.rs +++ b/actix-http/src/client/connection.rs @@ -75,11 +75,14 @@ pub trait Connection { type Io: AsyncRead + AsyncWrite + Unpin; /// Send request and body - fn send_request>( + fn send_request( self, head: H, body: B, - ) -> LocalBoxFuture<'static, Result<(ResponseHead, Payload), SendRequestError>>; + ) -> LocalBoxFuture<'static, Result<(ResponseHead, Payload), SendRequestError>> + where + B: MessageBody + 'static, + H: Into + 'static; /// Send request, returns Response and Framed fn open_tunnel + 'static>( @@ -144,47 +147,31 @@ impl IoConnection { pub(crate) fn into_parts(self) -> (ConnectionType, time::Instant, Acquired) { (self.io.unwrap(), self.created, self.pool.unwrap()) } -} -impl Connection for IoConnection -where - T: AsyncRead + AsyncWrite + Unpin + 'static, -{ - type Io = T; - - fn send_request>( + async fn send_request>( mut self, head: H, body: B, - ) -> LocalBoxFuture<'static, Result<(ResponseHead, Payload), SendRequestError>> { + ) -> Result<(ResponseHead, Payload), SendRequestError> { match self.io.take().unwrap() { - ConnectionType::H1(io) => Box::pin(h1proto::send_request( - io, - head.into(), - body, - self.created, - self.pool, - )), - ConnectionType::H2(io) => Box::pin(h2proto::send_request( - io, - head.into(), - body, - self.created, - self.pool, - )), + ConnectionType::H1(io) => { + h1proto::send_request(io, head.into(), body, self.created, self.pool) + .await + } + ConnectionType::H2(io) => { + h2proto::send_request(io, head.into(), body, self.created, self.pool) + .await + } } } /// Send request, returns Response and Framed - fn open_tunnel>( + async fn open_tunnel>( mut self, head: H, - ) -> LocalBoxFuture< - 'static, - Result<(ResponseHead, Framed), SendRequestError>, - > { + ) -> Result<(ResponseHead, Framed), SendRequestError> { match self.io.take().unwrap() { - ConnectionType::H1(io) => Box::pin(h1proto::open_tunnel(io, head.into())), + ConnectionType::H1(io) => h1proto::open_tunnel(io, head.into()).await, ConnectionType::H2(io) => { if let Some(mut pool) = self.pool.take() { pool.release(IoConnection::new( @@ -193,7 +180,7 @@ where None, )); } - Box::pin(async { Err(SendRequestError::TunnelNotSupported) }) + Err(SendRequestError::TunnelNotSupported) } } } @@ -216,14 +203,18 @@ where { type Io = EitherIo; - fn send_request>( + fn send_request( self, head: H, body: RB, - ) -> LocalBoxFuture<'static, Result<(ResponseHead, Payload), SendRequestError>> { + ) -> LocalBoxFuture<'static, Result<(ResponseHead, Payload), SendRequestError>> + where + RB: MessageBody + 'static, + H: Into + 'static, + { match self { - EitherIoConnection::A(con) => con.send_request(head, body), - EitherIoConnection::B(con) => con.send_request(head, body), + EitherIoConnection::A(con) => Box::pin(con.send_request(head, body)), + EitherIoConnection::B(con) => Box::pin(con.send_request(head, body)), } } diff --git a/actix-http/src/client/connector.rs b/actix-http/src/client/connector.rs index 65536f257..8aa5b1319 100644 --- a/actix-http/src/client/connector.rs +++ b/actix-http/src/client/connector.rs @@ -1,5 +1,8 @@ use std::fmt; +use std::future::Future; use std::marker::PhantomData; +use std::pin::Pin; +use std::task::{Context, Poll}; use std::time::Duration; use actix_codec::{AsyncRead, AsyncWrite}; @@ -12,7 +15,7 @@ use actix_utils::timeout::{TimeoutError, TimeoutService}; use http::Uri; use super::config::ConnectorConfig; -use super::connection::Connection; +use super::connection::{Connection, EitherIoConnection}; use super::error::ConnectError; use super::pool::{ConnectionPool, Protocol}; use super::Connect; @@ -55,7 +58,7 @@ pub struct Connector { _phantom: PhantomData, } -trait Io: AsyncRead + AsyncWrite + Unpin {} +pub trait Io: AsyncRead + AsyncWrite + Unpin {} impl Io for T {} impl Connector<(), ()> { @@ -244,28 +247,43 @@ where self, ) -> impl Service + Clone { + let tcp_service = TimeoutService::new( + self.config.timeout, + apply_fn(self.connector.clone(), |msg: Connect, srv| { + srv.call(TcpConnect::new(msg.uri).set_addr(msg.addr)) + }) + .map_err(ConnectError::from) + .map(|stream| (stream.into_parts().0, Protocol::Http1)), + ) + .map_err(|e| match e { + TimeoutError::Service(e) => e, + TimeoutError::Timeout => ConnectError::Timeout, + }); + #[cfg(not(any(feature = "openssl", feature = "rustls")))] { - let connector = TimeoutService::new( - self.config.timeout, - apply_fn(self.connector, |msg: Connect, srv| { - srv.call(TcpConnect::new(msg.uri).set_addr(msg.addr)) - }) - .map_err(ConnectError::from) - .map(|stream| (stream.into_parts().0, Protocol::Http1)), - ) - .map_err(|e| match e { - TimeoutError::Service(e) => e, - TimeoutError::Timeout => ConnectError::Timeout, - }); + // A dummy service for annotate tls pool's type signature. + pub type DummyService = Box< + dyn Service< + Connect, + Response = (Box, Protocol), + Error = ConnectError, + Future = futures_core::future::LocalBoxFuture< + 'static, + Result<(Box, Protocol), ConnectError>, + >, + >, + >; - connect_impl::InnerConnector { + InnerConnector::<_, DummyService, _, Box> { tcp_pool: ConnectionPool::new( - connector, + tcp_service, self.config.no_disconnect_timeout(), ), + tls_pool: None, } } + #[cfg(any(feature = "openssl", feature = "rustls"))] { const H2: &[u8] = b"h2"; @@ -328,172 +346,97 @@ where TimeoutError::Timeout => ConnectError::Timeout, }); - let tcp_service = TimeoutService::new( - self.config.timeout, - apply_fn(self.connector, |msg: Connect, srv| { - srv.call(TcpConnect::new(msg.uri).set_addr(msg.addr)) - }) - .map_err(ConnectError::from) - .map(|stream| (stream.into_parts().0, Protocol::Http1)), - ) - .map_err(|e| match e { - TimeoutError::Service(e) => e, - TimeoutError::Timeout => ConnectError::Timeout, - }); - - connect_impl::InnerConnector { + InnerConnector { tcp_pool: ConnectionPool::new( tcp_service, self.config.no_disconnect_timeout(), ), - ssl_pool: ConnectionPool::new(ssl_service, self.config), + tls_pool: Some(ConnectionPool::new(ssl_service, self.config)), } } } } -#[cfg(not(any(feature = "openssl", feature = "rustls")))] -mod connect_impl { - use std::task::{Context, Poll}; +struct InnerConnector +where + S1: Service + 'static, + S2: Service + 'static, + Io1: AsyncRead + AsyncWrite + Unpin + 'static, + Io2: AsyncRead + AsyncWrite + Unpin + 'static, +{ + tcp_pool: ConnectionPool, + tls_pool: Option>, +} - use futures_core::future::LocalBoxFuture; - - use super::*; - use crate::client::connection::IoConnection; - - pub(crate) struct InnerConnector - where - Io: AsyncRead + AsyncWrite + Unpin + 'static, - T: Service + 'static, - { - pub(crate) tcp_pool: ConnectionPool, - } - - impl Clone for InnerConnector - where - Io: AsyncRead + AsyncWrite + Unpin + 'static, - T: Service + 'static, - { - fn clone(&self) -> Self { - InnerConnector { - tcp_pool: self.tcp_pool.clone(), - } - } - } - - impl Service for InnerConnector - where - Io: AsyncRead + AsyncWrite + Unpin + 'static, - T: Service + 'static, - { - type Response = IoConnection; - type Error = ConnectError; - type Future = LocalBoxFuture<'static, Result, ConnectError>>; - - fn poll_ready(&self, cx: &mut Context<'_>) -> Poll> { - self.tcp_pool.poll_ready(cx) - } - - fn call(&self, req: Connect) -> Self::Future { - match req.uri.scheme_str() { - Some("https") | Some("wss") => { - Box::pin(async { Err(ConnectError::SslIsNotSupported) }) - } - _ => self.tcp_pool.call(req), - } +impl Clone for InnerConnector +where + S1: Service + 'static, + S2: Service + 'static, + Io1: AsyncRead + AsyncWrite + Unpin + 'static, + Io2: AsyncRead + AsyncWrite + Unpin + 'static, +{ + fn clone(&self) -> Self { + InnerConnector { + tcp_pool: self.tcp_pool.clone(), + tls_pool: self.tls_pool.as_ref().cloned(), } } } -#[cfg(any(feature = "openssl", feature = "rustls"))] -mod connect_impl { - use std::future::Future; - use std::pin::Pin; - use std::task::{Context, Poll}; +impl Service for InnerConnector +where + S1: Service + 'static, + S2: Service + 'static, + Io1: AsyncRead + AsyncWrite + Unpin + 'static, + Io2: AsyncRead + AsyncWrite + Unpin + 'static, +{ + type Response = EitherIoConnection; + type Error = ConnectError; + type Future = InnerConnectorResponse; - use super::*; - use crate::client::connection::EitherIoConnection; - - pub(crate) struct InnerConnector - where - S1: Service + 'static, - S2: Service + 'static, - Io1: AsyncRead + AsyncWrite + Unpin + 'static, - Io2: AsyncRead + AsyncWrite + Unpin + 'static, - { - pub(crate) tcp_pool: ConnectionPool, - pub(crate) ssl_pool: ConnectionPool, + fn poll_ready(&self, cx: &mut Context<'_>) -> Poll> { + self.tcp_pool.poll_ready(cx) } - impl Clone for InnerConnector - where - S1: Service + 'static, - S2: Service + 'static, - Io1: AsyncRead + AsyncWrite + Unpin + 'static, - Io2: AsyncRead + AsyncWrite + Unpin + 'static, - { - fn clone(&self) -> Self { - InnerConnector { - tcp_pool: self.tcp_pool.clone(), - ssl_pool: self.ssl_pool.clone(), - } + fn call(&self, req: Connect) -> Self::Future { + match req.uri.scheme_str() { + Some("https") | Some("wss") => match self.tls_pool { + None => InnerConnectorResponse::SslIsNotSupported, + Some(ref pool) => InnerConnectorResponse::Io2(pool.call(req)), + }, + _ => InnerConnectorResponse::Io1(self.tcp_pool.call(req)), } } +} - impl Service for InnerConnector - where - S1: Service + 'static, - S2: Service + 'static, - Io1: AsyncRead + AsyncWrite + Unpin + 'static, - Io2: AsyncRead + AsyncWrite + Unpin + 'static, - { - type Response = EitherIoConnection; - type Error = ConnectError; - type Future = InnerConnectorResponse; +#[pin_project::pin_project(project = InnerConnectorProj)] +enum InnerConnectorResponse +where + S1: Service + 'static, + S2: Service + 'static, + Io1: AsyncRead + AsyncWrite + Unpin + 'static, + Io2: AsyncRead + AsyncWrite + Unpin + 'static, +{ + Io1(#[pin] as Service>::Future), + Io2(#[pin] as Service>::Future), + SslIsNotSupported, +} - fn poll_ready(&self, cx: &mut Context<'_>) -> Poll> { - self.tcp_pool.poll_ready(cx) - } +impl Future for InnerConnectorResponse +where + S1: Service + 'static, + S2: Service + 'static, + Io1: AsyncRead + AsyncWrite + Unpin + 'static, + Io2: AsyncRead + AsyncWrite + Unpin + 'static, +{ + type Output = Result, ConnectError>; - fn call(&self, req: Connect) -> Self::Future { - match req.uri.scheme_str() { - Some("https") | Some("wss") => { - InnerConnectorResponse::Io2(self.ssl_pool.call(req)) - } - _ => InnerConnectorResponse::Io1(self.tcp_pool.call(req)), - } - } - } - - #[pin_project::pin_project(project = InnerConnectorProj)] - pub(crate) enum InnerConnectorResponse - where - S1: Service + 'static, - S2: Service + 'static, - Io1: AsyncRead + AsyncWrite + Unpin + 'static, - Io2: AsyncRead + AsyncWrite + Unpin + 'static, - { - Io1(#[pin] as Service>::Future), - Io2(#[pin] as Service>::Future), - } - - impl Future for InnerConnectorResponse - where - S1: Service + 'static, - S2: Service + 'static, - Io1: AsyncRead + AsyncWrite + Unpin + 'static, - Io2: AsyncRead + AsyncWrite + Unpin + 'static, - { - type Output = Result, ConnectError>; - - fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - match self.project() { - InnerConnectorProj::Io1(fut) => { - fut.poll(cx).map_ok(EitherIoConnection::A) - } - InnerConnectorProj::Io2(fut) => { - fut.poll(cx).map_ok(EitherIoConnection::B) - } + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + match self.project() { + InnerConnectorProj::Io1(fut) => fut.poll(cx).map_ok(EitherIoConnection::A), + InnerConnectorProj::Io2(fut) => fut.poll(cx).map_ok(EitherIoConnection::B), + InnerConnectorProj::SslIsNotSupported => { + Poll::Ready(Err(ConnectError::SslIsNotSupported)) } } } From 5845b3965cb346c7969e93f4b531c6bff8d90dd0 Mon Sep 17 00:00:00 2001 From: Alex Rebert Date: Mon, 22 Feb 2021 07:00:08 -0500 Subject: [PATCH 060/339] actix-http-test: minimize features of dependencies (#2019) --- actix-http-test/Cargo.toml | 4 ++-- actix-http/src/error.rs | 1 + actix-http/src/response.rs | 8 ++++++-- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/actix-http-test/Cargo.toml b/actix-http-test/Cargo.toml index 6dcf73637..8ec073661 100644 --- a/actix-http-test/Cargo.toml +++ b/actix-http-test/Cargo.toml @@ -35,7 +35,7 @@ actix-tls = "3.0.0-beta.3" actix-utils = "3.0.0-beta.2" actix-rt = "2" actix-server = "2.0.0-beta.3" -awc = "3.0.0-beta.2" +awc = { version = "3.0.0-beta.2", default-features = false } base64 = "0.13" bytes = "1" @@ -57,5 +57,5 @@ features = ["vendored"] optional = true [dev-dependencies] -actix-web = "4.0.0-beta.3" +actix-web = { version = "4.0.0-beta.3", default-features = false, features = ["cookies"] } actix-http = "3.0.0-beta.3" diff --git a/actix-http/src/error.rs b/actix-http/src/error.rs index 97f2b3eff..d3095e68d 100644 --- a/actix-http/src/error.rs +++ b/actix-http/src/error.rs @@ -983,6 +983,7 @@ mod tests { assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR); } + #[cfg(feature = "cookies")] #[test] fn test_cookie_parse() { let resp: Response = CookieParseError::EmptyName.error_response(); diff --git a/actix-http/src/response.rs b/actix-http/src/response.rs index 471dacd28..f96f8f9b6 100644 --- a/actix-http/src/response.rs +++ b/actix-http/src/response.rs @@ -896,8 +896,9 @@ mod tests { use super::*; use crate::body::Body; - use crate::http::header::{HeaderValue, CONTENT_TYPE, COOKIE, SET_COOKIE}; - use crate::HttpMessage; + use crate::http::header::{HeaderValue, CONTENT_TYPE, COOKIE}; + #[cfg(feature = "cookies")] + use crate::{http::header::SET_COOKIE, HttpMessage}; #[test] fn test_debug() { @@ -909,6 +910,7 @@ mod tests { assert!(dbg.contains("Response")); } + #[cfg(feature = "cookies")] #[test] fn test_response_cookies() { let req = crate::test::TestRequest::default() @@ -946,6 +948,7 @@ mod tests { ); } + #[cfg(feature = "cookies")] #[test] fn test_update_response_cookies() { let mut r = Response::Ok() @@ -1097,6 +1100,7 @@ mod tests { assert_eq!(resp.body().get_ref(), b"test"); } + #[cfg(feature = "cookies")] #[test] fn test_into_builder() { let mut resp: Response = "test".into(); From d92ab7e8e0ddb2d170e5a7ea939f1b4a80091fb1 Mon Sep 17 00:00:00 2001 From: Ibraheem Ahmed Date: Mon, 22 Feb 2021 10:39:31 -0500 Subject: [PATCH 061/339] add msrv to clippy config (#1862) --- clippy.toml | 1 + 1 file changed, 1 insertion(+) create mode 100644 clippy.toml diff --git a/clippy.toml b/clippy.toml new file mode 100644 index 000000000..eb66960ac --- /dev/null +++ b/clippy.toml @@ -0,0 +1 @@ +msrv = "1.46" From f6393728c7d4a820ab9cfff81a1cf627a2007889 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Wed, 24 Feb 2021 09:08:56 +0000 Subject: [PATCH 062/339] remove usage of actix_utils::mpsc (#2023) --- actix-multipart/Cargo.toml | 4 +++- actix-multipart/src/server.rs | 15 +++++++++++---- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/actix-multipart/Cargo.toml b/actix-multipart/Cargo.toml index 67b2698bd..f3bb92336 100644 --- a/actix-multipart/Cargo.toml +++ b/actix-multipart/Cargo.toml @@ -22,7 +22,7 @@ actix-utils = "3.0.0-beta.2" bytes = "1" derive_more = "0.99.5" httparse = "1.3" -futures-util = { version = "0.3.7", default-features = false } +futures-util = { version = "0.3.7", default-features = false, features = ["alloc"] } log = "0.4" mime = "0.3" twoway = "0.2" @@ -30,3 +30,5 @@ twoway = "0.2" [dev-dependencies] actix-rt = "2" actix-http = "3.0.0-beta.3" +tokio = { version = "1", features = ["sync"] } +tokio-stream = "0.1" diff --git a/actix-multipart/src/server.rs b/actix-multipart/src/server.rs index 8cd1c8e0c..d9ff3d574 100644 --- a/actix-multipart/src/server.rs +++ b/actix-multipart/src/server.rs @@ -804,12 +804,13 @@ mod tests { use super::*; use actix_http::h1::Payload; - use actix_utils::mpsc; use actix_web::http::header::{DispositionParam, DispositionType}; use actix_web::test::TestRequest; use actix_web::FromRequest; use bytes::Bytes; use futures_util::future::lazy; + use tokio::sync::mpsc; + use tokio_stream::wrappers::UnboundedReceiverStream; #[actix_rt::test] async fn test_boundary() { @@ -855,13 +856,17 @@ mod tests { } fn create_stream() -> ( - mpsc::Sender>, + mpsc::UnboundedSender>, impl Stream>, ) { - let (tx, rx) = mpsc::channel(); + let (tx, rx) = mpsc::unbounded_channel(); - (tx, rx.map(|res| res.map_err(|_| panic!()))) + ( + tx, + UnboundedReceiverStream::new(rx).map(|res| res.map_err(|_| panic!())), + ) } + // Stream that returns from a Bytes, one char at a time and Pending every other poll() struct SlowStream { bytes: Bytes, @@ -889,9 +894,11 @@ mod tests { cx.waker().wake_by_ref(); return Poll::Pending; } + if this.pos == this.bytes.len() { return Poll::Ready(None); } + let res = Poll::Ready(Some(Ok(this.bytes.slice(this.pos..(this.pos + 1))))); this.pos += 1; this.ready = false; From 42711c23d75fb53d1daf2eadaa15489e386e170b Mon Sep 17 00:00:00 2001 From: Erik Johnston Date: Wed, 24 Feb 2021 12:26:56 +0000 Subject: [PATCH 063/339] Port over doc comments in route macros. (#2022) Co-authored-by: Jonas Platte Co-authored-by: Rob Ede --- actix-web-codegen/CHANGES.md | 3 +++ actix-web-codegen/src/route.rs | 18 ++++++++++++++++++ actix-web-codegen/tests/trybuild.rs | 2 ++ .../tests/trybuild/docstring-ok.rs | 17 +++++++++++++++++ 4 files changed, 40 insertions(+) create mode 100644 actix-web-codegen/tests/trybuild/docstring-ok.rs diff --git a/actix-web-codegen/CHANGES.md b/actix-web-codegen/CHANGES.md index 2ce728aad..f1dbf8e5f 100644 --- a/actix-web-codegen/CHANGES.md +++ b/actix-web-codegen/CHANGES.md @@ -1,6 +1,9 @@ # Changes ## Unreleased - 2021-xx-xx +* Preserve doc comments when using route macros. [#2022] + +[#2022]: https://github.com/actix/actix-web/pull/2022 ## 0.5.0-beta.1 - 2021-02-10 diff --git a/actix-web-codegen/src/route.rs b/actix-web-codegen/src/route.rs index ddbd42454..e3c5f0368 100644 --- a/actix-web-codegen/src/route.rs +++ b/actix-web-codegen/src/route.rs @@ -176,6 +176,9 @@ pub struct Route { args: Args, 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 { @@ -221,6 +224,18 @@ 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. + let doc_attributes = ast + .attrs + .iter() + .filter(|attr| attr.path.is_ident("doc")) + .cloned() + .collect(); + let args = Args::new(args, method)?; if args.methods.is_empty() { return Err(syn::Error::new( @@ -248,6 +263,7 @@ impl Route { args, ast, resource_type, + doc_attributes, }) } } @@ -265,6 +281,7 @@ impl ToTokens for Route { methods, }, resource_type, + doc_attributes, } = self; let resource_name = name.to_string(); let method_guards = { @@ -287,6 +304,7 @@ impl ToTokens for Route { }; let stream = quote! { + #(#doc_attributes)* #[allow(non_camel_case_types, missing_docs)] pub struct #name; diff --git a/actix-web-codegen/tests/trybuild.rs b/actix-web-codegen/tests/trybuild.rs index d2d8a38f5..afbe7b728 100644 --- a/actix-web-codegen/tests/trybuild.rs +++ b/actix-web-codegen/tests/trybuild.rs @@ -9,6 +9,8 @@ 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.pass("tests/trybuild/docstring-ok.rs"); } // #[rustversion::not(nightly)] diff --git a/actix-web-codegen/tests/trybuild/docstring-ok.rs b/actix-web-codegen/tests/trybuild/docstring-ok.rs new file mode 100644 index 000000000..2910976c7 --- /dev/null +++ b/actix-web-codegen/tests/trybuild/docstring-ok.rs @@ -0,0 +1,17 @@ +use actix_web::{Responder, HttpResponse, App, test}; +use actix_web_codegen::*; + +/// Docstrings shouldn't break anything. +#[get("/")] +async fn index() -> impl Responder { + HttpResponse::Ok() +} + +#[actix_web::main] +async fn main() { + let srv = test::start(|| App::new().service(index)); + + let request = srv.get("/"); + let response = request.send().await.unwrap(); + assert!(response.status().is_success()); +} From ebaf25d55a6bd62fc26472ec17184ccc7a574a31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Baran=20Demirba=C5=9F?= <77154643+barandemirbas@users.noreply.github.com> Date: Wed, 24 Feb 2021 19:19:40 +0300 Subject: [PATCH 064/339] Fix typos in CHANGES.md (#2025) Co-authored-by: Rob Ede --- CHANGES.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 7cb03c30c..819237ab7 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -168,7 +168,7 @@ ## 3.0.0-beta.4 - 2020-09-09 ### Added -* `middleware::NormalizePath` now has configurable behaviour 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 @@ -496,7 +496,7 @@ ## [1.0.0-rc] - 2019-05-18 -### Add +### Added * Add `Query::from_query()` to extract parameters from a query string. #846 * `QueryConfig`, similar to `JsonConfig` for customizing error handling of query extractors. @@ -512,7 +512,7 @@ ## [1.0.0-beta.4] - 2019-05-12 -### Add +### Added * Allow to set/override app data on scope level @@ -538,7 +538,7 @@ * CORS handling without headers #702 -* Allow to construct `Data` instances to avoid double `Arc` for `Send + Sync` types. +* Allow constructing `Data` instances to avoid double `Arc` for `Send + Sync` types. ### Fixed @@ -602,7 +602,7 @@ ### Changed -* Allow to use any service as default service. +* Allow using any service as default service. * Remove generic type for request payload, always use default. @@ -665,13 +665,13 @@ ### 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 From 8f2a97c6e34eec3f55f5dae4f28a80f97e1c37bf Mon Sep 17 00:00:00 2001 From: "Daniel T. Rodrigues" <45438149+danitrod@users.noreply.github.com> Date: Thu, 25 Feb 2021 22:21:56 -0300 Subject: [PATCH 065/339] Update README example links (#2027) The examples repo went through a folder restructuring. More info: https://github.com/actix/examples/pull/411 --- README.md | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index bf68e7961..b3448140a 100644 --- a/README.md +++ b/README.md @@ -71,18 +71,18 @@ async fn main() -> std::io::Result<()> { ### More examples -* [Basic Setup](https://github.com/actix/examples/tree/master/basics/) -* [Application State](https://github.com/actix/examples/tree/master/state/) -* [JSON Handling](https://github.com/actix/examples/tree/master/json/) -* [Multipart Streams](https://github.com/actix/examples/tree/master/multipart/) -* [Diesel Integration](https://github.com/actix/examples/tree/master/diesel/) -* [r2d2 Integration](https://github.com/actix/examples/tree/master/r2d2/) -* [Simple WebSocket](https://github.com/actix/examples/tree/master/websocket/) -* [Tera Templates](https://github.com/actix/examples/tree/master/template_tera/) -* [Askama Templates](https://github.com/actix/examples/tree/master/template_askama/) -* [HTTPS using Rustls](https://github.com/actix/examples/tree/master/rustls/) -* [HTTPS using OpenSSL](https://github.com/actix/examples/tree/master/openssl/) -* [WebSocket Chat](https://github.com/actix/examples/tree/master/websocket-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. From b95e1dda34a79b4d2ffb260eb6a1874f20a1caef Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sat, 27 Feb 2021 19:57:09 +0000 Subject: [PATCH 066/339] pin h2 to 0.3.0 --- .github/workflows/upload-doc.yml | 10 ++++------ Cargo.toml | 2 +- actix-http-test/Cargo.toml | 2 +- actix-http/Cargo.toml | 6 +++--- actix-http/src/h1/service.rs | 8 ++++---- actix-http/src/h2/service.rs | 6 +++--- actix-http/src/service.rs | 10 +++++----- awc/Cargo.toml | 2 +- tests/test_server.rs | 5 ----- 9 files changed, 22 insertions(+), 29 deletions(-) diff --git a/.github/workflows/upload-doc.yml b/.github/workflows/upload-doc.yml index c080dd8c3..94a2ddfbe 100644 --- a/.github/workflows/upload-doc.yml +++ b/.github/workflows/upload-doc.yml @@ -1,14 +1,12 @@ -name: Upload documentation +name: Upload Documentation on: push: - branches: - - master + branches: [master] jobs: build: runs-on: ubuntu-latest - if: github.repository == 'actix/actix-web' steps: - uses: actions/checkout@v2 @@ -20,14 +18,14 @@ jobs: profile: minimal override: true - - name: check build + - name: Build Docs uses: actions-rs/cargo@v1 with: command: doc args: --workspace --all-features --no-deps - name: Tweak HTML - run: echo "" > target/doc/index.html + run: echo '' > target/doc/index.html - name: Deploy to GitHub Pages uses: JamesIves/github-pages-deploy-action@3.7.1 diff --git a/Cargo.toml b/Cargo.toml index 1a1b8645c..bc96072d1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -84,7 +84,7 @@ actix-rt = "2" actix-server = "2.0.0-beta.3" actix-service = "2.0.0-beta.4" actix-utils = "3.0.0-beta.2" -actix-tls = { version = "3.0.0-beta.3", default-features = false, optional = true } +actix-tls = { version = "3.0.0-beta.4", default-features = false, optional = true } actix-web-codegen = "0.5.0-beta.1" actix-http = "3.0.0-beta.3" diff --git a/actix-http-test/Cargo.toml b/actix-http-test/Cargo.toml index 8ec073661..c880cba78 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-beta.4" actix-codec = "0.4.0-beta.1" -actix-tls = "3.0.0-beta.3" +actix-tls = "3.0.0-beta.4" actix-utils = "3.0.0-beta.2" actix-rt = "2" actix-server = "2.0.0-beta.3" diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 78fb55079..8ebf55c05 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -48,7 +48,7 @@ actix-service = "2.0.0-beta.4" actix-codec = "0.4.0-beta.1" actix-utils = "3.0.0-beta.2" actix-rt = "2" -actix-tls = "3.0.0-beta.2" +actix-tls = "3.0.0-beta.4" ahash = "0.7" base64 = "0.13" @@ -61,7 +61,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.0" +h2 = "=0.3.0" http = "0.2.2" httparse = "1.3" itoa = "0.4" @@ -90,7 +90,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.2", features = ["openssl"] } -actix-tls = { version = "3.0.0-beta.2", features = ["openssl"] } +actix-tls = { version = "3.0.0-beta.4", features = ["openssl"] } criterion = "0.3" env_logger = "0.8" rcgen = "0.8" diff --git a/actix-http/src/h1/service.rs b/actix-http/src/h1/service.rs index b79453ebd..51303886b 100644 --- a/actix-http/src/h1/service.rs +++ b/actix-http/src/h1/service.rs @@ -94,10 +94,10 @@ mod openssl { use super::*; use actix_service::ServiceFactoryExt; - use actix_tls::accept::openssl::{Acceptor, SslAcceptor, SslError, SslStream}; + use actix_tls::accept::openssl::{Acceptor, SslAcceptor, SslError, TlsStream}; use actix_tls::accept::TlsError; - impl H1Service, S, B, X, U> + impl H1Service, S, B, X, U> where S: ServiceFactory, S::Error: Into, @@ -108,7 +108,7 @@ mod openssl { X::Error: Into, X::InitError: fmt::Debug, U: ServiceFactory< - (Request, Framed, Codec>), + (Request, Framed, Codec>), Config = (), Response = (), >, @@ -131,7 +131,7 @@ mod openssl { .map_err(TlsError::Tls) .map_init_err(|_| panic!()), ) - .and_then(|io: SslStream| { + .and_then(|io: TlsStream| { let peer_addr = io.get_ref().peer_addr().ok(); ready(Ok((io, peer_addr))) }) diff --git a/actix-http/src/h2/service.rs b/actix-http/src/h2/service.rs index e00c8d968..0984b3f23 100644 --- a/actix-http/src/h2/service.rs +++ b/actix-http/src/h2/service.rs @@ -93,12 +93,12 @@ where #[cfg(feature = "openssl")] mod openssl { use actix_service::{fn_factory, fn_service, ServiceFactoryExt}; - use actix_tls::accept::openssl::{Acceptor, SslAcceptor, SslError, SslStream}; + use actix_tls::accept::openssl::{Acceptor, SslAcceptor, SslError, TlsStream}; use actix_tls::accept::TlsError; use super::*; - impl H2Service, S, B> + impl H2Service, S, B> where S: ServiceFactory, S::Error: Into + 'static, @@ -123,7 +123,7 @@ mod openssl { .map_init_err(|_| panic!()), ) .and_then(fn_factory(|| { - ok::<_, S::InitError>(fn_service(|io: SslStream| { + ok::<_, S::InitError>(fn_service(|io: TlsStream| { let peer_addr = io.get_ref().peer_addr().ok(); ok((io, peer_addr)) })) diff --git a/actix-http/src/service.rs b/actix-http/src/service.rs index fee26dcc3..402affb7e 100644 --- a/actix-http/src/service.rs +++ b/actix-http/src/service.rs @@ -185,10 +185,10 @@ where mod openssl { use super::*; use actix_service::ServiceFactoryExt; - use actix_tls::accept::openssl::{Acceptor, SslAcceptor, SslError, SslStream}; + use actix_tls::accept::openssl::{Acceptor, SslAcceptor, SslError, TlsStream}; use actix_tls::accept::TlsError; - impl HttpService, S, B, X, U> + impl HttpService, S, B, X, U> where S: ServiceFactory, S::Error: Into + 'static, @@ -201,13 +201,13 @@ mod openssl { X::InitError: fmt::Debug, >::Future: 'static, U: ServiceFactory< - (Request, Framed, h1::Codec>), + (Request, Framed, h1::Codec>), Config = (), Response = (), >, U::Error: fmt::Display + Into, U::InitError: fmt::Debug, - , h1::Codec>)>>::Future: 'static, + , h1::Codec>)>>::Future: 'static, { /// Create openssl based service pub fn openssl( @@ -225,7 +225,7 @@ mod openssl { .map_err(TlsError::Tls) .map_init_err(|_| panic!()), ) - .and_then(|io: SslStream| async { + .and_then(|io: TlsStream| async { let proto = if let Some(protos) = io.ssl().selected_alpn_protocol() { if protos.windows(2).any(|window| window == b"h2") { Protocol::Http2 diff --git a/awc/Cargo.toml b/awc/Cargo.toml index 9beecc6d4..45b355aba 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -76,7 +76,7 @@ actix-http = { version = "3.0.0-beta.3", features = ["openssl"] } actix-http-test = { version = "3.0.0-beta.2", features = ["openssl"] } actix-utils = "3.0.0-beta.1" actix-server = "2.0.0-beta.3" -actix-tls = { version = "3.0.0-beta.3", features = ["openssl", "rustls"] } +actix-tls = { version = "3.0.0-beta.4", features = ["openssl", "rustls"] } brotli2 = "0.3.2" flate2 = "1.0.13" diff --git a/tests/test_server.rs b/tests/test_server.rs index 2466730f9..195895342 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -786,11 +786,6 @@ mod plus_rustls { #[actix_rt::test] async fn test_reading_deflate_encoding_large_random_rustls() { - use rustls::internal::pemfile::{certs, pkcs8_private_keys}; - use rustls::{NoClientAuth, ServerConfig}; - use std::fs::File; - use std::io::BufReader; - let data = rand::thread_rng() .sample_iter(&Alphanumeric) .take(160_000) From d242f577587637473a28e7e23ddb89fcd270907a Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sat, 27 Feb 2021 20:58:44 +0000 Subject: [PATCH 067/339] fix tests for codecov --- Cargo.toml | 2 +- tests/test_server.rs | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index bc96072d1..3795bc7d2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -66,7 +66,7 @@ required-features = ["compress"] [[test]] name = "test_server" -required-features = ["compress"] +required-features = ["compress", "cookies"] [[example]] name = "on_connect" diff --git a/tests/test_server.rs b/tests/test_server.rs index 195895342..b35af657d 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -21,6 +21,7 @@ use flate2::{ Compression, }; use futures_util::ready; +#[cfg(feature = "openssl")] use openssl::{ pkey::PKey, ssl::{SslAcceptor, SslMethod}, @@ -54,6 +55,7 @@ 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"; +#[cfg(feature = "openssl")] fn openssl_config() -> SslAcceptor { let cert = rcgen::generate_simple_self_signed(vec!["localhost".to_owned()]).unwrap(); let cert_file = cert.serialize_pem().unwrap(); From ebda60fd6b38c6f02a9672378925e36d35acd995 Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Sat, 27 Feb 2021 13:00:36 -0800 Subject: [PATCH 068/339] refactor boxed route (#2033) --- src/route.rs | 152 +++++++-------------------------------------------- 1 file changed, 20 insertions(+), 132 deletions(-) diff --git a/src/route.rs b/src/route.rs index b6b2482cd..c157025b8 100644 --- a/src/route.rs +++ b/src/route.rs @@ -1,13 +1,13 @@ #![allow(clippy::rc_buffer)] // inner value is mutated before being shared (`Rc::get_mut`) -use std::future::Future; -use std::pin::Pin; -use std::rc::Rc; -use std::task::{Context, Poll}; +use std::{future::Future, rc::Rc}; use actix_http::{http::Method, Error}; -use actix_service::{Service, ServiceFactory}; -use futures_util::future::{ready, FutureExt, LocalBoxFuture}; +use actix_service::{ + boxed::{self, BoxService, BoxServiceFactory}, + Service, ServiceFactory, +}; +use futures_core::future::LocalBoxFuture; use crate::extract::FromRequest; use crate::guard::{self, Guard}; @@ -16,33 +16,12 @@ use crate::responder::Responder; use crate::service::{ServiceRequest, ServiceResponse}; use crate::HttpResponse; -type BoxedRouteService = Box< - dyn Service< - ServiceRequest, - Response = ServiceResponse, - Error = Error, - Future = LocalBoxFuture<'static, Result>, - >, ->; - -type BoxedRouteNewService = Box< - dyn ServiceFactory< - ServiceRequest, - Config = (), - Response = ServiceResponse, - Error = Error, - InitError = (), - Service = BoxedRouteService, - Future = LocalBoxFuture<'static, Result>, - >, ->; - /// Resource route definition /// /// Route uses builder-like pattern for configuration. /// If handler is not explicitly set, default *404 Not Found* handler is used. pub struct Route { - service: BoxedRouteNewService, + service: BoxServiceFactory<(), ServiceRequest, ServiceResponse, Error, ()>, guards: Rc>>, } @@ -51,9 +30,7 @@ impl Route { #[allow(clippy::new_without_default)] pub fn new() -> Route { Route { - service: Box::new(RouteNewService::new(HandlerService::new(|| { - ready(HttpResponse::NotFound()) - }))), + service: boxed::factory(HandlerService::new(HttpResponse::NotFound)), guards: Rc::new(Vec::new()), } } @@ -64,44 +41,26 @@ impl Route { } impl ServiceFactory for Route { - type Config = (); type Response = ServiceResponse; type Error = Error; - type InitError = (); + type Config = (); type Service = RouteService; - type Future = CreateRouteService; + type InitError = (); + type Future = LocalBoxFuture<'static, Result>; fn new_service(&self, _: ()) -> Self::Future { - CreateRouteService { - fut: self.service.new_service(()), - guards: self.guards.clone(), - } - } -} + let fut = self.service.new_service(()); + let guards = self.guards.clone(); -pub struct CreateRouteService { - fut: LocalBoxFuture<'static, Result>, - guards: Rc>>, -} - -impl Future for CreateRouteService { - type Output = Result; - - fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - let this = self.get_mut(); - - match this.fut.as_mut().poll(cx)? { - Poll::Ready(service) => Poll::Ready(Ok(RouteService { - service, - guards: this.guards.clone(), - })), - Poll::Pending => Poll::Pending, - } + Box::pin(async move { + let service = fut.await?; + Ok(RouteService { service, guards }) + }) } } pub struct RouteService { - service: BoxedRouteService, + service: BoxService, guards: Rc>>, } @@ -121,9 +80,7 @@ impl Service for RouteService { type Error = Error; type Future = LocalBoxFuture<'static, Result>; - fn poll_ready(&self, cx: &mut Context<'_>) -> Poll> { - self.service.poll_ready(cx) - } + actix_service::forward_ready!(service); fn call(&self, req: ServiceRequest) -> Self::Future { self.service.call(req) @@ -224,80 +181,11 @@ impl Route { R: Future + 'static, R::Output: Responder + 'static, { - self.service = Box::new(RouteNewService::new(HandlerService::new(handler))); + self.service = boxed::factory(HandlerService::new(handler)); self } } -struct RouteNewService -where - T: ServiceFactory, -{ - service: T, -} - -impl RouteNewService -where - T: ServiceFactory, - T::Future: 'static, - T::Service: 'static, - >::Future: 'static, -{ - pub fn new(service: T) -> Self { - RouteNewService { service } - } -} - -impl ServiceFactory for RouteNewService -where - T: ServiceFactory, - T::Future: 'static, - T::Service: 'static, - >::Future: 'static, -{ - type Response = ServiceResponse; - type Error = Error; - type Config = (); - type Service = BoxedRouteService; - type InitError = (); - type Future = LocalBoxFuture<'static, Result>; - - fn new_service(&self, _: ()) -> Self::Future { - self.service - .new_service(()) - .map(|result| match result { - Ok(service) => { - let service = Box::new(RouteServiceWrapper { service }) as _; - Ok(service) - } - Err(_) => Err(()), - }) - .boxed_local() - } -} - -struct RouteServiceWrapper> { - service: T, -} - -impl Service for RouteServiceWrapper -where - T::Future: 'static, - T: Service, -{ - type Response = ServiceResponse; - type Error = Error; - type Future = LocalBoxFuture<'static, Result>; - - fn poll_ready(&self, cx: &mut Context<'_>) -> Poll> { - self.service.poll_ready(cx) - } - - fn call(&self, req: ServiceRequest) -> Self::Future { - Box::pin(self.service.call(req)) - } -} - #[cfg(test)] mod tests { use std::time::Duration; From 1f34718ecdd412b2817de4f83502e2ac15079661 Mon Sep 17 00:00:00 2001 From: fboulnois Date: Sat, 27 Feb 2021 16:55:50 -0500 Subject: [PATCH 069/339] Use once_cell instead of lazy_static (#2029) --- actix-http/Cargo.toml | 2 +- actix-http/src/header/common/content_disposition.rs | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 8ebf55c05..14a9ff1e1 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -66,7 +66,7 @@ http = "0.2.2" httparse = "1.3" itoa = "0.4" language-tags = "0.2" -lazy_static = "1.4" +once_cell = "1.5" log = "0.4" mime = "0.3" percent-encoding = "2.1" diff --git a/actix-http/src/header/common/content_disposition.rs b/actix-http/src/header/common/content_disposition.rs index ecc59aba3..6076d033c 100644 --- a/actix-http/src/header/common/content_disposition.rs +++ b/actix-http/src/header/common/content_disposition.rs @@ -6,7 +6,7 @@ //! Browser conformance tests at: http://greenbytes.de/tech/tc2231/ //! IANA assignment: http://www.iana.org/assignments/cont-disp/cont-disp.xhtml -use lazy_static::lazy_static; +use once_cell::sync::Lazy; use regex::Regex; use std::fmt::{self, Write}; @@ -520,9 +520,7 @@ impl fmt::Display for DispositionParam { // // // See also comments in test_from_raw_unnecessary_percent_decode. - lazy_static! { - static ref RE: Regex = Regex::new("[\x00-\x08\x10-\x1F\x7F\"\\\\]").unwrap(); - } + 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) => { From badae2f8fd31908ac0aa905b2d6bcfbca8a0369c Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Sat, 27 Feb 2021 14:31:14 -0800 Subject: [PATCH 070/339] add local_address bind for client builder (#2024) --- Cargo.toml | 2 +- actix-files/Cargo.toml | 2 +- actix-http-test/Cargo.toml | 2 +- actix-http/Cargo.toml | 2 +- actix-http/src/client/config.rs | 3 ++ actix-http/src/client/connector.rs | 48 ++++++++++++++++++++++-------- actix-multipart/Cargo.toml | 2 +- actix-web-actors/Cargo.toml | 2 +- actix-web-codegen/Cargo.toml | 2 +- awc/CHANGES.md | 2 ++ awc/Cargo.toml | 2 +- awc/src/builder.rs | 13 ++++++++ awc/tests/test_client.rs | 32 ++++++++++++++++++++ 13 files changed, 94 insertions(+), 20 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 3795bc7d2..bd758ab10 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -80,7 +80,7 @@ required-features = ["rustls"] actix-codec = "0.4.0-beta.1" actix-macros = "0.2.0" actix-router = "0.2.7" -actix-rt = "2" +actix-rt = "2.1" actix-server = "2.0.0-beta.3" actix-service = "2.0.0-beta.4" actix-utils = "3.0.0-beta.2" diff --git a/actix-files/Cargo.toml b/actix-files/Cargo.toml index 08b7b36fc..8f1a9ec5a 100644 --- a/actix-files/Cargo.toml +++ b/actix-files/Cargo.toml @@ -33,5 +33,5 @@ mime_guess = "2.0.1" percent-encoding = "2.1" [dev-dependencies] -actix-rt = "2" +actix-rt = "2.1" actix-web = "4.0.0-beta.3" diff --git a/actix-http-test/Cargo.toml b/actix-http-test/Cargo.toml index c880cba78..900960215 100644 --- a/actix-http-test/Cargo.toml +++ b/actix-http-test/Cargo.toml @@ -33,7 +33,7 @@ actix-service = "2.0.0-beta.4" actix-codec = "0.4.0-beta.1" actix-tls = "3.0.0-beta.4" actix-utils = "3.0.0-beta.2" -actix-rt = "2" +actix-rt = "2.1" actix-server = "2.0.0-beta.3" awc = { version = "3.0.0-beta.2", default-features = false } diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 14a9ff1e1..96967e18a 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -47,7 +47,7 @@ trust-dns = ["trust-dns-resolver"] actix-service = "2.0.0-beta.4" actix-codec = "0.4.0-beta.1" actix-utils = "3.0.0-beta.2" -actix-rt = "2" +actix-rt = "2.1" actix-tls = "3.0.0-beta.4" ahash = "0.7" diff --git a/actix-http/src/client/config.rs b/actix-http/src/client/config.rs index fad902d04..0d54e1b49 100644 --- a/actix-http/src/client/config.rs +++ b/actix-http/src/client/config.rs @@ -1,3 +1,4 @@ +use std::net::IpAddr; use std::time::Duration; const DEFAULT_H2_CONN_WINDOW: u32 = 1024 * 1024 * 2; // 2MB @@ -13,6 +14,7 @@ pub(crate) struct ConnectorConfig { pub(crate) limit: usize, pub(crate) conn_window_size: u32, pub(crate) stream_window_size: u32, + pub(crate) local_address: Option, } impl Default for ConnectorConfig { @@ -25,6 +27,7 @@ impl Default for ConnectorConfig { limit: 100, conn_window_size: DEFAULT_H2_CONN_WINDOW, stream_window_size: DEFAULT_H2_STREAM_WINDOW, + local_address: None, } } } diff --git a/actix-http/src/client/connector.rs b/actix-http/src/client/connector.rs index 8aa5b1319..1a926fd6c 100644 --- a/actix-http/src/client/connector.rs +++ b/actix-http/src/client/connector.rs @@ -1,9 +1,12 @@ -use std::fmt; -use std::future::Future; -use std::marker::PhantomData; -use std::pin::Pin; -use std::task::{Context, Poll}; -use std::time::Duration; +use std::{ + fmt, + future::Future, + marker::PhantomData, + net::IpAddr, + pin::Pin, + task::{Context, Poll}, + time::Duration, +}; use actix_codec::{AsyncRead, AsyncWrite}; use actix_rt::net::TcpStream; @@ -240,6 +243,12 @@ where self } + /// Set local IP Address the connector would use for establishing connection. + pub fn local_address(mut self, addr: IpAddr) -> Self { + self.config.local_address = Some(addr); + self + } + /// Finish configuration process and create connector service. /// The Connector builder always concludes by calling `finish()` last in /// its combinator chain. @@ -247,10 +256,19 @@ where self, ) -> impl Service + Clone { + let local_address = self.config.local_address; + let timeout = self.config.timeout; + let tcp_service = TimeoutService::new( - self.config.timeout, - apply_fn(self.connector.clone(), |msg: Connect, srv| { - srv.call(TcpConnect::new(msg.uri).set_addr(msg.addr)) + timeout, + apply_fn(self.connector.clone(), move |msg: Connect, srv| { + let mut req = TcpConnect::new(msg.uri).set_addr(msg.addr); + + if let Some(local_addr) = local_address { + req = req.set_local_addr(local_addr); + } + + srv.call(req) }) .map_err(ConnectError::from) .map(|stream| (stream.into_parts().0, Protocol::Http1)), @@ -294,10 +312,16 @@ where use actix_tls::connect::ssl::rustls::{RustlsConnector, Session}; let ssl_service = TimeoutService::new( - self.config.timeout, + timeout, pipeline( - apply_fn(self.connector.clone(), |msg: Connect, srv| { - srv.call(TcpConnect::new(msg.uri).set_addr(msg.addr)) + apply_fn(self.connector.clone(), move |msg: Connect, srv| { + let mut req = TcpConnect::new(msg.uri).set_addr(msg.addr); + + if let Some(local_addr) = local_address { + req = req.set_local_addr(local_addr); + } + + srv.call(req) }) .map_err(ConnectError::from), ) diff --git a/actix-multipart/Cargo.toml b/actix-multipart/Cargo.toml index f3bb92336..fcbadde36 100644 --- a/actix-multipart/Cargo.toml +++ b/actix-multipart/Cargo.toml @@ -28,7 +28,7 @@ mime = "0.3" twoway = "0.2" [dev-dependencies] -actix-rt = "2" +actix-rt = "2.1" actix-http = "3.0.0-beta.3" tokio = { version = "1", features = ["sync"] } tokio-stream = "0.1" diff --git a/actix-web-actors/Cargo.toml b/actix-web-actors/Cargo.toml index 698ff9420..58508071a 100644 --- a/actix-web-actors/Cargo.toml +++ b/actix-web-actors/Cargo.toml @@ -28,6 +28,6 @@ pin-project = "1.0.0" tokio = { version = "1", features = ["sync"] } [dev-dependencies] -actix-rt = "2" +actix-rt = "2.1" 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 886d9ac3e..e43e91e22 100644 --- a/actix-web-codegen/Cargo.toml +++ b/actix-web-codegen/Cargo.toml @@ -19,7 +19,7 @@ syn = { version = "1", features = ["full", "parsing"] } proc-macro2 = "1" [dev-dependencies] -actix-rt = "2" +actix-rt = "2.1" actix-web = "4.0.0-beta.3" futures-util = { version = "0.3.7", default-features = false } trybuild = "1" diff --git a/awc/CHANGES.md b/awc/CHANGES.md index e6ead2cc8..9fbc6d042 100644 --- a/awc/CHANGES.md +++ b/awc/CHANGES.md @@ -3,6 +3,7 @@ ## Unreleased - 2021-xx-xx ### 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] ### Changed * Feature `cookies` is now optional and enabled by default. [#1981] @@ -15,6 +16,7 @@ [#1931]: https://github.com/actix/actix-web/pull/1931 [#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 ## 3.0.0-beta.2 - 2021-02-10 ### Added diff --git a/awc/Cargo.toml b/awc/Cargo.toml index 45b355aba..8cbba432c 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -47,7 +47,7 @@ trust-dns = ["actix-http/trust-dns"] actix-codec = "0.4.0-beta.1" actix-service = "2.0.0-beta.4" actix-http = "3.0.0-beta.3" -actix-rt = "2" +actix-rt = "2.1" base64 = "0.13" bytes = "1" diff --git a/awc/src/builder.rs b/awc/src/builder.rs index 4495b39fd..b7cdefd40 100644 --- a/awc/src/builder.rs +++ b/awc/src/builder.rs @@ -1,5 +1,6 @@ use std::convert::TryFrom; use std::fmt; +use std::net::IpAddr; use std::rc::Rc; use std::time::Duration; @@ -25,6 +26,7 @@ pub struct ClientBuilder { conn_window_size: Option, headers: HeaderMap, timeout: Option, + local_address: Option, connector: Connector, } @@ -42,6 +44,7 @@ impl ClientBuilder { 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, @@ -72,6 +75,7 @@ where default_headers: self.default_headers, headers: self.headers, timeout: self.timeout, + local_address: None, connector, max_http_version: self.max_http_version, stream_window_size: self.stream_window_size, @@ -94,6 +98,12 @@ where self } + /// Set local IP Address the connector would use for establishing connection. + pub fn local_address(mut self, addr: IpAddr) -> Self { + self.local_address = Some(addr); + self + } + /// Maximum supported HTTP major version. /// /// Supported versions are HTTP/1.1 and HTTP/2. @@ -184,6 +194,9 @@ where if let Some(val) = self.stream_window_size { connector = connector.initial_window_size(val) }; + if let Some(val) = self.local_address { + connector = connector.local_address(val); + } let config = ClientConfig { headers: self.headers, diff --git a/awc/tests/test_client.rs b/awc/tests/test_client.rs index a41a8dac3..c7fa82de8 100644 --- a/awc/tests/test_client.rs +++ b/awc/tests/test_client.rs @@ -1,5 +1,6 @@ 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; @@ -871,3 +872,34 @@ async fn client_bearer_auth() { let response = request.send().await.unwrap(); assert!(response.status().is_success()); } + +#[actix_rt::test] +async fn test_local_address() { + let ip = IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)); + + let srv = test::start(move || { + App::new().service(web::resource("/").route(web::to( + move |req: HttpRequest| async move { + assert_eq!(req.peer_addr().unwrap().ip(), ip); + Ok::<_, Error>(HttpResponse::Ok()) + }, + ))) + }); + let client = awc::Client::builder().local_address(ip).finish(); + + let res = client.get(srv.url("/")).send().await.unwrap(); + + assert_eq!(res.status(), 200); + + let client = awc::Client::builder() + .connector( + // connector local address setting should always be override by client builder. + awc::Connector::new().local_address(IpAddr::V4(Ipv4Addr::new(128, 0, 0, 1))), + ) + .local_address(ip) + .finish(); + + let res = client.get(srv.url("/")).send().await.unwrap(); + + assert_eq!(res.status(), 200); +} From c836de44af0667246a2691a01df9f0dc55bcafd7 Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Sun, 28 Feb 2021 10:17:08 -0800 Subject: [PATCH 071/339] add client middleware (#2013) --- awc/Cargo.toml | 3 +- awc/src/builder.rs | 59 +++++- awc/src/connect.rs | 123 ++++++++---- awc/src/lib.rs | 13 +- awc/src/middleware/mod.rs | 71 +++++++ awc/src/middleware/redirect.rs | 350 +++++++++++++++++++++++++++++++++ awc/src/response.rs | 4 +- src/types/form.rs | 8 +- src/types/json.rs | 4 +- 9 files changed, 570 insertions(+), 65 deletions(-) create mode 100644 awc/src/middleware/mod.rs create mode 100644 awc/src/middleware/redirect.rs diff --git a/awc/Cargo.toml b/awc/Cargo.toml index 8cbba432c..ca345d3cb 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -47,7 +47,7 @@ trust-dns = ["actix-http/trust-dns"] actix-codec = "0.4.0-beta.1" actix-service = "2.0.0-beta.4" actix-http = "3.0.0-beta.3" -actix-rt = "2.1" +actix-rt = { version = "2.1", default-features = false } base64 = "0.13" bytes = "1" @@ -57,6 +57,7 @@ futures-core = { version = "0.3.7", default-features = false } log =" 0.4" mime = "0.3" percent-encoding = "2.1" +pin-project-lite = "0.2" rand = "0.8" serde = "1.0" serde_json = "1.0" diff --git a/awc/src/builder.rs b/awc/src/builder.rs index b7cdefd40..363056c02 100644 --- a/awc/src/builder.rs +++ b/awc/src/builder.rs @@ -10,24 +10,27 @@ use actix_http::{ http::{self, header, Error as HttpError, HeaderMap, HeaderName, Uri}, }; use actix_rt::net::TcpStream; -use actix_service::Service; +use actix_service::{boxed, Service}; -use crate::connect::ConnectorWrapper; -use crate::{Client, ClientConfig}; +use crate::connect::DefaultConnector; +use crate::error::SendRequestError; +use crate::middleware::{NestTransform, Transform}; +use crate::{Client, ClientConfig, ConnectRequest, ConnectResponse, ConnectorService}; /// An HTTP Client builder /// /// This type can be used to construct an instance of `Client` through a /// builder-like pattern. -pub struct ClientBuilder { +pub struct ClientBuilder { default_headers: bool, max_http_version: Option, stream_window_size: Option, conn_window_size: Option, headers: HeaderMap, timeout: Option, + connector: Connector, + middleware: M, local_address: Option, - connector: Connector, } impl ClientBuilder { @@ -39,8 +42,10 @@ impl ClientBuilder { Error = TcpConnectError, > + Clone, TcpStream, + (), > { ClientBuilder { + middleware: (), default_headers: true, headers: HeaderMap::new(), timeout: Some(Duration::from_secs(5)), @@ -53,7 +58,7 @@ impl ClientBuilder { } } -impl ClientBuilder +impl ClientBuilder where S: Service, Response = TcpConnection, Error = TcpConnectError> + Clone @@ -61,7 +66,7 @@ where Io: AsyncRead + AsyncWrite + Unpin + fmt::Debug + 'static, { /// Use custom connector service. - pub fn connector(self, connector: Connector) -> ClientBuilder + pub fn connector(self, connector: Connector) -> ClientBuilder where S1: Service< TcpConnect, @@ -72,10 +77,11 @@ where Io1: AsyncRead + AsyncWrite + Unpin + fmt::Debug + 'static, { ClientBuilder { + middleware: self.middleware, default_headers: self.default_headers, headers: self.headers, timeout: self.timeout, - local_address: None, + local_address: self.local_address, connector, max_http_version: self.max_http_version, stream_window_size: self.stream_window_size, @@ -181,8 +187,38 @@ where self.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. + pub fn wrap( + self, + mw: M1, + ) -> ClientBuilder> + where + M: Transform, + M1: Transform, + { + ClientBuilder { + middleware: NestTransform::new(self.middleware, mw), + default_headers: self.default_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, + timeout: self.timeout, + connector: self.connector, + local_address: self.local_address, + } + } + /// Finish build process and create `Client` instance. - pub fn finish(self) -> Client { + pub fn finish(self) -> Client + where + M: Transform + 'static, + M::Transform: + Service, + { let mut connector = self.connector; if let Some(val) = self.max_http_version { @@ -198,10 +234,13 @@ where connector = connector.local_address(val); } + let connector = boxed::service(DefaultConnector::new(connector.finish())); + let connector = boxed::service(self.middleware.new_transform(connector)); + let config = ClientConfig { headers: self.headers, timeout: self.timeout, - connector: Box::new(ConnectorWrapper::new(connector.finish())) as _, + connector, }; Client(Rc::new(config)) diff --git a/awc/src/connect.rs b/awc/src/connect.rs index 97af2d1cc..a4abbc46b 100644 --- a/awc/src/connect.rs +++ b/awc/src/connect.rs @@ -1,5 +1,7 @@ use std::{ - fmt, io, net, + fmt, + future::Future, + io, net, pin::Pin, task::{Context, Poll}, }; @@ -9,24 +11,14 @@ use actix_http::{ body::Body, client::{Connect as ClientConnect, ConnectError, Connection, SendRequestError}, h1::ClientCodec, - RequestHead, RequestHeadType, ResponseHead, + Payload, RequestHead, RequestHeadType, ResponseHead, }; use actix_service::Service; -use futures_core::future::LocalBoxFuture; +use futures_core::{future::LocalBoxFuture, ready}; use crate::response::ClientResponse; -pub(crate) struct ConnectorWrapper { - connector: T, -} - -impl ConnectorWrapper { - pub(crate) fn new(connector: T) -> Self { - Self { connector } - } -} - -pub type ConnectService = Box< +pub type ConnectorService = Box< dyn Service< ConnectRequest, Response = ConnectResponse, @@ -65,16 +57,25 @@ impl ConnectResponse { } } -impl Service for ConnectorWrapper +pub(crate) struct DefaultConnector { + connector: S, +} + +impl DefaultConnector { + pub(crate) fn new(connector: S) -> Self { + Self { connector } + } +} + +impl Service for DefaultConnector where - T: Service, - T::Response: Connection, - ::Io: 'static, - T::Future: 'static, + S: Service, + S::Response: Connection, + ::Io: 'static, { type Response = ConnectResponse; type Error = SendRequestError; - type Future = LocalBoxFuture<'static, Result>; + type Future = ConnectRequestFuture::Io>; actix_service::forward_ready!(connector); @@ -91,26 +92,76 @@ where }), }; - Box::pin(async move { - let connection = fut.await?; + ConnectRequestFuture::Connection { + fut, + req: Some(req), + } + } +} - match req { - ConnectRequest::Client(head, body, ..) => { - // send request - let (head, payload) = connection.send_request(head, body).await?; +pin_project_lite::pin_project! { + #[project = ConnectRequestProj] + pub(crate) enum ConnectRequestFuture { + Connection { + #[pin] + fut: Fut, + req: Option + }, + Client { + fut: LocalBoxFuture<'static, Result<(ResponseHead, Payload), SendRequestError>> + }, + Tunnel { + fut: LocalBoxFuture< + 'static, + Result<(ResponseHead, Framed), SendRequestError>, + >, + } + } +} - Ok(ConnectResponse::Client(ClientResponse::new(head, payload))) - } - ConnectRequest::Tunnel(head, ..) => { - // send request - let (head, framed) = - connection.open_tunnel(RequestHeadType::from(head)).await?; - - let framed = framed.into_map_io(|io| BoxedSocket(Box::new(Socket(io)))); - Ok(ConnectResponse::Tunnel(head, framed)) +impl Future for ConnectRequestFuture +where + Fut: Future>, + C: Connection, + Io: AsyncRead + AsyncWrite + Unpin + 'static, +{ + type Output = Result; + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + match self.as_mut().project() { + 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.as_mut().set(fut); + } + ConnectRequest::Tunnel(head, ..) => { + // send request + let fut = ConnectRequestFuture::Tunnel { + fut: connection.open_tunnel(RequestHeadType::from(head)), + }; + self.as_mut().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| BoxedSocket(Box::new(Socket(io)))); + Poll::Ready(Ok(ConnectResponse::Tunnel(head, framed))) + } + } } } diff --git a/awc/src/lib.rs b/awc/src/lib.rs index 66ff55402..2f48dca79 100644 --- a/awc/src/lib.rs +++ b/awc/src/lib.rs @@ -107,12 +107,13 @@ use actix_http::{ RequestHead, }; use actix_rt::net::TcpStream; -use actix_service::Service; +use actix_service::{boxed, Service}; mod builder; mod connect; pub mod error; mod frozen; +pub mod middleware; mod request; mod response; mod sender; @@ -120,14 +121,12 @@ pub mod test; pub mod ws; pub use self::builder::ClientBuilder; -pub use self::connect::{BoxedSocket, ConnectRequest, ConnectResponse, ConnectService}; +pub use self::connect::{BoxedSocket, ConnectRequest, ConnectResponse, ConnectorService}; pub use self::frozen::{FrozenClientRequest, FrozenSendBuilder}; pub use self::request::ClientRequest; pub use self::response::{ClientResponse, JsonBody, MessageBody}; pub use self::sender::SendClientRequest; -use self::connect::ConnectorWrapper; - /// An asynchronous HTTP and WebSocket client. /// /// ## Examples @@ -151,7 +150,7 @@ use self::connect::ConnectorWrapper; pub struct Client(Rc); pub(crate) struct ClientConfig { - pub(crate) connector: ConnectService, + pub(crate) connector: ConnectorService, pub(crate) headers: HeaderMap, pub(crate) timeout: Option, } @@ -159,7 +158,9 @@ pub(crate) struct ClientConfig { impl Default for Client { fn default() -> Self { Client(Rc::new(ClientConfig { - connector: Box::new(ConnectorWrapper::new(Connector::new().finish())), + connector: boxed::service(self::connect::DefaultConnector::new( + Connector::new().finish(), + )), headers: HeaderMap::new(), timeout: Some(Duration::from_secs(5)), })) diff --git a/awc/src/middleware/mod.rs b/awc/src/middleware/mod.rs new file mode 100644 index 000000000..330e3b7fe --- /dev/null +++ b/awc/src/middleware/mod.rs @@ -0,0 +1,71 @@ +mod redirect; + +pub use self::redirect::Redirect; + +use std::marker::PhantomData; + +use actix_service::Service; + +/// Trait for transform a type to another one. +/// Both the input and output type should impl [actix_service::Service] trait. +pub trait Transform { + type Transform: Service; + + /// Creates and returns a new Transform component. + fn new_transform(self, service: S) -> Self::Transform; +} + +#[doc(hidden)] +/// Helper struct for constructing Nested types that would call `Transform::new_transform` +/// in a chain. +/// +/// The child field would be called first and the output `Service` type is +/// passed to parent as input type. +pub struct NestTransform +where + T1: Transform, + T2: Transform, +{ + child: T1, + parent: T2, + _service: PhantomData<(S, Req)>, +} + +impl NestTransform +where + T1: Transform, + T2: Transform, +{ + pub(crate) fn new(child: T1, parent: T2) -> Self { + NestTransform { + child, + parent, + _service: PhantomData, + } + } +} + +impl Transform for NestTransform +where + T1: Transform, + T2: Transform, +{ + type Transform = T2::Transform; + + fn new_transform(self, service: S) -> Self::Transform { + let service = self.child.new_transform(service); + self.parent.new_transform(service) + } +} + +/// Dummy impl for kick start `NestTransform` type in `ClientBuilder` type +impl Transform for () +where + S: Service, +{ + type Transform = S; + + fn new_transform(self, service: S) -> Self::Transform { + service + } +} diff --git a/awc/src/middleware/redirect.rs b/awc/src/middleware/redirect.rs new file mode 100644 index 000000000..1d0ace166 --- /dev/null +++ b/awc/src/middleware/redirect.rs @@ -0,0 +1,350 @@ +use std::{ + convert::TryFrom, + future::Future, + net::SocketAddr, + pin::Pin, + rc::Rc, + task::{Context, Poll}, +}; + +use actix_http::{ + body::Body, + client::{InvalidUrl, SendRequestError}, + http::{header, Method, StatusCode, Uri}, + RequestHead, RequestHeadType, +}; +use actix_service::Service; +use bytes::Bytes; +use futures_core::ready; + +use super::Transform; + +use crate::connect::{ConnectRequest, ConnectResponse}; +use crate::ClientResponse; + +pub struct Redirect { + max_redirect_times: u8, +} + +impl Default for Redirect { + fn default() -> Self { + Self::new() + } +} + +impl Redirect { + pub fn new() -> Self { + Self { + max_redirect_times: 10, + } + } + + pub fn max_redirect_times(mut self, times: u8) -> Self { + self.max_redirect_times = times; + self + } +} + +impl Transform for Redirect +where + S: Service + 'static, +{ + type Transform = RedirectService; + + fn new_transform(self, service: S) -> Self::Transform { + RedirectService { + max_redirect_times: self.max_redirect_times, + connector: Rc::new(service), + } + } +} + +pub struct RedirectService { + max_redirect_times: u8, + connector: Rc, +} + +impl Service for RedirectService +where + S: Service + 'static, +{ + type Response = S::Response; + type Error = S::Error; + type Future = RedirectServiceFuture; + + actix_service::forward_ready!(connector); + + fn call(&self, req: ConnectRequest) -> Self::Future { + match req { + ConnectRequest::Tunnel(head, addr) => { + let fut = self.connector.call(ConnectRequest::Tunnel(head, addr)); + RedirectServiceFuture::Tunnel { fut } + } + ConnectRequest::Client(head, body, addr) => { + let connector = self.connector.clone(); + 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()), + RequestHeadType::Rc(ref head, ..) => { + (head.uri.clone(), head.method.clone()) + } + }; + + let body_opt = match body { + Body::Bytes(ref b) => Some(b.clone()), + _ => None, + }; + + let fut = connector.call(ConnectRequest::Client(head, body, addr)); + + RedirectServiceFuture::Client { + fut, + max_redirect_times, + uri: Some(uri), + method: Some(method), + body: body_opt, + addr, + connector: Some(connector), + } + } + } + } +} + +pin_project_lite::pin_project! { + #[project = RedirectServiceProj] + pub enum RedirectServiceFuture + where + S: Service, + S: 'static + { + Tunnel { #[pin] fut: S::Future }, + Client { + #[pin] + fut: S::Future, + max_redirect_times: u8, + uri: Option, + method: Option, + body: Option, + addr: Option, + connector: Option> + } + } +} + +impl Future for RedirectServiceFuture +where + S: Service + 'static, +{ + type Output = Result; + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + match self.as_mut().project() { + RedirectServiceProj::Tunnel { fut } => fut.poll(cx), + RedirectServiceProj::Client { + fut, + max_redirect_times, + uri, + method, + body, + addr, + connector, + } => match ready!(fut.poll(cx))? { + ConnectResponse::Client(res) => match res.head().status { + StatusCode::MOVED_PERMANENTLY + | StatusCode::FOUND + | StatusCode::SEE_OTHER + 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)?; + + // reset method + let method = method.take().unwrap(); + let method = match method { + Method::GET | Method::HEAD => method, + _ => Method::GET, + }; + + // 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.as_mut().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, + }; + + let addr = addr.take(); + let method = method.take().unwrap(); + 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() + .call(ConnectRequest::Client(head, body_new, addr)); + + self.as_mut().set(RedirectServiceFuture::Client { + fut, + max_redirect_times, + uri: Some(uri), + method: Some(method), + body, + addr, + connector, + }); + + self.poll(cx) + } + _ => Poll::Ready(Ok(ConnectResponse::Client(res))), + }, + _ => unreachable!("ConnectRequest::Tunnel is not handled by Redirect"), + }, + } + } +} + +fn rebuild_uri(res: &ClientResponse, org_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(org_uri.scheme().cloned().unwrap()) + .authority(org_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))??; + + Ok(uri) +} + +#[cfg(test)] +mod tests { + use actix_web::{test::start, web, App, Error, HttpResponse}; + + use super::*; + + use crate::ClientBuilder; + + #[actix_rt::test] + async fn test_basic_redirect() { + let client = ClientBuilder::new() + .connector(crate::Connector::new()) + .wrap(Redirect::new().max_redirect_times(10)) + .finish(); + + let srv = start(|| { + App::new() + .service(web::resource("/test").route(web::to(|| async { + Ok::<_, Error>(HttpResponse::BadRequest()) + }))) + .service(web::resource("/").route(web::to(|| async { + Ok::<_, Error>( + HttpResponse::Found() + .append_header(("location", "/test")) + .finish(), + ) + }))) + }); + + let res = client.get(srv.url("/")).send().await.unwrap(); + + assert_eq!(res.status().as_u16(), 400); + } + + #[actix_rt::test] + async fn test_redirect_limit() { + let client = ClientBuilder::new() + .wrap(Redirect::new().max_redirect_times(1)) + .connector(crate::Connector::new()) + .finish(); + + let srv = start(|| { + App::new() + .service(web::resource("/").route(web::to(|| async { + Ok::<_, Error>( + HttpResponse::Found() + .append_header(("location", "/test")) + .finish(), + ) + }))) + .service(web::resource("/test").route(web::to(|| async { + Ok::<_, Error>( + HttpResponse::Found() + .append_header(("location", "/test2")) + .finish(), + ) + }))) + .service(web::resource("/test2").route(web::to(|| async { + Ok::<_, Error>(HttpResponse::BadRequest()) + }))) + }); + + let res = client.get(srv.url("/")).send().await.unwrap(); + + assert_eq!(res.status().as_u16(), 302); + } +} diff --git a/awc/src/response.rs b/awc/src/response.rs index 514b8a90b..40de3dc17 100644 --- a/awc/src/response.rs +++ b/awc/src/response.rs @@ -492,9 +492,7 @@ mod tests { JsonPayloadError::Payload(PayloadError::Overflow) => { matches!(other, JsonPayloadError::Payload(PayloadError::Overflow)) } - JsonPayloadError::ContentType => { - matches!(other, JsonPayloadError::ContentType) - } + JsonPayloadError::ContentType => matches!(other, JsonPayloadError::ContentType), _ => false, } } diff --git a/src/types/form.rs b/src/types/form.rs index 0b5c3c1b4..4f3ecbe7c 100644 --- a/src/types/form.rs +++ b/src/types/form.rs @@ -400,12 +400,8 @@ mod tests { UrlencodedError::Overflow { .. } => { matches!(other, UrlencodedError::Overflow { .. }) } - UrlencodedError::UnknownLength => { - matches!(other, UrlencodedError::UnknownLength) - } - UrlencodedError::ContentType => { - matches!(other, UrlencodedError::ContentType) - } + UrlencodedError::UnknownLength => matches!(other, UrlencodedError::UnknownLength), + UrlencodedError::ContentType => matches!(other, UrlencodedError::ContentType), _ => false, } } diff --git a/src/types/json.rs b/src/types/json.rs index 31ff680f4..866d835f2 100644 --- a/src/types/json.rs +++ b/src/types/json.rs @@ -441,9 +441,7 @@ mod tests { fn json_eq(err: JsonPayloadError, other: JsonPayloadError) -> bool { match err { JsonPayloadError::Overflow => matches!(other, JsonPayloadError::Overflow), - JsonPayloadError::ContentType => { - matches!(other, JsonPayloadError::ContentType) - } + JsonPayloadError::ContentType => matches!(other, JsonPayloadError::ContentType), _ => false, } } From cd652dca756ce7748ad0ba0dc4270110fb11b198 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sun, 28 Feb 2021 19:55:34 +0000 Subject: [PATCH 072/339] refactor websocket key hashing (#2035) --- actix-http/CHANGES.md | 2 + actix-http/Cargo.toml | 4 ++ actix-http/examples/ws.rs | 107 +++++++++++++++++++++++++++++ actix-http/src/ws/mod.rs | 27 +++++--- actix-http/src/ws/proto.rs | 134 +++++++++++++++++++++---------------- actix-web-actors/src/ws.rs | 11 ++- awc/src/error.rs | 17 +++-- awc/src/ws.rs | 8 ++- 8 files changed, 234 insertions(+), 76 deletions(-) create mode 100644 actix-http/examples/ws.rs diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 6ba111eb3..165b004a6 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -3,6 +3,7 @@ ## Unreleased - 2021-xx-xx ### Changed * Feature `cookies` is now optional and disabled by default. [#1981] +* `ws::hash_key` now returns array. [#2035] ### Removed * re-export of `futures_channel::oneshot::Canceled` is removed from `error` mod. [#1994] @@ -10,6 +11,7 @@ [#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 ## 3.0.0-beta.3 - 2021-02-10 diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 96967e18a..d7915ddf9 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -103,6 +103,10 @@ version = "0.10.9" package = "openssl" features = ["vendored"] +[[example]] +name = "ws" +required-features = ["rustls"] + [[bench]] name = "write-camel-case" harness = false diff --git a/actix-http/examples/ws.rs b/actix-http/examples/ws.rs new file mode 100644 index 000000000..4e03aa8ab --- /dev/null +++ b/actix-http/examples/ws.rs @@ -0,0 +1,107 @@ +//! Sets up a WebSocket server over TCP and TLS. +//! Sends a heartbeat message every 4 seconds but does not respond to any incoming frames. + +extern crate tls_rustls as rustls; + +use std::{ + env, io, + pin::Pin, + task::{Context, Poll}, + time::Duration, +}; + +use actix_codec::Encoder; +use actix_http::{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}; + +#[actix_rt::main] +async fn main() -> io::Result<()> { + env::set_var("RUST_LOG", "actix=info,h2_ws=info"); + env_logger::init(); + + Server::build() + .bind("tcp", ("127.0.0.1", 8080), || { + HttpService::build().h1(handler).tcp() + })? + .bind("tls", ("127.0.0.1", 8443), || { + HttpService::build().finish(handler).rustls(tls_config()) + })? + .run() + .await +} + +async fn handler(req: Request) -> Result { + log::info!("handshaking"); + let mut res = ws::handshake(req.head())?; + + // handshake will always fail under HTTP/2 + + log::info!("responding"); + Ok(res.streaming(Heartbeat::new(ws::Codec::new()))) +} + +struct Heartbeat { + codec: ws::Codec, + interval: Interval, +} + +impl Heartbeat { + fn new(codec: ws::Codec) -> Self { + Self { + codec, + interval: interval(Duration::from_secs(4)), + } + } +} + +impl Stream for Heartbeat { + type Item = Result; + + fn poll_next( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + ) -> Poll> { + log::trace!("poll"); + + ready!(self.as_mut().interval.poll_tick(cx)); + + let mut buffer = BytesMut::new(); + + self.as_mut() + .codec + .encode( + ws::Message::Text(ByteString::from_static("hello world")), + &mut buffer, + ) + .unwrap(); + + Poll::Ready(Some(Ok(buffer.freeze()))) + } +} + +fn tls_config() -> rustls::ServerConfig { + use std::io::BufReader; + + use rustls::{ + internal::pemfile::{certs, pkcs8_private_keys}, + NoClientAuth, ServerConfig, + }; + + let cert = rcgen::generate_simple_self_signed(vec!["localhost".to_owned()]).unwrap(); + let cert_file = cert.serialize_pem().unwrap(); + let key_file = cert.serialize_private_key_pem(); + + let mut config = ServerConfig::new(NoClientAuth::new()); + let cert_file = &mut BufReader::new(cert_file.as_bytes()); + let key_file = &mut BufReader::new(key_file.as_bytes()); + + let cert_chain = certs(cert_file).unwrap(); + let mut keys = pkcs8_private_keys(key_file).unwrap(); + config.set_single_cert(cert_chain, keys.remove(0)).unwrap(); + + config +} diff --git a/actix-http/src/ws/mod.rs b/actix-http/src/ws/mod.rs index 0490163d5..cec73db96 100644 --- a/actix-http/src/ws/mod.rs +++ b/actix-http/src/ws/mod.rs @@ -1,4 +1,4 @@ -//! WebSocket protocol. +//! WebSocket protocol implementation. //! //! To setup a WebSocket, first perform the WebSocket handshake then on success convert `Payload` into a //! `WsStream` stream and then use `WsWriter` to communicate with the peer. @@ -8,9 +8,12 @@ use std::io; use derive_more::{Display, Error, From}; use http::{header, Method, StatusCode}; -use crate::error::ResponseError; -use crate::message::RequestHead; -use crate::response::{Response, ResponseBuilder}; +use crate::{ + error::ResponseError, + header::HeaderValue, + message::RequestHead, + response::{Response, ResponseBuilder}, +}; mod codec; mod dispatcher; @@ -89,7 +92,7 @@ pub enum HandshakeError { NoVersionHeader, /// Unsupported WebSocket version. - #[display(fmt = "Unsupported version.")] + #[display(fmt = "Unsupported WebSocket version.")] UnsupportedVersion, /// WebSocket key is not set or wrong. @@ -105,19 +108,19 @@ impl ResponseError for HandshakeError { .finish(), HandshakeError::NoWebsocketUpgrade => Response::BadRequest() - .reason("No WebSocket UPGRADE header found") + .reason("No WebSocket Upgrade header found") .finish(), HandshakeError::NoConnectionUpgrade => Response::BadRequest() - .reason("No CONNECTION upgrade") + .reason("No Connection upgrade") .finish(), HandshakeError::NoVersionHeader => Response::BadRequest() - .reason("Websocket version header is required") + .reason("WebSocket version header is required") .finish(), HandshakeError::UnsupportedVersion => Response::BadRequest() - .reason("Unsupported version") + .reason("Unsupported WebSocket version") .finish(), HandshakeError::BadWebsocketKey => { @@ -193,7 +196,11 @@ 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)) + .insert_header(( + header::SEC_WEBSOCKET_ACCEPT, + // key is known to be header value safe ascii + HeaderValue::from_bytes(&key).unwrap(), + )) .take() } diff --git a/actix-http/src/ws/proto.rs b/actix-http/src/ws/proto.rs index 1e8bf7af3..fdcde5eac 100644 --- a/actix-http/src/ws/proto.rs +++ b/actix-http/src/ws/proto.rs @@ -1,5 +1,7 @@ -use std::convert::{From, Into}; -use std::fmt; +use std::{ + convert::{From, Into}, + fmt, +}; /// Operation codes as part of RFC6455. #[derive(Debug, Eq, PartialEq, Clone, Copy)] @@ -28,8 +30,9 @@ pub enum OpCode { impl fmt::Display for OpCode { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - use self::OpCode::*; - match *self { + use OpCode::*; + + match self { Continue => write!(f, "CONTINUE"), Text => write!(f, "TEXT"), Binary => write!(f, "BINARY"), @@ -44,6 +47,7 @@ impl fmt::Display for OpCode { impl From for u8 { fn from(op: OpCode) -> u8 { use self::OpCode::*; + match op { Continue => 0, Text => 1, @@ -62,6 +66,7 @@ impl From for u8 { impl From for OpCode { fn from(byte: u8) -> OpCode { use self::OpCode::*; + match byte { 0 => Continue, 1 => Text, @@ -77,63 +82,66 @@ impl From for OpCode { /// Status code used to indicate why an endpoint is closing the WebSocket connection. #[derive(Debug, Eq, PartialEq, Clone, Copy)] pub enum CloseCode { - /// Indicates a normal closure, meaning that the purpose for - /// which the connection was established has been fulfilled. + /// Indicates a normal closure, meaning that the purpose for which the connection was + /// established has been fulfilled. Normal, - /// Indicates that an endpoint is "going away", such as a server - /// going down or a browser having navigated away from a page. + + /// Indicates that an endpoint is "going away", such as a server going down or a browser having + /// navigated away from a page. Away, - /// Indicates that an endpoint is terminating the connection due - /// to a protocol error. + + /// Indicates that an endpoint is terminating the connection due to a protocol error. Protocol, - /// Indicates that an endpoint is terminating the connection - /// because it has received a type of data it cannot accept (e.g., an - /// endpoint that understands only text data MAY send this if it + + /// Indicates that an endpoint is terminating the connection because it has received a type of + /// data it cannot accept (e.g., an endpoint that understands only text data MAY send this if it /// receives a binary message). Unsupported, - /// Indicates an abnormal closure. If the abnormal closure was due to an - /// error, this close code will not be used. Instead, the `on_error` method - /// of the handler will be called with the error. However, if the connection - /// is simply dropped, without an error, this close code will be sent to the - /// handler. + + /// Indicates an abnormal closure. If the abnormal closure was due to an error, this close code + /// will not be used. Instead, the `on_error` method of the handler will be called with + /// the error. However, if the connection is simply dropped, without an error, this close code + /// will be sent to the handler. 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\] + + /// 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\] /// data within a text message). Invalid, - /// Indicates that an endpoint is terminating the connection - /// because it has received a message that violates its policy. This - /// is a generic status code that can be returned when there is no - /// other more suitable status code (e.g., Unsupported or Size) or if there - /// is a need to hide specific details about the policy. + + /// Indicates that an endpoint is terminating the connection because it has received a message + /// that violates its policy. This is a generic status code that can be returned when there is + /// no other more suitable status code (e.g., Unsupported or Size) or if there is a need to hide + /// specific details about the policy. Policy, - /// Indicates that an endpoint is terminating the connection - /// because it has received a message that is too big for it to - /// process. + + /// Indicates that an endpoint is terminating the connection because it has received a message + /// that is too big for it to process. Size, - /// Indicates that an endpoint (client) is terminating the - /// connection because it has expected the server to negotiate one or - /// more extension, but the server didn't return them in the response - /// message of the WebSocket handshake. The list of extensions that - /// are needed should be given as the reason for closing. - /// Note that this status code is not used by the server, because it - /// can fail the WebSocket handshake instead. + + /// Indicates that an endpoint (client) is terminating the connection because it has expected + /// the server to negotiate one or more extension, but the server didn't return them in the + /// response message of the WebSocket handshake. The list of extensions that are needed should + /// be given as the reason for closing. Note that this status code is not used by the server, + /// because it can fail the WebSocket handshake instead. Extension, - /// Indicates that a server is terminating the connection because - /// it encountered an unexpected condition that prevented it from - /// fulfilling the request. + + /// Indicates that a server is terminating the connection because it encountered an unexpected + /// condition that prevented it from fulfilling the request. Error, - /// Indicates that the server is restarting. A client may choose to - /// reconnect, and if it does, it should use a randomized delay of 5-30 - /// seconds between attempts. + + /// Indicates that the server is restarting. A client may choose to reconnect, and if it does, + /// it should use a randomized delay of 5-30 seconds between attempts. Restart, - /// Indicates that the server is overloaded and the client should either - /// connect to a different IP (when multiple targets exist), or - /// reconnect to the same IP when a user has performed an action. + + /// Indicates that the server is overloaded and the client should either connect to a different + /// IP (when multiple targets exist), or reconnect to the same IP when a user has performed + /// an action. Again, + #[doc(hidden)] Tls, + #[doc(hidden)] Other(u16), } @@ -141,6 +149,7 @@ pub enum CloseCode { impl From for u16 { fn from(code: CloseCode) -> u16 { use self::CloseCode::*; + match code { Normal => 1000, Away => 1001, @@ -163,6 +172,7 @@ impl From for u16 { impl From for CloseCode { fn from(code: u16) -> CloseCode { use self::CloseCode::*; + match code { 1000 => Normal, 1001 => Away, @@ -210,17 +220,29 @@ impl> From<(CloseCode, T)> for CloseReason { } } -static WS_GUID: &str = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; +/// The WebSocket GUID as stated in the spec. See https://tools.ietf.org/html/rfc6455#section-1.3. +static WS_GUID: &[u8] = b"258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; -// TODO: hash is always same size, we don't need String -pub fn hash_key(key: &[u8]) -> String { - use sha1::Digest; - let mut hasher = sha1::Sha1::new(); +/// Hashes the `Sec-WebSocket-Key` header according to the WebSocket spec. +/// +/// Result is a Base64 encoded byte array. `base64(sha1(input))` is always 28 bytes. +pub fn hash_key(key: &[u8]) -> [u8; 28] { + let hash = { + use sha1::Digest as _; - hasher.update(key); - hasher.update(WS_GUID.as_bytes()); + let mut hasher = sha1::Sha1::new(); - base64::encode(&hasher.finalize()) + hasher.update(key); + hasher.update(WS_GUID); + + hasher.finalize() + }; + + let mut hash_b64 = [0; 28]; + let n = base64::encode_config_slice(&hash, base64::STANDARD, &mut hash_b64); + assert_eq!(n, 28); + + hash_b64 } #[cfg(test)] @@ -288,11 +310,11 @@ mod test { #[test] fn test_hash_key() { let hash = hash_key(b"hello actix-web"); - assert_eq!(&hash, "cR1dlyUUJKp0s/Bel25u5TgvC3E="); + assert_eq!(&hash, b"cR1dlyUUJKp0s/Bel25u5TgvC3E="); } #[test] - fn closecode_from_u16() { + fn close_code_from_u16() { assert_eq!(CloseCode::from(1000u16), CloseCode::Normal); assert_eq!(CloseCode::from(1001u16), CloseCode::Away); assert_eq!(CloseCode::from(1002u16), CloseCode::Protocol); @@ -310,7 +332,7 @@ mod test { } #[test] - fn closecode_into_u16() { + fn close_code_into_u16() { assert_eq!(1000u16, Into::::into(CloseCode::Normal)); assert_eq!(1001u16, Into::::into(CloseCode::Away)); assert_eq!(1002u16, Into::::into(CloseCode::Protocol)); diff --git a/actix-web-actors/src/ws.rs b/actix-web-actors/src/ws.rs index 1ab4cfce5..de2802d21 100644 --- a/actix-web-actors/src/ws.rs +++ b/actix-web-actors/src/ws.rs @@ -15,10 +15,13 @@ use actix::{ SpawnHandle, }; use actix_codec::{Decoder, Encoder}; -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::dev::HttpResponseBuilder; use actix_web::error::{Error, PayloadError}; use actix_web::http::{header, Method, StatusCode}; @@ -162,7 +165,11 @@ pub fn handshake_with_protocols( let mut response = HttpResponse::build(StatusCode::SWITCHING_PROTOCOLS) .upgrade("websocket") - .insert_header((header::SEC_WEBSOCKET_ACCEPT, key)) + .insert_header(( + header::SEC_WEBSOCKET_ACCEPT, + // key is known to be header value safe ascii + HeaderValue::from_bytes(&key).unwrap(), + )) .take(); if let Some(protocol) = protocol { diff --git a/awc/src/error.rs b/awc/src/error.rs index f86224e62..b715f6213 100644 --- a/awc/src/error.rs +++ b/awc/src/error.rs @@ -18,24 +18,31 @@ pub enum WsClientError { /// Invalid response status #[display(fmt = "Invalid response status")] InvalidResponseStatus(StatusCode), + /// Invalid upgrade header #[display(fmt = "Invalid upgrade header")] InvalidUpgradeHeader, + /// Invalid connection header #[display(fmt = "Invalid connection header")] InvalidConnectionHeader(HeaderValue), - /// Missing CONNECTION header - #[display(fmt = "Missing CONNECTION header")] + + /// Missing Connection header + #[display(fmt = "Missing Connection header")] MissingConnectionHeader, - /// Missing SEC-WEBSOCKET-ACCEPT header - #[display(fmt = "Missing SEC-WEBSOCKET-ACCEPT header")] + + /// Missing Sec-Websocket-Accept header + #[display(fmt = "Missing Sec-Websocket-Accept header")] MissingWebSocketAcceptHeader, + /// Invalid challenge response #[display(fmt = "Invalid challenge response")] - InvalidChallengeResponse(String, HeaderValue), + InvalidChallengeResponse([u8; 28], HeaderValue), + /// Protocol error #[display(fmt = "{}", _0)] Protocol(WsProtocolError), + /// Send request error #[display(fmt = "{}", _0)] SendRequest(SendRequestError), diff --git a/awc/src/ws.rs b/awc/src/ws.rs index 5f4570963..1aa426ac7 100644 --- a/awc/src/ws.rs +++ b/awc/src/ws.rs @@ -381,12 +381,14 @@ impl WebsocketsRequest { if let Some(hdr_key) = head.headers.get(&header::SEC_WEBSOCKET_ACCEPT) { let encoded = ws::hash_key(key.as_ref()); - if hdr_key.as_bytes() != encoded.as_bytes() { + + if hdr_key.as_bytes() != &encoded { log::trace!( - "Invalid challenge response: expected: {} received: {:?}", - encoded, + "Invalid challenge response: expected: {:?} received: {:?}", + &encoded, key ); + return Err(WsClientError::InvalidChallengeResponse( encoded, hdr_key.clone(), From abc7fd374b7c603a01033ed0a6dc2f20987bc9fb Mon Sep 17 00:00:00 2001 From: "Daniel T. Rodrigues" <45438149+danitrod@users.noreply.github.com> Date: Sun, 28 Feb 2021 18:41:07 -0300 Subject: [PATCH 073/339] update example links (#2036) Co-authored-by: Rob Ede --- actix-files/README.md | 2 +- awc/README.md | 3 ++- examples/on_connect.rs | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/actix-files/README.md b/actix-files/README.md index 463f20224..a4f0445aa 100644 --- a/actix-files/README.md +++ b/actix-files/README.md @@ -14,6 +14,6 @@ ## Documentation & Resources - [API Documentation](https://docs.rs/actix-files/) -- [Example Project](https://github.com/actix/examples/tree/master/static_index) +- [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/awc/README.md b/awc/README.md index 043ae6a41..1f6e3b8fb 100644 --- a/awc/README.md +++ b/awc/README.md @@ -11,11 +11,12 @@ ## Documentation & Resources - [API Documentation](https://docs.rs/awc) -- [Example Project](https://github.com/actix/examples/tree/HEAD/awc_https) +- [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 + ```rust use actix_rt::System; use awc::Client; diff --git a/examples/on_connect.rs b/examples/on_connect.rs index ba5a18f3f..24ac86c6b 100644 --- a/examples/on_connect.rs +++ b/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}; From fb019f15b4362c9c1d6b1e89649eec9bb0e57bb2 Mon Sep 17 00:00:00 2001 From: Florian Dreschner Date: Mon, 1 Mar 2021 00:01:59 +0100 Subject: [PATCH 074/339] test(files): Fix test and remove outdated case (#2037) Co-authored-by: Rob Ede --- actix-files/src/lib.rs | 89 ++++++++++++------------------------------ 1 file changed, 26 insertions(+), 63 deletions(-) diff --git a/actix-files/src/lib.rs b/actix-files/src/lib.rs index 04dd9f07f..3c34c0403 100644 --- a/actix-files/src/lib.rs +++ b/actix-files/src/lib.rs @@ -662,8 +662,12 @@ mod tests { #[actix_rt::test] async fn test_static_files_bad_directory() { - let _st: Files = Files::new("/", "missing"); - let _st: Files = Files::new("/", "Cargo.toml"); + let service = Files::new("/", "./missing").new_service(()).await.unwrap(); + + let req = TestRequest::with_uri("/").to_srv_request(); + let resp = test::call_service(&service, req).await; + + assert_eq!(resp.status(), StatusCode::NOT_FOUND); } #[actix_rt::test] @@ -676,75 +680,34 @@ mod tests { .await .unwrap(); let req = TestRequest::with_uri("/missing").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")); } - // #[actix_rt::test] - // async fn test_serve_index() { - // let st = Files::new(".").index_file("test.binary"); - // let req = TestRequest::default().uri("/tests").finish(); + #[actix_rt::test] + async fn test_serve_index_nested() { + let service = Files::new(".", ".") + .index_file("lib.rs") + .new_service(()) + .await + .unwrap(); - // let resp = st.handle(&req).respond_to(&req).unwrap(); - // let resp = resp.as_msg(); - // assert_eq!(resp.status(), StatusCode::OK); - // assert_eq!( - // resp.headers() - // .get(header::CONTENT_TYPE) - // .expect("content type"), - // "application/octet-stream" - // ); - // assert_eq!( - // resp.headers() - // .get(header::CONTENT_DISPOSITION) - // .expect("content disposition"), - // "attachment; filename=\"test.binary\"" - // ); + let req = TestRequest::default().uri("/src").to_srv_request(); + let resp = test::call_service(&service, req).await; - // let req = TestRequest::default().uri("/tests/").finish(); - // let resp = st.handle(&req).respond_to(&req).unwrap(); - // let resp = resp.as_msg(); - // assert_eq!(resp.status(), StatusCode::OK); - // assert_eq!( - // resp.headers().get(header::CONTENT_TYPE).unwrap(), - // "application/octet-stream" - // ); - // assert_eq!( - // resp.headers().get(header::CONTENT_DISPOSITION).unwrap(), - // "attachment; filename=\"test.binary\"" - // ); - - // // nonexistent index file - // let req = TestRequest::default().uri("/tests/unknown").finish(); - // let resp = st.handle(&req).respond_to(&req).unwrap(); - // let resp = resp.as_msg(); - // assert_eq!(resp.status(), StatusCode::NOT_FOUND); - - // let req = TestRequest::default().uri("/tests/unknown/").finish(); - // let resp = st.handle(&req).respond_to(&req).unwrap(); - // let resp = resp.as_msg(); - // assert_eq!(resp.status(), StatusCode::NOT_FOUND); - // } - - // #[actix_rt::test] - // async fn test_serve_index_nested() { - // let st = Files::new(".").index_file("mod.rs"); - // let req = TestRequest::default().uri("/src/client").finish(); - // let resp = st.handle(&req).respond_to(&req).unwrap(); - // let resp = resp.as_msg(); - // assert_eq!(resp.status(), StatusCode::OK); - // assert_eq!( - // resp.headers().get(header::CONTENT_TYPE).unwrap(), - // "text/x-rust" - // ); - // assert_eq!( - // resp.headers().get(header::CONTENT_DISPOSITION).unwrap(), - // "inline; filename=\"mod.rs\"" - // ); - // } + assert_eq!(resp.status(), StatusCode::OK); + assert_eq!( + resp.headers().get(header::CONTENT_TYPE).unwrap(), + "text/x-rust" + ); + assert_eq!( + resp.headers().get(header::CONTENT_DISPOSITION).unwrap(), + "inline; filename=\"lib.rs\"" + ); + } #[actix_rt::test] async fn integration_serve_index() { From 01958247940264639758ef88cca4897ea334bbb3 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Tue, 2 Mar 2021 13:18:16 +0000 Subject: [PATCH 075/339] Update codecov.yml --- codecov.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/codecov.yml b/codecov.yml index e6bc40203..e45672bfc 100644 --- a/codecov.yml +++ b/codecov.yml @@ -4,10 +4,10 @@ coverage: status: project: default: - threshold: 10% # make CI green + threshold: 100% # make CI green patch: default: - threshold: 10% # make CI green + threshold: 100% # make CI green ignore: # ignore code coverage on following paths - "**/tests" From 14b249b804d371007a0ab8ed45d8e4a47fe49818 Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Thu, 4 Mar 2021 03:39:29 -0800 Subject: [PATCH 076/339] update to actix-0.11.0-beta.3 for actix-web-actors (#2042) --- actix-web-actors/Cargo.toml | 2 +- actix-web-actors/src/context.rs | 4 ++-- actix-web-actors/src/ws.rs | 5 ++--- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/actix-web-actors/Cargo.toml b/actix-web-actors/Cargo.toml index 58508071a..e0c4fc073 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.2", default-features = false } +actix = { version = "0.11.0-beta.3", default-features = false } actix-codec = "0.4.0-beta.1" actix-http = "3.0.0-beta.3" actix-web = { version = "4.0.0-beta.3", default-features = false } diff --git a/actix-web-actors/src/context.rs b/actix-web-actors/src/context.rs index bd6635cba..d7459aea4 100644 --- a/actix-web-actors/src/context.rs +++ b/actix-web-actors/src/context.rs @@ -44,7 +44,7 @@ where #[inline] fn spawn(&mut self, fut: F) -> SpawnHandle where - F: ActorFuture + 'static, + F: ActorFuture + 'static, { self.inner.spawn(fut) } @@ -52,7 +52,7 @@ where #[inline] fn wait(&mut self, fut: F) where - F: ActorFuture + 'static, + F: ActorFuture + 'static, { self.inner.wait(fut) } diff --git a/actix-web-actors/src/ws.rs b/actix-web-actors/src/ws.rs index de2802d21..77d7041f0 100644 --- a/actix-web-actors/src/ws.rs +++ b/actix-web-actors/src/ws.rs @@ -211,14 +211,14 @@ where { fn spawn(&mut self, fut: F) -> SpawnHandle where - F: ActorFuture + 'static, + F: ActorFuture + 'static, { self.inner.spawn(fut) } fn wait(&mut self, fut: F) where - F: ActorFuture + 'static, + F: ActorFuture + 'static, { self.inner.wait(fut) } @@ -495,7 +495,6 @@ where if !*this.closed { loop { - this = self.as_mut().project(); match Pin::new(&mut this.stream).poll_next(cx) { Poll::Ready(Some(Ok(chunk))) => { this.buf.extend_from_slice(&chunk[..]); From fc6f974617a606077ab1d99b8ad06b9b75492682 Mon Sep 17 00:00:00 2001 From: Richard Chien Date: Thu, 4 Mar 2021 20:38:47 +0800 Subject: [PATCH 077/339] Add "name" attribute to route macro (#1934) --- actix-web-codegen/CHANGES.md | 2 ++ actix-web-codegen/src/lib.rs | 2 ++ actix-web-codegen/src/route.rs | 19 +++++++++++++++++-- actix-web-codegen/tests/test_macro.rs | 12 ++++++++++++ 4 files changed, 33 insertions(+), 2 deletions(-) diff --git a/actix-web-codegen/CHANGES.md b/actix-web-codegen/CHANGES.md index f1dbf8e5f..7c6543d49 100644 --- a/actix-web-codegen/CHANGES.md +++ b/actix-web-codegen/CHANGES.md @@ -2,8 +2,10 @@ ## Unreleased - 2021-xx-xx * 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 diff --git a/actix-web-codegen/src/lib.rs b/actix-web-codegen/src/lib.rs index 670d82ce9..48414d491 100644 --- a/actix-web-codegen/src/lib.rs +++ b/actix-web-codegen/src/lib.rs @@ -71,6 +71,7 @@ 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. @@ -116,6 +117,7 @@ Creates route handler with `actix_web::guard::", stringify!($variant), "`. # 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. diff --git a/actix-web-codegen/src/route.rs b/actix-web-codegen/src/route.rs index e3c5f0368..ac0b7cea1 100644 --- a/actix-web-codegen/src/route.rs +++ b/actix-web-codegen/src/route.rs @@ -78,6 +78,7 @@ impl TryFrom<&syn::LitStr> for MethodType { struct Args { path: syn::LitStr, + resource_name: Option, guards: Vec, wrappers: Vec, methods: HashSet, @@ -86,6 +87,7 @@ struct Args { impl Args { fn new(args: AttributeArgs, method: Option) -> syn::Result { let mut path = None; + let mut resource_name = None; let mut guards = Vec::new(); let mut wrappers = Vec::new(); let mut methods = HashSet::new(); @@ -109,7 +111,16 @@ impl Args { } }, NestedMeta::Meta(syn::Meta::NameValue(nv)) => { - if nv.path.is_ident("guard") { + if nv.path.is_ident("name") { + if let syn::Lit::Str(lit) = nv.lit { + resource_name = Some(lit); + } else { + return Err(syn::Error::new_spanned( + nv.lit, + "Attribute name expects literal string!", + )); + } + } else if nv.path.is_ident("guard") { if let syn::Lit::Str(lit) = nv.lit { guards.push(Ident::new(&lit.value(), Span::call_site())); } else { @@ -164,6 +175,7 @@ impl Args { } Ok(Args { path: path.unwrap(), + resource_name, guards, wrappers, methods, @@ -276,6 +288,7 @@ impl ToTokens for Route { args: Args { path, + resource_name, guards, wrappers, methods, @@ -283,7 +296,9 @@ impl ToTokens for Route { resource_type, doc_attributes, } = self; - let resource_name = name.to_string(); + let resource_name = resource_name + .as_ref() + .map_or_else(|| name.to_string(), |n| n.value()); let method_guards = { let mut others = methods.iter(); // unwrapping since length is checked to be at least one diff --git a/actix-web-codegen/tests/test_macro.rs b/actix-web-codegen/tests/test_macro.rs index 34cdad649..0cbb64ba5 100644 --- a/actix-web-codegen/tests/test_macro.rs +++ b/actix-web-codegen/tests/test_macro.rs @@ -83,6 +83,13 @@ async fn route_test() -> impl Responder { HttpResponse::Ok() } +#[get("/custom_resource_name", name = "custom")] +async fn custom_resource_name_test<'a>(req: actix_web::HttpRequest) -> impl Responder { + assert!(req.url_for_static("custom").is_ok()); + assert!(req.url_for_static("custom_resource_name_test").is_err()); + HttpResponse::Ok() +} + pub struct ChangeStatusCode; impl Transform for ChangeStatusCode @@ -174,6 +181,7 @@ async fn test_body() { .service(patch_test) .service(test_handler) .service(route_test) + .service(custom_resource_name_test) }); let request = srv.request(http::Method::GET, srv.url("/test")); let response = request.send().await.unwrap(); @@ -228,6 +236,10 @@ async fn test_body() { let request = srv.request(http::Method::PATCH, srv.url("/multi")); 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()); } #[actix_rt::test] From c1c4400c4a235d6d8bd14217aac61161d98b9699 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Thu, 4 Mar 2021 13:27:54 +0000 Subject: [PATCH 078/339] fix h2 tests (#2034) --- actix-http/Cargo.toml | 2 +- actix-http/tests/test_openssl.rs | 32 +++++++++++++++++++------------- actix-http/tests/test_rustls.rs | 30 ++++++++++++++++++------------ 3 files changed, 38 insertions(+), 26 deletions(-) diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index d7915ddf9..c79ad11b2 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -61,7 +61,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.0" +h2 = "0.3.1" http = "0.2.2" httparse = "1.3" itoa = "0.4" diff --git a/actix-http/tests/test_openssl.rs b/actix-http/tests/test_openssl.rs index f44968baa..d5ec645a4 100644 --- a/actix-http/tests/test_openssl.rs +++ b/actix-http/tests/test_openssl.rs @@ -123,16 +123,14 @@ async fn test_h2_content_length() { let srv = test_server(move || { HttpService::build() .h2(|req: Request| { - let indx: usize = req.uri().path()[1..].parse().unwrap(); + let idx: usize = req.uri().path()[1..].parse().unwrap(); let statuses = [ - StatusCode::NO_CONTENT, StatusCode::CONTINUE, - StatusCode::SWITCHING_PROTOCOLS, - StatusCode::PROCESSING, + StatusCode::NO_CONTENT, StatusCode::OK, StatusCode::NOT_FOUND, ]; - ok::<_, ()>(Response::new(statuses[indx])) + ok::<_, ()>(Response::new(statuses[idx])) }) .openssl(tls_config()) .map_err(|_| ()) @@ -143,21 +141,29 @@ async fn test_h2_content_length() { let value = HeaderValue::from_static("0"); { - for i in 0..4 { + 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::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); + } + + 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::HEAD, srv.surl(&format!("/{}", i))) - .send(); - let response = req.await.unwrap(); - assert_eq!(response.headers().get(&header), None); } - for i in 4..6 { + for &i in &[2, 3] { let req = srv .request(Method::GET, srv.surl(&format!("/{}", i))) .send(); diff --git a/actix-http/tests/test_rustls.rs b/actix-http/tests/test_rustls.rs index a36400910..81edb5c18 100644 --- a/actix-http/tests/test_rustls.rs +++ b/actix-http/tests/test_rustls.rs @@ -17,7 +17,6 @@ use rustls::{ NoClientAuth, ServerConfig as RustlsServerConfig, }; -use std::fs::File; use std::io::{self, BufReader}; async fn load_body(mut stream: S) -> Result @@ -139,10 +138,8 @@ async fn test_h2_content_length() { .h2(|req: Request| { let indx: usize = req.uri().path()[1..].parse().unwrap(); let statuses = [ - StatusCode::NO_CONTENT, StatusCode::CONTINUE, - StatusCode::SWITCHING_PROTOCOLS, - StatusCode::PROCESSING, + StatusCode::NO_CONTENT, StatusCode::OK, StatusCode::NOT_FOUND, ]; @@ -154,22 +151,31 @@ async fn test_h2_content_length() { let header = HeaderName::from_static("content-length"); let value = HeaderValue::from_static("0"); + { - for i in 0..4 { + 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::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); + } + + 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::HEAD, srv.surl(&format!("/{}", i))) - .send(); - let response = req.await.unwrap(); - assert_eq!(response.headers().get(&header), None); } - for i in 4..6 { + for &i in &[2, 3] { let req = srv .request(Method::GET, srv.surl(&format!("/{}", i))) .send(); From 78384c3ff57f2fee1820cdb13a720cbf75de54e9 Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Thu, 4 Mar 2021 11:19:01 -0800 Subject: [PATCH 079/339] make actix_http::ws::Codec::new const (#2043) --- actix-http/src/ws/codec.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actix-http/src/ws/codec.rs b/actix-http/src/ws/codec.rs index 54d850854..8655216fa 100644 --- a/actix-http/src/ws/codec.rs +++ b/actix-http/src/ws/codec.rs @@ -80,7 +80,7 @@ bitflags! { impl Codec { /// Create new WebSocket frames decoder. - pub fn new() -> Codec { + pub const fn new() -> Codec { Codec { max_size: 65_536, flags: Flags::SERVER, From 880b863f958fdbead8f604fd935e928eb442120d Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Sun, 7 Mar 2021 10:33:16 -0800 Subject: [PATCH 080/339] fix h1 client for handling expect header request (#2049) --- actix-http/src/client/h1proto.rs | 83 ++++++++++++++++++++++++-------- actix-http/tests/test_client.rs | 61 ++++++++++++++++++++++- 2 files changed, 121 insertions(+), 23 deletions(-) diff --git a/actix-http/src/client/h1proto.rs b/actix-http/src/client/h1proto.rs index 082c4b8e2..d2db18cec 100644 --- a/actix-http/src/client/h1proto.rs +++ b/actix-http/src/client/h1proto.rs @@ -7,13 +7,15 @@ use actix_codec::{AsyncRead, AsyncWrite, Framed, ReadBuf}; use bytes::buf::BufMut; use bytes::{Bytes, BytesMut}; use futures_core::Stream; -use futures_util::future::poll_fn; -use futures_util::{SinkExt, StreamExt}; +use futures_util::{future::poll_fn, SinkExt, StreamExt}; use crate::error::PayloadError; use crate::h1; use crate::header::HeaderMap; -use crate::http::header::{IntoHeaderValue, HOST}; +use crate::http::{ + header::{IntoHeaderValue, EXPECT, HOST}, + StatusCode, +}; use crate::message::{RequestHeadType, ResponseHead}; use crate::payload::{Payload, PayloadStream}; @@ -66,33 +68,72 @@ where io: Some(io), }; - // create Framed and send request - let mut framed_inner = Framed::new(io, h1::ClientCodec::default()); - framed_inner.send((head, body.size()).into()).await?; + // create Framed and prepare sending request + let mut framed = Framed::new(io, h1::ClientCodec::default()); - // send request body - match body.size() { - BodySize::None | BodySize::Empty | BodySize::Sized(0) => {} - _ => send_body(body, Pin::new(&mut framed_inner)).await?, - }; + // Check EXPECT header and enable expect handle flag accordingly. + // + // 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) => { + let pin_framed = Pin::new(&mut framed); - // read response and init read body - let res = Pin::new(&mut framed_inner).into_future().await; - let (head, framed) = if let (Some(result), framed) = res { - let item = result.map_err(SendRequestError::from)?; - (item, framed) + let force_close = !pin_framed.codec_ref().keepalive(); + release_connection(pin_framed, force_close); + + // TODO: use a new variant or a new type better describing error violate + // `Requirements for clients` session of above RFC + return Err(SendRequestError::Connect(ConnectError::Disconnected)); + } + _ => true, + } } else { - return Err(SendRequestError::from(ConnectError::Disconnected)); + false }; - match framed.codec_ref().message_type() { + 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 { + let head = poll_fn(|cx| pin_framed.as_mut().poll_next(cx)) + .await + .ok_or(ConnectError::Disconnected)??; + + // return response head in case status code is not continue + // and current head would be used as final response head. + (head.status == StatusCode::CONTINUE, Some(head)) + } else { + (true, None) + }; + + if do_send { + // send request body + match body.size() { + BodySize::None | BodySize::Empty | BodySize::Sized(0) => {} + _ => send_body(body, pin_framed.as_mut()).await?, + }; + + // read response and init read body + let head = poll_fn(|cx| pin_framed.as_mut().poll_next(cx)) + .await + .ok_or(ConnectError::Disconnected)??; + + res_head = Some(head); + } + + let head = res_head.unwrap(); + + match pin_framed.codec_ref().message_type() { h1::MessageType::None => { - let force_close = !framed.codec_ref().keepalive(); - release_connection(framed, force_close); + let force_close = !pin_framed.codec_ref().keepalive(); + release_connection(pin_framed, force_close); Ok((head, Payload::None)) } _ => { - let pl: PayloadStream = PlStream::new(framed_inner).boxed_local(); + let pl: PayloadStream = Box::pin(PlStream::new(framed)); Ok((head, pl.into())) } } diff --git a/actix-http/tests/test_client.rs b/actix-http/tests/test_client.rs index 91b2412f4..a50f2404d 100644 --- a/actix-http/tests/test_client.rs +++ b/actix-http/tests/test_client.rs @@ -1,8 +1,13 @@ -use actix_http::{http, HttpService, Request, Response}; +use actix_http::{ + error, http, http::StatusCode, HttpMessage, HttpService, Request, Response, +}; use actix_http_test::test_server; use actix_service::ServiceFactoryExt; use bytes::Bytes; -use futures_util::future::{self, ok}; +use futures_util::{ + future::{self, ok}, + StreamExt, +}; const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ Hello World Hello World Hello World Hello World Hello World \ @@ -88,3 +93,55 @@ async fn test_with_query_parameter() { let response = request.send().await.unwrap(); assert!(response.status().is_success()); } + +#[actix_rt::test] +async fn test_h1_expect() { + let srv = test_server(move || { + HttpService::build() + .expect(|req: Request| async { + if req.headers().contains_key("AUTH") { + Ok(req) + } else { + Err(error::ErrorExpectationFailed("expect failed")) + } + }) + .h1(|req: Request| async move { + let (_, mut body) = req.into_parts(); + let mut buf = Vec::new(); + while let Some(Ok(chunk)) = body.next().await { + buf.extend_from_slice(&chunk); + } + let str = std::str::from_utf8(&buf).unwrap(); + assert_eq!(str, "expect body"); + + Ok::<_, ()>(Response::Ok().finish()) + }) + .tcp() + }) + .await; + + // test expect without payload. + let request = srv + .request(http::Method::GET, srv.url("/")) + .insert_header(("Expect", "100-continue")); + + let response = request.send().await; + assert!(response.is_err()); + + // test expect would fail to continue + let request = srv + .request(http::Method::GET, srv.url("/")) + .insert_header(("Expect", "100-continue")); + + let response = request.send_body("expect body").await.unwrap(); + assert_eq!(response.status(), StatusCode::EXPECTATION_FAILED); + + // test exepct would continue + let request = srv + .request(http::Method::GET, srv.url("/")) + .insert_header(("Expect", "100-continue")) + .insert_header(("AUTH", "996")); + + let response = request.send_body("expect body").await.unwrap(); + assert!(response.status().is_success()); +} From ca69b6577e0871d65721582cc452a010d6ed1fe4 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sun, 7 Mar 2021 19:29:02 +0000 Subject: [PATCH 081/339] use iota for more content-length insertions (#2050) --- actix-http/src/client/h2proto.rs | 18 +++++++++++------ actix-http/src/h1/encoder.rs | 5 ++--- actix-http/src/h2/dispatcher.rs | 25 ++++++++++++------------ actix-http/src/response.rs | 3 ++- awc/CHANGES.md | 3 +++ awc/Cargo.toml | 3 ++- awc/src/request.rs | 33 +++++++++++++------------------- awc/tests/test_client.rs | 4 ++-- 8 files changed, 49 insertions(+), 45 deletions(-) diff --git a/actix-http/src/client/h2proto.rs b/actix-http/src/client/h2proto.rs index 0deb5c014..7292972de 100644 --- a/actix-http/src/client/h2proto.rs +++ b/actix-http/src/client/h2proto.rs @@ -1,4 +1,3 @@ -use std::convert::TryFrom; use std::future::Future; use std::time; @@ -61,10 +60,14 @@ where BodySize::Empty => req .headers_mut() .insert(CONTENT_LENGTH, HeaderValue::from_static("0")), - BodySize::Sized(len) => req.headers_mut().insert( - CONTENT_LENGTH, - HeaderValue::try_from(format!("{}", len)).unwrap(), - ), + BodySize::Sized(len) => { + let mut buf = itoa::Buffer::new(); + + req.headers_mut().insert( + CONTENT_LENGTH, + HeaderValue::from_str(buf.format(len)).unwrap(), + ) + } }; // Extracting extra headers from RequestHeadType. HeaderMap::new() does not allocate. @@ -87,7 +90,10 @@ where // copy headers for (key, value) in headers { match *key { - CONNECTION | TRANSFER_ENCODING => continue, // http2 specific + // TODO: consider skipping other headers according to: + // https://tools.ietf.org/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, _ => {} diff --git a/actix-http/src/h1/encoder.rs b/actix-http/src/h1/encoder.rs index 97916e7db..4e9903284 100644 --- a/actix-http/src/h1/encoder.rs +++ b/actix-http/src/h1/encoder.rs @@ -632,10 +632,9 @@ mod tests { let mut res: Response<()> = Response::new(StatusCode::SWITCHING_PROTOCOLS).into_body::<()>(); + res.headers_mut().insert(DATE, HeaderValue::from_static("")); res.headers_mut() - .insert(DATE, HeaderValue::from_static(&"")); - res.headers_mut() - .insert(CONTENT_LENGTH, HeaderValue::from_static(&"0")); + .insert(CONTENT_LENGTH, HeaderValue::from_static("0")); let _ = res.encode_headers( &mut bytes, diff --git a/actix-http/src/h2/dispatcher.rs b/actix-http/src/h2/dispatcher.rs index 5ccd2a9d1..8e01afcb5 100644 --- a/actix-http/src/h2/dispatcher.rs +++ b/actix-http/src/h2/dispatcher.rs @@ -1,10 +1,5 @@ -use std::future::Future; -use std::marker::PhantomData; -use std::net; -use std::pin::Pin; -use std::rc::Rc; use std::task::{Context, Poll}; -use std::{cmp, convert::TryFrom}; +use std::{cmp, future::Future, marker::PhantomData, net, pin::Pin, rc::Rc}; use actix_codec::{AsyncRead, AsyncWrite}; use actix_rt::time::{Instant, Sleep}; @@ -125,7 +120,7 @@ where let pl = Payload::::H2(pl); let mut req = Request::with_payload(pl); - let head = &mut req.head_mut(); + let head = req.head_mut(); head.uri = parts.uri; head.method = parts.method; head.version = parts.version; @@ -203,16 +198,22 @@ where BodySize::Empty => res .headers_mut() .insert(CONTENT_LENGTH, HeaderValue::from_static("0")), - BodySize::Sized(len) => res.headers_mut().insert( - CONTENT_LENGTH, - HeaderValue::try_from(format!("{}", len)).unwrap(), - ), + BodySize::Sized(len) => { + let mut buf = itoa::Buffer::new(); + + res.headers_mut().insert( + CONTENT_LENGTH, + HeaderValue::from_str(buf.format(*len)).unwrap(), + ) + } }; // copy headers for (key, value) in head.headers.iter() { match *key { - // omit HTTP/1 only headers + // TODO: consider skipping other headers according to: + // https://tools.ietf.org/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, diff --git a/actix-http/src/response.rs b/actix-http/src/response.rs index f96f8f9b6..c5a4aec13 100644 --- a/actix-http/src/response.rs +++ b/actix-http/src/response.rs @@ -498,7 +498,8 @@ impl ResponseBuilder { /// Disable chunked transfer encoding for HTTP/1.1 streaming responses. #[inline] pub fn no_chunking(&mut self, len: u64) -> &mut Self { - self.insert_header((header::CONTENT_LENGTH, len)); + let mut buf = itoa::Buffer::new(); + self.insert_header((header::CONTENT_LENGTH, buf.format(len))); if let Some(parts) = parts(&mut self.head, &self.err) { parts.no_chunking(true); diff --git a/awc/CHANGES.md b/awc/CHANGES.md index 9fbc6d042..211db5a80 100644 --- a/awc/CHANGES.md +++ b/awc/CHANGES.md @@ -8,6 +8,7 @@ ### 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] ### Removed * `ClientBuilder::default` function [#2008] @@ -17,6 +18,8 @@ [#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 + ## 3.0.0-beta.2 - 2021-02-10 ### Added diff --git a/awc/Cargo.toml b/awc/Cargo.toml index ca345d3cb..761871ba2 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -54,6 +54,7 @@ bytes = "1" cfg-if = "1.0" derive_more = "0.99.5" futures-core = { version = "0.3.7", default-features = false } +itoa = "0.4" log =" 0.4" mime = "0.3" percent-encoding = "2.1" @@ -80,8 +81,8 @@ actix-server = "2.0.0-beta.3" actix-tls = { version = "3.0.0-beta.4", features = ["openssl", "rustls"] } brotli2 = "0.3.2" +env_logger = "0.8" flate2 = "1.0.13" futures-util = { version = "0.3.7", default-features = false } -env_logger = "0.8" rcgen = "0.8" webpki = "0.21" diff --git a/awc/src/request.rs b/awc/src/request.rs index 812c0e805..1b63f3687 100644 --- a/awc/src/request.rs +++ b/awc/src/request.rs @@ -248,30 +248,25 @@ impl ClientRequest { /// Set content length #[inline] pub fn content_length(self, len: u64) -> Self { - self.append_header((header::CONTENT_LENGTH, len)) + let mut buf = itoa::Buffer::new(); + self.insert_header((header::CONTENT_LENGTH, buf.format(len))) } - /// Set HTTP basic authorization header - pub fn basic_auth(self, username: U, password: Option<&str>) -> Self - where - U: fmt::Display, - { - let auth = match password { - Some(password) => format!("{}:{}", username, password), - None => format!("{}:", username), - }; - self.append_header(( + /// Set HTTP basic authorization header. + /// + /// If no password is needed, just provide an empty string. + pub fn basic_auth(self, username: impl fmt::Display, password: impl fmt::Display) -> Self { + let auth = format!("{}:{}", username, password); + + self.insert_header(( header::AUTHORIZATION, format!("Basic {}", base64::encode(&auth)), )) } /// Set HTTP bearer authentication header - pub fn bearer_auth(self, token: T) -> Self - where - T: fmt::Display, - { - self.append_header((header::AUTHORIZATION, format!("Bearer {}", token))) + pub fn bearer_auth(self, token: impl fmt::Display) -> Self { + self.insert_header((header::AUTHORIZATION, format!("Bearer {}", token))) } /// Set a cookie @@ -643,9 +638,7 @@ mod tests { #[actix_rt::test] async fn client_basic_auth() { - let req = Client::new() - .get("/") - .basic_auth("username", Some("password")); + let req = Client::new().get("/").basic_auth("username", "password"); assert_eq!( req.head .headers @@ -656,7 +649,7 @@ mod tests { "Basic dXNlcm5hbWU6cGFzc3dvcmQ=" ); - let req = Client::new().get("/").basic_auth("username", None); + let req = Client::new().get("/").basic_auth("username", ""); assert_eq!( req.head .headers diff --git a/awc/tests/test_client.rs b/awc/tests/test_client.rs index c7fa82de8..50d2b5eac 100644 --- a/awc/tests/test_client.rs +++ b/awc/tests/test_client.rs @@ -829,7 +829,7 @@ async fn client_basic_auth() { .unwrap() .to_str() .unwrap() - == "Basic dXNlcm5hbWU6cGFzc3dvcmQ=" + == format!("Basic {}", base64::encode("username:password")) { HttpResponse::Ok() } else { @@ -840,7 +840,7 @@ async fn client_basic_auth() { }); // set authorization header to Basic - let request = srv.get("/").basic_auth("username", Some("password")); + let request = srv.get("/").basic_auth("username", "password"); let response = request.send().await.unwrap(); assert!(response.status().is_success()); } From fe0b3f459fd40aa279b86f3e99a1e1c3b2ee30e4 Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Sun, 7 Mar 2021 13:23:42 -0800 Subject: [PATCH 082/339] remove localwaker from h1::payload (#2051) --- actix-http/src/h1/payload.rs | 63 +++++++++++++++++++++++++++++------- 1 file changed, 51 insertions(+), 12 deletions(-) diff --git a/actix-http/src/h1/payload.rs b/actix-http/src/h1/payload.rs index d4cfee146..32275ac6b 100644 --- a/actix-http/src/h1/payload.rs +++ b/actix-http/src/h1/payload.rs @@ -3,9 +3,8 @@ use std::cell::RefCell; use std::collections::VecDeque; use std::pin::Pin; use std::rc::{Rc, Weak}; -use std::task::{Context, Poll}; +use std::task::{Context, Poll, Waker}; -use actix_utils::task::LocalWaker; use bytes::Bytes; use futures_core::Stream; @@ -134,7 +133,7 @@ impl PayloadSender { if shared.borrow().need_read { PayloadStatus::Read } else { - shared.borrow_mut().io_task.register(cx.waker()); + shared.borrow_mut().register_io(cx); PayloadStatus::Pause } } else { @@ -150,8 +149,8 @@ struct Inner { err: Option, need_read: bool, items: VecDeque, - task: LocalWaker, - io_task: LocalWaker, + task: Option, + io_task: Option, } impl Inner { @@ -162,8 +161,48 @@ impl Inner { err: None, items: VecDeque::new(), need_read: true, - task: LocalWaker::new(), - io_task: LocalWaker::new(), + task: None, + io_task: None, + } + } + + /// Wake up future waiting for payload data to be available. + fn wake(&mut self) { + if let Some(waker) = self.task.take() { + waker.wake(); + } + } + + /// Wake up future feeding data to Payload. + fn wake_io(&mut self) { + if let Some(waker) = self.io_task.take() { + waker.wake(); + } + } + + /// Register future waiting data from payload. + /// Waker would be used in `Inner::wake` + fn register(&mut self, cx: &mut Context<'_>) { + if self + .task + .as_ref() + .map(|w| !cx.waker().will_wake(w)) + .unwrap_or(true) + { + self.task = Some(cx.waker().clone()); + } + } + + // Register future feeding data to payload. + /// Waker would be used in `Inner::wake_io` + fn register_io(&mut self, cx: &mut Context<'_>) { + if self + .io_task + .as_ref() + .map(|w| !cx.waker().will_wake(w)) + .unwrap_or(true) + { + self.io_task = Some(cx.waker().clone()); } } @@ -182,7 +221,7 @@ impl Inner { self.len += data.len(); self.items.push_back(data); self.need_read = self.len < MAX_BUFFER_SIZE; - self.task.wake(); + self.wake(); } #[cfg(test)] @@ -199,9 +238,9 @@ impl Inner { self.need_read = self.len < MAX_BUFFER_SIZE; if self.need_read && !self.eof { - self.task.register(cx.waker()); + self.register(cx); } - self.io_task.wake(); + self.wake_io(); Poll::Ready(Some(Ok(data))) } else if let Some(err) = self.err.take() { Poll::Ready(Some(Err(err))) @@ -209,8 +248,8 @@ impl Inner { Poll::Ready(None) } else { self.need_read = true; - self.task.register(cx.waker()); - self.io_task.wake(); + self.register(cx); + self.wake_io(); Poll::Pending } } From 2d3a0d6038a835af7917e11bdcd4e9dcfa8a52ec Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sun, 7 Mar 2021 22:11:39 +0000 Subject: [PATCH 083/339] json method receives plain serialize (#2052) --- actix-http/CHANGES.md | 2 ++ actix-http/src/response.rs | 16 ++++++++-------- src/data.rs | 13 +++++++++++++ src/types/form.rs | 12 ++++++++++++ src/types/json.rs | 12 ++++++++++++ 5 files changed, 47 insertions(+), 8 deletions(-) diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 165b004a6..14218289d 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -4,6 +4,7 @@ ### 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] ### Removed * re-export of `futures_channel::oneshot::Canceled` is removed from `error` mod. [#1994] @@ -12,6 +13,7 @@ [#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 ## 3.0.0-beta.3 - 2021-02-10 diff --git a/actix-http/src/response.rs b/actix-http/src/response.rs index c5a4aec13..d581fd293 100644 --- a/actix-http/src/response.rs +++ b/actix-http/src/response.rs @@ -5,7 +5,6 @@ use std::{ convert::TryInto, fmt, future::Future, - ops, pin::Pin, str, task::{Context, Poll}, @@ -673,12 +672,8 @@ impl ResponseBuilder { /// Set a json body and generate `Response` /// /// `ResponseBuilder` can not be used after this call. - pub fn json(&mut self, value: T) -> Response - where - T: ops::Deref, - T::Target: Serialize, - { - match serde_json::to_string(&*value) { + pub fn json(&mut self, value: impl Serialize) -> Response { + match serde_json::to_string(&value) { Ok(body) => { let contains = if let Some(parts) = parts(&mut self.head, &self.err) { parts.headers.contains_key(header::CONTENT_TYPE) @@ -1007,7 +1002,12 @@ mod tests { #[test] fn test_json() { - let resp = Response::build(StatusCode::OK).json(vec!["v1", "v2", "v3"]); + let resp = Response::Ok().json(vec!["v1", "v2", "v3"]); + let ct = resp.headers().get(CONTENT_TYPE).unwrap(); + assert_eq!(ct, HeaderValue::from_static("application/json")); + assert_eq!(resp.body().get_ref(), b"[\"v1\",\"v2\",\"v3\"]"); + + let resp = Response::Ok().json(&["v1", "v2", "v3"]); let ct = resp.headers().get(CONTENT_TYPE).unwrap(); assert_eq!(ct, HeaderValue::from_static("application/json")); assert_eq!(resp.body().get_ref(), b"[\"v1\",\"v2\",\"v3\"]"); diff --git a/src/data.rs b/src/data.rs index 133248212..0336553ca 100644 --- a/src/data.rs +++ b/src/data.rs @@ -5,6 +5,7 @@ use std::sync::Arc; use actix_http::error::{Error, ErrorInternalServerError}; use actix_http::Extensions; use futures_util::future::{err, ok, LocalBoxFuture, Ready}; +use serde::Serialize; use crate::dev::Payload; use crate::extract::FromRequest; @@ -102,6 +103,18 @@ impl From> for Data { } } +impl Serialize for Data +where + T: Serialize, +{ + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + self.0.serialize(serializer) + } +} + impl FromRequest for Data { type Config = (); type Error = Error; diff --git a/src/types/form.rs b/src/types/form.rs index 4f3ecbe7c..57a742e38 100644 --- a/src/types/form.rs +++ b/src/types/form.rs @@ -106,6 +106,18 @@ impl ops::DerefMut for Form { } } +impl Serialize for Form +where + T: Serialize, +{ + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + self.0.serialize(serializer) + } +} + /// See [here](#extractor) for example of usage as an extractor. impl FromRequest for Form where diff --git a/src/types/json.rs b/src/types/json.rs index 866d835f2..d8ce3cb71 100644 --- a/src/types/json.rs +++ b/src/types/json.rs @@ -114,6 +114,18 @@ where } } +impl Serialize for Json +where + T: Serialize, +{ + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + self.0.serialize(serializer) + } +} + /// Creates response with OK status code, correct content type header, and serialized JSON payload. /// /// If serialization failed From 5b4105e1e6e5c69e7f71efaae20923fe7746f792 Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Sun, 7 Mar 2021 15:57:32 -0800 Subject: [PATCH 084/339] Refactor/client builder (#2053) --- awc/CHANGES.md | 1 - awc/src/builder.rs | 38 +++++++++++++++++++++++++++++++++- awc/src/lib.rs | 10 ++------- awc/src/middleware/redirect.rs | 3 ++- 4 files changed, 41 insertions(+), 11 deletions(-) diff --git a/awc/CHANGES.md b/awc/CHANGES.md index 211db5a80..b11d64628 100644 --- a/awc/CHANGES.md +++ b/awc/CHANGES.md @@ -12,7 +12,6 @@ ### Removed * `ClientBuilder::default` function [#2008] -* `ClientBuilder::disable_redirects` and `ClientBuilder::max_redirects` method [#2008] [#1931]: https://github.com/actix/actix-web/pull/1931 [#1981]: https://github.com/actix/actix-web/pull/1981 diff --git a/awc/src/builder.rs b/awc/src/builder.rs index 363056c02..72a0f4f04 100644 --- a/awc/src/builder.rs +++ b/awc/src/builder.rs @@ -14,7 +14,7 @@ use actix_service::{boxed, Service}; use crate::connect::DefaultConnector; use crate::error::SendRequestError; -use crate::middleware::{NestTransform, Transform}; +use crate::middleware::{NestTransform, Redirect, Transform}; use crate::{Client, ClientConfig, ConnectRequest, ConnectResponse, ConnectorService}; /// An HTTP Client builder @@ -31,6 +31,7 @@ pub struct ClientBuilder { connector: Connector, middleware: M, local_address: Option, + max_redirects: u8, } impl ClientBuilder { @@ -54,6 +55,7 @@ impl ClientBuilder { max_http_version: None, stream_window_size: None, conn_window_size: None, + max_redirects: 10, } } } @@ -86,6 +88,7 @@ where max_http_version: self.max_http_version, stream_window_size: self.stream_window_size, conn_window_size: self.conn_window_size, + max_redirects: self.max_redirects, } } @@ -118,6 +121,22 @@ where self } + /// Do not follow redirects. + /// + /// Redirects are allowed by default. + pub fn disable_redirects(mut self) -> Self { + self.max_redirects = 0; + self + } + + /// Set max number of redirects. + /// + /// Max redirects is set to 10 by default. + pub fn max_redirects(mut self, num: u8) -> Self { + self.max_redirects = num; + self + } + /// Indicates the initial window size (in octets) for /// HTTP2 stream-level flow control for received data. /// @@ -209,11 +228,28 @@ where timeout: self.timeout, connector: self.connector, local_address: self.local_address, + max_redirects: self.max_redirects, } } /// Finish build process and create `Client` instance. pub fn finish(self) -> Client + where + M: Transform + 'static, + M::Transform: + Service, + { + let redirect_time = self.max_redirects; + + if redirect_time > 0 { + self.wrap(Redirect::new().max_redirect_times(redirect_time)) + ._finish() + } else { + self._finish() + } + } + + fn _finish(self) -> Client where M: Transform + 'static, M::Transform: diff --git a/awc/src/lib.rs b/awc/src/lib.rs index 2f48dca79..4cd1d5bb2 100644 --- a/awc/src/lib.rs +++ b/awc/src/lib.rs @@ -107,7 +107,7 @@ use actix_http::{ RequestHead, }; use actix_rt::net::TcpStream; -use actix_service::{boxed, Service}; +use actix_service::Service; mod builder; mod connect; @@ -157,13 +157,7 @@ pub(crate) struct ClientConfig { impl Default for Client { fn default() -> Self { - Client(Rc::new(ClientConfig { - connector: boxed::service(self::connect::DefaultConnector::new( - Connector::new().finish(), - )), - headers: HeaderMap::new(), - timeout: Some(Duration::from_secs(5)), - })) + ClientBuilder::new().finish() } } diff --git a/awc/src/middleware/redirect.rs b/awc/src/middleware/redirect.rs index 1d0ace166..f8bdd2def 100644 --- a/awc/src/middleware/redirect.rs +++ b/awc/src/middleware/redirect.rs @@ -292,7 +292,7 @@ mod tests { #[actix_rt::test] async fn test_basic_redirect() { let client = ClientBuilder::new() - .connector(crate::Connector::new()) + .disable_redirects() .wrap(Redirect::new().max_redirect_times(10)) .finish(); @@ -318,6 +318,7 @@ mod tests { #[actix_rt::test] async fn test_redirect_limit() { let client = ClientBuilder::new() + .disable_redirects() .wrap(Redirect::new().max_redirect_times(1)) .connector(crate::Connector::new()) .finish(); From 5e811053173a6442e1e228ef8025f4704e4a9216 Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Mon, 8 Mar 2021 12:00:20 -0800 Subject: [PATCH 085/339] remove ka timer from h2 dispatcher (#2057) --- actix-http/src/h2/dispatcher.rs | 110 +++++++++++--------------------- actix-http/src/h2/service.rs | 1 - actix-http/src/service.rs | 1 - 3 files changed, 37 insertions(+), 75 deletions(-) diff --git a/actix-http/src/h2/dispatcher.rs b/actix-http/src/h2/dispatcher.rs index 8e01afcb5..958c761d5 100644 --- a/actix-http/src/h2/dispatcher.rs +++ b/actix-http/src/h2/dispatcher.rs @@ -2,7 +2,6 @@ use std::task::{Context, Poll}; use std::{cmp, future::Future, marker::PhantomData, net, pin::Pin, rc::Rc}; use actix_codec::{AsyncRead, AsyncWrite}; -use actix_rt::time::{Instant, Sleep}; use actix_service::Service; use bytes::{Bytes, BytesMut}; use futures_core::ready; @@ -36,8 +35,6 @@ where on_connect_data: OnConnectData, config: ServiceConfig, peer_addr: Option, - ka_expire: Instant, - ka_timer: Option, _phantom: PhantomData, } @@ -54,33 +51,14 @@ where connection: Connection, on_connect_data: OnConnectData, config: ServiceConfig, - timeout: Option, peer_addr: Option, ) -> Self { - // let keepalive = config.keep_alive_enabled(); - // let flags = if keepalive { - // Flags::KEEPALIVE | Flags::KEEPALIVE_ENABLED - // } else { - // Flags::empty() - // }; - - // keep-alive timer - let (ka_expire, ka_timer) = if let Some(delay) = timeout { - (delay.deadline(), Some(delay)) - } else if let Some(delay) = config.keep_alive_timer() { - (delay.deadline(), Some(delay)) - } else { - (config.now(), None) - }; - Dispatcher { flow, config, peer_addr, connection, on_connect_data, - ka_expire, - ka_timer, _phantom: PhantomData, } } @@ -108,13 +86,6 @@ where Some(Err(err)) => return Poll::Ready(Err(err.into())), Some(Ok((req, res))) => { - // update keep-alive expire - if this.ka_timer.is_some() { - if let Some(expire) = this.config.keep_alive_expire() { - this.ka_expire = expire; - } - } - let (parts, body) = req.into_parts(); let pl = crate::h2::Payload::new(body); let pl = Payload::::H2(pl); @@ -130,7 +101,7 @@ where // merge on_connect_ext data into request extensions this.on_connect_data.merge_into(&mut req); - let svc = ServiceResponse:: { + let svc = ServiceResponse { state: ServiceResponseState::ServiceCall( this.flow.service.call(req), Some(res), @@ -312,57 +283,50 @@ where ServiceResponseStateProj::SendPayload(ref mut stream, ref mut body) => { loop { - loop { - match this.buffer { - Some(ref mut buffer) => { - match ready!(stream.poll_capacity(cx)) { - None => return Poll::Ready(()), + match this.buffer { + Some(ref mut buffer) => match ready!(stream.poll_capacity(cx)) { + None => return Poll::Ready(()), - Some(Ok(cap)) => { - let len = buffer.len(); - let bytes = buffer.split_to(cmp::min(cap, len)); + Some(Ok(cap)) => { + let len = buffer.len(); + let bytes = buffer.split_to(cmp::min(cap, len)); - if let Err(e) = stream.send_data(bytes, false) { - warn!("{:?}", e); - return Poll::Ready(()); - } else if !buffer.is_empty() { - let cap = cmp::min(buffer.len(), CHUNK_SIZE); - stream.reserve_capacity(cap); - } else { - this.buffer.take(); - } - } - - Some(Err(e)) => { - warn!("{:?}", e); - return Poll::Ready(()); - } + if let Err(e) = stream.send_data(bytes, false) { + warn!("{:?}", e); + return Poll::Ready(()); + } else if !buffer.is_empty() { + let cap = cmp::min(buffer.len(), CHUNK_SIZE); + stream.reserve_capacity(cap); + } else { + this.buffer.take(); } } - None => match ready!(body.as_mut().poll_next(cx)) { - None => { - if let Err(e) = stream.send_data(Bytes::new(), true) - { - warn!("{:?}", e); - } - return Poll::Ready(()); - } + Some(Err(e)) => { + warn!("{:?}", e); + return Poll::Ready(()); + } + }, - Some(Ok(chunk)) => { - stream.reserve_capacity(cmp::min( - chunk.len(), - CHUNK_SIZE, - )); - *this.buffer = Some(chunk); + None => match ready!(body.as_mut().poll_next(cx)) { + None => { + if let Err(e) = stream.send_data(Bytes::new(), true) { + warn!("{:?}", e); } + return Poll::Ready(()); + } - Some(Err(e)) => { - error!("Response payload stream error: {:?}", e); - return Poll::Ready(()); - } - }, - } + Some(Ok(chunk)) => { + stream + .reserve_capacity(cmp::min(chunk.len(), CHUNK_SIZE)); + *this.buffer = Some(chunk); + } + + Some(Err(e)) => { + error!("Response payload stream error: {:?}", e); + return Poll::Ready(()); + } + }, } } } diff --git a/actix-http/src/h2/service.rs b/actix-http/src/h2/service.rs index 0984b3f23..1dc290e49 100644 --- a/actix-http/src/h2/service.rs +++ b/actix-http/src/h2/service.rs @@ -368,7 +368,6 @@ where conn, on_connect_data, config.take().unwrap(), - None, *peer_addr, )); self.poll(cx) diff --git a/actix-http/src/service.rs b/actix-http/src/service.rs index 402affb7e..89f3e3bb1 100644 --- a/actix-http/src/service.rs +++ b/actix-http/src/service.rs @@ -658,7 +658,6 @@ where conn, on_connect_data, cfg, - None, peer_addr, ))); self.poll(cx) From 95130fcfd00a4344a44ebaccee54eb967098bf89 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Mon, 8 Mar 2021 20:32:19 +0000 Subject: [PATCH 086/339] address clippy warnings --- awc/src/ws.rs | 2 +- examples/basic.rs | 2 +- examples/uds.rs | 2 +- src/middleware/condition.rs | 1 + src/middleware/err_handlers.rs | 2 ++ tests/test_httpserver.rs | 4 ++-- 6 files changed, 8 insertions(+), 5 deletions(-) diff --git a/awc/src/ws.rs b/awc/src/ws.rs index 1aa426ac7..f64e9e19a 100644 --- a/awc/src/ws.rs +++ b/awc/src/ws.rs @@ -382,7 +382,7 @@ impl WebsocketsRequest { if let Some(hdr_key) = head.headers.get(&header::SEC_WEBSOCKET_ACCEPT) { let encoded = ws::hash_key(key.as_ref()); - if hdr_key.as_bytes() != &encoded { + if hdr_key.as_bytes() != encoded { log::trace!( "Invalid challenge response: expected: {:?} received: {:?}", &encoded, diff --git a/examples/basic.rs b/examples/basic.rs index 99eef3ee1..796f002e8 100644 --- a/examples/basic.rs +++ b/examples/basic.rs @@ -30,7 +30,7 @@ async fn main() -> std::io::Result<()> { .service( web::resource("/resource2/index.html") .wrap(middleware::DefaultHeaders::new().header("X-Version-R2", "0.3")) - .default_service(web::route().to(|| HttpResponse::MethodNotAllowed())) + .default_service(web::route().to(HttpResponse::MethodNotAllowed)) .route(web::get().to(index_async)), ) .service(web::resource("/test1.html").to(|| async { "Test\r\n" })) diff --git a/examples/uds.rs b/examples/uds.rs index 096781984..1db252fef 100644 --- a/examples/uds.rs +++ b/examples/uds.rs @@ -34,7 +34,7 @@ async fn main() -> std::io::Result<()> { .service( web::resource("/resource2/index.html") .wrap(middleware::DefaultHeaders::new().header("X-Version-R2", "0.3")) - .default_service(web::route().to(|| HttpResponse::MethodNotAllowed())) + .default_service(web::route().to(HttpResponse::MethodNotAllowed)) .route(web::get().to(index_async)), ) .service(web::resource("/test1.html").to(|| async { "Test\r\n" })) diff --git a/src/middleware/condition.rs b/src/middleware/condition.rs index f0c344062..63a90c853 100644 --- a/src/middleware/condition.rs +++ b/src/middleware/condition.rs @@ -106,6 +106,7 @@ mod tests { HttpResponse, }; + #[allow(clippy::unnecessary_wraps)] fn render_500(mut res: ServiceResponse) -> Result> { res.response_mut() .headers_mut() diff --git a/src/middleware/err_handlers.rs b/src/middleware/err_handlers.rs index 4673ed4ce..06e88cefd 100644 --- a/src/middleware/err_handlers.rs +++ b/src/middleware/err_handlers.rs @@ -182,6 +182,7 @@ mod tests { use crate::test::{self, TestRequest}; use crate::HttpResponse; + #[allow(clippy::unnecessary_wraps)] fn render_500(mut res: ServiceResponse) -> Result> { res.response_mut() .headers_mut() @@ -205,6 +206,7 @@ mod tests { assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0001"); } + #[allow(clippy::unnecessary_wraps)] fn render_500_async( mut res: ServiceResponse, ) -> Result> { diff --git a/tests/test_httpserver.rs b/tests/test_httpserver.rs index 043159376..b6c0528dd 100644 --- a/tests/test_httpserver.rs +++ b/tests/test_httpserver.rs @@ -72,7 +72,7 @@ async fn test_start() { } #[cfg(feature = "openssl")] -fn ssl_acceptor() -> std::io::Result { +fn ssl_acceptor() -> SslAcceptorBuilder { use openssl::{ pkey::PKey, ssl::{SslAcceptor, SslMethod}, @@ -102,7 +102,7 @@ async fn test_start_ssl() { thread::spawn(move || { let sys = actix_rt::System::new(); - let builder = ssl_acceptor().unwrap(); + let builder = ssl_acceptor(); let srv = HttpServer::new(|| { App::new().service(web::resource("/").route(web::to(|req: HttpRequest| { From effacf8fc8ee8aedf1a5c809dbb1b066aad7a862 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Mon, 8 Mar 2021 20:51:50 +0000 Subject: [PATCH 087/339] fix ssl test --- actix-http/tests/test_server.rs | 4 ++-- tests/test_httpserver.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/actix-http/tests/test_server.rs b/actix-http/tests/test_server.rs index 910fa81f2..a4c1f92b5 100644 --- a/actix-http/tests/test_server.rs +++ b/actix-http/tests/test_server.rs @@ -126,7 +126,7 @@ async fn test_chunked_payload() { .take_payload() .map(|res| match res { Ok(pl) => pl, - Err(e) => panic!(format!("Error reading payload: {}", e)), + Err(e) => panic!("Error reading payload: {}", e), }) .fold(0usize, |acc, chunk| ready(acc + chunk.len())) .map(|req_size| { @@ -162,7 +162,7 @@ async fn test_chunked_payload() { let re = Regex::new(r"size=(\d+)").unwrap(); let size: usize = match re.captures(&data) { Some(caps) => caps.get(1).unwrap().as_str().parse().unwrap(), - None => panic!(format!("Failed to find size in HTTP Response: {}", data)), + None => panic!("Failed to find size in HTTP Response: {}", data), }; size }; diff --git a/tests/test_httpserver.rs b/tests/test_httpserver.rs index b6c0528dd..aa2b2ca74 100644 --- a/tests/test_httpserver.rs +++ b/tests/test_httpserver.rs @@ -89,7 +89,7 @@ fn ssl_acceptor() -> SslAcceptorBuilder { builder.set_certificate(&cert).unwrap(); builder.set_private_key(&key).unwrap(); - Ok(builder) + builder } #[actix_rt::test] From fc31b091e4e00067c52aba2801d79c25bb93a2c9 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Mon, 8 Mar 2021 23:07:40 +0000 Subject: [PATCH 088/339] prepare http release 3.0.0-beta.4 --- Cargo.toml | 2 +- actix-http-test/Cargo.toml | 2 +- actix-http/CHANGES.md | 5 ++++- actix-http/Cargo.toml | 2 +- actix-http/README.md | 4 ++-- actix-multipart/Cargo.toml | 2 +- actix-web-actors/Cargo.toml | 2 +- awc/Cargo.toml | 4 ++-- 8 files changed, 13 insertions(+), 10 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index bd758ab10..28bb432c7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -87,7 +87,7 @@ actix-utils = "3.0.0-beta.2" actix-tls = { version = "3.0.0-beta.4", default-features = false, optional = true } actix-web-codegen = "0.5.0-beta.1" -actix-http = "3.0.0-beta.3" +actix-http = "3.0.0-beta.4" awc = { version = "3.0.0-beta.2", default-features = false } ahash = "0.7" diff --git a/actix-http-test/Cargo.toml b/actix-http-test/Cargo.toml index 900960215..fd3edd9b8 100644 --- a/actix-http-test/Cargo.toml +++ b/actix-http-test/Cargo.toml @@ -58,4 +58,4 @@ optional = true [dev-dependencies] actix-web = { version = "4.0.0-beta.3", default-features = false, features = ["cookies"] } -actix-http = "3.0.0-beta.3" +actix-http = "3.0.0-beta.4" diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 14218289d..a5e69eda3 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -1,13 +1,16 @@ # Changes ## Unreleased - 2021-xx-xx + + +## 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] ### Removed -* re-export of `futures_channel::oneshot::Canceled` is removed from `error` mod. [#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 diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index c79ad11b2..d10e1ebdf 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-http" -version = "3.0.0-beta.3" +version = "3.0.0-beta.4" 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 881fbc8c5..53fedd40e 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.3)](https://docs.rs/actix-http/3.0.0-beta.3) +[![Documentation](https://docs.rs/actix-http/badge.svg?version=3.0.0-beta.4)](https://docs.rs/actix-http/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.svg)
-[![dependency status](https://deps.rs/crate/actix-http/3.0.0-beta.3/status.svg)](https://deps.rs/crate/actix-http/3.0.0-beta.3) +[![dependency status](https://deps.rs/crate/actix-http/3.0.0-beta.4/status.svg)](https://deps.rs/crate/actix-http/3.0.0-beta.4) [![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/Cargo.toml b/actix-multipart/Cargo.toml index fcbadde36..34817e087 100644 --- a/actix-multipart/Cargo.toml +++ b/actix-multipart/Cargo.toml @@ -29,6 +29,6 @@ twoway = "0.2" [dev-dependencies] actix-rt = "2.1" -actix-http = "3.0.0-beta.3" +actix-http = "3.0.0-beta.4" tokio = { version = "1", features = ["sync"] } tokio-stream = "0.1" diff --git a/actix-web-actors/Cargo.toml b/actix-web-actors/Cargo.toml index e0c4fc073..5ddbe53ce 100644 --- a/actix-web-actors/Cargo.toml +++ b/actix-web-actors/Cargo.toml @@ -18,7 +18,7 @@ path = "src/lib.rs" [dependencies] actix = { version = "0.11.0-beta.3", default-features = false } actix-codec = "0.4.0-beta.1" -actix-http = "3.0.0-beta.3" +actix-http = "3.0.0-beta.4" actix-web = { version = "4.0.0-beta.3", default-features = false } bytes = "1" diff --git a/awc/Cargo.toml b/awc/Cargo.toml index 761871ba2..166a08fd5 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -46,7 +46,7 @@ trust-dns = ["actix-http/trust-dns"] [dependencies] actix-codec = "0.4.0-beta.1" actix-service = "2.0.0-beta.4" -actix-http = "3.0.0-beta.3" +actix-http = "3.0.0-beta.4" actix-rt = { version = "2.1", default-features = false } base64 = "0.13" @@ -74,7 +74,7 @@ optional = true [dev-dependencies] actix-web = { version = "4.0.0-beta.3", features = ["openssl"] } -actix-http = { version = "3.0.0-beta.3", features = ["openssl"] } +actix-http = { version = "3.0.0-beta.4", features = ["openssl"] } actix-http-test = { version = "3.0.0-beta.2", features = ["openssl"] } actix-utils = "3.0.0-beta.1" actix-server = "2.0.0-beta.3" From 23b0e64199eaf4d10cdc1727c68d7ad3da298eb2 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Mon, 8 Mar 2021 23:15:53 +0000 Subject: [PATCH 089/339] prepare awc release 3.0.0-beta.3 --- Cargo.toml | 2 +- actix-http-test/Cargo.toml | 2 +- awc/CHANGES.md | 3 +++ awc/Cargo.toml | 2 +- awc/README.md | 4 ++-- 5 files changed, 8 insertions(+), 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 28bb432c7..48606f6ed 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -88,7 +88,7 @@ actix-tls = { version = "3.0.0-beta.4", default-features = false, optional = tru actix-web-codegen = "0.5.0-beta.1" actix-http = "3.0.0-beta.4" -awc = { version = "3.0.0-beta.2", default-features = false } +awc = { version = "3.0.0-beta.3", default-features = false } ahash = "0.7" bytes = "1" diff --git a/actix-http-test/Cargo.toml b/actix-http-test/Cargo.toml index fd3edd9b8..ca51bd325 100644 --- a/actix-http-test/Cargo.toml +++ b/actix-http-test/Cargo.toml @@ -35,7 +35,7 @@ actix-tls = "3.0.0-beta.4" actix-utils = "3.0.0-beta.2" actix-rt = "2.1" actix-server = "2.0.0-beta.3" -awc = { version = "3.0.0-beta.2", default-features = false } +awc = { version = "3.0.0-beta.3", default-features = false } base64 = "0.13" bytes = "1" diff --git a/awc/CHANGES.md b/awc/CHANGES.md index b11d64628..bf7bffc49 100644 --- a/awc/CHANGES.md +++ b/awc/CHANGES.md @@ -1,6 +1,9 @@ # Changes ## Unreleased - 2021-xx-xx + + +## 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] diff --git a/awc/Cargo.toml b/awc/Cargo.toml index 166a08fd5..c55465b5d 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "awc" -version = "3.0.0-beta.2" +version = "3.0.0-beta.3" authors = ["Nikolay Kim "] description = "Async HTTP and WebSocket client library built on the Actix ecosystem" readme = "README.md" diff --git a/awc/README.md b/awc/README.md index 1f6e3b8fb..ec3984d60 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.2)](https://docs.rs/awc/3.0.0-beta.2) +[![Documentation](https://docs.rs/awc/badge.svg?version=3.0.0-beta.3)](https://docs.rs/awc/3.0.0-beta.3) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/awc) -[![Dependency Status](https://deps.rs/crate/awc/3.0.0-beta.2/status.svg)](https://deps.rs/crate/awc/3.0.0-beta.2) +[![Dependency Status](https://deps.rs/crate/awc/3.0.0-beta.3/status.svg)](https://deps.rs/crate/awc/3.0.0-beta.3) [![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 c4e565121569880e6e6b60e8d755ca3fa637f230 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Mon, 8 Mar 2021 23:49:12 +0000 Subject: [PATCH 090/339] update docs --- docs/graphs/web-focus.dot | 53 +++++++++++++++++++++++---------------- 1 file changed, 31 insertions(+), 22 deletions(-) diff --git a/docs/graphs/web-focus.dot b/docs/graphs/web-focus.dot index ec0f7a946..2c6e2779b 100644 --- a/docs/graphs/web-focus.dot +++ b/docs/graphs/web-focus.dot @@ -1,35 +1,44 @@ digraph { subgraph cluster_web { - label="actix/actix-web" + label="actix/web" "awc" - "actix-web" - "actix-files" - "actix-http" - "actix-multipart" - "actix-web-actors" - "actix-web-codegen" - "actix-http-test" + "web" + "files" + "http" + "multipart" + "web-actors" + "web-codegen" + "http-test" + + { rank=same; "multipart" "web-actors" "http-test" }; + { rank=same; "files" "awc" "web" }; + { rank=same; "web-codegen" "http" }; } - "actix-web" -> { "actix-codec" "actix-service" "actix-utils" "actix-router" "actix-rt" "actix-server" "macros" "threadpool" "actix-tls" "actix-web-codegen" "actix-http" "awc" } - "awc" -> { "actix-codec" "actix-service" "actix-http" "actix-rt" } - "actix-web-actors" -> { "actix" "actix-web" "actix-http" "actix-codec" } - "actix-multipart" -> { "actix-web" "actix-service" "actix-utils" } - "actix-http" -> { "actix-service" "actix-codec" "actix-tls" "actix-utils" "actix-rt" "threadpool" } - "actix-http" -> { "actix-tls" }[color=blue] // optional - "actix-files" -> { "actix-web" } - "actix-http-test" -> { "actix-service" "actix-codec" "actix-tls" "actix-utils" "actix-rt" "actix-server" "awc" } + "web" -> { "codec" "service" "utils" "router" "rt" "server" "macros" "web-codegen" "http" "awc" } + "web" -> { "tls" }[color=blue] // optional + "awc" -> { "codec" "service" "http" "rt" } + "web-actors" -> { "actix" "web" "http" "codec" } + "multipart" -> { "web" "service" "utils" } + "http" -> { "service" "codec" "utils" "rt" } + "http" -> { "tls" }[color=blue] // optional + "files" -> { "web" } + "http-test" -> { "service" "codec" "utils" "rt" "server" "awc" } + "http-test" -> { "tls" }[color=blue] // optional // net - "actix-utils" -> { "actix-service" "actix-rt" "actix-codec" } - "actix-tracing" -> { "actix-service" } - "actix-tls" -> { "actix-service" "actix-codec" "actix-utils" } - "actix-server" -> { "actix-service" "actix-rt" "actix-codec" "actix-utils" } - "actix-rt" -> { "macros" "threadpool" } + "utils" -> { "service" "rt" "codec" } + "tracing" -> { "service" } + "tls" -> { "service" "codec" "utils" } + "server" -> { "service" "rt" "codec" "utils" } + "rt" -> { "macros" } + + { rank=same; "utils" "codec" }; + { rank=same; "rt" "macros" "service" "router" }; // actix - "actix" -> { "actix-rt" } + "actix" -> { "rt" } } From b7c406637d7566547891843f45fd57991336b31f Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Tue, 9 Mar 2021 23:27:38 +0000 Subject: [PATCH 091/339] prepare actix-web-codegen release 0.5.0-beta.2 --- Cargo.toml | 2 +- actix-web-codegen/CHANGES.md | 3 +++ actix-web-codegen/Cargo.toml | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 48606f6ed..d002ce676 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -86,7 +86,7 @@ actix-service = "2.0.0-beta.4" actix-utils = "3.0.0-beta.2" actix-tls = { version = "3.0.0-beta.4", default-features = false, optional = true } -actix-web-codegen = "0.5.0-beta.1" +actix-web-codegen = "0.5.0-beta.2" actix-http = "3.0.0-beta.4" awc = { version = "3.0.0-beta.3", default-features = false } diff --git a/actix-web-codegen/CHANGES.md b/actix-web-codegen/CHANGES.md index 7c6543d49..f2c9b44b5 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.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 e43e91e22..e74e0ed85 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.1" +version = "0.5.0-beta.2" description = "Routing and runtime macros for Actix Web" readme = "README.md" homepage = "https://actix.rs" From 4b46351d36454224e5d33cde001114ec54c36aa2 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Tue, 9 Mar 2021 23:31:44 +0000 Subject: [PATCH 092/339] prepare actix-web release 4.0.0-beta.4 --- CHANGES.md | 7 +++++-- Cargo.toml | 2 +- README.md | 4 ++-- actix-files/Cargo.toml | 4 ++-- actix-http-test/Cargo.toml | 2 +- actix-multipart/Cargo.toml | 2 +- actix-web-actors/Cargo.toml | 2 +- actix-web-codegen/Cargo.toml | 2 +- actix-web-codegen/README.md | 4 ++-- awc/Cargo.toml | 2 +- 10 files changed, 17 insertions(+), 14 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 819237ab7..a6d4487dc 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,10 +1,13 @@ # Changes ## Unreleased - 2021-xx-xx + + +## 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] +* `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 diff --git a/Cargo.toml b/Cargo.toml index d002ce676..8477c8ede 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-web" -version = "4.0.0-beta.3" +version = "4.0.0-beta.4" authors = ["Nikolay Kim "] description = "Actix Web is a powerful, pragmatic, and extremely fast web framework for Rust" readme = "README.md" diff --git a/README.md b/README.md index b3448140a..64fd7d08d 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.2)](https://docs.rs/actix-web/4.0.0-beta.2) +[![Documentation](https://docs.rs/actix-web/badge.svg?version=4.0.0-beta.4)](https://docs.rs/actix-web/4.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-web.svg) -[![Dependency Status](https://deps.rs/crate/actix-web/4.0.0-beta.2/status.svg)](https://deps.rs/crate/actix-web/4.0.0-beta.2) +[![Dependency Status](https://deps.rs/crate/actix-web/4.0.0-beta.4/status.svg)](https://deps.rs/crate/actix-web/4.0.0-beta.4)
[![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 8f1a9ec5a..80b0310bc 100644 --- a/actix-files/Cargo.toml +++ b/actix-files/Cargo.toml @@ -17,7 +17,7 @@ name = "actix_files" path = "src/lib.rs" [dependencies] -actix-web = { version = "4.0.0-beta.3", default-features = false } +actix-web = { version = "4.0.0-beta.4", default-features = false } actix-service = "2.0.0-beta.4" askama_escape = "0.10" @@ -34,4 +34,4 @@ percent-encoding = "2.1" [dev-dependencies] actix-rt = "2.1" -actix-web = "4.0.0-beta.3" +actix-web = "4.0.0-beta.4" diff --git a/actix-http-test/Cargo.toml b/actix-http-test/Cargo.toml index ca51bd325..855f53fe9 100644 --- a/actix-http-test/Cargo.toml +++ b/actix-http-test/Cargo.toml @@ -57,5 +57,5 @@ features = ["vendored"] optional = true [dev-dependencies] -actix-web = { version = "4.0.0-beta.3", default-features = false, features = ["cookies"] } +actix-web = { version = "4.0.0-beta.4", default-features = false, features = ["cookies"] } actix-http = "3.0.0-beta.4" diff --git a/actix-multipart/Cargo.toml b/actix-multipart/Cargo.toml index 34817e087..23c41a0f3 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.3", default-features = false } +actix-web = { version = "4.0.0-beta.4", default-features = false } actix-utils = "3.0.0-beta.2" bytes = "1" diff --git a/actix-web-actors/Cargo.toml b/actix-web-actors/Cargo.toml index 5ddbe53ce..e305cd750 100644 --- a/actix-web-actors/Cargo.toml +++ b/actix-web-actors/Cargo.toml @@ -19,7 +19,7 @@ path = "src/lib.rs" actix = { version = "0.11.0-beta.3", default-features = false } actix-codec = "0.4.0-beta.1" actix-http = "3.0.0-beta.4" -actix-web = { version = "4.0.0-beta.3", default-features = false } +actix-web = { version = "4.0.0-beta.4", default-features = false } bytes = "1" bytestring = "1" diff --git a/actix-web-codegen/Cargo.toml b/actix-web-codegen/Cargo.toml index e74e0ed85..d8a189565 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.1" -actix-web = "4.0.0-beta.3" +actix-web = "4.0.0-beta.4" futures-util = { version = "0.3.7", default-features = false } trybuild = "1" rustversion = "1" diff --git a/actix-web-codegen/README.md b/actix-web-codegen/README.md index 5820bb443..9552d4b56 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.1)](https://docs.rs/actix-web-codegen/0.5.0-beta.1) +[![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) [![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.1/status.svg)](https://deps.rs/crate/actix-web-codegen/0.5.0-beta.1) +[![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) [![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/Cargo.toml b/awc/Cargo.toml index c55465b5d..bca3ada4a 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -73,7 +73,7 @@ features = ["vendored"] optional = true [dev-dependencies] -actix-web = { version = "4.0.0-beta.3", features = ["openssl"] } +actix-web = { version = "4.0.0-beta.4", features = ["openssl"] } actix-http = { version = "3.0.0-beta.4", features = ["openssl"] } actix-http-test = { version = "3.0.0-beta.2", features = ["openssl"] } actix-utils = "3.0.0-beta.1" From 18c3783a1c0bd74cac3e764fb264c4bf53986895 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Tue, 9 Mar 2021 23:35:42 +0000 Subject: [PATCH 093/339] prepare actix-http-test release 3.0.0-beta.3 --- actix-http-test/CHANGES.md | 4 ++++ actix-http-test/Cargo.toml | 2 +- actix-http/Cargo.toml | 2 +- awc/Cargo.toml | 2 +- 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/actix-http-test/CHANGES.md b/actix-http-test/CHANGES.md index 2f47d700d..d6a2cdd9b 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.3 - 2021-03-09 +* No notable changes. + + ## 3.0.0-beta.2 - 2021-02-10 * No notable changes. diff --git a/actix-http-test/Cargo.toml b/actix-http-test/Cargo.toml index 855f53fe9..69b2a3335 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.2" +version = "3.0.0-beta.3" authors = ["Nikolay Kim "] description = "Various helpers for Actix applications to use during testing" readme = "README.md" diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index d10e1ebdf..edcc7efd5 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -89,7 +89,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.2", features = ["openssl"] } +actix-http-test = { version = "3.0.0-beta.3", features = ["openssl"] } actix-tls = { version = "3.0.0-beta.4", features = ["openssl"] } criterion = "0.3" env_logger = "0.8" diff --git a/awc/Cargo.toml b/awc/Cargo.toml index bca3ada4a..981b93a52 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -75,7 +75,7 @@ optional = true [dev-dependencies] actix-web = { version = "4.0.0-beta.4", features = ["openssl"] } actix-http = { version = "3.0.0-beta.4", features = ["openssl"] } -actix-http-test = { version = "3.0.0-beta.2", features = ["openssl"] } +actix-http-test = { version = "3.0.0-beta.3", features = ["openssl"] } actix-utils = "3.0.0-beta.1" actix-server = "2.0.0-beta.3" actix-tls = { version = "3.0.0-beta.4", features = ["openssl", "rustls"] } From 3451d6874f3efea0ae5dfcf90643c9450686093f Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Tue, 9 Mar 2021 23:39:40 +0000 Subject: [PATCH 094/339] prepare actix-files release 0.6.0-beta.3 --- actix-files/CHANGES.md | 4 ++++ actix-files/Cargo.toml | 2 +- actix-files/README.md | 4 ++-- actix-http-test/README.md | 4 ++-- actix-multipart/README.md | 1 - 5 files changed, 9 insertions(+), 6 deletions(-) diff --git a/actix-files/CHANGES.md b/actix-files/CHANGES.md index 6e2a241ac..c035d5afe 100644 --- a/actix-files/CHANGES.md +++ b/actix-files/CHANGES.md @@ -3,6 +3,10 @@ ## Unreleased - 2021-xx-xx +## 0.6.0-beta.3 - 2021-03-09 +* 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] diff --git a/actix-files/Cargo.toml b/actix-files/Cargo.toml index 80b0310bc..49cd6966c 100644 --- a/actix-files/Cargo.toml +++ b/actix-files/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-files" -version = "0.6.0-beta.2" +version = "0.6.0-beta.3" authors = ["Nikolay Kim "] description = "Static file serving for Actix Web" readme = "README.md" diff --git a/actix-files/README.md b/actix-files/README.md index a4f0445aa..c7b7424ec 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.5.0)](https://docs.rs/actix-files/0.5.0) +[![Documentation](https://docs.rs/actix-files/badge.svg?version=0.6.0-beta.3)](https://docs.rs/actix-files/0.6.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-files.svg)
-[![dependency status](https://deps.rs/crate/actix-files/0.5.0/status.svg)](https://deps.rs/crate/actix-files/0.5.0) +[![dependency status](https://deps.rs/crate/actix-files/0.6.0-beta.3/status.svg)](https://deps.rs/crate/actix-files/0.6.0-beta.3) [![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/README.md b/actix-http-test/README.md index 66f15979d..8cec94808 100644 --- a/actix-http-test/README.md +++ b/actix-http-test/README.md @@ -3,9 +3,9 @@ > 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=2.1.0)](https://docs.rs/actix-http-test/2.1.0) +[![Documentation](https://docs.rs/actix-http-test/badge.svg?version=3.0.0-beta.3)](https://docs.rs/actix-http-test/3.0.0-beta.3) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-http-test) -[![Dependency Status](https://deps.rs/crate/actix-http-test/2.1.0/status.svg)](https://deps.rs/crate/actix-http-test/2.1.0) +[![Dependency Status](https://deps.rs/crate/actix-http-test/3.0.0-beta.3/status.svg)](https://deps.rs/crate/actix-http-test/3.0.0-beta.3) [![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 diff --git a/actix-multipart/README.md b/actix-multipart/README.md index defcf7828..d39d8496d 100644 --- a/actix-multipart/README.md +++ b/actix-multipart/README.md @@ -9,7 +9,6 @@
[![dependency status](https://deps.rs/crate/actix-multipart/0.4.0-beta.2/status.svg)](https://deps.rs/crate/actix-multipart/0.4.0-beta.2) [![Download](https://img.shields.io/crates/d/actix-multipart.svg)](https://crates.io/crates/actix-multipart) -[![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) ## Documentation & Resources From 5e9a3eb6ae801a8b1370d917926ab20df713d421 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Tue, 9 Mar 2021 23:40:50 +0000 Subject: [PATCH 095/339] prepare actix-multipart release 0.4.0-beta.3 --- 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 2142ebf4b..ce5bd57d7 100644 --- a/actix-multipart/CHANGES.md +++ b/actix-multipart/CHANGES.md @@ -3,6 +3,10 @@ ## Unreleased - 2021-xx-xx +## 0.4.0-beta.3 - 2021-03-09 +* No notable changes. + + ## 0.4.0-beta.2 - 2021-02-10 * No notable changes. diff --git a/actix-multipart/Cargo.toml b/actix-multipart/Cargo.toml index 23c41a0f3..9a3ea7bb5 100644 --- a/actix-multipart/Cargo.toml +++ b/actix-multipart/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-multipart" -version = "0.4.0-beta.2" +version = "0.4.0-beta.3" authors = ["Nikolay Kim "] description = "Multipart form support for Actix Web" readme = "README.md" diff --git a/actix-multipart/README.md b/actix-multipart/README.md index d39d8496d..e4a54ac47 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.2)](https://docs.rs/actix-multipart/0.4.0-beta.2) +[![Documentation](https://docs.rs/actix-multipart/badge.svg?version=0.4.0-beta.3)](https://docs.rs/actix-multipart/0.4.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) ![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.2/status.svg)](https://deps.rs/crate/actix-multipart/0.4.0-beta.2) +[![dependency status](https://deps.rs/crate/actix-multipart/0.4.0-beta.3/status.svg)](https://deps.rs/crate/actix-multipart/0.4.0-beta.3) [![Download](https://img.shields.io/crates/d/actix-multipart.svg)](https://crates.io/crates/actix-multipart) ## Documentation & Resources From b62da7e86b97ebe3669d3ba4dbc0628a2c90efc7 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Tue, 9 Mar 2021 23:44:26 +0000 Subject: [PATCH 096/339] prepare actix-web-actors release 4.0.0-beta.3 --- 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 acd9ceada..80eef08c1 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.3 - 2021-03-09 +* No notable changes. + + ## 4.0.0-beta.2 - 2021-02-10 * No notable changes. diff --git a/actix-web-actors/Cargo.toml b/actix-web-actors/Cargo.toml index e305cd750..77663540c 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.2" +version = "4.0.0-beta.3" authors = ["Nikolay Kim "] description = "Actix actors support for Actix Web" readme = "README.md" diff --git a/actix-web-actors/README.md b/actix-web-actors/README.md index c9b588153..6d9d573d7 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=0.5.0)](https://docs.rs/actix-web-actors/0.5.0) +[![Documentation](https://docs.rs/actix-web-actors/badge.svg?version=4.0.0-beta.3)](https://docs.rs/actix-web-actors/4.0.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-actors.svg)
-[![dependency status](https://deps.rs/crate/actix-web-actors/0.5.0/status.svg)](https://deps.rs/crate/actix-web-actors/0.5.0) +[![dependency status](https://deps.rs/crate/actix-web-actors/4.0.0-beta.3/status.svg)](https://deps.rs/crate/actix-web-actors/4.0.0-beta.3) [![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) From d0c1f1a84cb0d1fdf86ccf715a30f7989c988a51 Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Tue, 9 Mar 2021 17:31:50 -0800 Subject: [PATCH 097/339] remove actix_http::client::pool::Protocol (#2061) --- actix-http/src/client/connector.rs | 24 +++++------------------- actix-http/src/client/mod.rs | 2 +- actix-http/src/client/pool.rs | 23 ++++------------------- 3 files changed, 10 insertions(+), 39 deletions(-) diff --git a/actix-http/src/client/connector.rs b/actix-http/src/client/connector.rs index 1a926fd6c..28c865ffa 100644 --- a/actix-http/src/client/connector.rs +++ b/actix-http/src/client/connector.rs @@ -20,8 +20,9 @@ use http::Uri; use super::config::ConnectorConfig; use super::connection::{Connection, EitherIoConnection}; use super::error::ConnectError; -use super::pool::{ConnectionPool, Protocol}; +use super::pool::ConnectionPool; use super::Connect; +use super::Protocol; #[cfg(feature = "openssl")] use actix_tls::connect::ssl::openssl::SslConnector as OpensslConnector; @@ -148,7 +149,7 @@ where + 'static, { /// Connection timeout, i.e. max time to connect to remote host including dns name resolution. - /// Set to 1 second by default. + /// Set to 5 second by default. pub fn timeout(mut self, timeout: Duration) -> Self { self.config.timeout = timeout; self @@ -162,6 +163,7 @@ where } #[cfg(feature = "rustls")] + /// Use custom `SslConnector` instance. pub fn rustls(mut self, connector: Arc) -> Self { self.ssl = SslConnector::Rustls(connector); self @@ -254,8 +256,7 @@ where /// its combinator chain. pub fn finish( self, - ) -> impl Service + Clone - { + ) -> impl Service { let local_address = self.config.local_address; let timeout = self.config.timeout; @@ -392,21 +393,6 @@ where tls_pool: Option>, } -impl Clone for InnerConnector -where - S1: Service + 'static, - S2: Service + 'static, - Io1: AsyncRead + AsyncWrite + Unpin + 'static, - Io2: AsyncRead + AsyncWrite + Unpin + 'static, -{ - fn clone(&self) -> Self { - InnerConnector { - tcp_pool: self.tcp_pool.clone(), - tls_pool: self.tls_pool.as_ref().cloned(), - } - } -} - impl Service for InnerConnector where S1: Service + 'static, diff --git a/actix-http/src/client/mod.rs b/actix-http/src/client/mod.rs index 5f5e57edb..c6f998c2a 100644 --- a/actix-http/src/client/mod.rs +++ b/actix-http/src/client/mod.rs @@ -17,7 +17,7 @@ pub use actix_tls::connect::{ pub use self::connection::Connection; pub use self::connector::Connector; pub use self::error::{ConnectError, FreezeRequestError, InvalidUrl, SendRequestError}; -pub use self::pool::Protocol; +pub use crate::Protocol; #[derive(Clone)] pub struct Connect { diff --git a/actix-http/src/client/pool.rs b/actix-http/src/client/pool.rs index 3800696fa..b5b4a30d3 100644 --- a/actix-http/src/client/pool.rs +++ b/actix-http/src/client/pool.rs @@ -25,13 +25,7 @@ use super::connection::{ConnectionType, H2Connection, IoConnection}; use super::error::ConnectError; use super::h2proto::handshake; use super::Connect; - -#[derive(Clone, Copy, PartialEq)] -/// Protocol version -pub enum Protocol { - Http1, - Http2, -} +use super::Protocol; #[derive(Hash, Eq, PartialEq, Clone, Debug)] pub(crate) struct Key { @@ -148,18 +142,6 @@ where } } -impl Clone for ConnectionPool -where - Io: AsyncWrite + Unpin + 'static, -{ - fn clone(&self) -> Self { - Self { - connector: self.connector.clone(), - inner: self.inner.clone(), - } - } -} - impl Service for ConnectionPool where S: Service + 'static, @@ -243,6 +225,9 @@ where None => { let (io, proto) = connector.call(req).await?; + // TODO: remove when http3 is added in support. + assert!(proto != Protocol::Http3); + if proto == Protocol::Http1 { Ok(IoConnection::new( ConnectionType::H1(io), From a2b0e866325b1492c2daa9fd0593ce5d91e40af8 Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Wed, 10 Mar 2021 15:57:32 -0800 Subject: [PATCH 098/339] simplify connector generic type (#2063) --- actix-http/CHANGES.md | 4 ++++ actix-http/src/client/connector.rs | 22 +++++++++++----------- awc/src/builder.rs | 11 +++++------ awc/src/lib.rs | 1 - 4 files changed, 20 insertions(+), 18 deletions(-) diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index a5e69eda3..e96c22274 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -1,6 +1,10 @@ # Changes ## Unreleased - 2021-xx-xx +### Chaged +* `client::Connector` type now only have one generic type for `actix_service::Service`. [#2063] + +[#2063]: https://github.com/actix/actix-web/pull/2063 ## 3.0.0-beta.4 - 2021-03-08 diff --git a/actix-http/src/client/connector.rs b/actix-http/src/client/connector.rs index 28c865ffa..ebdbbb6eb 100644 --- a/actix-http/src/client/connector.rs +++ b/actix-http/src/client/connector.rs @@ -1,7 +1,6 @@ use std::{ fmt, future::Future, - marker::PhantomData, net::IpAddr, pin::Pin, task::{Context, Poll}, @@ -15,6 +14,7 @@ use actix_tls::connect::{ new_connector, Connect as TcpConnect, Connection as TcpConnection, Resolver, }; use actix_utils::timeout::{TimeoutError, TimeoutService}; +use futures_core::ready; use http::Uri; use super::config::ConnectorConfig; @@ -54,18 +54,17 @@ type SslConnector = (); /// .timeout(Duration::from_secs(5)) /// .finish(); /// ``` -pub struct Connector { +pub struct Connector { connector: T, config: ConnectorConfig, #[allow(dead_code)] ssl: SslConnector, - _phantom: PhantomData, } pub trait Io: AsyncRead + AsyncWrite + Unpin {} impl Io for T {} -impl Connector<(), ()> { +impl Connector<()> { #[allow(clippy::new_ret_no_self, clippy::let_unit_value)] pub fn new() -> Connector< impl Service< @@ -73,13 +72,11 @@ impl Connector<(), ()> { Response = TcpConnection, Error = actix_tls::connect::ConnectError, > + Clone, - TcpStream, > { Connector { ssl: Self::build_ssl(vec![b"h2".to_vec(), b"http/1.1".to_vec()]), connector: new_connector(resolver::resolver()), config: ConnectorConfig::default(), - _phantom: PhantomData, } } @@ -118,9 +115,9 @@ impl Connector<(), ()> { fn build_ssl(_: Vec>) -> SslConnector {} } -impl Connector { +impl Connector { /// Use custom connector. - pub fn connector(self, connector: T1) -> Connector + pub fn connector(self, connector: T1) -> Connector where U1: AsyncRead + AsyncWrite + Unpin + fmt::Debug, T1: Service< @@ -133,12 +130,11 @@ impl Connector { connector, config: self.config, ssl: self.ssl, - _phantom: PhantomData, } } } -impl Connector +impl Connector where U: AsyncRead + AsyncWrite + Unpin + fmt::Debug + 'static, T: Service< @@ -405,7 +401,11 @@ where type Future = InnerConnectorResponse; fn poll_ready(&self, cx: &mut Context<'_>) -> Poll> { - self.tcp_pool.poll_ready(cx) + ready!(self.tcp_pool.poll_ready(cx))?; + if let Some(ref tls_pool) = self.tls_pool { + ready!(tls_pool.poll_ready(cx))?; + } + Poll::Ready(Ok(())) } fn call(&self, req: Connect) -> Self::Future { diff --git a/awc/src/builder.rs b/awc/src/builder.rs index 72a0f4f04..33f43ab93 100644 --- a/awc/src/builder.rs +++ b/awc/src/builder.rs @@ -21,14 +21,14 @@ use crate::{Client, ClientConfig, ConnectRequest, ConnectResponse, ConnectorServ /// /// This type can be used to construct an instance of `Client` through a /// builder-like pattern. -pub struct ClientBuilder { +pub struct ClientBuilder { default_headers: bool, max_http_version: Option, stream_window_size: Option, conn_window_size: Option, headers: HeaderMap, timeout: Option, - connector: Connector, + connector: Connector, middleware: M, local_address: Option, max_redirects: u8, @@ -42,7 +42,6 @@ impl ClientBuilder { Response = TcpConnection, Error = TcpConnectError, > + Clone, - TcpStream, (), > { ClientBuilder { @@ -60,7 +59,7 @@ impl ClientBuilder { } } -impl ClientBuilder +impl ClientBuilder where S: Service, Response = TcpConnection, Error = TcpConnectError> + Clone @@ -68,7 +67,7 @@ where Io: AsyncRead + AsyncWrite + Unpin + fmt::Debug + 'static, { /// Use custom connector service. - pub fn connector(self, connector: Connector) -> ClientBuilder + pub fn connector(self, connector: Connector) -> ClientBuilder where S1: Service< TcpConnect, @@ -213,7 +212,7 @@ where pub fn wrap( self, mw: M1, - ) -> ClientBuilder> + ) -> ClientBuilder> where M: Transform, M1: Transform, diff --git a/awc/src/lib.rs b/awc/src/lib.rs index 4cd1d5bb2..77e08fbbd 100644 --- a/awc/src/lib.rs +++ b/awc/src/lib.rs @@ -175,7 +175,6 @@ impl Client { Response = TcpConnection, Error = TcpConnectError, > + Clone, - TcpStream, > { ClientBuilder::new() } From 909ef0344b0fb5a30ead66420a89358e867b1933 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Thu, 11 Mar 2021 00:43:03 +0000 Subject: [PATCH 099/339] document client mod removal closes #2064 --- CHANGES.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index a6d4487dc..adaa273e8 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,8 @@ # Changes ## Unreleased - 2021-xx-xx +* The `client` mod was removed. Clients should now use `awc` directly. + [871ca5e4](https://github.com/actix/actix-web/commit/871ca5e4ae2bdc22d1ea02701c2992fa8d04aed7) ## 4.0.0-beta.4 - 2021-03-09 From 22dcc311937967b9da972b6304cb97d5684d2cae Mon Sep 17 00:00:00 2001 From: Ilya Averyanov Date: Thu, 11 Mar 2021 17:12:42 +0300 Subject: [PATCH 100/339] Fix logger middleware properly escape %% (#2067) --- CHANGES.md | 6 ++++++ src/middleware/logger.rs | 34 +++++++++++++++++++++++++++++++++- 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index adaa273e8..ef9ee900a 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,9 +1,15 @@ # Changes ## Unreleased - 2021-xx-xx +### 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) +[#2067]: https://github.com/actix/actix-web/pull/2067 + ## 4.0.0-beta.4 - 2021-03-09 ### Changed diff --git a/src/middleware/logger.rs b/src/middleware/logger.rs index 5b5b5577c..cedf51197 100644 --- a/src/middleware/logger.rs +++ b/src/middleware/logger.rs @@ -363,7 +363,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]|xi)|[%atPrUsbTD]?)").unwrap(); let mut idx = 0; let mut results = Vec::new(); @@ -639,6 +639,38 @@ mod tests { let _res = srv.call(req).await.unwrap(); } + #[actix_rt::test] + async fn test_escape_percent() { + let mut format = Format::new("%%{r}a"); + + let req = TestRequest::default() + .insert_header(( + header::FORWARDED, + header::HeaderValue::from_static("for=192.0.2.60;proto=http;by=203.0.113.43"), + )) + .to_srv_request(); + + let now = OffsetDateTime::now_utc(); + for unit in &mut format.0 { + unit.render_request(now, &req); + } + + let resp = HttpResponse::build(StatusCode::OK).force_close().finish(); + for unit in &mut format.0 { + unit.render_response(&resp); + } + + let entry_time = OffsetDateTime::now_utc(); + let render = |fmt: &mut fmt::Formatter<'_>| { + for unit in &format.0 { + unit.render(fmt, 1024, entry_time)?; + } + Ok(()) + }; + let s = format!("{}", FormatDisplay(&render)); + assert_eq!(s, "%{r}a"); + } + #[actix_rt::test] async fn test_url_path() { let mut format = Format::new("%T %U"); From 515d0e3fb45d2936fad00daf0ab709ab2cc62103 Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Sat, 13 Mar 2021 14:20:18 -0800 Subject: [PATCH 101/339] change behavior of default upgrade handler (#2071) --- actix-http/src/h1/dispatcher.rs | 39 ++++++++++++++++++-------- actix-http/src/h1/upgrade.rs | 8 +++--- src/server.rs | 49 +++++++++++++++++---------------- 3 files changed, 57 insertions(+), 39 deletions(-) diff --git a/actix-http/src/h1/dispatcher.rs b/actix-http/src/h1/dispatcher.rs index 6df579c0a..e5989e5ee 100644 --- a/actix-http/src/h1/dispatcher.rs +++ b/actix-http/src/h1/dispatcher.rs @@ -662,7 +662,7 @@ where // got timeout during shutdown, drop connection if this.flags.contains(Flags::SHUTDOWN) { return Err(DispatchError::DisconnectTimeout); - // exceed deadline. check for any outstanding tasks + // 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() { @@ -695,15 +695,15 @@ where this.flags.insert(Flags::STARTED | Flags::SHUTDOWN); this.state.set(State::None); } - // still have unfinished task. try to reset and register keep-alive. + // 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. + // 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); @@ -951,14 +951,15 @@ mod tests { use std::str; use actix_service::fn_service; - use futures_util::future::{lazy, ready}; + use futures_util::future::{lazy, ready, Ready}; use super::*; - use crate::test::TestBuffer; - use crate::{error::Error, KeepAlive}; use crate::{ + error::Error, h1::{ExpectHandler, UpgradeHandler}, - test::TestSeqBuffer, + http::Method, + test::{TestBuffer, TestSeqBuffer}, + HttpMessage, KeepAlive, }; fn find_slice(haystack: &[u8], needle: &[u8], from: usize) -> Option { @@ -1282,14 +1283,30 @@ mod tests { #[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(UpgradeHandler)); + let services = HttpFlow::new(ok_service(), ExpectHandler, Some(TestUpgrade)); - let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new( + let h1 = Dispatcher::<_, _, _, _, TestUpgrade>::new( buf.clone(), cfg, services, diff --git a/actix-http/src/h1/upgrade.rs b/actix-http/src/h1/upgrade.rs index 5e24d84e3..fbfbc83c1 100644 --- a/actix-http/src/h1/upgrade.rs +++ b/actix-http/src/h1/upgrade.rs @@ -2,7 +2,7 @@ use std::task::Poll; use actix_codec::Framed; use actix_service::{Service, ServiceFactory}; -use futures_util::future::{ready, Ready}; +use futures_core::future::LocalBoxFuture; use crate::error::Error; use crate::h1::Codec; @@ -16,7 +16,7 @@ impl ServiceFactory<(Request, Framed)> for UpgradeHandler { type Config = (); type Service = UpgradeHandler; type InitError = Error; - type Future = Ready>; + type Future = LocalBoxFuture<'static, Result>; fn new_service(&self, _: ()) -> Self::Future { unimplemented!() @@ -26,11 +26,11 @@ impl ServiceFactory<(Request, Framed)> for UpgradeHandler { impl Service<(Request, Framed)> for UpgradeHandler { type Response = (); type Error = Error; - type Future = Ready>; + type Future = LocalBoxFuture<'static, Result>; actix_service::always_ready!(); fn call(&self, _: (Request, Framed)) -> Self::Future { - ready(Ok(())) + unimplemented!() } } diff --git a/src/server.rs b/src/server.rs index d69d6570d..99355593a 100644 --- a/src/server.rs +++ b/src/server.rs @@ -12,13 +12,6 @@ use actix_http::{ use actix_server::{Server, ServerBuilder}; use actix_service::{map_config, IntoServiceFactory, Service, ServiceFactory}; -#[cfg(unix)] -use actix_http::Protocol; -#[cfg(unix)] -use actix_service::pipeline_factory; -#[cfg(unix)] -use futures_util::future::ok; - #[cfg(feature = "openssl")] use actix_tls::accept::openssl::{AlpnError, SslAcceptor, SslAcceptorBuilder}; #[cfg(feature = "rustls")] @@ -489,7 +482,9 @@ where #[cfg(unix)] /// Start listening for unix domain (UDS) connections on existing listener. pub fn listen_uds(mut self, lst: std::os::unix::net::UnixListener) -> io::Result { + use actix_http::Protocol; use actix_rt::net::UnixStream; + use actix_service::pipeline_factory; let cfg = self.config.clone(); let factory = self.factory.clone(); @@ -511,19 +506,22 @@ where c.host.clone().unwrap_or_else(|| format!("{}", socket_addr)), ); - pipeline_factory(|io: UnixStream| ok((io, Protocol::Http1, None))).and_then({ - let svc = HttpService::build() - .keep_alive(c.keep_alive) - .client_timeout(c.client_timeout); + pipeline_factory(|io: UnixStream| async { Ok((io, Protocol::Http1, None)) }) + .and_then({ + let 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 - }; + 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 + }; - svc.finish(map_config(factory(), move |_| config.clone())) - }) + svc.finish(map_config(factory(), move |_| config.clone())) + }) })?; Ok(self) } @@ -534,7 +532,9 @@ where where A: AsRef, { + use actix_http::Protocol; use actix_rt::net::UnixStream; + use actix_service::pipeline_factory; let cfg = self.config.clone(); let factory = self.factory.clone(); @@ -555,12 +555,13 @@ where socket_addr, c.host.clone().unwrap_or_else(|| format!("{}", socket_addr)), ); - pipeline_factory(|io: UnixStream| 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())), - ) + pipeline_factory(|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())), + ) }, )?; Ok(self) From a55e87faaa6ffe7b3f93aea082a783c6a64af9f7 Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Sun, 14 Mar 2021 19:33:51 -0700 Subject: [PATCH 102/339] refactor actix_http::helpers to generic over bufmut trait (#2069) --- actix-http/src/helpers.rs | 41 ++++++++++++++++++++++----------------- 1 file changed, 23 insertions(+), 18 deletions(-) diff --git a/actix-http/src/helpers.rs b/actix-http/src/helpers.rs index 13195f7db..74188717d 100644 --- a/actix-http/src/helpers.rs +++ b/actix-http/src/helpers.rs @@ -1,15 +1,15 @@ use std::io; -use bytes::{BufMut, BytesMut}; +use bytes::BufMut; use http::Version; const DIGITS_START: u8 = b'0'; -pub(crate) fn write_status_line(version: Version, n: u16, bytes: &mut BytesMut) { +pub(crate) fn write_status_line(version: Version, n: u16, buf: &mut B) { match version { - Version::HTTP_11 => bytes.put_slice(b"HTTP/1.1 "), - Version::HTTP_10 => bytes.put_slice(b"HTTP/1.0 "), - Version::HTTP_09 => bytes.put_slice(b"HTTP/0.9 "), + Version::HTTP_11 => buf.put_slice(b"HTTP/1.1 "), + Version::HTTP_10 => buf.put_slice(b"HTTP/1.0 "), + Version::HTTP_09 => buf.put_slice(b"HTTP/0.9 "), _ => { // other HTTP version handlers do not use this method } @@ -19,33 +19,36 @@ pub(crate) fn write_status_line(version: Version, n: u16, bytes: &mut BytesMut) let d10 = ((n / 10) % 10) as u8; let d1 = (n % 10) as u8; - bytes.put_u8(DIGITS_START + d100); - bytes.put_u8(DIGITS_START + d10); - bytes.put_u8(DIGITS_START + d1); + buf.put_u8(DIGITS_START + d100); + buf.put_u8(DIGITS_START + d10); + buf.put_u8(DIGITS_START + d1); // trailing space before reason - bytes.put_u8(b' '); + buf.put_u8(b' '); } /// NOTE: bytes object has to contain enough space -pub fn write_content_length(n: u64, bytes: &mut BytesMut) { +pub fn write_content_length(n: u64, buf: &mut B) { if n == 0 { - bytes.put_slice(b"\r\ncontent-length: 0\r\n"); + buf.put_slice(b"\r\ncontent-length: 0\r\n"); return; } - let mut buf = itoa::Buffer::new(); + let mut buffer = itoa::Buffer::new(); - bytes.put_slice(b"\r\ncontent-length: "); - bytes.put_slice(buf.format(n).as_bytes()); - bytes.put_slice(b"\r\n"); + buf.put_slice(b"\r\ncontent-length: "); + buf.put_slice(buffer.format(n).as_bytes()); + buf.put_slice(b"\r\n"); } -pub(crate) struct Writer<'a>(pub &'a mut BytesMut); +pub(crate) struct Writer<'a, B>(pub &'a mut B); -impl<'a> io::Write for Writer<'a> { +impl<'a, B> io::Write for Writer<'a, B> +where + B: BufMut, +{ fn write(&mut self, buf: &[u8]) -> io::Result { - self.0.extend_from_slice(buf); + self.0.put_slice(buf); Ok(buf.len()) } @@ -58,6 +61,8 @@ impl<'a> io::Write for Writer<'a> { mod tests { use std::str::from_utf8; + use bytes::BytesMut; + use super::*; #[test] From d93314a683d1a34826b23a3666e433283ffed850 Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Mon, 15 Mar 2021 03:59:42 -0700 Subject: [PATCH 103/339] fix awc readme example (#2076) --- awc/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/awc/README.md b/awc/README.md index ec3984d60..2e7dad320 100644 --- a/awc/README.md +++ b/awc/README.md @@ -27,7 +27,7 @@ fn main() { let res = client .get("http://www.rust-lang.org") // <- Create request builder - .header("User-Agent", "Actix-web") + .insert_header(("User-Agent", "Actix-web")) .send() // <- Send http request .await; From 69dd1a9bd643d8eb4f2689b6332eb16de4e06e90 Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Mon, 15 Mar 2021 19:56:23 -0700 Subject: [PATCH 104/339] Remove ConnectionLifetime trait. Simplify Acquired handling (#2072) --- actix-http/src/client/connection.rs | 26 +------ actix-http/src/client/h1proto.rs | 117 +++++++++++++--------------- actix-http/src/client/h2proto.rs | 22 +++--- actix-http/src/client/pool.rs | 18 ++--- actix-http/src/h1/expect.rs | 2 - actix-http/src/h1/upgrade.rs | 2 - src/app_service.rs | 1 - src/resource.rs | 1 - src/scope.rs | 1 - 9 files changed, 76 insertions(+), 114 deletions(-) diff --git a/actix-http/src/client/connection.rs b/actix-http/src/client/connection.rs index 97ecd0515..9fb5c58cc 100644 --- a/actix-http/src/client/connection.rs +++ b/actix-http/src/client/connection.rs @@ -94,14 +94,6 @@ pub trait Connection { >; } -pub(crate) trait ConnectionLifetime: AsyncRead + AsyncWrite + 'static { - /// Close connection - fn close(self: Pin<&mut Self>); - - /// Release connection to the connection pool - fn release(self: Pin<&mut Self>); -} - #[doc(hidden)] /// HTTP client connection pub struct IoConnection @@ -110,7 +102,7 @@ where { io: Option>, created: time::Instant, - pool: Option>, + pool: Acquired, } impl fmt::Debug for IoConnection @@ -130,7 +122,7 @@ impl IoConnection { pub(crate) fn new( io: ConnectionType, created: time::Instant, - pool: Option>, + pool: Acquired, ) -> Self { IoConnection { pool, @@ -139,13 +131,9 @@ impl IoConnection { } } - pub(crate) fn into_inner(self) -> (ConnectionType, time::Instant) { - (self.io.unwrap(), self.created) - } - #[cfg(test)] pub(crate) fn into_parts(self) -> (ConnectionType, time::Instant, Acquired) { - (self.io.unwrap(), self.created, self.pool.unwrap()) + (self.io.unwrap(), self.created, self.pool) } async fn send_request>( @@ -173,13 +161,7 @@ impl IoConnection { match self.io.take().unwrap() { ConnectionType::H1(io) => h1proto::open_tunnel(io, head.into()).await, ConnectionType::H2(io) => { - if let Some(mut pool) = self.pool.take() { - pool.release(IoConnection::new( - ConnectionType::H2(io), - self.created, - None, - )); - } + self.pool.release(ConnectionType::H2(io), self.created); Err(SendRequestError::TunnelNotSupported) } } diff --git a/actix-http/src/client/h1proto.rs b/actix-http/src/client/h1proto.rs index d2db18cec..cd0b45734 100644 --- a/actix-http/src/client/h1proto.rs +++ b/actix-http/src/client/h1proto.rs @@ -7,7 +7,7 @@ use actix_codec::{AsyncRead, AsyncWrite, Framed, ReadBuf}; use bytes::buf::BufMut; use bytes::{Bytes, BytesMut}; use futures_core::Stream; -use futures_util::{future::poll_fn, SinkExt, StreamExt}; +use futures_util::{future::poll_fn, SinkExt}; use crate::error::PayloadError; use crate::h1; @@ -19,7 +19,7 @@ use crate::http::{ use crate::message::{RequestHeadType, ResponseHead}; use crate::payload::{Payload, PayloadStream}; -use super::connection::{ConnectionLifetime, ConnectionType, IoConnection}; +use super::connection::ConnectionType; use super::error::{ConnectError, SendRequestError}; use super::pool::Acquired; use crate::body::{BodySize, MessageBody}; @@ -29,7 +29,7 @@ pub(crate) async fn send_request( mut head: RequestHeadType, body: B, created: time::Instant, - pool: Option>, + acquired: Acquired, ) -> Result<(ResponseHead, Payload), SendRequestError> where T: AsyncRead + AsyncWrite + Unpin + 'static, @@ -42,9 +42,9 @@ where if let Some(host) = head.as_ref().uri.host() { let mut wrt = BytesMut::with_capacity(host.len() + 5).writer(); - let _ = match head.as_ref().uri.port_u16() { - None | Some(80) | Some(443) => write!(wrt, "{}", host), - Some(port) => write!(wrt, "{}:{}", host, port), + match head.as_ref().uri.port_u16() { + None | Some(80) | Some(443) => write!(wrt, "{}", host)?, + Some(port) => write!(wrt, "{}:{}", host, port)?, }; match wrt.get_mut().split().freeze().try_into_value() { @@ -64,7 +64,7 @@ where let io = H1Connection { created, - pool, + acquired, io: Some(io), }; @@ -77,10 +77,8 @@ where let is_expect = if head.as_ref().headers.contains_key(EXPECT) { match body.size() { BodySize::None | BodySize::Empty | BodySize::Sized(0) => { - let pin_framed = Pin::new(&mut framed); - - let force_close = !pin_framed.codec_ref().keepalive(); - release_connection(pin_framed, force_close); + let keep_alive = framed.codec_ref().keepalive(); + framed.io_mut().on_release(keep_alive); // TODO: use a new variant or a new type better describing error violate // `Requirements for clients` session of above RFC @@ -128,8 +126,9 @@ where match pin_framed.codec_ref().message_type() { h1::MessageType::None => { - let force_close = !pin_framed.codec_ref().keepalive(); - release_connection(pin_framed, force_close); + let keep_alive = pin_framed.codec_ref().keepalive(); + pin_framed.io_mut().on_release(keep_alive); + Ok((head, Payload::None)) } _ => { @@ -151,12 +150,11 @@ where framed.send((head, BodySize::None).into()).await?; // read response - if let (Some(result), framed) = framed.into_future().await { - let head = result.map_err(SendRequestError::from)?; - Ok((head, framed)) - } else { - Err(SendRequestError::from(ConnectError::Disconnected)) - } + let head = poll_fn(|cx| Pin::new(&mut framed).poll_next(cx)) + .await + .ok_or(ConnectError::Disconnected)??; + + Ok((head, framed)) } /// send request body to the peer @@ -165,7 +163,7 @@ pub(crate) async fn send_body( mut framed: Pin<&mut Framed>, ) -> Result<(), SendRequestError> where - T: ConnectionLifetime + Unpin, + T: AsyncRead + AsyncWrite + Unpin + 'static, B: MessageBody, { actix_rt::pin!(body); @@ -200,7 +198,7 @@ where } } - SinkExt::flush(Pin::into_inner(framed)).await?; + SinkExt::flush(framed.get_mut()).await?; Ok(()) } @@ -208,41 +206,37 @@ where /// HTTP client connection pub struct H1Connection where - T: AsyncWrite + Unpin + 'static, + T: AsyncRead + AsyncWrite + Unpin + 'static, { /// T should be `Unpin` io: Option, created: time::Instant, - pool: Option>, + acquired: Acquired, } -impl ConnectionLifetime for H1Connection +impl H1Connection where T: AsyncRead + AsyncWrite + Unpin + 'static, { + fn on_release(&mut self, keep_alive: bool) { + if keep_alive { + self.release(); + } else { + self.close(); + } + } + /// Close connection - fn close(mut self: Pin<&mut Self>) { - if let Some(mut pool) = self.pool.take() { - if let Some(io) = self.io.take() { - pool.close(IoConnection::new( - ConnectionType::H1(io), - self.created, - None, - )); - } + fn close(&mut self) { + if let Some(io) = self.io.take() { + self.acquired.close(ConnectionType::H1(io)); } } /// Release this connection to the connection pool - fn release(mut self: Pin<&mut Self>) { - if let Some(mut pool) = self.pool.take() { - if let Some(io) = self.io.take() { - pool.release(IoConnection::new( - ConnectionType::H1(io), - self.created, - None, - )); - } + fn release(&mut self) { + if let Some(io) = self.io.take() { + self.acquired.release(ConnectionType::H1(io), self.created); } } } @@ -282,13 +276,19 @@ impl AsyncWrite for H1Connection } #[pin_project::pin_project] -pub(crate) struct PlStream { +pub(crate) struct PlStream +where + Io: AsyncRead + AsyncWrite + Unpin + 'static, +{ #[pin] - framed: Option>, + framed: Option, h1::ClientPayloadCodec>>, } -impl PlStream { - fn new(framed: Framed) -> Self { +impl PlStream +where + Io: AsyncRead + AsyncWrite + Unpin + 'static, +{ + fn new(framed: Framed, h1::ClientCodec>) -> Self { let framed = framed.into_map_codec(|codec| codec.into_payload_codec()); PlStream { @@ -297,24 +297,26 @@ impl PlStream { } } -impl Stream for PlStream { +impl Stream for PlStream +where + Io: AsyncRead + AsyncWrite + Unpin + 'static, +{ type Item = Result; fn poll_next( self: Pin<&mut Self>, cx: &mut Context<'_>, ) -> Poll> { - let mut this = self.project(); + let mut framed = self.project().framed.as_pin_mut().unwrap(); - match this.framed.as_mut().as_pin_mut().unwrap().next_item(cx)? { + match framed.as_mut().next_item(cx)? { Poll::Pending => Poll::Pending, Poll::Ready(Some(chunk)) => { if let Some(chunk) = chunk { Poll::Ready(Some(Ok(chunk))) } else { - let framed = this.framed.as_mut().as_pin_mut().unwrap(); - let force_close = !framed.codec_ref().keepalive(); - release_connection(framed, force_close); + let keep_alive = framed.codec_ref().keepalive(); + framed.io_mut().on_release(keep_alive); Poll::Ready(None) } } @@ -322,14 +324,3 @@ impl Stream for PlStream { } } } - -fn release_connection(framed: Pin<&mut Framed>, force_close: bool) -where - T: ConnectionLifetime, -{ - if !force_close && framed.is_read_buf_empty() && framed.is_write_buf_empty() { - framed.io_pin().release() - } else { - framed.io_pin().close() - } -} diff --git a/actix-http/src/client/h2proto.rs b/actix-http/src/client/h2proto.rs index 7292972de..82e81c7ff 100644 --- a/actix-http/src/client/h2proto.rs +++ b/actix-http/src/client/h2proto.rs @@ -17,7 +17,7 @@ use crate::message::{RequestHeadType, ResponseHead}; use crate::payload::Payload; use super::config::ConnectorConfig; -use super::connection::{ConnectionType, IoConnection}; +use super::connection::ConnectionType; use super::error::SendRequestError; use super::pool::Acquired; use crate::client::connection::H2Connection; @@ -27,7 +27,7 @@ pub(crate) async fn send_request( head: RequestHeadType, body: B, created: time::Instant, - pool: Option>, + acquired: Acquired, ) -> Result<(ResponseHead, Payload), SendRequestError> where T: AsyncRead + AsyncWrite + Unpin + 'static, @@ -103,13 +103,13 @@ where let res = poll_fn(|cx| io.poll_ready(cx)).await; if let Err(e) = res { - release(io, pool, created, e.is_io()); + release(io, acquired, created, e.is_io()); return Err(SendRequestError::from(e)); } let resp = match io.send_request(req, eof) { Ok((fut, send)) => { - release(io, pool, created, false); + release(io, acquired, created, false); if !eof { send_body(body, send).await?; @@ -117,7 +117,7 @@ where fut.await.map_err(SendRequestError::from)? } Err(e) => { - release(io, pool, created, e.is_io()); + release(io, acquired, created, e.is_io()); return Err(e.into()); } }; @@ -181,16 +181,14 @@ async fn send_body( /// release SendRequest object fn release( io: H2Connection, - pool: Option>, + acquired: Acquired, created: time::Instant, close: bool, ) { - if let Some(mut pool) = pool { - if close { - pool.close(IoConnection::new(ConnectionType::H2(io), created, None)); - } else { - pool.release(IoConnection::new(ConnectionType::H2(io), created, None)); - } + if close { + acquired.close(ConnectionType::H2(io)); + } else { + acquired.release(ConnectionType::H2(io), created); } } diff --git a/actix-http/src/client/pool.rs b/actix-http/src/client/pool.rs index b5b4a30d3..79adcf3e2 100644 --- a/actix-http/src/client/pool.rs +++ b/actix-http/src/client/pool.rs @@ -217,7 +217,7 @@ where // construct acquired. It's used to put Io type back to pool/ close the Io type. // permit is carried with the whole lifecycle of Acquired. - let acquired = Some(Acquired { key, inner, permit }); + let acquired = Acquired { key, inner, permit }; // match the connection and spawn new one if did not get anything. match conn { @@ -235,7 +235,7 @@ where acquired, )) } else { - let config = &acquired.as_ref().unwrap().inner.config; + let config = &acquired.inner.config; let (sender, connection) = handshake(io, config).await?; Ok(IoConnection::new( ConnectionType::H2(H2Connection::new(sender, connection)), @@ -346,14 +346,12 @@ where Io: AsyncRead + AsyncWrite + Unpin + 'static, { /// Close the IO. - pub(crate) fn close(&mut self, conn: IoConnection) { - let (conn, _) = conn.into_inner(); + pub(crate) fn close(&self, conn: ConnectionType) { self.inner.close(conn); } /// Release IO back into pool. - pub(crate) fn release(&mut self, conn: IoConnection) { - let (io, created) = conn.into_inner(); + pub(crate) fn release(&self, conn: ConnectionType, created: Instant) { let Acquired { key, inner, .. } = self; inner @@ -362,12 +360,12 @@ where .entry(key.clone()) .or_insert_with(VecDeque::new) .push_back(PooledConnection { - conn: io, + conn, created, used: Instant::now(), }); - let _ = &mut self.permit; + let _ = &self.permit; } } @@ -447,8 +445,8 @@ mod test { where T: AsyncRead + AsyncWrite + Unpin + 'static, { - let (conn, created, mut acquired) = conn.into_parts(); - acquired.release(IoConnection::new(conn, created, None)); + let (conn, created, acquired) = conn.into_parts(); + acquired.release(conn, created); } #[actix_rt::test] diff --git a/actix-http/src/h1/expect.rs b/actix-http/src/h1/expect.rs index 65856edf6..5015069bb 100644 --- a/actix-http/src/h1/expect.rs +++ b/actix-http/src/h1/expect.rs @@ -1,5 +1,3 @@ -use std::task::Poll; - use actix_service::{Service, ServiceFactory}; use futures_util::future::{ready, Ready}; diff --git a/actix-http/src/h1/upgrade.rs b/actix-http/src/h1/upgrade.rs index fbfbc83c1..e57ea8ae9 100644 --- a/actix-http/src/h1/upgrade.rs +++ b/actix-http/src/h1/upgrade.rs @@ -1,5 +1,3 @@ -use std::task::Poll; - use actix_codec::Framed; use actix_service::{Service, ServiceFactory}; use futures_core::future::LocalBoxFuture; diff --git a/src/app_service.rs b/src/app_service.rs index 9b4ae3354..be4ccf22f 100644 --- a/src/app_service.rs +++ b/src/app_service.rs @@ -1,6 +1,5 @@ use std::cell::RefCell; use std::rc::Rc; -use std::task::Poll; use actix_http::{Extensions, Request, Response}; use actix_router::{Path, ResourceDef, Router, Url}; diff --git a/src/resource.rs b/src/resource.rs index 944beeefa..1a5619de6 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -2,7 +2,6 @@ use std::cell::RefCell; use std::fmt; use std::future::Future; use std::rc::Rc; -use std::task::Poll; use actix_http::{Error, Extensions, Response}; use actix_router::IntoPattern; diff --git a/src/scope.rs b/src/scope.rs index dd02501b0..e96f5b9e8 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -2,7 +2,6 @@ use std::cell::RefCell; use std::fmt; use std::future::Future; use std::rc::Rc; -use std::task::Poll; use actix_http::Extensions; use actix_router::{ResourceDef, Router}; From c8f6d37290e907a79a3acf1a20ad6327ba5bfd65 Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Tue, 16 Mar 2021 09:31:14 -0700 Subject: [PATCH 105/339] rename client io trait. reduce duplicate code (#2079) --- actix-http/src/client/connection.rs | 4 ++ actix-http/src/client/connector.rs | 22 ++++----- actix-http/src/client/mod.rs | 2 +- awc/src/connect.rs | 73 ++++------------------------- 4 files changed, 26 insertions(+), 75 deletions(-) diff --git a/actix-http/src/client/connection.rs b/actix-http/src/client/connection.rs index 9fb5c58cc..291148b5b 100644 --- a/actix-http/src/client/connection.rs +++ b/actix-http/src/client/connection.rs @@ -19,6 +19,10 @@ use super::error::SendRequestError; use super::pool::Acquired; use super::{h1proto, h2proto}; +pub trait ConnectionIo: AsyncRead + AsyncWrite + Unpin + 'static {} + +impl ConnectionIo for T {} + pub(crate) enum ConnectionType { H1(Io), H2(H2Connection), diff --git a/actix-http/src/client/connector.rs b/actix-http/src/client/connector.rs index ebdbbb6eb..0c9159b87 100644 --- a/actix-http/src/client/connector.rs +++ b/actix-http/src/client/connector.rs @@ -18,7 +18,7 @@ use futures_core::ready; use http::Uri; use super::config::ConnectorConfig; -use super::connection::{Connection, EitherIoConnection}; +use super::connection::{Connection, ConnectionIo, EitherIoConnection}; use super::error::ConnectError; use super::pool::ConnectionPool; use super::Connect; @@ -61,9 +61,6 @@ pub struct Connector { ssl: SslConnector, } -pub trait Io: AsyncRead + AsyncWrite + Unpin {} -impl Io for T {} - impl Connector<()> { #[allow(clippy::new_ret_no_self, clippy::let_unit_value)] pub fn new() -> Connector< @@ -281,16 +278,16 @@ where pub type DummyService = Box< dyn Service< Connect, - Response = (Box, Protocol), + Response = (Box, Protocol), Error = ConnectError, Future = futures_core::future::LocalBoxFuture< 'static, - Result<(Box, Protocol), ConnectError>, + Result<(Box, Protocol), ConnectError>, >, >, >; - InnerConnector::<_, DummyService, _, Box> { + InnerConnector::<_, DummyService, _, Box> { tcp_pool: ConnectionPool::new( tcp_service, self.config.no_disconnect_timeout(), @@ -334,9 +331,12 @@ where .map(|protos| protos.windows(2).any(|w| w == H2)) .unwrap_or(false); if h2 { - (Box::new(sock) as Box, Protocol::Http2) + ( + Box::new(sock) as Box, + Protocol::Http2, + ) } else { - (Box::new(sock) as Box, Protocol::Http1) + (Box::new(sock) as _, Protocol::Http1) } }) .map_err(ConnectError::from), @@ -354,9 +354,9 @@ where .map(|protos| protos.windows(2).any(|w| w == H2)) .unwrap_or(false); if h2 { - (Box::new(sock) as Box, Protocol::Http2) + (Box::new(sock) as _, Protocol::Http2) } else { - (Box::new(sock) as Box, Protocol::Http1) + (Box::new(sock) as _, Protocol::Http1) } }), ), diff --git a/actix-http/src/client/mod.rs b/actix-http/src/client/mod.rs index c6f998c2a..ce6aa3bc1 100644 --- a/actix-http/src/client/mod.rs +++ b/actix-http/src/client/mod.rs @@ -14,7 +14,7 @@ pub use actix_tls::connect::{ Connect as TcpConnect, ConnectError as TcpConnectError, Connection as TcpConnection, }; -pub use self::connection::Connection; +pub use self::connection::{Connection, ConnectionIo}; pub use self::connector::Connector; pub use self::error::{ConnectError, FreezeRequestError, InvalidUrl, SendRequestError}; pub use crate::Protocol; diff --git a/awc/src/connect.rs b/awc/src/connect.rs index a4abbc46b..d3bc01ed6 100644 --- a/awc/src/connect.rs +++ b/awc/src/connect.rs @@ -1,15 +1,16 @@ use std::{ - fmt, future::Future, - io, net, + net, pin::Pin, task::{Context, Poll}, }; -use actix_codec::{AsyncRead, AsyncWrite, Framed, ReadBuf}; +use actix_codec::Framed; use actix_http::{ body::Body, - client::{Connect as ClientConnect, ConnectError, Connection, SendRequestError}, + client::{ + Connect as ClientConnect, ConnectError, Connection, ConnectionIo, SendRequestError, + }, h1::ClientCodec, Payload, RequestHead, RequestHeadType, ResponseHead, }; @@ -123,7 +124,7 @@ impl Future for ConnectRequestFuture where Fut: Future>, C: Connection, - Io: AsyncRead + AsyncWrite + Unpin + 'static, + Io: ConnectionIo, { type Output = Result; @@ -138,14 +139,14 @@ where let fut = ConnectRequestFuture::Client { fut: connection.send_request(head, body), }; - self.as_mut().set(fut); + self.set(fut); } ConnectRequest::Tunnel(head, ..) => { // send request let fut = ConnectRequestFuture::Tunnel { fut: connection.open_tunnel(RequestHeadType::from(head)), }; - self.as_mut().set(fut); + self.set(fut); } } self.poll(cx) @@ -158,65 +159,11 @@ where } ConnectRequestProj::Tunnel { fut } => { let (head, framed) = ready!(fut.as_mut().poll(cx))?; - let framed = framed.into_map_io(|io| BoxedSocket(Box::new(Socket(io)))); + let framed = framed.into_map_io(|io| Box::new(io) as _); Poll::Ready(Ok(ConnectResponse::Tunnel(head, framed))) } } } } -trait AsyncSocket { - fn as_read(&self) -> &(dyn AsyncRead + Unpin); - fn as_read_mut(&mut self) -> &mut (dyn AsyncRead + Unpin); - fn as_write(&mut self) -> &mut (dyn AsyncWrite + Unpin); -} - -struct Socket(T); - -impl AsyncSocket for Socket { - fn as_read(&self) -> &(dyn AsyncRead + Unpin) { - &self.0 - } - fn as_read_mut(&mut self) -> &mut (dyn AsyncRead + Unpin) { - &mut self.0 - } - fn as_write(&mut self) -> &mut (dyn AsyncWrite + Unpin) { - &mut self.0 - } -} - -pub struct BoxedSocket(Box); - -impl fmt::Debug for BoxedSocket { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "BoxedSocket") - } -} - -impl AsyncRead for BoxedSocket { - fn poll_read( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - buf: &mut ReadBuf<'_>, - ) -> Poll> { - Pin::new(self.get_mut().0.as_read_mut()).poll_read(cx, buf) - } -} - -impl AsyncWrite for BoxedSocket { - fn poll_write( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - buf: &[u8], - ) -> Poll> { - Pin::new(self.get_mut().0.as_write()).poll_write(cx, buf) - } - - fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - Pin::new(self.get_mut().0.as_write()).poll_flush(cx) - } - - fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - Pin::new(self.get_mut().0.as_write()).poll_shutdown(cx) - } -} +pub type BoxedSocket = Box; From 3dc2d145efcde2feb3e5f20d9002390c283b1ea4 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Thu, 11 Mar 2021 03:48:38 +0000 Subject: [PATCH 106/339] import some traits as _ --- actix-http/examples/echo.rs | 2 +- actix-http/examples/echo2.rs | 2 +- actix-http/src/client/h1proto.rs | 4 ++-- actix-http/src/h2/dispatcher.rs | 6 ++++-- actix-http/src/h2/service.rs | 4 ++-- actix-http/src/service.rs | 19 ++++++++++++------- actix-http/src/time_parser.rs | 2 +- actix-http/tests/test_client.rs | 2 +- actix-http/tests/test_openssl.rs | 7 +++++-- actix-http/tests/test_rustls.rs | 3 ++- actix-http/tests/test_server.rs | 2 +- actix-http/tests/test_ws.rs | 2 +- 12 files changed, 33 insertions(+), 22 deletions(-) diff --git a/actix-http/examples/echo.rs b/actix-http/examples/echo.rs index 90d768cbe..176ac5c2b 100644 --- a/actix-http/examples/echo.rs +++ b/actix-http/examples/echo.rs @@ -3,7 +3,7 @@ use std::{env, io}; use actix_http::{Error, HttpService, Request, Response}; use actix_server::Server; use bytes::BytesMut; -use futures_util::StreamExt; +use futures_util::StreamExt as _; use http::header::HeaderValue; use log::info; diff --git a/actix-http/examples/echo2.rs b/actix-http/examples/echo2.rs index bc932ce8f..408a40114 100644 --- a/actix-http/examples/echo2.rs +++ b/actix-http/examples/echo2.rs @@ -4,7 +4,7 @@ use actix_http::http::HeaderValue; use actix_http::{Error, HttpService, Request, Response}; use actix_server::Server; use bytes::BytesMut; -use futures_util::StreamExt; +use futures_util::StreamExt as _; use log::info; async fn handle_request(mut req: Request) -> Result { diff --git a/actix-http/src/client/h1proto.rs b/actix-http/src/client/h1proto.rs index cd0b45734..75a3c1f3d 100644 --- a/actix-http/src/client/h1proto.rs +++ b/actix-http/src/client/h1proto.rs @@ -7,7 +7,7 @@ use actix_codec::{AsyncRead, AsyncWrite, Framed, ReadBuf}; use bytes::buf::BufMut; use bytes::{Bytes, BytesMut}; use futures_core::Stream; -use futures_util::{future::poll_fn, SinkExt}; +use futures_util::{future::poll_fn, SinkExt as _}; use crate::error::PayloadError; use crate::h1; @@ -198,7 +198,7 @@ where } } - SinkExt::flush(framed.get_mut()).await?; + framed.get_mut().flush().await?; Ok(()) } diff --git a/actix-http/src/h2/dispatcher.rs b/actix-http/src/h2/dispatcher.rs index 958c761d5..6e6cd5a2f 100644 --- a/actix-http/src/h2/dispatcher.rs +++ b/actix-http/src/h2/dispatcher.rs @@ -5,8 +5,10 @@ use actix_codec::{AsyncRead, AsyncWrite}; use actix_service::Service; use bytes::{Bytes, BytesMut}; use futures_core::ready; -use h2::server::{Connection, SendResponse}; -use h2::SendStream; +use h2::{ + server::{Connection, SendResponse}, + SendStream, +}; use http::header::{HeaderValue, CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCODING}; use log::{error, trace}; diff --git a/actix-http/src/h2/service.rs b/actix-http/src/h2/service.rs index 1dc290e49..c64139564 100644 --- a/actix-http/src/h2/service.rs +++ b/actix-http/src/h2/service.rs @@ -13,7 +13,7 @@ use actix_service::{ use bytes::Bytes; use futures_core::ready; use futures_util::future::ok; -use h2::server::{self, Handshake}; +use h2::server::{handshake, Handshake}; use log::error; use crate::body::MessageBody; @@ -307,7 +307,7 @@ where Some(self.cfg.clone()), addr, on_connect_data, - server::handshake(io), + handshake(io), ), } } diff --git a/actix-http/src/service.rs b/actix-http/src/service.rs index 89f3e3bb1..1a06cec3d 100644 --- a/actix-http/src/service.rs +++ b/actix-http/src/service.rs @@ -1,14 +1,19 @@ -use std::marker::PhantomData; -use std::pin::Pin; -use std::task::{Context, Poll}; -use std::{fmt, net, rc::Rc}; +use std::{ + fmt, + future::Future, + marker::PhantomData, + net, + pin::Pin, + rc::Rc, + task::{Context, Poll}, +}; use actix_codec::{AsyncRead, AsyncWrite, Framed}; use actix_rt::net::TcpStream; use actix_service::{pipeline_factory, IntoServiceFactory, Service, ServiceFactory}; use bytes::Bytes; -use futures_core::{ready, Future}; -use h2::server::{self, Handshake}; +use futures_core::ready; +use h2::server::{handshake, Handshake}; use pin_project::pin_project; use crate::body::MessageBody; @@ -562,7 +567,7 @@ where match proto { Protocol::Http2 => HttpServiceHandlerResponse { state: State::H2Handshake(Some(( - server::handshake(io), + handshake(io), self.cfg.clone(), self.flow.clone(), on_connect_data, diff --git a/actix-http/src/time_parser.rs b/actix-http/src/time_parser.rs index 46bf73037..fd82fd42e 100644 --- a/actix-http/src/time_parser.rs +++ b/actix-http/src/time_parser.rs @@ -1,7 +1,7 @@ use time::{Date, OffsetDateTime, PrimitiveDateTime}; /// Attempt to parse a `time` string as one of either RFC 1123, RFC 850, or asctime. -pub fn parse_http_date(time: &str) -> Option { +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)) diff --git a/actix-http/tests/test_client.rs b/actix-http/tests/test_client.rs index a50f2404d..758e39745 100644 --- a/actix-http/tests/test_client.rs +++ b/actix-http/tests/test_client.rs @@ -6,7 +6,7 @@ use actix_service::ServiceFactoryExt; use bytes::Bytes; use futures_util::{ future::{self, ok}, - StreamExt, + StreamExt as _, }; const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ diff --git a/actix-http/tests/test_openssl.rs b/actix-http/tests/test_openssl.rs index d5ec645a4..49a68a60d 100644 --- a/actix-http/tests/test_openssl.rs +++ b/actix-http/tests/test_openssl.rs @@ -12,8 +12,11 @@ use actix_http::{body, Error, HttpService, Request, Response}; use actix_http_test::test_server; use actix_service::{fn_service, ServiceFactoryExt}; use bytes::{Bytes, BytesMut}; -use futures_util::future::{err, ok, ready}; -use futures_util::stream::{once, Stream, StreamExt}; +use futures_core::Stream; +use futures_util::{ + future::{err, ok, ready}, + stream::{once, StreamExt as _}, +}; use openssl::{ pkey::PKey, ssl::{SslAcceptor, SslMethod}, diff --git a/actix-http/tests/test_rustls.rs b/actix-http/tests/test_rustls.rs index 81edb5c18..7a3cb1473 100644 --- a/actix-http/tests/test_rustls.rs +++ b/actix-http/tests/test_rustls.rs @@ -10,8 +10,9 @@ use actix_http_test::test_server; use actix_service::{fn_factory_with_config, fn_service}; use bytes::{Bytes, BytesMut}; +use futures_core::Stream; use futures_util::future::{self, err, ok}; -use futures_util::stream::{once, Stream, StreamExt}; +use futures_util::stream::{once, StreamExt as _}; use rustls::{ internal::pemfile::{certs, pkcs8_private_keys}, NoClientAuth, ServerConfig as RustlsServerConfig, diff --git a/actix-http/tests/test_server.rs b/actix-http/tests/test_server.rs index a4c1f92b5..6d145400c 100644 --- a/actix-http/tests/test_server.rs +++ b/actix-http/tests/test_server.rs @@ -7,7 +7,7 @@ use actix_rt::time::sleep; use actix_service::fn_service; use bytes::Bytes; use futures_util::future::{self, err, ok, ready, FutureExt}; -use futures_util::stream::{once, StreamExt}; +use futures_util::stream::{once, StreamExt as _}; use regex::Regex; use actix_http::HttpMessage; diff --git a/actix-http/tests/test_ws.rs b/actix-http/tests/test_ws.rs index 7ed9b0df1..3b90b4e54 100644 --- a/actix-http/tests/test_ws.rs +++ b/actix-http/tests/test_ws.rs @@ -12,7 +12,7 @@ use actix_utils::dispatcher::Dispatcher; use bytes::Bytes; use futures_util::future; use futures_util::task::{Context, Poll}; -use futures_util::{SinkExt, StreamExt}; +use futures_util::{SinkExt as _, StreamExt as _}; struct WsService(Arc, Cell)>>); From 983b6904a760134c00cff83edf2d83ffab96a590 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Wed, 17 Mar 2021 00:38:42 +0000 Subject: [PATCH 107/339] unvendor openssl --- .github/workflows/ci.yml | 15 +++++++++++++++ Cargo.toml | 6 ------ actix-http-test/Cargo.toml | 6 ------ actix-http/Cargo.toml | 5 ----- awc/Cargo.toml | 6 ------ 5 files changed, 15 insertions(+), 23 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7d0520d52..dc0ff0c19 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -26,6 +26,21 @@ jobs: steps: - uses: actions/checkout@v2 + # install OpenSSL on Windows + - 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: Install ${{ matrix.version }} uses: actions-rs/toolchain@v1 with: diff --git a/Cargo.toml b/Cargo.toml index 8477c8ede..0877edf9c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -111,12 +111,6 @@ tls-openssl = { package = "openssl", version = "0.10.9", optional = true } tls-rustls = { package = "rustls", version = "0.19.0", optional = true } url = "2.1" -[target.'cfg(windows)'.dependencies.tls-openssl] -version = "0.10.9" -package = "openssl" -features = ["vendored"] -optional = true - [dev-dependencies] brotli2 = "0.3.2" criterion = "0.3" diff --git a/actix-http-test/Cargo.toml b/actix-http-test/Cargo.toml index 69b2a3335..c1e61556b 100644 --- a/actix-http-test/Cargo.toml +++ b/actix-http-test/Cargo.toml @@ -50,12 +50,6 @@ serde_urlencoded = "0.7" time = { version = "0.2.23", default-features = false, features = ["std"] } tls-openssl = { version = "0.10.9", package = "openssl", optional = true } -[target.'cfg(windows)'.dependencies.tls-openssl] -version = "0.10.9" -package = "openssl" -features = ["vendored"] -optional = true - [dev-dependencies] actix-web = { version = "4.0.0-beta.4", default-features = false, features = ["cookies"] } actix-http = "3.0.0-beta.4" diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index edcc7efd5..e1aebb76b 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -98,11 +98,6 @@ serde_derive = "1.0" tls-openssl = { version = "0.10", package = "openssl" } tls-rustls = { version = "0.19", package = "rustls" } -[target.'cfg(windows)'.dev-dependencies.tls-openssl] -version = "0.10.9" -package = "openssl" -features = ["vendored"] - [[example]] name = "ws" required-features = ["rustls"] diff --git a/awc/Cargo.toml b/awc/Cargo.toml index 981b93a52..ec2e03a96 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -66,12 +66,6 @@ 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"] } -[target.'cfg(windows)'.dependencies.tls-openssl] -version = "0.10.9" -package = "openssl" -features = ["vendored"] -optional = true - [dev-dependencies] actix-web = { version = "4.0.0-beta.4", features = ["openssl"] } actix-http = { version = "3.0.0-beta.4", features = ["openssl"] } From abcb444dd90b82e96512bfa4ba41fe041860561e Mon Sep 17 00:00:00 2001 From: obayemi Date: Thu, 18 Mar 2021 14:21:44 +0100 Subject: [PATCH 108/339] fix routes in Path documentation (#2084) --- src/types/path.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/types/path.rs b/src/types/path.rs index 4ab124d53..294df6cf2 100644 --- a/src/types/path.rs +++ b/src/types/path.rs @@ -20,7 +20,7 @@ use crate::{dev::Payload, error::PathError, FromRequest, HttpRequest}; /// // extract path info from "/{name}/{count}/index.html" into tuple /// // {name} - deserialize a String /// // {count} - deserialize a u32 -/// #[get("/")] +/// #[get("/{name}/{count}/index.html")] /// async fn index(path: web::Path<(String, u32)>) -> String { /// let (name, count) = path.into_inner(); /// format!("Welcome {}! {}", name, count) @@ -40,7 +40,7 @@ use crate::{dev::Payload, error::PathError, FromRequest, HttpRequest}; /// } /// /// // extract `Info` from a path using serde -/// #[get("/")] +/// #[get("/{name}")] /// async fn index(info: web::Path) -> String { /// format!("Welcome {}!", info.name) /// } From b75b5114c30bf56692290775e012a152d13f0122 Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Thu, 18 Mar 2021 10:53:22 -0700 Subject: [PATCH 109/339] refactor actix_http connection types and connector services (#2081) --- actix-http/CHANGES.md | 6 + actix-http/src/client/config.rs | 2 + actix-http/src/client/connection.rs | 491 +++++++++++++++--------- actix-http/src/client/connector.rs | 575 +++++++++++++++++++--------- actix-http/src/client/h1proto.rs | 136 ++----- actix-http/src/client/h2proto.rs | 38 +- actix-http/src/client/mod.rs | 2 +- actix-http/src/client/pool.rs | 120 +++--- awc/CHANGES.md | 4 + awc/src/builder.rs | 20 +- awc/src/connect.rs | 30 +- awc/src/frozen.rs | 22 +- awc/src/lib.rs | 9 +- awc/src/middleware/redirect.rs | 4 +- awc/src/request.rs | 14 +- awc/src/ws.rs | 5 +- 16 files changed, 867 insertions(+), 611 deletions(-) diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index e96c22274..c4e0aec89 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -1,10 +1,16 @@ # Changes ## Unreleased - 2021-xx-xx +### Added +* `client::Connector::handshake_timeout` method for customize tls connection handshake timeout. [#2081] +* `client::ConnectorService` as `client::Connector::finish` method's return type [#2081] +* `client::ConnectionIo` trait alias [#2081] + ### Chaged * `client::Connector` type now only have one generic type for `actix_service::Service`. [#2063] [#2063]: https://github.com/actix/actix-web/pull/2063 +[#2081]: https://github.com/actix/actix-web/pull/2081 ## 3.0.0-beta.4 - 2021-03-08 diff --git a/actix-http/src/client/config.rs b/actix-http/src/client/config.rs index 0d54e1b49..1c0405cbc 100644 --- a/actix-http/src/client/config.rs +++ b/actix-http/src/client/config.rs @@ -8,6 +8,7 @@ const DEFAULT_H2_STREAM_WINDOW: u32 = 1024 * 1024; // 1MB #[derive(Clone)] pub(crate) struct ConnectorConfig { pub(crate) timeout: Duration, + pub(crate) handshake_timeout: Duration, pub(crate) conn_lifetime: Duration, pub(crate) conn_keep_alive: Duration, pub(crate) disconnect_timeout: Option, @@ -21,6 +22,7 @@ impl Default for ConnectorConfig { fn default() -> Self { Self { timeout: Duration::from_secs(5), + handshake_timeout: Duration::from_secs(5), conn_lifetime: Duration::from_secs(75), conn_keep_alive: Duration::from_secs(15), disconnect_timeout: Some(Duration::from_millis(3000)), diff --git a/actix-http/src/client/connection.rs b/actix-http/src/client/connection.rs index 291148b5b..89dfd59de 100644 --- a/actix-http/src/client/connection.rs +++ b/actix-http/src/client/connection.rs @@ -1,14 +1,16 @@ -use std::ops::{Deref, DerefMut}; -use std::pin::Pin; -use std::task::{Context, Poll}; -use std::{fmt, io, time}; +use std::{ + io, + ops::{Deref, DerefMut}, + pin::Pin, + task::{Context, Poll}, + time, +}; use actix_codec::{AsyncRead, AsyncWrite, Framed, ReadBuf}; use actix_rt::task::JoinHandle; use bytes::Bytes; use futures_core::future::LocalBoxFuture; use h2::client::SendRequest; -use pin_project::pin_project; use crate::body::MessageBody; use crate::h1::ClientCodec; @@ -19,32 +21,148 @@ use super::error::SendRequestError; use super::pool::Acquired; use super::{h1proto, h2proto}; +/// Trait alias for types impl [tokio::io::AsyncRead] and [tokio::io::AsyncWrite]. pub trait ConnectionIo: AsyncRead + AsyncWrite + Unpin + 'static {} impl ConnectionIo for T {} -pub(crate) enum ConnectionType { - H1(Io), - H2(H2Connection), +/// HTTP client connection +pub struct H1Connection { + io: Option, + created: time::Instant, + acquired: Acquired, } -/// `H2Connection` has two parts: `SendRequest` and `Connection`. +impl H1Connection { + /// close or release the connection to pool based on flag input + pub(super) fn on_release(&mut self, keep_alive: bool) { + if keep_alive { + self.release(); + } else { + self.close(); + } + } + + /// Close connection + fn close(&mut self) { + let io = self.io.take().unwrap(); + self.acquired.close(ConnectionInnerType::H1(io)); + } + + /// Release this connection to the connection pool + fn release(&mut self) { + let io = self.io.take().unwrap(); + self.acquired + .release(ConnectionInnerType::H1(io), self.created); + } + + fn io_pin_mut(self: Pin<&mut Self>) -> Pin<&mut Io> { + Pin::new(self.get_mut().io.as_mut().unwrap()) + } +} + +impl AsyncRead for H1Connection { + fn poll_read( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &mut ReadBuf<'_>, + ) -> Poll> { + self.io_pin_mut().poll_read(cx, buf) + } +} + +impl AsyncWrite for H1Connection { + fn poll_write( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &[u8], + ) -> Poll> { + self.io_pin_mut().poll_write(cx, buf) + } + + fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + self.io_pin_mut().poll_flush(cx) + } + + fn poll_shutdown( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + ) -> Poll> { + self.io_pin_mut().poll_shutdown(cx) + } + + fn poll_write_vectored( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + bufs: &[io::IoSlice<'_>], + ) -> Poll> { + self.io_pin_mut().poll_write_vectored(cx, bufs) + } + + fn is_write_vectored(&self) -> bool { + self.io.as_ref().unwrap().is_write_vectored() + } +} + +/// HTTP2 client connection +pub struct H2Connection { + io: Option, + created: time::Instant, + acquired: Acquired, +} + +impl Deref for H2Connection { + type Target = SendRequest; + + fn deref(&self) -> &Self::Target { + &self.io.as_ref().unwrap().sender + } +} + +impl DerefMut for H2Connection { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.io.as_mut().unwrap().sender + } +} + +impl H2Connection { + /// close or release the connection to pool based on flag input + pub(super) fn on_release(&mut self, close: bool) { + if close { + self.close(); + } else { + self.release(); + } + } + + /// Close connection + fn close(&mut self) { + let io = self.io.take().unwrap(); + self.acquired.close(ConnectionInnerType::H2(io)); + } + + /// Release this connection to the connection pool + fn release(&mut self) { + let io = self.io.take().unwrap(); + self.acquired + .release(ConnectionInnerType::H2(io), self.created); + } +} + +/// `H2ConnectionInner` has two parts: `SendRequest` and `Connection`. /// -/// `Connection` is spawned as an async task on runtime and `H2Connection` holds a handle for -/// this task. Therefore, it can wake up and quit the task when SendRequest is dropped. -pub(crate) struct H2Connection { +/// `Connection` is spawned as an async task on runtime and `H2ConnectionInner` holds a handle +/// for this task. Therefore, it can wake up and quit the task when SendRequest is dropped. +pub(super) struct H2ConnectionInner { handle: JoinHandle<()>, sender: SendRequest, } -impl H2Connection { - pub(crate) fn new( +impl H2ConnectionInner { + pub(super) fn new( sender: SendRequest, connection: h2::client::Connection, - ) -> Self - where - Io: AsyncRead + AsyncWrite + Unpin + 'static, - { + ) -> Self { let handle = actix_rt::spawn(async move { let _ = connection.await; }); @@ -53,143 +171,80 @@ impl H2Connection { } } -// cancel spawned connection task on drop. -impl Drop for H2Connection { +/// Cancel spawned connection task on drop. +impl Drop for H2ConnectionInner { fn drop(&mut self) { self.handle.abort(); } } -// only expose sender type to public. -impl Deref for H2Connection { - type Target = SendRequest; - - fn deref(&self) -> &Self::Target { - &self.sender - } -} - -impl DerefMut for H2Connection { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.sender - } -} - -pub trait Connection { - type Io: AsyncRead + AsyncWrite + Unpin; - - /// Send request and body - fn send_request( - self, - head: H, - body: B, - ) -> LocalBoxFuture<'static, Result<(ResponseHead, Payload), SendRequestError>> - where - B: MessageBody + 'static, - H: Into + 'static; - - /// Send request, returns Response and Framed - fn open_tunnel + 'static>( - self, - head: H, - ) -> LocalBoxFuture< - 'static, - Result<(ResponseHead, Framed), SendRequestError>, - >; -} - -#[doc(hidden)] -/// HTTP client connection -pub struct IoConnection -where - T: AsyncWrite + Unpin + 'static, -{ - io: Option>, - created: time::Instant, - pool: Acquired, -} - -impl fmt::Debug for IoConnection -where - T: AsyncWrite + Unpin + fmt::Debug + 'static, -{ - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self.io { - Some(ConnectionType::H1(ref io)) => write!(f, "H1Connection({:?})", io), - Some(ConnectionType::H2(_)) => write!(f, "H2Connection"), - None => write!(f, "Connection(Empty)"), - } - } -} - -impl IoConnection { - pub(crate) fn new( - io: ConnectionType, - created: time::Instant, - pool: Acquired, - ) -> Self { - IoConnection { - pool, - created, - io: Some(io), - } - } - - #[cfg(test)] - pub(crate) fn into_parts(self) -> (ConnectionType, time::Instant, Acquired) { - (self.io.unwrap(), self.created, self.pool) - } - - async fn send_request>( - mut self, - head: H, - body: B, - ) -> Result<(ResponseHead, Payload), SendRequestError> { - match self.io.take().unwrap() { - ConnectionType::H1(io) => { - h1proto::send_request(io, head.into(), body, self.created, self.pool) - .await - } - ConnectionType::H2(io) => { - h2proto::send_request(io, head.into(), body, self.created, self.pool) - .await - } - } - } - - /// Send request, returns Response and Framed - async fn open_tunnel>( - mut self, - head: H, - ) -> Result<(ResponseHead, Framed), SendRequestError> { - match self.io.take().unwrap() { - ConnectionType::H1(io) => h1proto::open_tunnel(io, head.into()).await, - ConnectionType::H2(io) => { - self.pool.release(ConnectionType::H2(io), self.created); - Err(SendRequestError::TunnelNotSupported) - } - } - } -} - #[allow(dead_code)] -pub(crate) enum EitherIoConnection +/// Unified connection type cover Http1 Plain/Tls and Http2 protocols +pub enum Connection> where - A: AsyncRead + AsyncWrite + Unpin + 'static, - B: AsyncRead + AsyncWrite + Unpin + 'static, + A: ConnectionIo, + B: ConnectionIo, { - A(IoConnection
), - B(IoConnection), + Tcp(ConnectionType), + Tls(ConnectionType), } -impl Connection for EitherIoConnection -where - A: AsyncRead + AsyncWrite + Unpin + 'static, - B: AsyncRead + AsyncWrite + Unpin + 'static, -{ - type Io = EitherIo; +/// Unified connection type cover Http1/2 protocols +pub enum ConnectionType { + H1(H1Connection), + H2(H2Connection), +} - fn send_request( +/// Helper type for storing connection types in pool. +pub(super) enum ConnectionInnerType { + H1(Io), + H2(H2ConnectionInner), +} + +impl ConnectionType { + pub(super) fn from_pool( + inner: ConnectionInnerType, + created: time::Instant, + acquired: Acquired, + ) -> Self { + match inner { + ConnectionInnerType::H1(io) => Self::from_h1(io, created, acquired), + ConnectionInnerType::H2(io) => Self::from_h2(io, created, acquired), + } + } + + pub(super) fn from_h1( + io: Io, + created: time::Instant, + acquired: Acquired, + ) -> Self { + Self::H1(H1Connection { + io: Some(io), + created, + acquired, + }) + } + + pub(super) fn from_h2( + io: H2ConnectionInner, + created: time::Instant, + acquired: Acquired, + ) -> Self { + Self::H2(H2Connection { + io: Some(io), + created, + acquired, + }) + } +} + +impl Connection +where + A: ConnectionIo, + B: ConnectionIo, +{ + /// Send a request through connection. + pub fn send_request( self, head: H, body: RB, @@ -198,76 +253,106 @@ where RB: MessageBody + 'static, H: Into + 'static, { - match self { - EitherIoConnection::A(con) => Box::pin(con.send_request(head, body)), - EitherIoConnection::B(con) => Box::pin(con.send_request(head, body)), - } + Box::pin(async move { + match self { + Connection::Tcp(ConnectionType::H1(conn)) => { + h1proto::send_request(conn, head.into(), body).await + } + Connection::Tls(ConnectionType::H1(conn)) => { + h1proto::send_request(conn, head.into(), body).await + } + Connection::Tls(ConnectionType::H2(conn)) => { + h2proto::send_request(conn, head.into(), body).await + } + _ => unreachable!( + "Plain Tcp connection can be used only in Http1 protocol" + ), + } + }) } - /// Send request, returns Response and Framed - fn open_tunnel + 'static>( + /// Send request, returns Response and Framed tunnel. + pub fn open_tunnel + 'static>( self, head: H, ) -> LocalBoxFuture< 'static, - Result<(ResponseHead, Framed), SendRequestError>, + Result<(ResponseHead, Framed, ClientCodec>), SendRequestError>, > { - match self { - EitherIoConnection::A(con) => Box::pin(async { - let (head, framed) = con.open_tunnel(head).await?; - Ok((head, framed.into_map_io(EitherIo::A))) - }), - EitherIoConnection::B(con) => Box::pin(async { - let (head, framed) = con.open_tunnel(head).await?; - Ok((head, framed.into_map_io(EitherIo::B))) - }), - } + Box::pin(async move { + match self { + Connection::Tcp(ConnectionType::H1(ref _conn)) => { + let (head, framed) = h1proto::open_tunnel(self, head.into()).await?; + Ok((head, framed)) + } + Connection::Tls(ConnectionType::H1(ref _conn)) => { + let (head, framed) = h1proto::open_tunnel(self, head.into()).await?; + Ok((head, framed)) + } + Connection::Tls(ConnectionType::H2(mut conn)) => { + conn.release(); + Err(SendRequestError::TunnelNotSupported) + } + Connection::Tcp(ConnectionType::H2(_)) => { + unreachable!( + "Plain Tcp connection can be used only in Http1 protocol" + ) + } + } + }) } } -#[pin_project(project = EitherIoProj)] -pub enum EitherIo { - A(#[pin] A), - B(#[pin] B), -} - -impl AsyncRead for EitherIo +impl AsyncRead for Connection where - A: AsyncRead, - B: AsyncRead, + A: ConnectionIo, + B: ConnectionIo, { fn poll_read( self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &mut ReadBuf<'_>, ) -> Poll> { - match self.project() { - EitherIoProj::A(val) => val.poll_read(cx, buf), - EitherIoProj::B(val) => val.poll_read(cx, buf), + 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) + } + _ => unreachable!("H2Connection can not impl AsyncRead trait"), } } } -impl AsyncWrite for EitherIo +const H2_UNREACHABLE_WRITE: &'static str = "H2Connection can not impl AsyncWrite trait"; + +impl AsyncWrite for Connection where - A: AsyncWrite, - B: AsyncWrite, + A: ConnectionIo, + B: ConnectionIo, { fn poll_write( self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &[u8], ) -> Poll> { - match self.project() { - EitherIoProj::A(val) => val.poll_write(cx, buf), - EitherIoProj::B(val) => val.poll_write(cx, buf), + 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), } } fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - match self.project() { - EitherIoProj::A(val) => val.poll_flush(cx), - EitherIoProj::B(val) => val.poll_flush(cx), + 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), } } @@ -275,9 +360,38 @@ where self: Pin<&mut Self>, cx: &mut Context<'_>, ) -> Poll> { - match self.project() { - EitherIoProj::A(val) => val.poll_shutdown(cx), - EitherIoProj::B(val) => val.poll_shutdown(cx), + 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), + } + } + + fn poll_write_vectored( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + bufs: &[io::IoSlice<'_>], + ) -> Poll> { + match self.get_mut() { + Connection::Tcp(ConnectionType::H1(conn)) => { + Pin::new(conn).poll_write_vectored(cx, bufs) + } + Connection::Tls(ConnectionType::H1(conn)) => { + Pin::new(conn).poll_write_vectored(cx, bufs) + } + _ => unreachable!(H2_UNREACHABLE_WRITE), + } + } + + fn is_write_vectored(&self) -> bool { + 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), } } } @@ -300,10 +414,13 @@ mod test { let tcp = TcpStream::connect(local).await.unwrap(); let (sender, connection) = h2::client::handshake(tcp).await.unwrap(); - let conn = H2Connection::new(sender.clone(), connection); + let conn = H2ConnectionInner::new(sender.clone(), connection); assert!(sender.clone().ready().await.is_ok()); - assert!(h2::client::SendRequest::clone(&*conn).ready().await.is_ok()); + assert!(h2::client::SendRequest::clone(&conn.sender) + .ready() + .await + .is_ok()); drop(conn); diff --git a/actix-http/src/client/connector.rs b/actix-http/src/client/connector.rs index 0c9159b87..1a5f32880 100644 --- a/actix-http/src/client/connector.rs +++ b/actix-http/src/client/connector.rs @@ -3,22 +3,26 @@ use std::{ future::Future, net::IpAddr, pin::Pin, + rc::Rc, task::{Context, Poll}, time::Duration, }; -use actix_codec::{AsyncRead, AsyncWrite}; -use actix_rt::net::TcpStream; -use actix_service::{apply_fn, Service, ServiceExt}; -use actix_tls::connect::{ - new_connector, Connect as TcpConnect, Connection as TcpConnection, Resolver, +use actix_rt::{ + net::TcpStream, + time::{sleep, Sleep}, }; -use actix_utils::timeout::{TimeoutError, TimeoutService}; -use futures_core::ready; +use actix_service::Service; +use actix_tls::connect::{ + new_connector, Connect as TcpConnect, ConnectError as TcpConnectError, + Connection as TcpConnection, Resolver, +}; +use futures_core::{future::LocalBoxFuture, ready}; use http::Uri; +use pin_project::pin_project; use super::config::ConnectorConfig; -use super::connection::{Connection, ConnectionIo, EitherIoConnection}; +use super::connection::{Connection, ConnectionIo}; use super::error::ConnectError; use super::pool::ConnectionPool; use super::Connect; @@ -28,18 +32,15 @@ use super::Protocol; use actix_tls::connect::ssl::openssl::SslConnector as OpensslConnector; #[cfg(feature = "rustls")] use actix_tls::connect::ssl::rustls::ClientConfig; -#[cfg(feature = "rustls")] -use std::sync::Arc; -#[cfg(any(feature = "openssl", feature = "rustls"))] enum SslConnector { + #[allow(dead_code)] + None, #[cfg(feature = "openssl")] Openssl(OpensslConnector), #[cfg(feature = "rustls")] - Rustls(Arc), + Rustls(std::sync::Arc), } -#[cfg(not(any(feature = "openssl", feature = "rustls")))] -type SslConnector = (); /// Manages HTTP client network connectivity. /// @@ -104,23 +105,25 @@ impl Connector<()> { config.root_store.add_server_trust_anchors( &actix_tls::connect::ssl::rustls::TLS_SERVER_ROOTS, ); - SslConnector::Rustls(Arc::new(config)) + 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 {} + fn build_ssl(_: Vec>) -> SslConnector { + SslConnector::None + } } -impl Connector { +impl Connector { /// Use custom connector. - pub fn connector(self, connector: T1) -> Connector + pub fn connector(self, connector: S1) -> Connector where - U1: AsyncRead + AsyncWrite + Unpin + fmt::Debug, - T1: Service< + Io1: ConnectionIo + fmt::Debug, + S1: Service< TcpConnect, - Response = TcpConnection, - Error = actix_tls::connect::ConnectError, + Response = TcpConnection, + Error = TcpConnectError, > + Clone, { Connector { @@ -131,23 +134,30 @@ impl Connector { } } -impl Connector +impl Connector where - U: AsyncRead + AsyncWrite + Unpin + fmt::Debug + 'static, - T: Service< + Io: ConnectionIo + fmt::Debug, + S: Service< TcpConnect, - Response = TcpConnection, - Error = actix_tls::connect::ConnectError, + Response = TcpConnection, + Error = TcpConnectError, > + Clone + 'static, { - /// Connection timeout, i.e. max time to connect to remote host including dns name resolution. - /// Set to 5 second by default. + /// Tcp connection timeout, i.e. max time to connect to remote host including dns name + /// resolution. Set to 5 second by default. pub fn timeout(mut self, timeout: Duration) -> Self { self.config.timeout = timeout; self } + /// Tls handshake timeout, i.e. max time to do tls handshake with remote host after tcp + /// connection established. Set to 5 second by default. + pub fn handshake_timeout(mut self, timeout: Duration) -> Self { + self.config.handshake_timeout = timeout; + self + } + #[cfg(feature = "openssl")] /// Use custom `SslConnector` instance. pub fn ssl(mut self, connector: OpensslConnector) -> Self { @@ -157,7 +167,7 @@ where #[cfg(feature = "rustls")] /// Use custom `SslConnector` instance. - pub fn rustls(mut self, connector: Arc) -> Self { + pub fn rustls(mut self, connector: std::sync::Arc) -> Self { self.ssl = SslConnector::Rustls(connector); self } @@ -247,158 +257,369 @@ 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, - ) -> impl Service { + pub fn finish(self) -> ConnectorService { let local_address = self.config.local_address; let timeout = self.config.timeout; - let tcp_service = TimeoutService::new( - timeout, - apply_fn(self.connector.clone(), move |msg: Connect, srv| { - let mut req = TcpConnect::new(msg.uri).set_addr(msg.addr); + let tcp_service_inner = + TcpConnectorInnerService::new(self.connector, timeout, local_address); - if let Some(local_addr) = local_address { - req = req.set_local_addr(local_addr); + #[allow(clippy::redundant_clone)] + let tcp_service = TcpConnectorService { + service: tcp_service_inner.clone(), + }; + + let tls_service = match self.ssl { + SslConnector::None => None, + #[cfg(feature = "openssl")] + SslConnector::Openssl(tls) => { + const H2: &[u8] = b"h2"; + + use actix_tls::connect::ssl::openssl::{OpensslConnector, SslStream}; + + impl IntoConnectionIo for TcpConnection> { + fn into_connection_io(self) -> (Box, Protocol) { + let sock = self.into_parts().0; + let h2 = sock + .ssl() + .selected_alpn_protocol() + .map(|protos| protos.windows(2).any(|w| w == H2)) + .unwrap_or(false); + if h2 { + (Box::new(sock), Protocol::Http2) + } else { + (Box::new(sock), Protocol::Http1) + } + } } - srv.call(req) - }) - .map_err(ConnectError::from) - .map(|stream| (stream.into_parts().0, Protocol::Http1)), - ) - .map_err(|e| match e { - TimeoutError::Service(e) => e, - TimeoutError::Timeout => ConnectError::Timeout, - }); + let handshake_timeout = self.config.handshake_timeout; - #[cfg(not(any(feature = "openssl", feature = "rustls")))] - { - // A dummy service for annotate tls pool's type signature. - pub type DummyService = Box< - dyn Service< - Connect, - Response = (Box, Protocol), - Error = ConnectError, - Future = futures_core::future::LocalBoxFuture< - 'static, - Result<(Box, Protocol), ConnectError>, - >, - >, - >; + let tls_service = TlsConnectorService { + tcp_service: tcp_service_inner, + tls_service: OpensslConnector::service(tls), + timeout: handshake_timeout, + }; - InnerConnector::<_, DummyService, _, Box> { - tcp_pool: ConnectionPool::new( - tcp_service, - self.config.no_disconnect_timeout(), - ), - tls_pool: None, + Some(actix_service::boxed::rc_service(tls_service)) } - } - - #[cfg(any(feature = "openssl", feature = "rustls"))] - { - const H2: &[u8] = b"h2"; - use actix_service::{boxed::service, pipeline}; - #[cfg(feature = "openssl")] - use actix_tls::connect::ssl::openssl::OpensslConnector; #[cfg(feature = "rustls")] - use actix_tls::connect::ssl::rustls::{RustlsConnector, Session}; + SslConnector::Rustls(tls) => { + const H2: &[u8] = b"h2"; - let ssl_service = TimeoutService::new( - timeout, - pipeline( - apply_fn(self.connector.clone(), move |msg: Connect, srv| { - let mut req = TcpConnect::new(msg.uri).set_addr(msg.addr); + use actix_tls::connect::ssl::rustls::{ + RustlsConnector, Session, TlsStream, + }; - if let Some(local_addr) = local_address { - req = req.set_local_addr(local_addr); + 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(|protos| protos.windows(2).any(|w| w == H2)) + .unwrap_or(false); + if h2 { + (Box::new(sock), Protocol::Http2) + } else { + (Box::new(sock), Protocol::Http1) } + } + } - srv.call(req) - }) - .map_err(ConnectError::from), - ) - .and_then(match self.ssl { - #[cfg(feature = "openssl")] - SslConnector::Openssl(ssl) => service( - OpensslConnector::service(ssl) - .map(|stream| { - let sock = stream.into_parts().0; - let h2 = sock - .ssl() - .selected_alpn_protocol() - .map(|protos| protos.windows(2).any(|w| w == H2)) - .unwrap_or(false); - if h2 { - ( - Box::new(sock) as Box, - Protocol::Http2, - ) - } else { - (Box::new(sock) as _, Protocol::Http1) - } - }) - .map_err(ConnectError::from), - ), - #[cfg(feature = "rustls")] - SslConnector::Rustls(ssl) => service( - RustlsConnector::service(ssl) - .map_err(ConnectError::from) - .map(|stream| { - let sock = stream.into_parts().0; - let h2 = sock - .get_ref() - .1 - .get_alpn_protocol() - .map(|protos| protos.windows(2).any(|w| w == H2)) - .unwrap_or(false); - if h2 { - (Box::new(sock) as _, Protocol::Http2) - } else { - (Box::new(sock) as _, Protocol::Http1) - } - }), - ), - }), - ) - .map_err(|e| match e { - TimeoutError::Service(e) => e, - TimeoutError::Timeout => ConnectError::Timeout, - }); + let handshake_timeout = self.config.handshake_timeout; - InnerConnector { - tcp_pool: ConnectionPool::new( - tcp_service, - self.config.no_disconnect_timeout(), - ), - tls_pool: Some(ConnectionPool::new(ssl_service, self.config)), + let tls_service = TlsConnectorService { + tcp_service: tcp_service_inner, + tls_service: RustlsConnector::service(tls), + timeout: handshake_timeout, + }; + + Some(actix_service::boxed::rc_service(tls_service)) } + }; + + let tcp_config = self.config.no_disconnect_timeout(); + + 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)); + + ConnectorServicePriv { tcp_pool, tls_pool } + } +} + +/// tcp service for map `TcpConnection` type to `(Io, Protocol)` +#[derive(Clone)] +pub struct TcpConnectorService { + service: S, +} + +impl Service for TcpConnectorService +where + S: Service, Error = ConnectError> + + Clone + + 'static, +{ + type Response = (Io, Protocol); + type Error = ConnectError; + type Future = TcpConnectorFuture; + + actix_service::forward_ready!(service); + + fn call(&self, req: Connect) -> Self::Future { + TcpConnectorFuture { + fut: self.service.call(req), } } } -struct InnerConnector +#[pin_project] +pub struct TcpConnectorFuture { + #[pin] + fut: Fut, +} + +impl Future for TcpConnectorFuture where - S1: Service + 'static, - S2: Service + 'static, - Io1: AsyncRead + AsyncWrite + Unpin + 'static, - Io2: AsyncRead + AsyncWrite + Unpin + 'static, + Fut: Future, ConnectError>>, +{ + type Output = Result<(Io, Protocol), ConnectError>; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + self.project() + .fut + .poll(cx) + .map_ok(|res| (res.into_parts().0, Protocol::Http1)) + } +} + +/// 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, + timeout: Duration, +} + +impl Service for TlsConnectorService +where + S: Service, Error = ConnectError> + + Clone + + 'static, + St: Service, Response = Res, Error = std::io::Error> + + Clone + + 'static, + Io: ConnectionIo, + Res: IntoConnectionIo, +{ + type Response = (Box, Protocol); + type Error = ConnectError; + type Future = TlsConnectorFuture; + + fn poll_ready(&self, cx: &mut Context<'_>) -> Poll> { + ready!(self.tcp_service.poll_ready(cx))?; + ready!(self.tls_service.poll_ready(cx))?; + Poll::Ready(Ok(())) + } + + fn call(&self, req: Connect) -> Self::Future { + let fut = self.tcp_service.call(req); + let tls_service = self.tls_service.clone(); + let timeout = self.timeout; + + TlsConnectorFuture::TcpConnect { + fut, + tls_service: Some(tls_service), + timeout, + } + } +} + +#[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); +} + +impl Future for TlsConnectorFuture +where + S: Service< + TcpConnection, + Response = Res, + Error = std::io::Error, + Future = Fut2, + >, + Fut1: Future, ConnectError>>, + Fut2: Future>, + Io: ConnectionIo, + Res: IntoConnectionIo, +{ + type Output = Result<(Box, Protocol), ConnectError>; + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + match self.as_mut().project() { + TlsConnectorProj::TcpConnect { + fut, + tls_service, + timeout, + } => { + let res = ready!(fut.poll(cx))?; + let fut = tls_service + .take() + .expect("TlsConnectorFuture polled after complete") + .call(res); + let timeout = sleep(*timeout); + self.set(TlsConnectorFuture::TlsConnect { fut, timeout }); + self.poll(cx) + } + TlsConnectorProj::TlsConnect { fut, timeout } => match fut.poll(cx)? { + Poll::Ready(res) => Poll::Ready(Ok(res.into_connection_io())), + Poll::Pending => timeout.poll(cx).map(|_| Err(ConnectError::Timeout)), + }, + } + } +} + +/// service for establish tcp connection. +/// operation is canceled when timeout limit reached. +#[derive(Clone)] +pub struct TcpConnectorInnerService { + service: S, + timeout: Duration, + local_address: Option, +} + +impl TcpConnectorInnerService { + fn new( + service: S, + timeout: Duration, + local_address: Option, + ) -> Self { + Self { + service, + timeout, + local_address, + } + } +} + +impl Service for TcpConnectorInnerService +where + S: Service< + TcpConnect, + Response = TcpConnection, + Error = TcpConnectError, + > + Clone + + 'static, +{ + type Response = S::Response; + type Error = ConnectError; + type Future = TcpConnectorInnerFuture; + + actix_service::forward_ready!(service); + + fn call(&self, req: Connect) -> Self::Future { + let mut req = TcpConnect::new(req.uri).set_addr(req.addr); + + if let Some(local_addr) = self.local_address { + req = req.set_local_addr(local_addr); + } + + TcpConnectorInnerFuture { + fut: self.service.call(req), + timeout: sleep(self.timeout), + } + } +} + +#[pin_project] +pub struct TcpConnectorInnerFuture { + #[pin] + fut: Fut, + #[pin] + timeout: Sleep, +} + +impl Future for TcpConnectorInnerFuture +where + Fut: Future, TcpConnectError>>, +{ + type Output = Result, ConnectError>; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let this = self.project(); + match this.fut.poll(cx) { + Poll::Ready(res) => Poll::Ready(res.map_err(ConnectError::from)), + Poll::Pending => this.timeout.poll(cx).map(|_| Err(ConnectError::Timeout)), + } + } +} + +/// Connector service for pooled Plain/Tls Tcp connections. +pub type ConnectorService = ConnectorServicePriv< + TcpConnectorService>, + Rc< + dyn Service< + Connect, + Response = (Box, Protocol), + Error = ConnectError, + Future = LocalBoxFuture< + 'static, + Result<(Box, Protocol), ConnectError>, + >, + >, + >, + Io, + Box, +>; + +pub struct ConnectorServicePriv +where + S1: Service, + S2: Service, + Io1: ConnectionIo, + Io2: ConnectionIo, { tcp_pool: ConnectionPool, tls_pool: Option>, } -impl Service for InnerConnector +impl Service for ConnectorServicePriv where - S1: Service + 'static, - S2: Service + 'static, - Io1: AsyncRead + AsyncWrite + Unpin + 'static, - Io2: AsyncRead + AsyncWrite + Unpin + 'static, + S1: Service + + Clone + + 'static, + S2: Service + + Clone + + 'static, + Io1: ConnectionIo, + Io2: ConnectionIo, { - type Response = EitherIoConnection; + type Response = Connection; type Error = ConnectError; - type Future = InnerConnectorResponse; + type Future = ConnectorServiceFuture; fn poll_ready(&self, cx: &mut Context<'_>) -> Poll> { ready!(self.tcp_pool.poll_ready(cx))?; @@ -411,41 +632,49 @@ where fn call(&self, req: Connect) -> Self::Future { match req.uri.scheme_str() { Some("https") | Some("wss") => match self.tls_pool { - None => InnerConnectorResponse::SslIsNotSupported, - Some(ref pool) => InnerConnectorResponse::Io2(pool.call(req)), + None => ConnectorServiceFuture::SslIsNotSupported, + Some(ref pool) => ConnectorServiceFuture::Tls(pool.call(req)), }, - _ => InnerConnectorResponse::Io1(self.tcp_pool.call(req)), + _ => ConnectorServiceFuture::Tcp(self.tcp_pool.call(req)), } } } -#[pin_project::pin_project(project = InnerConnectorProj)] -enum InnerConnectorResponse +#[pin_project(project = ConnectorServiceProj)] +pub enum ConnectorServiceFuture where - S1: Service + 'static, - S2: Service + 'static, - Io1: AsyncRead + AsyncWrite + Unpin + 'static, - Io2: AsyncRead + AsyncWrite + Unpin + 'static, + S1: Service + + Clone + + 'static, + S2: Service + + Clone + + 'static, + Io1: ConnectionIo, + Io2: ConnectionIo, { - Io1(#[pin] as Service>::Future), - Io2(#[pin] as Service>::Future), + Tcp(#[pin] as Service>::Future), + Tls(#[pin] as Service>::Future), SslIsNotSupported, } -impl Future for InnerConnectorResponse +impl Future for ConnectorServiceFuture where - S1: Service + 'static, - S2: Service + 'static, - Io1: AsyncRead + AsyncWrite + Unpin + 'static, - Io2: AsyncRead + AsyncWrite + Unpin + 'static, + S1: Service + + Clone + + 'static, + S2: Service + + Clone + + 'static, + Io1: ConnectionIo, + Io2: ConnectionIo, { - type Output = Result, ConnectError>; + type Output = Result, ConnectError>; fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { match self.project() { - InnerConnectorProj::Io1(fut) => fut.poll(cx).map_ok(EitherIoConnection::A), - InnerConnectorProj::Io2(fut) => fut.poll(cx).map_ok(EitherIoConnection::B), - InnerConnectorProj::SslIsNotSupported => { + ConnectorServiceProj::Tcp(fut) => fut.poll(cx).map_ok(Connection::Tcp), + ConnectorServiceProj::Tls(fut) => fut.poll(cx).map_ok(Connection::Tls), + ConnectorServiceProj::SslIsNotSupported => { Poll::Ready(Err(ConnectError::SslIsNotSupported)) } } diff --git a/actix-http/src/client/h1proto.rs b/actix-http/src/client/h1proto.rs index 75a3c1f3d..01a6e1edf 100644 --- a/actix-http/src/client/h1proto.rs +++ b/actix-http/src/client/h1proto.rs @@ -1,9 +1,10 @@ -use std::io::Write; -use std::pin::Pin; -use std::task::{Context, Poll}; -use std::{io, time}; +use std::{ + io::Write, + pin::Pin, + task::{Context, Poll}, +}; -use actix_codec::{AsyncRead, AsyncWrite, Framed, ReadBuf}; +use actix_codec::Framed; use bytes::buf::BufMut; use bytes::{Bytes, BytesMut}; use futures_core::Stream; @@ -11,28 +12,24 @@ use futures_util::{future::poll_fn, SinkExt as _}; use crate::error::PayloadError; use crate::h1; -use crate::header::HeaderMap; use crate::http::{ - header::{IntoHeaderValue, EXPECT, HOST}, + header::{HeaderMap, IntoHeaderValue, EXPECT, HOST}, StatusCode, }; use crate::message::{RequestHeadType, ResponseHead}; use crate::payload::{Payload, PayloadStream}; -use super::connection::ConnectionType; +use super::connection::{ConnectionIo, H1Connection}; use super::error::{ConnectError, SendRequestError}; -use super::pool::Acquired; use crate::body::{BodySize, MessageBody}; -pub(crate) async fn send_request( - io: T, +pub(crate) async fn send_request( + io: H1Connection, mut head: RequestHeadType, body: B, - created: time::Instant, - acquired: Acquired, ) -> Result<(ResponseHead, Payload), SendRequestError> where - T: AsyncRead + AsyncWrite + Unpin + 'static, + Io: ConnectionIo, B: MessageBody, { // set request host header @@ -62,12 +59,6 @@ where } } - let io = H1Connection { - created, - acquired, - io: Some(io), - }; - // create Framed and prepare sending request let mut framed = Framed::new(io, h1::ClientCodec::default()); @@ -138,18 +129,18 @@ where } } -pub(crate) async fn open_tunnel( - io: T, +pub(crate) async fn open_tunnel( + io: Io, head: RequestHeadType, -) -> Result<(ResponseHead, Framed), SendRequestError> +) -> Result<(ResponseHead, Framed), SendRequestError> where - T: AsyncRead + AsyncWrite + Unpin + 'static, + Io: ConnectionIo, { - // create Framed and send request + // create Framed and send request. let mut framed = Framed::new(io, h1::ClientCodec::default()); framed.send((head, BodySize::None).into()).await?; - // read response + // read response head. let head = poll_fn(|cx| Pin::new(&mut framed).poll_next(cx)) .await .ok_or(ConnectError::Disconnected)??; @@ -158,12 +149,12 @@ where } /// send request body to the peer -pub(crate) async fn send_body( +pub(crate) async fn send_body( body: B, - mut framed: Pin<&mut Framed>, + mut framed: Pin<&mut Framed>, ) -> Result<(), SendRequestError> where - T: AsyncRead + AsyncWrite + Unpin + 'static, + Io: ConnectionIo, B: MessageBody, { actix_rt::pin!(body); @@ -202,92 +193,16 @@ where Ok(()) } -#[doc(hidden)] -/// HTTP client connection -pub struct H1Connection -where - T: AsyncRead + AsyncWrite + Unpin + 'static, -{ - /// T should be `Unpin` - io: Option, - created: time::Instant, - acquired: Acquired, -} - -impl H1Connection -where - T: AsyncRead + AsyncWrite + Unpin + 'static, -{ - fn on_release(&mut self, keep_alive: bool) { - if keep_alive { - self.release(); - } else { - self.close(); - } - } - - /// Close connection - fn close(&mut self) { - if let Some(io) = self.io.take() { - self.acquired.close(ConnectionType::H1(io)); - } - } - - /// Release this connection to the connection pool - fn release(&mut self) { - if let Some(io) = self.io.take() { - self.acquired.release(ConnectionType::H1(io), self.created); - } - } -} - -impl AsyncRead for H1Connection { - fn poll_read( - mut self: Pin<&mut Self>, - cx: &mut Context<'_>, - buf: &mut ReadBuf<'_>, - ) -> Poll> { - Pin::new(&mut self.io.as_mut().unwrap()).poll_read(cx, buf) - } -} - -impl AsyncWrite for H1Connection { - fn poll_write( - mut self: Pin<&mut Self>, - cx: &mut Context<'_>, - buf: &[u8], - ) -> Poll> { - Pin::new(&mut self.io.as_mut().unwrap()).poll_write(cx, buf) - } - - fn poll_flush( - mut self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll> { - Pin::new(self.io.as_mut().unwrap()).poll_flush(cx) - } - - fn poll_shutdown( - mut self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll> { - Pin::new(self.io.as_mut().unwrap()).poll_shutdown(cx) - } -} - #[pin_project::pin_project] -pub(crate) struct PlStream +pub(crate) struct PlStream where - Io: AsyncRead + AsyncWrite + Unpin + 'static, + Io: ConnectionIo, { #[pin] framed: Option, h1::ClientPayloadCodec>>, } -impl PlStream -where - Io: AsyncRead + AsyncWrite + Unpin + 'static, -{ +impl PlStream { fn new(framed: Framed, h1::ClientCodec>) -> Self { let framed = framed.into_map_codec(|codec| codec.into_payload_codec()); @@ -297,10 +212,7 @@ where } } -impl Stream for PlStream -where - Io: AsyncRead + AsyncWrite + Unpin + 'static, -{ +impl Stream for PlStream { type Item = Result; fn poll_next( diff --git a/actix-http/src/client/h2proto.rs b/actix-http/src/client/h2proto.rs index 82e81c7ff..437b9ae76 100644 --- a/actix-http/src/client/h2proto.rs +++ b/actix-http/src/client/h2proto.rs @@ -1,7 +1,5 @@ use std::future::Future; -use std::time; -use actix_codec::{AsyncRead, AsyncWrite}; use bytes::Bytes; use futures_util::future::poll_fn; use h2::{ @@ -17,20 +15,16 @@ use crate::message::{RequestHeadType, ResponseHead}; use crate::payload::Payload; use super::config::ConnectorConfig; -use super::connection::ConnectionType; +use super::connection::{ConnectionIo, H2Connection}; use super::error::SendRequestError; -use super::pool::Acquired; -use crate::client::connection::H2Connection; -pub(crate) async fn send_request( - mut io: H2Connection, +pub(crate) async fn send_request( + mut io: H2Connection, head: RequestHeadType, body: B, - created: time::Instant, - acquired: Acquired, ) -> Result<(ResponseHead, Payload), SendRequestError> where - T: AsyncRead + AsyncWrite + Unpin + 'static, + Io: ConnectionIo, B: MessageBody, { trace!("Sending client request: {:?} {:?}", head, body.size()); @@ -103,13 +97,13 @@ where let res = poll_fn(|cx| io.poll_ready(cx)).await; if let Err(e) = res { - release(io, acquired, created, e.is_io()); + io.on_release(e.is_io()); return Err(SendRequestError::from(e)); } let resp = match io.send_request(req, eof) { Ok((fut, send)) => { - release(io, acquired, created, false); + io.on_release(false); if !eof { send_body(body, send).await?; @@ -117,7 +111,7 @@ where fut.await.map_err(SendRequestError::from)? } Err(e) => { - release(io, acquired, created, e.is_io()); + io.on_release(e.is_io()); return Err(e.into()); } }; @@ -178,26 +172,10 @@ async fn send_body( } } -/// release SendRequest object -fn release( - io: H2Connection, - acquired: Acquired, - created: time::Instant, - close: bool, -) { - if close { - acquired.close(ConnectionType::H2(io)); - } else { - acquired.release(ConnectionType::H2(io), created); - } -} - -pub(crate) fn handshake( +pub(crate) fn handshake( io: Io, config: &ConnectorConfig, ) -> impl Future, Connection), h2::Error>> -where - Io: AsyncRead + AsyncWrite + Unpin + 'static, { let mut builder = Builder::new(); builder diff --git a/actix-http/src/client/mod.rs b/actix-http/src/client/mod.rs index ce6aa3bc1..41d5fef2a 100644 --- a/actix-http/src/client/mod.rs +++ b/actix-http/src/client/mod.rs @@ -15,7 +15,7 @@ pub use actix_tls::connect::{ }; pub use self::connection::{Connection, ConnectionIo}; -pub use self::connector::Connector; +pub use self::connector::{Connector, ConnectorService}; pub use self::error::{ConnectError, FreezeRequestError, InvalidUrl, SendRequestError}; pub use crate::Protocol; diff --git a/actix-http/src/client/pool.rs b/actix-http/src/client/pool.rs index 79adcf3e2..88188038f 100644 --- a/actix-http/src/client/pool.rs +++ b/actix-http/src/client/pool.rs @@ -1,34 +1,38 @@ //! Client connection pooling keyed on the authority part of the connection URI. -use std::collections::VecDeque; -use std::future::Future; -use std::ops::Deref; -use std::pin::Pin; -use std::rc::Rc; -use std::sync::Arc; -use std::task::{Context, Poll}; -use std::time::{Duration, Instant}; -use std::{cell::RefCell, io}; +use std::{ + cell::RefCell, + collections::VecDeque, + future::Future, + io, + ops::Deref, + pin::Pin, + rc::Rc, + sync::Arc, + task::{Context, Poll}, + time::{Duration, Instant}, +}; -use actix_codec::{AsyncRead, AsyncWrite}; +use actix_codec::{AsyncRead, AsyncWrite, ReadBuf}; 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 tokio::io::ReadBuf; use tokio::sync::{OwnedSemaphorePermit, Semaphore}; use super::config::ConnectorConfig; -use super::connection::{ConnectionType, H2Connection, IoConnection}; +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(crate) struct Key { +pub struct Key { authority: Authority, } @@ -38,17 +42,18 @@ impl From for Key { } } +#[doc(hidden)] /// Connections pool for reuse Io type for certain [`http::uri::Authority`] as key. -pub(crate) struct ConnectionPool +pub struct ConnectionPool where Io: AsyncWrite + Unpin + 'static, { - connector: Rc, + connector: S, inner: ConnectionPoolInner, } /// wrapper type for check the ref count of Rc. -struct ConnectionPoolInner(Rc>) +pub struct ConnectionPoolInner(Rc>) where Io: AsyncWrite + Unpin + 'static; @@ -56,10 +61,21 @@ impl ConnectionPoolInner where Io: AsyncWrite + Unpin + 'static, { + fn new(config: ConnectorConfig) -> Self { + let permits = Arc::new(Semaphore::new(config.limit)); + let available = RefCell::new(AHashMap::default()); + + Self(Rc::new(ConnectionPoolInnerPriv { + config, + available, + permits, + })) + } + /// spawn a async for graceful shutdown h1 Io type with a timeout. - fn close(&self, conn: ConnectionType) { + fn close(&self, conn: ConnectionInnerType) { if let Some(timeout) = self.config.disconnect_timeout { - if let ConnectionType::H1(io) = conn { + if let ConnectionInnerType::H1(io) = conn { actix_rt::spawn(CloseConnection::new(io, timeout)); } } @@ -104,7 +120,7 @@ where } } -struct ConnectionPoolInnerPriv +pub struct ConnectionPoolInnerPriv where Io: AsyncWrite + Unpin + 'static, { @@ -128,15 +144,7 @@ where /// Any requests beyond limit would be wait in fifo order and get notified in async manner /// by [`tokio::sync::Semaphore`] pub(crate) fn new(connector: S, config: ConnectorConfig) -> Self { - let permits = Arc::new(Semaphore::new(config.limit)); - let available = RefCell::new(AHashMap::default()); - let connector = Rc::new(connector); - - let inner = ConnectionPoolInner(Rc::new(ConnectionPoolInnerPriv { - config, - available, - permits, - })); + let inner = ConnectionPoolInner::new(config); Self { connector, inner } } @@ -144,12 +152,14 @@ where impl Service for ConnectionPool where - S: Service + 'static, - Io: AsyncRead + AsyncWrite + Unpin + 'static, + S: Service + + Clone + + 'static, + Io: ConnectionIo, { - type Response = IoConnection; + type Response = ConnectionType; type Error = ConnectError; - type Future = LocalBoxFuture<'static, Result, ConnectError>>; + type Future = LocalBoxFuture<'static, Result>; actix_service::forward_ready!(connector); @@ -193,7 +203,7 @@ where inner.close(c.conn); } else { // check if the connection is still usable - if let ConnectionType::H1(ref mut io) = c.conn { + if let ConnectionInnerType::H1(ref mut io) = c.conn { let check = ConnectionCheckFuture { io }; match check.await { ConnectionState::Tainted => { @@ -221,7 +231,9 @@ where // match the connection and spawn new one if did not get anything. match conn { - Some(conn) => Ok(IoConnection::new(conn.conn, conn.created, acquired)), + Some(conn) => { + Ok(ConnectionType::from_pool(conn.conn, conn.created, acquired)) + } None => { let (io, proto) = connector.call(req).await?; @@ -229,19 +241,12 @@ where assert!(proto != Protocol::Http3); if proto == Protocol::Http1 { - Ok(IoConnection::new( - ConnectionType::H1(io), - Instant::now(), - acquired, - )) + Ok(ConnectionType::from_h1(io, Instant::now(), acquired)) } else { let config = &acquired.inner.config; let (sender, connection) = handshake(io, config).await?; - Ok(IoConnection::new( - ConnectionType::H2(H2Connection::new(sender, connection)), - Instant::now(), - acquired, - )) + let inner = H2ConnectionInner::new(sender, connection); + Ok(ConnectionType::from_h2(inner, Instant::now(), acquired)) } } } @@ -292,7 +297,7 @@ where } struct PooledConnection { - conn: ConnectionType, + conn: ConnectionInnerType, used: Instant, created: Instant, } @@ -332,26 +337,26 @@ where } } -pub(crate) struct Acquired +pub struct Acquired where Io: AsyncWrite + Unpin + 'static, { + /// authority key for identify connection. key: Key, + /// handle to connection pool. inner: ConnectionPoolInner, + /// permit for limit concurrent in-flight connection for a Client object. permit: OwnedSemaphorePermit, } -impl Acquired -where - Io: AsyncRead + AsyncWrite + Unpin + 'static, -{ +impl Acquired { /// Close the IO. - pub(crate) fn close(&self, conn: ConnectionType) { + pub(super) fn close(&self, conn: ConnectionInnerType) { self.inner.close(conn); } /// Release IO back into pool. - pub(crate) fn release(&self, conn: ConnectionType, created: Instant) { + pub(super) fn release(&self, conn: ConnectionInnerType, created: Instant) { let Acquired { key, inner, .. } = self; inner @@ -376,7 +381,7 @@ mod test { use http::Uri; use super::*; - use crate::client::connection::IoConnection; + use crate::client::connection::ConnectionType; /// A stream type that always returns pending on async read. /// @@ -423,6 +428,7 @@ mod test { } } + #[derive(Clone)] struct TestPoolConnector { generated: Rc>, } @@ -441,12 +447,14 @@ mod test { } } - fn release(conn: IoConnection) + fn release(conn: ConnectionType) where T: AsyncRead + AsyncWrite + Unpin + 'static, { - let (conn, created, acquired) = conn.into_parts(); - acquired.release(conn, created); + match conn { + ConnectionType::H1(mut conn) => conn.on_release(true), + ConnectionType::H2(mut conn) => conn.on_release(false), + } } #[actix_rt::test] diff --git a/awc/CHANGES.md b/awc/CHANGES.md index bf7bffc49..d2cb7c009 100644 --- a/awc/CHANGES.md +++ b/awc/CHANGES.md @@ -1,6 +1,10 @@ # Changes ## Unreleased - 2021-xx-xx +### Changed +* `ConnectorService` type is renamed to `BoxConnectorService` [#2081] + +[#2081]: https://github.com/actix/actix-web/pull/2081 ## 3.0.0-beta.3 - 2021-03-08 diff --git a/awc/src/builder.rs b/awc/src/builder.rs index 33f43ab93..925d9ae2a 100644 --- a/awc/src/builder.rs +++ b/awc/src/builder.rs @@ -6,7 +6,7 @@ use std::time::Duration; use actix_codec::{AsyncRead, AsyncWrite}; use actix_http::{ - client::{Connector, TcpConnect, TcpConnectError, TcpConnection}, + client::{Connector, ConnectorService, TcpConnect, TcpConnectError, TcpConnection}, http::{self, header, Error as HttpError, HeaderMap, HeaderName, Uri}, }; use actix_rt::net::TcpStream; @@ -15,7 +15,7 @@ use actix_service::{boxed, Service}; use crate::connect::DefaultConnector; use crate::error::SendRequestError; use crate::middleware::{NestTransform, Redirect, Transform}; -use crate::{Client, ClientConfig, ConnectRequest, ConnectResponse, ConnectorService}; +use crate::{Client, ClientConfig, ConnectRequest, ConnectResponse}; /// An HTTP Client builder /// @@ -234,7 +234,7 @@ where /// Finish build process and create `Client` instance. pub fn finish(self) -> Client where - M: Transform + 'static, + M: Transform>, ConnectRequest> + 'static, M::Transform: Service, { @@ -250,7 +250,7 @@ where fn _finish(self) -> Client where - M: Transform + 'static, + M: Transform>, ConnectRequest> + 'static, M::Transform: Service, { @@ -269,16 +269,14 @@ where connector = connector.local_address(val); } - let connector = boxed::service(DefaultConnector::new(connector.finish())); - let connector = boxed::service(self.middleware.new_transform(connector)); + let connector = DefaultConnector::new(connector.finish()); + let connector = boxed::rc_service(self.middleware.new_transform(connector)); - let config = ClientConfig { - headers: self.headers, + Client(ClientConfig { + headers: Rc::new(self.headers), timeout: self.timeout, connector, - }; - - Client(Rc::new(config)) + }) } } diff --git a/awc/src/connect.rs b/awc/src/connect.rs index d3bc01ed6..6a9fc4630 100644 --- a/awc/src/connect.rs +++ b/awc/src/connect.rs @@ -2,6 +2,7 @@ use std::{ future::Future, net, pin::Pin, + rc::Rc, task::{Context, Poll}, }; @@ -19,7 +20,7 @@ use futures_core::{future::LocalBoxFuture, ready}; use crate::response::ClientResponse; -pub type ConnectorService = Box< +pub type BoxConnectorService = Rc< dyn Service< ConnectRequest, Response = ConnectResponse, @@ -28,6 +29,8 @@ pub type ConnectorService = Box< >, >; +pub type BoxedSocket = Box; + pub enum ConnectRequest { Client(RequestHeadType, Body, Option), Tunnel(RequestHead, Option), @@ -58,7 +61,7 @@ impl ConnectResponse { } } -pub(crate) struct DefaultConnector { +pub struct DefaultConnector { connector: S, } @@ -68,15 +71,14 @@ impl DefaultConnector { } } -impl Service for DefaultConnector +impl Service for DefaultConnector where - S: Service, - S::Response: Connection, - ::Io: 'static, + S: Service>, + Io: ConnectionIo, { type Response = ConnectResponse; type Error = SendRequestError; - type Future = ConnectRequestFuture::Io>; + type Future = ConnectRequestFuture; actix_service::forward_ready!(connector); @@ -102,7 +104,10 @@ where pin_project_lite::pin_project! { #[project = ConnectRequestProj] - pub(crate) enum ConnectRequestFuture { + pub enum ConnectRequestFuture + where + Io: ConnectionIo + { Connection { #[pin] fut: Fut, @@ -114,16 +119,15 @@ pin_project_lite::pin_project! { Tunnel { fut: LocalBoxFuture< 'static, - Result<(ResponseHead, Framed), SendRequestError>, + Result<(ResponseHead, Framed, ClientCodec>), SendRequestError>, >, } } } -impl Future for ConnectRequestFuture +impl Future for ConnectRequestFuture where - Fut: Future>, - C: Connection, + Fut: Future, ConnectError>>, Io: ConnectionIo, { type Output = Result; @@ -165,5 +169,3 @@ where } } } - -pub type BoxedSocket = Box; diff --git a/awc/src/frozen.rs b/awc/src/frozen.rs index 46b4063a0..5fe8edb19 100644 --- a/awc/src/frozen.rs +++ b/awc/src/frozen.rs @@ -23,7 +23,7 @@ pub struct FrozenClientRequest { pub(crate) addr: Option, pub(crate) response_decompress: bool, pub(crate) timeout: Option, - pub(crate) config: Rc, + pub(crate) config: ClientConfig, } impl FrozenClientRequest { @@ -51,7 +51,7 @@ impl FrozenClientRequest { self.addr, self.response_decompress, self.timeout, - self.config.as_ref(), + &self.config, body, ) } @@ -62,7 +62,7 @@ impl FrozenClientRequest { self.addr, self.response_decompress, self.timeout, - self.config.as_ref(), + &self.config, value, ) } @@ -73,7 +73,7 @@ impl FrozenClientRequest { self.addr, self.response_decompress, self.timeout, - self.config.as_ref(), + &self.config, value, ) } @@ -88,7 +88,7 @@ impl FrozenClientRequest { self.addr, self.response_decompress, self.timeout, - self.config.as_ref(), + &self.config, stream, ) } @@ -99,7 +99,7 @@ impl FrozenClientRequest { self.addr, self.response_decompress, self.timeout, - self.config.as_ref(), + &self.config, ) } @@ -168,7 +168,7 @@ impl FrozenSendBuilder { self.req.addr, self.req.response_decompress, self.req.timeout, - self.req.config.as_ref(), + &self.req.config, body, ) } @@ -183,7 +183,7 @@ impl FrozenSendBuilder { self.req.addr, self.req.response_decompress, self.req.timeout, - self.req.config.as_ref(), + &self.req.config, value, ) } @@ -198,7 +198,7 @@ impl FrozenSendBuilder { self.req.addr, self.req.response_decompress, self.req.timeout, - self.req.config.as_ref(), + &self.req.config, value, ) } @@ -217,7 +217,7 @@ impl FrozenSendBuilder { self.req.addr, self.req.response_decompress, self.req.timeout, - self.req.config.as_ref(), + &self.req.config, stream, ) } @@ -232,7 +232,7 @@ impl FrozenSendBuilder { self.req.addr, self.req.response_decompress, self.req.timeout, - self.req.config.as_ref(), + &self.req.config, ) } } diff --git a/awc/src/lib.rs b/awc/src/lib.rs index 77e08fbbd..8addf4d8b 100644 --- a/awc/src/lib.rs +++ b/awc/src/lib.rs @@ -121,7 +121,7 @@ pub mod test; pub mod ws; pub use self::builder::ClientBuilder; -pub use self::connect::{BoxedSocket, ConnectRequest, ConnectResponse, ConnectorService}; +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}; @@ -147,11 +147,12 @@ pub use self::sender::SendClientRequest; /// } /// ``` #[derive(Clone)] -pub struct Client(Rc); +pub struct Client(ClientConfig); +#[derive(Clone)] pub(crate) struct ClientConfig { - pub(crate) connector: ConnectorService, - pub(crate) headers: HeaderMap, + pub(crate) connector: BoxConnectorService, + pub(crate) headers: Rc, pub(crate) timeout: Option, } diff --git a/awc/src/middleware/redirect.rs b/awc/src/middleware/redirect.rs index f8bdd2def..62ea1d0ac 100644 --- a/awc/src/middleware/redirect.rs +++ b/awc/src/middleware/redirect.rs @@ -189,7 +189,7 @@ where // remove body .call(ConnectRequest::Client(head, Body::None, addr)); - self.as_mut().set(RedirectServiceFuture::Client { + self.set(RedirectServiceFuture::Client { fut, max_redirect_times, uri: Some(uri), @@ -236,7 +236,7 @@ where .unwrap() .call(ConnectRequest::Client(head, body_new, addr)); - self.as_mut().set(RedirectServiceFuture::Client { + self.set(RedirectServiceFuture::Client { fut, max_redirect_times, uri: Some(uri), diff --git a/awc/src/request.rs b/awc/src/request.rs index 1b63f3687..3e9babb7d 100644 --- a/awc/src/request.rs +++ b/awc/src/request.rs @@ -57,7 +57,7 @@ pub struct ClientRequest { addr: Option, response_decompress: bool, timeout: Option, - config: Rc, + config: ClientConfig, #[cfg(feature = "cookies")] cookies: Option, @@ -65,7 +65,7 @@ pub struct ClientRequest { impl ClientRequest { /// Create new client request builder. - pub(crate) fn new(method: Method, uri: U, config: Rc) -> Self + pub(crate) fn new(method: Method, uri: U, config: ClientConfig) -> Self where Uri: TryFrom, >::Error: Into, @@ -398,7 +398,7 @@ impl ClientRequest { slf.addr, slf.response_decompress, slf.timeout, - slf.config.as_ref(), + &slf.config, body, ) } @@ -414,7 +414,7 @@ impl ClientRequest { slf.addr, slf.response_decompress, slf.timeout, - slf.config.as_ref(), + &slf.config, value, ) } @@ -432,7 +432,7 @@ impl ClientRequest { slf.addr, slf.response_decompress, slf.timeout, - slf.config.as_ref(), + &slf.config, value, ) } @@ -452,7 +452,7 @@ impl ClientRequest { slf.addr, slf.response_decompress, slf.timeout, - slf.config.as_ref(), + &slf.config, stream, ) } @@ -468,7 +468,7 @@ impl ClientRequest { slf.addr, slf.response_decompress, slf.timeout, - slf.config.as_ref(), + &slf.config, ) } diff --git a/awc/src/ws.rs b/awc/src/ws.rs index f64e9e19a..df25b7289 100644 --- a/awc/src/ws.rs +++ b/awc/src/ws.rs @@ -28,7 +28,6 @@ use std::convert::TryFrom; use std::net::SocketAddr; -use std::rc::Rc; use std::{fmt, str}; use actix_codec::Framed; @@ -56,7 +55,7 @@ pub struct WebsocketsRequest { addr: Option, max_size: usize, server_mode: bool, - config: Rc, + config: ClientConfig, #[cfg(feature = "cookies")] cookies: Option, @@ -64,7 +63,7 @@ pub struct WebsocketsRequest { impl WebsocketsRequest { /// Create new WebSocket connection - pub(crate) fn new(uri: U, config: Rc) -> Self + pub(crate) fn new(uri: U, config: ClientConfig) -> Self where Uri: TryFrom, >::Error: Into, From 81942d31d66b2a3fd4a5ed673ce31d21ae8ec5fa Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Fri, 19 Mar 2021 02:02:30 +0000 Subject: [PATCH 110/339] fix new dyn trait lint --- actix-http/src/error.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actix-http/src/error.rs b/actix-http/src/error.rs index d3095e68d..d9b97d6a6 100644 --- a/actix-http/src/error.rs +++ b/actix-http/src/error.rs @@ -54,7 +54,7 @@ impl Error { /// Similar to `as_response_error` but downcasts. pub fn as_error(&self) -> Option<&T> { - ResponseError::downcast_ref(self.cause.as_ref()) + ::downcast_ref(self.cause.as_ref()) } } From 78fcd0237a4033e9802313de0299ce9cbb0a614c Mon Sep 17 00:00:00 2001 From: Ibraheem Ahmed Date: Fri, 19 Mar 2021 00:08:23 -0400 Subject: [PATCH 111/339] Format extract macro (#2087) --- src/extract.rs | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/src/extract.rs b/src/extract.rs index 7a677bca4..921d9fc36 100644 --- a/src/extract.rs +++ b/src/extract.rs @@ -302,13 +302,13 @@ macro_rules! tuple_from_req ({$fut_type:ident, $(($n:tt, $T:ident)),+} => { } )+ - if ready { - Poll::Ready(Ok( - ($(this.items.$n.take().unwrap(),)+) - )) - } else { - Poll::Pending - } + if ready { + Poll::Ready(Ok( + ($(this.items.$n.take().unwrap(),)+) + )) + } else { + Poll::Pending + } } } } @@ -318,16 +318,16 @@ macro_rules! tuple_from_req ({$fut_type:ident, $(($n:tt, $T:ident)),+} => { mod m { 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)); + 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)); } #[cfg(test)] From 351286486c5b88d6fa879ba2d0ccc0335f41dfc1 Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Fri, 19 Mar 2021 04:25:35 -0700 Subject: [PATCH 112/339] fix clippy warning on nightly (#2088) * fix clippy warning on nightly --- actix-files/src/service.rs | 2 +- actix-http/src/client/connection.rs | 2 +- actix-http/src/config.rs | 20 +++++--------------- actix-http/src/header/shared/entity.rs | 5 ++--- actix-http/src/header/shared/extended.rs | 2 +- src/config.rs | 2 +- src/lib.rs | 12 ++---------- src/middleware/compress.rs | 17 +++++++++-------- src/resource.rs | 2 +- src/test.rs | 2 +- tests/test_httpserver.rs | 16 ++++++---------- 11 files changed, 30 insertions(+), 52 deletions(-) diff --git a/actix-files/src/service.rs b/actix-files/src/service.rs index 14eea6ebc..3214963ed 100644 --- a/actix-files/src/service.rs +++ b/actix-files/src/service.rs @@ -1,4 +1,4 @@ -use std::{fmt, io, path::PathBuf, rc::Rc, task::Poll}; +use std::{fmt, io, path::PathBuf, rc::Rc}; use actix_service::Service; use actix_web::{ diff --git a/actix-http/src/client/connection.rs b/actix-http/src/client/connection.rs index 89dfd59de..78101397d 100644 --- a/actix-http/src/client/connection.rs +++ b/actix-http/src/client/connection.rs @@ -325,7 +325,7 @@ where } } -const H2_UNREACHABLE_WRITE: &'static str = "H2Connection can not impl AsyncWrite trait"; +const H2_UNREACHABLE_WRITE: &str = "H2Connection can not impl AsyncWrite trait"; impl AsyncWrite for Connection where diff --git a/actix-http/src/config.rs b/actix-http/src/config.rs index 9f84b8694..9a2293e92 100644 --- a/actix-http/src/config.rs +++ b/actix-http/src/config.rs @@ -126,9 +126,7 @@ impl ServiceConfig { pub fn client_timer(&self) -> Option { let delay_time = self.0.client_timeout; if delay_time != 0 { - Some(sleep_until( - self.0.date_service.now() + Duration::from_millis(delay_time), - )) + Some(sleep_until(self.now() + Duration::from_millis(delay_time))) } else { None } @@ -138,7 +136,7 @@ impl ServiceConfig { pub fn client_timer_expire(&self) -> Option { let delay = self.0.client_timeout; if delay != 0 { - Some(self.0.date_service.now() + Duration::from_millis(delay)) + Some(self.now() + Duration::from_millis(delay)) } else { None } @@ -148,7 +146,7 @@ impl ServiceConfig { pub fn client_disconnect_timer(&self) -> Option { let delay = self.0.client_disconnect; if delay != 0 { - Some(self.0.date_service.now() + Duration::from_millis(delay)) + Some(self.now() + Duration::from_millis(delay)) } else { None } @@ -157,20 +155,12 @@ impl ServiceConfig { #[inline] /// Return keep-alive timer delay is configured. pub fn keep_alive_timer(&self) -> Option { - if let Some(ka) = self.0.keep_alive { - Some(sleep_until(self.0.date_service.now() + ka)) - } else { - None - } + self.keep_alive().map(|ka| sleep_until(self.now() + ka)) } /// Keep-alive expire time pub fn keep_alive_expire(&self) -> Option { - if let Some(ka) = self.0.keep_alive { - Some(self.0.date_service.now() + ka) - } else { - None - } + self.keep_alive().map(|ka| self.now() + ka) } #[inline] diff --git a/actix-http/src/header/shared/entity.rs b/actix-http/src/header/shared/entity.rs index eb383cd6f..2505216f2 100644 --- a/actix-http/src/header/shared/entity.rs +++ b/actix-http/src/header/shared/entity.rs @@ -127,9 +127,8 @@ impl Display for EntityTag { impl FromStr for EntityTag { type Err = crate::error::ParseError; - fn from_str(s: &str) -> Result { - let length: usize = s.len(); - let slice = &s[..]; + fn from_str(slice: &str) -> Result { + let length = slice.len(); // Early exits if it doesn't terminate in a DQUOTE. if !slice.ends_with('"') || slice.len() < 2 { return Err(crate::error::ParseError::Header); diff --git a/actix-http/src/header/shared/extended.rs b/actix-http/src/header/shared/extended.rs index 6bdcb7922..9fd4cdfb0 100644 --- a/actix-http/src/header/shared/extended.rs +++ b/actix-http/src/header/shared/extended.rs @@ -88,9 +88,9 @@ pub fn parse_extended_value( }; Ok(ExtendedValue { - value, charset, language_tag, + value, }) } diff --git a/src/config.rs b/src/config.rs index bd9a25c6f..cd14eb4cc 100644 --- a/src/config.rs +++ b/src/config.rs @@ -113,7 +113,7 @@ pub struct AppConfig { impl AppConfig { pub(crate) fn new(secure: bool, addr: SocketAddr, host: String) -> Self { - AppConfig { secure, addr, host } + AppConfig { secure, host, addr } } /// Server host name. diff --git a/src/lib.rs b/src/lib.rs index 16b2ab186..136c462b8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -173,11 +173,7 @@ pub mod dev { impl BodyEncoding for ResponseBuilder { fn get_encoding(&self) -> Option { - if let Some(ref enc) = self.extensions().get::() { - Some(enc.0) - } else { - None - } + self.extensions().get::().map(|enc| enc.0) } fn encoding(&mut self, encoding: ContentEncoding) -> &mut Self { @@ -188,11 +184,7 @@ pub mod dev { impl BodyEncoding for Response { fn get_encoding(&self) -> Option { - if let Some(ref enc) = self.extensions().get::() { - Some(enc.0) - } else { - None - } + self.extensions().get::().map(|enc| enc.0) } fn encoding(&mut self, encoding: ContentEncoding) -> &mut Self { diff --git a/src/middleware/compress.rs b/src/middleware/compress.rs index 698ba768e..fc1a85d30 100644 --- a/src/middleware/compress.rs +++ b/src/middleware/compress.rs @@ -197,22 +197,23 @@ impl AcceptEncoding { /// Parse a raw Accept-Encoding header value into an ordered list. pub fn parse(raw: &str, encoding: ContentEncoding) -> ContentEncoding { - let mut encodings: Vec<_> = raw + let mut encodings = raw .replace(' ', "") .split(',') .map(|l| AcceptEncoding::new(l)) - .collect(); + .flatten() + .collect::>(); + encodings.sort(); for enc in encodings { - if let Some(enc) = enc { - if encoding == ContentEncoding::Auto { - return enc.encoding; - } else if encoding == enc.encoding { - return encoding; - } + if encoding == ContentEncoding::Auto { + return enc.encoding; + } else if encoding == enc.encoding { + return encoding; } } + ContentEncoding::Identity } } diff --git a/src/resource.rs b/src/resource.rs index 1a5619de6..d35131cbb 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -449,9 +449,9 @@ impl ServiceFactory for ResourceFactory { .collect::, _>>()?; Ok(ResourceService { + routes, app_data, default, - routes, }) }) } diff --git a/src/test.rs b/src/test.rs index dd2426fec..2ebd64558 100644 --- a/src/test.rs +++ b/src/test.rs @@ -774,10 +774,10 @@ where }; TestServer { - ssl, addr, client, system, + ssl, server, } } diff --git a/tests/test_httpserver.rs b/tests/test_httpserver.rs index aa2b2ca74..12225b7e5 100644 --- a/tests/test_httpserver.rs +++ b/tests/test_httpserver.rs @@ -1,15 +1,11 @@ -use std::sync::mpsc; -use std::{thread, time::Duration}; - #[cfg(feature = "openssl")] extern crate tls_openssl as openssl; -#[cfg(feature = "rustls")] -extern crate tls_rustls as rustls; -#[cfg(feature = "openssl")] -use openssl::ssl::SslAcceptorBuilder; - -use actix_web::{test, web, App, HttpResponse, HttpServer}; +#[cfg(any(unix, feature = "openssl"))] +use { + actix_web::{test, web, App, HttpResponse, HttpServer}, + std::{sync::mpsc, thread, time::Duration}, +}; #[cfg(unix)] #[actix_rt::test] @@ -72,7 +68,7 @@ async fn test_start() { } #[cfg(feature = "openssl")] -fn ssl_acceptor() -> SslAcceptorBuilder { +fn ssl_acceptor() -> openssl::ssl::SslAcceptorBuilder { use openssl::{ pkey::PKey, ssl::{SslAcceptor, SslMethod}, From 9488757c29bf575a113041886050bafad2129a2d Mon Sep 17 00:00:00 2001 From: Thomas de Zeeuw Date: Fri, 19 Mar 2021 12:17:06 +0000 Subject: [PATCH 113/339] Update to socket2 v0.4 (#2092) --- Cargo.toml | 2 +- actix-http-test/Cargo.toml | 2 +- actix-http-test/src/lib.rs | 4 ++-- src/server.rs | 9 +++------ src/test.rs | 4 ++-- 5 files changed, 9 insertions(+), 12 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 0877edf9c..f3a6271ee 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -105,7 +105,7 @@ serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" serde_urlencoded = "0.7" smallvec = "1.6" -socket2 = "0.3.16" +socket2 = "0.4.0" time = { version = "0.2.23", default-features = false, features = ["std"] } tls-openssl = { package = "openssl", version = "0.10.9", optional = true } tls-rustls = { package = "rustls", version = "0.19.0", optional = true } diff --git a/actix-http-test/Cargo.toml b/actix-http-test/Cargo.toml index c1e61556b..a7efc5310 100644 --- a/actix-http-test/Cargo.toml +++ b/actix-http-test/Cargo.toml @@ -42,7 +42,7 @@ bytes = "1" futures-core = { version = "0.3.7", default-features = false } http = "0.2.2" log = "0.4" -socket2 = "0.3" +socket2 = "0.4" serde = "1.0" serde_json = "1.0" slab = "0.4" diff --git a/actix-http-test/src/lib.rs b/actix-http-test/src/lib.rs index 8de07c8d3..138f14313 100644 --- a/actix-http-test/src/lib.rs +++ b/actix-http-test/src/lib.rs @@ -118,10 +118,10 @@ pub async fn test_server_with_addr>( /// Get first available unused address pub fn unused_addr() -> net::SocketAddr { let addr: net::SocketAddr = "127.0.0.1:0".parse().unwrap(); - let socket = Socket::new(Domain::ipv4(), Type::stream(), Some(Protocol::tcp())).unwrap(); + let socket = Socket::new(Domain::IPV4, Type::STREAM, Some(Protocol::TCP)).unwrap(); socket.bind(&addr.into()).unwrap(); socket.set_reuse_address(true).unwrap(); - let tcp = socket.into_tcp_listener(); + let tcp = net::TcpListener::from(socket); tcp.local_addr().unwrap() } diff --git a/src/server.rs b/src/server.rs index 99355593a..cfd8ae4b4 100644 --- a/src/server.rs +++ b/src/server.rs @@ -607,17 +607,14 @@ where fn create_tcp_listener(addr: net::SocketAddr, backlog: u32) -> io::Result { use socket2::{Domain, Protocol, Socket, Type}; - let domain = match addr { - net::SocketAddr::V4(_) => Domain::ipv4(), - net::SocketAddr::V6(_) => Domain::ipv6(), - }; - let socket = Socket::new(domain, Type::stream(), Some(Protocol::tcp()))?; + let domain = Domain::for_address(addr); + let socket = Socket::new(domain, Type::STREAM, Some(Protocol::TCP))?; socket.set_reuse_address(true)?; socket.bind(&addr.into())?; // clamp backlog to max u32 that fits in i32 range let backlog = cmp::min(backlog, i32::MAX as u32) as i32; socket.listen(backlog)?; - Ok(socket.into_tcp_listener()) + Ok(net::TcpListener::from(socket)) } #[cfg(feature = "openssl")] diff --git a/src/test.rs b/src/test.rs index 2ebd64558..bc19296e2 100644 --- a/src/test.rs +++ b/src/test.rs @@ -862,10 +862,10 @@ impl TestServerConfig { /// Get first available unused address pub fn unused_addr() -> net::SocketAddr { let addr: net::SocketAddr = "127.0.0.1:0".parse().unwrap(); - let socket = Socket::new(Domain::ipv4(), Type::stream(), Some(Protocol::tcp())).unwrap(); + let socket = Socket::new(Domain::IPV4, Type::STREAM, Some(Protocol::TCP)).unwrap(); socket.bind(&addr.into()).unwrap(); socket.set_reuse_address(true).unwrap(); - let tcp = socket.into_tcp_listener(); + let tcp = net::TcpListener::from(socket); tcp.local_addr().unwrap() } From 8d9de768264e78cf62087be44872c17e24f5428a Mon Sep 17 00:00:00 2001 From: Ibraheem Ahmed Date: Fri, 19 Mar 2021 12:30:53 -0400 Subject: [PATCH 114/339] Simplify handler factory macro (#2086) --- src/handler.rs | 50 +++++++++++++++++++------------------------------- 1 file changed, 19 insertions(+), 31 deletions(-) diff --git a/src/handler.rs b/src/handler.rs index 0016b741e..7e3c5f47e 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -28,17 +28,6 @@ where fn call(&self, param: T) -> R; } -impl Handler<(), R> for F -where - F: Fn() -> R + Clone + 'static, - R: Future, - R::Output: Responder, -{ - fn call(&self, _: ()) -> R { - (self)() - } -} - #[doc(hidden)] /// Extract arguments from request, run factory function and make response. pub struct HandlerService @@ -177,30 +166,29 @@ where } /// FromRequest trait impl for tuples -macro_rules! factory_tuple ({ $(($n:tt, $T:ident)),+} => { - impl Handler<($($T,)+), Res> for Func - where Func: Fn($($T,)+) -> Res + Clone + 'static, +macro_rules! factory_tuple ({ $($param:ident)* } => { + impl Handler<($($param,)*), Res> for Func + where Func: Fn($($param),*) -> Res + Clone + 'static, Res: Future, Res::Output: Responder, { - fn call(&self, param: ($($T,)+)) -> Res { - (self)($(param.$n,)+) + #[allow(non_snake_case)] + fn call(&self, ($($param,)*): ($($param,)*)) -> Res { + (self)($($param,)*) } } }); -#[rustfmt::skip] -mod m { - use super::*; - - factory_tuple!((0, A)); - factory_tuple!((0, A), (1, B)); - factory_tuple!((0, A), (1, B), (2, C)); - factory_tuple!((0, A), (1, B), (2, C), (3, D)); - factory_tuple!((0, A), (1, B), (2, C), (3, D), (4, E)); - factory_tuple!((0, A), (1, B), (2, C), (3, D), (4, E), (5, F)); - factory_tuple!((0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G)); - factory_tuple!((0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G), (7, H)); - factory_tuple!((0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G), (7, H), (8, I)); - factory_tuple!((0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G), (7, H), (8, I), (9, J)); -} +factory_tuple! {} +factory_tuple! { A } +factory_tuple! { A B } +factory_tuple! { A B C } +factory_tuple! { A B C D } +factory_tuple! { A B C D E } +factory_tuple! { A B C D E F } +factory_tuple! { A B C D E F G } +factory_tuple! { A B C D E F G H } +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 } From 746d983849d7e5bb5ca4ac242276f6e65cca5643 Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Fri, 19 Mar 2021 22:18:06 -0700 Subject: [PATCH 115/339] handle header error with CustomResponder (#2093) --- CHANGES.md | 5 +++++ src/responder.rs | 31 +++++++++++++++---------------- 2 files changed, 20 insertions(+), 16 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index ef9ee900a..5df0b6d6d 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -4,11 +4,16 @@ ### 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) [#2067]: https://github.com/actix/actix-web/pull/2067 +[#2093]: https://github.com/actix/actix-web/pull/2093 ## 4.0.0-beta.4 - 2021-03-09 diff --git a/src/responder.rs b/src/responder.rs index 92945cdaa..0b6f47c6b 100644 --- a/src/responder.rs +++ b/src/responder.rs @@ -155,8 +155,7 @@ impl Responder for BytesMut { pub struct CustomResponder { responder: T, status: Option, - headers: Option, - error: Option, + headers: Result, } impl CustomResponder { @@ -164,8 +163,7 @@ impl CustomResponder { CustomResponder { responder, status: None, - headers: None, - error: None, + headers: Ok(HeaderMap::new()), } } @@ -206,32 +204,33 @@ impl CustomResponder { where H: IntoHeaderPair, { - if self.headers.is_none() { - self.headers = Some(HeaderMap::new()); + 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()), + }; } - match header.try_into_header_pair() { - Ok((key, value)) => self.headers.as_mut().unwrap().append(key, value), - Err(e) => self.error = Some(e.into()), - }; - self } } impl Responder for CustomResponder { fn respond_to(self, req: &HttpRequest) -> HttpResponse { + let headers = match self.headers { + Ok(headers) => headers, + Err(err) => return HttpResponse::from_error(Error::from(err)), + }; + let mut res = self.responder.respond_to(req); if let Some(status) = self.status { *res.status_mut() = status; } - if let Some(ref headers) = self.headers { - for (k, v) in headers { - // TODO: before v4, decide if this should be append instead - res.headers_mut().insert(k.clone(), v.clone()); - } + for (k, v) in headers { + // TODO: before v4, decide if this should be append instead + res.headers_mut().insert(k, v); } res From 1be54efbebe0695e7ad104d2747a1f3f66fb5dea Mon Sep 17 00:00:00 2001 From: Ibraheem Ahmed Date: Tue, 23 Mar 2021 09:42:46 -0400 Subject: [PATCH 116/339] Simplify service factory macro (#2108) --- src/service.rs | 35 ++++++++++++++++------------------- 1 file changed, 16 insertions(+), 19 deletions(-) diff --git a/src/service.rs b/src/service.rs index fcbe61a02..491810286 100644 --- a/src/service.rs +++ b/src/service.rs @@ -573,31 +573,28 @@ macro_rules! services { } /// HttpServiceFactory trait impl for tuples -macro_rules! service_tuple ({ $(($n:tt, $T:ident)),+} => { +macro_rules! service_tuple ({ $($T:ident)+ } => { impl<$($T: HttpServiceFactory),+> HttpServiceFactory for ($($T,)+) { + #[allow(non_snake_case)] fn register(self, config: &mut AppService) { - $(self.$n.register(config);)+ + let ($($T,)*) = self; + $($T.register(config);)+ } } }); -#[rustfmt::skip] -mod m { - use super::*; - - service_tuple!((0, A)); - service_tuple!((0, A), (1, B)); - service_tuple!((0, A), (1, B), (2, C)); - service_tuple!((0, A), (1, B), (2, C), (3, D)); - service_tuple!((0, A), (1, B), (2, C), (3, D), (4, E)); - service_tuple!((0, A), (1, B), (2, C), (3, D), (4, E), (5, F)); - service_tuple!((0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G)); - service_tuple!((0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G), (7, H)); - service_tuple!((0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G), (7, H), (8, I)); - service_tuple!((0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G), (7, H), (8, I), (9, J)); - service_tuple!((0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G), (7, H), (8, I), (9, J), (10, K)); - service_tuple!((0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G), (7, H), (8, I), (9, J), (10, K), (11, L)); -} +service_tuple! { A } +service_tuple! { A B } +service_tuple! { A B C } +service_tuple! { A B C D } +service_tuple! { A B C D E } +service_tuple! { A B C D E F } +service_tuple! { A B C D E F G } +service_tuple! { A B C D E F G H } +service_tuple! { A B C D E F G H I } +service_tuple! { A B C D E F G H I J } +service_tuple! { A B C D E F G H I J K } +service_tuple! { A B C D E F G H I J K L } #[cfg(test)] mod tests { From 9704beddf86930251f97557ee5c058c95ec91e71 Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Wed, 24 Mar 2021 04:44:03 -0700 Subject: [PATCH 117/339] Relax MessageBody limit to 2048kb (#2110) * relax MessageBody limit to 2048kb * fix clippy * Update awc/src/response.rs Co-authored-by: Rob Ede * fix default body limit Co-authored-by: Rob Ede --- awc/src/response.rs | 83 +++++++++++++++++++++------------------------ 1 file changed, 38 insertions(+), 45 deletions(-) diff --git a/awc/src/response.rs b/awc/src/response.rs index 40de3dc17..994ddb761 100644 --- a/awc/src/response.rs +++ b/awc/src/response.rs @@ -228,12 +228,13 @@ impl fmt::Debug for ClientResponse { } } +const DEFAULT_BODY_LIMIT: usize = 2 * 1024 * 1024; + /// Future that resolves to a complete HTTP message body. pub struct MessageBody { length: Option, - err: Option, timeout: ResponseTimeout, - fut: Option>, + body: Result, Option>, } impl MessageBody @@ -242,41 +243,38 @@ where { /// Create `MessageBody` for request. pub fn new(res: &mut ClientResponse) -> MessageBody { - 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) - } else { - return Self::err(PayloadError::UnknownLength); + 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, } - } else { - return Self::err(PayloadError::UnknownLength); } - } + None => None, + }; MessageBody { - length: len, - err: None, + length, timeout: std::mem::take(&mut res.timeout), - fut: Some(ReadBody::new(res.take_payload(), 262_144)), + body: Ok(ReadBody::new(res.take_payload(), DEFAULT_BODY_LIMIT)), } } - /// Change max size of payload. By default max size is 256kB + /// Change max size of payload. By default max size is 2048kB pub fn limit(mut self, limit: usize) -> Self { - if let Some(ref mut fut) = self.fut { - fut.limit = limit; + if let Ok(ref mut body) = self.body { + body.limit = limit; } self } fn err(e: PayloadError) -> Self { MessageBody { - fut: None, - err: Some(e), length: None, timeout: ResponseTimeout::default(), + body: Err(Some(e)), } } } @@ -290,19 +288,20 @@ where fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { let this = self.get_mut(); - if let Some(err) = this.err.take() { - return Poll::Ready(Err(err)); - } + 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)); + } + } - if let Some(len) = this.length.take() { - if len > this.fut.as_ref().unwrap().limit { - return Poll::Ready(Err(PayloadError::Overflow)); + this.timeout.poll_timeout(cx)?; + + Pin::new(body).poll(cx) } } - - this.timeout.poll_timeout(cx)?; - - Pin::new(&mut this.fut.as_mut().unwrap()).poll(cx) } } @@ -415,7 +414,7 @@ impl ReadBody { fn new(stream: Payload, limit: usize) -> Self { Self { stream, - buf: BytesMut::with_capacity(std::cmp::min(limit, 32768)), + buf: BytesMut::new(), limit, } } @@ -430,20 +429,14 @@ where fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { let this = self.get_mut(); - loop { - return match Pin::new(&mut this.stream).poll_next(cx)? { - Poll::Ready(Some(chunk)) => { - if (this.buf.len() + chunk.len()) > this.limit { - Poll::Ready(Err(PayloadError::Overflow)) - } else { - this.buf.extend_from_slice(&chunk); - continue; - } - } - Poll::Ready(None) => Poll::Ready(Ok(this.buf.split().freeze())), - Poll::Pending => Poll::Pending, - }; + 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())) } } @@ -462,7 +455,7 @@ mod tests { _ => unreachable!("error"), } - let mut req = TestResponse::with_header(header::CONTENT_LENGTH, "1000000").finish(); + let mut req = TestResponse::with_header(header::CONTENT_LENGTH, "10000000").finish(); match req.body().await.err().unwrap() { PayloadError::Overflow => {} _ => unreachable!("error"), From 3188ef573159263e281f25416b4f960ee6d67cad Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Thu, 25 Mar 2021 08:45:52 +0000 Subject: [PATCH 118/339] don't use rust annotation on code doc blocks --- actix-files/src/files.rs | 2 +- actix-files/src/lib.rs | 2 +- actix-files/src/named.rs | 6 ++--- actix-http-test/src/lib.rs | 2 +- actix-http/src/client/connector.rs | 2 +- actix-http/src/error.rs | 2 +- actix-http/src/header/common/cache_control.rs | 2 +- actix-http/src/response.rs | 8 +++---- actix-http/src/test.rs | 2 +- actix-multipart/src/extractor.rs | 2 +- actix-web-codegen/src/lib.rs | 6 ++--- awc/src/lib.rs | 2 +- awc/src/request.rs | 6 ++--- benches/service.rs | 2 +- src/app.rs | 16 ++++++------- src/data.rs | 2 +- src/extract.rs | 4 ++-- src/guard.rs | 10 ++++---- src/lib.rs | 2 +- src/middleware/compat.rs | 2 +- src/middleware/compress.rs | 2 +- src/middleware/condition.rs | 2 +- src/middleware/default_headers.rs | 2 +- src/middleware/err_handlers.rs | 2 +- src/middleware/logger.rs | 4 ++-- src/middleware/normalize.rs | 2 +- src/request.rs | 6 ++--- src/request_data.rs | 2 +- src/resource.rs | 16 ++++++------- src/responder.rs | 8 +++---- src/route.rs | 8 +++---- src/scope.rs | 14 +++++------ src/server.rs | 4 ++-- src/service.rs | 2 +- src/test.rs | 18 +++++++------- src/web.rs | 24 +++++++++---------- 36 files changed, 99 insertions(+), 99 deletions(-) diff --git a/actix-files/src/files.rs b/actix-files/src/files.rs index 6f8b28bbf..292e3fdf3 100644 --- a/actix-files/src/files.rs +++ b/actix-files/src/files.rs @@ -19,7 +19,7 @@ use crate::{ /// /// `Files` service must be registered with `App::service()` method. /// -/// ```rust +/// ``` /// use actix_web::App; /// use actix_files::Files; /// diff --git a/actix-files/src/lib.rs b/actix-files/src/lib.rs index 3c34c0403..018079b21 100644 --- a/actix-files/src/lib.rs +++ b/actix-files/src/lib.rs @@ -3,7 +3,7 @@ //! Provides a non-blocking service for serving static files from disk. //! //! # Example -//! ```rust +//! ``` //! use actix_web::App; //! use actix_files::Files; //! diff --git a/actix-files/src/named.rs b/actix-files/src/named.rs index a688b2e6c..2846646a2 100644 --- a/actix-files/src/named.rs +++ b/actix-files/src/named.rs @@ -60,7 +60,7 @@ impl NamedFile { /// /// # Examples /// - /// ```rust + /// ``` /// use actix_files::NamedFile; /// use std::io::{self, Write}; /// use std::env; @@ -137,7 +137,7 @@ impl NamedFile { /// /// # Examples /// - /// ```rust + /// ``` /// use actix_files::NamedFile; /// /// let file = NamedFile::open("foo.txt"); @@ -156,7 +156,7 @@ impl NamedFile { /// /// # Examples /// - /// ```rust + /// ``` /// # use std::io; /// use actix_files::NamedFile; /// diff --git a/actix-http-test/src/lib.rs b/actix-http-test/src/lib.rs index 138f14313..3749b78ca 100644 --- a/actix-http-test/src/lib.rs +++ b/actix-http-test/src/lib.rs @@ -26,7 +26,7 @@ use socket2::{Domain, Protocol, Socket, Type}; /// /// # Examples /// -/// ```rust +/// ``` /// use actix_http::HttpService; /// use actix_http_test::TestServer; /// use actix_web::{web, App, HttpResponse, Error}; diff --git a/actix-http/src/client/connector.rs b/actix-http/src/client/connector.rs index 1a5f32880..6996677d2 100644 --- a/actix-http/src/client/connector.rs +++ b/actix-http/src/client/connector.rs @@ -47,7 +47,7 @@ enum SslConnector { /// The `Connector` type uses a builder-like combinator pattern for service /// construction that finishes by calling the `.finish()` method. /// -/// ```rust,ignore +/// ```ignore /// use std::time::Duration; /// use actix_http::client::Connector; /// diff --git a/actix-http/src/error.rs b/actix-http/src/error.rs index d9b97d6a6..1354e998e 100644 --- a/actix-http/src/error.rs +++ b/actix-http/src/error.rs @@ -483,7 +483,7 @@ where /// response as opposite to *INTERNAL SERVER ERROR* which is defined by /// default. /// -/// ```rust +/// ``` /// # use std::io; /// # use actix_http::*; /// diff --git a/actix-http/src/header/common/cache_control.rs b/actix-http/src/header/common/cache_control.rs index 94ce9a750..b19823d22 100644 --- a/actix-http/src/header/common/cache_control.rs +++ b/actix-http/src/header/common/cache_control.rs @@ -36,7 +36,7 @@ use crate::header::{ /// builder.insert_header(CacheControl(vec![CacheDirective::MaxAge(86400u32)])); /// ``` /// -/// ```rust +/// ``` /// use actix_http::Response; /// use actix_http::http::header::{CacheControl, CacheDirective}; /// diff --git a/actix-http/src/response.rs b/actix-http/src/response.rs index d581fd293..94f12bcc3 100644 --- a/actix-http/src/response.rs +++ b/actix-http/src/response.rs @@ -357,7 +357,7 @@ impl ResponseBuilder { /// Insert a header, replacing any that were set with an equivalent field name. /// - /// ```rust + /// ``` /// # use actix_http::Response; /// use actix_http::http::header::ContentType; /// @@ -384,7 +384,7 @@ impl ResponseBuilder { /// Append a header, keeping any that were set with an equivalent field name. /// - /// ```rust + /// ``` /// # use actix_http::Response; /// use actix_http::http::header::ContentType; /// @@ -525,7 +525,7 @@ impl ResponseBuilder { /// Set a cookie /// - /// ```rust + /// ``` /// use actix_http::{http, Request, Response}; /// /// fn index(req: Request) -> Response { @@ -555,7 +555,7 @@ impl ResponseBuilder { /// Remove cookie /// - /// ```rust + /// ``` /// use actix_http::{http, Request, Response, HttpMessage}; /// /// fn index(req: Request) -> Response { diff --git a/actix-http/src/test.rs b/actix-http/src/test.rs index 870a656df..ad3dc74b2 100644 --- a/actix-http/src/test.rs +++ b/actix-http/src/test.rs @@ -26,7 +26,7 @@ use crate::{ /// Test `Request` builder /// -/// ```rust,ignore +/// ```ignore /// # use http::{header, StatusCode}; /// # use actix_web::*; /// use actix_web::test::TestRequest; diff --git a/actix-multipart/src/extractor.rs b/actix-multipart/src/extractor.rs index 6aaa415c4..bffbe8a1b 100644 --- a/actix-multipart/src/extractor.rs +++ b/actix-multipart/src/extractor.rs @@ -10,7 +10,7 @@ use crate::server::Multipart; /// /// ## Server example /// -/// ```rust +/// ``` /// use futures_util::stream::{Stream, StreamExt}; /// use actix_web::{web, HttpResponse, Error}; /// use actix_multipart as mp; diff --git a/actix-web-codegen/src/lib.rs b/actix-web-codegen/src/lib.rs index 48414d491..336345014 100644 --- a/actix-web-codegen/src/lib.rs +++ b/actix-web-codegen/src/lib.rs @@ -82,7 +82,7 @@ mod route; /// /// # Example /// -/// ```rust +/// ``` /// # use actix_web::HttpResponse; /// # use actix_web_codegen::route; /// #[route("/test", method="GET", method="HEAD")] @@ -127,7 +127,7 @@ code, e.g `my_guard` or `my_module::my_guard`. # Example -```rust +``` # use actix_web::HttpResponse; # use actix_web_codegen::"#, stringify!($method), "; #[", stringify!($method), r#"("/")] @@ -162,7 +162,7 @@ method_macro! { /// This macro can be applied with `#[actix_web::main]` when used in Actix Web applications. /// /// # Examples -/// ```rust +/// ``` /// #[actix_web_codegen::main] /// async fn main() { /// async { println!("Hello world"); }.await diff --git a/awc/src/lib.rs b/awc/src/lib.rs index 8addf4d8b..c7bb68a8f 100644 --- a/awc/src/lib.rs +++ b/awc/src/lib.rs @@ -131,7 +131,7 @@ pub use self::sender::SendClientRequest; /// /// ## Examples /// -/// ```rust +/// ``` /// use awc::Client; /// /// #[actix_rt::main] diff --git a/awc/src/request.rs b/awc/src/request.rs index 3e9babb7d..a847b09a3 100644 --- a/awc/src/request.rs +++ b/awc/src/request.rs @@ -36,7 +36,7 @@ cfg_if::cfg_if! { /// This type can be used to construct an instance of `ClientRequest` through a /// builder-like pattern. /// -/// ```rust +/// ``` /// #[actix_rt::main] /// async fn main() { /// let response = awc::Client::new() @@ -190,7 +190,7 @@ impl ClientRequest { /// Append a header, keeping any that were set with an equivalent field name. /// - /// ```rust + /// ``` /// # #[actix_rt::main] /// # async fn main() { /// # use awc::Client; @@ -271,7 +271,7 @@ impl ClientRequest { /// Set a cookie /// - /// ```rust + /// ``` /// #[actix_rt::main] /// async fn main() { /// let resp = awc::Client::new().get("https://www.rust-lang.org") diff --git a/benches/service.rs b/benches/service.rs index 0d3264857..30708477d 100644 --- a/benches/service.rs +++ b/benches/service.rs @@ -9,7 +9,7 @@ use actix_web::test::{init_service, ok_service, TestRequest}; /// Criterion Benchmark for async Service /// Should be used from within criterion group: -/// ```rust,ignore +/// ```ignore /// let mut criterion: ::criterion::Criterion<_> = /// ::criterion::Criterion::default().configure_from_args(); /// bench_async_service(&mut criterion, ok_service(), "async_service_direct"); diff --git a/src/app.rs b/src/app.rs index 7a26a3a89..f2c6bce8a 100644 --- a/src/app.rs +++ b/src/app.rs @@ -79,7 +79,7 @@ where /// uses `Arc` so data could be created outside of app factory and clones could /// be stored via `App::app_data()` method. /// - /// ```rust + /// ``` /// use std::cell::Cell; /// use actix_web::{web, App, HttpResponse, Responder}; /// @@ -152,7 +152,7 @@ where /// different module or even library. For example, /// some of the resource's configuration could be moved to different module. /// - /// ```rust + /// ``` /// use actix_web::{web, App, HttpResponse}; /// /// // this function could be located in different module @@ -185,7 +185,7 @@ where /// This method can be used multiple times with same path, in that case /// multiple resources with one route would be registered for same resource path. /// - /// ```rust + /// ``` /// use actix_web::{web, App, HttpResponse}; /// /// async fn index(data: web::Path<(String, String)>) -> &'static str { @@ -228,7 +228,7 @@ where /// /// It is possible to use services like `Resource`, `Route`. /// - /// ```rust + /// ``` /// use actix_web::{web, App, HttpResponse}; /// /// async fn index() -> &'static str { @@ -246,7 +246,7 @@ where /// /// It is also possible to use static files as default service. /// - /// ```rust + /// ``` /// use actix_web::{web, App, HttpResponse}; /// /// fn main() { @@ -283,7 +283,7 @@ where /// and are never considered for matching at request time. Calls to /// `HttpRequest::url_for()` will work as expected. /// - /// ```rust + /// ``` /// use actix_web::{web, App, HttpRequest, HttpResponse, Result}; /// /// async fn index(req: HttpRequest) -> Result { @@ -325,7 +325,7 @@ where /// the builder chain. Consequently, the *first* middleware registered /// in the builder chain is the *last* to execute during request processing. /// - /// ```rust + /// ``` /// use actix_service::Service; /// use actix_web::{middleware, web, App}; /// use actix_web::http::{header::CONTENT_TYPE, HeaderValue}; @@ -382,7 +382,7 @@ where /// /// Use middleware when you need to read or modify *every* request or response in some way. /// - /// ```rust + /// ``` /// use actix_service::Service; /// use actix_web::{web, App}; /// use actix_web::http::{header::CONTENT_TYPE, HeaderValue}; diff --git a/src/data.rs b/src/data.rs index 0336553ca..56ecdb8ae 100644 --- a/src/data.rs +++ b/src/data.rs @@ -37,7 +37,7 @@ pub(crate) type FnDataFactory = /// If route data is not set for a handler, using `Data` extractor would cause *Internal /// Server Error* response. /// -/// ```rust +/// ``` /// use std::sync::Mutex; /// use actix_web::{web, App, HttpResponse, Responder}; /// diff --git a/src/extract.rs b/src/extract.rs index 921d9fc36..8851481e3 100644 --- a/src/extract.rs +++ b/src/extract.rs @@ -51,7 +51,7 @@ pub trait FromRequest: Sized { /// /// ## Example /// -/// ```rust +/// ``` /// use actix_web::{web, dev, App, Error, HttpRequest, FromRequest}; /// use actix_web::error::ErrorBadRequest; /// use futures_util::future::{ok, err, Ready}; @@ -143,7 +143,7 @@ where /// /// ## Example /// -/// ```rust +/// ``` /// use actix_web::{web, dev, App, Result, Error, HttpRequest, FromRequest}; /// use actix_web::error::ErrorBadRequest; /// use futures_util::future::{ok, err, Ready}; diff --git a/src/guard.rs b/src/guard.rs index 5d0de58c2..3a1f5bb14 100644 --- a/src/guard.rs +++ b/src/guard.rs @@ -12,7 +12,7 @@ //! to store extra attributes on a request by using the `Extensions` container. //! Extensions containers are available via the `RequestHead::extensions()` method. //! -//! ```rust +//! ``` //! use actix_web::{web, http, dev, guard, App, HttpResponse}; //! //! fn main() { @@ -42,7 +42,7 @@ pub trait Guard { /// Create guard object for supplied function. /// -/// ```rust +/// ``` /// use actix_web::{guard, web, App, HttpResponse}; /// /// fn main() { @@ -85,7 +85,7 @@ where /// Return guard that matches if any of supplied guards. /// -/// ```rust +/// ``` /// use actix_web::{web, guard, App, HttpResponse}; /// /// fn main() { @@ -124,7 +124,7 @@ impl Guard for AnyGuard { /// Return guard that matches if all of the supplied guards. /// -/// ```rust +/// ``` /// use actix_web::{guard, web, App, HttpResponse}; /// /// fn main() { @@ -259,7 +259,7 @@ impl Guard for HeaderGuard { /// Return predicate that matches if request contains specified Host name. /// -/// ```rust +/// ``` /// use actix_web::{web, guard::Host, App, HttpResponse}; /// /// fn main() { diff --git a/src/lib.rs b/src/lib.rs index 136c462b8..7a6498546 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,7 +2,7 @@ //! //! ## Example //! -//! ```rust,no_run +//! ```no_run //! use actix_web::{get, web, App, HttpServer, Responder}; //! //! #[get("/{id}/{name}/index.html")] diff --git a/src/middleware/compat.rs b/src/middleware/compat.rs index 6f60264b1..71193a5c5 100644 --- a/src/middleware/compat.rs +++ b/src/middleware/compat.rs @@ -16,7 +16,7 @@ use crate::{error::Error, service::ServiceResponse}; /// [`Scope::wrap`](crate::Scope::wrap) and [`Condition`](super::Condition). /// /// # Examples -/// ```rust +/// ``` /// use actix_web::middleware::{Logger, Compat}; /// use actix_web::{App, web}; /// diff --git a/src/middleware/compress.rs b/src/middleware/compress.rs index fc1a85d30..a397bccd6 100644 --- a/src/middleware/compress.rs +++ b/src/middleware/compress.rs @@ -31,7 +31,7 @@ use crate::{ /// encoding to `ContentEncoding::Identity`. /// /// # Examples -/// ```rust +/// ``` /// use actix_web::{web, middleware, App, HttpResponse}; /// /// let app = App::new() diff --git a/src/middleware/condition.rs b/src/middleware/condition.rs index 63a90c853..dd599a0cb 100644 --- a/src/middleware/condition.rs +++ b/src/middleware/condition.rs @@ -12,7 +12,7 @@ use futures_util::future::{Either, FutureExt, LocalBoxFuture}; /// middleware for a workaround. /// /// # Examples -/// ```rust +/// ``` /// use actix_web::middleware::{Condition, NormalizePath}; /// use actix_web::App; /// diff --git a/src/middleware/default_headers.rs b/src/middleware/default_headers.rs index a36cc2f29..12d70ab2c 100644 --- a/src/middleware/default_headers.rs +++ b/src/middleware/default_headers.rs @@ -29,7 +29,7 @@ use crate::{ /// Headers with the same key that are already set in a response will *not* be overwritten. /// /// # Examples -/// ```rust +/// ``` /// use actix_web::{web, http, middleware, App, HttpResponse}; /// /// fn main() { diff --git a/src/middleware/err_handlers.rs b/src/middleware/err_handlers.rs index 06e88cefd..fddd87a99 100644 --- a/src/middleware/err_handlers.rs +++ b/src/middleware/err_handlers.rs @@ -34,7 +34,7 @@ type ErrorHandler = dyn Fn(ServiceResponse) -> Result) -> String { "jwt_uid".to_owned() } /// Logger::new("example %{JWT_ID}xi") diff --git a/src/middleware/normalize.rs b/src/middleware/normalize.rs index ea21a7215..2a97a047b 100644 --- a/src/middleware/normalize.rs +++ b/src/middleware/normalize.rs @@ -54,7 +54,7 @@ impl Default for TrailingSlash { /// `TrailingSlash::Always` behavior), as shown in the example tests below. /// /// # Examples -/// ```rust +/// ``` /// use actix_web::{web, middleware, App}; /// /// # actix_web::rt::System::new().block_on(async { diff --git a/src/request.rs b/src/request.rs index 514b7466e..15c97345c 100644 --- a/src/request.rs +++ b/src/request.rs @@ -159,7 +159,7 @@ impl HttpRequest { /// Generate url for named resource /// - /// ```rust + /// ``` /// # use actix_web::{web, App, HttpRequest, HttpResponse}; /// # /// fn index(req: HttpRequest) -> HttpResponse { @@ -231,7 +231,7 @@ impl HttpRequest { /// /// If `App::data` was used to store object, use `Data`: /// - /// ```rust,ignore + /// ```ignore /// let opt_t = req.app_data::>(); /// ``` pub fn app_data(&self) -> Option<&T> { @@ -302,7 +302,7 @@ impl Drop for HttpRequest { /// /// ## Example /// -/// ```rust +/// ``` /// use actix_web::{web, App, HttpRequest}; /// use serde_derive::Deserialize; /// diff --git a/src/request_data.rs b/src/request_data.rs index beee8ac12..fc711d011 100644 --- a/src/request_data.rs +++ b/src/request_data.rs @@ -23,7 +23,7 @@ use crate::{dev::Payload, FromRequest, HttpRequest}; /// provided to make this potential foot-gun more obvious. /// /// # Example -/// ```rust,no_run +/// ```no_run /// # use actix_web::{web, HttpResponse, HttpRequest, Responder}; /// /// #[derive(Debug, Clone, PartialEq)] diff --git a/src/resource.rs b/src/resource.rs index d35131cbb..8f356c76d 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -35,7 +35,7 @@ type HttpNewService = BoxServiceFactory<(), ServiceRequest, ServiceResponse, Err /// and check guards for specific route, if request matches all /// guards, route considered matched and route handler get called. /// -/// ```rust +/// ``` /// use actix_web::{web, App, HttpResponse}; /// /// fn main() { @@ -97,7 +97,7 @@ where /// Add match guard to a resource. /// - /// ```rust + /// ``` /// use actix_web::{web, guard, App, HttpResponse}; /// /// async fn index(data: web::Path<(String, String)>) -> &'static str { @@ -130,7 +130,7 @@ where /// Register a new route. /// - /// ```rust + /// ``` /// use actix_web::{web, guard, App, HttpResponse}; /// /// fn main() { @@ -147,7 +147,7 @@ where /// Multiple routes could be added to a resource. Resource object uses /// match guards for route selection. /// - /// ```rust + /// ``` /// use actix_web::{web, guard, App}; /// /// fn main() { @@ -172,7 +172,7 @@ where /// Provided data is available for all routes registered for the current resource. /// Resource data overrides data registered by `App::data()` method. /// - /// ```rust + /// ``` /// use actix_web::{web, App, FromRequest}; /// /// /// extract text data from request @@ -211,7 +211,7 @@ where /// Register a new route and add handler. This route matches all requests. /// - /// ```rust + /// ``` /// use actix_web::*; /// /// fn index(req: HttpRequest) -> HttpResponse { @@ -223,7 +223,7 @@ where /// /// This is shortcut for: /// - /// ```rust + /// ``` /// # extern crate actix_web; /// # use actix_web::*; /// # fn index(req: HttpRequest) -> HttpResponse { unimplemented!() } @@ -289,7 +289,7 @@ where /// Resource level middlewares are not allowed to change response /// type (i.e modify response's body). /// - /// ```rust + /// ``` /// use actix_service::Service; /// use actix_web::{web, App}; /// use actix_web::http::{header::CONTENT_TYPE, HeaderValue}; diff --git a/src/responder.rs b/src/responder.rs index 0b6f47c6b..b75c95083 100644 --- a/src/responder.rs +++ b/src/responder.rs @@ -18,7 +18,7 @@ pub trait Responder { /// Override a status code for a Responder. /// - /// ```rust + /// ``` /// use actix_web::{http::StatusCode, HttpRequest, Responder}; /// /// fn index(req: HttpRequest) -> impl Responder { @@ -36,7 +36,7 @@ pub trait Responder { /// /// Overrides other headers with the same name. /// - /// ```rust + /// ``` /// use actix_web::{web, HttpRequest, Responder}; /// use serde::Serialize; /// @@ -169,7 +169,7 @@ impl CustomResponder { /// Override a status code for the Responder's response. /// - /// ```rust + /// ``` /// use actix_web::{HttpRequest, Responder, http::StatusCode}; /// /// fn index(req: HttpRequest) -> impl Responder { @@ -185,7 +185,7 @@ impl CustomResponder { /// /// Overrides other headers with the same name. /// - /// ```rust + /// ``` /// use actix_web::{web, HttpRequest, Responder}; /// use serde::Serialize; /// diff --git a/src/route.rs b/src/route.rs index c157025b8..0a297b456 100644 --- a/src/route.rs +++ b/src/route.rs @@ -90,7 +90,7 @@ impl Service for RouteService { impl Route { /// Add method guard to the route. /// - /// ```rust + /// ``` /// # use actix_web::*; /// # fn main() { /// App::new().service(web::resource("/path").route( @@ -110,7 +110,7 @@ impl Route { /// Add guard to the route. /// - /// ```rust + /// ``` /// # use actix_web::*; /// # fn main() { /// App::new().service(web::resource("/path").route( @@ -128,7 +128,7 @@ impl Route { /// Set handler function, use request extractors for parameters. /// - /// ```rust + /// ``` /// use actix_web::{web, http, App}; /// use serde_derive::Deserialize; /// @@ -152,7 +152,7 @@ impl Route { /// /// It is possible to use multiple extractors for one handler function. /// - /// ```rust + /// ``` /// # use std::collections::HashMap; /// # use serde_derive::Deserialize; /// use actix_web::{web, App}; diff --git a/src/scope.rs b/src/scope.rs index e96f5b9e8..693d6860f 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -40,7 +40,7 @@ type HttpNewService = BoxServiceFactory<(), ServiceRequest, ServiceResponse, Err /// You can get variable path segments from `HttpRequest::match_info()`. /// `Path` extractor also is able to extract scope level variable segments. /// -/// ```rust +/// ``` /// use actix_web::{web, App, HttpResponse}; /// /// fn main() { @@ -97,7 +97,7 @@ where { /// Add match guard to a scope. /// - /// ```rust + /// ``` /// use actix_web::{web, guard, App, HttpRequest, HttpResponse}; /// /// async fn index(data: web::Path<(String, String)>) -> &'static str { @@ -123,7 +123,7 @@ where /// Set or override application data. Application data could be accessed /// by using `Data` extractor where `T` is data type. /// - /// ```rust + /// ``` /// use std::cell::Cell; /// use actix_web::{web, App, HttpResponse, Responder}; /// @@ -168,7 +168,7 @@ where /// different module or even library. For example, /// some of the resource's configuration could be moved to different module. /// - /// ```rust + /// ``` /// # extern crate actix_web; /// use actix_web::{web, middleware, App, HttpResponse}; /// @@ -215,7 +215,7 @@ where /// * *Scope* is a set of resources with common root path. /// * "StaticFiles" is a service for static files support /// - /// ```rust + /// ``` /// use actix_web::{web, App, HttpRequest}; /// /// struct AppState; @@ -247,7 +247,7 @@ where /// This method can be called multiple times, in that case /// multiple resources with one route would be registered for same resource path. /// - /// ```rust + /// ``` /// use actix_web::{web, App, HttpResponse}; /// /// async fn index(data: web::Path<(String, String)>) -> &'static str { @@ -341,7 +341,7 @@ where /// to Route or Application level middleware, in that Scope-level middleware /// can not modify ServiceResponse. /// - /// ```rust + /// ``` /// use actix_service::Service; /// use actix_web::{web, App}; /// use actix_web::http::{header::CONTENT_TYPE, HeaderValue}; diff --git a/src/server.rs b/src/server.rs index cfd8ae4b4..97c4fdaa2 100644 --- a/src/server.rs +++ b/src/server.rs @@ -35,7 +35,7 @@ struct Config { /// /// Create new HTTP server with application factory. /// -/// ```rust,no_run +/// ```no_run /// use actix_web::{web, App, HttpResponse, HttpServer}; /// /// #[actix_rt::main] @@ -588,7 +588,7 @@ where /// This methods panics if no socket address can be bound or an `Actix` system is not yet /// configured. /// - /// ```rust,no_run + /// ```no_run /// use std::io; /// use actix_web::{web, App, HttpResponse, HttpServer}; /// diff --git a/src/service.rs b/src/service.rs index 491810286..32f152f7d 100644 --- a/src/service.rs +++ b/src/service.rs @@ -462,7 +462,7 @@ impl WebService { /// Add match guard to a web service. /// - /// ```rust + /// ``` /// use actix_web::{web, guard, dev, App, Error, HttpResponse}; /// /// async fn index(req: dev::ServiceRequest) -> Result { diff --git a/src/test.rs b/src/test.rs index bc19296e2..7e8bf1069 100644 --- a/src/test.rs +++ b/src/test.rs @@ -54,7 +54,7 @@ pub fn default_service( /// This method accepts application builder instance, and constructs /// service. /// -/// ```rust +/// ``` /// use actix_service::Service; /// use actix_web::{test, web, App, HttpResponse, http::StatusCode}; /// @@ -101,7 +101,7 @@ where /// Calls service and waits for response future completion. /// -/// ```rust +/// ``` /// use actix_web::{test, web, App, HttpResponse, http::StatusCode}; /// /// #[actix_rt::test] @@ -131,7 +131,7 @@ where /// Helper function that returns a response body of a TestRequest /// -/// ```rust +/// ``` /// use actix_web::{test, web, App, HttpResponse, http::header}; /// use bytes::Bytes; /// @@ -174,7 +174,7 @@ where /// Helper function that returns a response body of a ServiceResponse. /// -/// ```rust +/// ``` /// use actix_web::{test, web, App, HttpResponse, http::header}; /// use bytes::Bytes; /// @@ -212,7 +212,7 @@ where /// Helper function that returns a deserialized response body of a ServiceResponse. /// -/// ```rust +/// ``` /// use actix_web::{App, test, web, HttpResponse, http::header}; /// use serde::{Serialize, Deserialize}; /// @@ -271,7 +271,7 @@ where /// Helper function that returns a deserialized response body of a TestRequest /// -/// ```rust +/// ``` /// use actix_web::{App, test, web, HttpResponse, http::header}; /// use serde::{Serialize, Deserialize}; /// @@ -324,7 +324,7 @@ where /// * `TestRequest::to_srv_response` creates `ServiceResponse` instance. /// * `TestRequest::to_http_request` creates `HttpRequest` instance, which is used for testing handlers. /// -/// ```rust +/// ``` /// use actix_web::{test, HttpRequest, HttpResponse, HttpMessage}; /// use actix_web::http::{header, StatusCode}; /// @@ -572,7 +572,7 @@ impl TestRequest { /// /// # Examples /// -/// ```rust +/// ``` /// use actix_web::{web, test, App, HttpResponse, Error}; /// /// async fn my_handler() -> Result { @@ -612,7 +612,7 @@ where /// /// # Examples /// -/// ```rust +/// ``` /// use actix_web::{web, test, App, HttpResponse, Error}; /// /// async fn my_handler() -> Result { diff --git a/src/web.rs b/src/web.rs index 1cef37109..8662848a4 100644 --- a/src/web.rs +++ b/src/web.rs @@ -42,7 +42,7 @@ pub use crate::types::*; /// `/users/{userid}/{friend}` and store `userid` and `friend` in /// the exposed `Params` object: /// -/// ```rust +/// ``` /// # extern crate actix_web; /// use actix_web::{web, App, HttpResponse}; /// @@ -61,7 +61,7 @@ pub fn resource(path: T) -> Resource { /// Scopes collect multiple paths under a common path prefix. /// Scope path can contain variable path segments as resources. /// -/// ```rust +/// ``` /// use actix_web::{web, App, HttpResponse}; /// /// let app = App::new().service( @@ -88,7 +88,7 @@ pub fn route() -> Route { /// Create *route* with `GET` method guard. /// -/// ```rust +/// ``` /// use actix_web::{web, App, HttpResponse}; /// /// let app = App::new().service( @@ -106,7 +106,7 @@ pub fn get() -> Route { /// Create *route* with `POST` method guard. /// -/// ```rust +/// ``` /// use actix_web::{web, App, HttpResponse}; /// /// let app = App::new().service( @@ -124,7 +124,7 @@ pub fn post() -> Route { /// Create *route* with `PUT` method guard. /// -/// ```rust +/// ``` /// use actix_web::{web, App, HttpResponse}; /// /// let app = App::new().service( @@ -142,7 +142,7 @@ pub fn put() -> Route { /// Create *route* with `PATCH` method guard. /// -/// ```rust +/// ``` /// use actix_web::{web, App, HttpResponse}; /// /// let app = App::new().service( @@ -160,7 +160,7 @@ pub fn patch() -> Route { /// Create *route* with `DELETE` method guard. /// -/// ```rust +/// ``` /// use actix_web::{web, App, HttpResponse}; /// /// let app = App::new().service( @@ -178,7 +178,7 @@ pub fn delete() -> Route { /// Create *route* with `HEAD` method guard. /// -/// ```rust +/// ``` /// use actix_web::{web, App, HttpResponse}; /// /// let app = App::new().service( @@ -196,7 +196,7 @@ pub fn head() -> Route { /// Create *route* with `TRACE` method guard. /// -/// ```rust +/// ``` /// use actix_web::{web, App, HttpResponse}; /// /// let app = App::new().service( @@ -214,7 +214,7 @@ pub fn trace() -> Route { /// Create *route* and add method guard. /// -/// ```rust +/// ``` /// use actix_web::{web, http, App, HttpResponse}; /// /// let app = App::new().service( @@ -232,7 +232,7 @@ pub fn method(method: Method) -> Route { /// Create a new route and add handler. /// -/// ```rust +/// ``` /// use actix_web::{web, App, HttpResponse, Responder}; /// /// async fn index() -> impl Responder { @@ -256,7 +256,7 @@ where /// Create raw service for a specific path. /// -/// ```rust +/// ``` /// use actix_web::{dev, web, guard, App, Error, HttpResponse}; /// /// async fn my_service(req: dev::ServiceRequest) -> Result { From 8c2ce2dedb4b3fd98654ed7451641408ab535b09 Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Thu, 25 Mar 2021 15:47:37 -0700 Subject: [PATCH 119/339] fix awc compress feature (#2116) --- actix-http/Cargo.toml | 1 - awc/CHANGES.md | 4 +++- awc/Cargo.toml | 1 - awc/src/request.rs | 19 +++++++------------ 4 files changed, 10 insertions(+), 15 deletions(-) diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index e1aebb76b..c24878404 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -55,7 +55,6 @@ base64 = "0.13" bitflags = "1.2" bytes = "1" bytestring = "1" -cfg-if = "1" cookie = { version = "0.14.1", features = ["percent-encode"], optional = true } derive_more = "0.99.5" encoding_rs = "0.8" diff --git a/awc/CHANGES.md b/awc/CHANGES.md index d2cb7c009..4f72e3f93 100644 --- a/awc/CHANGES.md +++ b/awc/CHANGES.md @@ -2,9 +2,11 @@ ## Unreleased - 2021-xx-xx ### Changed -* `ConnectorService` type is renamed to `BoxConnectorService` [#2081] +* `ConnectorService` type is renamed to `BoxConnectorService`. [#2081] +* Fix http/https encoding when enabling `compress` feature. [#2116] [#2081]: https://github.com/actix/actix-web/pull/2081 +[#2116]: https://github.com/actix/actix-web/pull/2116 ## 3.0.0-beta.3 - 2021-03-08 diff --git a/awc/Cargo.toml b/awc/Cargo.toml index ec2e03a96..b555ebb22 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -51,7 +51,6 @@ actix-rt = { version = "2.1", default-features = false } base64 = "0.13" bytes = "1" -cfg-if = "1.0" derive_more = "0.99.5" futures-core = { version = "0.3.7", default-features = false } itoa = "0.4" diff --git a/awc/src/request.rs b/awc/src/request.rs index a847b09a3..8b896a00d 100644 --- a/awc/src/request.rs +++ b/awc/src/request.rs @@ -21,15 +21,10 @@ use crate::frozen::FrozenClientRequest; use crate::sender::{PrepForSendingError, RequestSender, SendClientRequest}; use crate::ClientConfig; -cfg_if::cfg_if! { - if #[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))] { - const HTTPS_ENCODING: &str = "br, gzip, deflate"; - } else if #[cfg(feature = "compress")] { - const HTTPS_ENCODING: &str = "br"; - } else { - const HTTPS_ENCODING: &str = "identity"; - } -} +#[cfg(feature = "compress")] +const HTTPS_ENCODING: &str = "br, gzip, deflate"; +#[cfg(not(feature = "compress"))] +const HTTPS_ENCODING: &str = "br"; /// An HTTP Client request builder /// @@ -521,11 +516,11 @@ impl ClientRequest { .unwrap_or(true); if https { - slf = slf.insert_header_if_none((header::ACCEPT_ENCODING, HTTPS_ENCODING)) + slf = slf.insert_header_if_none((header::ACCEPT_ENCODING, HTTPS_ENCODING)); } else { - #[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))] + #[cfg(feature = "compress")] { - slf = slf.insert_header_if_none((header::ACCEPT_ENCODING, "gzip, deflate")) + slf = slf.insert_header_if_none((header::ACCEPT_ENCODING, "gzip, deflate")); } }; } From 2f7f1fa97aef546a369495a177c6dc893c26f4bf Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Thu, 25 Mar 2021 17:05:31 -0700 Subject: [PATCH 120/339] fix broken pipe for h2 when client is instantly dropped (#2113) --- actix-http/src/client/connection.rs | 56 +++++++++++++++++++++++++---- 1 file changed, 49 insertions(+), 7 deletions(-) diff --git a/actix-http/src/client/connection.rs b/actix-http/src/client/connection.rs index 78101397d..0e3e97f3f 100644 --- a/actix-http/src/client/connection.rs +++ b/actix-http/src/client/connection.rs @@ -174,7 +174,13 @@ impl H2ConnectionInner { /// Cancel spawned connection task on drop. impl Drop for H2ConnectionInner { fn drop(&mut self) { - self.handle.abort(); + if self + .sender + .send_request(http::Request::new(()), true) + .is_err() + { + self.handle.abort(); + } } } @@ -398,9 +404,18 @@ where #[cfg(test)] mod test { - use std::net; + use std::{ + future::Future, + net, + pin::Pin, + task::{Context, Poll}, + time::{Duration, Instant}, + }; - use actix_rt::net::TcpStream; + use actix_rt::{ + net::TcpStream, + time::{interval, Interval}, + }; use super::*; @@ -424,9 +439,36 @@ mod test { drop(conn); - match sender.ready().await { - Ok(_) => panic!("connection should be gone and can not be ready"), - Err(e) => assert!(e.is_io()), - }; + struct DropCheck { + sender: h2::client::SendRequest, + interval: Interval, + start_from: Instant, + } + + impl Future for DropCheck { + type Output = (); + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let this = self.get_mut(); + match futures_core::ready!(this.sender.poll_ready(cx)) { + Ok(()) => { + if this.start_from.elapsed() > Duration::from_secs(10) { + panic!("connection should be gone and can not be ready"); + } else { + let _ = this.interval.poll_tick(cx); + Poll::Pending + } + } + Err(_) => Poll::Ready(()), + } + } + } + + DropCheck { + sender, + interval: interval(Duration::from_millis(100)), + start_from: Instant::now(), + } + .await; } } From 6822bf2f580a504afcd36bcaa3a95d12e628bc3b Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Fri, 26 Mar 2021 09:15:04 -0700 Subject: [PATCH 121/339] Refactor actix_http::h1::service (#2117) --- actix-http/src/h1/service.rs | 205 ++++++++++++----------------------- 1 file changed, 70 insertions(+), 135 deletions(-) diff --git a/actix-http/src/h1/service.rs b/actix-http/src/h1/service.rs index 51303886b..4fe79736b 100644 --- a/actix-http/src/h1/service.rs +++ b/actix-http/src/h1/service.rs @@ -1,6 +1,4 @@ -use std::future::Future; use std::marker::PhantomData; -use std::pin::Pin; use std::rc::Rc; use std::task::{Context, Poll}; use std::{fmt, net}; @@ -8,7 +6,7 @@ use std::{fmt, net}; use actix_codec::{AsyncRead, AsyncWrite, Framed}; use actix_rt::net::TcpStream; use actix_service::{pipeline_factory, IntoServiceFactory, Service, ServiceFactory}; -use futures_core::ready; +use futures_core::{future::LocalBoxFuture, ready}; use futures_util::future::ready; use crate::body::MessageBody; @@ -60,14 +58,17 @@ where impl H1Service where S: ServiceFactory, + S::Future: 'static, S::Error: Into, S::InitError: fmt::Debug, S::Response: Into>, B: MessageBody, X: ServiceFactory, + X::Future: 'static, X::Error: Into, X::InitError: fmt::Debug, U: ServiceFactory<(Request, Framed), Config = (), Response = ()>, + U::Future: 'static, U::Error: fmt::Display + Into, U::InitError: fmt::Debug, { @@ -94,17 +95,21 @@ mod openssl { use super::*; use actix_service::ServiceFactoryExt; - use actix_tls::accept::openssl::{Acceptor, SslAcceptor, SslError, TlsStream}; - use actix_tls::accept::TlsError; + use actix_tls::accept::{ + openssl::{Acceptor, SslAcceptor, SslError, TlsStream}, + TlsError, + }; impl H1Service, S, B, X, U> where S: ServiceFactory, + S::Future: 'static, S::Error: Into, S::InitError: fmt::Debug, S::Response: Into>, B: MessageBody, X: ServiceFactory, + X::Future: 'static, X::Error: Into, X::InitError: fmt::Debug, U: ServiceFactory< @@ -112,6 +117,7 @@ mod openssl { Config = (), Response = (), >, + U::Future: 'static, U::Error: fmt::Display + Into, U::InitError: fmt::Debug, { @@ -143,19 +149,25 @@ mod openssl { #[cfg(feature = "rustls")] mod rustls { use super::*; + + use std::io; + use actix_service::ServiceFactoryExt; - use actix_tls::accept::rustls::{Acceptor, ServerConfig, TlsStream}; - use actix_tls::accept::TlsError; - use std::{fmt, io}; + use actix_tls::accept::{ + rustls::{Acceptor, ServerConfig, TlsStream}, + TlsError, + }; impl H1Service, S, B, X, U> where S: ServiceFactory, + S::Future: 'static, S::Error: Into, S::InitError: fmt::Debug, S::Response: Into>, B: MessageBody, X: ServiceFactory, + X::Future: 'static, X::Error: Into, X::InitError: fmt::Debug, U: ServiceFactory< @@ -163,6 +175,7 @@ mod rustls { Config = (), Response = (), >, + U::Future: 'static, U::Error: fmt::Display + Into, U::InitError: fmt::Debug, { @@ -241,16 +254,19 @@ where impl ServiceFactory<(T, Option)> for H1Service where - T: AsyncRead + AsyncWrite + Unpin, + T: AsyncRead + AsyncWrite + Unpin + 'static, S: ServiceFactory, + S::Future: 'static, S::Error: Into, S::Response: Into>, S::InitError: fmt::Debug, B: MessageBody, X: ServiceFactory, + X::Future: 'static, X::Error: Into, X::InitError: fmt::Debug, U: ServiceFactory<(Request, Framed), Config = (), Response = ()>, + U::Future: 'static, U::Error: fmt::Display + Into, U::InitError: fmt::Debug, { @@ -259,103 +275,42 @@ where type Config = (); type Service = H1ServiceHandler; type InitError = (); - type Future = H1ServiceResponse; + type Future = LocalBoxFuture<'static, Result>; fn new_service(&self, _: ()) -> Self::Future { - H1ServiceResponse { - fut: self.srv.new_service(()), - fut_ex: Some(self.expect.new_service(())), - fut_upg: self.upgrade.as_ref().map(|f| f.new_service(())), - expect: None, - upgrade: None, - on_connect_ext: self.on_connect_ext.clone(), - cfg: Some(self.cfg.clone()), - _phantom: PhantomData, - } - } -} + let service = self.srv.new_service(()); + let expect = self.expect.new_service(()); + let upgrade = self.upgrade.as_ref().map(|s| s.new_service(())); + let on_connect_ext = self.on_connect_ext.clone(); + let cfg = self.cfg.clone(); -#[doc(hidden)] -#[pin_project::pin_project] -pub struct H1ServiceResponse -where - S: ServiceFactory, - S::Error: Into, - S::InitError: fmt::Debug, - X: ServiceFactory, - X::Error: Into, - X::InitError: fmt::Debug, - U: ServiceFactory<(Request, Framed), Response = ()>, - U::Error: fmt::Display, - U::InitError: fmt::Debug, -{ - #[pin] - fut: S::Future, - #[pin] - fut_ex: Option, - #[pin] - fut_upg: Option, - expect: Option, - upgrade: Option, - on_connect_ext: Option>>, - cfg: Option, - _phantom: PhantomData, -} + Box::pin(async move { + let expect = expect + .await + .map_err(|e| log::error!("Init http expect service error: {:?}", e))?; -impl Future for H1ServiceResponse -where - T: AsyncRead + AsyncWrite + Unpin, - S: ServiceFactory, - S::Error: Into, - S::Response: Into>, - S::InitError: fmt::Debug, - B: MessageBody, - X: ServiceFactory, - X::Error: Into, - X::InitError: fmt::Debug, - U: ServiceFactory<(Request, Framed), Response = ()>, - U::Error: fmt::Display, - U::InitError: fmt::Debug, -{ - type Output = Result, ()>; + let upgrade = match upgrade { + Some(upgrade) => { + let upgrade = upgrade.await.map_err(|e| { + log::error!("Init http upgrade service error: {:?}", e) + })?; + Some(upgrade) + } + None => None, + }; - fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - let mut this = self.as_mut().project(); + let service = service + .await + .map_err(|e| log::error!("Init http service error: {:?}", e))?; - if let Some(fut) = this.fut_ex.as_pin_mut() { - let expect = ready!(fut - .poll(cx) - .map_err(|e| log::error!("Init http service error: {:?}", e)))?; - this = self.as_mut().project(); - *this.expect = Some(expect); - this.fut_ex.set(None); - } - - if let Some(fut) = this.fut_upg.as_pin_mut() { - let upgrade = ready!(fut - .poll(cx) - .map_err(|e| log::error!("Init http service error: {:?}", e)))?; - this = self.as_mut().project(); - *this.upgrade = Some(upgrade); - this.fut_upg.set(None); - } - - let result = ready!(this - .fut - .poll(cx) - .map_err(|e| log::error!("Init http service error: {:?}", e))); - - Poll::Ready(result.map(|service| { - let this = self.as_mut().project(); - - H1ServiceHandler::new( - this.cfg.take().unwrap(), + Ok(H1ServiceHandler::new( + cfg, service, - this.expect.take().unwrap(), - this.upgrade.take(), - this.on_connect_ext.clone(), - ) - })) + expect, + upgrade, + on_connect_ext, + )) + }) } } @@ -417,47 +372,27 @@ where type Future = Dispatcher; fn poll_ready(&self, cx: &mut Context<'_>) -> Poll> { - let ready = self - .flow - .expect - .poll_ready(cx) - .map_err(|e| { - let e = e.into(); - log::error!("Http service readiness error: {:?}", e); - DispatchError::Service(e) - })? - .is_ready(); + ready!(self.flow.expect.poll_ready(cx)).map_err(|e| { + let e = e.into(); + log::error!("Http expect service readiness error: {:?}", e); + DispatchError::Service(e) + })?; - let ready = self - .flow - .service - .poll_ready(cx) - .map_err(|e| { + if let Some(ref upg) = self.flow.upgrade { + ready!(upg.poll_ready(cx)).map_err(|e| { let e = e.into(); - log::error!("Http service readiness error: {:?}", e); + log::error!("Http upgrade service readiness error: {:?}", e); DispatchError::Service(e) - })? - .is_ready() - && ready; - - let ready = if let Some(ref upg) = self.flow.upgrade { - upg.poll_ready(cx) - .map_err(|e| { - let e = e.into(); - log::error!("Http service readiness error: {:?}", e); - DispatchError::Service(e) - })? - .is_ready() - && ready - } else { - ready + })?; }; - if ready { - Poll::Ready(Ok(())) - } else { - Poll::Pending - } + ready!(self.flow.service.poll_ready(cx)).map_err(|e| { + let e = e.into(); + log::error!("Http service readiness error: {:?}", e); + DispatchError::Service(e) + })?; + + Poll::Ready(Ok(())) } fn call(&self, (io, addr): (T, Option)) -> Self::Future { From 60f9cfbb2a0fc77ba6dc918f199ee3a602c64549 Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Fri, 26 Mar 2021 11:24:51 -0700 Subject: [PATCH 122/339] Refactor actix_http::h2::service module. Reduce loc. (#2118) --- actix-http/src/h2/service.rs | 88 ++++++++++++------------------------ 1 file changed, 30 insertions(+), 58 deletions(-) diff --git a/actix-http/src/h2/service.rs b/actix-http/src/h2/service.rs index c64139564..db0b580b3 100644 --- a/actix-http/src/h2/service.rs +++ b/actix-http/src/h2/service.rs @@ -11,8 +11,8 @@ use actix_service::{ ServiceFactory, }; use bytes::Bytes; -use futures_core::ready; -use futures_util::future::ok; +use futures_core::{future::LocalBoxFuture, ready}; +use futures_util::future::ready; use h2::server::{handshake, Handshake}; use log::error; @@ -65,6 +65,7 @@ where impl H2Service where S: ServiceFactory, + S::Future: 'static, S::Error: Into + 'static, S::Response: Into> + 'static, >::Future: 'static, @@ -80,11 +81,11 @@ where Error = DispatchError, InitError = S::InitError, > { - pipeline_factory(fn_factory(|| async { - Ok::<_, S::InitError>(fn_service(|io: TcpStream| { + pipeline_factory(fn_factory(|| { + ready(Ok::<_, S::InitError>(fn_service(|io: TcpStream| { let peer_addr = io.peer_addr().ok(); - ok::<_, DispatchError>((io, peer_addr)) - })) + ready(Ok::<_, DispatchError>((io, peer_addr))) + }))) })) .and_then(self) } @@ -101,6 +102,7 @@ mod openssl { impl H2Service, S, B> where S: ServiceFactory, + S::Future: 'static, S::Error: Into + 'static, S::Response: Into> + 'static, >::Future: 'static, @@ -123,10 +125,12 @@ mod openssl { .map_init_err(|_| panic!()), ) .and_then(fn_factory(|| { - ok::<_, S::InitError>(fn_service(|io: TlsStream| { - let peer_addr = io.get_ref().peer_addr().ok(); - ok((io, peer_addr)) - })) + ready(Ok::<_, S::InitError>(fn_service( + |io: TlsStream| { + let peer_addr = io.get_ref().peer_addr().ok(); + ready(Ok((io, peer_addr))) + }, + ))) })) .and_then(self.map_err(TlsError::Service)) } @@ -144,6 +148,7 @@ mod rustls { impl H2Service, S, B> where S: ServiceFactory, + S::Future: 'static, S::Error: Into + 'static, S::Response: Into> + 'static, >::Future: 'static, @@ -169,10 +174,12 @@ mod rustls { .map_init_err(|_| panic!()), ) .and_then(fn_factory(|| { - ok::<_, S::InitError>(fn_service(|io: TlsStream| { - let peer_addr = io.get_ref().0.peer_addr().ok(); - ok((io, peer_addr)) - })) + ready(Ok::<_, S::InitError>(fn_service( + |io: TlsStream| { + let peer_addr = io.get_ref().0.peer_addr().ok(); + ready(Ok((io, peer_addr))) + }, + ))) })) .and_then(self.map_err(TlsError::Service)) } @@ -181,8 +188,9 @@ mod rustls { impl ServiceFactory<(T, Option)> for H2Service where - T: AsyncRead + AsyncWrite + Unpin, + T: AsyncRead + AsyncWrite + Unpin + 'static, S: ServiceFactory, + S::Future: 'static, S::Error: Into + 'static, S::Response: Into> + 'static, >::Future: 'static, @@ -193,52 +201,16 @@ where type Config = (); type Service = H2ServiceHandler; type InitError = S::InitError; - type Future = H2ServiceResponse; + type Future = LocalBoxFuture<'static, Result>; fn new_service(&self, _: ()) -> Self::Future { - H2ServiceResponse { - fut: self.srv.new_service(()), - cfg: Some(self.cfg.clone()), - on_connect_ext: self.on_connect_ext.clone(), - _phantom: PhantomData, - } - } -} + let service = self.srv.new_service(()); + let cfg = self.cfg.clone(); + let on_connect_ext = self.on_connect_ext.clone(); -#[doc(hidden)] -#[pin_project::pin_project] -pub struct H2ServiceResponse -where - S: ServiceFactory, -{ - #[pin] - fut: S::Future, - cfg: Option, - on_connect_ext: Option>>, - _phantom: PhantomData, -} - -impl Future for H2ServiceResponse -where - T: AsyncRead + AsyncWrite + Unpin, - S: ServiceFactory, - S::Error: Into + 'static, - S::Response: Into> + 'static, - >::Future: 'static, - B: MessageBody + 'static, -{ - type Output = Result, S::InitError>; - - fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - let this = self.as_mut().project(); - - this.fut.poll(cx).map_ok(|service| { - let this = self.as_mut().project(); - H2ServiceHandler::new( - this.cfg.take().unwrap(), - this.on_connect_ext.clone(), - service, - ) + Box::pin(async move { + let service = service.await?; + Ok(H2ServiceHandler::new(cfg, on_connect_ext, service)) }) } } From f954a30c341fba28ecfca598b0b29f4af3cb4386 Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Mon, 29 Mar 2021 11:18:05 +0200 Subject: [PATCH 123/339] Fix typo in CHANGES.md (#2124) --- actix-http/CHANGES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index c4e0aec89..2c71031ab 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -6,7 +6,7 @@ * `client::ConnectorService` as `client::Connector::finish` method's return type [#2081] * `client::ConnectionIo` trait alias [#2081] -### Chaged +### Changed * `client::Connector` type now only have one generic type for `actix_service::Service`. [#2063] [#2063]: https://github.com/actix/actix-web/pull/2063 From e8ce73b49674895624801265c2a8525bfbf6bc62 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Mon, 29 Mar 2021 11:52:59 +0100 Subject: [PATCH 124/339] update dep docs --- actix-http/tests/test_ws.rs | 2 +- docs/graphs/net-only.dot | 24 +++++++++++------------- 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/actix-http/tests/test_ws.rs b/actix-http/tests/test_ws.rs index 3b90b4e54..51238215a 100644 --- a/actix-http/tests/test_ws.rs +++ b/actix-http/tests/test_ws.rs @@ -3,6 +3,7 @@ use std::future::Future; use std::marker::PhantomData; use std::pin::Pin; use std::sync::{Arc, Mutex}; +use std::task::{Context, Poll}; use actix_codec::{AsyncRead, AsyncWrite, Framed}; use actix_http::{body, h1, ws, Error, HttpService, Request, Response}; @@ -11,7 +12,6 @@ use actix_service::{fn_factory, Service}; use actix_utils::dispatcher::Dispatcher; use bytes::Bytes; use futures_util::future; -use futures_util::task::{Context, Poll}; use futures_util::{SinkExt as _, StreamExt as _}; struct WsService(Arc, Cell)>>); diff --git a/docs/graphs/net-only.dot b/docs/graphs/net-only.dot index 9488f3fe7..babd612a6 100644 --- a/docs/graphs/net-only.dot +++ b/docs/graphs/net-only.dot @@ -1,21 +1,19 @@ digraph { subgraph cluster_net { label="actix/actix-net"; - "actix-codec" - "actix-macros" - "actix-rt" - "actix-server" - "actix-service" - "actix-threadpool" - "actix-tls" - "actix-tracing" - "actix-utils" - "actix-router" + "actix-codec" "actix-macros" "actix-rt" "actix-server" "actix-service" + "actix-tls" "actix-tracing" "actix-utils" "actix-router" + "local-channel" "local-waker" } - "actix-utils" -> { "actix-service" "actix-rt" "actix-codec" } + "actix-codec" -> { "actix-rt" "actix-service" "local-channel" "tokio" } + "actix-utils" -> { "actix-rt" "actix-service" "local-waker" } "actix-tracing" -> { "actix-service" } "actix-tls" -> { "actix-service" "actix-codec" "actix-utils" "actix-rt" } - "actix-server" -> { "actix-service" "actix-rt" "actix-codec" "actix-utils" } - "actix-rt" -> { "actix-macros" "actix-threadpool" } + "actix-server" -> { "actix-service" "actix-rt" "actix-codec" "actix-utils" "tokio" } + "actix-rt" -> { "actix-macros" "tokio" } + + "local-channel" -> { "local-waker" } + + "tokio" [fontcolor = darkgreen] } From 980ecc5f07bc70d1e179f8189c4c30e2ecfdc632 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Mon, 29 Mar 2021 13:01:37 +0100 Subject: [PATCH 125/339] fix openssl windows ci --- .github/workflows/ci.yml | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index dc0ff0c19..3aac6efa8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,6 +23,9 @@ jobs: name: ${{ matrix.target.name }} / ${{ matrix.version }} runs-on: ${{ matrix.target.os }} + env: + VCPKGRS_DYNAMIC: 1 + steps: - uses: actions/checkout@v2 @@ -65,7 +68,13 @@ jobs: uses: actions-rs/cargo@v1 with: command: hack - args: --clean-per-run check --workspace --no-default-features --tests + args: check --workspace --no-default-features + + - name: check minimal + tests + uses: actions-rs/cargo@v1 + with: + command: hack + args: check --workspace --no-default-features --tests --examples - name: check full uses: actions-rs/cargo@v1 From 222acfd070aeccd08d856e5d45090eb38b67ec4a Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Mon, 29 Mar 2021 05:45:48 -0700 Subject: [PATCH 126/339] Fix build for next actix-tls-beta release (#2122) --- Cargo.toml | 4 ++-- actix-files/Cargo.toml | 2 +- actix-http-test/Cargo.toml | 4 ++-- actix-http/Cargo.toml | 6 +++--- actix-http/src/client/connector.rs | 23 ++++++++++++++--------- actix-multipart/Cargo.toml | 2 +- actix-web-actors/Cargo.toml | 2 +- actix-web-codegen/Cargo.toml | 2 +- awc/Cargo.toml | 2 +- awc/src/builder.rs | 7 +++---- 10 files changed, 29 insertions(+), 25 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index f3a6271ee..7dd7635cd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -80,11 +80,11 @@ required-features = ["rustls"] actix-codec = "0.4.0-beta.1" actix-macros = "0.2.0" actix-router = "0.2.7" -actix-rt = "2.1" +actix-rt = "2.2" actix-server = "2.0.0-beta.3" actix-service = "2.0.0-beta.4" actix-utils = "3.0.0-beta.2" -actix-tls = { version = "3.0.0-beta.4", default-features = false, optional = true } +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.4" diff --git a/actix-files/Cargo.toml b/actix-files/Cargo.toml index 49cd6966c..472bd0362 100644 --- a/actix-files/Cargo.toml +++ b/actix-files/Cargo.toml @@ -33,5 +33,5 @@ mime_guess = "2.0.1" percent-encoding = "2.1" [dev-dependencies] -actix-rt = "2.1" +actix-rt = "2.2" actix-web = "4.0.0-beta.4" diff --git a/actix-http-test/Cargo.toml b/actix-http-test/Cargo.toml index a7efc5310..0e7d57fc3 100644 --- a/actix-http-test/Cargo.toml +++ b/actix-http-test/Cargo.toml @@ -31,9 +31,9 @@ openssl = ["tls-openssl", "awc/openssl"] [dependencies] actix-service = "2.0.0-beta.4" actix-codec = "0.4.0-beta.1" -actix-tls = "3.0.0-beta.4" +actix-tls = "3.0.0-beta.5" actix-utils = "3.0.0-beta.2" -actix-rt = "2.1" +actix-rt = "2.2" actix-server = "2.0.0-beta.3" awc = { version = "3.0.0-beta.3", default-features = false } diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index c24878404..679e8c992 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -47,8 +47,8 @@ trust-dns = ["trust-dns-resolver"] actix-service = "2.0.0-beta.4" actix-codec = "0.4.0-beta.1" actix-utils = "3.0.0-beta.2" -actix-rt = "2.1" -actix-tls = "3.0.0-beta.4" +actix-rt = "2.2" +actix-tls = "3.0.0-beta.5" ahash = "0.7" base64 = "0.13" @@ -89,7 +89,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.3", features = ["openssl"] } -actix-tls = { version = "3.0.0-beta.4", features = ["openssl"] } +actix-tls = { version = "3.0.0-beta.5", features = ["openssl"] } criterion = "0.3" env_logger = "0.8" rcgen = "0.8" diff --git a/actix-http/src/client/connector.rs b/actix-http/src/client/connector.rs index 6996677d2..508fe748b 100644 --- a/actix-http/src/client/connector.rs +++ b/actix-http/src/client/connector.rs @@ -9,7 +9,7 @@ use std::{ }; use actix_rt::{ - net::TcpStream, + net::{ActixStream, TcpStream}, time::{sleep, Sleep}, }; use actix_service::Service; @@ -119,7 +119,7 @@ impl Connector { /// Use custom connector. pub fn connector(self, connector: S1) -> Connector where - Io1: ConnectionIo + fmt::Debug, + Io1: ActixStream + fmt::Debug + 'static, S1: Service< TcpConnect, Response = TcpConnection, @@ -136,7 +136,14 @@ impl Connector { impl Connector where - Io: ConnectionIo + fmt::Debug, + // Note: + // Input Io type is bound to ActixStream trait but internally in client module they + // are bound to ConnectionIo trait alias. And latter is the trait exposed to public + // in the form of Box type. + // + // 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, @@ -407,16 +414,14 @@ struct TlsConnectorService { timeout: Duration, } -impl Service for TlsConnectorService +impl Service for TlsConnectorService where S: Service, Error = ConnectError> + Clone + 'static, - St: Service, Response = Res, Error = std::io::Error> - + Clone - + 'static, + St: Service, Error = std::io::Error> + Clone + 'static, Io: ConnectionIo, - Res: IntoConnectionIo, + St::Response: IntoConnectionIo, { type Response = (Box, Protocol); type Error = ConnectError; @@ -471,10 +476,10 @@ where Error = std::io::Error, Future = Fut2, >, + S::Response: IntoConnectionIo, Fut1: Future, ConnectError>>, Fut2: Future>, Io: ConnectionIo, - Res: IntoConnectionIo, { type Output = Result<(Box, Protocol), ConnectError>; diff --git a/actix-multipart/Cargo.toml b/actix-multipart/Cargo.toml index 9a3ea7bb5..607e90849 100644 --- a/actix-multipart/Cargo.toml +++ b/actix-multipart/Cargo.toml @@ -28,7 +28,7 @@ mime = "0.3" twoway = "0.2" [dev-dependencies] -actix-rt = "2.1" +actix-rt = "2.2" actix-http = "3.0.0-beta.4" tokio = { version = "1", features = ["sync"] } tokio-stream = "0.1" diff --git a/actix-web-actors/Cargo.toml b/actix-web-actors/Cargo.toml index 77663540c..1f734582d 100644 --- a/actix-web-actors/Cargo.toml +++ b/actix-web-actors/Cargo.toml @@ -28,6 +28,6 @@ pin-project = "1.0.0" tokio = { version = "1", features = ["sync"] } [dev-dependencies] -actix-rt = "2.1" +actix-rt = "2.2" 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 d8a189565..fdfb9f6ba 100644 --- a/actix-web-codegen/Cargo.toml +++ b/actix-web-codegen/Cargo.toml @@ -19,7 +19,7 @@ syn = { version = "1", features = ["full", "parsing"] } proc-macro2 = "1" [dev-dependencies] -actix-rt = "2.1" +actix-rt = "2.2" actix-web = "4.0.0-beta.4" futures-util = { version = "0.3.7", default-features = false } trybuild = "1" diff --git a/awc/Cargo.toml b/awc/Cargo.toml index b555ebb22..cc6841606 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -71,7 +71,7 @@ actix-http = { version = "3.0.0-beta.4", features = ["openssl"] } actix-http-test = { version = "3.0.0-beta.3", features = ["openssl"] } actix-utils = "3.0.0-beta.1" actix-server = "2.0.0-beta.3" -actix-tls = { version = "3.0.0-beta.4", features = ["openssl", "rustls"] } +actix-tls = { version = "3.0.0-beta.5", features = ["openssl", "rustls"] } brotli2 = "0.3.2" env_logger = "0.8" diff --git a/awc/src/builder.rs b/awc/src/builder.rs index 925d9ae2a..c594b4836 100644 --- a/awc/src/builder.rs +++ b/awc/src/builder.rs @@ -4,12 +4,11 @@ use std::net::IpAddr; use std::rc::Rc; use std::time::Duration; -use actix_codec::{AsyncRead, AsyncWrite}; use actix_http::{ client::{Connector, ConnectorService, TcpConnect, TcpConnectError, TcpConnection}, http::{self, header, Error as HttpError, HeaderMap, HeaderName, Uri}, }; -use actix_rt::net::TcpStream; +use actix_rt::net::{ActixStream, TcpStream}; use actix_service::{boxed, Service}; use crate::connect::DefaultConnector; @@ -64,7 +63,7 @@ where S: Service, Response = TcpConnection, Error = TcpConnectError> + Clone + 'static, - Io: AsyncRead + AsyncWrite + Unpin + fmt::Debug + 'static, + Io: ActixStream + fmt::Debug + 'static, { /// Use custom connector service. pub fn connector(self, connector: Connector) -> ClientBuilder @@ -75,7 +74,7 @@ where Error = TcpConnectError, > + Clone + 'static, - Io1: AsyncRead + AsyncWrite + Unpin + fmt::Debug + 'static, + Io1: ActixStream + fmt::Debug + 'static, { ClientBuilder { middleware: self.middleware, From 1281a748d03aa63fb7662c638d725d91b29a44c7 Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Mon, 29 Mar 2021 19:06:16 -0700 Subject: [PATCH 127/339] merge H1ServiceHandler requests into HttpServiceHandler (#2126) --- actix-http/src/h1/service.rs | 68 +-------- actix-http/src/service.rs | 260 ++++++++++++----------------------- 2 files changed, 95 insertions(+), 233 deletions(-) diff --git a/actix-http/src/h1/service.rs b/actix-http/src/h1/service.rs index 4fe79736b..f915bfa47 100644 --- a/actix-http/src/h1/service.rs +++ b/actix-http/src/h1/service.rs @@ -6,7 +6,7 @@ use std::{fmt, net}; use actix_codec::{AsyncRead, AsyncWrite, Framed}; use actix_rt::net::TcpStream; use actix_service::{pipeline_factory, IntoServiceFactory, Service, ServiceFactory}; -use futures_core::{future::LocalBoxFuture, ready}; +use futures_core::future::LocalBoxFuture; use futures_util::future::ready; use crate::body::MessageBody; @@ -14,7 +14,7 @@ use crate::config::ServiceConfig; use crate::error::{DispatchError, Error}; use crate::request::Request; use crate::response::Response; -use crate::service::HttpFlow; +use crate::service::HttpServiceHandler; use crate::{ConnectCallback, OnConnectData}; use super::codec::Codec; @@ -315,47 +315,10 @@ where } /// `Service` implementation for HTTP/1 transport -pub struct H1ServiceHandler -where - S: Service, - X: Service, - U: Service<(Request, Framed)>, -{ - flow: Rc>, - on_connect_ext: Option>>, - cfg: ServiceConfig, - _phantom: PhantomData, -} - -impl H1ServiceHandler -where - S: Service, - S::Error: Into, - S::Response: Into>, - B: MessageBody, - X: Service, - X::Error: Into, - U: Service<(Request, Framed), Response = ()>, - U::Error: fmt::Display, -{ - fn new( - cfg: ServiceConfig, - service: S, - expect: X, - upgrade: Option, - on_connect_ext: Option>>, - ) -> H1ServiceHandler { - H1ServiceHandler { - flow: HttpFlow::new(service, expect, upgrade), - cfg, - on_connect_ext, - _phantom: PhantomData, - } - } -} +pub type H1ServiceHandler = HttpServiceHandler; impl Service<(T, Option)> - for H1ServiceHandler + for HttpServiceHandler where T: AsyncRead + AsyncWrite + Unpin, S: Service, @@ -372,27 +335,10 @@ where type Future = Dispatcher; fn poll_ready(&self, cx: &mut Context<'_>) -> Poll> { - ready!(self.flow.expect.poll_ready(cx)).map_err(|e| { - let e = e.into(); - log::error!("Http expect service readiness error: {:?}", e); + self._poll_ready(cx).map_err(|e| { + log::error!("HTTP/1 service readiness error: {:?}", e); DispatchError::Service(e) - })?; - - if let Some(ref upg) = self.flow.upgrade { - ready!(upg.poll_ready(cx)).map_err(|e| { - let e = e.into(); - log::error!("Http upgrade service readiness error: {:?}", e); - DispatchError::Service(e) - })?; - }; - - ready!(self.flow.service.poll_ready(cx)).map_err(|e| { - let e = e.into(); - log::error!("Http service readiness error: {:?}", e); - DispatchError::Service(e) - })?; - - Poll::Ready(Ok(())) + }) } fn call(&self, (io, addr): (T, Option)) -> Self::Future { diff --git a/actix-http/src/service.rs b/actix-http/src/service.rs index 1a06cec3d..fd97fb5ef 100644 --- a/actix-http/src/service.rs +++ b/actix-http/src/service.rs @@ -12,7 +12,7 @@ use actix_codec::{AsyncRead, AsyncWrite, Framed}; use actix_rt::net::TcpStream; use actix_service::{pipeline_factory, IntoServiceFactory, Service, ServiceFactory}; use bytes::Bytes; -use futures_core::ready; +use futures_core::{future::LocalBoxFuture, ready}; use h2::server::{handshake, Handshake}; use pin_project::pin_project; @@ -107,7 +107,6 @@ where X1: ServiceFactory, X1::Error: Into, X1::InitError: fmt::Debug, - >::Future: 'static, { HttpService { expect, @@ -128,7 +127,6 @@ where U1: ServiceFactory<(Request, Framed), Config = (), Response = ()>, U1::Error: fmt::Display, U1::InitError: fmt::Debug, - )>>::Future: 'static, { HttpService { upgrade, @@ -150,23 +148,24 @@ where impl HttpService where S: ServiceFactory, + S::Future: 'static, S::Error: Into + 'static, S::InitError: fmt::Debug, S::Response: Into> + 'static, >::Future: 'static, B: MessageBody + 'static, X: ServiceFactory, + X::Future: 'static, X::Error: Into, X::InitError: fmt::Debug, - >::Future: 'static, U: ServiceFactory< (Request, Framed), Config = (), Response = (), >, + U::Future: 'static, U::Error: fmt::Display + Into, U::InitError: fmt::Debug, - )>>::Future: 'static, { /// Create simple tcp stream service pub fn tcp( @@ -196,23 +195,24 @@ mod openssl { impl HttpService, S, B, X, U> where S: ServiceFactory, + S::Future: 'static, S::Error: Into + 'static, S::InitError: fmt::Debug, S::Response: Into> + 'static, >::Future: 'static, B: MessageBody + 'static, X: ServiceFactory, + X::Future: 'static, X::Error: Into, X::InitError: fmt::Debug, - >::Future: 'static, U: ServiceFactory< (Request, Framed, h1::Codec>), Config = (), Response = (), >, + U::Future: 'static, U::Error: fmt::Display + Into, U::InitError: fmt::Debug, - , h1::Codec>)>>::Future: 'static, { /// Create openssl based service pub fn openssl( @@ -261,23 +261,24 @@ mod rustls { impl HttpService, S, B, X, U> where S: ServiceFactory, + S::Future: 'static, S::Error: Into + 'static, S::InitError: fmt::Debug, S::Response: Into> + 'static, >::Future: 'static, B: MessageBody + 'static, X: ServiceFactory, + X::Future: 'static, X::Error: Into, X::InitError: fmt::Debug, - >::Future: 'static, U: ServiceFactory< (Request, Framed, h1::Codec>), Config = (), Response = (), >, + U::Future: 'static, U::Error: fmt::Display + Into, U::InitError: fmt::Debug, - , h1::Codec>)>>::Future: 'static, { /// Create openssl based service pub fn rustls( @@ -319,137 +320,117 @@ mod rustls { impl ServiceFactory<(T, Protocol, Option)> for HttpService where - T: AsyncRead + AsyncWrite + Unpin, + T: AsyncRead + AsyncWrite + Unpin + 'static, S: ServiceFactory, + S::Future: 'static, S::Error: Into + 'static, S::InitError: fmt::Debug, S::Response: Into> + 'static, >::Future: 'static, B: MessageBody + 'static, X: ServiceFactory, + X::Future: 'static, X::Error: Into, X::InitError: fmt::Debug, - >::Future: 'static, U: ServiceFactory<(Request, Framed), Config = (), Response = ()>, + U::Future: 'static, U::Error: fmt::Display + Into, U::InitError: fmt::Debug, - )>>::Future: 'static, { type Response = (); type Error = DispatchError; type Config = (); type Service = HttpServiceHandler; type InitError = (); - type Future = HttpServiceResponse; + type Future = LocalBoxFuture<'static, Result>; fn new_service(&self, _: ()) -> Self::Future { - HttpServiceResponse { - fut: self.srv.new_service(()), - fut_ex: Some(self.expect.new_service(())), - fut_upg: self.upgrade.as_ref().map(|f| f.new_service(())), - expect: None, - upgrade: None, - on_connect_ext: self.on_connect_ext.clone(), - cfg: self.cfg.clone(), - _phantom: PhantomData, - } - } -} + let service = self.srv.new_service(()); + let expect = self.expect.new_service(()); + let upgrade = self.upgrade.as_ref().map(|s| s.new_service(())); + let on_connect_ext = self.on_connect_ext.clone(); + let cfg = self.cfg.clone(); -#[doc(hidden)] -#[pin_project] -pub struct HttpServiceResponse -where - S: ServiceFactory, - X: ServiceFactory, - U: ServiceFactory<(Request, Framed)>, -{ - #[pin] - fut: S::Future, - #[pin] - fut_ex: Option, - #[pin] - fut_upg: Option, - expect: Option, - upgrade: Option, - on_connect_ext: Option>>, - cfg: ServiceConfig, - _phantom: PhantomData, -} + Box::pin(async move { + let expect = expect + .await + .map_err(|e| log::error!("Init http expect service error: {:?}", e))?; -impl Future for HttpServiceResponse -where - T: AsyncRead + AsyncWrite + Unpin, - S: ServiceFactory, - S::Error: Into + 'static, - S::InitError: fmt::Debug, - S::Response: Into> + 'static, - >::Future: 'static, - B: MessageBody + 'static, - X: ServiceFactory, - X::Error: Into, - X::InitError: fmt::Debug, - >::Future: 'static, - U: ServiceFactory<(Request, Framed), Response = ()>, - U::Error: fmt::Display, - U::InitError: fmt::Debug, - )>>::Future: 'static, -{ - type Output = - Result, ()>; + let upgrade = match upgrade { + Some(upgrade) => { + let upgrade = upgrade.await.map_err(|e| { + log::error!("Init http upgrade service error: {:?}", e) + })?; + Some(upgrade) + } + None => None, + }; - fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - let mut this = self.as_mut().project(); + let service = service + .await + .map_err(|e| log::error!("Init http service error: {:?}", e))?; - if let Some(fut) = this.fut_ex.as_pin_mut() { - let expect = ready!(fut - .poll(cx) - .map_err(|e| log::error!("Init http service error: {:?}", e)))?; - this = self.as_mut().project(); - *this.expect = Some(expect); - this.fut_ex.set(None); - } - - if let Some(fut) = this.fut_upg.as_pin_mut() { - let upgrade = ready!(fut - .poll(cx) - .map_err(|e| log::error!("Init http service error: {:?}", e)))?; - this = self.as_mut().project(); - *this.upgrade = Some(upgrade); - this.fut_upg.set(None); - } - - let result = ready!(this - .fut - .poll(cx) - .map_err(|e| log::error!("Init http service error: {:?}", e))); - - Poll::Ready(result.map(|service| { - let this = self.as_mut().project(); - HttpServiceHandler::new( - this.cfg.clone(), + Ok(HttpServiceHandler::new( + cfg, service, - this.expect.take().unwrap(), - this.upgrade.take(), - this.on_connect_ext.clone(), - ) - })) + expect, + upgrade, + on_connect_ext, + )) + }) } } -/// `Service` implementation for HTTP transport +/// `Service` implementation for HTTP/1 and HTTP/2 transport pub struct HttpServiceHandler where S: Service, X: Service, U: Service<(Request, Framed)>, { - flow: Rc>, - cfg: ServiceConfig, - on_connect_ext: Option>>, + pub(super) flow: Rc>, + pub(super) cfg: ServiceConfig, + pub(super) on_connect_ext: Option>>, _phantom: PhantomData, } +impl HttpServiceHandler +where + S: Service, + S::Error: Into, + X: Service, + X::Error: Into, + U: Service<(Request, Framed)>, + U::Error: Into, +{ + pub(super) fn new( + cfg: ServiceConfig, + service: S, + expect: X, + upgrade: Option, + on_connect_ext: Option>>, + ) -> HttpServiceHandler { + HttpServiceHandler { + cfg, + on_connect_ext, + flow: HttpFlow::new(service, expect, upgrade), + _phantom: PhantomData, + } + } + + 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))?; + + if let Some(ref upg) = self.flow.upgrade { + ready!(upg.poll_ready(cx).map_err(Into::into))?; + }; + + Poll::Ready(Ok(())) + } +} + /// A collection of services that describe an HTTP request flow. pub(super) struct HttpFlow { pub(super) service: S, @@ -467,34 +448,6 @@ impl HttpFlow { } } -impl HttpServiceHandler -where - S: Service, - S::Error: Into + 'static, - S::Future: 'static, - S::Response: Into> + 'static, - B: MessageBody + 'static, - X: Service, - X::Error: Into, - U: Service<(Request, Framed), Response = ()>, - U::Error: fmt::Display, -{ - fn new( - cfg: ServiceConfig, - service: S, - expect: X, - upgrade: Option, - on_connect_ext: Option>>, - ) -> HttpServiceHandler { - HttpServiceHandler { - cfg, - on_connect_ext, - flow: HttpFlow::new(service, expect, upgrade), - _phantom: PhantomData, - } - } -} - impl Service<(T, Protocol, Option)> for HttpServiceHandler where @@ -514,47 +467,10 @@ where type Future = HttpServiceHandlerResponse; fn poll_ready(&self, cx: &mut Context<'_>) -> Poll> { - let ready = self - .flow - .expect - .poll_ready(cx) - .map_err(|e| { - let e = e.into(); - log::error!("Http service readiness error: {:?}", e); - DispatchError::Service(e) - })? - .is_ready(); - - let ready = self - .flow - .service - .poll_ready(cx) - .map_err(|e| { - let e = e.into(); - log::error!("Http service readiness error: {:?}", e); - DispatchError::Service(e) - })? - .is_ready() - && ready; - - let ready = if let Some(ref upg) = self.flow.upgrade { - upg.poll_ready(cx) - .map_err(|e| { - let e = e.into(); - log::error!("Http service readiness error: {:?}", e); - DispatchError::Service(e) - })? - .is_ready() - && ready - } else { - ready - }; - - if ready { - Poll::Ready(Ok(())) - } else { - Poll::Pending - } + self._poll_ready(cx).map_err(|e| { + log::error!("HTTP service readiness error: {:?}", e); + DispatchError::Service(e) + }) } fn call( From f66774e30b239546991fb6c0d54c8885171d1b8f Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Tue, 30 Mar 2021 03:32:22 +0100 Subject: [PATCH 128/339] remove `From` impl from HttpDate fully removes time crate from public api of -http --- .cargo/config.toml | 3 +++ actix-http/src/header/shared/httpdate.rs | 24 ++++++++++-------------- docs/graphs/net-only.dot | 2 +- 3 files changed, 14 insertions(+), 15 deletions(-) create mode 100644 .cargo/config.toml diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 000000000..9d0d9da8c --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,3 @@ +[alias] +chk = "hack check --workspace --tests --examples" +lint = "hack --clean-per-run clippy --workspace --tests --examples" diff --git a/actix-http/src/header/shared/httpdate.rs b/actix-http/src/header/shared/httpdate.rs index 72a225589..18278a6d8 100644 --- a/actix-http/src/header/shared/httpdate.rs +++ b/actix-http/src/header/shared/httpdate.rs @@ -1,18 +1,20 @@ -use std::fmt::{self, Display}; -use std::io::Write; -use std::str::FromStr; -use std::time::{SystemTime, UNIX_EPOCH}; +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::{offset, OffsetDateTime, PrimitiveDateTime}; +use time::{OffsetDateTime, PrimitiveDateTime, UtcOffset}; use crate::error::ParseError; use crate::header::IntoHeaderValue; use crate::time_parser; -/// A timestamp with HTTP formatting and parsing +/// A timestamp with HTTP formatting and parsing. #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] pub struct HttpDate(OffsetDateTime); @@ -27,18 +29,12 @@ impl FromStr for HttpDate { } } -impl Display for HttpDate { +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(dt: OffsetDateTime) -> HttpDate { - HttpDate(dt) - } -} - impl From for HttpDate { fn from(sys: SystemTime) -> HttpDate { HttpDate(PrimitiveDateTime::from(sys).assume_utc()) @@ -54,7 +50,7 @@ impl IntoHeaderValue for HttpDate { wrt, "{}", self.0 - .to_offset(offset!(UTC)) + .to_offset(UtcOffset::UTC) .format("%a, %d %b %Y %H:%M:%S GMT") ) .unwrap(); diff --git a/docs/graphs/net-only.dot b/docs/graphs/net-only.dot index babd612a6..84227cdb0 100644 --- a/docs/graphs/net-only.dot +++ b/docs/graphs/net-only.dot @@ -7,7 +7,7 @@ digraph { } "actix-codec" -> { "actix-rt" "actix-service" "local-channel" "tokio" } - "actix-utils" -> { "actix-rt" "actix-service" "local-waker" } + "actix-utils" -> { "local-waker" } "actix-tracing" -> { "actix-service" } "actix-tls" -> { "actix-service" "actix-codec" "actix-utils" "actix-rt" } "actix-server" -> { "actix-service" "actix-rt" "actix-codec" "actix-utils" "tokio" } From c49fe79207aadd5ef7e2a8db5daadf19fab2366a Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Tue, 30 Mar 2021 07:46:09 -0700 Subject: [PATCH 129/339] Simplify lifetime annotation in HttpServiceBuilder. Simplify PlStream (#2129) --- actix-http/src/builder.rs | 6 ----- actix-http/src/client/h1proto.rs | 40 +++++++++++--------------------- 2 files changed, 14 insertions(+), 32 deletions(-) diff --git a/actix-http/src/builder.rs b/actix-http/src/builder.rs index fa430c4fe..623bfdda2 100644 --- a/actix-http/src/builder.rs +++ b/actix-http/src/builder.rs @@ -63,11 +63,9 @@ where X: ServiceFactory, X::Error: Into, X::InitError: fmt::Debug, - >::Future: 'static, U: ServiceFactory<(Request, Framed), Config = (), Response = ()>, U::Error: fmt::Display, U::InitError: fmt::Debug, - )>>::Future: 'static, { /// Set server keep-alive setting. /// @@ -127,7 +125,6 @@ where X1: ServiceFactory, X1::Error: Into, X1::InitError: fmt::Debug, - >::Future: 'static, { HttpServiceBuilder { keep_alive: self.keep_alive, @@ -152,7 +149,6 @@ where U1: ServiceFactory<(Request, Framed), Config = (), Response = ()>, U1::Error: fmt::Display, U1::InitError: fmt::Debug, - )>>::Future: 'static, { HttpServiceBuilder { keep_alive: self.keep_alive, @@ -211,7 +207,6 @@ where S::Error: Into + 'static, S::InitError: fmt::Debug, S::Response: Into> + 'static, - >::Future: 'static, { let cfg = ServiceConfig::new( self.keep_alive, @@ -233,7 +228,6 @@ where S::Error: Into + 'static, S::InitError: fmt::Debug, S::Response: Into> + 'static, - >::Future: 'static, { let cfg = ServiceConfig::new( self.keep_alive, diff --git a/actix-http/src/client/h1proto.rs b/actix-http/src/client/h1proto.rs index 01a6e1edf..8fb08b0ce 100644 --- a/actix-http/src/client/h1proto.rs +++ b/actix-http/src/client/h1proto.rs @@ -7,7 +7,7 @@ use std::{ use actix_codec::Framed; use bytes::buf::BufMut; use bytes::{Bytes, BytesMut}; -use futures_core::Stream; +use futures_core::{ready, Stream}; use futures_util::{future::poll_fn, SinkExt as _}; use crate::error::PayloadError; @@ -17,7 +17,7 @@ use crate::http::{ StatusCode, }; use crate::message::{RequestHeadType, ResponseHead}; -use crate::payload::{Payload, PayloadStream}; +use crate::payload::Payload; use super::connection::{ConnectionIo, H1Connection}; use super::error::{ConnectError, SendRequestError}; @@ -122,10 +122,7 @@ where Ok((head, Payload::None)) } - _ => { - let pl: PayloadStream = Box::pin(PlStream::new(framed)); - Ok((head, pl.into())) - } + _ => Ok((head, Payload::Stream(Box::pin(PlStream::new(framed))))), } } @@ -194,21 +191,16 @@ where } #[pin_project::pin_project] -pub(crate) struct PlStream -where - Io: ConnectionIo, -{ +pub(crate) struct PlStream { #[pin] - framed: Option, h1::ClientPayloadCodec>>, + framed: Framed, h1::ClientPayloadCodec>, } impl PlStream { fn new(framed: Framed, h1::ClientCodec>) -> Self { let framed = framed.into_map_codec(|codec| codec.into_payload_codec()); - PlStream { - framed: Some(framed), - } + PlStream { framed } } } @@ -219,20 +211,16 @@ impl Stream for PlStream { self: Pin<&mut Self>, cx: &mut Context<'_>, ) -> Poll> { - let mut framed = self.project().framed.as_pin_mut().unwrap(); + let mut this = self.project(); - match framed.as_mut().next_item(cx)? { - Poll::Pending => Poll::Pending, - Poll::Ready(Some(chunk)) => { - if let Some(chunk) = chunk { - Poll::Ready(Some(Ok(chunk))) - } else { - let keep_alive = framed.codec_ref().keepalive(); - framed.io_mut().on_release(keep_alive); - Poll::Ready(None) - } + 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(); + this.framed.io_mut().on_release(keep_alive); + Poll::Ready(None) } - Poll::Ready(None) => Poll::Ready(None), + None => Poll::Ready(None), } } } From 1f1be6fd3d868f242ffd0ef72a7871f9e8f0d2b1 Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Wed, 31 Mar 2021 03:43:56 -0700 Subject: [PATCH 130/339] add Client::headers (#2114) --- awc/CHANGES.md | 4 ++++ awc/src/lib.rs | 8 ++++++++ 2 files changed, 12 insertions(+) diff --git a/awc/CHANGES.md b/awc/CHANGES.md index 4f72e3f93..b745e9868 100644 --- a/awc/CHANGES.md +++ b/awc/CHANGES.md @@ -1,11 +1,15 @@ # Changes ## Unreleased - 2021-xx-xx +### Added +* 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] [#2081]: https://github.com/actix/actix-web/pull/2081 +[#2114]: https://github.com/actix/actix-web/pull/2114 [#2116]: https://github.com/actix/actix-web/pull/2116 diff --git a/awc/src/lib.rs b/awc/src/lib.rs index c7bb68a8f..f1aecbd37 100644 --- a/awc/src/lib.rs +++ b/awc/src/lib.rs @@ -286,4 +286,12 @@ impl Client { } 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.headers) + } } From a807d33600e3797401caf1f0aedb04b50400e710 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Pokrywka?= Date: Thu, 1 Apr 2021 07:40:10 +0200 Subject: [PATCH 131/339] added TestServer::client_headers (#2097) Co-authored-by: fakeshadow <24548779@qq.com> Co-authored-by: Rob Ede --- CHANGES.md | 3 ++ actix-http-test/CHANGES.md | 3 +- actix-http-test/src/lib.rs | 12 ++++++- actix-web-actors/Cargo.toml | 1 + actix-web-actors/tests/test_ws.rs | 53 ++++++++++++++++++++++++++++++- src/test.rs | 10 +++++- 6 files changed, 78 insertions(+), 4 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 5df0b6d6d..39743be2b 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,9 @@ # Changes ## Unreleased - 2021-xx-xx +### Added +* Added `TestServer::client_headers` method. [#2097] + ### Fixed * Double ampersand in Logger format is escaped correctly. [#2067] diff --git a/actix-http-test/CHANGES.md b/actix-http-test/CHANGES.md index d6a2cdd9b..0fac84a6c 100644 --- a/actix-http-test/CHANGES.md +++ b/actix-http-test/CHANGES.md @@ -1,7 +1,8 @@ # Changes ## Unreleased - 2021-xx-xx - +### Added +* Added `TestServer::client_headers` method. [#2097] ## 3.0.0-beta.3 - 2021-03-09 * No notable changes. diff --git a/actix-http-test/src/lib.rs b/actix-http-test/src/lib.rs index 3749b78ca..9a5069c49 100644 --- a/actix-http-test/src/lib.rs +++ b/actix-http-test/src/lib.rs @@ -13,7 +13,9 @@ use std::{net, thread, time}; use actix_codec::{AsyncRead, AsyncWrite, Framed}; use actix_rt::{net::TcpStream, System}; use actix_server::{Server, ServiceFactory}; -use awc::{error::PayloadError, ws, Client, ClientRequest, ClientResponse, Connector}; +use awc::{ + error::PayloadError, http::HeaderMap, ws, Client, ClientRequest, ClientResponse, Connector, +}; use bytes::Bytes; use futures_core::stream::Stream; use http::Method; @@ -258,6 +260,14 @@ impl TestServer { self.ws_at("/").await } + /// 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 client_headers(&mut self) -> Option<&mut HeaderMap> { + self.client.headers() + } + /// Stop HTTP server fn stop(&mut self) { self.system.stop(); diff --git a/actix-web-actors/Cargo.toml b/actix-web-actors/Cargo.toml index 1f734582d..e60d4301c 100644 --- a/actix-web-actors/Cargo.toml +++ b/actix-web-actors/Cargo.toml @@ -29,5 +29,6 @@ tokio = { version = "1", features = ["sync"] } [dev-dependencies] actix-rt = "2.2" +awc = { version = "3.0.0-beta.3", default-features = false } env_logger = "0.8" futures-util = { version = "0.3.7", default-features = false } diff --git a/actix-web-actors/tests/test_ws.rs b/actix-web-actors/tests/test_ws.rs index 912480ae4..7999beed9 100644 --- a/actix-web-actors/tests/test_ws.rs +++ b/actix-web-actors/tests/test_ws.rs @@ -1,5 +1,8 @@ use actix::prelude::*; -use actix_web::{test, web, App, HttpRequest}; +use actix_web::{ + http::{header, StatusCode}, + test, web, App, HttpRequest, HttpResponse, +}; use actix_web_actors::*; use bytes::Bytes; use futures_util::{SinkExt, StreamExt}; @@ -56,3 +59,51 @@ async fn test_simple() { 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 = 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)) + } + }, + )) + }); + + // 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()))); +} diff --git a/src/test.rs b/src/test.rs index 7e8bf1069..18bf89cda 100644 --- a/src/test.rs +++ b/src/test.rs @@ -8,7 +8,7 @@ use std::{fmt, net, thread, time}; use actix_codec::{AsyncRead, AsyncWrite, Framed}; #[cfg(feature = "cookies")] use actix_http::cookie::Cookie; -use actix_http::http::header::{ContentType, IntoHeaderPair}; +use actix_http::http::header::{ContentType, HeaderMap, IntoHeaderPair}; use actix_http::http::{Method, StatusCode, Uri, Version}; use actix_http::test::TestRequest as HttpTestRequest; use actix_http::{ws, Extensions, HttpService, Request}; @@ -962,6 +962,14 @@ impl TestServer { self.ws_at("/").await } + /// 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 client_headers(&mut self) -> Option<&mut HeaderMap> { + self.client.headers() + } + /// Gracefully stop HTTP server pub async fn stop(self) { self.server.stop(true).await; From c8ed8dd1a4baddc3071d5d3f297da49617faa8b0 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Thu, 1 Apr 2021 15:26:13 +0100 Subject: [PATCH 132/339] migrate to -utils beta 4 (#2127) --- .cargo/config.toml | 2 +- Cargo.toml | 2 +- actix-files/Cargo.toml | 4 +- actix-files/src/files.rs | 15 +- actix-files/src/lib.rs | 2 +- actix-files/src/path_buf.rs | 2 +- actix-files/src/service.rs | 48 ++-- actix-http-test/Cargo.toml | 2 +- actix-http/CHANGES.md | 4 + actix-http/Cargo.toml | 4 +- actix-http/examples/hello-world.rs | 2 +- actix-http/src/body/mod.rs | 3 +- actix-http/src/client/h1proto.rs | 3 +- actix-http/src/client/h2proto.rs | 2 +- actix-http/src/error.rs | 24 -- actix-http/src/h1/dispatcher.rs | 3 +- actix-http/src/h1/expect.rs | 2 +- actix-http/src/h1/payload.rs | 2 +- actix-http/src/h1/service.rs | 2 +- actix-http/src/h2/service.rs | 2 +- actix-http/src/ws/dispatcher.rs | 393 +++++++++++++++++++++++++- actix-http/tests/test_client.rs | 12 +- actix-http/tests/test_openssl.rs | 6 +- actix-http/tests/test_rustls.rs | 16 +- actix-http/tests/test_server.rs | 35 +-- actix-http/tests/test_ws.rs | 7 +- actix-multipart/Cargo.toml | 6 +- actix-multipart/src/extractor.rs | 17 +- actix-multipart/src/server.rs | 10 +- actix-web-actors/tests/test_ws.rs | 2 +- actix-web-codegen/Cargo.toml | 3 +- actix-web-codegen/tests/test_macro.rs | 3 +- awc/Cargo.toml | 2 +- awc/src/ws.rs | 2 +- awc/tests/test_client.rs | 5 +- awc/tests/test_rustls_client.rs | 2 +- awc/tests/test_ssl_client.rs | 2 +- awc/tests/test_ws.rs | 4 +- benches/responder.rs | 8 +- docs/graphs/net-only.dot | 28 +- src/app.rs | 4 +- src/data.rs | 5 +- src/extract.rs | 6 +- src/handler.rs | 4 +- src/middleware/compress.rs | 2 +- src/middleware/condition.rs | 10 +- src/middleware/default_headers.rs | 8 +- src/middleware/err_handlers.rs | 3 +- src/middleware/logger.rs | 7 +- src/middleware/normalize.rs | 2 +- src/request.rs | 2 +- src/request_data.rs | 8 +- src/resource.rs | 2 +- src/scope.rs | 2 +- src/service.rs | 2 +- src/test.rs | 4 +- src/types/either.rs | 3 +- src/types/form.rs | 6 +- src/types/json.rs | 2 +- src/types/path.rs | 2 +- src/types/payload.rs | 39 ++- src/types/query.rs | 2 +- src/types/readlines.rs | 2 +- tests/test_server.rs | 2 +- 64 files changed, 612 insertions(+), 210 deletions(-) diff --git a/.cargo/config.toml b/.cargo/config.toml index 9d0d9da8c..40fe3e573 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -1,3 +1,3 @@ [alias] -chk = "hack check --workspace --tests --examples" +chk = "hack check --workspace --all-features --tests --examples" lint = "hack --clean-per-run clippy --workspace --tests --examples" diff --git a/Cargo.toml b/Cargo.toml index 7dd7635cd..15f9f5fe8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -83,7 +83,7 @@ actix-router = "0.2.7" actix-rt = "2.2" actix-server = "2.0.0-beta.3" actix-service = "2.0.0-beta.4" -actix-utils = "3.0.0-beta.2" +actix-utils = "3.0.0-beta.4" actix-tls = { version = "3.0.0-beta.5", default-features = false, optional = true } actix-web-codegen = "0.5.0-beta.2" diff --git a/actix-files/Cargo.toml b/actix-files/Cargo.toml index 472bd0362..0cc02c6bd 100644 --- a/actix-files/Cargo.toml +++ b/actix-files/Cargo.toml @@ -19,12 +19,12 @@ path = "src/lib.rs" [dependencies] actix-web = { version = "4.0.0-beta.4", default-features = false } actix-service = "2.0.0-beta.4" +actix-utils = "3.0.0-beta.4" askama_escape = "0.10" bitflags = "1" bytes = "1" -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"] } http-range = "0.1.4" derive_more = "0.99.5" log = "0.4" diff --git a/actix-files/src/files.rs b/actix-files/src/files.rs index 292e3fdf3..ff4241340 100644 --- a/actix-files/src/files.rs +++ b/actix-files/src/files.rs @@ -1,6 +1,7 @@ use std::{cell::RefCell, fmt, io, 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}, error::Error, @@ -8,7 +9,7 @@ use actix_web::{ http::header::DispositionType, HttpRequest, }; -use futures_util::future::{ok, FutureExt, LocalBoxFuture}; +use futures_core::future::LocalBoxFuture; use crate::{ directory_listing, named, Directory, DirectoryRenderer, FilesService, HttpNewService, @@ -263,18 +264,18 @@ impl ServiceFactory for Files { }; if let Some(ref default) = *self.default.borrow() { - default - .new_service(()) - .map(move |result| match result { + let fut = default.new_service(()); + Box::pin(async { + match fut.await { Ok(default) => { srv.default = Some(default); Ok(srv) } Err(_) => Err(()), - }) - .boxed_local() + } + }) } else { - ok(srv).boxed_local() + Box::pin(ok(srv)) } } } diff --git a/actix-files/src/lib.rs b/actix-files/src/lib.rs index 018079b21..f8583febe 100644 --- a/actix-files/src/lib.rs +++ b/actix-files/src/lib.rs @@ -65,6 +65,7 @@ mod tests { }; use actix_service::ServiceFactory; + use actix_utils::future::ok; use actix_web::{ guard, http::{ @@ -76,7 +77,6 @@ mod tests { web::{self, Bytes}, App, HttpResponse, Responder, }; - use futures_util::future::ok; use super::*; diff --git a/actix-files/src/path_buf.rs b/actix-files/src/path_buf.rs index dd8e5b503..8a87acd5d 100644 --- a/actix-files/src/path_buf.rs +++ b/actix-files/src/path_buf.rs @@ -3,8 +3,8 @@ use std::{ str::FromStr, }; +use actix_utils::future::{ready, Ready}; use actix_web::{dev::Payload, FromRequest, HttpRequest}; -use futures_util::future::{ready, Ready}; use crate::error::UriSegmentError; diff --git a/actix-files/src/service.rs b/actix-files/src/service.rs index 3214963ed..d2db8503f 100644 --- a/actix-files/src/service.rs +++ b/actix-files/src/service.rs @@ -1,6 +1,7 @@ use std::{fmt, io, path::PathBuf, rc::Rc}; use actix_service::Service; +use actix_utils::future::ok; use actix_web::{ dev::{ServiceRequest, ServiceResponse}, error::Error, @@ -8,7 +9,7 @@ use actix_web::{ http::{header, Method}, HttpResponse, }; -use futures_util::future::{ok, Either, LocalBoxFuture, Ready}; +use futures_core::future::LocalBoxFuture; use crate::{ named, Directory, DirectoryRenderer, FilesError, HttpService, MimeOverride, NamedFile, @@ -29,19 +30,18 @@ pub struct FilesService { pub(crate) hidden_files: bool, } -type FilesServiceFuture = Either< - Ready>, - LocalBoxFuture<'static, Result>, ->; - impl FilesService { - fn handle_err(&self, e: io::Error, req: ServiceRequest) -> FilesServiceFuture { - log::debug!("Failed to handle {}: {}", req.path(), e); + fn handle_err( + &self, + err: io::Error, + req: ServiceRequest, + ) -> LocalBoxFuture<'static, Result> { + log::debug!("error handling {}: {}", req.path(), err); if let Some(ref default) = self.default { - Either::Right(default.call(req)) + Box::pin(default.call(req)) } else { - Either::Left(ok(req.error_response(e))) + Box::pin(ok(req.error_response(err))) } } } @@ -55,7 +55,7 @@ impl fmt::Debug for FilesService { impl Service for FilesService { type Response = ServiceResponse; type Error = Error; - type Future = FilesServiceFuture; + type Future = LocalBoxFuture<'static, Result>; actix_service::always_ready!(); @@ -69,7 +69,7 @@ impl Service for FilesService { }; if !is_method_valid { - return Either::Left(ok(req.into_response( + 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."), @@ -79,13 +79,13 @@ impl Service for FilesService { let real_path = match PathBufWrap::parse_path(req.match_info().path(), self.hidden_files) { Ok(item) => item, - Err(e) => return Either::Left(ok(req.error_response(e))), + Err(e) => return Box::pin(ok(req.error_response(e))), }; // full file path let path = match self.directory.join(&real_path).canonicalize() { Ok(path) => path, - Err(e) => return self.handle_err(e, req), + Err(err) => return Box::pin(self.handle_err(err, req)), }; if path.is_dir() { @@ -93,7 +93,7 @@ impl Service for FilesService { if self.redirect_to_slash && !req.path().ends_with('/') { let redirect_to = format!("{}/", req.path()); - return Either::Left(ok(req.into_response( + return Box::pin(ok(req.into_response( HttpResponse::Found() .insert_header((header::LOCATION, redirect_to)) .body("") @@ -114,9 +114,9 @@ impl Service for FilesService { let (req, _) = req.into_parts(); let res = named_file.into_response(&req); - Either::Left(ok(ServiceResponse::new(req, res))) + Box::pin(ok(ServiceResponse::new(req, res))) } - Err(e) => self.handle_err(e, req), + Err(err) => self.handle_err(err, req), } } else if self.show_index { let dir = Directory::new(self.directory.clone(), path); @@ -124,12 +124,12 @@ impl Service for FilesService { let (req, _) = req.into_parts(); let x = (self.renderer)(&dir, &req); - match x { - Ok(resp) => Either::Left(ok(resp)), - Err(e) => Either::Left(ok(ServiceResponse::from_err(e, req))), - } + Box::pin(match x { + Ok(resp) => ok(resp), + Err(err) => ok(ServiceResponse::from_err(err, req)), + }) } else { - Either::Left(ok(ServiceResponse::from_err( + Box::pin(ok(ServiceResponse::from_err( FilesError::IsDirectory, req.into_parts().0, ))) @@ -145,9 +145,9 @@ impl Service for FilesService { let (req, _) = req.into_parts(); let res = named_file.into_response(&req); - Either::Left(ok(ServiceResponse::new(req, res))) + Box::pin(ok(ServiceResponse::new(req, res))) } - Err(e) => self.handle_err(e, req), + Err(err) => self.handle_err(err, req), } } } diff --git a/actix-http-test/Cargo.toml b/actix-http-test/Cargo.toml index 0e7d57fc3..31a83eddb 100644 --- a/actix-http-test/Cargo.toml +++ b/actix-http-test/Cargo.toml @@ -32,7 +32,7 @@ openssl = ["tls-openssl", "awc/openssl"] actix-service = "2.0.0-beta.4" actix-codec = "0.4.0-beta.1" actix-tls = "3.0.0-beta.5" -actix-utils = "3.0.0-beta.2" +actix-utils = "3.0.0-beta.4" actix-rt = "2.2" actix-server = "2.0.0-beta.3" awc = { version = "3.0.0-beta.3", default-features = false } diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 2c71031ab..3a7b4f024 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -9,8 +9,12 @@ ### Changed * `client::Connector` type now only have one generic type for `actix_service::Service`. [#2063] +### Removed +* `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 +[#2127]: https://github.com/actix/actix-web/pull/2127 ## 3.0.0-beta.4 - 2021-03-08 diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 679e8c992..d9af75aa5 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -46,7 +46,7 @@ trust-dns = ["trust-dns-resolver"] [dependencies] actix-service = "2.0.0-beta.4" actix-codec = "0.4.0-beta.1" -actix-utils = "3.0.0-beta.2" +actix-utils = "3.0.0-beta.4" actix-rt = "2.2" actix-tls = "3.0.0-beta.5" @@ -65,11 +65,13 @@ http = "0.2.2" httparse = "1.3" itoa = "0.4" language-tags = "0.2" +local-channel = "0.1" once_cell = "1.5" log = "0.4" mime = "0.3" percent-encoding = "2.1" pin-project = "1.0.0" +pin-project-lite = "0.2" rand = "0.8" regex = "1.3" serde = "1.0" diff --git a/actix-http/examples/hello-world.rs b/actix-http/examples/hello-world.rs index a84e9aac6..a99ddae46 100644 --- a/actix-http/examples/hello-world.rs +++ b/actix-http/examples/hello-world.rs @@ -2,7 +2,7 @@ use std::{env, io}; use actix_http::{HttpService, Response}; use actix_server::Server; -use futures_util::future; +use actix_utils::future; use http::header::HeaderValue; use log::info; diff --git a/actix-http/src/body/mod.rs b/actix-http/src/body/mod.rs index a4d6ba2b6..fa43e1b03 100644 --- a/actix-http/src/body/mod.rs +++ b/actix-http/src/body/mod.rs @@ -20,8 +20,9 @@ mod tests { use std::pin::Pin; use actix_rt::pin; + use actix_utils::future::poll_fn; use bytes::{Bytes, BytesMut}; - use futures_util::{future::poll_fn, stream}; + use futures_util::stream; use super::*; diff --git a/actix-http/src/client/h1proto.rs b/actix-http/src/client/h1proto.rs index 8fb08b0ce..fa4469d35 100644 --- a/actix-http/src/client/h1proto.rs +++ b/actix-http/src/client/h1proto.rs @@ -5,10 +5,11 @@ use std::{ }; use actix_codec::Framed; +use actix_utils::future::poll_fn; use bytes::buf::BufMut; use bytes::{Bytes, BytesMut}; use futures_core::{ready, Stream}; -use futures_util::{future::poll_fn, SinkExt as _}; +use futures_util::SinkExt as _; use crate::error::PayloadError; use crate::h1; diff --git a/actix-http/src/client/h2proto.rs b/actix-http/src/client/h2proto.rs index 437b9ae76..8cb2e2522 100644 --- a/actix-http/src/client/h2proto.rs +++ b/actix-http/src/client/h2proto.rs @@ -1,7 +1,7 @@ use std::future::Future; +use actix_utils::future::poll_fn; use bytes::Bytes; -use futures_util::future::poll_fn; use h2::{ client::{Builder, Connection, SendRequest}, SendStream, diff --git a/actix-http/src/error.rs b/actix-http/src/error.rs index 1354e998e..0178be80c 100644 --- a/actix-http/src/error.rs +++ b/actix-http/src/error.rs @@ -6,9 +6,6 @@ use std::str::Utf8Error; use std::string::FromUtf8Error; use std::{fmt, io, result}; -use actix_codec::{Decoder, Encoder}; -use actix_utils::dispatcher::DispatcherError as FramedDispatcherError; -use actix_utils::timeout::TimeoutError; use bytes::BytesMut; use derive_more::{Display, From}; use http::uri::InvalidUri; @@ -148,19 +145,6 @@ impl From for Error { } } -/// Inspects the underlying enum and returns an appropriate status code. -/// -/// If the variant is [`TimeoutError::Service`], the error code of the service is returned. -/// Otherwise, [`StatusCode::GATEWAY_TIMEOUT`] is returned. -impl ResponseError for TimeoutError { - fn status_code(&self) -> StatusCode { - match self { - TimeoutError::Service(e) => e.status_code(), - TimeoutError::Timeout => StatusCode::GATEWAY_TIMEOUT, - } - } -} - #[derive(Debug, Display)] #[display(fmt = "UnknownError")] struct UnitError; @@ -469,14 +453,6 @@ impl ResponseError for ContentTypeError { } } -impl + Decoder, I> ResponseError for FramedDispatcherError -where - E: fmt::Debug + fmt::Display, - >::Error: fmt::Debug, - ::Error: fmt::Debug, -{ -} - /// Helper type that can wrap any error and generate custom response. /// /// In following example any `io::Error` will be converted into "BAD REQUEST" diff --git a/actix-http/src/h1/dispatcher.rs b/actix-http/src/h1/dispatcher.rs index e5989e5ee..bf0365693 100644 --- a/actix-http/src/h1/dispatcher.rs +++ b/actix-http/src/h1/dispatcher.rs @@ -951,7 +951,8 @@ mod tests { use std::str; use actix_service::fn_service; - use futures_util::future::{lazy, ready, Ready}; + use actix_utils::future::{ready, Ready}; + use futures_util::future::lazy; use super::*; use crate::{ diff --git a/actix-http/src/h1/expect.rs b/actix-http/src/h1/expect.rs index 5015069bb..bb8e28e95 100644 --- a/actix-http/src/h1/expect.rs +++ b/actix-http/src/h1/expect.rs @@ -1,5 +1,5 @@ use actix_service::{Service, ServiceFactory}; -use futures_util::future::{ready, Ready}; +use actix_utils::future::{ready, Ready}; use crate::error::Error; use crate::request::Request; diff --git a/actix-http/src/h1/payload.rs b/actix-http/src/h1/payload.rs index 32275ac6b..e72493fa2 100644 --- a/actix-http/src/h1/payload.rs +++ b/actix-http/src/h1/payload.rs @@ -263,7 +263,7 @@ impl Inner { #[cfg(test)] mod tests { use super::*; - use futures_util::future::poll_fn; + use actix_utils::future::poll_fn; #[actix_rt::test] async fn test_unread_data() { diff --git a/actix-http/src/h1/service.rs b/actix-http/src/h1/service.rs index f915bfa47..a98f6bd0a 100644 --- a/actix-http/src/h1/service.rs +++ b/actix-http/src/h1/service.rs @@ -6,8 +6,8 @@ use std::{fmt, net}; use actix_codec::{AsyncRead, AsyncWrite, Framed}; use actix_rt::net::TcpStream; use actix_service::{pipeline_factory, IntoServiceFactory, Service, ServiceFactory}; +use actix_utils::future::ready; use futures_core::future::LocalBoxFuture; -use futures_util::future::ready; use crate::body::MessageBody; use crate::config::ServiceConfig; diff --git a/actix-http/src/h2/service.rs b/actix-http/src/h2/service.rs index db0b580b3..8f202e752 100644 --- a/actix-http/src/h2/service.rs +++ b/actix-http/src/h2/service.rs @@ -10,9 +10,9 @@ use actix_service::{ fn_factory, fn_service, pipeline_factory, IntoServiceFactory, Service, ServiceFactory, }; +use actix_utils::future::ready; use bytes::Bytes; use futures_core::{future::LocalBoxFuture, ready}; -use futures_util::future::ready; use h2::server::{handshake, Handshake}; use log::error; diff --git a/actix-http/src/ws/dispatcher.rs b/actix-http/src/ws/dispatcher.rs index 7be7cf637..576851139 100644 --- a/actix-http/src/ws/dispatcher.rs +++ b/actix-http/src/ws/dispatcher.rs @@ -4,7 +4,6 @@ use std::task::{Context, Poll}; use actix_codec::{AsyncRead, AsyncWrite, Framed}; use actix_service::{IntoService, Service}; -use actix_utils::dispatcher::{Dispatcher as InnerDispatcher, DispatcherError}; use super::{Codec, Frame, Message}; @@ -15,7 +14,7 @@ where T: AsyncRead + AsyncWrite, { #[pin] - inner: InnerDispatcher, + inner: inner::Dispatcher, } impl Dispatcher @@ -27,13 +26,13 @@ where { pub fn new>(io: T, service: F) -> Self { Dispatcher { - inner: InnerDispatcher::new(Framed::new(io, Codec::new()), service), + inner: inner::Dispatcher::new(Framed::new(io, Codec::new()), service), } } pub fn with>(framed: Framed, service: F) -> Self { Dispatcher { - inner: InnerDispatcher::new(framed, service), + inner: inner::Dispatcher::new(framed, service), } } } @@ -45,9 +44,393 @@ where S::Future: 'static, S::Error: 'static, { - type Output = Result<(), DispatcherError>; + type Output = Result<(), inner::DispatcherError>; fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { self.project().inner.poll(cx) } } + +/// Framed dispatcher service and related utilities. +mod inner { + // allow dead code since this mod was ripped from actix-utils + #![allow(dead_code)] + + use core::{ + fmt, + future::Future, + mem, + pin::Pin, + task::{Context, Poll}, + }; + + use actix_service::{IntoService, Service}; + use futures_core::stream::Stream; + use local_channel::mpsc; + use log::debug; + use pin_project_lite::pin_project; + + use actix_codec::{AsyncRead, AsyncWrite, Decoder, Encoder, Framed}; + + use crate::ResponseError; + + /// Framed transport errors + pub enum DispatcherError + where + U: Encoder + Decoder, + { + /// Inner service error. + Service(E), + + /// Frame encoding error. + Encoder(>::Error), + + /// Frame decoding error. + Decoder(::Error), + } + + impl From for DispatcherError + where + U: Encoder + Decoder, + { + fn from(err: E) -> Self { + DispatcherError::Service(err) + } + } + + impl fmt::Debug for DispatcherError + where + E: fmt::Debug, + U: Encoder + Decoder, + >::Error: fmt::Debug, + ::Error: fmt::Debug, + { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + match *self { + DispatcherError::Service(ref e) => { + write!(fmt, "DispatcherError::Service({:?})", e) + } + DispatcherError::Encoder(ref e) => { + write!(fmt, "DispatcherError::Encoder({:?})", e) + } + DispatcherError::Decoder(ref e) => { + write!(fmt, "DispatcherError::Decoder({:?})", e) + } + } + } + } + + impl fmt::Display for DispatcherError + where + E: fmt::Display, + U: Encoder + Decoder, + >::Error: fmt::Debug, + ::Error: fmt::Debug, + { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + match *self { + DispatcherError::Service(ref e) => write!(fmt, "{}", e), + DispatcherError::Encoder(ref e) => write!(fmt, "{:?}", e), + DispatcherError::Decoder(ref e) => write!(fmt, "{:?}", e), + } + } + } + + impl ResponseError for DispatcherError + where + E: fmt::Debug + fmt::Display, + U: Encoder + Decoder, + >::Error: fmt::Debug, + ::Error: fmt::Debug, + { + } + + /// Message type wrapper for signalling end of message stream. + pub enum Message { + /// Message item. + Item(T), + + /// Signal from service to flush all messages and stop processing. + Close, + } + + pin_project! { + /// A future that reads frames from a [`Framed`] object and passes them to a [`Service`]. + pub struct Dispatcher + where + S: Service<::Item, Response = I>, + S::Error: 'static, + S::Future: 'static, + T: AsyncRead, + T: AsyncWrite, + U: Encoder, + U: Decoder, + I: 'static, + >::Error: fmt::Debug, + { + service: S, + state: State, + #[pin] + framed: Framed, + rx: mpsc::Receiver, S::Error>>, + tx: mpsc::Sender, S::Error>>, + } + } + + enum State + where + S: Service<::Item>, + U: Encoder + Decoder, + { + Processing, + Error(DispatcherError), + FramedError(DispatcherError), + FlushAndStop, + Stopping, + } + + impl State + where + S: Service<::Item>, + U: Encoder + Decoder, + { + fn take_error(&mut self) -> DispatcherError { + match mem::replace(self, State::Processing) { + State::Error(err) => err, + _ => panic!(), + } + } + + fn take_framed_error(&mut self) -> DispatcherError { + match mem::replace(self, State::Processing) { + State::FramedError(err) => err, + _ => panic!(), + } + } + } + + impl Dispatcher + where + S: Service<::Item, Response = I>, + S::Error: 'static, + S::Future: 'static, + T: AsyncRead + AsyncWrite, + U: Decoder + Encoder, + I: 'static, + ::Error: fmt::Debug, + >::Error: fmt::Debug, + { + /// Create new `Dispatcher`. + pub fn new(framed: Framed, service: F) -> Self + where + F: IntoService::Item>, + { + let (tx, rx) = mpsc::channel(); + Dispatcher { + framed, + rx, + tx, + service: service.into_service(), + state: State::Processing, + } + } + + /// Construct new `Dispatcher` instance with customer `mpsc::Receiver` + pub fn with_rx( + framed: Framed, + service: F, + rx: mpsc::Receiver, S::Error>>, + ) -> Self + where + F: IntoService::Item>, + { + let tx = rx.sender(); + Dispatcher { + framed, + rx, + tx, + service: service.into_service(), + state: State::Processing, + } + } + + /// Get sender handle. + pub fn tx(&self) -> mpsc::Sender, S::Error>> { + self.tx.clone() + } + + /// Get reference to a service wrapped by `Dispatcher` instance. + pub fn service(&self) -> &S { + &self.service + } + + /// Get mutable reference to a service wrapped by `Dispatcher` instance. + pub fn service_mut(&mut self) -> &mut S { + &mut self.service + } + + /// Get reference to a framed instance wrapped by `Dispatcher` instance. + pub fn framed(&self) -> &Framed { + &self.framed + } + + /// Get mutable reference to a framed instance wrapped by `Dispatcher` instance. + pub fn framed_mut(&mut self) -> &mut Framed { + &mut self.framed + } + + /// Read from framed object. + fn poll_read(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> bool + where + S: Service<::Item, Response = I>, + S::Error: 'static, + S::Future: 'static, + T: AsyncRead + AsyncWrite, + U: Decoder + Encoder, + I: 'static, + >::Error: fmt::Debug, + { + loop { + let this = self.as_mut().project(); + match this.service.poll_ready(cx) { + Poll::Ready(Ok(_)) => { + 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)); + return true; + } + Poll::Pending => return false, + Poll::Ready(None) => { + *this.state = State::Stopping; + return true; + } + }; + + let tx = this.tx.clone(); + let fut = this.service.call(item); + actix_rt::spawn(async move { + let item = fut.await; + let _ = tx.send(item.map(Message::Item)); + }); + } + Poll::Pending => return false, + Poll::Ready(Err(err)) => { + *this.state = State::Error(DispatcherError::Service(err)); + return true; + } + } + } + } + + /// Write to framed object. + fn poll_write(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> bool + where + S: Service<::Item, Response = I>, + S::Error: 'static, + S::Future: 'static, + T: AsyncRead + AsyncWrite, + U: Decoder + Encoder, + I: 'static, + >::Error: fmt::Debug, + { + loop { + let mut this = self.as_mut().project(); + while !this.framed.is_write_buf_full() { + 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)); + return true; + } + } + Poll::Ready(Some(Ok(Message::Close))) => { + *this.state = State::FlushAndStop; + return true; + } + Poll::Ready(Some(Err(err))) => { + *this.state = State::Error(DispatcherError::Service(err)); + return true; + } + Poll::Ready(None) | Poll::Pending => break, + } + } + + if !this.framed.is_write_buf_empty() { + match this.framed.flush(cx) { + Poll::Pending => break, + Poll::Ready(Ok(_)) => {} + Poll::Ready(Err(err)) => { + debug!("Error sending data: {:?}", err); + *this.state = + State::FramedError(DispatcherError::Encoder(err)); + return true; + } + } + } else { + break; + } + } + + false + } + } + + impl Future for Dispatcher + where + S: Service<::Item, Response = I>, + S::Error: 'static, + S::Future: 'static, + T: AsyncRead + AsyncWrite, + U: Decoder + Encoder, + I: 'static, + >::Error: fmt::Debug, + ::Error: fmt::Debug, + { + type Output = Result<(), DispatcherError>; + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + loop { + let this = self.as_mut().project(); + + return match this.state { + State::Processing => { + if self.as_mut().poll_read(cx) || self.as_mut().poll_write(cx) { + continue; + } else { + Poll::Pending + } + } + State::Error(_) => { + // flush write buffer + if !this.framed.is_write_buf_empty() + && this.framed.flush(cx).is_pending() + { + return Poll::Pending; + } + Poll::Ready(Err(this.state.take_error())) + } + State::FlushAndStop => { + if !this.framed.is_write_buf_empty() { + this.framed.flush(cx).map(|res| { + if let Err(err) = res { + debug!("Error sending data: {:?}", err); + } + + Ok(()) + }) + } else { + Poll::Ready(Ok(())) + } + } + State::FramedError(_) => { + Poll::Ready(Err(this.state.take_framed_error())) + } + State::Stopping => Poll::Ready(Ok(())), + }; + } + } + } +} diff --git a/actix-http/tests/test_client.rs b/actix-http/tests/test_client.rs index 758e39745..b5f8d54b9 100644 --- a/actix-http/tests/test_client.rs +++ b/actix-http/tests/test_client.rs @@ -3,11 +3,9 @@ use actix_http::{ }; use actix_http_test::test_server; use actix_service::ServiceFactoryExt; +use actix_utils::future; use bytes::Bytes; -use futures_util::{ - future::{self, ok}, - StreamExt as _, -}; +use futures_util::StreamExt as _; const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ Hello World Hello World Hello World Hello World Hello World \ @@ -63,7 +61,7 @@ async fn test_h1_v2() { async fn test_connection_close() { let srv = test_server(move || { HttpService::build() - .finish(|_| ok::<_, ()>(Response::Ok().body(STR))) + .finish(|_| future::ok::<_, ()>(Response::Ok().body(STR))) .tcp() .map(|_| ()) }) @@ -79,9 +77,9 @@ async fn test_with_query_parameter() { HttpService::build() .finish(|req: Request| { if req.uri().query().unwrap().contains("qp=") { - ok::<_, ()>(Response::Ok().finish()) + future::ok::<_, ()>(Response::Ok().finish()) } else { - ok::<_, ()>(Response::BadRequest().finish()) + future::ok::<_, ()>(Response::BadRequest().finish()) } }) .tcp() diff --git a/actix-http/tests/test_openssl.rs b/actix-http/tests/test_openssl.rs index 49a68a60d..c9cfa7d18 100644 --- a/actix-http/tests/test_openssl.rs +++ b/actix-http/tests/test_openssl.rs @@ -11,12 +11,10 @@ use actix_http::HttpMessage; use actix_http::{body, Error, HttpService, Request, Response}; use actix_http_test::test_server; use actix_service::{fn_service, ServiceFactoryExt}; +use actix_utils::future::{err, ok, ready}; use bytes::{Bytes, BytesMut}; use futures_core::Stream; -use futures_util::{ - future::{err, ok, ready}, - 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_rustls.rs b/actix-http/tests/test_rustls.rs index 7a3cb1473..5b8ba6582 100644 --- a/actix-http/tests/test_rustls.rs +++ b/actix-http/tests/test_rustls.rs @@ -8,10 +8,10 @@ use actix_http::http::{Method, StatusCode, Version}; use actix_http::{body, error, Error, HttpService, Request, Response}; use actix_http_test::test_server; use actix_service::{fn_factory_with_config, fn_service}; +use actix_utils::future::{err, ok}; use bytes::{Bytes, BytesMut}; use futures_core::Stream; -use futures_util::future::{self, err, ok}; use futures_util::stream::{once, StreamExt as _}; use rustls::{ internal::pemfile::{certs, pkcs8_private_keys}, @@ -51,7 +51,7 @@ fn tls_config() -> RustlsServerConfig { async fn test_h1() -> io::Result<()> { let srv = test_server(move || { HttpService::build() - .h1(|_| future::ok::<_, Error>(Response::Ok().finish())) + .h1(|_| ok::<_, Error>(Response::Ok().finish())) .rustls(tls_config()) }) .await; @@ -65,7 +65,7 @@ async fn test_h1() -> io::Result<()> { async fn test_h2() -> io::Result<()> { let srv = test_server(move || { HttpService::build() - .h2(|_| future::ok::<_, Error>(Response::Ok().finish())) + .h2(|_| ok::<_, Error>(Response::Ok().finish())) .rustls(tls_config()) }) .await; @@ -82,7 +82,7 @@ async fn test_h1_1() -> io::Result<()> { .h1(|req: Request| { assert!(req.peer_addr().is_some()); assert_eq!(req.version(), Version::HTTP_11); - future::ok::<_, Error>(Response::Ok().finish()) + ok::<_, Error>(Response::Ok().finish()) }) .rustls(tls_config()) }) @@ -100,7 +100,7 @@ async fn test_h2_1() -> io::Result<()> { .finish(|req: Request| { assert!(req.peer_addr().is_some()); assert_eq!(req.version(), Version::HTTP_2); - future::ok::<_, Error>(Response::Ok().finish()) + ok::<_, Error>(Response::Ok().finish()) }) .rustls(tls_config()) }) @@ -144,7 +144,7 @@ async fn test_h2_content_length() { StatusCode::OK, StatusCode::NOT_FOUND, ]; - future::ok::<_, ()>(Response::new(statuses[indx])) + ok::<_, ()>(Response::new(statuses[indx])) }) .rustls(tls_config()) }) @@ -213,7 +213,7 @@ async fn test_h2_headers() { TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST ", )); } - future::ok::<_, ()>(config.body(data.clone())) + ok::<_, ()>(config.body(data.clone())) }) .rustls(tls_config()) }).await; @@ -252,7 +252,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(|_| future::ok::<_, ()>(Response::Ok().body(STR))) + .h2(|_| ok::<_, ()>(Response::Ok().body(STR))) .rustls(tls_config()) }) .await; diff --git a/actix-http/tests/test_server.rs b/actix-http/tests/test_server.rs index 6d145400c..9084a597f 100644 --- a/actix-http/tests/test_server.rs +++ b/actix-http/tests/test_server.rs @@ -5,9 +5,10 @@ use std::{net, thread}; 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 futures_util::future::{self, err, ok, ready, FutureExt}; use futures_util::stream::{once, StreamExt as _}; +use futures_util::FutureExt as _; use regex::Regex; use actix_http::HttpMessage; @@ -24,7 +25,7 @@ async fn test_h1() { .client_disconnect(1000) .h1(|req: Request| { assert!(req.peer_addr().is_some()); - future::ok::<_, ()>(Response::Ok().finish()) + ok::<_, ()>(Response::Ok().finish()) }) .tcp() }) @@ -44,7 +45,7 @@ async fn test_h1_2() { .finish(|req: Request| { assert!(req.peer_addr().is_some()); assert_eq!(req.version(), http::Version::HTTP_11); - future::ok::<_, ()>(Response::Ok().finish()) + ok::<_, ()>(Response::Ok().finish()) }) .tcp() }) @@ -65,7 +66,7 @@ async fn test_expect_continue() { err(error::ErrorPreconditionFailed("error")) } })) - .finish(|_| future::ok::<_, ()>(Response::Ok().finish())) + .finish(|_| ok::<_, ()>(Response::Ok().finish())) .tcp() }) .await; @@ -96,7 +97,7 @@ async fn test_expect_continue_h1() { } }) })) - .h1(fn_service(|_| future::ok::<_, ()>(Response::Ok().finish()))) + .h1(fn_service(|_| ok::<_, ()>(Response::Ok().finish()))) .tcp() }) .await; @@ -175,7 +176,7 @@ async fn test_slow_request() { let srv = test_server(|| { HttpService::build() .client_timeout(100) - .finish(|_| future::ok::<_, ()>(Response::Ok().finish())) + .finish(|_| ok::<_, ()>(Response::Ok().finish())) .tcp() }) .await; @@ -191,7 +192,7 @@ async fn test_slow_request() { async fn test_http1_malformed_request() { let srv = test_server(|| { HttpService::build() - .h1(|_| future::ok::<_, ()>(Response::Ok().finish())) + .h1(|_| ok::<_, ()>(Response::Ok().finish())) .tcp() }) .await; @@ -207,7 +208,7 @@ async fn test_http1_malformed_request() { async fn test_http1_keepalive() { let srv = test_server(|| { HttpService::build() - .h1(|_| future::ok::<_, ()>(Response::Ok().finish())) + .h1(|_| ok::<_, ()>(Response::Ok().finish())) .tcp() }) .await; @@ -229,7 +230,7 @@ async fn test_http1_keepalive_timeout() { let srv = test_server(|| { HttpService::build() .keep_alive(1) - .h1(|_| future::ok::<_, ()>(Response::Ok().finish())) + .h1(|_| ok::<_, ()>(Response::Ok().finish())) .tcp() }) .await; @@ -250,7 +251,7 @@ async fn test_http1_keepalive_timeout() { async fn test_http1_keepalive_close() { let srv = test_server(|| { HttpService::build() - .h1(|_| future::ok::<_, ()>(Response::Ok().finish())) + .h1(|_| ok::<_, ()>(Response::Ok().finish())) .tcp() }) .await; @@ -271,7 +272,7 @@ async fn test_http1_keepalive_close() { async fn test_http10_keepalive_default_close() { let srv = test_server(|| { HttpService::build() - .h1(|_| future::ok::<_, ()>(Response::Ok().finish())) + .h1(|_| ok::<_, ()>(Response::Ok().finish())) .tcp() }) .await; @@ -291,7 +292,7 @@ async fn test_http10_keepalive_default_close() { async fn test_http10_keepalive() { let srv = test_server(|| { HttpService::build() - .h1(|_| future::ok::<_, ()>(Response::Ok().finish())) + .h1(|_| ok::<_, ()>(Response::Ok().finish())) .tcp() }) .await; @@ -319,7 +320,7 @@ async fn test_http1_keepalive_disabled() { let srv = test_server(|| { HttpService::build() .keep_alive(KeepAlive::Disabled) - .h1(|_| future::ok::<_, ()>(Response::Ok().finish())) + .h1(|_| ok::<_, ()>(Response::Ok().finish())) .tcp() }) .await; @@ -354,7 +355,7 @@ async fn test_content_length() { StatusCode::OK, StatusCode::NOT_FOUND, ]; - future::ok::<_, ()>(Response::new(statuses[indx])) + ok::<_, ()>(Response::new(statuses[indx])) }) .tcp() }) @@ -409,7 +410,7 @@ async fn test_h1_headers() { TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST ", )); } - future::ok::<_, ()>(builder.body(data.clone())) + ok::<_, ()>(builder.body(data.clone())) }).tcp() }).await; @@ -645,7 +646,7 @@ async fn test_h1_response_http_error_handling() { async fn test_h1_service_error() { let mut srv = test_server(|| { HttpService::build() - .h1(|_| future::err::(error::ErrorBadRequest("error"))) + .h1(|_| err::(error::ErrorBadRequest("error"))) .tcp() }) .await; @@ -667,7 +668,7 @@ async fn test_h1_on_connect() { }) .h1(|req: Request| { assert!(req.extensions().contains::()); - future::ok::<_, ()>(Response::Ok().finish()) + ok::<_, ()>(Response::Ok().finish()) }) .tcp() }) diff --git a/actix-http/tests/test_ws.rs b/actix-http/tests/test_ws.rs index 51238215a..9a2e57711 100644 --- a/actix-http/tests/test_ws.rs +++ b/actix-http/tests/test_ws.rs @@ -9,11 +9,12 @@ use actix_codec::{AsyncRead, AsyncWrite, Framed}; use actix_http::{body, h1, ws, Error, HttpService, Request, Response}; use actix_http_test::test_server; use actix_service::{fn_factory, Service}; -use actix_utils::dispatcher::Dispatcher; +use actix_utils::future; use bytes::Bytes; -use futures_util::future; use futures_util::{SinkExt as _, StreamExt as _}; +use crate::ws::Dispatcher; + struct WsService(Arc, Cell)>>); impl WsService { @@ -58,7 +59,7 @@ where .await .unwrap(); - Dispatcher::new(framed.replace_codec(ws::Codec::new()), service) + Dispatcher::with(framed.replace_codec(ws::Codec::new()), service) .await .map_err(|_| panic!()) }; diff --git a/actix-multipart/Cargo.toml b/actix-multipart/Cargo.toml index 607e90849..cb00a0e74 100644 --- a/actix-multipart/Cargo.toml +++ b/actix-multipart/Cargo.toml @@ -17,12 +17,14 @@ path = "src/lib.rs" [dependencies] actix-web = { version = "4.0.0-beta.4", default-features = false } -actix-utils = "3.0.0-beta.2" +actix-utils = "3.0.0-beta.4" bytes = "1" derive_more = "0.99.5" -httparse = "1.3" +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" mime = "0.3" twoway = "0.2" diff --git a/actix-multipart/src/extractor.rs b/actix-multipart/src/extractor.rs index bffbe8a1b..c87f8cc2d 100644 --- a/actix-multipart/src/extractor.rs +++ b/actix-multipart/src/extractor.rs @@ -1,21 +1,22 @@ //! Multipart payload support + +use actix_utils::future::{ready, Ready}; use actix_web::{dev::Payload, Error, FromRequest, HttpRequest}; -use futures_util::future::{ok, Ready}; use crate::server::Multipart; -/// Get request's payload as multipart stream +/// Get request's payload as multipart stream. /// /// Content-type: multipart/form-data; /// /// ## Server example /// /// ``` -/// use futures_util::stream::{Stream, StreamExt}; /// use actix_web::{web, HttpResponse, Error}; -/// use actix_multipart as mp; +/// use actix_multipart::Multipart; +/// use futures_util::stream::StreamExt as _; /// -/// async fn index(mut payload: mp::Multipart) -> Result { +/// async fn index(mut payload: Multipart) -> Result { /// // iterate over multipart stream /// while let Some(item) = payload.next().await { /// let mut field = item?; @@ -25,9 +26,9 @@ use crate::server::Multipart; /// println!("-- CHUNK: \n{:?}", std::str::from_utf8(&chunk?)); /// } /// } +/// /// Ok(HttpResponse::Ok().into()) /// } -/// # fn main() {} /// ``` impl FromRequest for Multipart { type Error = Error; @@ -36,9 +37,9 @@ impl FromRequest for Multipart { #[inline] fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future { - ok(match Multipart::boundary(req.headers()) { + ready(Ok(match Multipart::boundary(req.headers()) { Ok(boundary) => Multipart::from_boundary(boundary, payload.take()), Err(err) => Multipart::from_error(err), - }) + })) } } diff --git a/actix-multipart/src/server.rs b/actix-multipart/src/server.rs index d9ff3d574..b7d251537 100644 --- a/actix-multipart/src/server.rs +++ b/actix-multipart/src/server.rs @@ -1,4 +1,4 @@ -//! Multipart payload support +//! Multipart response payload support. use std::cell::{Cell, RefCell, RefMut}; use std::convert::TryFrom; @@ -8,12 +8,12 @@ use std::rc::Rc; use std::task::{Context, Poll}; use std::{cmp, fmt}; -use bytes::{Bytes, BytesMut}; -use futures_util::stream::{LocalBoxStream, Stream, StreamExt}; - -use actix_utils::task::LocalWaker; use actix_web::error::{ParseError, PayloadError}; use actix_web::http::header::{self, ContentDisposition, HeaderMap, HeaderName, HeaderValue}; +use bytes::{Bytes, BytesMut}; +use futures_core::stream::{LocalBoxStream, Stream}; +use futures_util::stream::StreamExt as _; +use local_waker::LocalWaker; use crate::error::MultipartError; diff --git a/actix-web-actors/tests/test_ws.rs b/actix-web-actors/tests/test_ws.rs index 7999beed9..4ffedb2ef 100644 --- a/actix-web-actors/tests/test_ws.rs +++ b/actix-web-actors/tests/test_ws.rs @@ -5,7 +5,7 @@ use actix_web::{ }; use actix_web_actors::*; 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 fdfb9f6ba..a513b820b 100644 --- a/actix-web-codegen/Cargo.toml +++ b/actix-web-codegen/Cargo.toml @@ -21,6 +21,7 @@ proc-macro2 = "1" [dev-dependencies] actix-rt = "2.2" actix-web = "4.0.0-beta.4" -futures-util = { version = "0.3.7", default-features = false } +actix-utils = "3.0.0-beta.4" +futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] } trybuild = "1" rustversion = "1" diff --git a/actix-web-codegen/tests/test_macro.rs b/actix-web-codegen/tests/test_macro.rs index 0cbb64ba5..4e06e15ed 100644 --- a/actix-web-codegen/tests/test_macro.rs +++ b/actix-web-codegen/tests/test_macro.rs @@ -1,11 +1,12 @@ use std::future::Future; use std::task::{Context, Poll}; +use actix_utils::future; use actix_web::dev::{Service, ServiceRequest, ServiceResponse, Transform}; use actix_web::http::header::{HeaderName, HeaderValue}; use actix_web::{http, test, web::Path, App, Error, HttpResponse, Responder}; use actix_web_codegen::{connect, delete, get, head, options, patch, post, put, route, trace}; -use futures_util::future::{self, LocalBoxFuture}; +use futures_core::future::LocalBoxFuture; // Make sure that we can name function as 'config' #[get("/config")] diff --git a/awc/Cargo.toml b/awc/Cargo.toml index cc6841606..d0e781e55 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -69,7 +69,7 @@ tls-rustls = { version = "0.19.0", package = "rustls", optional = true, features actix-web = { version = "4.0.0-beta.4", features = ["openssl"] } actix-http = { version = "3.0.0-beta.4", features = ["openssl"] } actix-http-test = { version = "3.0.0-beta.3", features = ["openssl"] } -actix-utils = "3.0.0-beta.1" +actix-utils = "3.0.0-beta.4" actix-server = "2.0.0-beta.3" actix-tls = { version = "3.0.0-beta.5", features = ["openssl", "rustls"] } diff --git a/awc/src/ws.rs b/awc/src/ws.rs index df25b7289..8458d3e31 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, stream::StreamExt}; +//! use futures_util::{sink::SinkExt as _, stream::StreamExt as _}; //! //! #[actix_rt::main] //! async fn main() { diff --git a/awc/tests/test_client.rs b/awc/tests/test_client.rs index 50d2b5eac..9682bc254 100644 --- a/awc/tests/test_client.rs +++ b/awc/tests/test_client.rs @@ -5,12 +5,13 @@ use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::Arc; use std::time::Duration; +use actix_utils::future::ok; use brotli2::write::BrotliEncoder; use bytes::Bytes; use flate2::read::GzDecoder; use flate2::write::GzEncoder; use flate2::Compression; -use futures_util::{future::ok, stream}; +use futures_util::stream; use rand::Rng; use actix_http::{ @@ -159,7 +160,7 @@ async fn test_timeout_override() { #[actix_rt::test] async fn test_response_timeout() { - use futures_util::stream::{once, StreamExt}; + use futures_util::stream::{once, StreamExt as _}; let srv = test::start(|| { App::new().service(web::resource("/").route(web::to(|| async { diff --git a/awc/tests/test_rustls_client.rs b/awc/tests/test_rustls_client.rs index 464edfe89..080eaf792 100644 --- a/awc/tests/test_rustls_client.rs +++ b/awc/tests/test_rustls_client.rs @@ -13,8 +13,8 @@ use std::{ use actix_http::HttpService; use actix_http_test::test_server; use actix_service::{map_config, pipeline_factory, ServiceFactoryExt}; +use actix_utils::future::ok; use actix_web::{dev::AppConfig, http::Version, web, App, HttpResponse}; -use futures_util::future::ok; use rustls::internal::pemfile::{certs, pkcs8_private_keys}; use rustls::{ClientConfig, NoClientAuth, ServerConfig}; diff --git a/awc/tests/test_ssl_client.rs b/awc/tests/test_ssl_client.rs index 3079aaf5e..502223401 100644 --- a/awc/tests/test_ssl_client.rs +++ b/awc/tests/test_ssl_client.rs @@ -8,9 +8,9 @@ use std::sync::Arc; use actix_http::HttpService; use actix_http_test::test_server; use actix_service::{map_config, pipeline_factory, ServiceFactoryExt}; +use actix_utils::future::ok; use actix_web::http::Version; use actix_web::{dev::AppConfig, web, App, HttpResponse}; -use futures_util::future::ok; use openssl::{ pkey::PKey, ssl::{SslAcceptor, SslConnector, SslMethod, SslVerifyMode}, diff --git a/awc/tests/test_ws.rs b/awc/tests/test_ws.rs index 1b3f780dc..3f19ac4e8 100644 --- a/awc/tests/test_ws.rs +++ b/awc/tests/test_ws.rs @@ -3,9 +3,9 @@ use std::io; use actix_codec::Framed; use actix_http::{body::BodySize, h1, ws, Error, HttpService, Request, Response}; use actix_http_test::test_server; +use actix_utils::future::ok; use bytes::Bytes; -use futures_util::future::ok; -use futures_util::{SinkExt, StreamExt}; +use futures_util::{SinkExt as _, StreamExt as _}; async fn ws_service(req: ws::Frame) -> Result { match req { diff --git a/benches/responder.rs b/benches/responder.rs index 8cfdbd3ea..0dfc8cd18 100644 --- a/benches/responder.rs +++ b/benches/responder.rs @@ -1,12 +1,12 @@ -use std::future::Future; -use std::time::Instant; +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; use actix_web::{error, Error, HttpRequest, HttpResponse, Responder}; use criterion::{criterion_group, criterion_main, Criterion}; -use futures_util::future::{ready, Either, Ready}; +use futures_util::future::{join_all, Either}; // responder simulate the old responder trait. trait FutureResponder { @@ -79,7 +79,7 @@ fn future_responder(c: &mut Criterion) { .await }); - let futs = futures_util::future::join_all(futs); + let futs = join_all(futs); let start = Instant::now(); diff --git a/docs/graphs/net-only.dot b/docs/graphs/net-only.dot index 84227cdb0..bee0185ab 100644 --- a/docs/graphs/net-only.dot +++ b/docs/graphs/net-only.dot @@ -1,19 +1,37 @@ digraph { + rankdir=TB + subgraph cluster_net { - label="actix/actix-net"; + label="actix-net" "actix-codec" "actix-macros" "actix-rt" "actix-server" "actix-service" "actix-tls" "actix-tracing" "actix-utils" "actix-router" - "local-channel" "local-waker" + } + + subgraph cluster_other { + label="other actix owned crates" + { rank=same; "local-channel" "local-waker" "bytestring" } } - "actix-codec" -> { "actix-rt" "actix-service" "local-channel" "tokio" } + subgraph cluster_tokio { + label="tokio" + "tokio" "tokio-util" + } + + "actix-codec" -> { "tokio" } + "actix-codec" -> { "tokio-util" }[color=red] "actix-utils" -> { "local-waker" } "actix-tracing" -> { "actix-service" } "actix-tls" -> { "actix-service" "actix-codec" "actix-utils" "actix-rt" } - "actix-server" -> { "actix-service" "actix-rt" "actix-codec" "actix-utils" "tokio" } + "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" } - "tokio" [fontcolor = darkgreen] + // invisible edges to force nicer layout + edge [style=invis] + "actix-macros" -> "tokio" + "actix-service" -> "bytestring" + "actix-macros" -> "bytestring" } diff --git a/src/app.rs b/src/app.rs index f2c6bce8a..357d45eeb 100644 --- a/src/app.rs +++ b/src/app.rs @@ -10,7 +10,7 @@ use actix_service::boxed::{self, BoxServiceFactory}; use actix_service::{ apply, apply_fn_factory, IntoServiceFactory, ServiceFactory, ServiceFactoryExt, Transform, }; -use futures_util::future::FutureExt; +use futures_util::future::FutureExt as _; use crate::app_service::{AppEntry, AppInit, AppRoutingFactory}; use crate::config::ServiceConfig; @@ -465,8 +465,8 @@ where #[cfg(test)] mod tests { use actix_service::Service; + use actix_utils::future::{err, ok}; use bytes::Bytes; - use futures_util::future::{err, ok}; use super::*; use crate::http::{header, HeaderValue, Method, StatusCode}; diff --git a/src/data.rs b/src/data.rs index 56ecdb8ae..3bc54a465 100644 --- a/src/data.rs +++ b/src/data.rs @@ -4,7 +4,8 @@ use std::sync::Arc; use actix_http::error::{Error, ErrorInternalServerError}; use actix_http::Extensions; -use futures_util::future::{err, ok, LocalBoxFuture, Ready}; +use actix_utils::future::{err, ok, Ready}; +use futures_core::future::LocalBoxFuture; use serde::Serialize; use crate::dev::Payload; @@ -147,10 +148,10 @@ impl DataFactory for Data { #[cfg(test)] mod tests { - use actix_service::Service; use std::sync::atomic::{AtomicUsize, Ordering}; use super::*; + use crate::dev::Service; use crate::http::StatusCode; use crate::test::{self, init_service, TestRequest}; use crate::{web, App, HttpResponse}; diff --git a/src/extract.rs b/src/extract.rs index 8851481e3..80f2384a0 100644 --- a/src/extract.rs +++ b/src/extract.rs @@ -6,10 +6,8 @@ use std::{ task::{Context, Poll}, }; -use futures_util::{ - future::{ready, Ready}, - ready, -}; +use actix_utils::future::{ready, Ready}; +use futures_core::ready; use crate::{dev::Payload, Error, HttpRequest}; diff --git a/src/handler.rs b/src/handler.rs index 7e3c5f47e..e005a96a6 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -5,8 +5,8 @@ use std::task::{Context, Poll}; use actix_http::{Error, Response}; use actix_service::{Service, ServiceFactory}; -use futures_util::future::{ready, Ready}; -use futures_util::ready; +use actix_utils::future::{ready, Ready}; +use futures_core::ready; use pin_project::pin_project; use crate::extract::FromRequest; diff --git a/src/middleware/compress.rs b/src/middleware/compress.rs index a397bccd6..6a56e6de0 100644 --- a/src/middleware/compress.rs +++ b/src/middleware/compress.rs @@ -16,8 +16,8 @@ use actix_http::{ Error, }; use actix_service::{Service, Transform}; +use actix_utils::future::{ok, Ready}; use futures_core::ready; -use futures_util::future::{ok, Ready}; use pin_project::pin_project; use crate::{ diff --git a/src/middleware/condition.rs b/src/middleware/condition.rs index dd599a0cb..d1ba7ee4d 100644 --- a/src/middleware/condition.rs +++ b/src/middleware/condition.rs @@ -3,7 +3,9 @@ use std::task::{Context, Poll}; use actix_service::{Service, Transform}; -use futures_util::future::{Either, FutureExt, LocalBoxFuture}; +use actix_utils::future::Either; +use futures_core::future::LocalBoxFuture; +use futures_util::future::FutureExt as _; /// Middleware for conditionally enabling other middleware. /// @@ -85,8 +87,8 @@ 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) => Either::left(service.call(req)), + ConditionMiddleware::Disable(service) => Either::right(service.call(req)), } } } @@ -94,7 +96,7 @@ where #[cfg(test)] mod tests { use actix_service::IntoService; - use futures_util::future::ok; + use actix_utils::future::ok; use super::*; use crate::{ diff --git a/src/middleware/default_headers.rs b/src/middleware/default_headers.rs index 12d70ab2c..d8a947aab 100644 --- a/src/middleware/default_headers.rs +++ b/src/middleware/default_headers.rs @@ -9,10 +9,8 @@ use std::{ task::{Context, Poll}, }; -use futures_util::{ - future::{ready, Ready}, - ready, -}; +use actix_utils::future::{ready, Ready}; +use futures_core::ready; use crate::{ dev::{Service, Transform}, @@ -188,7 +186,7 @@ where #[cfg(test)] mod tests { use actix_service::IntoService; - use futures_util::future::ok; + use actix_utils::future::ok; use super::*; use crate::{ diff --git a/src/middleware/err_handlers.rs b/src/middleware/err_handlers.rs index fddd87a99..88834f8ce 100644 --- a/src/middleware/err_handlers.rs +++ b/src/middleware/err_handlers.rs @@ -175,7 +175,8 @@ where #[cfg(test)] mod tests { use actix_service::IntoService; - use futures_util::future::{ok, FutureExt}; + use actix_utils::future::ok; + use futures_util::future::FutureExt as _; use super::*; use crate::http::{header::CONTENT_TYPE, HeaderValue, StatusCode}; diff --git a/src/middleware/logger.rs b/src/middleware/logger.rs index 8f5391757..3fd372117 100644 --- a/src/middleware/logger.rs +++ b/src/middleware/logger.rs @@ -13,8 +13,9 @@ use std::{ }; use actix_service::{Service, Transform}; +use actix_utils::future::{ok, Ready}; use bytes::Bytes; -use futures_util::future::{ok, Ready}; +use futures_core::ready; use log::{debug, warn}; use regex::{Regex, RegexSet}; use time::OffsetDateTime; @@ -269,7 +270,7 @@ where fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { let this = self.project(); - let res = match futures_util::ready!(this.fut.poll(cx)) { + let res = match ready!(this.fut.poll(cx)) { Ok(res) => res, Err(e) => return Poll::Ready(Err(e)), }; @@ -588,7 +589,7 @@ impl<'a> fmt::Display for FormatDisplay<'a> { #[cfg(test)] mod tests { use actix_service::{IntoService, Service, Transform}; - use futures_util::future::ok; + use actix_utils::future::ok; use super::*; use crate::http::{header, StatusCode}; diff --git a/src/middleware/normalize.rs b/src/middleware/normalize.rs index 2a97a047b..ec6c2a344 100644 --- a/src/middleware/normalize.rs +++ b/src/middleware/normalize.rs @@ -2,8 +2,8 @@ use actix_http::http::{PathAndQuery, Uri}; use actix_service::{Service, Transform}; +use actix_utils::future::{ready, Ready}; use bytes::Bytes; -use futures_util::future::{ready, Ready}; use regex::Regex; use crate::{ diff --git a/src/request.rs b/src/request.rs index 15c97345c..3fdbb13e1 100644 --- a/src/request.rs +++ b/src/request.rs @@ -5,7 +5,7 @@ use std::{fmt, net}; use actix_http::http::{HeaderMap, Method, Uri, Version}; use actix_http::{Error, Extensions, HttpMessage, Message, Payload, RequestHead}; use actix_router::{Path, Url}; -use futures_util::future::{ok, Ready}; +use actix_utils::future::{ok, Ready}; use smallvec::SmallVec; use crate::app_service::AppInitServiceState; diff --git a/src/request_data.rs b/src/request_data.rs index fc711d011..60471cbf9 100644 --- a/src/request_data.rs +++ b/src/request_data.rs @@ -1,7 +1,7 @@ use std::{any::type_name, ops::Deref}; use actix_http::error::{Error, ErrorInternalServerError}; -use futures_util::future; +use actix_utils::future::{err, ok, Ready}; use crate::{dev::Payload, FromRequest, HttpRequest}; @@ -67,11 +67,11 @@ impl Deref for ReqData { impl FromRequest for ReqData { type Config = (); type Error = Error; - type Future = future::Ready>; + type Future = Ready>; fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future { if let Some(st) = req.extensions().get::() { - future::ok(ReqData(st.clone())) + ok(ReqData(st.clone())) } else { log::debug!( "Failed to construct App-level ReqData extractor. \ @@ -79,7 +79,7 @@ impl FromRequest for ReqData { req.path(), type_name::(), ); - future::err(ErrorInternalServerError( + err(ErrorInternalServerError( "Missing expected request extension data", )) } diff --git a/src/resource.rs b/src/resource.rs index 8f356c76d..e868bb547 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -519,7 +519,7 @@ mod tests { use actix_rt::time::sleep; use actix_service::Service; - use futures_util::future::ok; + use actix_utils::future::ok; use crate::http::{header, HeaderValue, Method, StatusCode}; use crate::middleware::DefaultHeaders; diff --git a/src/scope.rs b/src/scope.rs index 693d6860f..3be6adb0c 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -575,8 +575,8 @@ impl ServiceFactory for ScopeEndpoint { #[cfg(test)] mod tests { use actix_service::Service; + use actix_utils::future::ok; use bytes::Bytes; - use futures_util::future::ok; use crate::dev::{Body, ResponseBody}; use crate::http::{header, HeaderValue, Method, StatusCode}; diff --git a/src/service.rs b/src/service.rs index 32f152f7d..570d88e7d 100644 --- a/src/service.rs +++ b/src/service.rs @@ -602,7 +602,7 @@ mod tests { use crate::test::{init_service, TestRequest}; use crate::{guard, http, web, App, HttpResponse}; use actix_service::Service; - use futures_util::future::ok; + use actix_utils::future::ok; #[actix_rt::test] async fn test_service() { diff --git a/src/test.rs b/src/test.rs index 18bf89cda..8ba019445 100644 --- a/src/test.rs +++ b/src/test.rs @@ -15,12 +15,12 @@ use actix_http::{ws, Extensions, HttpService, Request}; use actix_router::{Path, ResourceDef, Url}; use actix_rt::{time::sleep, System}; use actix_service::{map_config, IntoService, IntoServiceFactory, Service, ServiceFactory}; +use actix_utils::future::ok; use awc::error::PayloadError; use awc::{Client, ClientRequest, ClientResponse, Connector}; use bytes::{Bytes, BytesMut}; use futures_core::Stream; -use futures_util::future::ok; -use futures_util::StreamExt; +use futures_util::StreamExt as _; use serde::de::DeserializeOwned; use serde::Serialize; use socket2::{Domain, Protocol, Socket, Type}; diff --git a/src/types/either.rs b/src/types/either.rs index bbab48dec..210495e47 100644 --- a/src/types/either.rs +++ b/src/types/either.rs @@ -1,7 +1,8 @@ //! For either helper, see [`Either`]. use bytes::Bytes; -use futures_util::{future::LocalBoxFuture, FutureExt, TryFutureExt}; +use futures_core::future::LocalBoxFuture; +use futures_util::{FutureExt as _, TryFutureExt as _}; use crate::{ dev, diff --git a/src/types/form.rs b/src/types/form.rs index 57a742e38..0985bd945 100644 --- a/src/types/form.rs +++ b/src/types/form.rs @@ -12,10 +12,8 @@ use std::{ use actix_http::Payload; use bytes::BytesMut; use encoding_rs::{Encoding, UTF_8}; -use futures_util::{ - future::{FutureExt, LocalBoxFuture}, - StreamExt, -}; +use futures_core::future::LocalBoxFuture; +use futures_util::{FutureExt as _, StreamExt as _}; use serde::{de::DeserializeOwned, Serialize}; #[cfg(feature = "compress")] diff --git a/src/types/json.rs b/src/types/json.rs index d8ce3cb71..068dfeb2c 100644 --- a/src/types/json.rs +++ b/src/types/json.rs @@ -11,7 +11,7 @@ use std::{ }; use bytes::BytesMut; -use futures_util::{ready, stream::Stream}; +use futures_core::{ready, stream::Stream as _}; use serde::{de::DeserializeOwned, Serialize}; use actix_http::Payload; diff --git a/src/types/path.rs b/src/types/path.rs index 294df6cf2..90ee5296b 100644 --- a/src/types/path.rs +++ b/src/types/path.rs @@ -4,7 +4,7 @@ use std::{fmt, ops, sync::Arc}; use actix_http::error::{Error, ErrorNotFound}; use actix_router::PathDeserializer; -use futures_util::future::{ready, Ready}; +use actix_utils::future::{ready, Ready}; use serde::de; use crate::{dev::Payload, error::PathError, FromRequest, HttpRequest}; diff --git a/src/types/payload.rs b/src/types/payload.rs index 781347b84..f88800855 100644 --- a/src/types/payload.rs +++ b/src/types/payload.rs @@ -8,13 +8,10 @@ use std::{ }; use actix_http::error::{ErrorBadRequest, PayloadError}; +use actix_utils::future::{ready, Either, Ready}; use bytes::{Bytes, BytesMut}; use encoding_rs::{Encoding, UTF_8}; -use futures_core::stream::Stream; -use futures_util::{ - future::{ready, Either, ErrInto, Ready, TryFutureExt as _}, - ready, -}; +use futures_core::{ready, stream::Stream}; use mime::Mime; use crate::{dev, http::header, web, Error, FromRequest, HttpMessage, HttpRequest}; @@ -26,7 +23,7 @@ use crate::{dev, http::header, web, Error, FromRequest, HttpMessage, HttpRequest /// # Examples /// ``` /// use std::future::Future; -/// use futures_util::stream::{Stream, StreamExt}; +/// use futures_util::stream::StreamExt as _; /// use actix_web::{post, web}; /// /// // `body: web::Payload` parameter extracts raw payload stream from request @@ -91,7 +88,7 @@ impl FromRequest for Payload { impl FromRequest for Bytes { type Config = PayloadConfig; type Error = Error; - type Future = Either, Ready>>; + type Future = Either>>; #[inline] fn from_request(req: &HttpRequest, payload: &mut dev::Payload) -> Self::Future { @@ -99,12 +96,25 @@ impl FromRequest for Bytes { let cfg = PayloadConfig::from_req(req); if let Err(err) = cfg.check_mimetype(req) { - return Either::Right(ready(Err(err))); + return Either::right(ready(Err(err))); } - let limit = cfg.limit; - let fut = HttpMessageBody::new(req, payload).limit(limit); - Either::Left(fut.err_into()) + Either::left(BytesExtractFut { + body_fut: HttpMessageBody::new(req, payload).limit(cfg.limit), + }) + } +} + +/// Future for `Bytes` extractor. +pub struct BytesExtractFut { + body_fut: HttpMessageBody, +} + +impl<'a> Future for BytesExtractFut { + type Output = Result; + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + Pin::new(&mut self.body_fut).poll(cx).map_err(Into::into) } } @@ -135,21 +145,22 @@ impl FromRequest for String { // check content-type if let Err(err) = cfg.check_mimetype(req) { - return Either::Right(ready(Err(err))); + return Either::right(ready(Err(err))); } // check charset let encoding = match req.encoding() { Ok(enc) => enc, - Err(err) => return Either::Right(ready(Err(err.into()))), + Err(err) => return Either::right(ready(Err(err.into()))), }; let limit = cfg.limit; let body_fut = HttpMessageBody::new(req, payload).limit(limit); - Either::Left(StringExtractFut { body_fut, encoding }) + Either::left(StringExtractFut { body_fut, encoding }) } } +/// Future for `String` extractor. pub struct StringExtractFut { body_fut: HttpMessageBody, encoding: &'static Encoding, diff --git a/src/types/query.rs b/src/types/query.rs index 79af32581..b6c025ef3 100644 --- a/src/types/query.rs +++ b/src/types/query.rs @@ -2,7 +2,7 @@ use std::{fmt, ops, sync::Arc}; -use futures_util::future::{err, ok, Ready}; +use actix_utils::future::{err, ok, Ready}; use serde::de; use crate::{dev::Payload, error::QueryPayloadError, Error, FromRequest, HttpRequest}; diff --git a/src/types/readlines.rs b/src/types/readlines.rs index b8bdcc504..6c456e21c 100644 --- a/src/types/readlines.rs +++ b/src/types/readlines.rs @@ -177,7 +177,7 @@ where #[cfg(test)] mod tests { - use futures_util::stream::StreamExt; + use futures_util::stream::StreamExt as _; use super::*; use crate::test::TestRequest; diff --git a/tests/test_server.rs b/tests/test_server.rs index b35af657d..db9fe37db 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -20,7 +20,7 @@ use flate2::{ write::{GzEncoder, ZlibDecoder, ZlibEncoder}, Compression, }; -use futures_util::ready; +use futures_core::ready; #[cfg(feature = "openssl")] use openssl::{ pkey::PKey, From 50dc13f280573163d3c63b49fc1168004db0e870 Mon Sep 17 00:00:00 2001 From: Ibraheem Ahmed Date: Thu, 1 Apr 2021 11:42:18 -0400 Subject: [PATCH 133/339] move typed headers and implement FromRequest (#2094) Co-authored-by: Rob Ede --- CHANGES.md | 3 + Cargo.toml | 2 + actix-http/CHANGES.md | 2 + actix-http/src/header/mod.rs | 33 ------ .../{common => shared}/content_encoding.rs | 0 actix-http/src/header/shared/mod.rs | 6 +- actix-http/src/header/shared/quality_item.rs | 61 +++++++++- actix-http/src/header/utils.rs | 3 +- actix-http/src/response.rs | 16 +-- awc/CHANGES.md | 2 + awc/src/request.rs | 8 +- awc/src/response.rs | 24 ++-- awc/src/test.rs | 43 ++++--- .../common => src/http/header}/accept.rs | 28 ++--- .../http/header}/accept_charset.rs | 22 ++-- .../http/header}/accept_encoding.rs | 21 ++-- .../http/header}/accept_language.rs | 16 +-- .../common => src/http/header}/allow.rs | 18 +-- .../http/header}/cache_control.rs | 22 ++-- .../http/header}/content_disposition.rs | 11 +- .../http/header}/content_language.rs | 16 +-- .../http/header}/content_range.rs | 4 +- .../http/header}/content_type.rs | 16 +-- .../header/common => src/http/header}/date.rs | 10 +- .../shared => src/http/header}/encoding.rs | 0 .../shared => src/http/header}/entity.rs | 2 +- .../header/common => src/http/header}/etag.rs | 16 +-- .../common => src/http/header}/expires.rs | 10 +- .../common => src/http/header}/if_match.rs | 16 +-- .../http/header}/if_modified_since.rs | 10 +- .../http/header}/if_none_match.rs | 20 ++-- .../common => src/http/header}/if_range.rs | 17 +-- .../http/header}/if_unmodified_since.rs | 10 +- .../http/header}/last_modified.rs | 10 +- .../header/common => src/http/header}/mod.rs | 50 ++++++-- .../common => src/http/header}/range.rs | 4 +- src/http/mod.rs | 2 + src/lib.rs | 3 +- src/test.rs | 22 ++-- src/types/header.rs | 112 ++++++++++++++++++ src/types/mod.rs | 2 + 41 files changed, 445 insertions(+), 248 deletions(-) rename actix-http/src/header/{common => shared}/content_encoding.rs (100%) rename {actix-http/src/header/common => src/http/header}/accept.rs (93%) rename {actix-http/src/header/common => src/http/header}/accept_charset.rs (73%) rename {actix-http/src/header/common => src/http/header}/accept_encoding.rs (76%) rename {actix-http/src/header/common => src/http/header}/accept_language.rs (82%) rename {actix-http/src/header/common => src/http/header}/allow.rs (83%) rename {actix-http/src/header/common => src/http/header}/cache_control.rs (95%) rename {actix-http/src/header/common => src/http/header}/content_disposition.rs (99%) rename {actix-http/src/header/common => src/http/header}/content_language.rs (77%) rename {actix-http/src/header/common => src/http/header}/content_range.rs (99%) rename {actix-http/src/header/common => src/http/header}/content_type.rs (91%) rename {actix-http/src/header/common => src/http/header}/date.rs (83%) rename {actix-http/src/header/shared => src/http/header}/encoding.rs (100%) rename {actix-http/src/header/shared => src/http/header}/entity.rs (99%) rename {actix-http/src/header/common => src/http/header}/etag.rs (90%) rename {actix-http/src/header/common => src/http/header}/expires.rs (84%) rename {actix-http/src/header/common => src/http/header}/if_match.rs (86%) rename {actix-http/src/header/common => src/http/header}/if_modified_since.rs (84%) rename {actix-http/src/header/common => src/http/header}/if_none_match.rs (86%) rename {actix-http/src/header/common => src/http/header}/if_range.rs (89%) rename {actix-http/src/header/common => src/http/header}/if_unmodified_since.rs (85%) rename {actix-http/src/header/common => src/http/header}/last_modified.rs (84%) rename {actix-http/src/header/common => src/http/header}/mod.rs (92%) rename {actix-http/src/header/common => src/http/header}/range.rs (99%) create mode 100644 src/http/mod.rs create mode 100644 src/types/header.rs diff --git a/CHANGES.md b/CHANGES.md index 39743be2b..8e7b22f58 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -2,6 +2,7 @@ ## Unreleased - 2021-xx-xx ### Added +* `Header` extractor for extracting common HTTP headers in handlers. [#2094] * Added `TestServer::client_headers` method. [#2097] ### Fixed @@ -17,6 +18,8 @@ [#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 ## 4.0.0-beta.4 - 2021-03-09 diff --git a/Cargo.toml b/Cargo.toml index 15f9f5fe8..420e76405 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -97,6 +97,8 @@ 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 } +language-tags = "0.2" +once_cell = "1.5" log = "0.4" mime = "0.3" pin-project = "1.0.0" diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 3a7b4f024..374c5f199 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -10,10 +10,12 @@ * `client::Connector` type now only have one generic type for `actix_service::Service`. [#2063] ### Removed +* Common HTTP headers were moved into 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 +[#2094]: https://github.com/actix/actix-web/pull/2094 [#2127]: https://github.com/actix/actix-web/pull/2127 diff --git a/actix-http/src/header/mod.rs b/actix-http/src/header/mod.rs index 1100a959d..a6056ace4 100644 --- a/actix-http/src/header/mod.rs +++ b/actix-http/src/header/mod.rs @@ -1,9 +1,6 @@ //! Typed HTTP headers, pre-defined `HeaderName`s, traits for parsing and conversion, and other //! header utility methods. -use std::fmt; - -use bytes::{Bytes, BytesMut}; use percent_encoding::{AsciiSet, CONTROLS}; pub use http::header::*; @@ -16,11 +13,9 @@ mod into_pair; mod into_value; mod utils; -mod common; pub(crate) mod map; mod shared; -pub use self::common::*; #[doc(hidden)] pub use self::shared::*; @@ -41,34 +36,6 @@ pub trait Header: IntoHeaderValue { fn parse(msg: &T) -> Result; } -#[derive(Debug, Default)] -pub(crate) struct Writer { - buf: BytesMut, -} - -impl Writer { - fn new() -> Writer { - Writer::default() - } - - fn take(&mut self) -> Bytes { - self.buf.split().freeze() - } -} - -impl fmt::Write for Writer { - #[inline] - fn write_str(&mut self, s: &str) -> fmt::Result { - self.buf.extend_from_slice(s.as_bytes()); - Ok(()) - } - - #[inline] - fn write_fmt(&mut self, args: fmt::Arguments<'_>) -> fmt::Result { - fmt::write(self, args) - } -} - /// Convert `http::HeaderMap` to our `HeaderMap`. impl From for HeaderMap { fn from(mut map: http::HeaderMap) -> HeaderMap { diff --git a/actix-http/src/header/common/content_encoding.rs b/actix-http/src/header/shared/content_encoding.rs similarity index 100% rename from actix-http/src/header/common/content_encoding.rs rename to actix-http/src/header/shared/content_encoding.rs diff --git a/actix-http/src/header/shared/mod.rs b/actix-http/src/header/shared/mod.rs index 72161e46b..b8f9173f9 100644 --- a/actix-http/src/header/shared/mod.rs +++ b/actix-http/src/header/shared/mod.rs @@ -1,15 +1,13 @@ //! Originally taken from `hyper::header::shared`. mod charset; -mod encoding; -mod entity; +mod content_encoding; mod extended; mod httpdate; mod quality_item; pub use self::charset::Charset; -pub use self::encoding::Encoding; -pub use self::entity::EntityTag; +pub use self::content_encoding::ContentEncoding; pub use self::extended::{parse_extended_value, ExtendedValue}; pub use self::httpdate::HttpDate; pub use self::quality_item::{q, qitem, Quality, QualityItem}; diff --git a/actix-http/src/header/shared/quality_item.rs b/actix-http/src/header/shared/quality_item.rs index 01a3b988a..240a0afa2 100644 --- a/actix-http/src/header/shared/quality_item.rs +++ b/actix-http/src/header/shared/quality_item.rs @@ -193,21 +193,69 @@ where #[cfg(test)] mod tests { - use super::super::encoding::*; use super::*; + // copy of encoding from actix-web headers + #[derive(Clone, PartialEq, Debug)] + pub enum Encoding { + Chunked, + Brotli, + Gzip, + Deflate, + Compress, + Identity, + Trailers, + EncodingExt(String), + } + + impl fmt::Display for Encoding { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + use Encoding::*; + f.write_str(match *self { + Chunked => "chunked", + Brotli => "br", + Gzip => "gzip", + Deflate => "deflate", + Compress => "compress", + Identity => "identity", + Trailers => "trailers", + EncodingExt(ref s) => s.as_ref(), + }) + } + } + + impl str::FromStr for Encoding { + type Err = crate::error::ParseError; + fn from_str(s: &str) -> Result { + use Encoding::*; + match s { + "chunked" => Ok(Chunked), + "br" => Ok(Brotli), + "deflate" => Ok(Deflate), + "gzip" => Ok(Gzip), + "compress" => Ok(Compress), + "identity" => Ok(Identity), + "trailers" => Ok(Trailers), + _ => Ok(EncodingExt(s.to_owned())), + } + } + } + #[test] fn test_quality_item_fmt_q_1() { + use Encoding::*; let x = qitem(Chunked); assert_eq!(format!("{}", x), "chunked"); } #[test] fn test_quality_item_fmt_q_0001() { + use Encoding::*; let x = QualityItem::new(Chunked, Quality(1)); assert_eq!(format!("{}", x), "chunked; q=0.001"); } #[test] fn test_quality_item_fmt_q_05() { + use Encoding::*; // Custom value let x = QualityItem { item: EncodingExt("identity".to_owned()), @@ -218,6 +266,7 @@ mod tests { #[test] fn test_quality_item_fmt_q_0() { + use Encoding::*; // Custom value let x = QualityItem { item: EncodingExt("identity".to_owned()), @@ -228,6 +277,7 @@ mod tests { #[test] fn test_quality_item_from_str1() { + use Encoding::*; let x: Result, _> = "chunked".parse(); assert_eq!( x.unwrap(), @@ -237,8 +287,10 @@ mod tests { } ); } + #[test] fn test_quality_item_from_str2() { + use Encoding::*; let x: Result, _> = "chunked; q=1".parse(); assert_eq!( x.unwrap(), @@ -248,8 +300,10 @@ mod tests { } ); } + #[test] fn test_quality_item_from_str3() { + use Encoding::*; let x: Result, _> = "gzip; q=0.5".parse(); assert_eq!( x.unwrap(), @@ -259,8 +313,10 @@ mod tests { } ); } + #[test] fn test_quality_item_from_str4() { + use Encoding::*; let x: Result, _> = "gzip; q=0.273".parse(); assert_eq!( x.unwrap(), @@ -270,16 +326,19 @@ mod tests { } ); } + #[test] fn test_quality_item_from_str5() { let x: Result, _> = "gzip; q=0.2739999".parse(); assert!(x.is_err()); } + #[test] fn test_quality_item_from_str6() { let x: Result, _> = "gzip; q=2".parse(); assert!(x.is_err()); } + #[test] fn test_quality_item_ordering() { let x: QualityItem = "gzip; q=0.5".parse().ok().unwrap(); diff --git a/actix-http/src/header/utils.rs b/actix-http/src/header/utils.rs index e232d462f..5e9652380 100644 --- a/actix-http/src/header/utils.rs +++ b/actix-http/src/header/utils.rs @@ -1,7 +1,6 @@ use std::{fmt, str::FromStr}; -use http::HeaderValue; - +use super::HeaderValue; use crate::{error::ParseError, header::HTTP_VALUE}; /// Reads a comma-delimited raw header into a Vec. diff --git a/actix-http/src/response.rs b/actix-http/src/response.rs index 94f12bcc3..b27f477c9 100644 --- a/actix-http/src/response.rs +++ b/actix-http/src/response.rs @@ -359,10 +359,10 @@ impl ResponseBuilder { /// /// ``` /// # use actix_http::Response; - /// use actix_http::http::header::ContentType; + /// use actix_http::http::header; /// /// Response::Ok() - /// .insert_header(ContentType(mime::APPLICATION_JSON)) + /// .insert_header((header::CONTENT_TYPE, mime::APPLICATION_JSON)) /// .insert_header(("X-TEST", "value")) /// .finish(); /// ``` @@ -386,10 +386,10 @@ impl ResponseBuilder { /// /// ``` /// # use actix_http::Response; - /// use actix_http::http::header::ContentType; + /// use actix_http::http::header; /// /// Response::Ok() - /// .append_header(ContentType(mime::APPLICATION_JSON)) + /// .append_header((header::CONTENT_TYPE, mime::APPLICATION_JSON)) /// .append_header(("X-TEST", "value1")) /// .append_header(("X-TEST", "value2")) /// .finish(); @@ -682,7 +682,7 @@ impl ResponseBuilder { }; if !contains { - self.insert_header(header::ContentType(mime::APPLICATION_JSON)); + self.insert_header((header::CONTENT_TYPE, mime::APPLICATION_JSON)); } self.body(Body::from(body)) @@ -1133,7 +1133,7 @@ mod tests { #[test] fn response_builder_header_insert_typed() { let mut res = Response::Ok(); - res.insert_header(header::ContentType(mime::APPLICATION_OCTET_STREAM)); + res.insert_header((header::CONTENT_TYPE, mime::APPLICATION_OCTET_STREAM)); let res = res.finish(); assert_eq!( @@ -1158,8 +1158,8 @@ mod tests { #[test] fn response_builder_header_append_typed() { let mut res = Response::Ok(); - res.append_header(header::ContentType(mime::APPLICATION_OCTET_STREAM)); - res.append_header(header::ContentType(mime::APPLICATION_JSON)); + res.append_header((header::CONTENT_TYPE, mime::APPLICATION_OCTET_STREAM)); + res.append_header((header::CONTENT_TYPE, mime::APPLICATION_JSON)); let res = res.finish(); let headers: Vec<_> = res.headers().get_all("Content-Type").cloned().collect(); diff --git a/awc/CHANGES.md b/awc/CHANGES.md index b745e9868..c1031239a 100644 --- a/awc/CHANGES.md +++ b/awc/CHANGES.md @@ -7,8 +7,10 @@ ### 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 methods now take `IntoHeaderPair` tuples. [#2094] [#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 diff --git a/awc/src/request.rs b/awc/src/request.rs index 8b896a00d..6ecb64f81 100644 --- a/awc/src/request.rs +++ b/awc/src/request.rs @@ -189,12 +189,12 @@ impl ClientRequest { /// # #[actix_rt::main] /// # async fn main() { /// # use awc::Client; - /// use awc::http::header::ContentType; + /// use awc::http::header::CONTENT_TYPE; /// /// Client::new() /// .get("http://www.rust-lang.org") /// .insert_header(("X-TEST", "value")) - /// .insert_header(ContentType(mime::APPLICATION_JSON)); + /// .insert_header((CONTENT_TYPE, mime::APPLICATION_JSON)); /// # } /// ``` pub fn append_header(mut self, header: H) -> Self @@ -548,6 +548,8 @@ impl fmt::Debug for ClientRequest { mod tests { use std::time::SystemTime; + use actix_http::http::header::HttpDate; + use super::*; use crate::Client; @@ -564,7 +566,7 @@ mod tests { let req = Client::new() .put("/") .version(Version::HTTP_2) - .insert_header(header::Date(SystemTime::now().into())) + .insert_header((header::DATE, HttpDate::from(SystemTime::now()))) .content_type("plain/text") .append_header((header::SERVER, "awc")); diff --git a/awc/src/response.rs b/awc/src/response.rs index 994ddb761..27ba83af7 100644 --- a/awc/src/response.rs +++ b/awc/src/response.rs @@ -449,13 +449,13 @@ mod tests { #[actix_rt::test] async fn test_body() { - let mut req = TestResponse::with_header(header::CONTENT_LENGTH, "xxxx").finish(); + 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(); + let mut req = TestResponse::with_header((header::CONTENT_LENGTH, "10000000")).finish(); match req.body().await.err().unwrap() { PayloadError::Overflow => {} _ => unreachable!("error"), @@ -497,23 +497,23 @@ mod tests { assert!(json_eq(json.err().unwrap(), JsonPayloadError::ContentType)); let mut req = TestResponse::default() - .header( + .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() - .header( + .insert_header(( header::CONTENT_TYPE, header::HeaderValue::from_static("application/json"), - ) - .header( + )) + .insert_header(( header::CONTENT_LENGTH, header::HeaderValue::from_static("10000"), - ) + )) .finish(); let json = JsonBody::<_, MyObject>::new(&mut req).limit(100).await; @@ -523,14 +523,14 @@ mod tests { )); let mut req = TestResponse::default() - .header( + .insert_header(( header::CONTENT_TYPE, header::HeaderValue::from_static("application/json"), - ) - .header( + )) + .insert_header(( header::CONTENT_LENGTH, header::HeaderValue::from_static("16"), - ) + )) .set_payload(Bytes::from_static(b"{\"name\": \"test\"}")) .finish(); diff --git a/awc/src/test.rs b/awc/src/test.rs index 97bbb9c3d..8e95396b3 100644 --- a/awc/src/test.rs +++ b/awc/src/test.rs @@ -1,8 +1,6 @@ //! Test helpers for actix http client to use during testing. -use std::convert::TryFrom; - -use actix_http::http::header::{Header, IntoHeaderValue}; -use actix_http::http::{Error as HttpError, HeaderName, StatusCode, Version}; +use actix_http::http::header::IntoHeaderPair; +use actix_http::http::{StatusCode, Version}; #[cfg(feature = "cookies")] use actix_http::{ cookie::{Cookie, CookieJar}, @@ -34,13 +32,11 @@ impl Default for TestResponse { impl TestResponse { /// Create TestResponse and set header - pub fn with_header(key: K, value: V) -> Self + pub fn with_header(header: H) -> Self where - HeaderName: TryFrom, - >::Error: Into, - V: IntoHeaderValue, + H: IntoHeaderPair, { - Self::default().header(key, value) + Self::default().insert_header(header) } /// Set HTTP version of this response @@ -49,27 +45,26 @@ impl TestResponse { self } - /// Set a header - pub fn set(mut self, hdr: H) -> Self { - if let Ok(value) = hdr.try_into_value() { - self.head.headers.append(H::name(), value); + /// 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() { + self.head.headers.insert(key, value); return self; } panic!("Can not set header"); } /// Append a header - pub fn header(mut self, key: K, value: V) -> Self + pub fn append_header(mut self, header: H) -> Self where - HeaderName: TryFrom, - >::Error: Into, - V: IntoHeaderValue, + H: IntoHeaderPair, { - if let Ok(key) = HeaderName::try_from(key) { - if let Ok(value) = value.try_into_value() { - self.head.headers.append(key, value); - return self; - } + if let Ok((key, value)) = header.try_into_header_pair() { + self.head.headers.append(key, value); + return self; } panic!("Can not create header"); } @@ -115,6 +110,8 @@ impl TestResponse { mod tests { use std::time::SystemTime; + use actix_http::http::header::HttpDate; + use super::*; use crate::{cookie, http::header}; @@ -122,7 +119,7 @@ mod tests { fn test_basics() { let res = TestResponse::default() .version(Version::HTTP_2) - .set(header::Date(SystemTime::now().into())) + .insert_header((header::DATE, HttpDate::from(SystemTime::now()))) .cookie(cookie::Cookie::build("name", "value").finish()) .finish(); assert!(res.headers().contains_key(header::SET_COOKIE)); diff --git a/actix-http/src/header/common/accept.rs b/src/http/header/accept.rs similarity index 93% rename from actix-http/src/header/common/accept.rs rename to src/http/header/accept.rs index 775da3394..1ec94e353 100644 --- a/actix-http/src/header/common/accept.rs +++ b/src/http/header/accept.rs @@ -2,10 +2,10 @@ use std::cmp::Ordering; use mime::Mime; -use crate::header::{qitem, QualityItem}; +use super::{qitem, QualityItem}; use crate::http::header; -header! { +crate::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 @@ -33,10 +33,10 @@ header! { /// /// # Examples /// ``` - /// use actix_http::Response; - /// use actix_http::http::header::{Accept, qitem}; + /// use actix_web::HttpResponse; + /// use actix_web::http::header::{Accept, qitem}; /// - /// let mut builder = Response::Ok(); + /// let mut builder = HttpResponse::Ok(); /// builder.insert_header( /// Accept(vec![ /// qitem(mime::TEXT_HTML), @@ -45,10 +45,10 @@ header! { /// ``` /// /// ``` - /// use actix_http::Response; - /// use actix_http::http::header::{Accept, qitem}; + /// use actix_web::HttpResponse; + /// use actix_web::http::header::{Accept, qitem}; /// - /// let mut builder = Response::Ok(); + /// let mut builder = HttpResponse::Ok(); /// builder.insert_header( /// Accept(vec![ /// qitem(mime::APPLICATION_JSON), @@ -57,10 +57,10 @@ header! { /// ``` /// /// ``` - /// use actix_http::Response; - /// use actix_http::http::header::{Accept, QualityItem, q, qitem}; + /// use actix_web::HttpResponse; + /// use actix_web::http::header::{Accept, QualityItem, q, qitem}; /// - /// let mut builder = Response::Ok(); + /// let mut builder = HttpResponse::Ok(); /// builder.insert_header( /// Accept(vec![ /// qitem(mime::TEXT_HTML), @@ -116,8 +116,8 @@ header! { #[test] fn test_fuzzing1() { - use crate::test::TestRequest; - let req = TestRequest::default().insert_header((crate::header::ACCEPT, "chunk#;e")).finish(); + use actix_http::test::TestRequest; + let req = TestRequest::default().insert_header((crate::http::header::ACCEPT, "chunk#;e")).finish(); let header = Accept::parse(&req); assert!(header.is_ok()); } @@ -213,7 +213,7 @@ impl Accept { #[cfg(test)] mod tests { use super::*; - use crate::header::q; + use crate::http::header::q; #[test] fn test_mime_precedence() { diff --git a/actix-http/src/header/common/accept_charset.rs b/src/http/header/accept_charset.rs similarity index 73% rename from actix-http/src/header/common/accept_charset.rs rename to src/http/header/accept_charset.rs index db530a8bc..9932ac57a 100644 --- a/actix-http/src/header/common/accept_charset.rs +++ b/src/http/header/accept_charset.rs @@ -1,6 +1,6 @@ -use crate::header::{Charset, QualityItem, ACCEPT_CHARSET}; +use super::{Charset, QualityItem, ACCEPT_CHARSET}; -header! { +crate::header! { /// `Accept-Charset` header, defined in /// [RFC7231](http://tools.ietf.org/html/rfc7231#section-5.3.3) /// @@ -22,20 +22,20 @@ header! { /// /// # Examples /// ``` - /// use actix_http::Response; - /// use actix_http::http::header::{AcceptCharset, Charset, qitem}; + /// use actix_web::HttpResponse; + /// use actix_web::http::header::{AcceptCharset, Charset, qitem}; /// - /// let mut builder = Response::Ok(); + /// let mut builder = HttpResponse::Ok(); /// builder.insert_header( /// AcceptCharset(vec![qitem(Charset::Us_Ascii)]) /// ); /// ``` /// /// ``` - /// use actix_http::Response; - /// use actix_http::http::header::{AcceptCharset, Charset, q, QualityItem}; + /// use actix_web::HttpResponse; + /// use actix_web::http::header::{AcceptCharset, Charset, q, QualityItem}; /// - /// let mut builder = Response::Ok(); + /// let mut builder = HttpResponse::Ok(); /// builder.insert_header( /// AcceptCharset(vec![ /// QualityItem::new(Charset::Us_Ascii, q(900)), @@ -45,10 +45,10 @@ header! { /// ``` /// /// ``` - /// use actix_http::Response; - /// use actix_http::http::header::{AcceptCharset, Charset, qitem}; + /// use actix_web::HttpResponse; + /// use actix_web::http::header::{AcceptCharset, Charset, qitem}; /// - /// let mut builder = Response::Ok(); + /// let mut builder = HttpResponse::Ok(); /// builder.insert_header( /// AcceptCharset(vec![qitem(Charset::Ext("utf-8".to_owned()))]) /// ); diff --git a/actix-http/src/header/common/accept_encoding.rs b/src/http/header/accept_encoding.rs similarity index 76% rename from actix-http/src/header/common/accept_encoding.rs rename to src/http/header/accept_encoding.rs index c90f529bc..e59351708 100644 --- a/actix-http/src/header/common/accept_encoding.rs +++ b/src/http/header/accept_encoding.rs @@ -26,18 +26,20 @@ header! { /// /// # Examples /// ``` - /// use hyper::header::{Headers, AcceptEncoding, Encoding, qitem}; + /// use actix_web::HttpResponse; + /// use actix_web::http::header::{AcceptEncoding, Encoding, qitem}; /// - /// let mut headers = Headers::new(); - /// headers.set( + /// let mut builder = HttpResponse::new(); + /// builder.insert_header( /// AcceptEncoding(vec![qitem(Encoding::Chunked)]) /// ); /// ``` /// ``` - /// use hyper::header::{Headers, AcceptEncoding, Encoding, qitem}; + /// use actix_web::HttpResponse; + /// use actix_web::http::header::{AcceptEncoding, Encoding, qitem}; /// - /// let mut headers = Headers::new(); - /// headers.set( + /// let mut builder = HttpResponse::new(); + /// builder.insert_header( /// AcceptEncoding(vec![ /// qitem(Encoding::Chunked), /// qitem(Encoding::Gzip), @@ -46,10 +48,11 @@ header! { /// ); /// ``` /// ``` - /// use hyper::header::{Headers, AcceptEncoding, Encoding, QualityItem, q, qitem}; + /// use actix_web::HttpResponse; + /// use actix_web::http::header::{AcceptEncoding, Encoding, QualityItem, q, qitem}; /// - /// let mut headers = Headers::new(); - /// headers.set( + /// let mut builder = HttpResponse::new(); + /// builder.insert_header( /// AcceptEncoding(vec![ /// qitem(Encoding::Chunked), /// QualityItem::new(Encoding::Gzip, q(600)), diff --git a/actix-http/src/header/common/accept_language.rs b/src/http/header/accept_language.rs similarity index 82% rename from actix-http/src/header/common/accept_language.rs rename to src/http/header/accept_language.rs index a7ad00863..2963844af 100644 --- a/actix-http/src/header/common/accept_language.rs +++ b/src/http/header/accept_language.rs @@ -1,7 +1,7 @@ -use crate::header::{QualityItem, ACCEPT_LANGUAGE}; +use super::{QualityItem, ACCEPT_LANGUAGE}; use language_tags::LanguageTag; -header! { +crate::header! { /// `Accept-Language` header, defined in /// [RFC7231](http://tools.ietf.org/html/rfc7231#section-5.3.5) /// @@ -24,10 +24,10 @@ header! { /// /// ``` /// use language_tags::langtag; - /// use actix_http::Response; - /// use actix_http::http::header::{AcceptLanguage, LanguageTag, qitem}; + /// use actix_web::HttpResponse; + /// use actix_web::http::header::{AcceptLanguage, LanguageTag, qitem}; /// - /// let mut builder = Response::Ok(); + /// let mut builder = HttpResponse::Ok(); /// let mut langtag: LanguageTag = Default::default(); /// langtag.language = Some("en".to_owned()); /// langtag.region = Some("US".to_owned()); @@ -40,10 +40,10 @@ header! { /// /// ``` /// use language_tags::langtag; - /// use actix_http::Response; - /// use actix_http::http::header::{AcceptLanguage, QualityItem, q, qitem}; + /// use actix_web::HttpResponse; + /// use actix_web::http::header::{AcceptLanguage, QualityItem, q, qitem}; /// - /// let mut builder = Response::Ok(); + /// let mut builder = HttpResponse::Ok(); /// builder.insert_header( /// AcceptLanguage(vec![ /// qitem(langtag!(da)), diff --git a/actix-http/src/header/common/allow.rs b/src/http/header/allow.rs similarity index 83% rename from actix-http/src/header/common/allow.rs rename to src/http/header/allow.rs index 06b1efedc..e1f2bb4b6 100644 --- a/actix-http/src/header/common/allow.rs +++ b/src/http/header/allow.rs @@ -1,7 +1,7 @@ -use http::header; -use http::Method; +use actix_http::http::Method; +use crate::http::header; -header! { +crate::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 @@ -23,20 +23,20 @@ header! { /// # Examples /// /// ``` - /// use actix_http::Response; - /// use actix_http::http::{header::Allow, Method}; + /// use actix_web::HttpResponse; + /// use actix_web::http::{header::Allow, Method}; /// - /// let mut builder = Response::Ok(); + /// let mut builder = HttpResponse::Ok(); /// builder.insert_header( /// Allow(vec![Method::GET]) /// ); /// ``` /// /// ``` - /// use actix_http::Response; - /// use actix_http::http::{header::Allow, Method}; + /// use actix_web::HttpResponse; + /// use actix_web::http::{header::Allow, Method}; /// - /// let mut builder = Response::Ok(); + /// let mut builder = HttpResponse::Ok(); /// builder.insert_header( /// Allow(vec![ /// Method::GET, diff --git a/actix-http/src/header/common/cache_control.rs b/src/http/header/cache_control.rs similarity index 95% rename from actix-http/src/header/common/cache_control.rs rename to src/http/header/cache_control.rs index b19823d22..6f020a931 100644 --- a/actix-http/src/header/common/cache_control.rs +++ b/src/http/header/cache_control.rs @@ -1,12 +1,12 @@ use std::fmt::{self, Write}; use std::str::FromStr; -use http::header; - -use crate::header::{ +use super::{ fmt_comma_delimited, from_comma_delimited, Header, IntoHeaderValue, Writer, }; +use crate::http::header; + /// `Cache-Control` header, defined in [RFC7234](https://tools.ietf.org/html/rfc7234#section-5.2) /// /// The `Cache-Control` header field is used to specify directives for @@ -29,18 +29,18 @@ use crate::header::{ /// /// # Examples /// ``` -/// use actix_http::Response; -/// use actix_http::http::header::{CacheControl, CacheDirective}; +/// use actix_web::HttpResponse; +/// use actix_web::http::header::{CacheControl, CacheDirective}; /// -/// let mut builder = Response::Ok(); +/// let mut builder = HttpResponse::Ok(); /// builder.insert_header(CacheControl(vec![CacheDirective::MaxAge(86400u32)])); /// ``` /// /// ``` -/// use actix_http::Response; -/// use actix_http::http::header::{CacheControl, CacheDirective}; +/// use actix_web::HttpResponse; +/// use actix_web::http::header::{CacheControl, CacheDirective}; /// -/// let mut builder = Response::Ok(); +/// let mut builder = HttpResponse::Ok(); /// builder.insert_header(CacheControl(vec![ /// CacheDirective::NoCache, /// CacheDirective::Private, @@ -191,8 +191,8 @@ impl FromStr for CacheDirective { #[cfg(test)] mod tests { use super::*; - use crate::header::Header; - use crate::test::TestRequest; + use crate::http::header::Header; + use actix_http::test::TestRequest; #[test] fn test_parse_multiple_headers() { diff --git a/actix-http/src/header/common/content_disposition.rs b/src/http/header/content_disposition.rs similarity index 99% rename from actix-http/src/header/common/content_disposition.rs rename to src/http/header/content_disposition.rs index 6076d033c..3dea0997b 100644 --- a/actix-http/src/header/common/content_disposition.rs +++ b/src/http/header/content_disposition.rs @@ -10,7 +10,8 @@ use once_cell::sync::Lazy; use regex::Regex; use std::fmt::{self, Write}; -use crate::header::{self, ExtendedValue, Header, IntoHeaderValue, Writer}; +use crate::http::header; +use super::{ExtendedValue, Header, IntoHeaderValue, Writer}; /// Split at the index of the first `needle` if it exists or at the end. fn split_once(haystack: &str, needle: char) -> (&str, &str) { @@ -63,7 +64,7 @@ impl<'a> From<&'a str> for DispositionType { /// /// # Examples /// ``` -/// use actix_http::http::header::DispositionParam; +/// use actix_web::http::header::DispositionParam; /// /// let param = DispositionParam::Filename(String::from("sample.txt")); /// assert!(param.is_filename()); @@ -240,7 +241,7 @@ impl DispositionParam { /// # Example /// /// ``` -/// use actix_http::http::header::{ +/// use actix_web::http::header::{ /// Charset, ContentDisposition, DispositionParam, DispositionType, /// ExtendedValue, /// }; @@ -554,8 +555,8 @@ impl fmt::Display for ContentDisposition { #[cfg(test)] mod tests { use super::{ContentDisposition, DispositionParam, DispositionType}; - use crate::header::shared::Charset; - use crate::header::{ExtendedValue, HeaderValue}; + use crate::http::header::Charset; + use crate::http::header::{ExtendedValue, HeaderValue}; #[test] fn test_from_raw_basic() { diff --git a/actix-http/src/header/common/content_language.rs b/src/http/header/content_language.rs similarity index 77% rename from actix-http/src/header/common/content_language.rs rename to src/http/header/content_language.rs index e9be67a1b..5dd8f72a5 100644 --- a/actix-http/src/header/common/content_language.rs +++ b/src/http/header/content_language.rs @@ -1,7 +1,7 @@ -use crate::header::{QualityItem, CONTENT_LANGUAGE}; +use super::{QualityItem, CONTENT_LANGUAGE}; use language_tags::LanguageTag; -header! { +crate::header! { /// `Content-Language` header, defined in /// [RFC7231](https://tools.ietf.org/html/rfc7231#section-3.1.3.2) /// @@ -25,10 +25,10 @@ header! { /// /// ``` /// use language_tags::langtag; - /// use actix_http::Response; - /// use actix_http::http::header::{ContentLanguage, qitem}; + /// use actix_web::HttpResponse; + /// use actix_web::http::header::{ContentLanguage, qitem}; /// - /// let mut builder = Response::Ok(); + /// let mut builder = HttpResponse::Ok(); /// builder.insert_header( /// ContentLanguage(vec![ /// qitem(langtag!(en)), @@ -38,10 +38,10 @@ header! { /// /// ``` /// use language_tags::langtag; - /// use actix_http::Response; - /// use actix_http::http::header::{ContentLanguage, qitem}; + /// use actix_web::HttpResponse; + /// use actix_web::http::header::{ContentLanguage, qitem}; /// - /// let mut builder = Response::Ok(); + /// let mut builder = HttpResponse::Ok(); /// builder.insert_header( /// ContentLanguage(vec![ /// qitem(langtag!(da)), diff --git a/actix-http/src/header/common/content_range.rs b/src/http/header/content_range.rs similarity index 99% rename from actix-http/src/header/common/content_range.rs rename to src/http/header/content_range.rs index 8b7552377..dfcf24251 100644 --- a/actix-http/src/header/common/content_range.rs +++ b/src/http/header/content_range.rs @@ -2,11 +2,11 @@ use std::fmt::{self, Display, Write}; use std::str::FromStr; use crate::error::ParseError; -use crate::header::{ +use super::{ HeaderValue, IntoHeaderValue, InvalidHeaderValue, Writer, CONTENT_RANGE, }; -header! { +crate::header! { /// `Content-Range` header, defined in /// [RFC7233](http://tools.ietf.org/html/rfc7233#section-4.2) (ContentRange, CONTENT_RANGE) => [ContentRangeSpec] diff --git a/actix-http/src/header/common/content_type.rs b/src/http/header/content_type.rs similarity index 91% rename from actix-http/src/header/common/content_type.rs rename to src/http/header/content_type.rs index ac5c7e5b8..a85e64ba9 100644 --- a/actix-http/src/header/common/content_type.rs +++ b/src/http/header/content_type.rs @@ -1,7 +1,7 @@ -use crate::header::CONTENT_TYPE; +use super::CONTENT_TYPE; use mime::Mime; -header! { +crate::header! { /// `Content-Type` header, defined in /// [RFC7231](http://tools.ietf.org/html/rfc7231#section-3.1.1.5) /// @@ -31,20 +31,20 @@ header! { /// # Examples /// /// ``` - /// use actix_http::Response; - /// use actix_http::http::header::ContentType; + /// use actix_web::HttpResponse; + /// use actix_web::http::header::ContentType; /// - /// let mut builder = Response::Ok(); + /// let mut builder = HttpResponse::Ok(); /// builder.insert_header( /// ContentType::json() /// ); /// ``` /// /// ``` - /// use actix_http::Response; - /// use actix_http::http::header::ContentType; + /// use actix_web::HttpResponse; + /// use actix_web::http::header::ContentType; /// - /// let mut builder = Response::Ok(); + /// let mut builder = HttpResponse::Ok(); /// builder.insert_header( /// ContentType(mime::TEXT_HTML) /// ); diff --git a/actix-http/src/header/common/date.rs b/src/http/header/date.rs similarity index 83% rename from actix-http/src/header/common/date.rs rename to src/http/header/date.rs index e5ace95e6..faceefb4f 100644 --- a/actix-http/src/header/common/date.rs +++ b/src/http/header/date.rs @@ -1,7 +1,7 @@ -use crate::header::{HttpDate, DATE}; +use super::{HttpDate, DATE}; use std::time::SystemTime; -header! { +crate::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 @@ -21,10 +21,10 @@ header! { /// /// ``` /// use std::time::SystemTime; - /// use actix_http::Response; - /// use actix_http::http::header::Date; + /// use actix_web::HttpResponse; + /// use actix_web::http::header::Date; /// - /// let mut builder = Response::Ok(); + /// let mut builder = HttpResponse::Ok(); /// builder.insert_header( /// Date(SystemTime::now().into()) /// ); diff --git a/actix-http/src/header/shared/encoding.rs b/src/http/header/encoding.rs similarity index 100% rename from actix-http/src/header/shared/encoding.rs rename to src/http/header/encoding.rs diff --git a/actix-http/src/header/shared/entity.rs b/src/http/header/entity.rs similarity index 99% rename from actix-http/src/header/shared/entity.rs rename to src/http/header/entity.rs index 2505216f2..5073ed692 100644 --- a/actix-http/src/header/shared/entity.rs +++ b/src/http/header/entity.rs @@ -1,7 +1,7 @@ use std::fmt::{self, Display, Write}; use std::str::FromStr; -use crate::header::{HeaderValue, IntoHeaderValue, InvalidHeaderValue, Writer}; +use super::{HeaderValue, IntoHeaderValue, InvalidHeaderValue, Writer}; /// check that each char in the slice is either: /// 1. `%x21`, or diff --git a/actix-http/src/header/common/etag.rs b/src/http/header/etag.rs similarity index 90% rename from actix-http/src/header/common/etag.rs rename to src/http/header/etag.rs index 4c1e8d262..8972564d0 100644 --- a/actix-http/src/header/common/etag.rs +++ b/src/http/header/etag.rs @@ -1,6 +1,6 @@ -use crate::header::{EntityTag, ETAG}; +use super::{EntityTag, ETAG}; -header! { +crate::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 @@ -28,20 +28,20 @@ header! { /// # Examples /// /// ``` - /// use actix_http::Response; - /// use actix_http::http::header::{ETag, EntityTag}; + /// use actix_web::HttpResponse; + /// use actix_web::http::header::{ETag, EntityTag}; /// - /// let mut builder = Response::Ok(); + /// let mut builder = HttpResponse::Ok(); /// builder.insert_header( /// ETag(EntityTag::new(false, "xyzzy".to_owned())) /// ); /// ``` /// /// ``` - /// use actix_http::Response; - /// use actix_http::http::header::{ETag, EntityTag}; + /// use actix_web::HttpResponse; + /// use actix_web::http::header::{ETag, EntityTag}; /// - /// let mut builder = Response::Ok(); + /// let mut builder = HttpResponse::Ok(); /// builder.insert_header( /// ETag(EntityTag::new(true, "xyzzy".to_owned())) /// ); diff --git a/actix-http/src/header/common/expires.rs b/src/http/header/expires.rs similarity index 84% rename from actix-http/src/header/common/expires.rs rename to src/http/header/expires.rs index 79563955d..1c306cae0 100644 --- a/actix-http/src/header/common/expires.rs +++ b/src/http/header/expires.rs @@ -1,6 +1,6 @@ -use crate::header::{HttpDate, EXPIRES}; +use super::{HttpDate, EXPIRES}; -header! { +crate::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 @@ -23,10 +23,10 @@ header! { /// /// ``` /// use std::time::{SystemTime, Duration}; - /// use actix_http::Response; - /// use actix_http::http::header::Expires; + /// use actix_web::HttpResponse; + /// use actix_web::http::header::Expires; /// - /// let mut builder = Response::Ok(); + /// let mut builder = HttpResponse::Ok(); /// let expiration = SystemTime::now() + Duration::from_secs(60 * 60 * 24); /// builder.insert_header( /// Expires(expiration.into()) diff --git a/actix-http/src/header/common/if_match.rs b/src/http/header/if_match.rs similarity index 86% rename from actix-http/src/header/common/if_match.rs rename to src/http/header/if_match.rs index db255e91a..80699e39c 100644 --- a/actix-http/src/header/common/if_match.rs +++ b/src/http/header/if_match.rs @@ -1,6 +1,6 @@ -use crate::header::{EntityTag, IF_MATCH}; +use super::{EntityTag, IF_MATCH}; -header! { +crate::header! { /// `If-Match` header, defined in /// [RFC7232](https://tools.ietf.org/html/rfc7232#section-3.1) /// @@ -30,18 +30,18 @@ header! { /// # Examples /// /// ``` - /// use actix_http::Response; - /// use actix_http::http::header::IfMatch; + /// use actix_web::HttpResponse; + /// use actix_web::http::header::IfMatch; /// - /// let mut builder = Response::Ok(); + /// let mut builder = HttpResponse::Ok(); /// builder.insert_header(IfMatch::Any); /// ``` /// /// ``` - /// use actix_http::Response; - /// use actix_http::http::header::{IfMatch, EntityTag}; + /// use actix_web::HttpResponse; + /// use actix_web::http::header::{IfMatch, EntityTag}; /// - /// let mut builder = Response::Ok(); + /// let mut builder = HttpResponse::Ok(); /// builder.insert_header( /// IfMatch::Items(vec![ /// EntityTag::new(false, "xyzzy".to_owned()), diff --git a/actix-http/src/header/common/if_modified_since.rs b/src/http/header/if_modified_since.rs similarity index 84% rename from actix-http/src/header/common/if_modified_since.rs rename to src/http/header/if_modified_since.rs index 99c7e441d..d777e0c5c 100644 --- a/actix-http/src/header/common/if_modified_since.rs +++ b/src/http/header/if_modified_since.rs @@ -1,6 +1,6 @@ -use crate::header::{HttpDate, IF_MODIFIED_SINCE}; +use super::{HttpDate, IF_MODIFIED_SINCE}; -header! { +crate::header! { /// `If-Modified-Since` header, defined in /// [RFC7232](http://tools.ietf.org/html/rfc7232#section-3.3) /// @@ -23,10 +23,10 @@ header! { /// /// ``` /// use std::time::{SystemTime, Duration}; - /// use actix_http::Response; - /// use actix_http::http::header::IfModifiedSince; + /// use actix_web::HttpResponse; + /// use actix_web::http::header::IfModifiedSince; /// - /// let mut builder = Response::Ok(); + /// let mut builder = HttpResponse::Ok(); /// let modified = SystemTime::now() - Duration::from_secs(60 * 60 * 24); /// builder.insert_header( /// IfModifiedSince(modified.into()) diff --git a/actix-http/src/header/common/if_none_match.rs b/src/http/header/if_none_match.rs similarity index 86% rename from actix-http/src/header/common/if_none_match.rs rename to src/http/header/if_none_match.rs index 464caf1ae..a5c06b374 100644 --- a/actix-http/src/header/common/if_none_match.rs +++ b/src/http/header/if_none_match.rs @@ -1,6 +1,6 @@ -use crate::header::{EntityTag, IF_NONE_MATCH}; +use super::{EntityTag, IF_NONE_MATCH}; -header! { +crate::header! { /// `If-None-Match` header, defined in /// [RFC7232](https://tools.ietf.org/html/rfc7232#section-3.2) /// @@ -32,18 +32,18 @@ header! { /// # Examples /// /// ``` - /// use actix_http::Response; - /// use actix_http::http::header::IfNoneMatch; + /// use actix_web::HttpResponse; + /// use actix_web::http::header::IfNoneMatch; /// - /// let mut builder = Response::Ok(); + /// let mut builder = HttpResponse::Ok(); /// builder.insert_header(IfNoneMatch::Any); /// ``` /// /// ``` - /// use actix_http::Response; - /// use actix_http::http::header::{IfNoneMatch, EntityTag}; + /// use actix_web::HttpResponse; + /// use actix_web::http::header::{IfNoneMatch, EntityTag}; /// - /// let mut builder = Response::Ok(); + /// let mut builder = HttpResponse::Ok(); /// builder.insert_header( /// IfNoneMatch::Items(vec![ /// EntityTag::new(false, "xyzzy".to_owned()), @@ -66,8 +66,8 @@ header! { #[cfg(test)] mod tests { use super::IfNoneMatch; - use crate::header::{EntityTag, Header, IF_NONE_MATCH}; - use crate::test::TestRequest; + use crate::http::header::{EntityTag, Header, IF_NONE_MATCH}; + use actix_http::test::TestRequest; #[test] fn test_if_none_match() { diff --git a/actix-http/src/header/common/if_range.rs b/src/http/header/if_range.rs similarity index 89% rename from actix-http/src/header/common/if_range.rs rename to src/http/header/if_range.rs index 0a5749505..f34332f22 100644 --- a/actix-http/src/header/common/if_range.rs +++ b/src/http/header/if_range.rs @@ -1,8 +1,9 @@ use std::fmt::{self, Display, Write}; +use crate::http::header; use crate::error::ParseError; -use crate::header::{ - self, from_one_raw_str, EntityTag, Header, HeaderName, HeaderValue, HttpDate, +use super::{ + from_one_raw_str, EntityTag, Header, HeaderName, HeaderValue, HttpDate, IntoHeaderValue, InvalidHeaderValue, Writer, }; use crate::HttpMessage; @@ -36,10 +37,10 @@ use crate::HttpMessage; /// # Examples /// /// ``` -/// use actix_http::Response; -/// use actix_http::http::header::{EntityTag, IfRange}; +/// use actix_web::HttpResponse; +/// use actix_web::http::header::{EntityTag, IfRange}; /// -/// let mut builder = Response::Ok(); +/// let mut builder = HttpResponse::Ok(); /// builder.insert_header( /// IfRange::EntityTag( /// EntityTag::new(false, "abc".to_owned()) @@ -49,9 +50,9 @@ use crate::HttpMessage; /// /// ``` /// use std::time::{Duration, SystemTime}; -/// use actix_http::{http::header::IfRange, Response}; +/// use actix_web::{http::header::IfRange, HttpResponse}; /// -/// let mut builder = Response::Ok(); +/// let mut builder = HttpResponse::Ok(); /// let fetched = SystemTime::now() - Duration::from_secs(60 * 60 * 24); /// builder.insert_header( /// IfRange::Date(fetched.into()) @@ -111,7 +112,7 @@ impl IntoHeaderValue for IfRange { #[cfg(test)] mod test_if_range { use super::IfRange as HeaderField; - use crate::header::*; + use crate::http::header::*; use std::str; test_header!(test1, vec![b"Sat, 29 Oct 1994 19:43:31 GMT"]); diff --git a/actix-http/src/header/common/if_unmodified_since.rs b/src/http/header/if_unmodified_since.rs similarity index 85% rename from actix-http/src/header/common/if_unmodified_since.rs rename to src/http/header/if_unmodified_since.rs index 1c2b4af78..8887982aa 100644 --- a/actix-http/src/header/common/if_unmodified_since.rs +++ b/src/http/header/if_unmodified_since.rs @@ -1,6 +1,6 @@ -use crate::header::{HttpDate, IF_UNMODIFIED_SINCE}; +use super::{HttpDate, IF_UNMODIFIED_SINCE}; -header! { +crate::header! { /// `If-Unmodified-Since` header, defined in /// [RFC7232](http://tools.ietf.org/html/rfc7232#section-3.4) /// @@ -24,10 +24,10 @@ header! { /// /// ``` /// use std::time::{SystemTime, Duration}; - /// use actix_http::Response; - /// use actix_http::http::header::IfUnmodifiedSince; + /// use actix_web::HttpResponse; + /// use actix_web::http::header::IfUnmodifiedSince; /// - /// let mut builder = Response::Ok(); + /// let mut builder = HttpResponse::Ok(); /// let modified = SystemTime::now() - Duration::from_secs(60 * 60 * 24); /// builder.insert_header( /// IfUnmodifiedSince(modified.into()) diff --git a/actix-http/src/header/common/last_modified.rs b/src/http/header/last_modified.rs similarity index 84% rename from actix-http/src/header/common/last_modified.rs rename to src/http/header/last_modified.rs index 65608d846..9ed6fcf69 100644 --- a/actix-http/src/header/common/last_modified.rs +++ b/src/http/header/last_modified.rs @@ -1,6 +1,6 @@ -use crate::header::{HttpDate, LAST_MODIFIED}; +use super::{HttpDate, LAST_MODIFIED}; -header! { +crate::header! { /// `Last-Modified` header, defined in /// [RFC7232](http://tools.ietf.org/html/rfc7232#section-2.2) /// @@ -23,10 +23,10 @@ header! { /// /// ``` /// use std::time::{SystemTime, Duration}; - /// use actix_http::Response; - /// use actix_http::http::header::LastModified; + /// use actix_web::HttpResponse; + /// use actix_web::http::header::LastModified; /// - /// let mut builder = Response::Ok(); + /// let mut builder = HttpResponse::Ok(); /// let modified = SystemTime::now() - Duration::from_secs(60 * 60 * 24); /// builder.insert_header( /// LastModified(modified.into()) diff --git a/actix-http/src/header/common/mod.rs b/src/http/header/mod.rs similarity index 92% rename from actix-http/src/header/common/mod.rs rename to src/http/header/mod.rs index 90e0a855e..a1c405344 100644 --- a/actix-http/src/header/common/mod.rs +++ b/src/http/header/mod.rs @@ -7,6 +7,10 @@ //! is used, such as `ContentType(pub Mime)`. #![cfg_attr(rustfmt, rustfmt_skip)] +use std::fmt; +use bytes::{BytesMut, Bytes}; + +pub use actix_http::http::header::*; pub use self::accept_charset::AcceptCharset; //pub use self::accept_encoding::AcceptEncoding; pub use self::accept::Accept; @@ -18,7 +22,6 @@ pub use self::content_disposition::{ }; pub use self::content_language::ContentLanguage; pub use self::content_range::{ContentRange, ContentRangeSpec}; -pub use self::content_encoding::{ContentEncoding}; pub use self::content_type::ContentType; pub use self::date::Date; pub use self::etag::ETag; @@ -29,7 +32,39 @@ 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::encoding::Encoding; +pub use self::entity::EntityTag; //pub use self::range::{Range, ByteRangeSpec}; +pub(crate) use actix_http::http::header::{fmt_comma_delimited, from_comma_delimited, from_one_raw_str}; + +#[derive(Debug, Default)] +struct Writer { + buf: BytesMut, +} + +impl Writer { + pub fn new() -> Writer { + Writer::default() + } + + pub fn take(&mut self) -> Bytes { + self.buf.split().freeze() + } +} + +impl fmt::Write for Writer { + #[inline] + fn write_str(&mut self, s: &str) -> fmt::Result { + self.buf.extend_from_slice(s.as_bytes()); + Ok(()) + } + + #[inline] + fn write_fmt(&mut self, args: fmt::Arguments<'_>) -> fmt::Result { + fmt::write(self, args) + } +} + #[doc(hidden)] #[macro_export] @@ -61,9 +96,9 @@ macro_rules! __hyper__tm { #[cfg(test)] mod $tm{ use std::str; - use http::Method; + use actix_http::http::Method; use mime::*; - use $crate::header::*; + use $crate::http::header::*; use super::$id as HeaderField; $($tf)* } @@ -77,8 +112,7 @@ macro_rules! test_header { ($id:ident, $raw:expr) => { #[test] fn $id() { - use super::*; - use $crate::test; + use actix_http::test; let raw = $raw; let a: Vec> = raw.iter().map(|x| x.to_vec()).collect(); @@ -106,7 +140,7 @@ macro_rules! test_header { ($id:ident, $raw:expr, $typed:expr) => { #[test] fn $id() { - use $crate::test; + use actix_http::test; let a: Vec> = $raw.iter().map(|x| x.to_vec()).collect(); let mut req = test::TestRequest::default(); @@ -134,6 +168,7 @@ macro_rules! test_header { }; } +#[doc(hidden)] #[macro_export] macro_rules! header { // $a:meta: Attributes associated with the header item (usually docs) @@ -341,7 +376,6 @@ mod allow; mod cache_control; mod content_disposition; mod content_language; -mod content_encoding; mod content_range; mod content_type; mod date; @@ -353,3 +387,5 @@ mod if_none_match; mod if_range; mod if_unmodified_since; mod last_modified; +mod encoding; +mod entity; diff --git a/actix-http/src/header/common/range.rs b/src/http/header/range.rs similarity index 99% rename from actix-http/src/header/common/range.rs rename to src/http/header/range.rs index f9e203bb2..a9b40b403 100644 --- a/actix-http/src/header/common/range.rs +++ b/src/http/header/range.rs @@ -1,8 +1,8 @@ use std::fmt::{self, Display}; use std::str::FromStr; -use header::parsing::from_one_raw_str; -use header::{Header, Raw}; +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) /// diff --git a/src/http/mod.rs b/src/http/mod.rs new file mode 100644 index 000000000..fa28a5fa9 --- /dev/null +++ b/src/http/mod.rs @@ -0,0 +1,2 @@ +pub mod header; +pub use actix_http::http::*; diff --git a/src/lib.rs b/src/lib.rs index 7a6498546..38d9d5734 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -84,6 +84,7 @@ pub mod error; mod extract; pub mod guard; mod handler; +pub mod http; mod info; pub mod middleware; mod request; @@ -102,7 +103,7 @@ pub mod web; #[cfg(feature = "cookies")] pub use actix_http::cookie; pub use actix_http::Response as HttpResponse; -pub use actix_http::{body, http, Error, HttpMessage, ResponseError, Result}; +pub use actix_http::{body, Error, HttpMessage, ResponseError, Result}; pub use actix_rt as rt; pub use actix_web_codegen::*; diff --git a/src/test.rs b/src/test.rs index 8ba019445..08c80df40 100644 --- a/src/test.rs +++ b/src/test.rs @@ -8,8 +8,7 @@ use std::{fmt, net, thread, time}; use actix_codec::{AsyncRead, AsyncWrite, Framed}; #[cfg(feature = "cookies")] use actix_http::cookie::Cookie; -use actix_http::http::header::{ContentType, HeaderMap, IntoHeaderPair}; -use actix_http::http::{Method, StatusCode, Uri, Version}; +use actix_http::http::{HeaderMap, Method, StatusCode, Uri, Version}; use actix_http::test::TestRequest as HttpTestRequest; use actix_http::{ws, Extensions, HttpService, Request}; use actix_router::{Path, ResourceDef, Url}; @@ -31,6 +30,7 @@ use crate::app_service::AppInitServiceState; use crate::config::AppConfig; use crate::data::Data; use crate::dev::{Body, MessageBody, Payload, Server}; +use crate::http::header::{ContentType, IntoHeaderPair}; use crate::rmap::ResourceMap; use crate::service::{ServiceRequest, ServiceResponse}; use crate::{Error, HttpRequest, HttpResponse}; @@ -162,7 +162,7 @@ where let mut resp = app .call(req) .await - .unwrap_or_else(|_| panic!("read_response failed at application call")); + .unwrap_or_else(|e| panic!("read_response failed at application call: {}", e)); let mut body = resp.take_body(); let mut bytes = BytesMut::new(); @@ -254,8 +254,12 @@ where { let body = read_body(res).await; - serde_json::from_slice(&body) - .unwrap_or_else(|e| panic!("read_response_json failed during deserialization: {}", e)) + 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 @@ -311,8 +315,12 @@ where { let body = read_response(app, req).await; - serde_json::from_slice(&body) - .unwrap_or_else(|_| panic!("read_response_json failed during deserialization")) + serde_json::from_slice(&body).unwrap_or_else(|_| { + panic!( + "read_response_json failed during deserialization of body: {:?}", + body + ) + }) } /// Test `Request` builder. diff --git a/src/types/header.rs b/src/types/header.rs new file mode 100644 index 000000000..1f8be707a --- /dev/null +++ b/src/types/header.rs @@ -0,0 +1,112 @@ +//! For header extractor helper documentation, see [`Header`](crate::types::Header). + +use std::{fmt, ops}; + +use actix_utils::future::{err, ok, Ready}; + +use crate::{ + dev::Payload, error::ParseError, extract::FromRequest, http::header::Header as ParseHeader, + HttpRequest, +}; + +/// Extract typed headers from the request. +/// +/// To extract a header, the inner type `T` must implement the +/// [`Header`](crate::http::header::Header) trait. +/// +/// # Examples +/// ``` +/// use actix_web::{get, web, http::header}; +/// +/// #[get("/")] +/// async fn index(date: web::Header) -> String { +/// format!("Request was sent at {}", date.to_string()) +/// } +/// ``` +#[derive(Clone, PartialEq, Eq, PartialOrd, Ord)] +pub struct Header(pub T); + +impl Header { + /// Unwrap into the inner `T` value. + pub fn into_inner(self) -> T { + self.0 + } +} + +impl ops::Deref for Header { + type Target = T; + + fn deref(&self) -> &T { + &self.0 + } +} + +impl ops::DerefMut for Header { + fn deref_mut(&mut self) -> &mut T { + &mut self.0 + } +} + +impl fmt::Debug for Header +where + T: fmt::Debug, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "Header: {:?}", self.0) + } +} + +impl fmt::Display for Header +where + T: fmt::Display, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Display::fmt(&self.0, f) + } +} + +impl FromRequest for Header +where + T: ParseHeader, +{ + type Error = ParseError; + type Future = Ready>; + type Config = (); + + #[inline] + fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future { + match ParseHeader::parse(req) { + Ok(header) => ok(Header(header)), + Err(e) => err(e), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::http::{header, Method}; + use crate::test::TestRequest; + + #[actix_rt::test] + async fn test_header_extract() { + let (req, mut pl) = TestRequest::default() + .insert_header((header::CONTENT_TYPE, mime::APPLICATION_JSON)) + .insert_header((header::ALLOW, header::Allow(vec![Method::GET]))) + .to_http_parts(); + + let s = Header::::from_request(&req, &mut pl) + .await + .unwrap(); + assert_eq!(s.into_inner().0, mime::APPLICATION_JSON); + + let s = Header::::from_request(&req, &mut pl) + .await + .unwrap(); + assert_eq!(s.into_inner().0, vec![Method::GET]); + + assert!(Header::::from_request(&req, &mut pl) + .await + .is_err()); + } +} diff --git a/src/types/mod.rs b/src/types/mod.rs index a062c351e..461d771eb 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -3,6 +3,7 @@ // TODO: review visibility mod either; pub(crate) mod form; +mod header; pub(crate) mod json; mod path; pub(crate) mod payload; @@ -11,6 +12,7 @@ pub(crate) mod readlines; pub use self::either::{Either, EitherExtractError}; pub use self::form::{Form, FormConfig}; +pub use self::header::Header; pub use self::json::{Json, JsonConfig}; pub use self::path::{Path, PathConfig}; pub use self::payload::{Payload, PayloadConfig}; From c54a0713de99ce0c437b4577e0752d3cfe3e077c Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Fri, 2 Apr 2021 08:26:59 +0100 Subject: [PATCH 134/339] migrate integration testing to new crate (#2112) --- CHANGES.md | 3 + Cargo.toml | 28 +- actix-files/Cargo.toml | 1 + actix-files/src/lib.rs | 6 +- actix-http-test/src/lib.rs | 20 +- actix-http/Cargo.toml | 2 +- actix-http/src/service.rs | 18 +- actix-test/CHANGES.md | 6 + actix-test/Cargo.toml | 35 ++ actix-test/LICENSE-APACHE | 1 + actix-test/LICENSE-MIT | 1 + actix-test/src/lib.rs | 472 +++++++++++++++++ actix-web-actors/Cargo.toml | 2 + actix-web-actors/tests/test_ws.rs | 6 +- actix-web-codegen/Cargo.toml | 4 +- actix-web-codegen/tests/test_macro.rs | 10 +- .../tests/trybuild/docstring-ok.rs | 6 +- .../trybuild/route-duplicate-method-fail.rs | 4 +- .../route-duplicate-method-fail.stderr | 6 +- .../trybuild/route-missing-method-fail.rs | 4 +- .../trybuild/route-missing-method-fail.stderr | 6 +- actix-web-codegen/tests/trybuild/route-ok.rs | 4 +- .../trybuild/route-unexpected-method-fail.rs | 4 +- .../route-unexpected-method-fail.stderr | 6 +- actix-web-codegen/tests/trybuild/simple.rs | 4 +- awc/Cargo.toml | 5 + {examples => awc/examples}/client.rs | 0 awc/src/middleware/redirect.rs | 7 +- awc/tests/test_client.rs | 47 +- benches/server.rs | 2 +- codecov.yml | 1 - src/config.rs | 13 +- src/data.rs | 55 +- src/lib.rs | 5 - src/middleware/compat.rs | 4 +- src/request.rs | 12 + src/server.rs | 15 +- src/service.rs | 6 + src/test.rs | 481 ++---------------- tests/test_httpserver.rs | 6 +- tests/test_server.rs | 115 +++-- 41 files changed, 789 insertions(+), 644 deletions(-) create mode 100644 actix-test/CHANGES.md create mode 100644 actix-test/Cargo.toml create mode 120000 actix-test/LICENSE-APACHE create mode 120000 actix-test/LICENSE-MIT create mode 100644 actix-test/src/lib.rs rename {examples => awc/examples}/client.rs (100%) diff --git a/CHANGES.md b/CHANGES.md index 8e7b22f58..5442be2e0 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -15,11 +15,14 @@ ### 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 diff --git a/Cargo.toml b/Cargo.toml index 420e76405..190b645d4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,25 +36,26 @@ members = [ "actix-web-actors", "actix-web-codegen", "actix-http-test", + "actix-test", ] [features] default = ["compress", "cookies"] # content-encoding support -compress = ["actix-http/compress", "awc/compress"] +compress = ["actix-http/compress"] # support for cookies -cookies = ["actix-http/cookies", "awc/cookies"] +cookies = ["actix-http/cookies"] # secure cookies feature secure-cookies = ["actix-http/secure-cookies"] # openssl -openssl = ["tls-openssl", "actix-tls/accept", "actix-tls/openssl", "awc/openssl"] +openssl = ["actix-http/openssl", "actix-tls/accept", "actix-tls/openssl"] # rustls -rustls = ["tls-rustls", "actix-tls/accept", "actix-tls/rustls", "awc/rustls"] +rustls = ["actix-http/rustls", "actix-tls/accept", "actix-tls/rustls"] [[example]] name = "basic" @@ -72,10 +73,6 @@ required-features = ["compress", "cookies"] name = "on_connect" required-features = [] -[[example]] -name = "client" -required-features = ["rustls"] - [dependencies] actix-codec = "0.4.0-beta.1" actix-macros = "0.2.0" @@ -88,7 +85,6 @@ actix-tls = { version = "3.0.0-beta.5", default-features = false, optional = tru actix-web-codegen = "0.5.0-beta.2" actix-http = "3.0.0-beta.4" -awc = { version = "3.0.0-beta.3", default-features = false } ahash = "0.7" bytes = "1" @@ -109,11 +105,12 @@ serde_urlencoded = "0.7" smallvec = "1.6" socket2 = "0.4.0" time = { version = "0.2.23", default-features = false, features = ["std"] } -tls-openssl = { package = "openssl", version = "0.10.9", optional = true } -tls-rustls = { package = "rustls", version = "0.19.0", optional = true } url = "2.1" [dev-dependencies] +actix-test = { version = "0.0.1", features = ["openssl", "rustls"] } +awc = { version = "3.0.0-beta.3", features = ["openssl"] } + brotli2 = "0.3.2" criterion = "0.3" env_logger = "0.8" @@ -121,6 +118,8 @@ flate2 = "1.0.13" 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" } [profile.release] lto = true @@ -128,13 +127,14 @@ opt-level = 3 codegen-units = 1 [patch.crates-io] -actix-web = { path = "." } +actix-files = { path = "actix-files" } actix-http = { path = "actix-http" } actix-http-test = { path = "actix-http-test" } +actix-multipart = { path = "actix-multipart" } +actix-test = { path = "actix-test" } +actix-web = { path = "." } actix-web-actors = { path = "actix-web-actors" } actix-web-codegen = { path = "actix-web-codegen" } -actix-multipart = { path = "actix-multipart" } -actix-files = { path = "actix-files" } awc = { path = "awc" } [[bench]] diff --git a/actix-files/Cargo.toml b/actix-files/Cargo.toml index 0cc02c6bd..6a60397a6 100644 --- a/actix-files/Cargo.toml +++ b/actix-files/Cargo.toml @@ -35,3 +35,4 @@ percent-encoding = "2.1" [dev-dependencies] actix-rt = "2.2" actix-web = "4.0.0-beta.4" +actix-test = "0.0.1" diff --git a/actix-files/src/lib.rs b/actix-files/src/lib.rs index f8583febe..24b903c04 100644 --- a/actix-files/src/lib.rs +++ b/actix-files/src/lib.rs @@ -413,7 +413,7 @@ mod tests { #[actix_rt::test] async fn test_named_file_content_range_headers() { - let srv = test::start(|| App::new().service(Files::new("/", "."))); + let srv = actix_test::start(|| App::new().service(Files::new("/", "."))); // Valid range header let response = srv @@ -438,7 +438,7 @@ mod tests { #[actix_rt::test] async fn test_named_file_content_length_headers() { - let srv = test::start(|| App::new().service(Files::new("/", "."))); + let srv = actix_test::start(|| App::new().service(Files::new("/", "."))); // Valid range header let response = srv @@ -477,7 +477,7 @@ mod tests { #[actix_rt::test] async fn test_head_content_length_headers() { - let srv = test::start(|| App::new().service(Files::new("/", "."))); + let srv = actix_test::start(|| App::new().service(Files::new("/", "."))); let response = srv.head("/tests/test.binary").send().await.unwrap(); diff --git a/actix-http-test/src/lib.rs b/actix-http-test/src/lib.rs index 9a5069c49..0f126c99a 100644 --- a/actix-http-test/src/lib.rs +++ b/actix-http-test/src/lib.rs @@ -117,16 +117,6 @@ pub async fn test_server_with_addr>( } } -/// Get first available unused address -pub fn unused_addr() -> net::SocketAddr { - let addr: net::SocketAddr = "127.0.0.1:0".parse().unwrap(); - let socket = Socket::new(Domain::IPV4, Type::STREAM, Some(Protocol::TCP)).unwrap(); - socket.bind(&addr.into()).unwrap(); - socket.set_reuse_address(true).unwrap(); - let tcp = net::TcpListener::from(socket); - tcp.local_addr().unwrap() -} - /// Test server controller pub struct TestServer { addr: net::SocketAddr, @@ -279,3 +269,13 @@ impl Drop for TestServer { self.stop() } } + +/// Get a localhost socket address with random, unused port. +pub fn unused_addr() -> net::SocketAddr { + let addr: net::SocketAddr = "127.0.0.1:0".parse().unwrap(); + let socket = Socket::new(Domain::IPV4, Type::STREAM, Some(Protocol::TCP)).unwrap(); + socket.bind(&addr.into()).unwrap(); + socket.set_reuse_address(true).unwrap(); + let tcp = net::TcpListener::from(socket); + tcp.local_addr().unwrap() +} diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index d9af75aa5..4857f083d 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -48,7 +48,7 @@ actix-service = "2.0.0-beta.4" actix-codec = "0.4.0-beta.1" actix-utils = "3.0.0-beta.4" actix-rt = "2.2" -actix-tls = "3.0.0-beta.5" +actix-tls = { version = "3.0.0-beta.5", features = ["accept", "connect"] } ahash = "0.7" base64 = "0.13" diff --git a/actix-http/src/service.rs b/actix-http/src/service.rs index fd97fb5ef..67a3ec42e 100644 --- a/actix-http/src/service.rs +++ b/actix-http/src/service.rs @@ -153,11 +153,14 @@ where S::InitError: fmt::Debug, S::Response: Into> + 'static, >::Future: 'static, + B: MessageBody + 'static, + X: ServiceFactory, X::Future: 'static, X::Error: Into, X::InitError: fmt::Debug, + U: ServiceFactory< (Request, Framed), Config = (), @@ -187,11 +190,12 @@ where #[cfg(feature = "openssl")] mod openssl { - use super::*; use actix_service::ServiceFactoryExt; use actix_tls::accept::openssl::{Acceptor, SslAcceptor, SslError, TlsStream}; use actix_tls::accept::TlsError; + use super::*; + impl HttpService, S, B, X, U> where S: ServiceFactory, @@ -200,11 +204,14 @@ mod openssl { S::InitError: fmt::Debug, S::Response: Into> + 'static, >::Future: 'static, + B: MessageBody + 'static, + X: ServiceFactory, X::Future: 'static, X::Error: Into, X::InitError: fmt::Debug, + U: ServiceFactory< (Request, Framed, h1::Codec>), Config = (), @@ -266,11 +273,14 @@ mod rustls { S::InitError: fmt::Debug, S::Response: Into> + 'static, >::Future: 'static, + B: MessageBody + 'static, + X: ServiceFactory, X::Future: 'static, X::Error: Into, X::InitError: fmt::Debug, + U: ServiceFactory< (Request, Framed, h1::Codec>), Config = (), @@ -280,7 +290,7 @@ mod rustls { U::Error: fmt::Display + Into, U::InitError: fmt::Debug, { - /// Create openssl based service + /// Create rustls based service pub fn rustls( self, mut config: ServerConfig, @@ -321,17 +331,21 @@ impl ServiceFactory<(T, Protocol, Option)> for HttpService where T: AsyncRead + AsyncWrite + Unpin + 'static, + S: ServiceFactory, S::Future: 'static, S::Error: Into + 'static, S::InitError: fmt::Debug, S::Response: Into> + 'static, >::Future: 'static, + B: MessageBody + 'static, + X: ServiceFactory, X::Future: 'static, X::Error: Into, X::InitError: fmt::Debug, + U: ServiceFactory<(Request, Framed), Config = (), Response = ()>, U::Future: 'static, U::Error: fmt::Display + Into, diff --git a/actix-test/CHANGES.md b/actix-test/CHANGES.md new file mode 100644 index 000000000..318f08f55 --- /dev/null +++ b/actix-test/CHANGES.md @@ -0,0 +1,6 @@ +# Changes + +## Unreleased - 2021-xx-xx +* Move integration testing structs from `actix-web`. [#???] + +[#???]: https://github.com/actix/actix-web/pull/??? diff --git a/actix-test/Cargo.toml b/actix-test/Cargo.toml new file mode 100644 index 000000000..884ec229c --- /dev/null +++ b/actix-test/Cargo.toml @@ -0,0 +1,35 @@ +[package] +name = "actix-test" +version = "0.0.1" +authors = ["Rob Ede "] +edition = "2018" +description = "Integration testing tools for Actix Web applications" +license = "MIT OR Apache-2.0" + +[features] +default = [] + +# rustls +rustls = ["tls-rustls", "actix-http/rustls"] + +# openssl +openssl = ["tls-openssl", "actix-http/openssl"] + +[dependencies] +actix-codec = "0.4.0-beta.1" +actix-http = { version = "3.0.0-beta.4", features = ["cookies"] } +actix-http-test = { version = "3.0.0-beta.3", features = [] } +actix-service = "2.0.0-beta.4" +actix-utils = "3.0.0-beta.2" +actix-web = { version = "4.0.0-beta.4", default-features = false, features = ["cookies"] } +actix-rt = "2.1" +awc = { version = "3.0.0-beta.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 = [] } +log = "0.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 } diff --git a/actix-test/LICENSE-APACHE b/actix-test/LICENSE-APACHE new file mode 120000 index 000000000..965b606f3 --- /dev/null +++ b/actix-test/LICENSE-APACHE @@ -0,0 +1 @@ +../LICENSE-APACHE \ No newline at end of file diff --git a/actix-test/LICENSE-MIT b/actix-test/LICENSE-MIT new file mode 120000 index 000000000..76219eb72 --- /dev/null +++ b/actix-test/LICENSE-MIT @@ -0,0 +1 @@ +../LICENSE-MIT \ No newline at end of file diff --git a/actix-test/src/lib.rs b/actix-test/src/lib.rs new file mode 100644 index 000000000..bd86c27ad --- /dev/null +++ b/actix-test/src/lib.rs @@ -0,0 +1,472 @@ +//! Integration testing tools for Actix Web applications. +//! +//! The main integration testing tool is [`TestServer`]. It spawns a real HTTP server on an +//! unused port and provides methods that use a real HTTP client. Therefore, it is much closer to +//! real-world cases than using `init_service`, which skips HTTP encoding and decoding. +//! +//! # Examples +//! ``` +//! use actix_web::{get, web, test, App, HttpResponse, Error, Responder}; +//! +//! #[get("/")] +//! async fn my_handler() -> Result { +//! Ok(HttpResponse::Ok()) +//! } +//! +//! #[actix_rt::test] +//! async fn test_example() { +//! let srv = actix_test::start(|| +//! App::new().service(my_handler) +//! ); +//! +//! let req = srv.get("/"); +//! let res = req.send().await.unwrap(); +//! +//! assert!(res.status().is_success()); +//! } +//! ``` + +#[cfg(feature = "openssl")] +extern crate tls_openssl as openssl; +#[cfg(feature = "rustls")] +extern crate tls_rustls as rustls; + +use std::{fmt, net, sync::mpsc, thread, time}; + +use actix_codec::{AsyncRead, AsyncWrite, Framed}; +pub use actix_http::test::TestBuffer; +use actix_http::{ + http::{HeaderMap, Method}, + ws, HttpService, Request, +}; +use actix_service::{map_config, IntoServiceFactory, ServiceFactory}; +use actix_web::{ + dev::{AppConfig, MessageBody, Server, Service}, + rt, web, Error, HttpResponse, +}; +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, +}; + +/// Start default [`TestServer`]. +/// +/// # Examples +/// ``` +/// use actix_web::{get, web, test, App, HttpResponse, Error, Responder}; +/// +/// #[get("/")] +/// async fn my_handler() -> Result { +/// Ok(HttpResponse::Ok()) +/// } +/// +/// #[actix_rt::test] +/// async fn test_example() { +/// let srv = actix_test::start(|| +/// App::new().service(my_handler) +/// ); +/// +/// let req = srv.get("/"); +/// let res = req.send().await.unwrap(); +/// +/// assert!(res.status().is_success()); +/// } +/// ``` +pub fn start(factory: F) -> TestServer +where + F: Fn() -> I + Send + Clone + 'static, + I: IntoServiceFactory, + S: ServiceFactory + 'static, + S::Error: Into + 'static, + S::InitError: fmt::Debug, + S::Response: Into> + 'static, + >::Future: 'static, + B: MessageBody + 'static, +{ + start_with(TestServerConfig::default(), factory) +} + +/// Start test server with custom configuration +/// +/// Check [`TestServerConfig`] docs for configuration options. +/// +/// # Examples +/// ``` +/// use actix_web::{get, web, test, App, HttpResponse, Error, Responder}; +/// +/// #[get("/")] +/// async fn my_handler() -> Result { +/// Ok(HttpResponse::Ok()) +/// } +/// +/// #[actix_rt::test] +/// async fn test_example() { +/// let srv = actix_test::start_with(actix_test::config().h1(), || +/// App::new().service(my_handler) +/// ); +/// +/// let req = srv.get("/"); +/// let res = req.send().await.unwrap(); +/// +/// assert!(res.status().is_success()); +/// } +/// ``` +pub fn start_with(cfg: TestServerConfig, factory: F) -> TestServer +where + F: Fn() -> I + Send + Clone + 'static, + I: IntoServiceFactory, + S: ServiceFactory + 'static, + S::Error: Into + 'static, + S::InitError: fmt::Debug, + S::Response: Into> + 'static, + >::Future: 'static, + B: MessageBody + 'static, +{ + let (tx, rx) = mpsc::channel(); + + let tls = match cfg.stream { + StreamType::Tcp => false, + #[cfg(feature = "openssl")] + StreamType::Openssl(_) => true, + #[cfg(feature = "rustls")] + StreamType::Rustls(_) => true, + }; + + // run server in separate 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(); + + 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(factory(), 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(factory(), 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(factory(), 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(factory(), 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(factory(), 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(factory(), 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(factory(), 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(factory(), 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(factory(), move |_| app_cfg.clone())) + .rustls(config.clone()) + }), + }, + } + .unwrap(); + + sys.block_on(async { + let srv = srv.run(); + tx.send((rt::System::current(), srv, local_addr)).unwrap(); + }); + + sys.run() + }); + + let (system, server, addr) = rx.recv().unwrap(); + + let client = { + let connector = { + #[cfg(feature = "openssl")] + { + 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(time::Duration::from_secs(0)) + .timeout(time::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)) + } + }; + + Client::builder().connector(connector).finish() + }; + + TestServer { + addr, + client, + system, + tls, + server, + } +} + +#[derive(Debug, Clone)] +enum HttpVer { + Http1, + Http2, + Both, +} + +#[derive(Clone)] +enum StreamType { + Tcp, + #[cfg(feature = "openssl")] + Openssl(openssl::ssl::SslAcceptor), + #[cfg(feature = "rustls")] + Rustls(rustls::ServerConfig), +} + +/// Create default test server config. +pub fn config() -> TestServerConfig { + TestServerConfig::default() +} + +#[derive(Clone)] +pub struct TestServerConfig { + tp: HttpVer, + stream: StreamType, + client_timeout: u64, +} + +impl Default for TestServerConfig { + fn default() -> Self { + TestServerConfig::new() + } +} + +impl TestServerConfig { + /// Create default server configuration + pub(crate) fn new() -> TestServerConfig { + TestServerConfig { + tp: HttpVer::Both, + stream: StreamType::Tcp, + client_timeout: 5000, + } + } + + /// Accept HTTP/1.1 only. + pub fn h1(mut self) -> Self { + self.tp = HttpVer::Http1; + self + } + + /// Accept HTTP/2 only. + pub fn h2(mut self) -> Self { + self.tp = HttpVer::Http2; + self + } + + /// Accept secure connections via OpenSSL. + #[cfg(feature = "openssl")] + pub fn openssl(mut self, acceptor: openssl::ssl::SslAcceptor) -> Self { + self.stream = StreamType::Openssl(acceptor); + self + } + + /// Accept secure connections via Rustls. + #[cfg(feature = "rustls")] + pub fn rustls(mut self, config: rustls::ServerConfig) -> Self { + self.stream = StreamType::Rustls(config); + self + } + + /// Set client timeout in milliseconds for first request. + pub fn client_timeout(mut self, val: u64) -> Self { + self.client_timeout = val; + self + } +} + +/// A basic HTTP server controller that simplifies the process of writing integration tests for +/// Actix Web applications. +/// +/// See [`start`] for usage example. +pub struct TestServer { + addr: net::SocketAddr, + client: awc::Client, + system: rt::System, + tls: bool, + server: Server, +} + +impl TestServer { + /// Construct test server url + pub fn addr(&self) -> net::SocketAddr { + self.addr + } + + /// Construct test server url + pub fn url(&self, uri: &str) -> String { + let scheme = if self.tls { "https" } else { "http" }; + + if uri.starts_with('/') { + format!("{}://localhost:{}{}", scheme, self.addr.port(), uri) + } else { + format!("{}://localhost:{}/{}", scheme, self.addr.port(), uri) + } + } + + /// Create `GET` request. + pub fn get(&self, path: impl AsRef) -> ClientRequest { + self.client.get(self.url(path.as_ref()).as_str()) + } + + /// Create `POST` request. + pub fn post(&self, path: impl AsRef) -> ClientRequest { + self.client.post(self.url(path.as_ref()).as_str()) + } + + /// Create `HEAD` request. + pub fn head(&self, path: impl AsRef) -> ClientRequest { + self.client.head(self.url(path.as_ref()).as_str()) + } + + /// Create `PUT` request. + pub fn put(&self, path: impl AsRef) -> ClientRequest { + self.client.put(self.url(path.as_ref()).as_str()) + } + + /// Create `PATCH` request. + pub fn patch(&self, path: impl AsRef) -> ClientRequest { + self.client.patch(self.url(path.as_ref()).as_str()) + } + + /// Create `DELETE` request. + pub fn delete(&self, path: impl AsRef) -> ClientRequest { + self.client.delete(self.url(path.as_ref()).as_str()) + } + + /// Create `OPTIONS` request. + pub fn options(&self, path: impl AsRef) -> ClientRequest { + self.client.options(self.url(path.as_ref()).as_str()) + } + + /// Connect request with given method and path. + pub fn request(&self, method: Method, path: impl AsRef) -> ClientRequest { + self.client.request(method, path.as_ref()) + } + + pub async fn load_body( + &mut self, + mut response: ClientResponse, + ) -> Result + where + S: Stream> + Unpin + 'static, + { + response.body().limit(10_485_760).await + } + + /// Connect to WebSocket server at a given path. + pub async fn ws_at( + &mut self, + path: &str, + ) -> Result, awc::error::WsClientError> { + let url = self.url(path); + let connect = self.client.ws(url).connect(); + connect.await.map(|(_, framed)| framed) + } + + /// Connect to a WebSocket server. + pub async fn ws( + &mut self, + ) -> Result, awc::error::WsClientError> { + self.ws_at("/").await + } + + /// 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 client_headers(&mut self) -> Option<&mut HeaderMap> { + self.client.headers() + } + + /// Gracefully stop HTTP server. + pub async fn stop(self) { + self.server.stop(true).await; + self.system.stop(); + rt::time::sleep(time::Duration::from_millis(100)).await; + } +} + +impl Drop for TestServer { + fn drop(&mut self) { + self.system.stop() + } +} diff --git a/actix-web-actors/Cargo.toml b/actix-web-actors/Cargo.toml index e60d4301c..4fd144195 100644 --- a/actix-web-actors/Cargo.toml +++ b/actix-web-actors/Cargo.toml @@ -29,6 +29,8 @@ tokio = { version = "1", features = ["sync"] } [dev-dependencies] actix-rt = "2.2" +actix-test = "0.0.1" + awc = { version = "3.0.0-beta.3", default-features = false } env_logger = "0.8" futures-util = { version = "0.3.7", default-features = false } diff --git a/actix-web-actors/tests/test_ws.rs b/actix-web-actors/tests/test_ws.rs index 4ffedb2ef..0a8e50b3e 100644 --- a/actix-web-actors/tests/test_ws.rs +++ b/actix-web-actors/tests/test_ws.rs @@ -1,7 +1,7 @@ use actix::prelude::*; use actix_web::{ http::{header, StatusCode}, - test, web, App, HttpRequest, HttpResponse, + web, App, HttpRequest, HttpResponse, }; use actix_web_actors::*; use bytes::Bytes; @@ -27,7 +27,7 @@ impl StreamHandler> for Ws { #[actix_rt::test] async fn test_simple() { - let mut srv = test::start(|| { + let mut srv = actix_test::start(|| { App::new().service(web::resource("/").to( |req: HttpRequest, stream: web::Payload| async move { ws::start(Ws, &req, stream) }, )) @@ -62,7 +62,7 @@ async fn test_simple() { #[actix_rt::test] async fn test_with_credentials() { - let mut srv = test::start(|| { + let mut srv = actix_test::start(|| { App::new().service(web::resource("/").to( |req: HttpRequest, stream: web::Payload| async move { if req.headers().contains_key("Authorization") { diff --git a/actix-web-codegen/Cargo.toml b/actix-web-codegen/Cargo.toml index a513b820b..daea993d0 100644 --- a/actix-web-codegen/Cargo.toml +++ b/actix-web-codegen/Cargo.toml @@ -20,8 +20,10 @@ proc-macro2 = "1" [dev-dependencies] actix-rt = "2.2" -actix-web = "4.0.0-beta.4" +actix-test = "0.0.1" actix-utils = "3.0.0-beta.4" +actix-web = "4.0.0-beta.4" + futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] } trybuild = "1" rustversion = "1" diff --git a/actix-web-codegen/tests/test_macro.rs b/actix-web-codegen/tests/test_macro.rs index 4e06e15ed..7b95edba2 100644 --- a/actix-web-codegen/tests/test_macro.rs +++ b/actix-web-codegen/tests/test_macro.rs @@ -4,7 +4,7 @@ use std::task::{Context, Poll}; use actix_utils::future; use actix_web::dev::{Service, ServiceRequest, ServiceResponse, Transform}; use actix_web::http::header::{HeaderName, HeaderValue}; -use actix_web::{http, test, web::Path, App, Error, HttpResponse, Responder}; +use actix_web::{http, web::Path, App, Error, HttpResponse, Responder}; use actix_web_codegen::{connect, delete, get, head, options, patch, post, put, route, trace}; use futures_core::future::LocalBoxFuture; @@ -149,7 +149,7 @@ async fn get_wrap(_: Path) -> impl Responder { #[actix_rt::test] async fn test_params() { - let srv = test::start(|| { + let srv = actix_test::start(|| { App::new() .service(get_param_test) .service(put_param_test) @@ -171,7 +171,7 @@ async fn test_params() { #[actix_rt::test] async fn test_body() { - let srv = test::start(|| { + let srv = actix_test::start(|| { App::new() .service(post_test) .service(put_test) @@ -245,7 +245,7 @@ async fn test_body() { #[actix_rt::test] async fn test_auto_async() { - let srv = test::start(|| App::new().service(auto_async)); + let srv = actix_test::start(|| App::new().service(auto_async)); let request = srv.request(http::Method::GET, srv.url("/test")); let response = request.send().await.unwrap(); @@ -254,7 +254,7 @@ async fn test_auto_async() { #[actix_rt::test] async fn test_wrap() { - let srv = test::start(|| App::new().service(get_wrap)); + let srv = actix_test::start(|| App::new().service(get_wrap)); let request = srv.request(http::Method::GET, srv.url("/test/wrap")); let response = request.send().await.unwrap(); diff --git a/actix-web-codegen/tests/trybuild/docstring-ok.rs b/actix-web-codegen/tests/trybuild/docstring-ok.rs index 2910976c7..4cf310be5 100644 --- a/actix-web-codegen/tests/trybuild/docstring-ok.rs +++ b/actix-web-codegen/tests/trybuild/docstring-ok.rs @@ -1,7 +1,7 @@ -use actix_web::{Responder, HttpResponse, App, test}; +use actix_web::{Responder, HttpResponse, App}; use actix_web_codegen::*; -/// Docstrings shouldn't break anything. +/// doc comments shouldn't break anything #[get("/")] async fn index() -> impl Responder { HttpResponse::Ok() @@ -9,7 +9,7 @@ async fn index() -> impl Responder { #[actix_web::main] async fn main() { - let srv = test::start(|| App::new().service(index)); + let srv = actix_test::start(|| App::new().service(index)); let request = srv.get("/"); let response = request.send().await.unwrap(); diff --git a/actix-web-codegen/tests/trybuild/route-duplicate-method-fail.rs b/actix-web-codegen/tests/trybuild/route-duplicate-method-fail.rs index 9a38050f7..9322b4895 100644 --- a/actix-web-codegen/tests/trybuild/route-duplicate-method-fail.rs +++ b/actix-web-codegen/tests/trybuild/route-duplicate-method-fail.rs @@ -7,9 +7,9 @@ async fn index() -> String { #[actix_web::main] async fn main() { - use actix_web::{App, test}; + use actix_web::App; - let srv = test::start(|| App::new().service(index)); + let srv = actix_test::start(|| App::new().service(index)); let request = srv.get("/"); let response = request.send().await.unwrap(); 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 f3eda68af..abdc895d7 100644 --- a/actix-web-codegen/tests/trybuild/route-duplicate-method-fail.stderr +++ b/actix-web-codegen/tests/trybuild/route-duplicate-method-fail.stderr @@ -5,7 +5,7 @@ error: HTTP method defined more than once: `GET` | ^^^^^ error[E0425]: cannot find value `index` in this scope - --> $DIR/route-duplicate-method-fail.rs:12:49 + --> $DIR/route-duplicate-method-fail.rs:12:55 | -12 | let srv = test::start(|| App::new().service(index)); - | ^^^^^ not found in this scope +12 | let srv = actix_test::start(|| App::new().service(index)); + | ^^^^^ not found in this scope diff --git a/actix-web-codegen/tests/trybuild/route-missing-method-fail.rs b/actix-web-codegen/tests/trybuild/route-missing-method-fail.rs index ce87a55a4..cd43c0669 100644 --- a/actix-web-codegen/tests/trybuild/route-missing-method-fail.rs +++ b/actix-web-codegen/tests/trybuild/route-missing-method-fail.rs @@ -7,9 +7,9 @@ async fn index() -> String { #[actix_web::main] async fn main() { - use actix_web::{App, test}; + use actix_web::App; - let srv = test::start(|| App::new().service(index)); + let srv = actix_test::start(|| App::new().service(index)); let request = srv.get("/"); let response = request.send().await.unwrap(); 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 0518a61ed..0e16b5e27 100644 --- a/actix-web-codegen/tests/trybuild/route-missing-method-fail.stderr +++ b/actix-web-codegen/tests/trybuild/route-missing-method-fail.stderr @@ -7,7 +7,7 @@ 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 - --> $DIR/route-missing-method-fail.rs:12:49 + --> $DIR/route-missing-method-fail.rs:12:55 | -12 | let srv = test::start(|| App::new().service(index)); - | ^^^^^ not found in this scope +12 | let srv = actix_test::start(|| App::new().service(index)); + | ^^^^^ not found in this scope diff --git a/actix-web-codegen/tests/trybuild/route-ok.rs b/actix-web-codegen/tests/trybuild/route-ok.rs index c4f679604..e1082e88e 100644 --- a/actix-web-codegen/tests/trybuild/route-ok.rs +++ b/actix-web-codegen/tests/trybuild/route-ok.rs @@ -7,9 +7,9 @@ async fn index() -> String { #[actix_web::main] async fn main() { - use actix_web::{App, test}; + use actix_web::App; - let srv = test::start(|| App::new().service(index)); + let srv = actix_test::start(|| App::new().service(index)); let request = srv.get("/"); let response = request.send().await.unwrap(); diff --git a/actix-web-codegen/tests/trybuild/route-unexpected-method-fail.rs b/actix-web-codegen/tests/trybuild/route-unexpected-method-fail.rs index 28cd1344c..1a50e01bc 100644 --- a/actix-web-codegen/tests/trybuild/route-unexpected-method-fail.rs +++ b/actix-web-codegen/tests/trybuild/route-unexpected-method-fail.rs @@ -7,9 +7,9 @@ async fn index() -> String { #[actix_web::main] async fn main() { - use actix_web::{App, test}; + use actix_web::App; - let srv = test::start(|| App::new().service(index)); + let srv = actix_test::start(|| App::new().service(index)); let request = srv.get("/"); let response = request.send().await.unwrap(); 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 9d87f310b..a638a96a6 100644 --- a/actix-web-codegen/tests/trybuild/route-unexpected-method-fail.stderr +++ b/actix-web-codegen/tests/trybuild/route-unexpected-method-fail.stderr @@ -5,7 +5,7 @@ error: Unexpected HTTP method: `UNEXPECTED` | ^^^^^^^^^^^^ error[E0425]: cannot find value `index` in this scope - --> $DIR/route-unexpected-method-fail.rs:12:49 + --> $DIR/route-unexpected-method-fail.rs:12:55 | -12 | let srv = test::start(|| App::new().service(index)); - | ^^^^^ not found in this scope +12 | let srv = actix_test::start(|| App::new().service(index)); + | ^^^^^ not found in this scope diff --git a/actix-web-codegen/tests/trybuild/simple.rs b/actix-web-codegen/tests/trybuild/simple.rs index 761b04905..8170edbfa 100644 --- a/actix-web-codegen/tests/trybuild/simple.rs +++ b/actix-web-codegen/tests/trybuild/simple.rs @@ -1,4 +1,4 @@ -use actix_web::{Responder, HttpResponse, App, test}; +use actix_web::{Responder, HttpResponse, App}; use actix_web_codegen::*; #[get("/config")] @@ -8,7 +8,7 @@ async fn config() -> impl Responder { #[actix_web::main] async fn main() { - let srv = test::start(|| App::new().service(config)); + let srv = actix_test::start(|| App::new().service(config)); let request = srv.get("/config"); let response = request.send().await.unwrap(); diff --git a/awc/Cargo.toml b/awc/Cargo.toml index d0e781e55..78110baed 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -72,6 +72,7 @@ actix-http-test = { version = "3.0.0-beta.3", features = ["openssl"] } actix-utils = "3.0.0-beta.4" actix-server = "2.0.0-beta.3" actix-tls = { version = "3.0.0-beta.5", features = ["openssl", "rustls"] } +actix-test = { version = "0.0.1", features = ["openssl", "rustls"] } brotli2 = "0.3.2" env_logger = "0.8" @@ -79,3 +80,7 @@ flate2 = "1.0.13" futures-util = { version = "0.3.7", default-features = false } rcgen = "0.8" webpki = "0.21" + +[[example]] +name = "client" +required-features = ["rustls"] diff --git a/examples/client.rs b/awc/examples/client.rs similarity index 100% rename from examples/client.rs rename to awc/examples/client.rs diff --git a/awc/src/middleware/redirect.rs b/awc/src/middleware/redirect.rs index 62ea1d0ac..ae09edf9c 100644 --- a/awc/src/middleware/redirect.rs +++ b/awc/src/middleware/redirect.rs @@ -283,10 +283,9 @@ fn rebuild_uri(res: &ClientResponse, org_uri: Uri) -> Result(HttpResponse::BadRequest()) @@ -323,7 +322,7 @@ mod tests { .connector(crate::Connector::new()) .finish(); - let srv = start(|| { + let srv = actix_test::start(|| { App::new() .service(web::resource("/").route(web::to(|| async { Ok::<_, Error>( diff --git a/awc/tests/test_client.rs b/awc/tests/test_client.rs index 9682bc254..a393a6415 100644 --- a/awc/tests/test_client.rs +++ b/awc/tests/test_client.rs @@ -24,7 +24,7 @@ use actix_web::{ dev::{AppConfig, BodyEncoding}, http::{header, Cookie}, middleware::Compress, - test, web, App, Error, HttpMessage, HttpRequest, HttpResponse, + web, App, Error, HttpMessage, HttpRequest, HttpResponse, }; use awc::error::{JsonPayloadError, PayloadError, SendRequestError}; @@ -52,7 +52,7 @@ const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ #[actix_rt::test] async fn test_simple() { - let srv = test::start(|| { + let srv = actix_test::start(|| { App::new().service(web::resource("/").route(web::to(|| HttpResponse::Ok().body(STR)))) }); @@ -78,7 +78,7 @@ async fn test_simple() { #[actix_rt::test] async fn test_json() { - let srv = test::start(|| { + let srv = actix_test::start(|| { App::new().service( web::resource("/").route(web::to(|_: web::Json| HttpResponse::Ok())), ) @@ -94,7 +94,7 @@ async fn test_json() { #[actix_rt::test] async fn test_form() { - let srv = test::start(|| { + let srv = actix_test::start(|| { App::new().service(web::resource("/").route(web::to( |_: web::Form>| HttpResponse::Ok(), ))) @@ -113,7 +113,7 @@ async fn test_form() { #[actix_rt::test] async fn test_timeout() { - let srv = test::start(|| { + 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)) @@ -138,7 +138,7 @@ async fn test_timeout() { #[actix_rt::test] async fn test_timeout_override() { - let srv = test::start(|| { + 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)) @@ -162,7 +162,7 @@ async fn test_timeout_override() { async fn test_response_timeout() { use futures_util::stream::{once, StreamExt as _}; - let srv = test::start(|| { + let srv = actix_test::start(|| { App::new().service(web::resource("/").route(web::to(|| async { Ok::<_, Error>( HttpResponse::Ok() @@ -444,7 +444,7 @@ async fn test_connection_wait_queue_force_close() { #[actix_rt::test] async fn test_with_query_parameter() { - let srv = test::start(|| { + let srv = actix_test::start(|| { App::new().service(web::resource("/").to(|req: HttpRequest| { if req.query_string().contains("qp") { HttpResponse::Ok() @@ -464,7 +464,7 @@ async fn test_with_query_parameter() { #[actix_rt::test] async fn test_no_decompress() { - let srv = test::start(|| { + let srv = actix_test::start(|| { App::new() .wrap(Compress::default()) .service(web::resource("/").route(web::to(|| { @@ -508,7 +508,7 @@ async fn test_no_decompress() { #[actix_rt::test] async fn test_client_gzip_encoding() { - let srv = test::start(|| { + 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(); @@ -531,7 +531,7 @@ async fn test_client_gzip_encoding() { #[actix_rt::test] async fn test_client_gzip_encoding_large() { - let srv = test::start(|| { + 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(); @@ -560,7 +560,7 @@ async fn test_client_gzip_encoding_large_random() { .map(char::from) .collect::(); - let srv = test::start(|| { + 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(); @@ -582,7 +582,7 @@ async fn test_client_gzip_encoding_large_random() { #[actix_rt::test] async fn test_client_brotli_encoding() { - let srv = test::start(|| { + 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(); @@ -610,7 +610,7 @@ async fn test_client_brotli_encoding_large_random() { .map(char::from) .collect::(); - let srv = test::start(|| { + 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(); @@ -633,7 +633,7 @@ async fn test_client_brotli_encoding_large_random() { #[actix_rt::test] async fn test_client_deflate_encoding() { - let srv = test::start(|| { + let srv = actix_test::start(|| { App::new().default_service(web::to(|body: Bytes| { HttpResponse::Ok() .encoding(http::ContentEncoding::Br) @@ -658,7 +658,7 @@ async fn test_client_deflate_encoding_large_random() { .take(70_000) .collect::(); - let srv = test::start(|| { + let srv = actix_test::start(|| { App::new().default_service(web::to(|body: Bytes| { HttpResponse::Ok() .encoding(http::ContentEncoding::Br) @@ -677,7 +677,7 @@ async fn test_client_deflate_encoding_large_random() { #[actix_rt::test] async fn test_client_streaming_explicit() { - let srv = test::start(|| { + let srv = actix_test::start(|| { App::new().default_service(web::to(|body: web::Payload| { HttpResponse::Ok() .encoding(http::ContentEncoding::Identity) @@ -698,7 +698,7 @@ async fn test_client_streaming_explicit() { #[actix_rt::test] async fn test_body_streaming_implicit() { - let srv = test::start(|| { + 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())) @@ -734,7 +734,7 @@ async fn test_client_cookie_handling() { let cookie1b = cookie1.clone(); let cookie2b = cookie2.clone(); - let srv = test::start(move || { + let srv = actix_test::start(move || { let cookie1 = cookie1b.clone(); let cookie2 = cookie2b.clone(); @@ -790,8 +790,7 @@ async fn test_client_cookie_handling() { #[actix_rt::test] async fn client_unread_response() { - let addr = test::unused_addr(); - + let addr = actix_test::unused_addr(); let lst = std::net::TcpListener::bind(addr).unwrap(); std::thread::spawn(move || { @@ -820,7 +819,7 @@ async fn client_unread_response() { #[actix_rt::test] async fn client_basic_auth() { - let srv = test::start(|| { + let srv = actix_test::start(|| { App::new().route( "/", web::to(|req: HttpRequest| { @@ -848,7 +847,7 @@ async fn client_basic_auth() { #[actix_rt::test] async fn client_bearer_auth() { - let srv = test::start(|| { + let srv = actix_test::start(|| { App::new().route( "/", web::to(|req: HttpRequest| { @@ -878,7 +877,7 @@ async fn client_bearer_auth() { async fn test_local_address() { let ip = IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)); - let srv = test::start(move || { + let srv = actix_test::start(move || { App::new().service(web::resource("/").route(web::to( move |req: HttpRequest| async move { assert_eq!(req.peer_addr().unwrap().ip(), ip); diff --git a/benches/server.rs b/benches/server.rs index 9dd540a73..c6695817f 100644 --- a/benches/server.rs +++ b/benches/server.rs @@ -32,7 +32,7 @@ fn bench_async_burst(c: &mut Criterion) { let rt = actix_rt::System::new(); let srv = rt.block_on(async { - test::start(|| { + actix_test::start(|| { App::new() .service(web::resource("/").route(web::to(|| HttpResponse::Ok().body(STR)))) }) diff --git a/codecov.yml b/codecov.yml index e45672bfc..d80835c7f 100644 --- a/codecov.yml +++ b/codecov.yml @@ -11,6 +11,5 @@ coverage: ignore: # ignore code coverage on following paths - "**/tests" - - "test-server" - "**/benches" - "**/examples" diff --git a/src/config.rs b/src/config.rs index cd14eb4cc..4bd76f2b7 100644 --- a/src/config.rs +++ b/src/config.rs @@ -103,8 +103,8 @@ impl AppService { } } -/// Application connection config -#[derive(Clone)] +/// Application connection config. +#[derive(Debug, Clone)] pub struct AppConfig { secure: bool, host: String, @@ -112,10 +112,15 @@ pub struct AppConfig { } impl AppConfig { - pub(crate) fn new(secure: bool, addr: SocketAddr, host: String) -> Self { + pub(crate) fn new(secure: bool, host: String, addr: SocketAddr) -> Self { AppConfig { secure, host, addr } } + #[doc(hidden)] + pub fn __priv_test_new(secure: bool, host: String, addr: SocketAddr) -> Self { + AppConfig::new(secure, host, addr) + } + /// Server host name. /// /// Host name is used by application router as a hostname for url generation. @@ -142,8 +147,8 @@ impl Default for AppConfig { fn default() -> Self { AppConfig::new( false, - "127.0.0.1:8080".parse().unwrap(), "localhost:8080".to_owned(), + "127.0.0.1:8080".parse().unwrap(), ) } } diff --git a/src/data.rs b/src/data.rs index 3bc54a465..bd9b88301 100644 --- a/src/data.rs +++ b/src/data.rs @@ -148,13 +148,13 @@ impl DataFactory for Data { #[cfg(test)] mod tests { - use std::sync::atomic::{AtomicUsize, Ordering}; - use super::*; - use crate::dev::Service; - use crate::http::StatusCode; - use crate::test::{self, init_service, TestRequest}; - use crate::{web, App, HttpResponse}; + use crate::{ + dev::Service, + http::StatusCode, + test::{init_service, TestRequest}, + web, App, HttpResponse, + }; #[actix_rt::test] async fn test_data_extractor() { @@ -270,49 +270,6 @@ mod tests { assert_eq!(resp.status(), StatusCode::OK); } - #[actix_rt::test] - async fn test_data_drop() { - struct TestData(Arc); - - impl TestData { - fn new(inner: Arc) -> Self { - let _ = inner.fetch_add(1, Ordering::SeqCst); - Self(inner) - } - } - - impl Clone for TestData { - fn clone(&self) -> Self { - let inner = self.0.clone(); - let _ = inner.fetch_add(1, Ordering::SeqCst); - Self(inner) - } - } - - impl Drop for TestData { - fn drop(&mut self) { - let _ = self.0.fetch_sub(1, Ordering::SeqCst); - } - } - - let num = Arc::new(AtomicUsize::new(0)); - let data = TestData::new(num.clone()); - assert_eq!(num.load(Ordering::SeqCst), 1); - - let srv = test::start(move || { - let data = data.clone(); - - App::new() - .data(data) - .service(web::resource("/").to(|_data: Data| async { "ok" })) - }); - - assert!(srv.get("/").send().await.unwrap().status().is_success()); - srv.stop().await; - - assert_eq!(num.load(Ordering::SeqCst), 0); - } - #[actix_rt::test] async fn test_data_from_arc() { let data_new = Data::new(String::from("test-123")); diff --git a/src/lib.rs b/src/lib.rs index 38d9d5734..1a11921a9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -71,11 +71,6 @@ #![doc(html_logo_url = "https://actix.rs/img/logo.png")] #![doc(html_favicon_url = "https://actix.rs/favicon.ico")] -#[cfg(feature = "openssl")] -extern crate tls_openssl as openssl; -#[cfg(feature = "rustls")] -extern crate tls_rustls as rustls; - mod app; mod app_service; mod config; diff --git a/src/middleware/compat.rs b/src/middleware/compat.rs index 71193a5c5..0d197ba80 100644 --- a/src/middleware/compat.rs +++ b/src/middleware/compat.rs @@ -137,7 +137,7 @@ mod tests { use crate::{web, App, HttpResponse}; #[actix_rt::test] - #[cfg(feature = "cookies")] + #[cfg(all(feature = "cookies", feature = "compress"))] async fn test_scope_middleware() { use crate::middleware::Compress; @@ -160,7 +160,7 @@ mod tests { } #[actix_rt::test] - #[cfg(feature = "cookies")] + #[cfg(all(feature = "cookies", feature = "compress"))] async fn test_resource_scope_middleware() { use crate::middleware::Compress; diff --git a/src/request.rs b/src/request.rs index 3fdbb13e1..f3cbc07b8 100644 --- a/src/request.rs +++ b/src/request.rs @@ -52,6 +52,18 @@ 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/server.rs b/src/server.rs index 97c4fdaa2..e3a9f6e01 100644 --- a/src/server.rs +++ b/src/server.rs @@ -71,12 +71,15 @@ impl HttpServer where F: Fn() -> I + Send + Clone + 'static, 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, { /// Create new HTTP server with application factory @@ -288,7 +291,7 @@ where }; svc.finish(map_config(factory(), move |_| { - AppConfig::new(false, addr, host.clone()) + AppConfig::new(false, host.clone(), addr) })) .tcp() })?; @@ -343,10 +346,11 @@ where }; svc.finish(map_config(factory(), move |_| { - AppConfig::new(true, addr, host.clone()) + AppConfig::new(true, host.clone(), addr) })) .openssl(acceptor.clone()) })?; + Ok(self) } @@ -396,10 +400,11 @@ where }; svc.finish(map_config(factory(), move |_| { - AppConfig::new(true, addr, host.clone()) + AppConfig::new(true, host.clone(), addr) })) .rustls(config.clone()) })?; + Ok(self) } @@ -502,8 +507,8 @@ where let c = cfg.lock().unwrap(); let config = AppConfig::new( false, - socket_addr, c.host.clone().unwrap_or_else(|| format!("{}", socket_addr)), + socket_addr, ); pipeline_factory(|io: UnixStream| async { Ok((io, Protocol::Http1, None)) }) @@ -552,8 +557,8 @@ where let c = cfg.lock().unwrap(); let config = AppConfig::new( false, - socket_addr, c.host.clone().unwrap_or_else(|| format!("{}", socket_addr)), + socket_addr, ); pipeline_factory(|io: UnixStream| async { Ok((io, Protocol::Http1, None)) }) .and_then( diff --git a/src/service.rs b/src/service.rs index 570d88e7d..c2ecc0033 100644 --- a/src/service.rs +++ b/src/service.rs @@ -69,6 +69,12 @@ 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) { diff --git a/src/test.rs b/src/test.rs index 08c80df40..37fb96e0e 100644 --- a/src/test.rs +++ b/src/test.rs @@ -1,47 +1,41 @@ //! Various helpers for Actix applications to use during testing. -use std::net::SocketAddr; -use std::rc::Rc; -use std::sync::mpsc; -use std::{fmt, net, thread, time}; +use std::{net::SocketAddr, rc::Rc}; -use actix_codec::{AsyncRead, AsyncWrite, Framed}; #[cfg(feature = "cookies")] use actix_http::cookie::Cookie; -use actix_http::http::{HeaderMap, Method, StatusCode, Uri, Version}; -use actix_http::test::TestRequest as HttpTestRequest; -use actix_http::{ws, Extensions, HttpService, Request}; +pub use actix_http::test::TestBuffer; +use actix_http::{ + http::{header::IntoHeaderPair, Method, StatusCode, Uri, Version}, + test::TestRequest as HttpTestRequest, + Extensions, Request, +}; use actix_router::{Path, ResourceDef, Url}; -use actix_rt::{time::sleep, System}; -use actix_service::{map_config, IntoService, IntoServiceFactory, Service, ServiceFactory}; +use actix_service::{IntoService, IntoServiceFactory, Service, ServiceFactory}; use actix_utils::future::ok; -use awc::error::PayloadError; -use awc::{Client, ClientRequest, ClientResponse, Connector}; -use bytes::{Bytes, BytesMut}; use futures_core::Stream; use futures_util::StreamExt as _; -use serde::de::DeserializeOwned; -use serde::Serialize; -use socket2::{Domain, Protocol, Socket, Type}; +use serde::{de::DeserializeOwned, Serialize}; -pub use actix_http::test::TestBuffer; +use crate::{ + app_service::AppInitServiceState, + config::AppConfig, + data::Data, + dev::{Body, MessageBody, Payload}, + http::header::ContentType, + rmap::ResourceMap, + service::{ServiceRequest, ServiceResponse}, + web::{Bytes, BytesMut}, + Error, HttpRequest, HttpResponse, +}; -use crate::app_service::AppInitServiceState; -use crate::config::AppConfig; -use crate::data::Data; -use crate::dev::{Body, MessageBody, Payload, Server}; -use crate::http::header::{ContentType, IntoHeaderPair}; -use crate::rmap::ResourceMap; -use crate::service::{ServiceRequest, ServiceResponse}; -use crate::{Error, HttpRequest, HttpResponse}; - -/// Create service that always responds with `HttpResponse::Ok()` +/// 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 responds with response with specified status code +/// Create service that always responds with given status code and no body. pub fn default_service( status_code: StatusCode, ) -> impl Service, Error = Error> { @@ -51,8 +45,7 @@ pub fn default_service( .into_service() } -/// This method accepts application builder instance, and constructs -/// service. +/// Initialize service from application builder instance. /// /// ``` /// use actix_service::Service; @@ -83,10 +76,10 @@ where { try_init_service(app) .await - .expect("service initilization failed") + .expect("service initialization failed") } -/// Fallible version of init_service that allows testing data factory errors. +/// Fallible version of [`init_service`] that allows testing initialization errors. pub(crate) async fn try_init_service( app: R, ) -> Result, Error = E>, S::InitError> @@ -166,9 +159,11 @@ where let mut body = resp.take_body(); let mut bytes = BytesMut::new(); + while let Some(item) = body.next().await { bytes.extend_from_slice(&item.unwrap()); } + bytes.freeze() } @@ -573,430 +568,12 @@ impl TestRequest { } } -/// Start test server with default configuration -/// -/// Test server is very simple server that simplify process of writing -/// integration tests cases for actix web applications. -/// -/// # Examples -/// -/// ``` -/// use actix_web::{web, test, App, HttpResponse, Error}; -/// -/// async fn my_handler() -> Result { -/// Ok(HttpResponse::Ok().into()) -/// } -/// -/// #[actix_rt::test] -/// async fn test_example() { -/// let srv = test::start( -/// || App::new().service( -/// web::resource("/").to(my_handler)) -/// ); -/// -/// let req = srv.get("/"); -/// let response = req.send().await.unwrap(); -/// assert!(response.status().is_success()); -/// } -/// ``` -pub fn start(factory: F) -> TestServer -where - F: Fn() -> I + Send + Clone + 'static, - I: IntoServiceFactory, - S: ServiceFactory + 'static, - S::Error: Into + 'static, - S::InitError: fmt::Debug, - S::Response: Into> + 'static, - >::Future: 'static, - B: MessageBody + 'static, -{ - start_with(TestServerConfig::default(), factory) -} - -/// Start test server with custom configuration -/// -/// Test server could be configured in different ways, for details check -/// `TestServerConfig` docs. -/// -/// # Examples -/// -/// ``` -/// use actix_web::{web, test, App, HttpResponse, Error}; -/// -/// async fn my_handler() -> Result { -/// Ok(HttpResponse::Ok().into()) -/// } -/// -/// #[actix_rt::test] -/// async fn test_example() { -/// let srv = test::start_with(test::config().h1(), || -/// App::new().service(web::resource("/").to(my_handler)) -/// ); -/// -/// let req = srv.get("/"); -/// let response = req.send().await.unwrap(); -/// assert!(response.status().is_success()); -/// } -/// ``` -pub fn start_with(cfg: TestServerConfig, factory: F) -> TestServer -where - F: Fn() -> I + Send + Clone + 'static, - I: IntoServiceFactory, - S: ServiceFactory + 'static, - S::Error: Into + 'static, - S::InitError: fmt::Debug, - S::Response: Into> + 'static, - >::Future: 'static, - B: MessageBody + 'static, -{ - let (tx, rx) = mpsc::channel(); - - let ssl = match cfg.stream { - StreamType::Tcp => false, - #[cfg(feature = "openssl")] - StreamType::Openssl(_) => true, - #[cfg(feature = "rustls")] - StreamType::Rustls(_) => true, - }; - - // run server in separate thread - thread::spawn(move || { - let sys = System::new(); - let tcp = net::TcpListener::bind("127.0.0.1:0").unwrap(); - let local_addr = tcp.local_addr().unwrap(); - let factory = factory.clone(); - let cfg = cfg.clone(); - let ctimeout = cfg.client_timeout; - let builder = Server::build().workers(1).disable_signals(); - - let srv = match cfg.stream { - StreamType::Tcp => match cfg.tp { - HttpVer::Http1 => builder.listen("test", tcp, move || { - let cfg = AppConfig::new(false, local_addr, format!("{}", local_addr)); - HttpService::build() - .client_timeout(ctimeout) - .h1(map_config(factory(), move |_| cfg.clone())) - .tcp() - }), - HttpVer::Http2 => builder.listen("test", tcp, move || { - let cfg = AppConfig::new(false, local_addr, format!("{}", local_addr)); - HttpService::build() - .client_timeout(ctimeout) - .h2(map_config(factory(), move |_| cfg.clone())) - .tcp() - }), - HttpVer::Both => builder.listen("test", tcp, move || { - let cfg = AppConfig::new(false, local_addr, format!("{}", local_addr)); - HttpService::build() - .client_timeout(ctimeout) - .finish(map_config(factory(), move |_| cfg.clone())) - .tcp() - }), - }, - #[cfg(feature = "openssl")] - StreamType::Openssl(acceptor) => match cfg.tp { - HttpVer::Http1 => builder.listen("test", tcp, move || { - let cfg = AppConfig::new(true, local_addr, format!("{}", local_addr)); - HttpService::build() - .client_timeout(ctimeout) - .h1(map_config(factory(), move |_| cfg.clone())) - .openssl(acceptor.clone()) - }), - HttpVer::Http2 => builder.listen("test", tcp, move || { - let cfg = AppConfig::new(true, local_addr, format!("{}", local_addr)); - HttpService::build() - .client_timeout(ctimeout) - .h2(map_config(factory(), move |_| cfg.clone())) - .openssl(acceptor.clone()) - }), - HttpVer::Both => builder.listen("test", tcp, move || { - let cfg = AppConfig::new(true, local_addr, format!("{}", local_addr)); - HttpService::build() - .client_timeout(ctimeout) - .finish(map_config(factory(), move |_| cfg.clone())) - .openssl(acceptor.clone()) - }), - }, - #[cfg(feature = "rustls")] - StreamType::Rustls(config) => match cfg.tp { - HttpVer::Http1 => builder.listen("test", tcp, move || { - let cfg = AppConfig::new(true, local_addr, format!("{}", local_addr)); - HttpService::build() - .client_timeout(ctimeout) - .h1(map_config(factory(), move |_| cfg.clone())) - .rustls(config.clone()) - }), - HttpVer::Http2 => builder.listen("test", tcp, move || { - let cfg = AppConfig::new(true, local_addr, format!("{}", local_addr)); - HttpService::build() - .client_timeout(ctimeout) - .h2(map_config(factory(), move |_| cfg.clone())) - .rustls(config.clone()) - }), - HttpVer::Both => builder.listen("test", tcp, move || { - let cfg = AppConfig::new(true, local_addr, format!("{}", local_addr)); - HttpService::build() - .client_timeout(ctimeout) - .finish(map_config(factory(), move |_| cfg.clone())) - .rustls(config.clone()) - }), - }, - } - .unwrap(); - - sys.block_on(async { - let srv = srv.run(); - tx.send((System::current(), srv, local_addr)).unwrap(); - }); - - sys.run() - }); - - let (system, server, addr) = rx.recv().unwrap(); - - let client = { - let connector = { - #[cfg(feature = "openssl")] - { - 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(time::Duration::from_secs(0)) - .timeout(time::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)) - } - }; - - Client::builder().connector(connector).finish() - }; - - TestServer { - addr, - client, - system, - ssl, - server, - } -} - -#[derive(Clone)] -pub struct TestServerConfig { - tp: HttpVer, - stream: StreamType, - client_timeout: u64, -} - -#[derive(Clone)] -enum HttpVer { - Http1, - Http2, - Both, -} - -#[derive(Clone)] -enum StreamType { - Tcp, - #[cfg(feature = "openssl")] - Openssl(openssl::ssl::SslAcceptor), - #[cfg(feature = "rustls")] - Rustls(rustls::ServerConfig), -} - -impl Default for TestServerConfig { - fn default() -> Self { - TestServerConfig::new() - } -} - -/// Create default test server config -pub fn config() -> TestServerConfig { - TestServerConfig::new() -} - -impl TestServerConfig { - /// Create default server configuration - pub(crate) fn new() -> TestServerConfig { - TestServerConfig { - tp: HttpVer::Both, - stream: StreamType::Tcp, - client_timeout: 5000, - } - } - - /// Start HTTP/1.1 server only - pub fn h1(mut self) -> Self { - self.tp = HttpVer::Http1; - self - } - - /// Start HTTP/2 server only - pub fn h2(mut self) -> Self { - self.tp = HttpVer::Http2; - self - } - - /// Start openssl server - #[cfg(feature = "openssl")] - pub fn openssl(mut self, acceptor: openssl::ssl::SslAcceptor) -> Self { - self.stream = StreamType::Openssl(acceptor); - self - } - - /// Start rustls server - #[cfg(feature = "rustls")] - pub fn rustls(mut self, config: rustls::ServerConfig) -> Self { - self.stream = StreamType::Rustls(config); - self - } - - /// Set server client timeout in milliseconds for first request. - pub fn client_timeout(mut self, val: u64) -> Self { - self.client_timeout = val; - self - } -} - -/// Get first available unused address -pub fn unused_addr() -> net::SocketAddr { - let addr: net::SocketAddr = "127.0.0.1:0".parse().unwrap(); - let socket = Socket::new(Domain::IPV4, Type::STREAM, Some(Protocol::TCP)).unwrap(); - socket.bind(&addr.into()).unwrap(); - socket.set_reuse_address(true).unwrap(); - let tcp = net::TcpListener::from(socket); - tcp.local_addr().unwrap() -} - -/// Test server controller -pub struct TestServer { - addr: net::SocketAddr, - client: awc::Client, - system: actix_rt::System, - ssl: bool, - server: Server, -} - -impl TestServer { - /// Construct test server url - pub fn addr(&self) -> net::SocketAddr { - self.addr - } - - /// Construct test server url - pub fn url(&self, uri: &str) -> String { - let scheme = if self.ssl { "https" } else { "http" }; - - if uri.starts_with('/') { - format!("{}://localhost:{}{}", scheme, self.addr.port(), uri) - } else { - format!("{}://localhost:{}/{}", scheme, self.addr.port(), uri) - } - } - - /// Create `GET` request - pub fn get>(&self, path: S) -> ClientRequest { - self.client.get(self.url(path.as_ref()).as_str()) - } - - /// Create `POST` request - pub fn post>(&self, path: S) -> ClientRequest { - self.client.post(self.url(path.as_ref()).as_str()) - } - - /// Create `HEAD` request - pub fn head>(&self, path: S) -> ClientRequest { - self.client.head(self.url(path.as_ref()).as_str()) - } - - /// Create `PUT` request - pub fn put>(&self, path: S) -> ClientRequest { - self.client.put(self.url(path.as_ref()).as_str()) - } - - /// Create `PATCH` request - pub fn patch>(&self, path: S) -> ClientRequest { - self.client.patch(self.url(path.as_ref()).as_str()) - } - - /// Create `DELETE` request - pub fn delete>(&self, path: S) -> ClientRequest { - self.client.delete(self.url(path.as_ref()).as_str()) - } - - /// Create `OPTIONS` request - pub fn options>(&self, path: S) -> ClientRequest { - self.client.options(self.url(path.as_ref()).as_str()) - } - - /// Connect to test HTTP server - pub fn request>(&self, method: Method, path: S) -> ClientRequest { - self.client.request(method, path.as_ref()) - } - - pub async fn load_body( - &mut self, - mut response: ClientResponse, - ) -> Result - where - S: Stream> + Unpin + 'static, - { - response.body().limit(10_485_760).await - } - - /// Connect to WebSocket server at a given path. - pub async fn ws_at( - &mut self, - path: &str, - ) -> Result, awc::error::WsClientError> { - let url = self.url(path); - let connect = self.client.ws(url).connect(); - connect.await.map(|(_, framed)| framed) - } - - /// Connect to a WebSocket server. - pub async fn ws( - &mut self, - ) -> Result, awc::error::WsClientError> { - self.ws_at("/").await - } - - /// 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 client_headers(&mut self) -> Option<&mut HeaderMap> { - self.client.headers() - } - - /// Gracefully stop HTTP server - pub async fn stop(self) { - self.server.stop(true).await; - self.system.stop(); - sleep(time::Duration::from_millis(100)).await; - } -} - -impl Drop for TestServer { - fn drop(&mut self) { - self.system.stop() - } -} - #[cfg(test)] mod tests { + use std::time::SystemTime; + use actix_http::HttpMessage; use serde::{Deserialize, Serialize}; - use std::time::SystemTime; use super::*; use crate::{http::header, web, App, HttpResponse, Responder}; diff --git a/tests/test_httpserver.rs b/tests/test_httpserver.rs index 12225b7e5..881c6ce94 100644 --- a/tests/test_httpserver.rs +++ b/tests/test_httpserver.rs @@ -3,14 +3,14 @@ extern crate tls_openssl as openssl; #[cfg(any(unix, feature = "openssl"))] use { - actix_web::{test, web, App, HttpResponse, HttpServer}, + actix_web::{web, App, HttpResponse, HttpServer}, std::{sync::mpsc, thread, time::Duration}, }; #[cfg(unix)] #[actix_rt::test] async fn test_start() { - let addr = test::unused_addr(); + let addr = actix_test::unused_addr(); let (tx, rx) = mpsc::channel(); thread::spawn(move || { @@ -93,7 +93,7 @@ fn ssl_acceptor() -> openssl::ssl::SslAcceptorBuilder { async fn test_start_ssl() { use actix_web::HttpRequest; - let addr = test::unused_addr(); + let addr = actix_test::unused_addr(); let (tx, rx) = mpsc::channel(); thread::spawn(move || { diff --git a/tests/test_server.rs b/tests/test_server.rs index db9fe37db..d114b022d 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -31,7 +31,7 @@ use rand::{distributions::Alphanumeric, Rng}; use actix_web::dev::BodyEncoding; use actix_web::middleware::{Compress, NormalizePath, TrailingSlash}; -use actix_web::{dev, test, web, App, Error, HttpResponse}; +use actix_web::{dev, 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 \ @@ -115,7 +115,7 @@ impl futures_core::stream::Stream for TestBody { #[actix_rt::test] async fn test_body() { - let srv = test::start(|| { + let srv = actix_test::start(|| { App::new().service(web::resource("/").route(web::to(|| HttpResponse::Ok().body(STR)))) }); @@ -129,7 +129,7 @@ async fn test_body() { #[actix_rt::test] async fn test_body_gzip() { - let srv = test::start_with(test::config().h1(), || { + let srv = actix_test::start_with(actix_test::config().h1(), || { App::new() .wrap(Compress::new(ContentEncoding::Gzip)) .service(web::resource("/").route(web::to(|| HttpResponse::Ok().body(STR)))) @@ -156,7 +156,7 @@ async fn test_body_gzip() { #[actix_rt::test] async fn test_body_gzip2() { - let srv = test::start_with(test::config().h1(), || { + let srv = actix_test::start_with(actix_test::config().h1(), || { App::new() .wrap(Compress::new(ContentEncoding::Gzip)) .service(web::resource("/").route(web::to(|| { @@ -185,7 +185,7 @@ async fn test_body_gzip2() { #[actix_rt::test] async fn test_body_encoding_override() { - let srv = test::start_with(test::config().h1(), || { + let srv = actix_test::start_with(actix_test::config().h1(), || { App::new() .wrap(Compress::new(ContentEncoding::Gzip)) .service(web::resource("/").route(web::to(|| { @@ -248,7 +248,7 @@ async fn test_body_gzip_large() { let data = STR.repeat(10); let srv_data = data.clone(); - let srv = test::start_with(test::config().h1(), move || { + let srv = actix_test::start_with(actix_test::config().h1(), move || { let data = srv_data.clone(); App::new() .wrap(Compress::new(ContentEncoding::Gzip)) @@ -286,7 +286,7 @@ async fn test_body_gzip_large_random() { .collect::(); let srv_data = data.clone(); - let srv = test::start_with(test::config().h1(), move || { + let srv = actix_test::start_with(actix_test::config().h1(), move || { let data = srv_data.clone(); App::new() .wrap(Compress::new(ContentEncoding::Gzip)) @@ -318,7 +318,7 @@ async fn test_body_gzip_large_random() { #[actix_rt::test] async fn test_body_chunked_implicit() { - let srv = test::start_with(test::config().h1(), || { + let srv = actix_test::start_with(actix_test::config().h1(), || { App::new() .wrap(Compress::new(ContentEncoding::Gzip)) .service(web::resource("/").route(web::get().to(move || { @@ -352,7 +352,7 @@ async fn test_body_chunked_implicit() { #[actix_rt::test] async fn test_body_br_streaming() { - let srv = test::start_with(test::config().h1(), || { + let srv = actix_test::start_with(actix_test::config().h1(), || { App::new() .wrap(Compress::new(ContentEncoding::Br)) .service(web::resource("/").route(web::to(move || { @@ -384,7 +384,7 @@ async fn test_body_br_streaming() { #[actix_rt::test] async fn test_head_binary() { - let srv = test::start_with(test::config().h1(), || { + let srv = actix_test::start_with(actix_test::config().h1(), || { App::new().service( web::resource("/").route(web::head().to(move || HttpResponse::Ok().body(STR))), ) @@ -405,7 +405,7 @@ async fn test_head_binary() { #[actix_rt::test] async fn test_no_chunking() { - let srv = test::start_with(test::config().h1(), || { + let srv = actix_test::start_with(actix_test::config().h1(), || { App::new().service(web::resource("/").route(web::to(move || { HttpResponse::Ok() .no_chunking(STR.len() as u64) @@ -424,7 +424,7 @@ async fn test_no_chunking() { #[actix_rt::test] async fn test_body_deflate() { - let srv = test::start_with(test::config().h1(), || { + let srv = actix_test::start_with(actix_test::config().h1(), || { App::new() .wrap(Compress::new(ContentEncoding::Deflate)) .service(web::resource("/").route(web::to(move || HttpResponse::Ok().body(STR)))) @@ -451,7 +451,7 @@ async fn test_body_deflate() { #[actix_rt::test] async fn test_body_brotli() { - let srv = test::start_with(test::config().h1(), || { + let srv = actix_test::start_with(actix_test::config().h1(), || { App::new() .wrap(Compress::new(ContentEncoding::Br)) .service(web::resource("/").route(web::to(move || HttpResponse::Ok().body(STR)))) @@ -479,7 +479,7 @@ async fn test_body_brotli() { #[actix_rt::test] async fn test_encoding() { - let srv = test::start_with(test::config().h1(), || { + 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))), ) @@ -504,7 +504,7 @@ async fn test_encoding() { #[actix_rt::test] async fn test_gzip_encoding() { - let srv = test::start_with(test::config().h1(), || { + 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))), ) @@ -530,7 +530,7 @@ async fn test_gzip_encoding() { #[actix_rt::test] async fn test_gzip_encoding_large() { let data = STR.repeat(10); - let srv = test::start_with(test::config().h1(), || { + 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))), ) @@ -561,7 +561,7 @@ async fn test_reading_gzip_encoding_large_random() { .map(char::from) .collect::(); - let srv = test::start_with(test::config().h1(), || { + 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))), ) @@ -587,7 +587,7 @@ async fn test_reading_gzip_encoding_large_random() { #[actix_rt::test] async fn test_reading_deflate_encoding() { - let srv = test::start_with(test::config().h1(), || { + 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))), ) @@ -613,7 +613,7 @@ async fn test_reading_deflate_encoding() { #[actix_rt::test] async fn test_reading_deflate_encoding_large() { let data = STR.repeat(10); - let srv = test::start_with(test::config().h1(), || { + 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))), ) @@ -644,7 +644,7 @@ async fn test_reading_deflate_encoding_large_random() { .map(char::from) .collect::(); - let srv = test::start_with(test::config().h1(), || { + 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))), ) @@ -670,7 +670,7 @@ async fn test_reading_deflate_encoding_large_random() { #[actix_rt::test] async fn test_brotli_encoding() { - let srv = test::start_with(test::config().h1(), || { + 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))), ) @@ -701,7 +701,7 @@ async fn test_brotli_encoding_large() { .map(char::from) .collect::(); - let srv = test::start_with(test::config().h1(), || { + let srv = actix_test::start_with(actix_test::config().h1(), || { App::new().service( web::resource("/") .app_data(web::PayloadConfig::new(320_000)) @@ -732,13 +732,14 @@ async fn test_brotli_encoding_large() { #[actix_rt::test] async fn test_brotli_encoding_large_openssl() { let data = STR.repeat(10); - let srv = test::start_with(test::config().openssl(openssl_config()), move || { - App::new().service(web::resource("/").route(web::to(|bytes: Bytes| { - HttpResponse::Ok() - .encoding(actix_web::http::ContentEncoding::Identity) - .body(bytes) - }))) - }); + let srv = + 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) + .body(bytes) + }))) + }); // body let mut e = BrotliEncoder::new(Vec::new(), 3); @@ -794,7 +795,7 @@ mod plus_rustls { .map(char::from) .collect::(); - let srv = test::start_with(test::config().rustls(rustls_config()), || { + let srv = actix_test::start_with(actix_test::config().rustls(rustls_config()), || { App::new().service(web::resource("/").route(web::to(|bytes: Bytes| { HttpResponse::Ok() .encoding(actix_web::http::ContentEncoding::Identity) @@ -827,7 +828,7 @@ mod plus_rustls { async fn test_server_cookies() { use actix_web::{http, HttpMessage}; - let srv = test::start(|| { + let srv = actix_test::start(|| { App::new().default_service(web::to(|| { HttpResponse::Ok() .cookie( @@ -880,7 +881,7 @@ async fn test_server_cookies() { async fn test_slow_request() { use std::net; - let srv = test::start_with(test::config().client_timeout(200), || { + let srv = actix_test::start_with(actix_test::config().client_timeout(200), || { App::new().service(web::resource("/").route(web::to(HttpResponse::Ok))) }); @@ -898,7 +899,7 @@ async fn test_slow_request() { #[actix_rt::test] async fn test_normalize() { - let srv = test::start_with(test::config().h1(), || { + let srv = actix_test::start_with(actix_test::config().h1(), || { App::new() .wrap(NormalizePath::new(TrailingSlash::Trim)) .service(web::resource("/one").route(web::to(|| HttpResponse::Ok().finish()))) @@ -907,3 +908,51 @@ async fn test_normalize() { let response = srv.get("/one/").send().await.unwrap(); assert!(response.status().is_success()); } + +#[actix_rt::test] +async fn test_data_drop() { + use std::sync::{ + atomic::{AtomicUsize, Ordering}, + Arc, + }; + + struct TestData(Arc); + + impl TestData { + fn new(inner: Arc) -> Self { + let _ = inner.fetch_add(1, Ordering::SeqCst); + Self(inner) + } + } + + impl Clone for TestData { + fn clone(&self) -> Self { + let inner = self.0.clone(); + let _ = inner.fetch_add(1, Ordering::SeqCst); + Self(inner) + } + } + + impl Drop for TestData { + fn drop(&mut self) { + self.0.fetch_sub(1, Ordering::SeqCst); + } + } + + let num = Arc::new(AtomicUsize::new(0)); + let data = TestData::new(num.clone()); + assert_eq!(num.load(Ordering::SeqCst), 1); + + let srv = actix_test::start(move || { + let data = data.clone(); + + App::new() + .data(data) + .service(web::resource("/").to(|_data: web::Data| async { "ok" })) + }); + + assert!(srv.get("/").send().await.unwrap().status().is_success()); + srv.stop().await; + + assert_eq!(num.load(Ordering::SeqCst), 0); +} From 6fb06a720a94f12af6cd32943e4b56cae584a05c Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Fri, 2 Apr 2021 09:27:11 +0100 Subject: [PATCH 135/339] prepare http release 3.0.0-beta.5 --- Cargo.toml | 2 +- actix-http-test/Cargo.toml | 2 +- actix-http/CHANGES.md | 7 +++++-- 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 ++-- docs/graphs/web-only.dot | 4 +++- 10 files changed, 18 insertions(+), 13 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 190b645d4..63b43918d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -84,7 +84,7 @@ actix-utils = "3.0.0-beta.4" 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.4" +actix-http = "3.0.0-beta.5" ahash = "0.7" bytes = "1" diff --git a/actix-http-test/Cargo.toml b/actix-http-test/Cargo.toml index 31a83eddb..d1ae3e609 100644 --- a/actix-http-test/Cargo.toml +++ b/actix-http-test/Cargo.toml @@ -52,4 +52,4 @@ tls-openssl = { version = "0.10.9", package = "openssl", optional = true } [dev-dependencies] actix-web = { version = "4.0.0-beta.4", default-features = false, features = ["cookies"] } -actix-http = "3.0.0-beta.4" +actix-http = "3.0.0-beta.5" diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 374c5f199..9765be3a6 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -1,8 +1,11 @@ # Changes ## Unreleased - 2021-xx-xx + + +## 3.0.0-beta.5 - 2021-04-02 ### Added -* `client::Connector::handshake_timeout` method for customize tls connection handshake timeout. [#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] @@ -10,7 +13,7 @@ * `client::Connector` type now only have one generic type for `actix_service::Service`. [#2063] ### Removed -* Common HTTP headers were moved into actix-web. [2094] +* 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 diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 4857f083d..93ef4ec15 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-http" -version = "3.0.0-beta.4" +version = "3.0.0-beta.5" 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 53fedd40e..50a17a02f 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.4)](https://docs.rs/actix-http/3.0.0-beta.4) +[![Documentation](https://docs.rs/actix-http/badge.svg?version=3.0.0-beta.5)](https://docs.rs/actix-http/3.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) ![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.4/status.svg)](https://deps.rs/crate/actix-http/3.0.0-beta.4) +[![dependency status](https://deps.rs/crate/actix-http/3.0.0-beta.5/status.svg)](https://deps.rs/crate/actix-http/3.0.0-beta.5) [![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/Cargo.toml b/actix-multipart/Cargo.toml index cb00a0e74..aa8660e40 100644 --- a/actix-multipart/Cargo.toml +++ b/actix-multipart/Cargo.toml @@ -31,6 +31,6 @@ twoway = "0.2" [dev-dependencies] actix-rt = "2.2" -actix-http = "3.0.0-beta.4" +actix-http = "3.0.0-beta.5" tokio = { version = "1", features = ["sync"] } tokio-stream = "0.1" diff --git a/actix-test/Cargo.toml b/actix-test/Cargo.toml index 884ec229c..6364c2757 100644 --- a/actix-test/Cargo.toml +++ b/actix-test/Cargo.toml @@ -17,7 +17,7 @@ openssl = ["tls-openssl", "actix-http/openssl"] [dependencies] actix-codec = "0.4.0-beta.1" -actix-http = { version = "3.0.0-beta.4", features = ["cookies"] } +actix-http = { version = "3.0.0-beta.5", features = ["cookies"] } actix-http-test = { version = "3.0.0-beta.3", features = [] } actix-service = "2.0.0-beta.4" actix-utils = "3.0.0-beta.2" diff --git a/actix-web-actors/Cargo.toml b/actix-web-actors/Cargo.toml index 4fd144195..cf596e25c 100644 --- a/actix-web-actors/Cargo.toml +++ b/actix-web-actors/Cargo.toml @@ -18,7 +18,7 @@ path = "src/lib.rs" [dependencies] actix = { version = "0.11.0-beta.3", default-features = false } actix-codec = "0.4.0-beta.1" -actix-http = "3.0.0-beta.4" +actix-http = "3.0.0-beta.5" actix-web = { version = "4.0.0-beta.4", default-features = false } bytes = "1" diff --git a/awc/Cargo.toml b/awc/Cargo.toml index 78110baed..19677e109 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -46,7 +46,7 @@ trust-dns = ["actix-http/trust-dns"] [dependencies] actix-codec = "0.4.0-beta.1" actix-service = "2.0.0-beta.4" -actix-http = "3.0.0-beta.4" +actix-http = "3.0.0-beta.5" actix-rt = { version = "2.1", default-features = false } base64 = "0.13" @@ -67,7 +67,7 @@ tls-rustls = { version = "0.19.0", package = "rustls", optional = true, features [dev-dependencies] actix-web = { version = "4.0.0-beta.4", features = ["openssl"] } -actix-http = { version = "3.0.0-beta.4", features = ["openssl"] } +actix-http = { version = "3.0.0-beta.5", features = ["openssl"] } actix-http-test = { version = "3.0.0-beta.3", features = ["openssl"] } actix-utils = "3.0.0-beta.4" actix-server = "2.0.0-beta.3" diff --git a/docs/graphs/web-only.dot b/docs/graphs/web-only.dot index 6f8292a3a..b0decd818 100644 --- a/docs/graphs/web-only.dot +++ b/docs/graphs/web-only.dot @@ -9,12 +9,14 @@ digraph { "actix-web-actors" "actix-web-codegen" "actix-http-test" + "actix-test" } - "actix-web" -> { "actix-web-codegen" "actix-http" "awc" } + "actix-web" -> { "actix-web-codegen" "actix-http" } "awc" -> { "actix-http" } "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" } } From 546e7c5da4201f0aa2212fdded1cd14ea8628f54 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Fri, 2 Apr 2021 09:37:51 +0100 Subject: [PATCH 136/339] prepare web release 4.0.0-beta.5 --- CHANGES.md | 7 +++++-- Cargo.toml | 2 +- README.md | 4 ++-- actix-files/Cargo.toml | 4 ++-- actix-http-test/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 +- 10 files changed, 16 insertions(+), 13 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 5442be2e0..a55005c7a 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,9 @@ # Changes ## Unreleased - 2021-xx-xx + + +## 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] @@ -9,8 +12,8 @@ * 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] +* `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. diff --git a/Cargo.toml b/Cargo.toml index 63b43918d..fd23aee73 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-web" -version = "4.0.0-beta.4" +version = "4.0.0-beta.5" authors = ["Nikolay Kim "] description = "Actix Web is a powerful, pragmatic, and extremely fast web framework for Rust" readme = "README.md" diff --git a/README.md b/README.md index 64fd7d08d..508736279 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.4)](https://docs.rs/actix-web/4.0.0-beta.4) +[![Documentation](https://docs.rs/actix-web/badge.svg?version=4.0.0-beta.5)](https://docs.rs/actix-web/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) ![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.4/status.svg)](https://deps.rs/crate/actix-web/4.0.0-beta.4) +[![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)
[![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 6a60397a6..ff8331762 100644 --- a/actix-files/Cargo.toml +++ b/actix-files/Cargo.toml @@ -17,7 +17,7 @@ name = "actix_files" path = "src/lib.rs" [dependencies] -actix-web = { version = "4.0.0-beta.4", default-features = false } +actix-web = { version = "4.0.0-beta.5", default-features = false } actix-service = "2.0.0-beta.4" actix-utils = "3.0.0-beta.4" @@ -34,5 +34,5 @@ percent-encoding = "2.1" [dev-dependencies] actix-rt = "2.2" -actix-web = "4.0.0-beta.4" +actix-web = "4.0.0-beta.5" actix-test = "0.0.1" diff --git a/actix-http-test/Cargo.toml b/actix-http-test/Cargo.toml index d1ae3e609..906d64091 100644 --- a/actix-http-test/Cargo.toml +++ b/actix-http-test/Cargo.toml @@ -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.4", default-features = false, features = ["cookies"] } +actix-web = { version = "4.0.0-beta.5", default-features = false, features = ["cookies"] } actix-http = "3.0.0-beta.5" diff --git a/actix-multipart/Cargo.toml b/actix-multipart/Cargo.toml index aa8660e40..e5a6bb7da 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.4", default-features = false } +actix-web = { version = "4.0.0-beta.5", default-features = false } actix-utils = "3.0.0-beta.4" bytes = "1" diff --git a/actix-test/Cargo.toml b/actix-test/Cargo.toml index 6364c2757..b5cafc7c4 100644 --- a/actix-test/Cargo.toml +++ b/actix-test/Cargo.toml @@ -21,7 +21,7 @@ actix-http = { version = "3.0.0-beta.5", features = ["cookies"] } actix-http-test = { version = "3.0.0-beta.3", features = [] } actix-service = "2.0.0-beta.4" actix-utils = "3.0.0-beta.2" -actix-web = { version = "4.0.0-beta.4", default-features = false, features = ["cookies"] } +actix-web = { version = "4.0.0-beta.5", default-features = false, features = ["cookies"] } actix-rt = "2.1" awc = { version = "3.0.0-beta.3", default-features = false, features = ["cookies"] } diff --git a/actix-web-actors/Cargo.toml b/actix-web-actors/Cargo.toml index cf596e25c..82cd19624 100644 --- a/actix-web-actors/Cargo.toml +++ b/actix-web-actors/Cargo.toml @@ -19,7 +19,7 @@ path = "src/lib.rs" actix = { version = "0.11.0-beta.3", default-features = false } actix-codec = "0.4.0-beta.1" actix-http = "3.0.0-beta.5" -actix-web = { version = "4.0.0-beta.4", default-features = false } +actix-web = { version = "4.0.0-beta.5", default-features = false } bytes = "1" bytestring = "1" diff --git a/actix-web-codegen/Cargo.toml b/actix-web-codegen/Cargo.toml index daea993d0..ec02a908e 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.0.1" actix-utils = "3.0.0-beta.4" -actix-web = "4.0.0-beta.4" +actix-web = "4.0.0-beta.5" futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] } trybuild = "1" diff --git a/awc/Cargo.toml b/awc/Cargo.toml index 19677e109..b3acbd5f1 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -66,7 +66,7 @@ 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.4", features = ["openssl"] } +actix-web = { version = "4.0.0-beta.5", features = ["openssl"] } actix-http = { version = "3.0.0-beta.5", features = ["openssl"] } actix-http-test = { version = "3.0.0-beta.3", features = ["openssl"] } actix-utils = "3.0.0-beta.4" From a32151525c51dcbdd9edab79fd1ec5c5761a7183 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Fri, 2 Apr 2021 09:40:36 +0100 Subject: [PATCH 137/339] prepare awc release 3.0.0-beta.4 --- Cargo.toml | 2 +- actix-http-test/Cargo.toml | 2 +- actix-test/Cargo.toml | 2 +- actix-web-actors/Cargo.toml | 2 +- awc/CHANGES.md | 6 +++++- awc/Cargo.toml | 7 +++++-- 6 files changed, 14 insertions(+), 7 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index fd23aee73..c63acebf3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -109,7 +109,7 @@ url = "2.1" [dev-dependencies] actix-test = { version = "0.0.1", features = ["openssl", "rustls"] } -awc = { version = "3.0.0-beta.3", features = ["openssl"] } +awc = { version = "3.0.0-beta.4", features = ["openssl"] } brotli2 = "0.3.2" criterion = "0.3" diff --git a/actix-http-test/Cargo.toml b/actix-http-test/Cargo.toml index 906d64091..f1827eb01 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-beta.4" actix-rt = "2.2" actix-server = "2.0.0-beta.3" -awc = { version = "3.0.0-beta.3", default-features = false } +awc = { version = "3.0.0-beta.4", default-features = false } base64 = "0.13" bytes = "1" diff --git a/actix-test/Cargo.toml b/actix-test/Cargo.toml index b5cafc7c4..56d82e934 100644 --- a/actix-test/Cargo.toml +++ b/actix-test/Cargo.toml @@ -23,7 +23,7 @@ actix-service = "2.0.0-beta.4" actix-utils = "3.0.0-beta.2" actix-web = { version = "4.0.0-beta.5", default-features = false, features = ["cookies"] } actix-rt = "2.1" -awc = { version = "3.0.0-beta.3", default-features = false, features = ["cookies"] } +awc = { version = "3.0.0-beta.4", 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 82cd19624..1c8dcbd1b 100644 --- a/actix-web-actors/Cargo.toml +++ b/actix-web-actors/Cargo.toml @@ -31,6 +31,6 @@ tokio = { version = "1", features = ["sync"] } actix-rt = "2.2" actix-test = "0.0.1" -awc = { version = "3.0.0-beta.3", default-features = false } +awc = { version = "3.0.0-beta.4", default-features = false } env_logger = "0.8" futures-util = { version = "0.3.7", default-features = false } diff --git a/awc/CHANGES.md b/awc/CHANGES.md index c1031239a..eb008ff98 100644 --- a/awc/CHANGES.md +++ b/awc/CHANGES.md @@ -1,13 +1,17 @@ # Changes ## Unreleased - 2021-xx-xx + + +## 3.0.0-beta.4 - 2021-04-02 ### Added * 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 methods now take `IntoHeaderPair` tuples. [#2094] +* Rename `TestResponse::header` to `append_header`, `set` to `insert_header`. `TestResponse` header + methods now take `IntoHeaderPair` tuples. [#2094] [#2081]: https://github.com/actix/actix-web/pull/2081 [#2094]: https://github.com/actix/actix-web/pull/2094 diff --git a/awc/Cargo.toml b/awc/Cargo.toml index b3acbd5f1..8f00913bb 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -1,7 +1,10 @@ [package] name = "awc" -version = "3.0.0-beta.3" -authors = ["Nikolay Kim "] +version = "3.0.0-beta.4" +authors = [ + "Nikolay Kim ", + "fakeshadow <24548779@qq.com>", +] description = "Async HTTP and WebSocket client library built on the Actix ecosystem" readme = "README.md" keywords = ["actix", "http", "framework", "async", "web"] From 8561263545d5df1004371a17ab5001acfe1de4e7 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Fri, 2 Apr 2021 09:43:51 +0100 Subject: [PATCH 138/339] prepare files release 0.6.0-beta.4 --- actix-files/CHANGES.md | 4 ++++ actix-files/Cargo.toml | 2 +- actix-files/README.md | 4 ++-- awc/README.md | 4 ++-- 4 files changed, 9 insertions(+), 5 deletions(-) diff --git a/actix-files/CHANGES.md b/actix-files/CHANGES.md index c035d5afe..eb66e0e07 100644 --- a/actix-files/CHANGES.md +++ b/actix-files/CHANGES.md @@ -3,6 +3,10 @@ ## Unreleased - 2021-xx-xx +## 0.6.0-beta.4 - 2021-04-02 +* No notable changes. + + ## 0.6.0-beta.3 - 2021-03-09 * No notable changes. diff --git a/actix-files/Cargo.toml b/actix-files/Cargo.toml index ff8331762..ce2a54667 100644 --- a/actix-files/Cargo.toml +++ b/actix-files/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-files" -version = "0.6.0-beta.3" +version = "0.6.0-beta.4" authors = ["Nikolay Kim "] description = "Static file serving for Actix Web" readme = "README.md" diff --git a/actix-files/README.md b/actix-files/README.md index c7b7424ec..895d5e687 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.3)](https://docs.rs/actix-files/0.6.0-beta.3) +[![Documentation](https://docs.rs/actix-files/badge.svg?version=0.6.0-beta.4)](https://docs.rs/actix-files/0.6.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) ![License](https://img.shields.io/crates/l/actix-files.svg)
-[![dependency status](https://deps.rs/crate/actix-files/0.6.0-beta.3/status.svg)](https://deps.rs/crate/actix-files/0.6.0-beta.3) +[![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) [![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/awc/README.md b/awc/README.md index 2e7dad320..a836a2497 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.3)](https://docs.rs/awc/3.0.0-beta.3) +[![Documentation](https://docs.rs/awc/badge.svg?version=3.0.0-beta.4)](https://docs.rs/awc/3.0.0-beta.4) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/awc) -[![Dependency Status](https://deps.rs/crate/awc/3.0.0-beta.3/status.svg)](https://deps.rs/crate/awc/3.0.0-beta.3) +[![Dependency Status](https://deps.rs/crate/awc/3.0.0-beta.4/status.svg)](https://deps.rs/crate/awc/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) ## Documentation & Resources From 05c7505563b306f96e46c0cfb6fe15317a9c9b14 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Fri, 2 Apr 2021 09:45:31 +0100 Subject: [PATCH 139/339] prepare multipart release 0.4.0-beta.4 --- actix-multipart/CHANGES.md | 4 ++++ actix-multipart/Cargo.toml | 4 ++-- actix-multipart/README.md | 4 ++-- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/actix-multipart/CHANGES.md b/actix-multipart/CHANGES.md index ce5bd57d7..cd50305cb 100644 --- a/actix-multipart/CHANGES.md +++ b/actix-multipart/CHANGES.md @@ -3,6 +3,10 @@ ## Unreleased - 2021-xx-xx +## 0.4.0-beta.4 - 2021-04-02 +* No notable changes. + + ## 0.4.0-beta.3 - 2021-03-09 * No notable changes. diff --git a/actix-multipart/Cargo.toml b/actix-multipart/Cargo.toml index e5a6bb7da..77558fcc5 100644 --- a/actix-multipart/Cargo.toml +++ b/actix-multipart/Cargo.toml @@ -1,13 +1,13 @@ [package] name = "actix-multipart" -version = "0.4.0-beta.3" +version = "0.4.0-beta.4" 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/" +documentation = "https://docs.rs/actix-multipart" license = "MIT OR Apache-2.0" edition = "2018" diff --git a/actix-multipart/README.md b/actix-multipart/README.md index e4a54ac47..8a4279a62 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.3)](https://docs.rs/actix-multipart/0.4.0-beta.3) +[![Documentation](https://docs.rs/actix-multipart/badge.svg?version=0.4.0-beta.4)](https://docs.rs/actix-multipart/0.4.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-multipart.svg)
-[![dependency status](https://deps.rs/crate/actix-multipart/0.4.0-beta.3/status.svg)](https://deps.rs/crate/actix-multipart/0.4.0-beta.3) +[![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) [![Download](https://img.shields.io/crates/d/actix-multipart.svg)](https://crates.io/crates/actix-multipart) ## Documentation & Resources From a9641e475adee39858ad7421e2c97d68f719c703 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Fri, 2 Apr 2021 09:54:35 +0100 Subject: [PATCH 140/339] prepare http-test release 3.0.0-beta.4 --- actix-http-test/CHANGES.md | 7 ++++++- actix-http-test/Cargo.toml | 2 +- actix-http-test/README.md | 4 ++-- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/actix-http-test/CHANGES.md b/actix-http-test/CHANGES.md index 0fac84a6c..1dbd9a15b 100644 --- a/actix-http-test/CHANGES.md +++ b/actix-http-test/CHANGES.md @@ -1,9 +1,14 @@ # Changes ## Unreleased - 2021-xx-xx -### Added + + +## 3.0.0-beta.4 - 2021-04-02 * 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. diff --git a/actix-http-test/Cargo.toml b/actix-http-test/Cargo.toml index f1827eb01..e1dd4c4e8 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.3" +version = "3.0.0-beta.4" authors = ["Nikolay Kim "] description = "Various helpers for Actix applications to use during testing" readme = "README.md" diff --git a/actix-http-test/README.md b/actix-http-test/README.md index 8cec94808..b8cf450d4 100644 --- a/actix-http-test/README.md +++ b/actix-http-test/README.md @@ -3,9 +3,9 @@ > 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.3)](https://docs.rs/actix-http-test/3.0.0-beta.3) +[![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) ![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.3/status.svg)](https://deps.rs/crate/actix-http-test/3.0.0-beta.3) +[![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) ## Documentation & Resources From e0ae8e59bf6ee0474d99d92e54309682f1e6a12a Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Fri, 2 Apr 2021 09:55:35 +0100 Subject: [PATCH 141/339] prepare actors release 4.0.0-beta.4 --- actix-web-actors/CHANGES.md | 4 ++++ actix-web-actors/Cargo.toml | 4 ++-- actix-web-actors/README.md | 4 ++-- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/actix-web-actors/CHANGES.md b/actix-web-actors/CHANGES.md index 80eef08c1..cd76b201e 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.4 - 2021-04-02 +* No notable changes. + + ## 4.0.0-beta.3 - 2021-03-09 * No notable changes. diff --git a/actix-web-actors/Cargo.toml b/actix-web-actors/Cargo.toml index 1c8dcbd1b..a539227e4 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.3" +version = "4.0.0-beta.4" authors = ["Nikolay Kim "] description = "Actix actors support for Actix Web" readme = "README.md" @@ -29,7 +29,7 @@ tokio = { version = "1", features = ["sync"] } [dev-dependencies] actix-rt = "2.2" -actix-test = "0.0.1" +actix-test = "0.1.0-beta.1" awc = { version = "3.0.0-beta.4", default-features = false } env_logger = "0.8" diff --git a/actix-web-actors/README.md b/actix-web-actors/README.md index 6d9d573d7..2dc779f10 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.3)](https://docs.rs/actix-web-actors/4.0.0-beta.3) +[![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) [![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.3/status.svg)](https://deps.rs/crate/actix-web-actors/4.0.0-beta.3) +[![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) [![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) From e0b2246c68fdcd30221b0d8a293fcb5cb7432851 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Fri, 2 Apr 2021 10:03:01 +0100 Subject: [PATCH 142/339] prepare test release 0.1.0-beta.1 --- Cargo.toml | 2 +- actix-files/Cargo.toml | 2 +- actix-http/Cargo.toml | 2 +- actix-test/CHANGES.md | 7 +++++-- actix-test/Cargo.toml | 9 ++++++--- actix-web-codegen/Cargo.toml | 2 +- awc/Cargo.toml | 4 ++-- 7 files changed, 17 insertions(+), 11 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index c63acebf3..52db6c335 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -108,7 +108,7 @@ time = { version = "0.2.23", default-features = false, features = ["std"] } url = "2.1" [dev-dependencies] -actix-test = { version = "0.0.1", features = ["openssl", "rustls"] } +actix-test = { version = "0.1.0-beta.1", features = ["openssl", "rustls"] } awc = { version = "3.0.0-beta.4", features = ["openssl"] } brotli2 = "0.3.2" diff --git a/actix-files/Cargo.toml b/actix-files/Cargo.toml index ce2a54667..84d8dd958 100644 --- a/actix-files/Cargo.toml +++ b/actix-files/Cargo.toml @@ -35,4 +35,4 @@ percent-encoding = "2.1" [dev-dependencies] actix-rt = "2.2" actix-web = "4.0.0-beta.5" -actix-test = "0.0.1" +actix-test = "0.1.0-beta.1" diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 93ef4ec15..e77d1139c 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -90,7 +90,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.3", features = ["openssl"] } +actix-http-test = { version = "3.0.0-beta.4", features = ["openssl"] } actix-tls = { version = "3.0.0-beta.5", features = ["openssl"] } criterion = "0.3" env_logger = "0.8" diff --git a/actix-test/CHANGES.md b/actix-test/CHANGES.md index 318f08f55..70e643ba7 100644 --- a/actix-test/CHANGES.md +++ b/actix-test/CHANGES.md @@ -1,6 +1,9 @@ # Changes ## Unreleased - 2021-xx-xx -* Move integration testing structs from `actix-web`. [#???] -[#???]: https://github.com/actix/actix-web/pull/??? + +## 0.1.0-beta.1 - 2021-04-02 +* Move integration testing structs from `actix-web`. [#2112] + +[#2112]: https://github.com/actix/actix-web/pull/2112 diff --git a/actix-test/Cargo.toml b/actix-test/Cargo.toml index 56d82e934..aa82fd046 100644 --- a/actix-test/Cargo.toml +++ b/actix-test/Cargo.toml @@ -1,7 +1,10 @@ [package] name = "actix-test" -version = "0.0.1" -authors = ["Rob Ede "] +version = "0.1.0-beta.1" +authors = [ + "Nikolay Kim ", + "Rob Ede ", +] edition = "2018" description = "Integration testing tools for Actix Web applications" license = "MIT OR Apache-2.0" @@ -18,7 +21,7 @@ openssl = ["tls-openssl", "actix-http/openssl"] [dependencies] actix-codec = "0.4.0-beta.1" actix-http = { version = "3.0.0-beta.5", features = ["cookies"] } -actix-http-test = { version = "3.0.0-beta.3", features = [] } +actix-http-test = { version = "3.0.0-beta.4", features = [] } actix-service = "2.0.0-beta.4" actix-utils = "3.0.0-beta.2" actix-web = { version = "4.0.0-beta.5", default-features = false, features = ["cookies"] } diff --git a/actix-web-codegen/Cargo.toml b/actix-web-codegen/Cargo.toml index ec02a908e..04c988392 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.0.1" +actix-test = "0.1.0-beta.1" actix-utils = "3.0.0-beta.4" actix-web = "4.0.0-beta.5" diff --git a/awc/Cargo.toml b/awc/Cargo.toml index 8f00913bb..aebf73ef5 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -71,11 +71,11 @@ tls-rustls = { version = "0.19.0", package = "rustls", optional = true, features [dev-dependencies] actix-web = { version = "4.0.0-beta.5", features = ["openssl"] } actix-http = { version = "3.0.0-beta.5", features = ["openssl"] } -actix-http-test = { version = "3.0.0-beta.3", features = ["openssl"] } +actix-http-test = { version = "3.0.0-beta.4", features = ["openssl"] } actix-utils = "3.0.0-beta.4" actix-server = "2.0.0-beta.3" actix-tls = { version = "3.0.0-beta.5", features = ["openssl", "rustls"] } -actix-test = { version = "0.0.1", features = ["openssl", "rustls"] } +actix-test = { version = "0.1.0-beta.1", features = ["openssl", "rustls"] } brotli2 = "0.3.2" env_logger = "0.8" From 3f5a73793aee0ba6e2639f48af3513a90d48b73b Mon Sep 17 00:00:00 2001 From: Ibraheem Ahmed Date: Thu, 8 Apr 2021 15:51:16 -0400 Subject: [PATCH 143/339] make module/crate re-exports doc inline (#2141) --- src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib.rs b/src/lib.rs index 1a11921a9..1d5d5b83d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -99,6 +99,7 @@ pub mod web; pub use actix_http::cookie; pub use actix_http::Response as HttpResponse; pub use actix_http::{body, Error, HttpMessage, ResponseError, Result}; +#[doc(inline)] pub use actix_rt as rt; pub use actix_web_codegen::*; From 44a2d2214ce99279f7cf215cfd7c6787babed5f8 Mon Sep 17 00:00:00 2001 From: Ibraheem Ahmed Date: Thu, 8 Apr 2021 20:28:35 -0400 Subject: [PATCH 144/339] update year in MIT license (#2143) --- LICENSE-MIT | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE-MIT b/LICENSE-MIT index 95938ef15..d559b1cd1 100644 --- a/LICENSE-MIT +++ b/LICENSE-MIT @@ -1,4 +1,4 @@ -Copyright (c) 2017 Actix Team +Copyright (c) 2017-NOW Actix Team Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated From c72d77065d068f1861ab8628fb072452ec5f0e33 Mon Sep 17 00:00:00 2001 From: Ibraheem Ahmed Date: Thu, 8 Apr 2021 22:22:51 -0400 Subject: [PATCH 145/339] derive debug where possible (#2142) --- src/types/form.rs | 8 +------- src/types/header.rs | 11 +---------- src/types/json.rs | 10 +--------- src/types/path.rs | 10 ++-------- src/types/query.rs | 18 +++++++++--------- 5 files changed, 14 insertions(+), 43 deletions(-) diff --git a/src/types/form.rs b/src/types/form.rs index 0985bd945..14c1369ff 100644 --- a/src/types/form.rs +++ b/src/types/form.rs @@ -80,7 +80,7 @@ use crate::{ /// }) /// } /// ``` -#[derive(PartialEq, Eq, PartialOrd, Ord)] +#[derive(PartialEq, Eq, PartialOrd, Ord, Debug)] pub struct Form(pub T); impl Form { @@ -150,12 +150,6 @@ where } } -impl fmt::Debug for Form { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.0.fmt(f) - } -} - impl fmt::Display for Form { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.0.fmt(f) diff --git a/src/types/header.rs b/src/types/header.rs index 1f8be707a..9b64f445d 100644 --- a/src/types/header.rs +++ b/src/types/header.rs @@ -23,7 +23,7 @@ use crate::{ /// format!("Request was sent at {}", date.to_string()) /// } /// ``` -#[derive(Clone, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug)] pub struct Header(pub T); impl Header { @@ -47,15 +47,6 @@ impl ops::DerefMut for Header { } } -impl fmt::Debug for Header -where - T: fmt::Debug, -{ - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "Header: {:?}", self.0) - } -} - impl fmt::Display for Header where T: fmt::Display, diff --git a/src/types/json.rs b/src/types/json.rs index 068dfeb2c..97439d8fd 100644 --- a/src/types/json.rs +++ b/src/types/json.rs @@ -73,6 +73,7 @@ use crate::{ /// }) /// } /// ``` +#[derive(Debug)] pub struct Json(pub T); impl Json { @@ -96,15 +97,6 @@ impl ops::DerefMut for Json { } } -impl fmt::Debug for Json -where - T: fmt::Debug, -{ - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "Json: {:?}", self.0) - } -} - impl fmt::Display for Json where T: fmt::Display, diff --git a/src/types/path.rs b/src/types/path.rs index 90ee5296b..33ea70629 100644 --- a/src/types/path.rs +++ b/src/types/path.rs @@ -45,7 +45,7 @@ use crate::{dev::Payload, error::PathError, FromRequest, HttpRequest}; /// format!("Welcome {}!", info.name) /// } /// ``` -#[derive(PartialEq, Eq, PartialOrd, Ord)] +#[derive(PartialEq, Eq, PartialOrd, Ord, Debug)] pub struct Path(T); impl Path { @@ -81,12 +81,6 @@ impl From for Path { } } -impl fmt::Debug for Path { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.0.fmt(f) - } -} - impl fmt::Display for Path { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.0.fmt(f) @@ -261,7 +255,7 @@ mod tests { assert_eq!(s.value, "user2"); assert_eq!( format!("{}, {:?}", s, s), - "MyStruct(name, user2), MyStruct { key: \"name\", value: \"user2\" }" + "MyStruct(name, user2), Path(MyStruct { key: \"name\", value: \"user2\" })" ); let s = s.into_inner(); assert_eq!(s.value, "user2"); diff --git a/src/types/query.rs b/src/types/query.rs index b6c025ef3..4807335bc 100644 --- a/src/types/query.rs +++ b/src/types/query.rs @@ -57,7 +57,7 @@ use crate::{dev::Payload, error::QueryPayloadError, Error, FromRequest, HttpRequ /// "OK".to_string() /// } /// ``` -#[derive(Clone, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug)] pub struct Query(pub T); impl Query { @@ -100,12 +100,6 @@ impl ops::DerefMut for Query { } } -impl fmt::Debug for Query { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.0.fmt(f) - } -} - impl fmt::Display for Query { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.0.fmt(f) @@ -226,7 +220,10 @@ mod tests { let mut s = Query::::from_query(&req.query_string()).unwrap(); assert_eq!(s.id, "test"); - assert_eq!(format!("{}, {:?}", s, s), "test, Id { id: \"test\" }"); + assert_eq!( + format!("{}, {:?}", s, s), + "test, Query(Id { id: \"test\" })" + ); s.id = "test1".to_string(); let s = s.into_inner(); @@ -244,7 +241,10 @@ mod tests { let mut s = Query::::from_request(&req, &mut pl).await.unwrap(); assert_eq!(s.id, "test"); - assert_eq!(format!("{}, {:?}", s, s), "test, Id { id: \"test\" }"); + assert_eq!( + format!("{}, {:?}", s, s), + "test, Query(Id { id: \"test\" })" + ); s.id = "test1".to_string(); let s = s.into_inner(); From 44c55dd036eb1e4adae0853267685866257dc789 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Fri, 9 Apr 2021 18:07:10 +0100 Subject: [PATCH 146/339] remove cookie support from -http (#2065) --- .cargo/config.toml | 4 + CHANGES.md | 3 + Cargo.toml | 46 +- actix-files/src/error.rs | 6 +- actix-http/CHANGES.md | 7 + actix-http/Cargo.toml | 11 +- actix-http/examples/ws.rs | 6 +- actix-http/src/error.rs | 22 +- actix-http/src/h1/dispatcher.rs | 2 +- actix-http/src/http_message.rs | 40 -- actix-http/src/lib.rs | 7 - actix-http/src/message.rs | 4 +- actix-http/src/request.rs | 14 +- actix-http/src/response.rs | 294 +------- actix-http/src/test.rs | 32 - actix-http/src/ws/mod.rs | 6 +- actix-multipart/src/error.rs | 3 +- actix-test/Cargo.toml | 2 +- actix-test/src/lib.rs | 8 +- actix-web-actors/src/ws.rs | 2 +- actix-web-codegen/tests/test_macro.rs | 18 +- awc/Cargo.toml | 3 +- awc/src/lib.rs | 7 +- awc/src/request.rs | 8 +- awc/src/response.rs | 52 +- awc/src/test.rs | 9 +- awc/src/ws.rs | 6 +- awc/tests/test_client.rs | 5 +- src/error.rs | 26 +- src/handler.rs | 27 +- src/lib.rs | 32 +- src/request.rs | 66 +- src/responder.rs | 35 +- src/response.rs | 968 ++++++++++++++++++++++++++ src/service.rs | 40 +- src/test.rs | 48 +- src/types/json.rs | 4 +- src/types/path.rs | 11 +- src/types/query.rs | 4 +- tests/test_server.rs | 13 +- 40 files changed, 1335 insertions(+), 566 deletions(-) create mode 100644 src/response.rs diff --git a/.cargo/config.toml b/.cargo/config.toml index 40fe3e573..0bab205cd 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -1,3 +1,7 @@ [alias] chk = "hack check --workspace --all-features --tests --examples" lint = "hack --clean-per-run 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" diff --git a/CHANGES.md b/CHANGES.md index a55005c7a..64e0891e5 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,7 +1,10 @@ # Changes ## Unreleased - 2021-xx-xx +### Added +* `HttpResponse` and `HttpResponseBuilder` structs. [#2065] +[#2065]: https://github.com/actix/actix-web/pull/2065 ## 4.0.0-beta.5 - 2021-04-02 ### Added diff --git a/Cargo.toml b/Cargo.toml index 52db6c335..b8b18a84c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,11 +16,7 @@ edition = "2018" [package.metadata.docs.rs] # features that docs.rs will build with -features = ["openssl", "rustls", "compress", "secure-cookies"] - -[badges] -travis-ci = { repository = "actix/actix-web", branch = "master" } -codecov = { repository = "actix/actix-web", branch = "master", service = "github" } +features = ["openssl", "rustls", "compress", "cookies", "secure-cookies"] [lib] name = "actix_web" @@ -38,6 +34,8 @@ members = [ "actix-http-test", "actix-test", ] +# enable when MSRV is 1.51+ +# resolver = "2" [features] default = ["compress", "cookies"] @@ -46,10 +44,10 @@ default = ["compress", "cookies"] compress = ["actix-http/compress"] # support for cookies -cookies = ["actix-http/cookies"] +cookies = ["cookie"] # secure cookies feature -secure-cookies = ["actix-http/secure-cookies"] +secure-cookies = ["cookie/secure"] # openssl openssl = ["actix-http/openssl", "actix-tls/accept", "actix-tls/openssl"] @@ -57,22 +55,6 @@ openssl = ["actix-http/openssl", "actix-tls/accept", "actix-tls/openssl"] # rustls rustls = ["actix-http/rustls", "actix-tls/accept", "actix-tls/rustls"] -[[example]] -name = "basic" -required-features = ["compress"] - -[[example]] -name = "uds" -required-features = ["compress"] - -[[test]] -name = "test_server" -required-features = ["compress", "cookies"] - -[[example]] -name = "on_connect" -required-features = [] - [dependencies] actix-codec = "0.4.0-beta.1" actix-macros = "0.2.0" @@ -88,11 +70,13 @@ actix-http = "3.0.0-beta.5" ahash = "0.7" bytes = "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 } +itoa = "0.4" language-tags = "0.2" once_cell = "1.5" log = "0.4" @@ -137,6 +121,22 @@ actix-web-actors = { path = "actix-web-actors" } actix-web-codegen = { path = "actix-web-codegen" } awc = { path = "awc" } +[[test]] +name = "test_server" +required-features = ["compress", "cookies"] + +[[example]] +name = "basic" +required-features = ["compress"] + +[[example]] +name = "uds" +required-features = ["compress"] + +[[example]] +name = "on_connect" +required-features = [] + [[bench]] name = "server" harness = false diff --git a/actix-files/src/error.rs b/actix-files/src/error.rs index 9b30cbaa2..e5f2d4779 100644 --- a/actix-files/src/error.rs +++ b/actix-files/src/error.rs @@ -1,4 +1,4 @@ -use actix_web::{http::StatusCode, HttpResponse, ResponseError}; +use actix_web::{http::StatusCode, ResponseError}; use derive_more::Display; /// Errors which can occur when serving static files. @@ -16,8 +16,8 @@ pub enum FilesError { /// Return `NotFound` for `FilesError` impl ResponseError for FilesError { - fn error_response(&self) -> HttpResponse { - HttpResponse::new(StatusCode::NOT_FOUND) + fn status_code(&self) -> StatusCode { + StatusCode::NOT_FOUND } } diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 9765be3a6..5e4258677 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -1,6 +1,13 @@ # Changes ## Unreleased - 2021-xx-xx +### 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] + +[#2065]: https://github.com/actix/actix-web/pull/2065 ## 3.0.0-beta.5 - 2021-04-02 diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index e77d1139c..573376b07 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", "cookies", "secure-cookies"] +features = ["openssl", "rustls", "compress"] [lib] name = "actix_http" @@ -34,12 +34,6 @@ rustls = ["actix-tls/rustls"] # enable compression support compress = ["flate2", "brotli2"] -# support for cookies -cookies = ["cookie"] - -# support for secure cookies -secure-cookies = ["cookies", "cookie/secure"] - # trust-dns as client dns resolver trust-dns = ["trust-dns-resolver"] @@ -55,7 +49,6 @@ base64 = "0.13" bitflags = "1.2" bytes = "1" bytestring = "1" -cookie = { version = "0.14.1", features = ["percent-encode"], optional = true } derive_more = "0.99.5" encoding_rs = "0.8" futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] } @@ -95,7 +88,7 @@ actix-tls = { version = "3.0.0-beta.5", features = ["openssl"] } criterion = "0.3" env_logger = "0.8" rcgen = "0.8" -serde_derive = "1.0" +serde = { version = "1.0", features = ["derive"] } tls-openssl = { version = "0.10", package = "openssl" } tls-rustls = { version = "0.19", package = "rustls" } diff --git a/actix-http/examples/ws.rs b/actix-http/examples/ws.rs index 4e03aa8ab..af66f7d71 100644 --- a/actix-http/examples/ws.rs +++ b/actix-http/examples/ws.rs @@ -11,7 +11,7 @@ use std::{ }; use actix_codec::Encoder; -use actix_http::{error::Error, ws, HttpService, Request, Response}; +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}; @@ -34,14 +34,14 @@ async fn main() -> io::Result<()> { .await } -async fn handler(req: Request) -> Result { +async fn handler(req: Request) -> Result>, Error> { log::info!("handshaking"); let mut res = ws::handshake(req.head())?; // handshake will always fail under HTTP/2 log::info!("responding"); - Ok(res.streaming(Heartbeat::new(ws::Codec::new()))) + Ok(res.message_body(BodyStream::new(Heartbeat::new(ws::Codec::new())))) } struct Heartbeat { diff --git a/actix-http/src/error.rs b/actix-http/src/error.rs index 0178be80c..03693ff2f 100644 --- a/actix-http/src/error.rs +++ b/actix-http/src/error.rs @@ -14,12 +14,7 @@ use serde::de::value::Error as DeError; use serde_json::error::Error as JsonError; use serde_urlencoded::ser::Error as FormError; -use crate::body::Body; -use crate::helpers::Writer; -use crate::response::{Response, ResponseBuilder}; - -#[cfg(feature = "cookies")] -pub use crate::cookie::ParseError as CookieParseError; +use crate::{body::Body, helpers::Writer, Response, ResponseBuilder}; /// A specialized [`std::result::Result`] /// for actix web operations @@ -378,14 +373,6 @@ impl ResponseError for PayloadError { } } -/// Return `BadRequest` for `cookie::ParseError` -#[cfg(feature = "cookies")] -impl ResponseError for crate::cookie::ParseError { - fn status_code(&self) -> StatusCode { - StatusCode::BAD_REQUEST - } -} - #[derive(Debug, Display, From)] /// A set of errors that can occur during dispatching HTTP requests pub enum DispatchError { @@ -959,13 +946,6 @@ mod tests { assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR); } - #[cfg(feature = "cookies")] - #[test] - fn test_cookie_parse() { - let resp: Response = CookieParseError::EmptyName.error_response(); - assert_eq!(resp.status(), StatusCode::BAD_REQUEST); - } - #[test] fn test_as_response() { let orig = io::Error::new(io::ErrorKind::Other, "other"); diff --git a/actix-http/src/h1/dispatcher.rs b/actix-http/src/h1/dispatcher.rs index bf0365693..bba79217a 100644 --- a/actix-http/src/h1/dispatcher.rs +++ b/actix-http/src/h1/dispatcher.rs @@ -346,7 +346,7 @@ where // send service call error as response Poll::Ready(Err(err)) => { - let res: Response = err.into().into(); + let res = Response::from_error(err.into()); let (res, body) = res.replace_body(()); self.as_mut().send_response(res, body.into_body())?; } diff --git a/actix-http/src/http_message.rs b/actix-http/src/http_message.rs index b1f04e50d..0f4e347e0 100644 --- a/actix-http/src/http_message.rs +++ b/actix-http/src/http_message.rs @@ -9,11 +9,6 @@ use crate::error::{ContentTypeError, ParseError}; use crate::extensions::Extensions; use crate::header::{Header, HeaderMap}; use crate::payload::Payload; -#[cfg(feature = "cookies")] -use crate::{cookie::Cookie, error::CookieParseError}; - -#[cfg(feature = "cookies")] -struct Cookies(Vec>); /// Trait that implements general purpose operations on HTTP messages. pub trait HttpMessage: Sized { @@ -104,41 +99,6 @@ pub trait HttpMessage: Sized { Ok(false) } } - - /// Load request cookies. - #[cfg(feature = "cookies")] - fn cookies(&self) -> Result>>, CookieParseError> { - if self.extensions().get::().is_none() { - let mut cookies = Vec::new(); - for hdr in self.headers().get_all(header::COOKIE) { - let s = - str::from_utf8(hdr.as_bytes()).map_err(CookieParseError::from)?; - for cookie_str in s.split(';').map(|s| s.trim()) { - if !cookie_str.is_empty() { - cookies.push(Cookie::parse_encoded(cookie_str)?.into_owned()); - } - } - } - self.extensions_mut().insert(Cookies(cookies)); - } - - Ok(Ref::map(self.extensions(), |ext| { - &ext.get::().unwrap().0 - })) - } - - /// Return request cookie. - #[cfg(feature = "cookies")] - 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<'a, T> HttpMessage for &'a mut T diff --git a/actix-http/src/lib.rs b/actix-http/src/lib.rs index 574d4ef68..5dd232491 100644 --- a/actix-http/src/lib.rs +++ b/actix-http/src/lib.rs @@ -6,13 +6,11 @@ //! | `openssl` | TLS support via [OpenSSL]. | //! | `rustls` | TLS support via [rustls]. | //! | `compress` | Payload compression support. (Deflate, Gzip & Brotli) | -//! | `cookies` | Support for cookies backed by the [cookie] crate. | //! | `secure-cookies` | Adds for secure cookies. Enables `cookies` feature. | //! | `trust-dns` | Use [trust-dns] as the client DNS resolver. | //! //! [OpenSSL]: https://crates.io/crates/openssl //! [rustls]: https://crates.io/crates/rustls -//! [cookie]: https://crates.io/crates/cookie //! [trust-dns]: https://crates.io/crates/trust-dns #![deny(rust_2018_idioms, nonstandard_style)] @@ -55,9 +53,6 @@ pub mod h2; pub mod test; pub mod ws; -#[cfg(feature = "cookies")] -pub use cookie; - pub use self::builder::HttpServiceBuilder; pub use self::config::{KeepAlive, ServiceConfig}; pub use self::error::{Error, ResponseError, Result}; @@ -78,8 +73,6 @@ pub mod http { pub use http::{uri, Error, Uri}; pub use http::{Method, StatusCode, Version}; - #[cfg(feature = "cookies")] - pub use crate::cookie::{Cookie, CookieBuilder}; pub use crate::header::HeaderMap; /// A collection of HTTP headers and helpers. diff --git a/actix-http/src/message.rs b/actix-http/src/message.rs index 6438ccba0..c89f5311a 100644 --- a/actix-http/src/message.rs +++ b/actix-http/src/message.rs @@ -345,8 +345,8 @@ impl ResponseHead { } 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. + /// Rc here should not be cloned by anyone. + /// It's used to reuse allocation of T and no shared ownership is allowed. head: Rc, } diff --git a/actix-http/src/request.rs b/actix-http/src/request.rs index 197ec11c6..09c6dd296 100644 --- a/actix-http/src/request.rs +++ b/actix-http/src/request.rs @@ -2,16 +2,18 @@ use std::{ cell::{Ref, RefMut}, - fmt, net, + fmt, net, str, }; use http::{header, Method, Uri, Version}; -use crate::extensions::Extensions; -use crate::header::HeaderMap; -use crate::message::{Message, RequestHead}; -use crate::payload::{Payload, PayloadStream}; -use crate::HttpMessage; +use crate::{ + extensions::Extensions, + header::HeaderMap, + message::{Message, RequestHead}, + payload::{Payload, PayloadStream}, + HttpMessage, +}; /// Request pub struct Request

{ diff --git a/actix-http/src/response.rs b/actix-http/src/response.rs index b27f477c9..a96a13bd3 100644 --- a/actix-http/src/response.rs +++ b/actix-http/src/response.rs @@ -14,17 +14,16 @@ use bytes::{Bytes, BytesMut}; use futures_core::Stream; use serde::Serialize; -use crate::body::{Body, BodyStream, MessageBody, ResponseBody}; -use crate::error::Error; -use crate::extensions::Extensions; -use crate::header::{IntoHeaderPair, IntoHeaderValue}; -use crate::http::header::{self, HeaderName}; -use crate::http::{Error as HttpError, HeaderMap, StatusCode}; -use crate::message::{BoxedResponseHead, ConnectionType, ResponseHead}; -#[cfg(feature = "cookies")] use crate::{ - cookie::{Cookie, CookieJar}, - http::header::HeaderValue, + body::{Body, BodyStream, MessageBody, ResponseBody}, + error::Error, + extensions::Extensions, + header::{IntoHeaderPair, IntoHeaderValue}, + http::{ + header::{self, HeaderName}, + Error as HttpError, HeaderMap, StatusCode, + }, + message::{BoxedResponseHead, ConnectionType, ResponseHead}, }; /// An HTTP Response @@ -135,54 +134,6 @@ impl Response { &mut self.head.headers } - /// Get an iterator for the cookies set by this response - #[cfg(feature = "cookies")] - #[inline] - pub fn cookies(&self) -> CookieIter<'_> { - CookieIter { - iter: self.head.headers.get_all(header::SET_COOKIE), - } - } - - /// Add a cookie to this response - #[cfg(feature = "cookies")] - #[inline] - pub fn add_cookie(&mut self, cookie: &Cookie<'_>) -> Result<(), HttpError> { - let h = &mut self.head.headers; - HeaderValue::from_str(&cookie.to_string()) - .map(|c| { - h.append(header::SET_COOKIE, c); - }) - .map_err(|e| e.into()) - } - - /// Remove all cookies with the given name from this response. Returns - /// the number of cookies removed. - #[cfg(feature = "cookies")] - #[inline] - pub fn del_cookie(&mut self, name: &str) -> usize { - let h = &mut self.head.headers; - let vals: Vec = h - .get_all(header::SET_COOKIE) - .map(|v| v.to_owned()) - .collect(); - h.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) { - if c.name() == name { - count += 1; - continue; - } - } - } - h.append(header::SET_COOKIE, v); - } - count - } - /// Connection upgrade status #[inline] pub fn upgrade(&self) -> bool { @@ -304,34 +255,12 @@ impl Future for Response { } } -#[cfg(feature = "cookies")] -pub struct CookieIter<'a> { - iter: header::GetAll<'a>, -} - -#[cfg(feature = "cookies")] -impl<'a> Iterator for CookieIter<'a> { - type Item = Cookie<'a>; - - #[inline] - fn next(&mut self) -> Option> { - for v in self.iter.by_ref() { - if let Ok(c) = Cookie::parse_encoded(v.to_str().ok()?) { - return Some(c); - } - } - None - } -} - /// An HTTP response builder. /// /// This type can be used to construct an instance of `Response` through a builder-like pattern. pub struct ResponseBuilder { head: Option, err: Option, - #[cfg(feature = "cookies")] - cookies: Option, } impl ResponseBuilder { @@ -341,8 +270,6 @@ impl ResponseBuilder { ResponseBuilder { head: Some(BoxedResponseHead::new(status)), err: None, - #[cfg(feature = "cookies")] - cookies: None, } } @@ -409,7 +336,10 @@ impl ResponseBuilder { } /// Replaced with [`Self::insert_header()`]. - #[deprecated = "Replaced with `insert_header((key, value))`."] + #[deprecated( + since = "4.0.0", + note = "Replaced with `insert_header((key, value))`. Will be removed in v5." + )] pub fn set_header(&mut self, key: K, value: V) -> &mut Self where K: TryInto, @@ -430,7 +360,10 @@ impl ResponseBuilder { } /// Replaced with [`Self::append_header()`]. - #[deprecated = "Replaced with `append_header((key, value))`."] + #[deprecated( + since = "4.0.0", + note = "Replaced with `append_header((key, value))`. Will be removed in v5." + )] pub fn header(&mut self, key: K, value: V) -> &mut Self where K: TryInto, @@ -523,63 +456,6 @@ impl ResponseBuilder { self } - /// Set a cookie - /// - /// ``` - /// use actix_http::{http, Request, Response}; - /// - /// fn index(req: Request) -> Response { - /// Response::Ok() - /// .cookie( - /// http::Cookie::build("name", "value") - /// .domain("www.rust-lang.org") - /// .path("/") - /// .secure(true) - /// .http_only(true) - /// .finish(), - /// ) - /// .finish() - /// } - /// ``` - #[cfg(feature = "cookies")] - pub fn cookie<'c>(&mut self, cookie: Cookie<'c>) -> &mut Self { - if self.cookies.is_none() { - let mut jar = CookieJar::new(); - jar.add(cookie.into_owned()); - self.cookies = Some(jar) - } else { - self.cookies.as_mut().unwrap().add(cookie.into_owned()); - } - self - } - - /// Remove cookie - /// - /// ``` - /// use actix_http::{http, Request, Response, HttpMessage}; - /// - /// fn index(req: Request) -> Response { - /// let mut builder = Response::Ok(); - /// - /// if let Some(ref cookie) = req.cookie("name") { - /// builder.del_cookie(cookie); - /// } - /// - /// builder.finish() - /// } - /// ``` - #[cfg(feature = "cookies")] - pub fn del_cookie<'a>(&mut self, cookie: &Cookie<'a>) -> &mut Self { - if self.cookies.is_none() { - self.cookies = Some(CookieJar::new()) - } - let jar = self.cookies.as_mut().unwrap(); - let cookie = cookie.clone().into_owned(); - jar.add_original(cookie.clone()); - jar.remove(cookie); - self - } - /// This method calls provided closure with builder reference if value is `true`. #[doc(hidden)] #[deprecated = "Use an if statement."] @@ -636,19 +512,7 @@ impl ResponseBuilder { return Response::from(Error::from(e)).into_body(); } - // allow unused mut when cookies feature is disabled - #[allow(unused_mut)] - let mut response = self.head.take().expect("cannot reuse response builder"); - - #[cfg(feature = "cookies")] - if let Some(ref jar) = self.cookies { - for cookie in jar.delta() { - match HeaderValue::from_str(&cookie.to_string()) { - Ok(val) => response.headers.append(header::SET_COOKIE, val), - Err(e) => return Response::from(Error::from(e)).into_body(), - }; - } - } + let response = self.head.take().expect("cannot reuse response builder"); Response { head: response, @@ -704,8 +568,6 @@ impl ResponseBuilder { ResponseBuilder { head: self.head.take(), err: self.err.take(), - #[cfg(feature = "cookies")] - cookies: self.cookies.take(), } } } @@ -724,29 +586,9 @@ fn parts<'a>( /// Convert `Response` to a `ResponseBuilder`. Body get dropped. impl From> for ResponseBuilder { fn from(res: Response) -> ResponseBuilder { - #[cfg(feature = "cookies")] - let jar = { - // If this response has cookies, load them into a jar - let mut jar: Option = None; - - for c in res.cookies() { - if let Some(ref mut j) = jar { - j.add_original(c.into_owned()); - } else { - let mut j = CookieJar::new(); - j.add_original(c.into_owned()); - jar = Some(j); - } - } - - jar - }; - ResponseBuilder { head: Some(res.head), err: None, - #[cfg(feature = "cookies")] - cookies: jar, } } } @@ -764,33 +606,9 @@ impl<'a> From<&'a ResponseHead> for ResponseBuilder { msg.no_chunking(!head.chunked()); - #[cfg(feature = "cookies")] - let jar = { - // If this response has cookies, load them into a jar - let mut jar: Option = None; - - let cookies = CookieIter { - iter: head.headers.get_all(header::SET_COOKIE), - }; - - for c in cookies { - if let Some(ref mut j) = jar { - j.add_original(c.into_owned()); - } else { - let mut j = CookieJar::new(); - j.add_original(c.into_owned()); - jar = Some(j); - } - } - - jar - }; - ResponseBuilder { head: Some(msg), err: None, - #[cfg(feature = "cookies")] - cookies: jar, } } } @@ -893,8 +711,6 @@ mod tests { use super::*; use crate::body::Body; use crate::http::header::{HeaderValue, CONTENT_TYPE, COOKIE}; - #[cfg(feature = "cookies")] - use crate::{http::header::SET_COOKIE, HttpMessage}; #[test] fn test_debug() { @@ -906,68 +722,6 @@ mod tests { assert!(dbg.contains("Response")); } - #[cfg(feature = "cookies")] - #[test] - fn test_response_cookies() { - let req = crate::test::TestRequest::default() - .append_header((COOKIE, "cookie1=value1")) - .append_header((COOKIE, "cookie2=value2")) - .finish(); - let cookies = req.cookies().unwrap(); - - let resp = Response::Ok() - .cookie( - crate::http::Cookie::build("name", "value") - .domain("www.rust-lang.org") - .path("/test") - .http_only(true) - .max_age(time::Duration::days(1)) - .finish(), - ) - .del_cookie(&cookies[0]) - .finish(); - - let mut val = resp - .headers() - .get_all(SET_COOKIE) - .map(|v| v.to_str().unwrap().to_owned()) - .collect::>(); - val.sort(); - - // the .del_cookie call - assert!(val[0].starts_with("cookie1=; Max-Age=0;")); - - // the .cookie call - assert_eq!( - val[1], - "name=value; HttpOnly; Path=/test; Domain=www.rust-lang.org; Max-Age=86400" - ); - } - - #[cfg(feature = "cookies")] - #[test] - fn test_update_response_cookies() { - let mut r = Response::Ok() - .cookie(crate::http::Cookie::new("original", "val100")) - .finish(); - - r.add_cookie(&crate::http::Cookie::new("cookie2", "val200")) - .unwrap(); - r.add_cookie(&crate::http::Cookie::new("cookie2", "val250")) - .unwrap(); - r.add_cookie(&crate::http::Cookie::new("cookie3", "val300")) - .unwrap(); - - assert_eq!(r.cookies().count(), 4); - r.del_cookie("cookie2"); - - let mut iter = r.cookies(); - let v = iter.next().unwrap(); - assert_eq!((v.name(), v.value()), ("original", "val100")); - let v = iter.next().unwrap(); - assert_eq!((v.name(), v.value()), ("cookie3", "val300")); - } - #[test] fn test_basic_builder() { let resp = Response::Ok().insert_header(("X-TEST", "value")).finish(); @@ -1026,6 +780,7 @@ mod tests { #[test] fn test_serde_json_in_body() { use serde_json::json; + let resp = Response::build(StatusCode::OK).body(json!({"test-key":"test-value"})); assert_eq!(resp.body().get_ref(), br#"{"test-key":"test-value"}"#); @@ -1101,21 +856,22 @@ mod tests { assert_eq!(resp.body().get_ref(), b"test"); } - #[cfg(feature = "cookies")] #[test] fn test_into_builder() { let mut resp: Response = "test".into(); assert_eq!(resp.status(), StatusCode::OK); - resp.add_cookie(&crate::http::Cookie::new("cookie1", "val100")) - .unwrap(); + resp.headers_mut().insert( + HeaderName::from_static("cookie"), + HeaderValue::from_static("cookie1=val100"), + ); let mut builder: ResponseBuilder = resp.into(); let resp = builder.status(StatusCode::BAD_REQUEST).finish(); assert_eq!(resp.status(), StatusCode::BAD_REQUEST); - let cookie = resp.cookies().next().unwrap(); - assert_eq!((cookie.name(), cookie.value()), ("cookie1", "val100")); + let cookie = resp.headers().get_all("Cookie").next().unwrap(); + assert_eq!(cookie.to_str().unwrap(), "cookie1=val100"); } #[test] diff --git a/actix-http/src/test.rs b/actix-http/src/test.rs index ad3dc74b2..ec781743d 100644 --- a/actix-http/src/test.rs +++ b/actix-http/src/test.rs @@ -13,11 +13,6 @@ use actix_codec::{AsyncRead, AsyncWrite, ReadBuf}; use bytes::{Bytes, BytesMut}; use http::{Method, Uri, Version}; -#[cfg(feature = "cookies")] -use crate::{ - cookie::{Cookie, CookieJar}, - header::{self, HeaderValue}, -}; use crate::{ header::{HeaderMap, IntoHeaderPair}, payload::Payload, @@ -54,8 +49,6 @@ struct Inner { method: Method, uri: Uri, headers: HeaderMap, - #[cfg(feature = "cookies")] - cookies: CookieJar, payload: Option, } @@ -66,8 +59,6 @@ impl Default for TestRequest { uri: Uri::from_str("/").unwrap(), version: Version::HTTP_11, headers: HeaderMap::new(), - #[cfg(feature = "cookies")] - cookies: CookieJar::new(), payload: None, })) } @@ -134,13 +125,6 @@ impl TestRequest { self } - /// Set cookie for this request. - #[cfg(feature = "cookies")] - pub fn cookie<'a>(&mut self, cookie: Cookie<'a>) -> &mut Self { - parts(&mut self.0).cookies.add(cookie.into_owned()); - self - } - /// Set request payload. pub fn set_payload>(&mut self, data: B) -> &mut Self { let mut payload = crate::h1::Payload::empty(); @@ -169,22 +153,6 @@ impl TestRequest { head.version = inner.version; head.headers = inner.headers; - #[cfg(feature = "cookies")] - { - let cookie: String = inner - .cookies - .delta() - // ensure only name=value is written to cookie header - .map(|c| Cookie::new(c.name(), c.value()).encoded().to_string()) - .collect::>() - .join("; "); - - if !cookie.is_empty() { - head.headers - .insert(header::COOKIE, HeaderValue::from_str(&cookie).unwrap()); - } - } - req } } diff --git a/actix-http/src/ws/mod.rs b/actix-http/src/ws/mod.rs index cec73db96..1a82ad839 100644 --- a/actix-http/src/ws/mod.rs +++ b/actix-http/src/ws/mod.rs @@ -9,10 +9,8 @@ use derive_more::{Display, Error, From}; use http::{header, Method, StatusCode}; use crate::{ - error::ResponseError, - header::HeaderValue, - message::RequestHead, - response::{Response, ResponseBuilder}, + error::ResponseError, header::HeaderValue, message::RequestHead, response::Response, + ResponseBuilder, }; mod codec; diff --git a/actix-multipart/src/error.rs b/actix-multipart/src/error.rs index cdbb5d395..5f91c60df 100644 --- a/actix-multipart/src/error.rs +++ b/actix-multipart/src/error.rs @@ -45,11 +45,10 @@ impl ResponseError for MultipartError { #[cfg(test)] mod tests { use super::*; - use actix_web::HttpResponse; #[test] fn test_multipart_error() { - let resp: HttpResponse = MultipartError::Boundary.error_response(); + let resp = MultipartError::Boundary.error_response(); assert_eq!(resp.status(), StatusCode::BAD_REQUEST); } } diff --git a/actix-test/Cargo.toml b/actix-test/Cargo.toml index aa82fd046..db7a50c5e 100644 --- a/actix-test/Cargo.toml +++ b/actix-test/Cargo.toml @@ -20,7 +20,7 @@ openssl = ["tls-openssl", "actix-http/openssl"] [dependencies] actix-codec = "0.4.0-beta.1" -actix-http = { version = "3.0.0-beta.5", features = ["cookies"] } +actix-http = "3.0.0-beta.5" actix-http-test = { version = "3.0.0-beta.4", features = [] } actix-service = "2.0.0-beta.4" actix-utils = "3.0.0-beta.2" diff --git a/actix-test/src/lib.rs b/actix-test/src/lib.rs index bd86c27ad..8fab33289 100644 --- a/actix-test/src/lib.rs +++ b/actix-test/src/lib.rs @@ -37,12 +37,12 @@ use actix_codec::{AsyncRead, AsyncWrite, Framed}; pub use actix_http::test::TestBuffer; use actix_http::{ http::{HeaderMap, Method}, - ws, HttpService, Request, + ws, HttpService, Request, Response, }; use actix_service::{map_config, IntoServiceFactory, ServiceFactory}; use actix_web::{ dev::{AppConfig, MessageBody, Server, Service}, - rt, web, Error, HttpResponse, + rt, web, Error, }; use awc::{error::PayloadError, Client, ClientRequest, ClientResponse, Connector}; use futures_core::Stream; @@ -83,7 +83,7 @@ where S: ServiceFactory + 'static, S::Error: Into + 'static, S::InitError: fmt::Debug, - S::Response: Into> + 'static, + S::Response: Into> + 'static, >::Future: 'static, B: MessageBody + 'static, { @@ -122,7 +122,7 @@ where S: ServiceFactory + 'static, S::Error: Into + 'static, S::InitError: fmt::Debug, - S::Response: Into> + 'static, + S::Response: Into> + 'static, >::Future: 'static, B: MessageBody + 'static, { diff --git a/actix-web-actors/src/ws.rs b/actix-web-actors/src/ws.rs index 77d7041f0..8c575206d 100644 --- a/actix-web-actors/src/ws.rs +++ b/actix-web-actors/src/ws.rs @@ -22,9 +22,9 @@ use actix_http::{ http::HeaderValue, ws::{hash_key, Codec}, }; -use actix_web::dev::HttpResponseBuilder; use actix_web::error::{Error, PayloadError}; use actix_web::http::{header, Method, StatusCode}; +use actix_web::HttpResponseBuilder; use actix_web::{HttpRequest, HttpResponse}; use bytes::{Bytes, BytesMut}; use bytestring::ByteString; diff --git a/actix-web-codegen/tests/test_macro.rs b/actix-web-codegen/tests/test_macro.rs index 7b95edba2..b983e6b1d 100644 --- a/actix-web-codegen/tests/test_macro.rs +++ b/actix-web-codegen/tests/test_macro.rs @@ -1,9 +1,10 @@ use std::future::Future; use std::task::{Context, Poll}; -use actix_utils::future; +use actix_utils::future::{ok, Ready}; use actix_web::dev::{Service, ServiceRequest, ServiceResponse, Transform}; use actix_web::http::header::{HeaderName, HeaderValue}; +use actix_web::http::StatusCode; use actix_web::{http, web::Path, App, Error, HttpResponse, Responder}; use actix_web_codegen::{connect, delete, get, head, options, patch, post, put, route, trace}; use futures_core::future::LocalBoxFuture; @@ -56,12 +57,12 @@ async fn trace_test() -> impl Responder { #[get("/test")] fn auto_async() -> impl Future> { - future::ok(HttpResponse::Ok().finish()) + ok(HttpResponse::Ok().finish()) } #[get("/test")] fn auto_sync() -> impl Future> { - future::ok(HttpResponse::Ok().finish()) + ok(HttpResponse::Ok().finish()) } #[put("/test/{param}")] @@ -103,10 +104,10 @@ where type Error = Error; type Transform = ChangeStatusCodeMiddleware; type InitError = (); - type Future = future::Ready>; + type Future = Ready>; fn new_transform(&self, service: S) -> Self::Future { - future::ok(ChangeStatusCodeMiddleware { service }) + ok(ChangeStatusCodeMiddleware { service }) } } @@ -144,6 +145,7 @@ where #[get("/test/wrap", wrap = "ChangeStatusCode")] async fn get_wrap(_: Path) -> impl Responder { + // panic!("actually never gets called because path failed to extract"); HttpResponse::Ok() } @@ -257,6 +259,10 @@ async fn test_wrap() { let srv = actix_test::start(|| App::new().service(get_wrap)); let request = srv.request(http::Method::GET, srv.url("/test/wrap")); - let response = request.send().await.unwrap(); + let mut response = request.send().await.unwrap(); + assert_eq!(response.status(), StatusCode::NOT_FOUND); assert!(response.headers().contains_key("custom-header")); + let body = response.body().await.unwrap(); + let body = String::from_utf8(body.to_vec()).unwrap(); + assert!(body.contains("wrong number of parameters")); } diff --git a/awc/Cargo.toml b/awc/Cargo.toml index aebf73ef5..27d8bdfbc 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -41,7 +41,7 @@ rustls = ["tls-rustls", "actix-http/rustls"] compress = ["actix-http/compress"] # cookie parsing and cookie jar -cookies = ["actix-http/cookies"] +cookies = ["cookie"] # trust-dns as dns resolver trust-dns = ["actix-http/trust-dns"] @@ -54,6 +54,7 @@ actix-rt = { version = "2.1", default-features = false } base64 = "0.13" bytes = "1" +cookie = { version = "0.15", features = ["percent-encode"], optional = true } derive_more = "0.99.5" futures-core = { version = "0.3.7", default-features = false } itoa = "0.4" diff --git a/awc/src/lib.rs b/awc/src/lib.rs index f1aecbd37..562d6ee7f 100644 --- a/awc/src/lib.rs +++ b/awc/src/lib.rs @@ -93,12 +93,11 @@ #![doc(html_logo_url = "https://actix.rs/img/logo.png")] #![doc(html_favicon_url = "https://actix.rs/favicon.ico")] -use std::convert::TryFrom; -use std::rc::Rc; -use std::time::Duration; +use std::{convert::TryFrom, rc::Rc, time::Duration}; #[cfg(feature = "cookies")] -pub use actix_http::cookie; +pub use cookie; + pub use actix_http::{client::Connector, http}; use actix_http::{ diff --git a/awc/src/request.rs b/awc/src/request.rs index 6ecb64f81..f5cb08f15 100644 --- a/awc/src/request.rs +++ b/awc/src/request.rs @@ -8,14 +8,14 @@ use futures_core::Stream; use serde::Serialize; use actix_http::body::Body; -#[cfg(feature = "cookies")] -use actix_http::cookie::{Cookie, CookieJar}; use actix_http::http::header::{self, IntoHeaderPair}; use actix_http::http::{ uri, ConnectionType, Error as HttpError, HeaderMap, HeaderValue, Method, Uri, Version, }; 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}; @@ -271,7 +271,7 @@ impl ClientRequest { /// async fn main() { /// let resp = awc::Client::new().get("https://www.rust-lang.org") /// .cookie( - /// awc::http::Cookie::build("name", "value") + /// awc::cookie::Cookie::build("name", "value") /// .domain("www.rust-lang.org") /// .path("/") /// .secure(true) @@ -494,7 +494,7 @@ impl ClientRequest { let cookie: String = jar .delta() // ensure only name=value is written to cookie header - .map(|c| Cookie::new(c.name(), c.value()).encoded().to_string()) + .map(|c| c.stripped().encoded().to_string()) .collect::>() .join("; "); diff --git a/awc/src/response.rs b/awc/src/response.rs index 27ba83af7..a966edd08 100644 --- a/awc/src/response.rs +++ b/awc/src/response.rs @@ -20,8 +20,7 @@ use futures_core::{ready, Stream}; use serde::de::DeserializeOwned; #[cfg(feature = "cookies")] -use actix_http::{cookie::Cookie, error::CookieParseError}; - +use crate::cookie::{Cookie, ParseError as CookieParseError}; use crate::error::JsonPayloadError; /// Client Response @@ -80,24 +79,6 @@ impl HttpMessage for ClientResponse { fn extensions_mut(&self) -> RefMut<'_, Extensions> { self.head.extensions_mut() } - - /// Load request cookies. - #[cfg(feature = "cookies")] - 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 - })) - } } impl ClientResponse { @@ -180,6 +161,37 @@ impl ClientResponse { 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 diff --git a/awc/src/test.rs b/awc/src/test.rs index 8e95396b3..1abe78811 100644 --- a/awc/src/test.rs +++ b/awc/src/test.rs @@ -1,14 +1,11 @@ //! Test helpers for actix http client to use during testing. use actix_http::http::header::IntoHeaderPair; use actix_http::http::{StatusCode, Version}; -#[cfg(feature = "cookies")] -use actix_http::{ - cookie::{Cookie, CookieJar}, - http::header::{self, HeaderValue}, -}; use actix_http::{h1, Payload, ResponseHead}; use bytes::Bytes; +#[cfg(feature = "cookies")] +use crate::cookie::{Cookie, CookieJar}; use crate::ClientResponse; /// Test `ClientResponse` builder @@ -92,6 +89,8 @@ impl TestResponse { #[cfg(feature = "cookies")] for cookie in self.cookies.delta() { + use actix_http::http::header::{self, HeaderValue}; + head.headers.insert( header::SET_COOKIE, HeaderValue::from_str(&cookie.encoded().to_string()).unwrap(), diff --git a/awc/src/ws.rs b/awc/src/ws.rs index 8458d3e31..34b71f052 100644 --- a/awc/src/ws.rs +++ b/awc/src/ws.rs @@ -31,8 +31,6 @@ use std::net::SocketAddr; use std::{fmt, str}; use actix_codec::Framed; -#[cfg(feature = "cookies")] -use actix_http::cookie::{Cookie, CookieJar}; use actix_http::{ws, Payload, RequestHead}; use actix_rt::time::timeout; use actix_service::Service; @@ -40,6 +38,8 @@ use actix_service::Service; pub use actix_http::ws::{CloseCode, CloseReason, Codec, Frame, Message}; use crate::connect::{BoxedSocket, ConnectRequest}; +#[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}; @@ -280,7 +280,7 @@ impl WebsocketsRequest { let cookie: String = jar .delta() // ensure only name=value is written to cookie header - .map(|c| Cookie::new(c.name(), c.value()).encoded().to_string()) + .map(|c| c.stripped().encoded().to_string()) .collect::>() .join("; "); diff --git a/awc/tests/test_client.rs b/awc/tests/test_client.rs index a393a6415..f1d29f0bc 100644 --- a/awc/tests/test_client.rs +++ b/awc/tests/test_client.rs @@ -8,6 +8,7 @@ use std::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; @@ -22,9 +23,9 @@ use actix_http_test::test_server; use actix_service::{map_config, pipeline_factory}; use actix_web::{ dev::{AppConfig, BodyEncoding}, - http::{header, Cookie}, + http::header, middleware::Compress, - web, App, Error, HttpMessage, HttpRequest, HttpResponse, + web, App, Error, HttpRequest, HttpResponse, }; use awc::error::{JsonPayloadError, PayloadError, SendRequestError}; diff --git a/src/error.rs b/src/error.rs index 0865257d3..25cdc9feb 100644 --- a/src/error.rs +++ b/src/error.rs @@ -5,7 +5,7 @@ use derive_more::{Display, Error, From}; use serde_json::error::Error as JsonError; use url::ParseError as UrlParseError; -use crate::{http::StatusCode, HttpResponse}; +use crate::http::StatusCode; /// Errors which can occur when attempting to generate resource uri. #[derive(Debug, PartialEq, Display, From)] @@ -90,12 +90,11 @@ pub enum JsonPayloadError { impl std::error::Error for JsonPayloadError {} -/// Return `BadRequest` for `JsonPayloadError` impl ResponseError for JsonPayloadError { - fn error_response(&self) -> HttpResponse { + fn status_code(&self) -> StatusCode { match *self { - JsonPayloadError::Overflow => HttpResponse::new(StatusCode::PAYLOAD_TOO_LARGE), - _ => HttpResponse::new(StatusCode::BAD_REQUEST), + JsonPayloadError::Overflow => StatusCode::PAYLOAD_TOO_LARGE, + _ => StatusCode::BAD_REQUEST, } } } @@ -168,26 +167,25 @@ mod tests { #[test] fn test_urlencoded_error() { - let resp: HttpResponse = - UrlencodedError::Overflow { size: 0, limit: 0 }.error_response(); + let resp = UrlencodedError::Overflow { size: 0, limit: 0 }.error_response(); assert_eq!(resp.status(), StatusCode::PAYLOAD_TOO_LARGE); - let resp: HttpResponse = UrlencodedError::UnknownLength.error_response(); + let resp = UrlencodedError::UnknownLength.error_response(); assert_eq!(resp.status(), StatusCode::LENGTH_REQUIRED); - let resp: HttpResponse = UrlencodedError::ContentType.error_response(); + let resp = UrlencodedError::ContentType.error_response(); assert_eq!(resp.status(), StatusCode::BAD_REQUEST); } #[test] fn test_json_payload_error() { - let resp: HttpResponse = JsonPayloadError::Overflow.error_response(); + let resp = JsonPayloadError::Overflow.error_response(); assert_eq!(resp.status(), StatusCode::PAYLOAD_TOO_LARGE); - let resp: HttpResponse = JsonPayloadError::ContentType.error_response(); + let resp = JsonPayloadError::ContentType.error_response(); assert_eq!(resp.status(), StatusCode::BAD_REQUEST); } #[test] fn test_query_payload_error() { - let resp: HttpResponse = QueryPayloadError::Deserialize( + let resp = QueryPayloadError::Deserialize( serde_urlencoded::from_str::("bad query").unwrap_err(), ) .error_response(); @@ -196,9 +194,9 @@ mod tests { #[test] fn test_readlines_error() { - let resp: HttpResponse = ReadlinesError::LimitOverflow.error_response(); + let resp = ReadlinesError::LimitOverflow.error_response(); assert_eq!(resp.status(), StatusCode::PAYLOAD_TOO_LARGE); - let resp: HttpResponse = ReadlinesError::EncodingError.error_response(); + let resp = ReadlinesError::EncodingError.error_response(); assert_eq!(resp.status(), StatusCode::BAD_REQUEST); } } diff --git a/src/handler.rs b/src/handler.rs index e005a96a6..822dcafdd 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -3,20 +3,23 @@ use std::marker::PhantomData; use std::pin::Pin; use std::task::{Context, Poll}; -use actix_http::{Error, Response}; +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; -use crate::request::HttpRequest; -use crate::responder::Responder; -use crate::service::{ServiceRequest, ServiceResponse}; +use crate::{ + extract::FromRequest, + request::HttpRequest, + responder::Responder, + response::HttpResponse, + service::{ServiceRequest, ServiceResponse}, +}; -/// A request handler is an async function that accepts zero or more parameters that can be -/// extracted from a request (ie, [`impl FromRequest`](crate::FromRequest)) and returns a type that can be converted into -/// an [`HttpResponse`](crate::HttpResponse) (ie, [`impl Responder`](crate::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). /// /// 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. @@ -102,9 +105,7 @@ where type Error = Error; type Future = HandlerServiceFuture; - fn poll_ready(&self, _: &mut Context<'_>) -> Poll> { - Poll::Ready(Ok(())) - } + actix_service::always_ready!(); fn call(&self, req: ServiceRequest) -> Self::Future { let (req, mut payload) = req.into_parts(); @@ -147,9 +148,9 @@ where let state = HandlerServiceFuture::Handle(fut, req.take()); self.as_mut().set(state); } - Err(e) => { - let res: Response = e.into().into(); + Err(err) => { let req = req.take().unwrap(); + let res = HttpResponse::from_error(err.into()); return Poll::Ready(Ok(ServiceResponse::new(req, res))); } }; diff --git a/src/lib.rs b/src/lib.rs index 1d5d5b83d..54db969df 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -86,6 +86,7 @@ mod request; mod request_data; mod resource; mod responder; +mod response; mod rmap; mod route; mod scope; @@ -95,19 +96,20 @@ pub mod test; pub(crate) mod types; pub mod web; -#[cfg(feature = "cookies")] -pub use actix_http::cookie; -pub use actix_http::Response as HttpResponse; +pub use actix_http::Response as BaseHttpResponse; pub use actix_http::{body, Error, HttpMessage, ResponseError, Result}; #[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::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::route::Route; pub use crate::scope::Scope; pub use crate::server::HttpServer; @@ -139,7 +141,7 @@ pub mod dev { pub use actix_http::body::{Body, BodySize, MessageBody, ResponseBody, SizedStream}; #[cfg(feature = "compress")] pub use actix_http::encoding::Decoder as Decompress; - pub use actix_http::ResponseBuilder as HttpResponseBuilder; + 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; @@ -189,4 +191,26 @@ pub mod dev { 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/request.rs b/src/request.rs index f3cbc07b8..e3da991de 100644 --- a/src/request.rs +++ b/src/request.rs @@ -1,19 +1,27 @@ -use std::cell::{Ref, RefCell, RefMut}; -use std::rc::Rc; -use std::{fmt, net}; +use std::{ + cell::{Ref, RefCell, RefMut}, + fmt, net, + rc::Rc, + str, +}; -use actix_http::http::{HeaderMap, Method, Uri, Version}; -use actix_http::{Error, Extensions, HttpMessage, Message, Payload, RequestHead}; +use actix_http::{ + http::{HeaderMap, Method, Uri, Version}, + Error, Extensions, HttpMessage, Message, Payload, RequestHead, +}; use actix_router::{Path, Url}; use actix_utils::future::{ok, Ready}; +#[cfg(feature = "cookies")] +use cookie::{Cookie, ParseError as CookieParseError}; use smallvec::SmallVec; -use crate::app_service::AppInitServiceState; -use crate::config::AppConfig; -use crate::error::UrlGenerationError; -use crate::extract::FromRequest; -use crate::info::ConnectionInfo; -use crate::rmap::ResourceMap; +use crate::{ + app_service::AppInitServiceState, config::AppConfig, error::UrlGenerationError, + extract::FromRequest, info::ConnectionInfo, rmap::ResourceMap, +}; + +#[cfg(feature = "cookies")] +struct Cookies(Vec>); #[derive(Clone)] /// An HTTP Request @@ -260,6 +268,42 @@ impl HttpRequest { fn app_state(&self) -> &AppInitServiceState { &*self.inner.app_state } + + /// Load request cookies. + #[cfg(feature = "cookies")] + pub fn cookies(&self) -> Result>>, CookieParseError> { + use actix_http::http::header::COOKIE; + + if self.extensions().get::().is_none() { + let mut cookies = Vec::new(); + for hdr in self.headers().get_all(COOKIE) { + let s = str::from_utf8(hdr.as_bytes()).map_err(CookieParseError::from)?; + for cookie_str in s.split(';').map(|s| s.trim()) { + if !cookie_str.is_empty() { + cookies.push(Cookie::parse_encoded(cookie_str)?.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 HttpMessage for HttpRequest { diff --git a/src/responder.rs b/src/responder.rs index b75c95083..66c93d257 100644 --- a/src/responder.rs +++ b/src/responder.rs @@ -3,11 +3,10 @@ use std::fmt; use actix_http::{ error::InternalError, http::{header::IntoHeaderPair, Error as HttpError, HeaderMap, StatusCode}, - ResponseBuilder, }; use bytes::{Bytes, BytesMut}; -use crate::{Error, HttpRequest, HttpResponse}; +use crate::{Error, HttpRequest, HttpResponse, HttpResponseBuilder}; /// Trait implemented by types that can be converted to an HTTP response. /// @@ -66,11 +65,32 @@ impl Responder for HttpResponse { } } +impl Responder for actix_http::Response { + #[inline] + fn respond_to(self, _: &HttpRequest) -> HttpResponse { + HttpResponse::from(self) + } +} + +impl Responder for HttpResponseBuilder { + #[inline] + fn respond_to(mut self, _: &HttpRequest) -> HttpResponse { + self.finish() + } +} + +impl Responder for actix_http::ResponseBuilder { + #[inline] + fn respond_to(mut self, _: &HttpRequest) -> HttpResponse { + HttpResponse::from(self.finish()) + } +} + impl Responder for Option { fn respond_to(self, req: &HttpRequest) -> HttpResponse { match self { - Some(t) => t.respond_to(req), - None => HttpResponse::build(StatusCode::NOT_FOUND).finish(), + Some(val) => val.respond_to(req), + None => HttpResponse::new(StatusCode::NOT_FOUND), } } } @@ -88,13 +108,6 @@ where } } -impl Responder for ResponseBuilder { - #[inline] - fn respond_to(mut self, _: &HttpRequest) -> HttpResponse { - self.finish() - } -} - impl Responder for (T, StatusCode) { fn respond_to(self, req: &HttpRequest) -> HttpResponse { let mut res = self.0.respond_to(req); diff --git a/src/response.rs b/src/response.rs new file mode 100644 index 000000000..1d017f080 --- /dev/null +++ b/src/response.rs @@ -0,0 +1,968 @@ +use std::{ + cell::{Ref, RefMut}, + convert::TryInto, + fmt, + future::Future, + pin::Pin, + task::{Context, Poll}, +}; + +use actix_http::{ + body::{Body, BodyStream, MessageBody, ResponseBody}, + http::{ + header::{self, HeaderMap, HeaderName, IntoHeaderPair, IntoHeaderValue}, + ConnectionType, Error as HttpError, StatusCode, + }, + Extensions, Response, ResponseHead, +}; +use bytes::Bytes; +use futures_core::Stream; +use serde::Serialize; + +#[cfg(feature = "cookies")] +use actix_http::http::header::HeaderValue; +#[cfg(feature = "cookies")] +use cookie::{Cookie, CookieJar}; + +use crate::error::Error; + +/// An HTTP Response +pub struct HttpResponse { + res: Response, + error: Option, +} + +impl HttpResponse { + /// Create HTTP response builder with specific status. + #[inline] + pub fn build(status: StatusCode) -> HttpResponseBuilder { + HttpResponseBuilder::new(status) + } + + /// Create HTTP response builder + #[inline] + pub fn build_from>(source: T) -> HttpResponseBuilder { + source.into() + } + + /// Create a response. + #[inline] + pub fn new(status: StatusCode) -> Self { + Self { + res: Response::new(status), + error: None, + } + } + + /// 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), + } + } + + /// Convert response to response with body + pub fn into_body(self) -> HttpResponse { + HttpResponse { + res: self.res.into_body(), + error: self.error, + } + } +} + +impl HttpResponse { + /// Constructs a response with body + #[inline] + pub fn with_body(status: StatusCode, body: B) -> Self { + Self { + res: Response::with_body(status, body), + error: None, + } + } + + /// Returns a reference to response head. + #[inline] + pub fn head(&self) -> &ResponseHead { + self.res.head() + } + + /// Returns a mutable reference to response head. + #[inline] + pub fn head_mut(&mut self) -> &mut ResponseHead { + self.res.head_mut() + } + + /// The source `error` for this response + #[inline] + pub fn error(&self) -> Option<&Error> { + self.error.as_ref() + } + + /// Get the response status code + #[inline] + pub fn status(&self) -> StatusCode { + self.res.status() + } + + /// Set the `StatusCode` for this response + #[inline] + pub fn status_mut(&mut self) -> &mut StatusCode { + self.res.status_mut() + } + + /// Get the headers from the response + #[inline] + pub fn headers(&self) -> &HeaderMap { + self.res.headers() + } + + /// Get a mutable reference to the headers + #[inline] + pub fn headers_mut(&mut self) -> &mut HeaderMap { + self.res.headers_mut() + } + + /// Get an iterator for the cookies set by this response. + #[cfg(feature = "cookies")] + pub fn cookies(&self) -> CookieIter<'_> { + CookieIter { + iter: self.headers().get_all(header::SET_COOKIE), + } + } + + /// Add a cookie to this response + #[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()) + } + + /// Remove all cookies with the given name from this response. Returns + /// the number of cookies removed. + #[cfg(feature = "cookies")] + pub fn del_cookie(&mut self, name: &str) -> usize { + let headers = self.headers_mut(); + + let vals: Vec = headers + .get_all(header::SET_COOKIE) + .map(|v| v.to_owned()) + .collect(); + + 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) { + if c.name() == name { + count += 1; + continue; + } + } + } + + // put set-cookie header head back if it does not validate + headers.append(header::SET_COOKIE, v); + } + + count + } + + /// Connection upgrade status + #[inline] + pub fn upgrade(&self) -> bool { + self.res.upgrade() + } + + /// Keep-alive status for this connection + pub fn keep_alive(&self) -> bool { + self.res.keep_alive() + } + + /// Responses extensions + #[inline] + pub fn extensions(&self) -> Ref<'_, Extensions> { + self.res.extensions() + } + + /// Mutable reference to a the response's extensions + #[inline] + pub fn extensions_mut(&mut self) -> RefMut<'_, Extensions> { + self.res.extensions_mut() + } + + /// Get body of this response + #[inline] + pub fn body(&self) -> &ResponseBody { + self.res.body() + } + + /// Set a body + pub fn set_body(self, body: B2) -> HttpResponse { + HttpResponse { + res: self.res.set_body(body), + error: None, + // error: self.error, ?? + } + } + + /// Split response and body + pub fn into_parts(self) -> (HttpResponse<()>, ResponseBody) { + let (head, body) = self.res.into_parts(); + + ( + HttpResponse { + res: head, + error: None, + }, + body, + ) + } + + /// Drop request's body + pub fn drop_body(self) -> HttpResponse<()> { + HttpResponse { + res: self.res.drop_body(), + error: None, + } + } + + /// Set a body and return previous body value + pub fn map_body(self, f: F) -> HttpResponse + where + F: FnOnce(&mut ResponseHead, ResponseBody) -> ResponseBody, + { + HttpResponse { + res: self.res.map_body(f), + error: self.error, + } + } + + /// Extract response body + pub fn take_body(&mut self) -> ResponseBody { + self.res.take_body() + } +} + +impl fmt::Debug for HttpResponse { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("HttpResponse") + .field("error", &self.error) + .field("res", &self.res) + .finish() + } +} + +impl From> for HttpResponse { + fn from(res: Response) -> Self { + HttpResponse { res, error: None } + } +} + +impl From for HttpResponse { + fn from(err: Error) -> Self { + HttpResponse::from_error(err) + } +} + +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 { + // eprintln!("impl From> for Response let Some(err)"); + // return Response::from_error(err).into_body(); + // } + + res.res + } +} + +impl Future for HttpResponse { + type Output = Result; + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + if let Some(err) = self.error.take() { + eprintln!("httpresponse future error"); + return Poll::Ready(Ok(Response::from_error(err).into_body())); + } + + let res = &mut self.res; + actix_rt::pin!(res); + + res.poll(cx) + } +} + +/// An HTTP response builder. +/// +/// This type can be used to construct an instance of `Response` through a builder-like pattern. +pub struct HttpResponseBuilder { + head: Option, + err: Option, + #[cfg(feature = "cookies")] + cookies: Option, +} + +impl HttpResponseBuilder { + #[inline] + /// Create response builder + pub fn new(status: StatusCode) -> Self { + Self { + head: Some(ResponseHead::new(status)), + err: None, + #[cfg(feature = "cookies")] + cookies: None, + } + } + + /// Set HTTP status code of this response. + #[inline] + pub fn status(&mut self, status: StatusCode) -> &mut Self { + if let Some(parts) = self.inner() { + parts.status = status; + } + self + } + + /// Insert a header, replacing any that were set with an equivalent field name. + /// + /// ``` + /// use actix_web::{HttpResponse, http::header}; + /// + /// HttpResponse::Ok() + /// .insert_header(header::ContentType(mime::APPLICATION_JSON)) + /// .insert_header(("X-TEST", "value")) + /// .finish(); + /// ``` + pub fn insert_header(&mut self, header: H) -> &mut Self + where + H: IntoHeaderPair, + { + if let Some(parts) = self.inner() { + match header.try_into_header_pair() { + Ok((key, value)) => { + parts.headers.insert(key, value); + } + Err(e) => self.err = Some(e.into()), + }; + } + + self + } + + /// Append a header, keeping any that were set with an equivalent field name. + /// + /// ``` + /// use actix_web::{HttpResponse, http::header}; + /// + /// HttpResponse::Ok() + /// .append_header(header::ContentType(mime::APPLICATION_JSON)) + /// .append_header(("X-TEST", "value1")) + /// .append_header(("X-TEST", "value2")) + /// .finish(); + /// ``` + pub fn append_header(&mut self, header: H) -> &mut Self + where + H: IntoHeaderPair, + { + if let Some(parts) = self.inner() { + match header.try_into_header_pair() { + Ok((key, value)) => parts.headers.append(key, value), + Err(e) => self.err = Some(e.into()), + }; + } + + self + } + + /// Replaced with [`Self::insert_header()`]. + #[deprecated = "Replaced with `insert_header((key, value))`."] + pub fn set_header(&mut self, key: K, value: V) -> &mut Self + where + K: TryInto, + K::Error: Into, + V: IntoHeaderValue, + { + if self.err.is_some() { + return self; + } + + match (key.try_into(), value.try_into_value()) { + (Ok(name), Ok(value)) => return self.insert_header((name, value)), + (Err(err), _) => self.err = Some(err.into()), + (_, Err(err)) => self.err = Some(err.into()), + } + + self + } + + /// Replaced with [`Self::append_header()`]. + #[deprecated = "Replaced with `append_header((key, value))`."] + pub fn header(&mut self, key: K, value: V) -> &mut Self + where + K: TryInto, + K::Error: Into, + V: IntoHeaderValue, + { + if self.err.is_some() { + return self; + } + + match (key.try_into(), value.try_into_value()) { + (Ok(name), Ok(value)) => return self.append_header((name, value)), + (Err(err), _) => self.err = Some(err.into()), + (_, Err(err)) => self.err = Some(err.into()), + } + + self + } + + /// Set the custom reason for the response. + #[inline] + pub fn reason(&mut self, reason: &'static str) -> &mut Self { + if let Some(parts) = self.inner() { + parts.reason = Some(reason); + } + self + } + + /// Set connection type to KeepAlive + #[inline] + pub fn keep_alive(&mut self) -> &mut Self { + if let Some(parts) = self.inner() { + parts.set_connection_type(ConnectionType::KeepAlive); + } + self + } + + /// Set connection type to Upgrade + #[inline] + pub fn upgrade(&mut self, value: V) -> &mut Self + where + V: IntoHeaderValue, + { + if let Some(parts) = self.inner() { + parts.set_connection_type(ConnectionType::Upgrade); + } + + if let Ok(value) = value.try_into_value() { + self.insert_header((header::UPGRADE, value)); + } + + self + } + + /// Force close connection, even if it is marked as keep-alive + #[inline] + pub fn force_close(&mut self) -> &mut Self { + if let Some(parts) = self.inner() { + parts.set_connection_type(ConnectionType::Close); + } + self + } + + /// Disable chunked transfer encoding for HTTP/1.1 streaming responses. + #[inline] + pub fn no_chunking(&mut self, len: u64) -> &mut Self { + let mut buf = itoa::Buffer::new(); + self.insert_header((header::CONTENT_LENGTH, buf.format(len))); + + if let Some(parts) = self.inner() { + parts.no_chunking(true); + } + self + } + + /// Set response content type. + #[inline] + pub fn content_type(&mut self, value: V) -> &mut Self + where + V: IntoHeaderValue, + { + if let Some(parts) = self.inner() { + match value.try_into_value() { + Ok(value) => { + parts.headers.insert(header::CONTENT_TYPE, value); + } + Err(e) => self.err = Some(e.into()), + }; + } + self + } + + /// Set a cookie. + /// + /// ``` + /// use actix_web::{HttpResponse, cookie::Cookie}; + /// + /// HttpResponse::Ok() + /// .cookie( + /// Cookie::build("name", "value") + /// .domain("www.rust-lang.org") + /// .path("/") + /// .secure(true) + /// .http_only(true) + /// .finish(), + /// ) + /// .finish(); + /// ``` + #[cfg(feature = "cookies")] + pub fn cookie<'c>(&mut self, cookie: Cookie<'c>) -> &mut Self { + if self.cookies.is_none() { + let mut jar = 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. + /// + /// ``` + /// use actix_web::{HttpRequest, HttpResponse, Responder}; + /// + /// async fn handler(req: HttpRequest) -> impl Responder { + /// let mut builder = HttpResponse::Ok(); + /// + /// if let Some(ref cookie) = req.cookie("name") { + /// builder.del_cookie(cookie); + /// } + /// + /// builder.finish() + /// } + /// ``` + #[cfg(feature = "cookies")] + pub fn del_cookie(&mut self, cookie: &Cookie<'_>) -> &mut Self { + if self.cookies.is_none() { + self.cookies = Some(CookieJar::new()) + } + let jar = self.cookies.as_mut().unwrap(); + let cookie = cookie.clone().into_owned(); + jar.add_original(cookie.clone()); + jar.remove(cookie); + self + } + + /// Responses extensions + #[inline] + pub fn extensions(&self) -> Ref<'_, Extensions> { + let head = self.head.as_ref().expect("cannot reuse response builder"); + head.extensions() + } + + /// 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_mut() + } + + /// Set a body and generate `Response`. + /// + /// `ResponseBuilder` can not be used after this call. + #[inline] + pub fn body>(&mut self, body: B) -> HttpResponse { + self.message_body(body.into()) + } + + /// Set a body and generate `Response`. + /// + /// `ResponseBuilder` can not be used after this call. + pub fn message_body(&mut self, body: B) -> HttpResponse { + if let Some(err) = self.err.take() { + return HttpResponse::from_error(Error::from(err)).into_body(); + } + + // allow unused mut when cookies feature is disabled + #[allow(unused_mut)] + let mut head = self.head.take().expect("cannot reuse response builder"); + + let mut res = HttpResponse::with_body(StatusCode::OK, body); + *res.head_mut() = head; + + #[cfg(feature = "cookies")] + if let Some(ref jar) = self.cookies { + for cookie in jar.delta() { + match HeaderValue::from_str(&cookie.to_string()) { + Ok(val) => res.headers_mut().append(header::SET_COOKIE, val), + Err(err) => return HttpResponse::from_error(Error::from(err)).into_body(), + }; + } + } + + res + } + + /// Set a streaming body and generate `Response`. + /// + /// `ResponseBuilder` can not be used after this call. + #[inline] + pub fn streaming(&mut self, stream: S) -> HttpResponse + where + S: Stream> + Unpin + 'static, + E: Into + 'static, + { + self.body(Body::from_message(BodyStream::new(stream))) + } + + /// Set a json body and generate `Response` + /// + /// `ResponseBuilder` can not be used after this call. + pub fn json(&mut self, value: impl Serialize) -> HttpResponse { + match serde_json::to_string(&value) { + Ok(body) => { + let contains = if let Some(parts) = self.inner() { + parts.headers.contains_key(header::CONTENT_TYPE) + } else { + true + }; + + if !contains { + self.insert_header((header::CONTENT_TYPE, mime::APPLICATION_JSON)); + } + + self.body(Body::from(body)) + } + Err(e) => HttpResponse::from_error(Error::from(e)), + } + } + + /// Set an empty body and generate `Response` + /// + /// `ResponseBuilder` can not be used after this call. + #[inline] + pub fn finish(&mut self) -> HttpResponse { + self.body(Body::Empty) + } + + /// This method construct new `ResponseBuilder` + pub fn take(&mut self) -> Self { + Self { + head: self.head.take(), + err: self.err.take(), + #[cfg(feature = "cookies")] + cookies: self.cookies.take(), + } + } + + #[inline] + fn inner(&mut self) -> Option<&mut ResponseHead> { + if self.err.is_some() { + return None; + } + + self.head.as_mut() + } +} + +impl From for HttpResponse { + fn from(mut builder: HttpResponseBuilder) -> Self { + builder.finish() + } +} + +impl From for Response { + fn from(mut builder: HttpResponseBuilder) -> Self { + builder.finish().into() + } +} + +impl Future for HttpResponseBuilder { + type Output = Result; + + fn poll(mut self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll { + eprintln!("httpresponse future error"); + Poll::Ready(Ok(self.finish())) + } +} + +#[cfg(feature = "cookies")] +pub struct CookieIter<'a> { + iter: header::GetAll<'a>, +} + +#[cfg(feature = "cookies")] +impl<'a> Iterator for CookieIter<'a> { + type Item = Cookie<'a>; + + #[inline] + fn next(&mut self) -> Option> { + for v in self.iter.by_ref() { + if let Ok(c) = Cookie::parse_encoded(v.to_str().ok()?) { + return Some(c); + } + } + None + } +} + +mod http_codes { + //! Status code based HTTP response builders. + + use actix_http::http::StatusCode; + + use super::{HttpResponse, HttpResponseBuilder}; + + macro_rules! static_resp { + ($name:ident, $status:expr) => { + #[allow(non_snake_case, missing_docs)] + pub fn $name() -> HttpResponseBuilder { + HttpResponseBuilder::new($status) + } + }; + } + + impl HttpResponse { + static_resp!(Continue, StatusCode::CONTINUE); + static_resp!(SwitchingProtocols, StatusCode::SWITCHING_PROTOCOLS); + static_resp!(Processing, StatusCode::PROCESSING); + + static_resp!(Ok, StatusCode::OK); + static_resp!(Created, StatusCode::CREATED); + static_resp!(Accepted, StatusCode::ACCEPTED); + static_resp!( + NonAuthoritativeInformation, + StatusCode::NON_AUTHORITATIVE_INFORMATION + ); + + static_resp!(NoContent, StatusCode::NO_CONTENT); + static_resp!(ResetContent, StatusCode::RESET_CONTENT); + static_resp!(PartialContent, StatusCode::PARTIAL_CONTENT); + static_resp!(MultiStatus, StatusCode::MULTI_STATUS); + static_resp!(AlreadyReported, StatusCode::ALREADY_REPORTED); + + static_resp!(MultipleChoices, StatusCode::MULTIPLE_CHOICES); + static_resp!(MovedPermanently, StatusCode::MOVED_PERMANENTLY); + static_resp!(Found, StatusCode::FOUND); + static_resp!(SeeOther, StatusCode::SEE_OTHER); + static_resp!(NotModified, StatusCode::NOT_MODIFIED); + static_resp!(UseProxy, StatusCode::USE_PROXY); + static_resp!(TemporaryRedirect, StatusCode::TEMPORARY_REDIRECT); + static_resp!(PermanentRedirect, StatusCode::PERMANENT_REDIRECT); + + static_resp!(BadRequest, StatusCode::BAD_REQUEST); + static_resp!(NotFound, StatusCode::NOT_FOUND); + static_resp!(Unauthorized, StatusCode::UNAUTHORIZED); + static_resp!(PaymentRequired, StatusCode::PAYMENT_REQUIRED); + static_resp!(Forbidden, StatusCode::FORBIDDEN); + static_resp!(MethodNotAllowed, StatusCode::METHOD_NOT_ALLOWED); + static_resp!(NotAcceptable, StatusCode::NOT_ACCEPTABLE); + static_resp!( + ProxyAuthenticationRequired, + StatusCode::PROXY_AUTHENTICATION_REQUIRED + ); + static_resp!(RequestTimeout, StatusCode::REQUEST_TIMEOUT); + static_resp!(Conflict, StatusCode::CONFLICT); + static_resp!(Gone, StatusCode::GONE); + static_resp!(LengthRequired, StatusCode::LENGTH_REQUIRED); + static_resp!(PreconditionFailed, StatusCode::PRECONDITION_FAILED); + static_resp!(PreconditionRequired, StatusCode::PRECONDITION_REQUIRED); + static_resp!(PayloadTooLarge, StatusCode::PAYLOAD_TOO_LARGE); + static_resp!(UriTooLong, StatusCode::URI_TOO_LONG); + static_resp!(UnsupportedMediaType, StatusCode::UNSUPPORTED_MEDIA_TYPE); + static_resp!(RangeNotSatisfiable, StatusCode::RANGE_NOT_SATISFIABLE); + static_resp!(ExpectationFailed, StatusCode::EXPECTATION_FAILED); + static_resp!(UnprocessableEntity, StatusCode::UNPROCESSABLE_ENTITY); + static_resp!(TooManyRequests, StatusCode::TOO_MANY_REQUESTS); + static_resp!( + RequestHeaderFieldsTooLarge, + StatusCode::REQUEST_HEADER_FIELDS_TOO_LARGE + ); + static_resp!( + UnavailableForLegalReasons, + StatusCode::UNAVAILABLE_FOR_LEGAL_REASONS + ); + + static_resp!(InternalServerError, StatusCode::INTERNAL_SERVER_ERROR); + static_resp!(NotImplemented, StatusCode::NOT_IMPLEMENTED); + static_resp!(BadGateway, StatusCode::BAD_GATEWAY); + static_resp!(ServiceUnavailable, StatusCode::SERVICE_UNAVAILABLE); + static_resp!(GatewayTimeout, StatusCode::GATEWAY_TIMEOUT); + static_resp!(VersionNotSupported, StatusCode::HTTP_VERSION_NOT_SUPPORTED); + static_resp!(VariantAlsoNegotiates, StatusCode::VARIANT_ALSO_NEGOTIATES); + static_resp!(InsufficientStorage, StatusCode::INSUFFICIENT_STORAGE); + static_resp!(LoopDetected, StatusCode::LOOP_DETECTED); + } + + #[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); + assert_eq!(resp.status(), StatusCode::OK); + } + } +} + +#[cfg(test)] +mod tests { + use bytes::{Bytes, BytesMut}; + use serde_json::json; + + use super::{HttpResponse as Response, HttpResponseBuilder as ResponseBuilder}; + use crate::dev::{Body, MessageBody, ResponseBody}; + use crate::http::header::{self, HeaderValue, CONTENT_TYPE, COOKIE}; + use crate::http::StatusCode; + + #[test] + fn test_debug() { + let resp = Response::Ok() + .append_header((COOKIE, HeaderValue::from_static("cookie1=value1; "))) + .append_header((COOKIE, HeaderValue::from_static("cookie2=value2; "))) + .finish(); + let dbg = format!("{:?}", resp); + assert!(dbg.contains("Response")); + } + + #[test] + fn test_basic_builder() { + let resp = Response::Ok().insert_header(("X-TEST", "value")).finish(); + assert_eq!(resp.status(), StatusCode::OK); + } + + #[test] + fn test_upgrade() { + let resp = ResponseBuilder::new(StatusCode::OK) + .upgrade("websocket") + .finish(); + assert!(resp.upgrade()); + assert_eq!( + resp.headers().get(header::UPGRADE).unwrap(), + HeaderValue::from_static("websocket") + ); + } + + #[test] + fn test_force_close() { + let resp = ResponseBuilder::new(StatusCode::OK).force_close().finish(); + assert!(!resp.keep_alive()) + } + + #[test] + fn test_content_type() { + let resp = ResponseBuilder::new(StatusCode::OK) + .content_type("text/plain") + .body(Body::Empty); + assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "text/plain") + } + + pub async fn read_body(mut body: ResponseBody) -> Bytes + where + B: MessageBody + Unpin, + { + use futures_util::StreamExt as _; + + let mut bytes = BytesMut::new(); + while let Some(item) = body.next().await { + bytes.extend_from_slice(&item.unwrap()); + } + bytes.freeze() + } + + #[actix_rt::test] + async fn test_json() { + let mut resp = Response::Ok().json(vec!["v1", "v2", "v3"]); + let ct = resp.headers().get(CONTENT_TYPE).unwrap(); + assert_eq!(ct, HeaderValue::from_static("application/json")); + assert_eq!( + read_body(resp.take_body()).await.as_ref(), + br#"["v1","v2","v3"]"# + ); + + let mut resp = Response::Ok().json(&["v1", "v2", "v3"]); + let ct = resp.headers().get(CONTENT_TYPE).unwrap(); + assert_eq!(ct, HeaderValue::from_static("application/json")); + assert_eq!( + read_body(resp.take_body()).await.as_ref(), + br#"["v1","v2","v3"]"# + ); + + // content type override + let mut resp = Response::Ok() + .insert_header((CONTENT_TYPE, "text/json")) + .json(&vec!["v1", "v2", "v3"]); + let ct = resp.headers().get(CONTENT_TYPE).unwrap(); + assert_eq!(ct, HeaderValue::from_static("text/json")); + assert_eq!( + read_body(resp.take_body()).await.as_ref(), + br#"["v1","v2","v3"]"# + ); + } + + #[actix_rt::test] + async fn test_serde_json_in_body() { + use serde_json::json; + let mut resp = Response::Ok().body(json!({"test-key":"test-value"})); + assert_eq!( + read_body(resp.take_body()).await.as_ref(), + br#"{"test-key":"test-value"}"# + ); + } + + #[test] + fn response_builder_header_insert_kv() { + let mut res = Response::Ok(); + res.insert_header(("Content-Type", "application/octet-stream")); + let res = res.finish(); + + assert_eq!( + res.headers().get("Content-Type"), + Some(&HeaderValue::from_static("application/octet-stream")) + ); + } + + #[test] + fn response_builder_header_insert_typed() { + let mut res = Response::Ok(); + res.insert_header((header::CONTENT_TYPE, mime::APPLICATION_OCTET_STREAM)); + let res = res.finish(); + + assert_eq!( + res.headers().get("Content-Type"), + Some(&HeaderValue::from_static("application/octet-stream")) + ); + } + + #[test] + fn response_builder_header_append_kv() { + let mut res = Response::Ok(); + res.append_header(("Content-Type", "application/octet-stream")); + res.append_header(("Content-Type", "application/json")); + let res = res.finish(); + + let headers: Vec<_> = res.headers().get_all("Content-Type").cloned().collect(); + assert_eq!(headers.len(), 2); + assert!(headers.contains(&HeaderValue::from_static("application/octet-stream"))); + assert!(headers.contains(&HeaderValue::from_static("application/json"))); + } + + #[test] + fn response_builder_header_append_typed() { + let mut res = Response::Ok(); + res.append_header((header::CONTENT_TYPE, mime::APPLICATION_OCTET_STREAM)); + res.append_header((header::CONTENT_TYPE, mime::APPLICATION_JSON)); + let res = res.finish(); + + let headers: Vec<_> = res.headers().get_all("Content-Type").cloned().collect(); + assert_eq!(headers.len(), 2); + assert!(headers.contains(&HeaderValue::from_static("application/octet-stream"))); + assert!(headers.contains(&HeaderValue::from_static("application/json"))); + } +} diff --git a/src/service.rs b/src/service.rs index c2ecc0033..9765343c1 100644 --- a/src/service.rs +++ b/src/service.rs @@ -10,12 +10,15 @@ use actix_http::{ use actix_router::{IntoPattern, Path, Resource, ResourceDef, Url}; use actix_service::{IntoServiceFactory, ServiceFactory}; -use crate::config::{AppConfig, AppService}; 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, +}; pub trait HttpServiceFactory { fn register(self, config: &mut AppService); @@ -99,13 +102,14 @@ impl ServiceRequest { /// Create service response #[inline] pub fn into_response>>(self, res: R) -> ServiceResponse { - ServiceResponse::new(self.req, res.into()) + let res = HttpResponse::from(res.into()); + ServiceResponse::new(self.req, res) } /// Create service response for error #[inline] pub fn error_response>(self, err: E) -> ServiceResponse { - let res: Response = err.into().into(); + let res = HttpResponse::from_error(err.into()); ServiceResponse::new(self.req, res.into_body()) } @@ -315,23 +319,19 @@ impl fmt::Debug for ServiceRequest { pub struct ServiceResponse { request: HttpRequest, - response: Response, + response: HttpResponse, } impl ServiceResponse { /// Create service response instance - pub fn new(request: HttpRequest, response: Response) -> Self { + pub fn new(request: HttpRequest, response: HttpResponse) -> Self { ServiceResponse { request, response } } /// Create service response from the error pub fn from_err>(err: E, request: HttpRequest) -> Self { - let e: Error = err.into(); - let res: Response = e.into(); - ServiceResponse { - request, - response: res.into_body(), - } + let response = HttpResponse::from_error(err.into()).into_body(); + ServiceResponse { request, response } } /// Create service response for error @@ -342,7 +342,7 @@ impl ServiceResponse { /// Create service response #[inline] - pub fn into_response(self, response: Response) -> ServiceResponse { + pub fn into_response(self, response: HttpResponse) -> ServiceResponse { ServiceResponse::new(self.request, response) } @@ -354,13 +354,13 @@ impl ServiceResponse { /// Get reference to response #[inline] - pub fn response(&self) -> &Response { + pub fn response(&self) -> &HttpResponse { &self.response } /// Get mutable reference to response #[inline] - pub fn response_mut(&mut self) -> &mut Response { + pub fn response_mut(&mut self) -> &mut HttpResponse { &mut self.response } @@ -376,8 +376,8 @@ impl ServiceResponse { self.response.headers() } - #[inline] /// Returns mutable response's headers. + #[inline] pub fn headers_mut(&mut self) -> &mut HeaderMap { self.response.headers_mut() } @@ -391,7 +391,7 @@ impl ServiceResponse { match f(&mut self) { Ok(_) => self, Err(err) => { - let res: Response = err.into().into(); + let res = HttpResponse::from_error(err.into()); ServiceResponse::new(self.request, res.into_body()) } } @@ -418,9 +418,15 @@ impl ServiceResponse { } } +impl From> for HttpResponse { + fn from(res: ServiceResponse) -> HttpResponse { + res.response + } +} + impl From> for Response { fn from(res: ServiceResponse) -> Response { - res.response + res.response.into() } } diff --git a/src/test.rs b/src/test.rs index 37fb96e0e..c2e456e58 100644 --- a/src/test.rs +++ b/src/test.rs @@ -2,8 +2,6 @@ use std::{net::SocketAddr, rc::Rc}; -#[cfg(feature = "cookies")] -use actix_http::cookie::Cookie; pub use actix_http::test::TestBuffer; use actix_http::{ http::{header::IntoHeaderPair, Method, StatusCode, Uri, Version}, @@ -17,6 +15,8 @@ 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, config::AppConfig, @@ -26,7 +26,7 @@ use crate::{ rmap::ResourceMap, service::{ServiceRequest, ServiceResponse}, web::{Bytes, BytesMut}, - Error, HttpRequest, HttpResponse, + Error, HttpRequest, HttpResponse, HttpResponseBuilder, }; /// Create service that always responds with `HttpResponse::Ok()` and no body. @@ -40,7 +40,7 @@ pub fn default_service( status_code: StatusCode, ) -> impl Service, Error = Error> { (move |req: ServiceRequest| { - ok(req.into_response(HttpResponse::build(status_code).finish())) + ok(req.into_response(HttpResponseBuilder::new(status_code).finish())) }) .into_service() } @@ -359,6 +359,8 @@ pub struct TestRequest { path: Path, peer_addr: Option, app_data: Extensions, + #[cfg(feature = "cookies")] + cookies: CookieJar, } impl Default for TestRequest { @@ -370,6 +372,8 @@ impl Default for TestRequest { path: Path::new(Url::new(Uri::default())), peer_addr: None, app_data: Extensions::new(), + #[cfg(feature = "cookies")] + cookies: CookieJar::new(), } } } @@ -445,7 +449,7 @@ impl TestRequest { /// Set cookie for this request. #[cfg(feature = "cookies")] pub fn cookie(mut self, cookie: Cookie<'_>) -> Self { - self.req.cookie(cookie); + self.cookies.add(cookie.into_owned()); self } @@ -507,16 +511,42 @@ impl TestRequest { 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::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.req.finish(); + 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.req.finish().into_parts(); + let (mut head, payload) = self.finish().into_parts(); head.peer_addr = self.peer_addr; self.path.get_mut().update(&head.uri); @@ -535,7 +565,7 @@ impl TestRequest { /// Complete request creation and generate `HttpRequest` instance pub fn to_http_request(mut self) -> HttpRequest { - let (mut head, _) = self.req.finish().into_parts(); + let (mut head, _) = self.finish().into_parts(); head.peer_addr = self.peer_addr; self.path.get_mut().update(&head.uri); @@ -546,7 +576,7 @@ impl TestRequest { /// Complete request creation and generate `HttpRequest` and `Payload` instances pub fn to_http_parts(mut self) -> (HttpRequest, Payload) { - let (mut head, payload) = self.req.finish().into_parts(); + let (mut head, payload) = self.finish().into_parts(); head.peer_addr = self.peer_addr; self.path.get_mut().update(&head.uri); diff --git a/src/types/json.rs b/src/types/json.rs index 97439d8fd..265beb56c 100644 --- a/src/types/json.rs +++ b/src/types/json.rs @@ -225,7 +225,7 @@ where /// .content_type(|mime| mime == mime::TEXT_PLAIN) /// // use custom error handler /// .error_handler(|err, req| { -/// error::InternalError::from_response(err, HttpResponse::Conflict().finish()).into() +/// error::InternalError::from_response(err, HttpResponse::Conflict().into()).into() /// }); /// /// App::new() @@ -486,7 +486,7 @@ mod tests { }; let resp = HttpResponse::BadRequest().body(serde_json::to_string(&msg).unwrap()); - InternalError::from_response(err, resp).into() + InternalError::from_response(err, resp.into()).into() })) .to_http_parts(); diff --git a/src/types/path.rs b/src/types/path.rs index 33ea70629..69a75e9cf 100644 --- a/src/types/path.rs +++ b/src/types/path.rs @@ -149,7 +149,7 @@ where /// .app_data(PathConfig::default().error_handler(|err, req| { /// error::InternalError::from_response( /// err, -/// HttpResponse::Conflict().finish(), +/// HttpResponse::Conflict().into(), /// ) /// .into() /// })) @@ -292,15 +292,18 @@ 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() + error::InternalError::from_response( + err, + HttpResponse::Conflict().finish().into(), + ) + .into() })) .to_http_parts(); let s = Path::<(usize,)>::from_request(&req, &mut pl) .await .unwrap_err(); - let res: HttpResponse = s.into(); + let res = HttpResponse::from_error(s.into()); assert_eq!(res.status(), http::StatusCode::CONFLICT); } diff --git a/src/types/query.rs b/src/types/query.rs index 4807335bc..978d00b5f 100644 --- a/src/types/query.rs +++ b/src/types/query.rs @@ -166,7 +166,7 @@ where /// let query_cfg = web::QueryConfig::default() /// // use custom error handler /// .error_handler(|err, req| { -/// error::InternalError::from_response(err, HttpResponse::Conflict().finish()).into() +/// error::InternalError::from_response(err, HttpResponse::Conflict().into()).into() /// }); /// /// App::new() @@ -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() + InternalError::from_response(e, resp.into()).into() })) .to_srv_request(); diff --git a/tests/test_server.rs b/tests/test_server.rs index d114b022d..2760cc7fb 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -15,6 +15,7 @@ use actix_http::http::header::{ }; use brotli2::write::{BrotliDecoder, BrotliEncoder}; use bytes::Bytes; +use cookie::{Cookie, CookieBuilder}; use flate2::{ read::GzDecoder, write::{GzEncoder, ZlibDecoder, ZlibEncoder}, @@ -826,18 +827,18 @@ mod plus_rustls { #[actix_rt::test] async fn test_server_cookies() { - use actix_web::{http, HttpMessage}; + use actix_web::http; let srv = actix_test::start(|| { App::new().default_service(web::to(|| { HttpResponse::Ok() .cookie( - http::CookieBuilder::new("first", "first_value") + CookieBuilder::new("first", "first_value") .http_only(true) .finish(), ) - .cookie(http::Cookie::new("second", "first_value")) - .cookie(http::Cookie::new("second", "second_value")) + .cookie(Cookie::new("second", "first_value")) + .cookie(Cookie::new("second", "second_value")) .finish() })) }); @@ -846,10 +847,10 @@ async fn test_server_cookies() { let res = req.send().await.unwrap(); assert!(res.status().is_success()); - let first_cookie = http::CookieBuilder::new("first", "first_value") + let first_cookie = CookieBuilder::new("first", "first_value") .http_only(true) .finish(); - let second_cookie = http::Cookie::new("second", "second_value"); + let second_cookie = Cookie::new("second", "second_value"); let cookies = res.cookies().expect("To have cookies"); assert_eq!(cookies.len(), 2); From 981c54432ce3d92e3b9c7c35bbb83905486360a9 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Mon, 12 Apr 2021 10:30:28 +0100 Subject: [PATCH 147/339] remove json and url encoded form support from -http (#2148) --- CHANGES.md | 5 ++ actix-http/CHANGES.md | 5 ++ actix-http/Cargo.toml | 3 +- actix-http/src/body/body.rs | 6 -- actix-http/src/body/mod.rs | 8 +- actix-http/src/error.rs | 31 +++----- actix-http/src/response.rs | 145 ++---------------------------------- awc/CHANGES.md | 4 + awc/src/request.rs | 28 ------- awc/src/sender.rs | 8 +- src/error.rs | 64 +++++++++++----- src/response.rs | 65 +++++++++------- src/types/form.rs | 8 +- src/types/json.rs | 5 +- src/types/path.rs | 10 +-- 15 files changed, 137 insertions(+), 258 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 64e0891e5..bf8fc9424 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -4,7 +4,12 @@ ### Added * `HttpResponse` and `HttpResponseBuilder` structs. [#2065] +### Changed +* Most error types are now marked `#[non_exhaustive]`. [#2148] + [#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 diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 5e4258677..e50259634 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -6,8 +6,13 @@ * 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] [#2065]: https://github.com/actix/actix-web/pull/2065 +[#2148]: https://github.com/actix/actix-web/pull/2148 ## 3.0.0-beta.5 - 2021-04-02 diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 573376b07..4bef9e37c 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -68,8 +68,6 @@ pin-project-lite = "0.2" rand = "0.8" regex = "1.3" serde = "1.0" -serde_json = "1.0" -serde_urlencoded = "0.7" sha-1 = "0.9" smallvec = "1.6" time = { version = "0.2.23", default-features = false, features = ["std"] } @@ -89,6 +87,7 @@ criterion = "0.3" env_logger = "0.8" rcgen = "0.8" serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" tls-openssl = { version = "0.10", package = "openssl" } tls-rustls = { version = "0.19", package = "rustls" } diff --git a/actix-http/src/body/body.rs b/actix-http/src/body/body.rs index a3fd7d41c..6814d54f7 100644 --- a/actix-http/src/body/body.rs +++ b/actix-http/src/body/body.rs @@ -132,12 +132,6 @@ impl From for Body { } } -impl From for Body { - fn from(v: serde_json::Value) -> Body { - Body::Bytes(v.to_string().into()) - } -} - impl From> for Body where S: Stream> + Unpin + 'static, diff --git a/actix-http/src/body/mod.rs b/actix-http/src/body/mod.rs index fa43e1b03..f5664e1dc 100644 --- a/actix-http/src/body/mod.rs +++ b/actix-http/src/body/mod.rs @@ -174,13 +174,15 @@ mod tests { #[actix_rt::test] async fn test_serde_json() { - use serde_json::json; + use serde_json::{json, Value}; assert_eq!( - Body::from(serde_json::Value::String("test".into())).size(), + Body::from(serde_json::to_vec(&Value::String("test".to_owned())).unwrap()) + .size(), BodySize::Sized(6) ); assert_eq!( - Body::from(json!({"test-key":"test-value"})).size(), + Body::from(serde_json::to_vec(&json!({"test-key":"test-value"})).unwrap()) + .size(), BodySize::Sized(25) ); } diff --git a/actix-http/src/error.rs b/actix-http/src/error.rs index 03693ff2f..60a870ecc 100644 --- a/actix-http/src/error.rs +++ b/actix-http/src/error.rs @@ -1,18 +1,18 @@ //! Error and Result module -use std::cell::RefCell; -use std::io::Write; -use std::str::Utf8Error; -use std::string::FromUtf8Error; -use std::{fmt, io, result}; +use std::{ + cell::RefCell, + fmt, + io::{self, Write as _}, + str::Utf8Error, + string::FromUtf8Error, +}; use bytes::BytesMut; -use derive_more::{Display, From}; +use derive_more::{Display, Error, From}; use http::uri::InvalidUri; use http::{header, Error as HttpError, StatusCode}; use serde::de::value::Error as DeError; -use serde_json::error::Error as JsonError; -use serde_urlencoded::ser::Error as FormError; use crate::{body::Body, helpers::Writer, Response, ResponseBuilder}; @@ -22,7 +22,7 @@ use crate::{body::Body, helpers::Writer, Response, ResponseBuilder}; /// This typedef is generally used to avoid writing out /// `actix_http::error::Error` directly and is otherwise a direct mapping to /// `Result`. -pub type Result = result::Result; +pub type Result = std::result::Result; /// General purpose actix web error. /// @@ -147,14 +147,8 @@ struct UnitError; /// Returns [`StatusCode::INTERNAL_SERVER_ERROR`] for [`UnitError`]. impl ResponseError for UnitError {} -/// Returns [`StatusCode::INTERNAL_SERVER_ERROR`] for [`JsonError`]. -impl ResponseError for JsonError {} - -/// Returns [`StatusCode::INTERNAL_SERVER_ERROR`] for [`FormError`]. -impl ResponseError for FormError {} - -#[cfg(feature = "openssl")] /// 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`]. @@ -421,18 +415,17 @@ pub enum DispatchError { } /// A set of error that can occur during parsing content type -#[derive(PartialEq, Debug, Display)] +#[derive(Debug, PartialEq, Display, Error)] pub enum ContentTypeError { /// Can not parse content type #[display(fmt = "Can not parse content type")] ParseError, + /// Unknown content encoding #[display(fmt = "Unknown content encoding")] UnknownEncoding, } -impl std::error::Error for ContentTypeError {} - /// Return `BadRequest` for `ContentTypeError` impl ResponseError for ContentTypeError { fn status_code(&self) -> StatusCode { diff --git a/actix-http/src/response.rs b/actix-http/src/response.rs index a96a13bd3..046c4ca56 100644 --- a/actix-http/src/response.rs +++ b/actix-http/src/response.rs @@ -2,7 +2,6 @@ use std::{ cell::{Ref, RefMut}, - convert::TryInto, fmt, future::Future, pin::Pin, @@ -12,17 +11,13 @@ use std::{ use bytes::{Bytes, BytesMut}; use futures_core::Stream; -use serde::Serialize; use crate::{ body::{Body, BodyStream, MessageBody, ResponseBody}, error::Error, extensions::Extensions, header::{IntoHeaderPair, IntoHeaderValue}, - http::{ - header::{self, HeaderName}, - Error as HttpError, HeaderMap, StatusCode, - }, + http::{header, Error as HttpError, HeaderMap, StatusCode}, message::{BoxedResponseHead, ConnectionType, ResponseHead}, }; @@ -335,54 +330,6 @@ impl ResponseBuilder { self } - /// Replaced with [`Self::insert_header()`]. - #[deprecated( - since = "4.0.0", - note = "Replaced with `insert_header((key, value))`. Will be removed in v5." - )] - pub fn set_header(&mut self, key: K, value: V) -> &mut Self - where - K: TryInto, - K::Error: Into, - V: IntoHeaderValue, - { - if self.err.is_some() { - return self; - } - - match (key.try_into(), value.try_into_value()) { - (Ok(name), Ok(value)) => return self.insert_header((name, value)), - (Err(err), _) => self.err = Some(err.into()), - (_, Err(err)) => self.err = Some(err.into()), - } - - self - } - - /// Replaced with [`Self::append_header()`]. - #[deprecated( - since = "4.0.0", - note = "Replaced with `append_header((key, value))`. Will be removed in v5." - )] - pub fn header(&mut self, key: K, value: V) -> &mut Self - where - K: TryInto, - K::Error: Into, - V: IntoHeaderValue, - { - if self.err.is_some() { - return self; - } - - match (key.try_into(), value.try_into_value()) { - (Ok(name), Ok(value)) => return self.append_header((name, value)), - (Err(err), _) => self.err = Some(err.into()), - (_, Err(err)) => self.err = Some(err.into()), - } - - self - } - /// Set the custom reason for the response. #[inline] pub fn reason(&mut self, reason: &'static str) -> &mut Self { @@ -456,32 +403,6 @@ impl ResponseBuilder { self } - /// This method calls provided closure with builder reference if value is `true`. - #[doc(hidden)] - #[deprecated = "Use an if statement."] - pub fn if_true(&mut self, value: bool, f: F) -> &mut Self - where - F: FnOnce(&mut ResponseBuilder), - { - if value { - f(self); - } - self - } - - /// This method calls provided closure with builder reference if value is `Some`. - #[doc(hidden)] - #[deprecated = "Use an if-let construction."] - pub fn if_some(&mut self, value: Option, f: F) -> &mut Self - where - F: FnOnce(T, &mut ResponseBuilder), - { - if let Some(val) = value { - f(val, self); - } - self - } - /// Responses extensions #[inline] pub fn extensions(&self) -> Ref<'_, Extensions> { @@ -496,10 +417,10 @@ impl ResponseBuilder { head.extensions.borrow_mut() } - #[inline] /// Set a body and generate `Response`. /// /// `ResponseBuilder` can not be used after this call. + #[inline] pub fn body>(&mut self, body: B) -> Response { self.message_body(body.into()) } @@ -521,10 +442,10 @@ impl ResponseBuilder { } } - #[inline] /// Set a streaming body and generate `Response`. /// /// `ResponseBuilder` can not be used after this call. + #[inline] pub fn streaming(&mut self, stream: S) -> Response where S: Stream> + Unpin + 'static, @@ -533,32 +454,10 @@ impl ResponseBuilder { self.body(Body::from_message(BodyStream::new(stream))) } - /// Set a json body and generate `Response` - /// - /// `ResponseBuilder` can not be used after this call. - pub fn json(&mut self, value: impl Serialize) -> Response { - match serde_json::to_string(&value) { - Ok(body) => { - let contains = if let Some(parts) = parts(&mut self.head, &self.err) { - parts.headers.contains_key(header::CONTENT_TYPE) - } else { - true - }; - - if !contains { - self.insert_header((header::CONTENT_TYPE, mime::APPLICATION_JSON)); - } - - self.body(Body::from(body)) - } - Err(e) => Error::from(e).into(), - } - } - - #[inline] /// Set an empty body and generate `Response` /// /// `ResponseBuilder` can not be used after this call. + #[inline] pub fn finish(&mut self) -> Response { self.body(Body::Empty) } @@ -706,11 +605,9 @@ impl From for Response { #[cfg(test)] mod tests { - use serde_json::json; - use super::*; use crate::body::Body; - use crate::http::header::{HeaderValue, CONTENT_TYPE, COOKIE}; + use crate::http::header::{HeaderName, HeaderValue, CONTENT_TYPE, COOKIE}; #[test] fn test_debug() { @@ -754,38 +651,6 @@ mod tests { assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "text/plain") } - #[test] - fn test_json() { - let resp = Response::Ok().json(vec!["v1", "v2", "v3"]); - let ct = resp.headers().get(CONTENT_TYPE).unwrap(); - assert_eq!(ct, HeaderValue::from_static("application/json")); - assert_eq!(resp.body().get_ref(), b"[\"v1\",\"v2\",\"v3\"]"); - - let resp = Response::Ok().json(&["v1", "v2", "v3"]); - let ct = resp.headers().get(CONTENT_TYPE).unwrap(); - assert_eq!(ct, HeaderValue::from_static("application/json")); - assert_eq!(resp.body().get_ref(), b"[\"v1\",\"v2\",\"v3\"]"); - } - - #[test] - fn test_json_ct() { - let resp = Response::build(StatusCode::OK) - .insert_header((CONTENT_TYPE, "text/json")) - .json(&vec!["v1", "v2", "v3"]); - let ct = resp.headers().get(CONTENT_TYPE).unwrap(); - assert_eq!(ct, HeaderValue::from_static("text/json")); - assert_eq!(resp.body().get_ref(), b"[\"v1\",\"v2\",\"v3\"]"); - } - - #[test] - fn test_serde_json_in_body() { - use serde_json::json; - - let resp = - Response::build(StatusCode::OK).body(json!({"test-key":"test-value"})); - assert_eq!(resp.body().get_ref(), br#"{"test-key":"test-value"}"#); - } - #[test] fn test_into_response() { let resp: Response = "test".into(); diff --git a/awc/CHANGES.md b/awc/CHANGES.md index eb008ff98..a0bfcac86 100644 --- a/awc/CHANGES.md +++ b/awc/CHANGES.md @@ -1,6 +1,10 @@ # Changes ## Unreleased - 2021-xx-xx +### Removed +* 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 diff --git a/awc/src/request.rs b/awc/src/request.rs index f5cb08f15..483524102 100644 --- a/awc/src/request.rs +++ b/awc/src/request.rs @@ -311,34 +311,6 @@ impl ClientRequest { self } - /// This method calls provided closure with builder reference if value is `true`. - #[doc(hidden)] - #[deprecated = "Use an if statement."] - pub fn if_true(self, value: bool, f: F) -> Self - where - F: FnOnce(ClientRequest) -> ClientRequest, - { - if value { - f(self) - } else { - self - } - } - - /// This method calls provided closure with builder reference if value is `Some`. - #[doc(hidden)] - #[deprecated = "Use an if-let construction."] - pub fn if_some(self, value: Option, f: F) -> Self - where - F: FnOnce(T, ClientRequest) -> ClientRequest, - { - if let Some(val) = value { - f(val, self) - } else { - self - } - } - /// Sets the query part of the request pub fn query( mut self, diff --git a/awc/src/sender.rs b/awc/src/sender.rs index 1170c69a0..0e63be221 100644 --- a/awc/src/sender.rs +++ b/awc/src/sender.rs @@ -1,6 +1,6 @@ use std::{ future::Future, - net, + io, net, pin::Pin, rc::Rc, task::{Context, Poll}, @@ -209,7 +209,8 @@ impl RequestSender { ) -> SendClientRequest { let body = match serde_json::to_string(value) { Ok(body) => body, - Err(e) => return Error::from(e).into(), + // TODO: own error type + Err(e) => return Error::from(io::Error::new(io::ErrorKind::Other, e)).into(), }; if let Err(e) = self.set_header_if_none(header::CONTENT_TYPE, "application/json") { @@ -235,7 +236,8 @@ impl RequestSender { ) -> SendClientRequest { let body = match serde_urlencoded::to_string(value) { Ok(body) => body, - Err(e) => return Error::from(e).into(), + // TODO: own error type + Err(e) => return Error::from(io::Error::new(io::ErrorKind::Other, e)).into(), }; // set content-type diff --git a/src/error.rs b/src/error.rs index 25cdc9feb..cc1a055b8 100644 --- a/src/error.rs +++ b/src/error.rs @@ -3,12 +3,15 @@ pub use actix_http::error::*; use derive_more::{Display, Error, From}; use serde_json::error::Error as JsonError; +use serde_urlencoded::de::Error as FormDeError; +use serde_urlencoded::ser::Error as FormError; use url::ParseError as UrlParseError; use crate::http::StatusCode; /// Errors which can occur when attempting to generate resource uri. -#[derive(Debug, PartialEq, Display, From)] +#[derive(Debug, PartialEq, Display, Error, From)] +#[non_exhaustive] pub enum UrlGenerationError { /// Resource not found #[display(fmt = "Resource not found")] @@ -23,13 +26,12 @@ pub enum UrlGenerationError { ParseError(UrlParseError), } -impl std::error::Error for UrlGenerationError {} - /// `InternalServerError` for `UrlGeneratorError` impl ResponseError for UrlGenerationError {} /// A set of errors that can occur during parsing urlencoded payloads #[derive(Debug, Display, Error, From)] +#[non_exhaustive] pub enum UrlencodedError { /// Can not decode chunked transfer encoding. #[display(fmt = "Can not decode chunked transfer encoding.")] @@ -52,8 +54,16 @@ pub enum UrlencodedError { ContentType, /// Parse error. - #[display(fmt = "Parse error.")] - Parse, + #[display(fmt = "Parse error: {}.", _0)] + Parse(FormDeError), + + /// Encoding error. + #[display(fmt = "Encoding error.")] + Encoding, + + /// Serialize error. + #[display(fmt = "Serialize error: {}.", _0)] + Serialize(FormError), /// Payload error. #[display(fmt = "Error that occur during reading payload: {}.", _0)] @@ -63,52 +73,66 @@ pub enum UrlencodedError { /// Return `BadRequest` for `UrlencodedError` impl ResponseError for UrlencodedError { fn status_code(&self) -> StatusCode { - match *self { - UrlencodedError::Overflow { .. } => StatusCode::PAYLOAD_TOO_LARGE, - UrlencodedError::UnknownLength => StatusCode::LENGTH_REQUIRED, + match self { + Self::Overflow { .. } => StatusCode::PAYLOAD_TOO_LARGE, + Self::UnknownLength => StatusCode::LENGTH_REQUIRED, + Self::Payload(err) => err.status_code(), _ => StatusCode::BAD_REQUEST, } } } /// A set of errors that can occur during parsing json payloads -#[derive(Debug, Display, From)] +#[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, + /// Content type error #[display(fmt = "Content type error")] ContentType, + /// Deserialize error #[display(fmt = "Json deserialize error: {}", _0)] Deserialize(JsonError), + + /// Serialize error + #[display(fmt = "Json serialize error: {}", _0)] + Serialize(JsonError), + /// Payload error #[display(fmt = "Error that occur during reading payload: {}", _0)] Payload(PayloadError), } -impl std::error::Error for JsonPayloadError {} +impl From for JsonPayloadError { + fn from(err: PayloadError) -> Self { + Self::Payload(err) + } +} impl ResponseError for JsonPayloadError { fn status_code(&self) -> StatusCode { - match *self { - JsonPayloadError::Overflow => StatusCode::PAYLOAD_TOO_LARGE, + match self { + Self::Overflow => StatusCode::PAYLOAD_TOO_LARGE, + Self::Serialize(_) => StatusCode::INTERNAL_SERVER_ERROR, + Self::Payload(err) => err.status_code(), _ => StatusCode::BAD_REQUEST, } } } /// A set of errors that can occur during parsing request paths -#[derive(Debug, Display, From)] +#[derive(Debug, Display, Error)] +#[non_exhaustive] pub enum PathError { /// Deserialize error #[display(fmt = "Path deserialize error: {}", _0)] Deserialize(serde::de::value::Error), } -impl std::error::Error for PathError {} - /// Return `BadRequest` for `PathError` impl ResponseError for PathError { fn status_code(&self) -> StatusCode { @@ -118,6 +142,7 @@ impl ResponseError for PathError { /// A set of errors that can occur during parsing query strings. #[derive(Debug, Display, Error, From)] +#[non_exhaustive] pub enum QueryPayloadError { /// Query deserialize error. #[display(fmt = "Query deserialize error: {}", _0)] @@ -132,25 +157,26 @@ impl ResponseError for QueryPayloadError { } /// Error type returned when reading body as lines. -#[derive(From, Display, Debug)] +#[derive(Debug, Display, Error, From)] +#[non_exhaustive] pub enum ReadlinesError { - /// Error when decoding a line. #[display(fmt = "Encoding error")] /// Payload size is bigger than allowed. (default: 256kB) EncodingError, + /// Payload error. #[display(fmt = "Error that occur during reading payload: {}", _0)] Payload(PayloadError), + /// Line limit exceeded. #[display(fmt = "Line limit exceeded")] LimitOverflow, + /// ContentType error. #[display(fmt = "Content-type error")] ContentTypeError(ContentTypeError), } -impl std::error::Error for ReadlinesError {} - /// Return `BadRequest` for `ReadlinesError` impl ResponseError for ReadlinesError { fn status_code(&self) -> StatusCode { diff --git a/src/response.rs b/src/response.rs index 1d017f080..ce6739dc8 100644 --- a/src/response.rs +++ b/src/response.rs @@ -24,7 +24,7 @@ use actix_http::http::header::HeaderValue; #[cfg(feature = "cookies")] use cookie::{Cookie, CookieJar}; -use crate::error::Error; +use crate::error::{Error, JsonPayloadError}; /// An HTTP Response pub struct HttpResponse { @@ -385,7 +385,10 @@ impl HttpResponseBuilder { } /// Replaced with [`Self::insert_header()`]. - #[deprecated = "Replaced with `insert_header((key, value))`."] + #[deprecated( + since = "4.0.0", + note = "Replaced with `insert_header((key, value))`. Will be removed in v5." + )] pub fn set_header(&mut self, key: K, value: V) -> &mut Self where K: TryInto, @@ -406,7 +409,10 @@ impl HttpResponseBuilder { } /// Replaced with [`Self::append_header()`]. - #[deprecated = "Replaced with `append_header((key, value))`."] + #[deprecated( + since = "4.0.0", + note = "Replaced with `append_header((key, value))`. Will be removed in v5." + )] pub fn header(&mut self, key: K, value: V) -> &mut Self where K: TryInto, @@ -572,7 +578,7 @@ impl HttpResponseBuilder { /// Set a body and generate `Response`. /// - /// `ResponseBuilder` can not be used after this call. + /// `HttpResponseBuilder` can not be used after this call. #[inline] pub fn body>(&mut self, body: B) -> HttpResponse { self.message_body(body.into()) @@ -580,7 +586,7 @@ impl HttpResponseBuilder { /// Set a body and generate `Response`. /// - /// `ResponseBuilder` can not be used after this call. + /// `HttpResponseBuilder` can not be used after this call. pub fn message_body(&mut self, body: B) -> HttpResponse { if let Some(err) = self.err.take() { return HttpResponse::from_error(Error::from(err)).into_body(); @@ -608,7 +614,7 @@ impl HttpResponseBuilder { /// Set a streaming body and generate `Response`. /// - /// `ResponseBuilder` can not be used after this call. + /// `HttpResponseBuilder` can not be used after this call. #[inline] pub fn streaming(&mut self, stream: S) -> HttpResponse where @@ -620,7 +626,7 @@ impl HttpResponseBuilder { /// Set a json body and generate `Response` /// - /// `ResponseBuilder` can not be used after this call. + /// `HttpResponseBuilder` can not be used after this call. pub fn json(&mut self, value: impl Serialize) -> HttpResponse { match serde_json::to_string(&value) { Ok(body) => { @@ -636,19 +642,19 @@ impl HttpResponseBuilder { self.body(Body::from(body)) } - Err(e) => HttpResponse::from_error(Error::from(e)), + Err(err) => HttpResponse::from_error(JsonPayloadError::Serialize(err).into()), } } /// Set an empty body and generate `Response` /// - /// `ResponseBuilder` can not be used after this call. + /// `HttpResponseBuilder` can not be used after this call. #[inline] pub fn finish(&mut self) -> HttpResponse { self.body(Body::Empty) } - /// This method construct new `ResponseBuilder` + /// This method construct new `HttpResponseBuilder` pub fn take(&mut self) -> Self { Self { head: self.head.take(), @@ -814,32 +820,33 @@ mod http_codes { #[cfg(test)] mod tests { use bytes::{Bytes, BytesMut}; - use serde_json::json; - use super::{HttpResponse as Response, HttpResponseBuilder as ResponseBuilder}; + use super::{HttpResponse, HttpResponseBuilder}; use crate::dev::{Body, MessageBody, ResponseBody}; use crate::http::header::{self, HeaderValue, CONTENT_TYPE, COOKIE}; use crate::http::StatusCode; #[test] fn test_debug() { - let resp = Response::Ok() + let resp = HttpResponse::Ok() .append_header((COOKIE, HeaderValue::from_static("cookie1=value1; "))) .append_header((COOKIE, HeaderValue::from_static("cookie2=value2; "))) .finish(); let dbg = format!("{:?}", resp); - assert!(dbg.contains("Response")); + assert!(dbg.contains("HttpResponse")); } #[test] fn test_basic_builder() { - let resp = Response::Ok().insert_header(("X-TEST", "value")).finish(); + let resp = HttpResponse::Ok() + .insert_header(("X-TEST", "value")) + .finish(); assert_eq!(resp.status(), StatusCode::OK); } #[test] fn test_upgrade() { - let resp = ResponseBuilder::new(StatusCode::OK) + let resp = HttpResponseBuilder::new(StatusCode::OK) .upgrade("websocket") .finish(); assert!(resp.upgrade()); @@ -851,13 +858,15 @@ mod tests { #[test] fn test_force_close() { - let resp = ResponseBuilder::new(StatusCode::OK).force_close().finish(); + let resp = HttpResponseBuilder::new(StatusCode::OK) + .force_close() + .finish(); assert!(!resp.keep_alive()) } #[test] fn test_content_type() { - let resp = ResponseBuilder::new(StatusCode::OK) + let resp = HttpResponseBuilder::new(StatusCode::OK) .content_type("text/plain") .body(Body::Empty); assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "text/plain") @@ -878,7 +887,7 @@ mod tests { #[actix_rt::test] async fn test_json() { - let mut resp = Response::Ok().json(vec!["v1", "v2", "v3"]); + let mut resp = HttpResponse::Ok().json(vec!["v1", "v2", "v3"]); let ct = resp.headers().get(CONTENT_TYPE).unwrap(); assert_eq!(ct, HeaderValue::from_static("application/json")); assert_eq!( @@ -886,7 +895,7 @@ mod tests { br#"["v1","v2","v3"]"# ); - let mut resp = Response::Ok().json(&["v1", "v2", "v3"]); + let mut resp = HttpResponse::Ok().json(&["v1", "v2", "v3"]); let ct = resp.headers().get(CONTENT_TYPE).unwrap(); assert_eq!(ct, HeaderValue::from_static("application/json")); assert_eq!( @@ -895,7 +904,7 @@ mod tests { ); // content type override - let mut resp = Response::Ok() + let mut resp = HttpResponse::Ok() .insert_header((CONTENT_TYPE, "text/json")) .json(&vec!["v1", "v2", "v3"]); let ct = resp.headers().get(CONTENT_TYPE).unwrap(); @@ -908,8 +917,10 @@ mod tests { #[actix_rt::test] async fn test_serde_json_in_body() { - use serde_json::json; - let mut resp = Response::Ok().body(json!({"test-key":"test-value"})); + let mut resp = HttpResponse::Ok().body( + serde_json::to_vec(&serde_json::json!({ "test-key": "test-value" })).unwrap(), + ); + assert_eq!( read_body(resp.take_body()).await.as_ref(), br#"{"test-key":"test-value"}"# @@ -918,7 +929,7 @@ mod tests { #[test] fn response_builder_header_insert_kv() { - let mut res = Response::Ok(); + let mut res = HttpResponse::Ok(); res.insert_header(("Content-Type", "application/octet-stream")); let res = res.finish(); @@ -930,7 +941,7 @@ mod tests { #[test] fn response_builder_header_insert_typed() { - let mut res = Response::Ok(); + let mut res = HttpResponse::Ok(); res.insert_header((header::CONTENT_TYPE, mime::APPLICATION_OCTET_STREAM)); let res = res.finish(); @@ -942,7 +953,7 @@ mod tests { #[test] fn response_builder_header_append_kv() { - let mut res = Response::Ok(); + let mut res = HttpResponse::Ok(); res.append_header(("Content-Type", "application/octet-stream")); res.append_header(("Content-Type", "application/json")); let res = res.finish(); @@ -955,7 +966,7 @@ mod tests { #[test] fn response_builder_header_append_typed() { - let mut res = Response::Ok(); + let mut res = HttpResponse::Ok(); res.append_header((header::CONTENT_TYPE, mime::APPLICATION_OCTET_STREAM)); res.append_header((header::CONTENT_TYPE, mime::APPLICATION_JSON)); let res = res.finish(); diff --git a/src/types/form.rs b/src/types/form.rs index 14c1369ff..9d2311a45 100644 --- a/src/types/form.rs +++ b/src/types/form.rs @@ -163,7 +163,7 @@ impl Responder for Form { Ok(body) => HttpResponse::Ok() .content_type(mime::APPLICATION_WWW_FORM_URLENCODED) .body(body), - Err(err) => HttpResponse::from_error(err.into()), + Err(err) => HttpResponse::from_error(UrlencodedError::Serialize(err).into()), } } } @@ -346,14 +346,14 @@ where } if encoding == UTF_8 { - serde_urlencoded::from_bytes::(&body).map_err(|_| UrlencodedError::Parse) + serde_urlencoded::from_bytes::(&body).map_err(UrlencodedError::Parse) } else { let body = encoding .decode_without_bom_handling_and_without_replacement(&body) .map(|s| s.into_owned()) - .ok_or(UrlencodedError::Parse)?; + .ok_or(UrlencodedError::Encoding)?; - serde_urlencoded::from_str::(&body).map_err(|_| UrlencodedError::Parse) + serde_urlencoded::from_str::(&body).map_err(UrlencodedError::Parse) } } .boxed_local(), diff --git a/src/types/json.rs b/src/types/json.rs index 265beb56c..322e5cbf3 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(err.into()), + Err(err) => HttpResponse::from_error(JsonPayloadError::Serialize(err).into()), } } } @@ -412,7 +412,8 @@ 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)); } } diff --git a/src/types/path.rs b/src/types/path.rs index 69a75e9cf..50e2cb510 100644 --- a/src/types/path.rs +++ b/src/types/path.rs @@ -93,7 +93,7 @@ where T: de::DeserializeOwned, { type Error = Error; - type Future = Ready>; + type Future = Ready>; type Config = PathConfig; #[inline] @@ -106,17 +106,17 @@ where ready( de::Deserialize::deserialize(PathDeserializer::new(req.match_info())) .map(Path) - .map_err(move |e| { + .map_err(move |err| { log::debug!( "Failed during Path extractor deserialization. \ Request path: {:?}", req.path() ); if let Some(error_handler) = error_handler { - let e = PathError::Deserialize(e); + let e = PathError::Deserialize(err); (error_handler)(e, req) } else { - ErrorNotFound(e) + ErrorNotFound(err) } }), ) @@ -303,7 +303,7 @@ mod tests { let s = Path::<(usize,)>::from_request(&req, &mut pl) .await .unwrap_err(); - let res = HttpResponse::from_error(s.into()); + let res = HttpResponse::from_error(s); assert_eq!(res.status(), http::StatusCode::CONFLICT); } From ce50cc952385295675faf763d255d62a95de9fa1 Mon Sep 17 00:00:00 2001 From: Ali MJ Al-Nasrawy Date: Tue, 13 Apr 2021 07:28:30 +0300 Subject: [PATCH 148/339] files: Don't use canonical path when serving file (#2156) --- actix-files/CHANGES.md | 3 +++ actix-files/src/lib.rs | 15 +++++++++++++++ actix-files/src/service.rs | 8 ++++---- actix-files/tests/symlink-test.png | 1 + 4 files changed, 23 insertions(+), 4 deletions(-) create mode 120000 actix-files/tests/symlink-test.png diff --git a/actix-files/CHANGES.md b/actix-files/CHANGES.md index eb66e0e07..004479183 100644 --- a/actix-files/CHANGES.md +++ b/actix-files/CHANGES.md @@ -1,6 +1,9 @@ # Changes ## Unreleased - 2021-xx-xx +* For symbolic links, `Content-Disposition` header no longer shows the filename of the original file. [#2156] + +[#2156]: https://github.com/actix/actix-web/pull/2156 ## 0.6.0-beta.4 - 2021-04-02 diff --git a/actix-files/src/lib.rs b/actix-files/src/lib.rs index 24b903c04..e9b55e87e 100644 --- a/actix-files/src/lib.rs +++ b/actix-files/src/lib.rs @@ -754,4 +754,19 @@ mod tests { let res = test::call_service(&srv, req).await; assert_eq!(res.status(), StatusCode::OK); } + + #[actix_rt::test] + async fn test_symlinks() { + let srv = test::init_service(App::new().service(Files::new("test", "."))).await; + + let req = TestRequest::get() + .uri("/test/tests/symlink-test.png") + .to_request(); + let res = test::call_service(&srv, req).await; + assert_eq!(res.status(), StatusCode::OK); + assert_eq!( + res.headers().get(header::CONTENT_DISPOSITION).unwrap(), + "inline; filename=\"symlink-test.png\"" + ); + } } diff --git a/actix-files/src/service.rs b/actix-files/src/service.rs index d2db8503f..dc51ada18 100644 --- a/actix-files/src/service.rs +++ b/actix-files/src/service.rs @@ -83,10 +83,10 @@ impl Service for FilesService { }; // full file path - let path = match self.directory.join(&real_path).canonicalize() { - Ok(path) => path, - Err(err) => return Box::pin(self.handle_err(err, req)), - }; + 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 let Some(ref redir_index) = self.index { diff --git a/actix-files/tests/symlink-test.png b/actix-files/tests/symlink-test.png new file mode 120000 index 000000000..65c0dcfd6 --- /dev/null +++ b/actix-files/tests/symlink-test.png @@ -0,0 +1 @@ +test.png \ No newline at end of file From edd9f14752b23211583b74a40d08ade293bbc284 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Tue, 13 Apr 2021 11:16:12 +0100 Subject: [PATCH 149/339] remove unpin from body types (#2152) --- actix-http/CHANGES.md | 9 ++ actix-http/examples/echo2.rs | 4 +- actix-http/src/body/body.rs | 16 ++- actix-http/src/body/body_stream.rs | 22 ++-- actix-http/src/body/message_body.rs | 13 ++ actix-http/src/body/sized_stream.rs | 26 ++-- actix-http/src/encoding/encoder.rs | 6 +- actix-http/src/error.rs | 178 ++++++++++++++-------------- actix-http/src/h1/dispatcher.rs | 16 +-- actix-http/src/h2/dispatcher.rs | 4 +- actix-http/src/http_codes.rs | 7 +- actix-http/src/lib.rs | 1 - actix-http/src/response.rs | 50 ++++---- actix-http/src/ws/mod.rs | 18 +-- actix-http/tests/test_openssl.rs | 18 +-- actix-http/tests/test_rustls.rs | 19 +-- actix-http/tests/test_server.rs | 9 +- src/responder.rs | 3 +- src/response.rs | 16 +-- 19 files changed, 241 insertions(+), 194 deletions(-) diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index e50259634..2949c40c3 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -1,6 +1,14 @@ # Changes ## Unreleased - 2021-xx-xx +### Added +* `impl MessageBody for Pin>`. [#2152] + +### 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] + ### Removed * `cookies` feature flag. [#2065] * Top-level `cookies` mod (re-export). [#2065] @@ -13,6 +21,7 @@ [#2065]: https://github.com/actix/actix-web/pull/2065 [#2148]: https://github.com/actix/actix-web/pull/2148 +[#2152]: https://github.com/actix/actix-web/pull/2152 ## 3.0.0-beta.5 - 2021-04-02 diff --git a/actix-http/examples/echo2.rs b/actix-http/examples/echo2.rs index 408a40114..483a79aac 100644 --- a/actix-http/examples/echo2.rs +++ b/actix-http/examples/echo2.rs @@ -1,13 +1,13 @@ use std::{env, io}; -use actix_http::http::HeaderValue; +use actix_http::{body::Body, http::HeaderValue}; 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 { +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 6814d54f7..5fc461d41 100644 --- a/actix-http/src/body/body.rs +++ b/actix-http/src/body/body.rs @@ -12,15 +12,19 @@ use crate::error::Error; use super::{BodySize, BodyStream, MessageBody, SizedStream}; /// Represents various types of HTTP message body. +// #[deprecated(since = "4.0.0", note = "Use body types directly.")] pub enum Body { /// 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(Box), + Message(Pin>), } impl Body { @@ -30,8 +34,8 @@ impl Body { } /// Create body from generic message body. - pub fn from_message(body: B) -> Body { - Body::Message(Box::new(body)) + pub fn from_message(body: B) -> Body { + Body::Message(Box::pin(body)) } } @@ -60,7 +64,7 @@ impl MessageBody for Body { Poll::Ready(Some(Ok(mem::take(bin)))) } } - Body::Message(body) => Pin::new(&mut **body).poll_next(cx), + Body::Message(body) => body.as_mut().poll_next(cx), } } } @@ -134,7 +138,7 @@ impl From for Body { impl From> for Body where - S: Stream> + Unpin + 'static, + S: Stream> + 'static, { fn from(s: SizedStream) -> Body { Body::from_message(s) @@ -143,7 +147,7 @@ where impl From> for Body where - S: Stream> + Unpin + 'static, + S: Stream> + 'static, E: Into + 'static, { fn from(s: BodyStream) -> Body { diff --git a/actix-http/src/body/body_stream.rs b/actix-http/src/body/body_stream.rs index 60e33b161..1157bc539 100644 --- a/actix-http/src/body/body_stream.rs +++ b/actix-http/src/body/body_stream.rs @@ -5,21 +5,25 @@ use std::{ use bytes::Bytes; use futures_core::{ready, Stream}; +use pin_project_lite::pin_project; use crate::error::Error; use super::{BodySize, MessageBody}; -/// Streaming response wrapper. -/// -/// Response does not contain `Content-Length` header and appropriate transfer encoding is used. -pub struct BodyStream { - stream: S, +pin_project! { + /// Streaming response wrapper. + /// + /// Response does not contain `Content-Length` header and appropriate transfer encoding is used. + pub struct BodyStream { + #[pin] + stream: S, + } } impl BodyStream where - S: Stream> + Unpin, + S: Stream>, E: Into, { pub fn new(stream: S) -> Self { @@ -29,7 +33,7 @@ where impl MessageBody for BodyStream where - S: Stream> + Unpin, + S: Stream>, E: Into, { fn size(&self) -> BodySize { @@ -46,9 +50,9 @@ where cx: &mut Context<'_>, ) -> Poll>> { loop { - let stream = &mut self.as_mut().stream; + let stream = self.as_mut().project().stream; - let chunk = match ready!(Pin::new(stream).poll_next(cx)) { + 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)), }; diff --git a/actix-http/src/body/message_body.rs b/actix-http/src/body/message_body.rs index 012329146..ea2cfd22d 100644 --- a/actix-http/src/body/message_body.rs +++ b/actix-http/src/body/message_body.rs @@ -52,6 +52,19 @@ impl MessageBody for Box { } } +impl MessageBody for Pin> { + 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 { fn size(&self) -> BodySize { BodySize::Sized(self.len() as u64) diff --git a/actix-http/src/body/sized_stream.rs b/actix-http/src/body/sized_stream.rs index af995a0fb..f648f6f0b 100644 --- a/actix-http/src/body/sized_stream.rs +++ b/actix-http/src/body/sized_stream.rs @@ -5,23 +5,27 @@ use std::{ use bytes::Bytes; use futures_core::{ready, Stream}; +use pin_project_lite::pin_project; use crate::error::Error; use super::{BodySize, MessageBody}; -/// 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. -pub struct SizedStream { - size: u64, - stream: S, +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. + pub struct SizedStream { + size: u64, + #[pin] + stream: S, + } } impl SizedStream where - S: Stream> + Unpin, + S: Stream>, { pub fn new(size: u64, stream: S) -> Self { SizedStream { size, stream } @@ -30,7 +34,7 @@ where impl MessageBody for SizedStream where - S: Stream> + Unpin, + S: Stream>, { fn size(&self) -> BodySize { BodySize::Sized(self.size as u64) @@ -46,9 +50,9 @@ where cx: &mut Context<'_>, ) -> Poll>> { loop { - let stream = &mut self.as_mut().stream; + let stream = self.as_mut().project().stream; - let chunk = match ready!(Pin::new(stream).poll_next(cx)) { + let chunk = match ready!(stream.poll_next(cx)) { Some(Ok(ref bytes)) if bytes.is_empty() => continue, val => val, }; diff --git a/actix-http/src/encoding/encoder.rs b/actix-http/src/encoding/encoder.rs index ee0587fbd..add6ee980 100644 --- a/actix-http/src/encoding/encoder.rs +++ b/actix-http/src/encoding/encoder.rs @@ -92,7 +92,7 @@ impl Encoder { enum EncoderBody { Bytes(Bytes), Stream(#[pin] B), - BoxedStream(Box), + BoxedStream(Pin>), } impl MessageBody for EncoderBody { @@ -117,9 +117,7 @@ impl MessageBody for EncoderBody { } } EncoderBodyProj::Stream(b) => b.poll_next(cx), - EncoderBodyProj::BoxedStream(ref mut b) => { - Pin::new(b.as_mut()).poll_next(cx) - } + EncoderBodyProj::BoxedStream(ref mut b) => b.as_mut().poll_next(cx), } } } diff --git a/actix-http/src/error.rs b/actix-http/src/error.rs index 60a870ecc..01c4beeba 100644 --- a/actix-http/src/error.rs +++ b/actix-http/src/error.rs @@ -62,7 +62,7 @@ pub trait ResponseError: fmt::Debug + fmt::Display { /// Create response for error /// /// Internal server error is generated by default. - fn error_response(&self) -> Response { + fn error_response(&self) -> Response { let mut resp = Response::new(self.status_code()); let mut buf = BytesMut::new(); let _ = write!(Writer(&mut buf), "{}", self); @@ -111,7 +111,7 @@ impl From for Error { } /// Convert `Error` to a `Response` instance -impl From for Response { +impl From for Response { fn from(err: Error) -> Self { Response::from_error(err) } @@ -127,8 +127,8 @@ impl From for Error { } /// Convert Response to a Error -impl From for Error { - fn from(res: Response) -> Error { +impl From> for Error { + fn from(res: Response) -> Error { InternalError::from_response("", res).into() } } @@ -454,7 +454,7 @@ pub struct InternalError { enum InternalErrorType { Status(StatusCode), - Response(RefCell>), + Response(RefCell>>), } impl InternalError { @@ -467,7 +467,7 @@ impl InternalError { } /// Create `InternalError` with predefined `Response`. - pub fn from_response(cause: T, response: Response) -> Self { + pub fn from_response(cause: T, response: Response) -> Self { InternalError { cause, status: InternalErrorType::Response(RefCell::new(Some(response))), @@ -510,7 +510,7 @@ where } } - fn error_response(&self) -> Response { + fn error_response(&self) -> Response { match self.status { InternalErrorType::Status(st) => { let mut res = Response::new(st); @@ -931,11 +931,11 @@ mod tests { #[test] fn test_into_response() { - let resp: Response = ParseError::Incomplete.error_response(); + let resp: Response = ParseError::Incomplete.error_response(); 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 = err.error_response(); assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR); } @@ -966,7 +966,7 @@ mod tests { 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 resp: Response = e.into(); assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR); } @@ -1023,7 +1023,7 @@ mod tests { fn test_internal_error() { let err = InternalError::from_response(ParseError::Method, Response::Ok().into()); - let resp: Response = err.error_response(); + let resp: Response = err.error_response(); assert_eq!(resp.status(), StatusCode::OK); } @@ -1039,121 +1039,121 @@ mod tests { #[test] fn test_error_helpers() { - let r: Response = ErrorBadRequest("err").into(); - assert_eq!(r.status(), StatusCode::BAD_REQUEST); + let res: Response = ErrorBadRequest("err").into(); + assert_eq!(res.status(), StatusCode::BAD_REQUEST); - let r: Response = ErrorUnauthorized("err").into(); - assert_eq!(r.status(), StatusCode::UNAUTHORIZED); + let res: Response = ErrorUnauthorized("err").into(); + assert_eq!(res.status(), StatusCode::UNAUTHORIZED); - let r: Response = ErrorPaymentRequired("err").into(); - assert_eq!(r.status(), StatusCode::PAYMENT_REQUIRED); + let res: Response = ErrorPaymentRequired("err").into(); + assert_eq!(res.status(), StatusCode::PAYMENT_REQUIRED); - let r: Response = ErrorForbidden("err").into(); - assert_eq!(r.status(), StatusCode::FORBIDDEN); + let res: Response = ErrorForbidden("err").into(); + assert_eq!(res.status(), StatusCode::FORBIDDEN); - let r: Response = ErrorNotFound("err").into(); - assert_eq!(r.status(), StatusCode::NOT_FOUND); + let res: Response = ErrorNotFound("err").into(); + assert_eq!(res.status(), StatusCode::NOT_FOUND); - let r: Response = ErrorMethodNotAllowed("err").into(); - assert_eq!(r.status(), StatusCode::METHOD_NOT_ALLOWED); + let res: Response = ErrorMethodNotAllowed("err").into(); + assert_eq!(res.status(), StatusCode::METHOD_NOT_ALLOWED); - let r: Response = ErrorNotAcceptable("err").into(); - assert_eq!(r.status(), StatusCode::NOT_ACCEPTABLE); + let res: Response = ErrorNotAcceptable("err").into(); + assert_eq!(res.status(), StatusCode::NOT_ACCEPTABLE); - let r: Response = ErrorProxyAuthenticationRequired("err").into(); - assert_eq!(r.status(), StatusCode::PROXY_AUTHENTICATION_REQUIRED); + let res: Response = ErrorProxyAuthenticationRequired("err").into(); + assert_eq!(res.status(), StatusCode::PROXY_AUTHENTICATION_REQUIRED); - let r: Response = ErrorRequestTimeout("err").into(); - assert_eq!(r.status(), StatusCode::REQUEST_TIMEOUT); + let res: Response = ErrorRequestTimeout("err").into(); + assert_eq!(res.status(), StatusCode::REQUEST_TIMEOUT); - let r: Response = ErrorConflict("err").into(); - assert_eq!(r.status(), StatusCode::CONFLICT); + let res: Response = ErrorConflict("err").into(); + assert_eq!(res.status(), StatusCode::CONFLICT); - let r: Response = ErrorGone("err").into(); - assert_eq!(r.status(), StatusCode::GONE); + let res: Response = ErrorGone("err").into(); + assert_eq!(res.status(), StatusCode::GONE); - let r: Response = ErrorLengthRequired("err").into(); - assert_eq!(r.status(), StatusCode::LENGTH_REQUIRED); + let res: Response = ErrorLengthRequired("err").into(); + assert_eq!(res.status(), StatusCode::LENGTH_REQUIRED); - let r: Response = ErrorPreconditionFailed("err").into(); - assert_eq!(r.status(), StatusCode::PRECONDITION_FAILED); + let res: Response = ErrorPreconditionFailed("err").into(); + assert_eq!(res.status(), StatusCode::PRECONDITION_FAILED); - let r: Response = ErrorPayloadTooLarge("err").into(); - assert_eq!(r.status(), StatusCode::PAYLOAD_TOO_LARGE); + let res: Response = ErrorPayloadTooLarge("err").into(); + assert_eq!(res.status(), StatusCode::PAYLOAD_TOO_LARGE); - let r: Response = ErrorUriTooLong("err").into(); - assert_eq!(r.status(), StatusCode::URI_TOO_LONG); + let res: Response = ErrorUriTooLong("err").into(); + assert_eq!(res.status(), StatusCode::URI_TOO_LONG); - let r: Response = ErrorUnsupportedMediaType("err").into(); - assert_eq!(r.status(), StatusCode::UNSUPPORTED_MEDIA_TYPE); + let res: Response = ErrorUnsupportedMediaType("err").into(); + assert_eq!(res.status(), StatusCode::UNSUPPORTED_MEDIA_TYPE); - let r: Response = ErrorRangeNotSatisfiable("err").into(); - assert_eq!(r.status(), StatusCode::RANGE_NOT_SATISFIABLE); + let res: Response = ErrorRangeNotSatisfiable("err").into(); + assert_eq!(res.status(), StatusCode::RANGE_NOT_SATISFIABLE); - let r: Response = ErrorExpectationFailed("err").into(); - assert_eq!(r.status(), StatusCode::EXPECTATION_FAILED); + let res: Response = ErrorExpectationFailed("err").into(); + assert_eq!(res.status(), StatusCode::EXPECTATION_FAILED); - let r: Response = ErrorImATeapot("err").into(); - assert_eq!(r.status(), StatusCode::IM_A_TEAPOT); + let res: Response = ErrorImATeapot("err").into(); + assert_eq!(res.status(), StatusCode::IM_A_TEAPOT); - let r: Response = ErrorMisdirectedRequest("err").into(); - assert_eq!(r.status(), StatusCode::MISDIRECTED_REQUEST); + let res: Response = ErrorMisdirectedRequest("err").into(); + assert_eq!(res.status(), StatusCode::MISDIRECTED_REQUEST); - let r: Response = ErrorUnprocessableEntity("err").into(); - assert_eq!(r.status(), StatusCode::UNPROCESSABLE_ENTITY); + let res: Response = ErrorUnprocessableEntity("err").into(); + assert_eq!(res.status(), StatusCode::UNPROCESSABLE_ENTITY); - let r: Response = ErrorLocked("err").into(); - assert_eq!(r.status(), StatusCode::LOCKED); + let res: Response = ErrorLocked("err").into(); + assert_eq!(res.status(), StatusCode::LOCKED); - let r: Response = ErrorFailedDependency("err").into(); - assert_eq!(r.status(), StatusCode::FAILED_DEPENDENCY); + let res: Response = ErrorFailedDependency("err").into(); + assert_eq!(res.status(), StatusCode::FAILED_DEPENDENCY); - let r: Response = ErrorUpgradeRequired("err").into(); - assert_eq!(r.status(), StatusCode::UPGRADE_REQUIRED); + let res: Response = ErrorUpgradeRequired("err").into(); + assert_eq!(res.status(), StatusCode::UPGRADE_REQUIRED); - let r: Response = ErrorPreconditionRequired("err").into(); - assert_eq!(r.status(), StatusCode::PRECONDITION_REQUIRED); + let res: Response = ErrorPreconditionRequired("err").into(); + assert_eq!(res.status(), StatusCode::PRECONDITION_REQUIRED); - let r: Response = ErrorTooManyRequests("err").into(); - assert_eq!(r.status(), StatusCode::TOO_MANY_REQUESTS); + let res: Response = ErrorTooManyRequests("err").into(); + assert_eq!(res.status(), StatusCode::TOO_MANY_REQUESTS); - let r: Response = ErrorRequestHeaderFieldsTooLarge("err").into(); - assert_eq!(r.status(), StatusCode::REQUEST_HEADER_FIELDS_TOO_LARGE); + let res: Response = ErrorRequestHeaderFieldsTooLarge("err").into(); + assert_eq!(res.status(), StatusCode::REQUEST_HEADER_FIELDS_TOO_LARGE); - let r: Response = ErrorUnavailableForLegalReasons("err").into(); - assert_eq!(r.status(), StatusCode::UNAVAILABLE_FOR_LEGAL_REASONS); + let res: Response = ErrorUnavailableForLegalReasons("err").into(); + assert_eq!(res.status(), StatusCode::UNAVAILABLE_FOR_LEGAL_REASONS); - let r: Response = ErrorInternalServerError("err").into(); - assert_eq!(r.status(), StatusCode::INTERNAL_SERVER_ERROR); + let res: Response = ErrorInternalServerError("err").into(); + assert_eq!(res.status(), StatusCode::INTERNAL_SERVER_ERROR); - let r: Response = ErrorNotImplemented("err").into(); - assert_eq!(r.status(), StatusCode::NOT_IMPLEMENTED); + let res: Response = ErrorNotImplemented("err").into(); + assert_eq!(res.status(), StatusCode::NOT_IMPLEMENTED); - let r: Response = ErrorBadGateway("err").into(); - assert_eq!(r.status(), StatusCode::BAD_GATEWAY); + let res: Response = ErrorBadGateway("err").into(); + assert_eq!(res.status(), StatusCode::BAD_GATEWAY); - let r: Response = ErrorServiceUnavailable("err").into(); - assert_eq!(r.status(), StatusCode::SERVICE_UNAVAILABLE); + let res: Response = ErrorServiceUnavailable("err").into(); + assert_eq!(res.status(), StatusCode::SERVICE_UNAVAILABLE); - let r: Response = ErrorGatewayTimeout("err").into(); - assert_eq!(r.status(), StatusCode::GATEWAY_TIMEOUT); + let res: Response = ErrorGatewayTimeout("err").into(); + assert_eq!(res.status(), StatusCode::GATEWAY_TIMEOUT); - let r: Response = ErrorHttpVersionNotSupported("err").into(); - assert_eq!(r.status(), StatusCode::HTTP_VERSION_NOT_SUPPORTED); + let res: Response = ErrorHttpVersionNotSupported("err").into(); + assert_eq!(res.status(), StatusCode::HTTP_VERSION_NOT_SUPPORTED); - let r: Response = ErrorVariantAlsoNegotiates("err").into(); - assert_eq!(r.status(), StatusCode::VARIANT_ALSO_NEGOTIATES); + let res: Response = ErrorVariantAlsoNegotiates("err").into(); + assert_eq!(res.status(), StatusCode::VARIANT_ALSO_NEGOTIATES); - let r: Response = ErrorInsufficientStorage("err").into(); - assert_eq!(r.status(), StatusCode::INSUFFICIENT_STORAGE); + let res: Response = ErrorInsufficientStorage("err").into(); + assert_eq!(res.status(), StatusCode::INSUFFICIENT_STORAGE); - let r: Response = ErrorLoopDetected("err").into(); - assert_eq!(r.status(), StatusCode::LOOP_DETECTED); + let res: Response = ErrorLoopDetected("err").into(); + assert_eq!(res.status(), StatusCode::LOOP_DETECTED); - let r: Response = ErrorNotExtended("err").into(); - assert_eq!(r.status(), StatusCode::NOT_EXTENDED); + let res: Response = ErrorNotExtended("err").into(); + assert_eq!(res.status(), StatusCode::NOT_EXTENDED); - let r: Response = ErrorNetworkAuthenticationRequired("err").into(); - assert_eq!(r.status(), StatusCode::NETWORK_AUTHENTICATION_REQUIRED); + let res: Response = ErrorNetworkAuthenticationRequired("err").into(); + assert_eq!(res.status(), StatusCode::NETWORK_AUTHENTICATION_REQUIRED); } } diff --git a/actix-http/src/h1/dispatcher.rs b/actix-http/src/h1/dispatcher.rs index bba79217a..2e66e0506 100644 --- a/actix-http/src/h1/dispatcher.rs +++ b/actix-http/src/h1/dispatcher.rs @@ -407,7 +407,7 @@ where } // send expect error as response Poll::Ready(Err(err)) => { - let res: Response = err.into().into(); + let res = Response::from_error(err.into()); let (res, body) = res.replace_body(()); self.as_mut().send_response(res, body.into_body())?; } @@ -456,8 +456,7 @@ where // to notify the dispatcher a new state is set and the outer loop // should be continue. Poll::Ready(Err(err)) => { - let err = err.into(); - let res: Response = err.into(); + let res = Response::from_error(err.into()); let (res, body) = res.replace_body(()); return self.send_response(res, body.into_body()); } @@ -477,7 +476,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().into(); + let res = Response::from_error(err.into()); let (res, body) = res.replace_body(()); self.send_response(res, body.into_body()) } @@ -979,19 +978,20 @@ mod tests { } } - fn ok_service() -> impl Service { + fn ok_service() -> impl Service, Error = Error> { fn_service(|_req: Request| ready(Ok::<_, Error>(Response::Ok().finish()))) } - fn echo_path_service() -> impl Service { + fn echo_path_service( + ) -> impl Service, Error = Error> { fn_service(|req: Request| { let path = req.path().as_bytes(); ready(Ok::<_, Error>(Response::Ok().body(Body::from_slice(path)))) }) } - fn echo_payload_service() -> impl Service - { + 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/h2/dispatcher.rs b/actix-http/src/h2/dispatcher.rs index 6e6cd5a2f..87dd66fe7 100644 --- a/actix-http/src/h2/dispatcher.rs +++ b/actix-http/src/h2/dispatcher.rs @@ -252,8 +252,8 @@ where } } - Err(e) => { - let res: Response = e.into().into(); + Err(err) => { + let res = Response::from_error(err.into()); let (res, body) = res.replace_body(()); let mut send = send.take().unwrap(); diff --git a/actix-http/src/http_codes.rs b/actix-http/src/http_codes.rs index 688a08be5..dc4f964de 100644 --- a/actix-http/src/http_codes.rs +++ b/actix-http/src/http_codes.rs @@ -4,7 +4,10 @@ use http::StatusCode; -use crate::response::{Response, ResponseBuilder}; +use crate::{ + body::Body, + response::{Response, ResponseBuilder}, +}; macro_rules! static_resp { ($name:ident, $status:expr) => { @@ -15,7 +18,7 @@ macro_rules! static_resp { }; } -impl Response { +impl Response { static_resp!(Continue, StatusCode::CONTINUE); static_resp!(SwitchingProtocols, StatusCode::SWITCHING_PROTOCOLS); static_resp!(Processing, StatusCode::PROCESSING); diff --git a/actix-http/src/lib.rs b/actix-http/src/lib.rs index 5dd232491..bba7af4c6 100644 --- a/actix-http/src/lib.rs +++ b/actix-http/src/lib.rs @@ -6,7 +6,6 @@ //! | `openssl` | TLS support via [OpenSSL]. | //! | `rustls` | TLS support via [rustls]. | //! | `compress` | Payload compression support. (Deflate, Gzip & Brotli) | -//! | `secure-cookies` | Adds for secure cookies. Enables `cookies` feature. | //! | `trust-dns` | Use [trust-dns] as the client DNS resolver. | //! //! [OpenSSL]: https://crates.io/crates/openssl diff --git a/actix-http/src/response.rs b/actix-http/src/response.rs index 046c4ca56..0c6272485 100644 --- a/actix-http/src/response.rs +++ b/actix-http/src/response.rs @@ -22,7 +22,7 @@ use crate::{ }; /// An HTTP Response -pub struct Response { +pub struct Response { head: BoxedResponseHead, body: ResponseBody, error: Option, @@ -43,7 +43,7 @@ impl Response { /// Constructs a response #[inline] - pub fn new(status: StatusCode) -> Response { + pub fn new(status: StatusCode) -> Response { Response { head: BoxedResponseHead::new(status), body: ResponseBody::Body(Body::Empty), @@ -53,7 +53,7 @@ impl Response { /// Constructs an error response #[inline] - pub fn from_error(error: Error) -> Response { + pub fn from_error(error: Error) -> Response { let mut resp = error.as_response_error().error_response(); if resp.head.status == StatusCode::INTERNAL_SERVER_ERROR { error!("Internal Server Error: {:?}", error); @@ -238,8 +238,8 @@ impl fmt::Debug for Response { } } -impl Future for Response { - type Output = Result; +impl Future for Response { + type Output = Result, Error>; fn poll(mut self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll { Poll::Ready(Ok(Response { @@ -421,7 +421,7 @@ impl ResponseBuilder { /// /// `ResponseBuilder` can not be used after this call. #[inline] - pub fn body>(&mut self, body: B) -> Response { + pub fn body>(&mut self, body: B) -> Response { self.message_body(body.into()) } @@ -446,7 +446,7 @@ impl ResponseBuilder { /// /// `ResponseBuilder` can not be used after this call. #[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, @@ -458,7 +458,7 @@ impl ResponseBuilder { /// /// `ResponseBuilder` can not be used after this call. #[inline] - pub fn finish(&mut self) -> Response { + pub fn finish(&mut self) -> Response { self.body(Body::Empty) } @@ -513,7 +513,7 @@ impl<'a> From<&'a ResponseHead> for ResponseBuilder { } impl Future for ResponseBuilder { - type Output = Result; + type Output = Result, Error>; fn poll(mut self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll { Poll::Ready(Ok(self.finish())) @@ -540,7 +540,7 @@ impl fmt::Debug for ResponseBuilder { } /// Helper converters -impl, E: Into> From> for Response { +impl>, E: Into> From> for Response { fn from(res: Result) -> Self { match res { Ok(val) => val.into(), @@ -549,13 +549,13 @@ impl, E: Into> From> for Response { } } -impl From for Response { +impl From for Response { fn from(mut builder: ResponseBuilder) -> Self { builder.finish() } } -impl From<&'static str> for Response { +impl From<&'static str> for Response { fn from(val: &'static str) -> Self { Response::Ok() .content_type(mime::TEXT_PLAIN_UTF_8) @@ -563,7 +563,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::Ok() .content_type(mime::APPLICATION_OCTET_STREAM) @@ -571,7 +571,7 @@ impl From<&'static [u8]> for Response { } } -impl From for Response { +impl From for Response { fn from(val: String) -> Self { Response::Ok() .content_type(mime::TEXT_PLAIN_UTF_8) @@ -579,7 +579,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::Ok() .content_type(mime::TEXT_PLAIN_UTF_8) @@ -587,7 +587,7 @@ impl<'a> From<&'a String> for Response { } } -impl From for Response { +impl From for Response { fn from(val: Bytes) -> Self { Response::Ok() .content_type(mime::APPLICATION_OCTET_STREAM) @@ -595,7 +595,7 @@ impl From for Response { } } -impl From for Response { +impl From for Response { fn from(val: BytesMut) -> Self { Response::Ok() .content_type(mime::APPLICATION_OCTET_STREAM) @@ -653,7 +653,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(), @@ -662,7 +662,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(), @@ -671,7 +671,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(), @@ -680,7 +680,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(), @@ -690,7 +690,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(), @@ -700,7 +700,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(), @@ -710,7 +710,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(), @@ -723,7 +723,7 @@ mod tests { #[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/ws/mod.rs b/actix-http/src/ws/mod.rs index 1a82ad839..5b18044b2 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::{ - error::ResponseError, header::HeaderValue, message::RequestHead, response::Response, - ResponseBuilder, + body::Body, error::ResponseError, header::HeaderValue, message::RequestHead, + response::Response, ResponseBuilder, }; mod codec; @@ -99,7 +99,7 @@ pub enum HandshakeError { } impl ResponseError for HandshakeError { - fn error_response(&self) -> Response { + fn error_response(&self) -> Response { match self { HandshakeError::GetMethodRequired => Response::MethodNotAllowed() .insert_header((header::ALLOW, "GET")) @@ -320,17 +320,17 @@ mod tests { #[test] fn test_wserror_http_response() { - let resp: Response = HandshakeError::GetMethodRequired.error_response(); + let resp = HandshakeError::GetMethodRequired.error_response(); assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED); - let resp: Response = HandshakeError::NoWebsocketUpgrade.error_response(); + let resp = HandshakeError::NoWebsocketUpgrade.error_response(); assert_eq!(resp.status(), StatusCode::BAD_REQUEST); - let resp: Response = HandshakeError::NoConnectionUpgrade.error_response(); + let resp = HandshakeError::NoConnectionUpgrade.error_response(); assert_eq!(resp.status(), StatusCode::BAD_REQUEST); - let resp: Response = HandshakeError::NoVersionHeader.error_response(); + let resp = HandshakeError::NoVersionHeader.error_response(); assert_eq!(resp.status(), StatusCode::BAD_REQUEST); - let resp: Response = HandshakeError::UnsupportedVersion.error_response(); + let resp = HandshakeError::UnsupportedVersion.error_response(); assert_eq!(resp.status(), StatusCode::BAD_REQUEST); - let resp: Response = HandshakeError::BadWebsocketKey.error_response(); + let resp = HandshakeError::BadWebsocketKey.error_response(); assert_eq!(resp.status(), StatusCode::BAD_REQUEST); } } diff --git a/actix-http/tests/test_openssl.rs b/actix-http/tests/test_openssl.rs index c9cfa7d18..dcf05e8d8 100644 --- a/actix-http/tests/test_openssl.rs +++ b/actix-http/tests/test_openssl.rs @@ -4,11 +4,15 @@ extern crate tls_openssl as openssl; use std::io; -use actix_http::error::{ErrorBadRequest, PayloadError}; -use actix_http::http::header::{self, HeaderName, HeaderValue}; -use actix_http::http::{Method, StatusCode, Version}; -use actix_http::HttpMessage; -use actix_http::{body, Error, HttpService, Request, Response}; +use actix_http::{ + body::{Body, SizedStream}, + error::{ErrorBadRequest, PayloadError}, + http::{ + header::{self, HeaderName, HeaderValue}, + Method, StatusCode, Version, + }, + Error, HttpMessage, HttpService, Request, Response, +}; use actix_http_test::test_server; use actix_service::{fn_service, ServiceFactoryExt}; use actix_utils::future::{err, ok, ready}; @@ -328,7 +332,7 @@ async fn test_h2_body_length() { .h2(|_| { let body = once(ok(Bytes::from_static(STR.as_ref()))); ok::<_, ()>( - Response::Ok().body(body::SizedStream::new(STR.len() as u64, body)), + Response::Ok().body(SizedStream::new(STR.len() as u64, body)), ) }) .openssl(tls_config()) @@ -401,7 +405,7 @@ async fn test_h2_response_http_error_handling() { async fn test_h2_service_error() { let mut srv = test_server(move || { HttpService::build() - .h2(|_| err::(ErrorBadRequest("error"))) + .h2(|_| err::, Error>(ErrorBadRequest("error"))) .openssl(tls_config()) .map_err(|_| ()) }) diff --git a/actix-http/tests/test_rustls.rs b/actix-http/tests/test_rustls.rs index 5b8ba6582..538a2b005 100644 --- a/actix-http/tests/test_rustls.rs +++ b/actix-http/tests/test_rustls.rs @@ -2,10 +2,15 @@ extern crate tls_rustls as rustls; -use actix_http::error::PayloadError; -use actix_http::http::header::{self, HeaderName, HeaderValue}; -use actix_http::http::{Method, StatusCode, Version}; -use actix_http::{body, error, Error, HttpService, Request, Response}; +use actix_http::{ + body::{Body, SizedStream}, + error::{self, PayloadError}, + http::{ + header::{self, HeaderName, HeaderValue}, + Method, StatusCode, Version, + }, + Error, HttpService, Request, Response, +}; use actix_http_test::test_server; use actix_service::{fn_factory_with_config, fn_service}; use actix_utils::future::{err, ok}; @@ -344,7 +349,7 @@ async fn test_h2_body_length() { .h2(|_| { let body = once(ok(Bytes::from_static(STR.as_ref()))); ok::<_, ()>( - Response::Ok().body(body::SizedStream::new(STR.len() as u64, body)), + Response::Ok().body(SizedStream::new(STR.len() as u64, body)), ) }) .rustls(tls_config()) @@ -416,7 +421,7 @@ async fn test_h2_response_http_error_handling() { async fn test_h2_service_error() { let mut srv = test_server(move || { HttpService::build() - .h2(|_| err::(error::ErrorBadRequest("error"))) + .h2(|_| err::, Error>(error::ErrorBadRequest("error"))) .rustls(tls_config()) }) .await; @@ -433,7 +438,7 @@ async fn test_h2_service_error() { async fn test_h1_service_error() { let mut srv = test_server(move || { HttpService::build() - .h1(|_| err::(error::ErrorBadRequest("error"))) + .h1(|_| err::, Error>(error::ErrorBadRequest("error"))) .rustls(tls_config()) }) .await; diff --git a/actix-http/tests/test_server.rs b/actix-http/tests/test_server.rs index 9084a597f..80ec0335b 100644 --- a/actix-http/tests/test_server.rs +++ b/actix-http/tests/test_server.rs @@ -13,7 +13,10 @@ use regex::Regex; use actix_http::HttpMessage; use actix_http::{ - body, error, http, http::header, Error, HttpService, KeepAlive, Request, Response, + body::{Body, SizedStream}, + error, http, + http::header, + Error, HttpService, KeepAlive, Request, Response, }; #[actix_rt::test] @@ -539,7 +542,7 @@ async fn test_h1_body_length() { .h1(|_| { let body = once(ok(Bytes::from_static(STR.as_ref()))); ok::<_, ()>( - Response::Ok().body(body::SizedStream::new(STR.len() as u64, body)), + Response::Ok().body(SizedStream::new(STR.len() as u64, body)), ) }) .tcp() @@ -646,7 +649,7 @@ async fn test_h1_response_http_error_handling() { async fn test_h1_service_error() { let mut srv = test_server(|| { HttpService::build() - .h1(|_| err::(error::ErrorBadRequest("error"))) + .h1(|_| err::, _>(error::ErrorBadRequest("error"))) .tcp() }) .await; diff --git a/src/responder.rs b/src/responder.rs index 66c93d257..2348e9276 100644 --- a/src/responder.rs +++ b/src/responder.rs @@ -1,6 +1,7 @@ use std::fmt; use actix_http::{ + body::Body, error::InternalError, http::{header::IntoHeaderPair, Error as HttpError, HeaderMap, StatusCode}, }; @@ -65,7 +66,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) diff --git a/src/response.rs b/src/response.rs index ce6739dc8..23244e6a5 100644 --- a/src/response.rs +++ b/src/response.rs @@ -3,6 +3,7 @@ use std::{ convert::TryInto, fmt, future::Future, + mem, pin::Pin, task::{Context, Poll}, }; @@ -287,18 +288,17 @@ impl From> for Response { } impl Future for HttpResponse { - type Output = Result; + type Output = Result, Error>; - fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + fn poll(mut self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll { if let Some(err) = self.error.take() { - eprintln!("httpresponse future error"); return Poll::Ready(Ok(Response::from_error(err).into_body())); } - let res = &mut self.res; - actix_rt::pin!(res); - - res.poll(cx) + Poll::Ready(Ok(mem::replace( + &mut self.res, + Response::new(StatusCode::default()), + ))) } } @@ -680,7 +680,7 @@ impl From for HttpResponse { } } -impl From for Response { +impl From for Response { fn from(mut builder: HttpResponseBuilder) -> Self { builder.finish().into() } From 4442535a45969fbd22639f368f976b265a0639fa Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Tue, 13 Apr 2021 12:44:38 +0100 Subject: [PATCH 150/339] clippy --- actix-http/src/h1/decoder.rs | 7 ++++--- actix-http/src/macros.rs | 1 + src/middleware/logger.rs | 6 +++--- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/actix-http/src/h1/decoder.rs b/actix-http/src/h1/decoder.rs index 93a4b13d2..8aba9f623 100644 --- a/actix-http/src/h1/decoder.rs +++ b/actix-http/src/h1/decoder.rs @@ -1213,8 +1213,9 @@ mod tests { #[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"[..], + "GET /test HTTP/1.1\r\n\ + transfer-encoding: chunked\r\n\ + \r\n", ); let mut reader = MessageDecoder::::default(); @@ -1233,7 +1234,7 @@ mod tests { #[test] fn test_response_http10_read_until_eof() { - let mut buf = BytesMut::from(&"HTTP/1.0 200 Ok\r\n\r\ntest data"[..]); + let mut buf = BytesMut::from("HTTP/1.0 200 Ok\r\n\r\ntest data"); let mut reader = MessageDecoder::::default(); let (_msg, pl) = reader.decode(&mut buf).unwrap().unwrap(); diff --git a/actix-http/src/macros.rs b/actix-http/src/macros.rs index 8973aa39b..714629b43 100644 --- a/actix-http/src/macros.rs +++ b/actix-http/src/macros.rs @@ -70,6 +70,7 @@ macro_rules! downcast { #[cfg(test)] mod tests { + #![allow(clippy::upper_case_acronyms)] trait MB { downcast_get_type_id!(); diff --git a/src/middleware/logger.rs b/src/middleware/logger.rs index 3fd372117..40ed9258f 100644 --- a/src/middleware/logger.rs +++ b/src/middleware/logger.rs @@ -380,7 +380,7 @@ impl Format { results.push(match cap.get(3).unwrap().as_str() { "a" => { if key.as_str() == "r" { - FormatText::RealIPRemoteAddr + FormatText::RealIpRemoteAddr } else { unreachable!() } @@ -434,7 +434,7 @@ enum FormatText { Time, TimeMillis, RemoteAddr, - RealIPRemoteAddr, + RealIpRemoteAddr, UrlPath, RequestHeader(HeaderName), ResponseHeader(HeaderName), @@ -554,7 +554,7 @@ impl FormatText { }; *self = s; } - FormatText::RealIPRemoteAddr => { + FormatText::RealIpRemoteAddr => { let s = if let Some(remote) = req.connection_info().realip_remote_addr() { FormatText::Str(remote.to_string()) } else { From 02ced426fd7ff3515f57917c889373299d15d9d5 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Tue, 13 Apr 2021 13:34:22 +0100 Subject: [PATCH 151/339] add body to_bytes helper (#2158) --- actix-http/CHANGES.md | 2 + actix-http/src/body/body_stream.rs | 46 +++++++++++ actix-http/src/body/mod.rs | 115 +++++++++++++++------------ actix-http/src/body/response_body.rs | 5 +- actix-http/src/body/sized_stream.rs | 46 +++++++++++ 5 files changed, 157 insertions(+), 57 deletions(-) diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 2949c40c3..f0f0a0255 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -3,6 +3,7 @@ ## Unreleased - 2021-xx-xx ### Added * `impl MessageBody for Pin>`. [#2152] +* Helper `body::to_bytes` for async collecting message body into Bytes. [#2158] ### Changes * The type parameter of `Response` no longer has a default. [#2152] @@ -22,6 +23,7 @@ [#2065]: https://github.com/actix/actix-web/pull/2065 [#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 ## 3.0.0-beta.5 - 2021-04-02 diff --git a/actix-http/src/body/body_stream.rs b/actix-http/src/body/body_stream.rs index 1157bc539..b81aeb4c1 100644 --- a/actix-http/src/body/body_stream.rs +++ b/actix-http/src/body/body_stream.rs @@ -61,3 +61,49 @@ where } } } + +#[cfg(test)] +mod tests { + use actix_rt::pin; + use actix_utils::future::poll_fn; + use futures_util::stream; + + use super::*; + use crate::body::to_bytes; + + #[actix_rt::test] + async fn skips_empty_chunks() { + let body = BodyStream::new(stream::iter( + ["1", "", "2"] + .iter() + .map(|&v| Ok(Bytes::from(v)) as Result), + )); + pin!(body); + + assert_eq!( + poll_fn(|cx| body.as_mut().poll_next(cx)) + .await + .unwrap() + .ok(), + Some(Bytes::from("1")), + ); + assert_eq!( + poll_fn(|cx| body.as_mut().poll_next(cx)) + .await + .unwrap() + .ok(), + Some(Bytes::from("2")), + ); + } + + #[actix_rt::test] + async fn read_to_bytes() { + let body = BodyStream::new(stream::iter( + ["1", "", "2"] + .iter() + .map(|&v| Ok(Bytes::from(v)) as Result), + )); + + assert_eq!(to_bytes(body).await.ok(), Some(Bytes::from("12"))); + } +} diff --git a/actix-http/src/body/mod.rs b/actix-http/src/body/mod.rs index f5664e1dc..c298dda11 100644 --- a/actix-http/src/body/mod.rs +++ b/actix-http/src/body/mod.rs @@ -1,5 +1,12 @@ //! 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; @@ -15,6 +22,50 @@ pub use self::response_body::ResponseBody; 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::{Body, to_bytes}; +/// use bytes::Bytes; +/// +/// # async fn test_to_bytes() { +/// let body = Body::Empty; +/// let bytes = to_bytes(body).await.unwrap(); +/// assert!(bytes.is_empty()); +/// +/// let body = Body::Bytes(Bytes::from_static(b"123")); +/// let bytes = to_bytes(body).await.unwrap(); +/// assert_eq!(bytes, b"123"[..]); +/// # } +/// ``` +pub async fn to_bytes(body: impl MessageBody) -> Result { + let cap = match body.size() { + BodySize::None | BodySize::Empty | BodySize::Sized(0) => return Ok(Bytes::new()), + BodySize::Sized(size) => size as usize, + 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(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; @@ -22,7 +73,6 @@ mod tests { use actix_rt::pin; use actix_utils::future::poll_fn; use bytes::{Bytes, BytesMut}; - use futures_util::stream; use super::*; @@ -187,58 +237,6 @@ mod tests { ); } - #[actix_rt::test] - async fn body_stream_skips_empty_chunks() { - let body = BodyStream::new(stream::iter( - ["1", "", "2"] - .iter() - .map(|&v| Ok(Bytes::from(v)) as Result), - )); - pin!(body); - - assert_eq!( - poll_fn(|cx| body.as_mut().poll_next(cx)) - .await - .unwrap() - .ok(), - Some(Bytes::from("1")), - ); - assert_eq!( - poll_fn(|cx| body.as_mut().poll_next(cx)) - .await - .unwrap() - .ok(), - Some(Bytes::from("2")), - ); - } - - mod sized_stream { - use super::*; - - #[actix_rt::test] - async fn skips_empty_chunks() { - let body = SizedStream::new( - 2, - stream::iter(["1", "", "2"].iter().map(|&v| Ok(Bytes::from(v)))), - ); - pin!(body); - assert_eq!( - poll_fn(|cx| body.as_mut().poll_next(cx)) - .await - .unwrap() - .ok(), - Some(Bytes::from("1")), - ); - assert_eq!( - poll_fn(|cx| body.as_mut().poll_next(cx)) - .await - .unwrap() - .ok(), - Some(Bytes::from("2")), - ); - } - } - #[actix_rt::test] async fn test_body_casting() { let mut body = String::from("hello cast"); @@ -252,4 +250,15 @@ mod tests { let not_body = resp_body.downcast_ref::<()>(); assert!(not_body.is_none()); } + + #[actix_rt::test] + async fn test_to_bytes() { + let body = Body::Empty; + let bytes = to_bytes(body).await.unwrap(); + assert!(bytes.is_empty()); + + let body = Body::Bytes(Bytes::from_static(b"123")); + let bytes = to_bytes(body).await.unwrap(); + assert_eq!(bytes, b"123"[..]); + } } diff --git a/actix-http/src/body/response_body.rs b/actix-http/src/body/response_body.rs index 97141e11e..b27112475 100644 --- a/actix-http/src/body/response_body.rs +++ b/actix-http/src/body/response_body.rs @@ -55,10 +55,7 @@ impl MessageBody for ResponseBody { self: Pin<&mut Self>, cx: &mut Context<'_>, ) -> Poll>> { - match self.project() { - ResponseBodyProj::Body(body) => body.poll_next(cx), - ResponseBodyProj::Other(body) => Pin::new(body).poll_next(cx), - } + Stream::poll_next(self, cx) } } diff --git a/actix-http/src/body/sized_stream.rs b/actix-http/src/body/sized_stream.rs index f648f6f0b..f0332fc8f 100644 --- a/actix-http/src/body/sized_stream.rs +++ b/actix-http/src/body/sized_stream.rs @@ -61,3 +61,49 @@ where } } } + +#[cfg(test)] +mod tests { + use actix_rt::pin; + use actix_utils::future::poll_fn; + use futures_util::stream; + + use super::*; + use crate::body::to_bytes; + + #[actix_rt::test] + async fn skips_empty_chunks() { + let body = SizedStream::new( + 2, + stream::iter(["1", "", "2"].iter().map(|&v| Ok(Bytes::from(v)))), + ); + + pin!(body); + + assert_eq!( + poll_fn(|cx| body.as_mut().poll_next(cx)) + .await + .unwrap() + .ok(), + Some(Bytes::from("1")), + ); + + assert_eq!( + poll_fn(|cx| body.as_mut().poll_next(cx)) + .await + .unwrap() + .ok(), + Some(Bytes::from("2")), + ); + } + + #[actix_rt::test] + async fn read_to_bytes() { + let body = SizedStream::new( + 2, + stream::iter(["1", "", "2"].iter().map(|&v| Ok(Bytes::from(v)))), + ); + + assert_eq!(to_bytes(body).await.ok(), Some(Bytes::from("12"))); + } +} From 23e0c9b6e0fe233442fdc94322afc1a8abea43cd Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Wed, 14 Apr 2021 02:00:14 +0100 Subject: [PATCH 152/339] remove http-codes builders from actix-http (#2159) --- actix-http/CHANGES.md | 4 + actix-http/examples/echo.rs | 4 +- actix-http/examples/echo2.rs | 4 +- actix-http/examples/hello-world.rs | 4 +- actix-http/src/error.rs | 3 +- actix-http/src/h1/dispatcher.rs | 24 +- actix-http/src/lib.rs | 1 - actix-http/src/response.rs | 81 ++-- actix-http/src/ws/mod.rs | 40 +- actix-http/tests/test_client.rs | 10 +- actix-http/tests/test_openssl.rs | 24 +- actix-http/tests/test_rustls.rs | 26 +- actix-http/tests/test_server.rs | 52 +- actix-http/tests/test_ws.rs | 2 +- awc/tests/test_ws.rs | 2 +- src/app_service.rs | 9 +- src/resource.rs | 6 +- src/{response.rs => response/builder.rs} | 453 +----------------- .../src => src/response}/http_codes.rs | 23 +- src/response/mod.rs | 10 + src/response/response.rs | 330 +++++++++++++ 21 files changed, 539 insertions(+), 573 deletions(-) rename src/{response.rs => response/builder.rs} (55%) rename {actix-http/src => src/response}/http_codes.rs (90%) create mode 100644 src/response/mod.rs create mode 100644 src/response/response.rs diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index f0f0a0255..17ca7340f 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -3,6 +3,7 @@ ## Unreleased - 2021-xx-xx ### 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] ### Changes @@ -19,10 +20,13 @@ * `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 [#2152]: https://github.com/actix/actix-web/pull/2152 +[#2159]: https://github.com/actix/actix-web/pull/2159 [#2158]: https://github.com/actix/actix-web/pull/2158 diff --git a/actix-http/examples/echo.rs b/actix-http/examples/echo.rs index 176ac5c2b..b2cdb0be1 100644 --- a/actix-http/examples/echo.rs +++ b/actix-http/examples/echo.rs @@ -1,6 +1,6 @@ use std::{env, io}; -use actix_http::{Error, HttpService, Request, Response}; +use actix_http::{http::StatusCode, Error, HttpService, Request, Response}; use actix_server::Server; use bytes::BytesMut; use futures_util::StreamExt as _; @@ -25,7 +25,7 @@ async fn main() -> io::Result<()> { info!("request body: {:?}", body); Ok::<_, Error>( - Response::Ok() + Response::build(StatusCode::OK) .insert_header(( "x-head", HeaderValue::from_static("dummy value!"), diff --git a/actix-http/examples/echo2.rs b/actix-http/examples/echo2.rs index 483a79aac..9acf4bbae 100644 --- a/actix-http/examples/echo2.rs +++ b/actix-http/examples/echo2.rs @@ -1,6 +1,6 @@ use std::{env, io}; -use actix_http::{body::Body, http::HeaderValue}; +use actix_http::{body::Body, http::HeaderValue, http::StatusCode}; use actix_http::{Error, HttpService, Request, Response}; use actix_server::Server; use bytes::BytesMut; @@ -14,7 +14,7 @@ async fn handle_request(mut req: Request) -> Result, Error> { } info!("request body: {:?}", body); - Ok(Response::Ok() + Ok(Response::build(StatusCode::OK) .insert_header(("x-head", HeaderValue::from_static("dummy value!"))) .body(body)) } diff --git a/actix-http/examples/hello-world.rs b/actix-http/examples/hello-world.rs index a99ddae46..85994556d 100644 --- a/actix-http/examples/hello-world.rs +++ b/actix-http/examples/hello-world.rs @@ -1,6 +1,6 @@ use std::{env, io}; -use actix_http::{HttpService, Response}; +use actix_http::{http::StatusCode, HttpService, Response}; use actix_server::Server; use actix_utils::future; use http::header::HeaderValue; @@ -18,7 +18,7 @@ async fn main() -> io::Result<()> { .client_disconnect(1000) .finish(|_req| { info!("{:?}", _req); - let mut res = Response::Ok(); + let mut res = Response::build(StatusCode::OK); res.insert_header(( "x-head", HeaderValue::from_static("dummy value!"), diff --git a/actix-http/src/error.rs b/actix-http/src/error.rs index 01c4beeba..705ae7f06 100644 --- a/actix-http/src/error.rs +++ b/actix-http/src/error.rs @@ -1021,8 +1021,7 @@ mod tests { #[test] fn test_internal_error() { - let err = - InternalError::from_response(ParseError::Method, Response::Ok().into()); + let err = InternalError::from_response(ParseError::Method, Response::ok()); let resp: Response = err.error_response(); assert_eq!(resp.status(), StatusCode::OK); } diff --git a/actix-http/src/h1/dispatcher.rs b/actix-http/src/h1/dispatcher.rs index 2e66e0506..e775846e9 100644 --- a/actix-http/src/h1/dispatcher.rs +++ b/actix-http/src/h1/dispatcher.rs @@ -21,6 +21,7 @@ use crate::body::{Body, BodySize, MessageBody, ResponseBody}; 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; @@ -562,7 +563,7 @@ where ); this.flags.insert(Flags::READ_DISCONNECT); this.messages.push_back(DispatcherMessage::Error( - Response::InternalServerError().finish().drop_body(), + Response::internal_server_error().drop_body(), )); *this.error = Some(DispatchError::InternalError); break; @@ -575,7 +576,7 @@ where error!("Internal server error: unexpected eof"); this.flags.insert(Flags::READ_DISCONNECT); this.messages.push_back(DispatcherMessage::Error( - Response::InternalServerError().finish().drop_body(), + Response::internal_server_error().drop_body(), )); *this.error = Some(DispatchError::InternalError); break; @@ -598,7 +599,8 @@ where } // Requests overflow buffer size should be responded with 431 this.messages.push_back(DispatcherMessage::Error( - Response::RequestHeaderFieldsTooLarge().finish().drop_body(), + Response::new(StatusCode::REQUEST_HEADER_FIELDS_TOO_LARGE) + .drop_body(), )); this.flags.insert(Flags::READ_DISCONNECT); *this.error = Some(ParseError::TooLarge.into()); @@ -611,7 +613,7 @@ where // Malformed requests should be responded with 400 this.messages.push_back(DispatcherMessage::Error( - Response::BadRequest().finish().drop_body(), + Response::bad_request().drop_body(), )); this.flags.insert(Flags::READ_DISCONNECT); *this.error = Some(err.into()); @@ -684,7 +686,8 @@ where if !this.flags.contains(Flags::STARTED) { trace!("Slow request timeout"); let _ = self.as_mut().send_response( - Response::RequestTimeout().finish().drop_body(), + Response::new(StatusCode::REQUEST_TIMEOUT) + .drop_body(), ResponseBody::Other(Body::Empty), ); this = self.project(); @@ -951,6 +954,7 @@ mod tests { use actix_service::fn_service; use actix_utils::future::{ready, Ready}; + use bytes::Bytes; use futures_util::future::lazy; use super::*; @@ -979,19 +983,21 @@ mod tests { } fn ok_service() -> impl Service, Error = Error> { - fn_service(|_req: Request| ready(Ok::<_, Error>(Response::Ok().finish()))) + 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().body(Body::from_slice(path)))) + ready(Ok::<_, Error>( + Response::ok().set_body(Body::from_slice(path)), + )) }) } fn echo_payload_service( - ) -> impl Service, Error = Error> { + ) -> impl Service, Error = Error> { fn_service(|mut req: Request| { Box::pin(async move { use futures_util::stream::StreamExt as _; @@ -1002,7 +1008,7 @@ mod tests { body.extend_from_slice(chunk.unwrap().chunk()) } - Ok::<_, Error>(Response::Ok().body(body)) + Ok::<_, Error>(Response::ok().set_body(body.freeze())) }) }) } diff --git a/actix-http/src/lib.rs b/actix-http/src/lib.rs index bba7af4c6..3125e3874 100644 --- a/actix-http/src/lib.rs +++ b/actix-http/src/lib.rs @@ -37,7 +37,6 @@ pub mod encoding; mod extensions; mod header; mod helpers; -mod http_codes; mod http_message; mod message; mod payload; diff --git a/actix-http/src/response.rs b/actix-http/src/response.rs index 0c6272485..7f73538fc 100644 --- a/actix-http/src/response.rs +++ b/actix-http/src/response.rs @@ -29,18 +29,6 @@ pub struct Response { } impl Response { - /// Create HTTP response builder with specific status. - #[inline] - pub fn build(status: StatusCode) -> ResponseBuilder { - ResponseBuilder::new(status) - } - - /// Create HTTP response builder - #[inline] - pub fn build_from>(source: T) -> ResponseBuilder { - source.into() - } - /// Constructs a response #[inline] pub fn new(status: StatusCode) -> Response { @@ -51,6 +39,41 @@ impl Response { } } + /// Create HTTP response builder with specific status. + #[inline] + pub fn build(status: StatusCode) -> ResponseBuilder { + ResponseBuilder::new(status) + } + + // just a couple frequently used shortcuts + // this list should not grow larger than a few + + /// Creates a new response with status 200 OK. + #[inline] + pub fn ok() -> Response { + Response::new(StatusCode::OK) + } + + /// Creates a new response with status 400 Bad Request. + #[inline] + pub fn bad_request() -> Response { + Response::new(StatusCode::BAD_REQUEST) + } + + /// Creates a new response with status 404 Not Found. + #[inline] + pub fn not_found() -> Response { + Response::new(StatusCode::NOT_FOUND) + } + + /// Creates a new response with status 500 Internal Server Error. + #[inline] + pub fn internal_server_error() -> Response { + Response::new(StatusCode::INTERNAL_SERVER_ERROR) + } + + // end shortcuts + /// Constructs an error response #[inline] pub fn from_error(error: Error) -> Response { @@ -281,9 +304,9 @@ impl ResponseBuilder { /// /// ``` /// # use actix_http::Response; - /// use actix_http::http::header; + /// use actix_http::http::{header, StatusCode}; /// - /// Response::Ok() + /// Response::build(StatusCode::OK) /// .insert_header((header::CONTENT_TYPE, mime::APPLICATION_JSON)) /// .insert_header(("X-TEST", "value")) /// .finish(); @@ -308,9 +331,9 @@ impl ResponseBuilder { /// /// ``` /// # use actix_http::Response; - /// use actix_http::http::header; + /// use actix_http::http::{header, StatusCode}; /// - /// Response::Ok() + /// Response::build(StatusCode::OK) /// .append_header((header::CONTENT_TYPE, mime::APPLICATION_JSON)) /// .append_header(("X-TEST", "value1")) /// .append_header(("X-TEST", "value2")) @@ -557,7 +580,7 @@ impl From for Response { impl From<&'static str> for Response { fn from(val: &'static str) -> Self { - Response::Ok() + Response::build(StatusCode::OK) .content_type(mime::TEXT_PLAIN_UTF_8) .body(val) } @@ -565,7 +588,7 @@ impl From<&'static str> for Response { impl From<&'static [u8]> for Response { fn from(val: &'static [u8]) -> Self { - Response::Ok() + Response::build(StatusCode::OK) .content_type(mime::APPLICATION_OCTET_STREAM) .body(val) } @@ -573,7 +596,7 @@ impl From<&'static [u8]> for Response { impl From for Response { fn from(val: String) -> Self { - Response::Ok() + Response::build(StatusCode::OK) .content_type(mime::TEXT_PLAIN_UTF_8) .body(val) } @@ -581,7 +604,7 @@ impl From for Response { impl<'a> From<&'a String> for Response { fn from(val: &'a String) -> Self { - Response::Ok() + Response::build(StatusCode::OK) .content_type(mime::TEXT_PLAIN_UTF_8) .body(val) } @@ -589,7 +612,7 @@ impl<'a> From<&'a String> for Response { impl From for Response { fn from(val: Bytes) -> Self { - Response::Ok() + Response::build(StatusCode::OK) .content_type(mime::APPLICATION_OCTET_STREAM) .body(val) } @@ -597,7 +620,7 @@ impl From for Response { impl From for Response { fn from(val: BytesMut) -> Self { - Response::Ok() + Response::build(StatusCode::OK) .content_type(mime::APPLICATION_OCTET_STREAM) .body(val) } @@ -611,7 +634,7 @@ mod tests { #[test] fn test_debug() { - let resp = Response::Ok() + let resp = Response::build(StatusCode::OK) .append_header((COOKIE, HeaderValue::from_static("cookie1=value1; "))) .append_header((COOKIE, HeaderValue::from_static("cookie2=value2; "))) .finish(); @@ -621,7 +644,9 @@ mod tests { #[test] fn test_basic_builder() { - let resp = Response::Ok().insert_header(("X-TEST", "value")).finish(); + let resp = Response::build(StatusCode::OK) + .insert_header(("X-TEST", "value")) + .finish(); assert_eq!(resp.status(), StatusCode::OK); } @@ -741,7 +766,7 @@ mod tests { #[test] fn response_builder_header_insert_kv() { - let mut res = Response::Ok(); + let mut res = Response::build(StatusCode::OK); res.insert_header(("Content-Type", "application/octet-stream")); let res = res.finish(); @@ -753,7 +778,7 @@ mod tests { #[test] fn response_builder_header_insert_typed() { - let mut res = Response::Ok(); + let mut res = Response::build(StatusCode::OK); res.insert_header((header::CONTENT_TYPE, mime::APPLICATION_OCTET_STREAM)); let res = res.finish(); @@ -765,7 +790,7 @@ mod tests { #[test] fn response_builder_header_append_kv() { - let mut res = Response::Ok(); + let mut res = Response::build(StatusCode::OK); res.append_header(("Content-Type", "application/octet-stream")); res.append_header(("Content-Type", "application/json")); let res = res.finish(); @@ -778,7 +803,7 @@ mod tests { #[test] fn response_builder_header_append_typed() { - let mut res = Response::Ok(); + let mut res = Response::build(StatusCode::OK); res.append_header((header::CONTENT_TYPE, mime::APPLICATION_OCTET_STREAM)); res.append_header((header::CONTENT_TYPE, mime::APPLICATION_JSON)); let res = res.finish(); diff --git a/actix-http/src/ws/mod.rs b/actix-http/src/ws/mod.rs index 5b18044b2..22df2b4ff 100644 --- a/actix-http/src/ws/mod.rs +++ b/actix-http/src/ws/mod.rs @@ -101,29 +101,37 @@ pub enum HandshakeError { impl ResponseError for HandshakeError { fn error_response(&self) -> Response { match self { - HandshakeError::GetMethodRequired => Response::MethodNotAllowed() - .insert_header((header::ALLOW, "GET")) - .finish(), + HandshakeError::GetMethodRequired => { + Response::build(StatusCode::METHOD_NOT_ALLOWED) + .insert_header((header::ALLOW, "GET")) + .finish() + } - HandshakeError::NoWebsocketUpgrade => Response::BadRequest() - .reason("No WebSocket Upgrade header found") - .finish(), + HandshakeError::NoWebsocketUpgrade => { + Response::build(StatusCode::BAD_REQUEST) + .reason("No WebSocket Upgrade header found") + .finish() + } - HandshakeError::NoConnectionUpgrade => Response::BadRequest() - .reason("No Connection upgrade") - .finish(), + HandshakeError::NoConnectionUpgrade => { + Response::build(StatusCode::BAD_REQUEST) + .reason("No Connection upgrade") + .finish() + } - HandshakeError::NoVersionHeader => Response::BadRequest() + HandshakeError::NoVersionHeader => Response::build(StatusCode::BAD_REQUEST) .reason("WebSocket version header is required") .finish(), - HandshakeError::UnsupportedVersion => Response::BadRequest() - .reason("Unsupported WebSocket version") - .finish(), - - HandshakeError::BadWebsocketKey => { - Response::BadRequest().reason("Handshake error").finish() + HandshakeError::UnsupportedVersion => { + Response::build(StatusCode::BAD_REQUEST) + .reason("Unsupported WebSocket version") + .finish() } + + HandshakeError::BadWebsocketKey => Response::build(StatusCode::BAD_REQUEST) + .reason("Handshake error") + .finish(), } } } diff --git a/actix-http/tests/test_client.rs b/actix-http/tests/test_client.rs index b5f8d54b9..0a06d90e5 100644 --- a/actix-http/tests/test_client.rs +++ b/actix-http/tests/test_client.rs @@ -33,7 +33,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().body(STR))) + .finish(|_| future::ok::<_, ()>(Response::ok().set_body(STR))) .tcp() }) .await; @@ -61,7 +61,7 @@ async fn test_h1_v2() { async fn test_connection_close() { let srv = test_server(move || { HttpService::build() - .finish(|_| future::ok::<_, ()>(Response::Ok().body(STR))) + .finish(|_| future::ok::<_, ()>(Response::ok().set_body(STR))) .tcp() .map(|_| ()) }) @@ -77,9 +77,9 @@ async fn test_with_query_parameter() { HttpService::build() .finish(|req: Request| { if req.uri().query().unwrap().contains("qp=") { - future::ok::<_, ()>(Response::Ok().finish()) + future::ok::<_, ()>(Response::ok()) } else { - future::ok::<_, ()>(Response::BadRequest().finish()) + future::ok::<_, ()>(Response::bad_request()) } }) .tcp() @@ -112,7 +112,7 @@ async fn test_h1_expect() { let str = std::str::from_utf8(&buf).unwrap(); assert_eq!(str, "expect body"); - Ok::<_, ()>(Response::Ok().finish()) + Ok::<_, ()>(Response::ok()) }) .tcp() }) diff --git a/actix-http/tests/test_openssl.rs b/actix-http/tests/test_openssl.rs index dcf05e8d8..7cbd58518 100644 --- a/actix-http/tests/test_openssl.rs +++ b/actix-http/tests/test_openssl.rs @@ -71,7 +71,7 @@ fn tls_config() -> SslAcceptor { async fn test_h2() -> io::Result<()> { let srv = test_server(move || { HttpService::build() - .h2(|_| ok::<_, Error>(Response::Ok().finish())) + .h2(|_| ok::<_, Error>(Response::ok())) .openssl(tls_config()) .map_err(|_| ()) }) @@ -89,7 +89,7 @@ async fn test_h2_1() -> io::Result<()> { .finish(|req: Request| { assert!(req.peer_addr().is_some()); assert_eq!(req.version(), Version::HTTP_2); - ok::<_, Error>(Response::Ok().finish()) + ok::<_, Error>(Response::ok()) }) .openssl(tls_config()) .map_err(|_| ()) @@ -108,7 +108,7 @@ async fn test_h2_body() -> io::Result<()> { HttpService::build() .h2(|mut req: Request<_>| async move { let body = load_body(req.take_payload()).await?; - Ok::<_, Error>(Response::Ok().body(body)) + Ok::<_, Error>(Response::ok().set_body(body)) }) .openssl(tls_config()) .map_err(|_| ()) @@ -186,7 +186,7 @@ async fn test_h2_headers() { let mut srv = test_server(move || { let data = data.clone(); HttpService::build().h2(move |_| { - let mut builder = Response::Ok(); + let mut builder = Response::build(StatusCode::OK); for idx in 0..90 { builder.insert_header( (format!("X-TEST-{}", idx).as_str(), @@ -245,7 +245,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().body(STR))) + .h2(|_| ok::<_, ()>(Response::ok().set_body(STR))) .openssl(tls_config()) .map_err(|_| ()) }) @@ -263,7 +263,7 @@ async fn test_h2_body2() { async fn test_h2_head_empty() { let mut srv = test_server(move || { HttpService::build() - .finish(|_| ok::<_, ()>(Response::Ok().body(STR))) + .finish(|_| ok::<_, ()>(Response::ok().set_body(STR))) .openssl(tls_config()) .map_err(|_| ()) }) @@ -287,7 +287,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().body(STR))) + .h2(|_| ok::<_, ()>(Response::ok().set_body(STR))) .openssl(tls_config()) .map_err(|_| ()) }) @@ -310,7 +310,7 @@ async fn test_h2_head_binary() { async fn test_h2_head_binary2() { let srv = test_server(move || { HttpService::build() - .h2(|_| ok::<_, ()>(Response::Ok().body(STR))) + .h2(|_| ok::<_, ()>(Response::ok().set_body(STR))) .openssl(tls_config()) .map_err(|_| ()) }) @@ -332,7 +332,7 @@ async fn test_h2_body_length() { .h2(|_| { let body = once(ok(Bytes::from_static(STR.as_ref()))); ok::<_, ()>( - Response::Ok().body(SizedStream::new(STR.len() as u64, body)), + Response::ok().set_body(SizedStream::new(STR.len() as u64, body)), ) }) .openssl(tls_config()) @@ -355,7 +355,7 @@ async fn test_h2_body_chunked_explicit() { .h2(|_| { let body = once(ok::<_, Error>(Bytes::from_static(STR.as_ref()))); ok::<_, ()>( - Response::Ok() + Response::build(StatusCode::OK) .insert_header((header::TRANSFER_ENCODING, "chunked")) .streaming(body), ) @@ -383,7 +383,7 @@ async fn test_h2_response_http_error_handling() { .h2(fn_service(|_| { let broken_header = Bytes::from_static(b"\0\0\0"); ok::<_, ()>( - Response::Ok() + Response::build(StatusCode::OK) .insert_header((header::CONTENT_TYPE, broken_header)) .body(STR), ) @@ -428,7 +428,7 @@ async fn test_h2_on_connect() { }) .h2(|req: Request| { assert!(req.extensions().contains::()); - ok::<_, ()>(Response::Ok().finish()) + ok::<_, ()>(Response::ok()) }) .openssl(tls_config()) .map_err(|_| ()) diff --git a/actix-http/tests/test_rustls.rs b/actix-http/tests/test_rustls.rs index 538a2b005..a122ab847 100644 --- a/actix-http/tests/test_rustls.rs +++ b/actix-http/tests/test_rustls.rs @@ -56,7 +56,7 @@ fn tls_config() -> RustlsServerConfig { async fn test_h1() -> io::Result<()> { let srv = test_server(move || { HttpService::build() - .h1(|_| ok::<_, Error>(Response::Ok().finish())) + .h1(|_| ok::<_, Error>(Response::ok())) .rustls(tls_config()) }) .await; @@ -70,7 +70,7 @@ async fn test_h1() -> io::Result<()> { async fn test_h2() -> io::Result<()> { let srv = test_server(move || { HttpService::build() - .h2(|_| ok::<_, Error>(Response::Ok().finish())) + .h2(|_| ok::<_, Error>(Response::ok())) .rustls(tls_config()) }) .await; @@ -87,7 +87,7 @@ async fn test_h1_1() -> io::Result<()> { .h1(|req: Request| { assert!(req.peer_addr().is_some()); assert_eq!(req.version(), Version::HTTP_11); - ok::<_, Error>(Response::Ok().finish()) + ok::<_, Error>(Response::ok()) }) .rustls(tls_config()) }) @@ -105,7 +105,7 @@ async fn test_h2_1() -> io::Result<()> { .finish(|req: Request| { assert!(req.peer_addr().is_some()); assert_eq!(req.version(), Version::HTTP_2); - ok::<_, Error>(Response::Ok().finish()) + ok::<_, Error>(Response::ok()) }) .rustls(tls_config()) }) @@ -123,7 +123,7 @@ async fn test_h2_body1() -> io::Result<()> { HttpService::build() .h2(|mut req: Request<_>| async move { let body = load_body(req.take_payload()).await?; - Ok::<_, Error>(Response::Ok().body(body)) + Ok::<_, Error>(Response::ok().set_body(body)) }) .rustls(tls_config()) }) @@ -199,7 +199,7 @@ async fn test_h2_headers() { let mut srv = test_server(move || { let data = data.clone(); HttpService::build().h2(move |_| { - let mut config = Response::Ok(); + let mut config = Response::build(StatusCode::OK); for idx in 0..90 { config.insert_header(( format!("X-TEST-{}", idx).as_str(), @@ -257,7 +257,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().body(STR))) + .h2(|_| ok::<_, ()>(Response::ok().set_body(STR))) .rustls(tls_config()) }) .await; @@ -274,7 +274,7 @@ async fn test_h2_body2() { async fn test_h2_head_empty() { let mut srv = test_server(move || { HttpService::build() - .finish(|_| ok::<_, ()>(Response::Ok().body(STR))) + .finish(|_| ok::<_, ()>(Response::ok().set_body(STR))) .rustls(tls_config()) }) .await; @@ -300,7 +300,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().body(STR))) + .h2(|_| ok::<_, ()>(Response::ok().set_body(STR))) .rustls(tls_config()) }) .await; @@ -325,7 +325,7 @@ async fn test_h2_head_binary() { async fn test_h2_head_binary2() { let srv = test_server(move || { HttpService::build() - .h2(|_| ok::<_, ()>(Response::Ok().body(STR))) + .h2(|_| ok::<_, ()>(Response::ok().set_body(STR))) .rustls(tls_config()) }) .await; @@ -349,7 +349,7 @@ async fn test_h2_body_length() { .h2(|_| { let body = once(ok(Bytes::from_static(STR.as_ref()))); ok::<_, ()>( - Response::Ok().body(SizedStream::new(STR.len() as u64, body)), + Response::ok().set_body(SizedStream::new(STR.len() as u64, body)), ) }) .rustls(tls_config()) @@ -371,7 +371,7 @@ async fn test_h2_body_chunked_explicit() { .h2(|_| { let body = once(ok::<_, Error>(Bytes::from_static(STR.as_ref()))); ok::<_, ()>( - Response::Ok() + Response::build(StatusCode::OK) .insert_header((header::TRANSFER_ENCODING, "chunked")) .streaming(body), ) @@ -399,7 +399,7 @@ async fn test_h2_response_http_error_handling() { ok::<_, ()>(fn_service(|_| { let broken_header = Bytes::from_static(b"\0\0\0"); ok::<_, ()>( - Response::Ok() + Response::build(StatusCode::OK) .insert_header((http::header::CONTENT_TYPE, broken_header)) .body(STR), ) diff --git a/actix-http/tests/test_server.rs b/actix-http/tests/test_server.rs index 80ec0335b..9b8b039c3 100644 --- a/actix-http/tests/test_server.rs +++ b/actix-http/tests/test_server.rs @@ -14,8 +14,8 @@ use regex::Regex; use actix_http::HttpMessage; use actix_http::{ body::{Body, SizedStream}, - error, http, - http::header, + error, + http::{self, header, StatusCode}, Error, HttpService, KeepAlive, Request, Response, }; @@ -28,7 +28,7 @@ async fn test_h1() { .client_disconnect(1000) .h1(|req: Request| { assert!(req.peer_addr().is_some()); - ok::<_, ()>(Response::Ok().finish()) + ok::<_, ()>(Response::ok()) }) .tcp() }) @@ -48,7 +48,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().finish()) + ok::<_, ()>(Response::ok()) }) .tcp() }) @@ -69,7 +69,7 @@ async fn test_expect_continue() { err(error::ErrorPreconditionFailed("error")) } })) - .finish(|_| ok::<_, ()>(Response::Ok().finish())) + .finish(|_| ok::<_, ()>(Response::ok())) .tcp() }) .await; @@ -100,7 +100,7 @@ async fn test_expect_continue_h1() { } }) })) - .h1(fn_service(|_| ok::<_, ()>(Response::Ok().finish()))) + .h1(fn_service(|_| ok::<_, ()>(Response::ok()))) .tcp() }) .await; @@ -134,7 +134,9 @@ async fn test_chunked_payload() { }) .fold(0usize, |acc, chunk| ready(acc + chunk.len())) .map(|req_size| { - Ok::<_, Error>(Response::Ok().body(format!("size={}", req_size))) + Ok::<_, Error>( + Response::ok().set_body(format!("size={}", req_size)), + ) }) })) .tcp() @@ -179,7 +181,7 @@ async fn test_slow_request() { let srv = test_server(|| { HttpService::build() .client_timeout(100) - .finish(|_| ok::<_, ()>(Response::Ok().finish())) + .finish(|_| ok::<_, ()>(Response::ok())) .tcp() }) .await; @@ -195,7 +197,7 @@ async fn test_slow_request() { async fn test_http1_malformed_request() { let srv = test_server(|| { HttpService::build() - .h1(|_| ok::<_, ()>(Response::Ok().finish())) + .h1(|_| ok::<_, ()>(Response::ok())) .tcp() }) .await; @@ -211,7 +213,7 @@ async fn test_http1_malformed_request() { async fn test_http1_keepalive() { let srv = test_server(|| { HttpService::build() - .h1(|_| ok::<_, ()>(Response::Ok().finish())) + .h1(|_| ok::<_, ()>(Response::ok())) .tcp() }) .await; @@ -233,7 +235,7 @@ async fn test_http1_keepalive_timeout() { let srv = test_server(|| { HttpService::build() .keep_alive(1) - .h1(|_| ok::<_, ()>(Response::Ok().finish())) + .h1(|_| ok::<_, ()>(Response::ok())) .tcp() }) .await; @@ -254,7 +256,7 @@ async fn test_http1_keepalive_timeout() { async fn test_http1_keepalive_close() { let srv = test_server(|| { HttpService::build() - .h1(|_| ok::<_, ()>(Response::Ok().finish())) + .h1(|_| ok::<_, ()>(Response::ok())) .tcp() }) .await; @@ -275,7 +277,7 @@ async fn test_http1_keepalive_close() { async fn test_http10_keepalive_default_close() { let srv = test_server(|| { HttpService::build() - .h1(|_| ok::<_, ()>(Response::Ok().finish())) + .h1(|_| ok::<_, ()>(Response::ok())) .tcp() }) .await; @@ -295,7 +297,7 @@ async fn test_http10_keepalive_default_close() { async fn test_http10_keepalive() { let srv = test_server(|| { HttpService::build() - .h1(|_| ok::<_, ()>(Response::Ok().finish())) + .h1(|_| ok::<_, ()>(Response::ok())) .tcp() }) .await; @@ -323,7 +325,7 @@ async fn test_http1_keepalive_disabled() { let srv = test_server(|| { HttpService::build() .keep_alive(KeepAlive::Disabled) - .h1(|_| ok::<_, ()>(Response::Ok().finish())) + .h1(|_| ok::<_, ()>(Response::ok())) .tcp() }) .await; @@ -394,7 +396,7 @@ async fn test_h1_headers() { let mut srv = test_server(move || { let data = data.clone(); HttpService::build().h1(move |_| { - let mut builder = Response::Ok(); + let mut builder = Response::build(StatusCode::OK); for idx in 0..90 { builder.insert_header(( format!("X-TEST-{}", idx).as_str(), @@ -451,7 +453,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().body(STR))) + .h1(|_| ok::<_, ()>(Response::ok().set_body(STR))) .tcp() }) .await; @@ -468,7 +470,7 @@ async fn test_h1_body() { async fn test_h1_head_empty() { let mut srv = test_server(|| { HttpService::build() - .h1(|_| ok::<_, ()>(Response::Ok().body(STR))) + .h1(|_| ok::<_, ()>(Response::ok().set_body(STR))) .tcp() }) .await; @@ -493,7 +495,7 @@ async fn test_h1_head_empty() { async fn test_h1_head_binary() { let mut srv = test_server(|| { HttpService::build() - .h1(|_| ok::<_, ()>(Response::Ok().body(STR))) + .h1(|_| ok::<_, ()>(Response::ok().set_body(STR))) .tcp() }) .await; @@ -518,7 +520,7 @@ async fn test_h1_head_binary() { async fn test_h1_head_binary2() { let srv = test_server(|| { HttpService::build() - .h1(|_| ok::<_, ()>(Response::Ok().body(STR))) + .h1(|_| ok::<_, ()>(Response::ok().set_body(STR))) .tcp() }) .await; @@ -542,7 +544,7 @@ async fn test_h1_body_length() { .h1(|_| { let body = once(ok(Bytes::from_static(STR.as_ref()))); ok::<_, ()>( - Response::Ok().body(SizedStream::new(STR.len() as u64, body)), + Response::ok().set_body(SizedStream::new(STR.len() as u64, body)), ) }) .tcp() @@ -564,7 +566,7 @@ async fn test_h1_body_chunked_explicit() { .h1(|_| { let body = once(ok::<_, Error>(Bytes::from_static(STR.as_ref()))); ok::<_, ()>( - Response::Ok() + Response::build(StatusCode::OK) .insert_header((header::TRANSFER_ENCODING, "chunked")) .streaming(body), ) @@ -598,7 +600,7 @@ async fn test_h1_body_chunked_implicit() { HttpService::build() .h1(|_| { let body = once(ok::<_, Error>(Bytes::from_static(STR.as_ref()))); - ok::<_, ()>(Response::Ok().streaming(body)) + ok::<_, ()>(Response::build(StatusCode::OK).streaming(body)) }) .tcp() }) @@ -628,7 +630,7 @@ async fn test_h1_response_http_error_handling() { .h1(fn_service(|_| { let broken_header = Bytes::from_static(b"\0\0\0"); ok::<_, ()>( - Response::Ok() + Response::build(StatusCode::OK) .insert_header((http::header::CONTENT_TYPE, broken_header)) .body(STR), ) @@ -671,7 +673,7 @@ async fn test_h1_on_connect() { }) .h1(|req: Request| { assert!(req.extensions().contains::()); - ok::<_, ()>(Response::Ok().finish()) + ok::<_, ()>(Response::ok()) }) .tcp() }) diff --git a/actix-http/tests/test_ws.rs b/actix-http/tests/test_ws.rs index 9a2e57711..72870bab5 100644 --- a/actix-http/tests/test_ws.rs +++ b/actix-http/tests/test_ws.rs @@ -91,7 +91,7 @@ async fn test_simple() { let ws_service = ws_service.clone(); HttpService::build() .upgrade(fn_factory(move || future::ok::<_, ()>(ws_service.clone()))) - .finish(|_| future::ok::<_, ()>(Response::NotFound())) + .finish(|_| future::ok::<_, ()>(Response::not_found())) .tcp() } }) diff --git a/awc/tests/test_ws.rs b/awc/tests/test_ws.rs index 3f19ac4e8..bfc81afbc 100644 --- a/awc/tests/test_ws.rs +++ b/awc/tests/test_ws.rs @@ -36,7 +36,7 @@ async fn test_simple() { ws::Dispatcher::with(framed, ws_service).await } }) - .finish(|_| ok::<_, Error>(Response::NotFound())) + .finish(|_| ok::<_, Error>(Response::not_found())) .tcp() }) .await; diff --git a/src/app_service.rs b/src/app_service.rs index be4ccf22f..32c779a32 100644 --- a/src/app_service.rs +++ b/src/app_service.rs @@ -1,20 +1,23 @@ use std::cell::RefCell; use std::rc::Rc; -use actix_http::{Extensions, Request, Response}; +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 futures_core::future::LocalBoxFuture; use futures_util::future::join_all; -use crate::config::{AppConfig, AppService}; 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, +}; type Guards = Vec>; type HttpService = BoxService; @@ -64,7 +67,7 @@ where // if no user defined default service exists. let default = self.default.clone().unwrap_or_else(|| { Rc::new(boxed::factory(fn_service(|req: ServiceRequest| async { - Ok(req.into_response(Response::NotFound().finish())) + Ok(req.into_response(HttpResponse::NotFound())) }))) }); diff --git a/src/resource.rs b/src/resource.rs index e868bb547..049e56291 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, Response}; +use actix_http::{Error, Extensions}; use actix_router::IntoPattern; use actix_service::boxed::{self, BoxService, BoxServiceFactory}; use actix_service::{ @@ -13,7 +13,6 @@ use actix_service::{ use futures_core::future::LocalBoxFuture; use futures_util::future::join_all; -use crate::data::Data; use crate::dev::{insert_slash, AppService, HttpServiceFactory, ResourceDef}; use crate::extract::FromRequest; use crate::guard::Guard; @@ -21,6 +20,7 @@ use crate::handler::Handler; use crate::responder::Responder; use crate::route::{Route, RouteService}; use crate::service::{ServiceRequest, ServiceResponse}; +use crate::{data::Data, HttpResponse}; type HttpService = BoxService; type HttpNewService = BoxServiceFactory<(), ServiceRequest, ServiceResponse, Error, ()>; @@ -71,7 +71,7 @@ impl Resource { guards: Vec::new(), app_data: None, default: boxed::factory(fn_service(|req: ServiceRequest| async { - Ok(req.into_response(Response::MethodNotAllowed().finish())) + Ok(req.into_response(HttpResponse::MethodNotAllowed())) })), } } diff --git a/src/response.rs b/src/response/builder.rs similarity index 55% rename from src/response.rs rename to src/response/builder.rs index 23244e6a5..2c04f3f64 100644 --- a/src/response.rs +++ b/src/response/builder.rs @@ -1,17 +1,15 @@ use std::{ cell::{Ref, RefMut}, convert::TryInto, - fmt, future::Future, - mem, pin::Pin, task::{Context, Poll}, }; use actix_http::{ - body::{Body, BodyStream, MessageBody, ResponseBody}, + body::{Body, BodyStream}, http::{ - header::{self, HeaderMap, HeaderName, IntoHeaderPair, IntoHeaderValue}, + header::{self, HeaderName, IntoHeaderPair, IntoHeaderValue}, ConnectionType, Error as HttpError, StatusCode, }, Extensions, Response, ResponseHead, @@ -25,282 +23,10 @@ use actix_http::http::header::HeaderValue; #[cfg(feature = "cookies")] use cookie::{Cookie, CookieJar}; -use crate::error::{Error, JsonPayloadError}; - -/// An HTTP Response -pub struct HttpResponse { - res: Response, - error: Option, -} - -impl HttpResponse { - /// Create HTTP response builder with specific status. - #[inline] - pub fn build(status: StatusCode) -> HttpResponseBuilder { - HttpResponseBuilder::new(status) - } - - /// Create HTTP response builder - #[inline] - pub fn build_from>(source: T) -> HttpResponseBuilder { - source.into() - } - - /// Create a response. - #[inline] - pub fn new(status: StatusCode) -> Self { - Self { - res: Response::new(status), - error: None, - } - } - - /// 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), - } - } - - /// Convert response to response with body - pub fn into_body(self) -> HttpResponse { - HttpResponse { - res: self.res.into_body(), - error: self.error, - } - } -} - -impl HttpResponse { - /// Constructs a response with body - #[inline] - pub fn with_body(status: StatusCode, body: B) -> Self { - Self { - res: Response::with_body(status, body), - error: None, - } - } - - /// Returns a reference to response head. - #[inline] - pub fn head(&self) -> &ResponseHead { - self.res.head() - } - - /// Returns a mutable reference to response head. - #[inline] - pub fn head_mut(&mut self) -> &mut ResponseHead { - self.res.head_mut() - } - - /// The source `error` for this response - #[inline] - pub fn error(&self) -> Option<&Error> { - self.error.as_ref() - } - - /// Get the response status code - #[inline] - pub fn status(&self) -> StatusCode { - self.res.status() - } - - /// Set the `StatusCode` for this response - #[inline] - pub fn status_mut(&mut self) -> &mut StatusCode { - self.res.status_mut() - } - - /// Get the headers from the response - #[inline] - pub fn headers(&self) -> &HeaderMap { - self.res.headers() - } - - /// Get a mutable reference to the headers - #[inline] - pub fn headers_mut(&mut self) -> &mut HeaderMap { - self.res.headers_mut() - } - - /// Get an iterator for the cookies set by this response. - #[cfg(feature = "cookies")] - pub fn cookies(&self) -> CookieIter<'_> { - CookieIter { - iter: self.headers().get_all(header::SET_COOKIE), - } - } - - /// Add a cookie to this response - #[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()) - } - - /// Remove all cookies with the given name from this response. Returns - /// the number of cookies removed. - #[cfg(feature = "cookies")] - pub fn del_cookie(&mut self, name: &str) -> usize { - let headers = self.headers_mut(); - - let vals: Vec = headers - .get_all(header::SET_COOKIE) - .map(|v| v.to_owned()) - .collect(); - - 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) { - if c.name() == name { - count += 1; - continue; - } - } - } - - // put set-cookie header head back if it does not validate - headers.append(header::SET_COOKIE, v); - } - - count - } - - /// Connection upgrade status - #[inline] - pub fn upgrade(&self) -> bool { - self.res.upgrade() - } - - /// Keep-alive status for this connection - pub fn keep_alive(&self) -> bool { - self.res.keep_alive() - } - - /// Responses extensions - #[inline] - pub fn extensions(&self) -> Ref<'_, Extensions> { - self.res.extensions() - } - - /// Mutable reference to a the response's extensions - #[inline] - pub fn extensions_mut(&mut self) -> RefMut<'_, Extensions> { - self.res.extensions_mut() - } - - /// Get body of this response - #[inline] - pub fn body(&self) -> &ResponseBody { - self.res.body() - } - - /// Set a body - pub fn set_body(self, body: B2) -> HttpResponse { - HttpResponse { - res: self.res.set_body(body), - error: None, - // error: self.error, ?? - } - } - - /// Split response and body - pub fn into_parts(self) -> (HttpResponse<()>, ResponseBody) { - let (head, body) = self.res.into_parts(); - - ( - HttpResponse { - res: head, - error: None, - }, - body, - ) - } - - /// Drop request's body - pub fn drop_body(self) -> HttpResponse<()> { - HttpResponse { - res: self.res.drop_body(), - error: None, - } - } - - /// Set a body and return previous body value - pub fn map_body(self, f: F) -> HttpResponse - where - F: FnOnce(&mut ResponseHead, ResponseBody) -> ResponseBody, - { - HttpResponse { - res: self.res.map_body(f), - error: self.error, - } - } - - /// Extract response body - pub fn take_body(&mut self) -> ResponseBody { - self.res.take_body() - } -} - -impl fmt::Debug for HttpResponse { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("HttpResponse") - .field("error", &self.error) - .field("res", &self.res) - .finish() - } -} - -impl From> for HttpResponse { - fn from(res: Response) -> Self { - HttpResponse { res, error: None } - } -} - -impl From for HttpResponse { - fn from(err: Error) -> Self { - HttpResponse::from_error(err) - } -} - -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 { - // eprintln!("impl From> for Response let Some(err)"); - // return Response::from_error(err).into_body(); - // } - - res.res - } -} - -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(Ok(Response::from_error(err).into_body())); - } - - Poll::Ready(Ok(mem::replace( - &mut self.res, - Response::new(StatusCode::default()), - ))) - } -} +use crate::{ + error::{Error, JsonPayloadError}, + HttpResponse, +}; /// An HTTP response builder. /// @@ -695,146 +421,18 @@ impl Future for HttpResponseBuilder { } } -#[cfg(feature = "cookies")] -pub struct CookieIter<'a> { - iter: header::GetAll<'a>, -} - -#[cfg(feature = "cookies")] -impl<'a> Iterator for CookieIter<'a> { - type Item = Cookie<'a>; - - #[inline] - fn next(&mut self) -> Option> { - for v in self.iter.by_ref() { - if let Ok(c) = Cookie::parse_encoded(v.to_str().ok()?) { - return Some(c); - } - } - None - } -} - -mod http_codes { - //! Status code based HTTP response builders. - - use actix_http::http::StatusCode; - - use super::{HttpResponse, HttpResponseBuilder}; - - macro_rules! static_resp { - ($name:ident, $status:expr) => { - #[allow(non_snake_case, missing_docs)] - pub fn $name() -> HttpResponseBuilder { - HttpResponseBuilder::new($status) - } - }; - } - - impl HttpResponse { - static_resp!(Continue, StatusCode::CONTINUE); - static_resp!(SwitchingProtocols, StatusCode::SWITCHING_PROTOCOLS); - static_resp!(Processing, StatusCode::PROCESSING); - - static_resp!(Ok, StatusCode::OK); - static_resp!(Created, StatusCode::CREATED); - static_resp!(Accepted, StatusCode::ACCEPTED); - static_resp!( - NonAuthoritativeInformation, - StatusCode::NON_AUTHORITATIVE_INFORMATION - ); - - static_resp!(NoContent, StatusCode::NO_CONTENT); - static_resp!(ResetContent, StatusCode::RESET_CONTENT); - static_resp!(PartialContent, StatusCode::PARTIAL_CONTENT); - static_resp!(MultiStatus, StatusCode::MULTI_STATUS); - static_resp!(AlreadyReported, StatusCode::ALREADY_REPORTED); - - static_resp!(MultipleChoices, StatusCode::MULTIPLE_CHOICES); - static_resp!(MovedPermanently, StatusCode::MOVED_PERMANENTLY); - static_resp!(Found, StatusCode::FOUND); - static_resp!(SeeOther, StatusCode::SEE_OTHER); - static_resp!(NotModified, StatusCode::NOT_MODIFIED); - static_resp!(UseProxy, StatusCode::USE_PROXY); - static_resp!(TemporaryRedirect, StatusCode::TEMPORARY_REDIRECT); - static_resp!(PermanentRedirect, StatusCode::PERMANENT_REDIRECT); - - static_resp!(BadRequest, StatusCode::BAD_REQUEST); - static_resp!(NotFound, StatusCode::NOT_FOUND); - static_resp!(Unauthorized, StatusCode::UNAUTHORIZED); - static_resp!(PaymentRequired, StatusCode::PAYMENT_REQUIRED); - static_resp!(Forbidden, StatusCode::FORBIDDEN); - static_resp!(MethodNotAllowed, StatusCode::METHOD_NOT_ALLOWED); - static_resp!(NotAcceptable, StatusCode::NOT_ACCEPTABLE); - static_resp!( - ProxyAuthenticationRequired, - StatusCode::PROXY_AUTHENTICATION_REQUIRED - ); - static_resp!(RequestTimeout, StatusCode::REQUEST_TIMEOUT); - static_resp!(Conflict, StatusCode::CONFLICT); - static_resp!(Gone, StatusCode::GONE); - static_resp!(LengthRequired, StatusCode::LENGTH_REQUIRED); - static_resp!(PreconditionFailed, StatusCode::PRECONDITION_FAILED); - static_resp!(PreconditionRequired, StatusCode::PRECONDITION_REQUIRED); - static_resp!(PayloadTooLarge, StatusCode::PAYLOAD_TOO_LARGE); - static_resp!(UriTooLong, StatusCode::URI_TOO_LONG); - static_resp!(UnsupportedMediaType, StatusCode::UNSUPPORTED_MEDIA_TYPE); - static_resp!(RangeNotSatisfiable, StatusCode::RANGE_NOT_SATISFIABLE); - static_resp!(ExpectationFailed, StatusCode::EXPECTATION_FAILED); - static_resp!(UnprocessableEntity, StatusCode::UNPROCESSABLE_ENTITY); - static_resp!(TooManyRequests, StatusCode::TOO_MANY_REQUESTS); - static_resp!( - RequestHeaderFieldsTooLarge, - StatusCode::REQUEST_HEADER_FIELDS_TOO_LARGE - ); - static_resp!( - UnavailableForLegalReasons, - StatusCode::UNAVAILABLE_FOR_LEGAL_REASONS - ); - - static_resp!(InternalServerError, StatusCode::INTERNAL_SERVER_ERROR); - static_resp!(NotImplemented, StatusCode::NOT_IMPLEMENTED); - static_resp!(BadGateway, StatusCode::BAD_GATEWAY); - static_resp!(ServiceUnavailable, StatusCode::SERVICE_UNAVAILABLE); - static_resp!(GatewayTimeout, StatusCode::GATEWAY_TIMEOUT); - static_resp!(VersionNotSupported, StatusCode::HTTP_VERSION_NOT_SUPPORTED); - static_resp!(VariantAlsoNegotiates, StatusCode::VARIANT_ALSO_NEGOTIATES); - static_resp!(InsufficientStorage, StatusCode::INSUFFICIENT_STORAGE); - static_resp!(LoopDetected, StatusCode::LOOP_DETECTED); - } - - #[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); - assert_eq!(resp.status(), StatusCode::OK); - } - } -} - #[cfg(test)] mod tests { - use bytes::{Bytes, BytesMut}; + use actix_http::body; - use super::{HttpResponse, HttpResponseBuilder}; - use crate::dev::{Body, MessageBody, ResponseBody}; - use crate::http::header::{self, HeaderValue, CONTENT_TYPE, COOKIE}; - use crate::http::StatusCode; - - #[test] - fn test_debug() { - let resp = HttpResponse::Ok() - .append_header((COOKIE, HeaderValue::from_static("cookie1=value1; "))) - .append_header((COOKIE, HeaderValue::from_static("cookie2=value2; "))) - .finish(); - let dbg = format!("{:?}", resp); - assert!(dbg.contains("HttpResponse")); - } + use super::*; + use crate::{ + dev::Body, + http::{ + header::{self, HeaderValue, CONTENT_TYPE}, + StatusCode, + }, + }; #[test] fn test_basic_builder() { @@ -872,26 +470,13 @@ mod tests { assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "text/plain") } - pub async fn read_body(mut body: ResponseBody) -> Bytes - where - B: MessageBody + Unpin, - { - use futures_util::StreamExt as _; - - let mut bytes = BytesMut::new(); - while let Some(item) = body.next().await { - bytes.extend_from_slice(&item.unwrap()); - } - bytes.freeze() - } - #[actix_rt::test] async fn test_json() { let mut resp = HttpResponse::Ok().json(vec!["v1", "v2", "v3"]); let ct = resp.headers().get(CONTENT_TYPE).unwrap(); assert_eq!(ct, HeaderValue::from_static("application/json")); assert_eq!( - read_body(resp.take_body()).await.as_ref(), + body::to_bytes(resp.take_body()).await.unwrap().as_ref(), br#"["v1","v2","v3"]"# ); @@ -899,7 +484,7 @@ mod tests { let ct = resp.headers().get(CONTENT_TYPE).unwrap(); assert_eq!(ct, HeaderValue::from_static("application/json")); assert_eq!( - read_body(resp.take_body()).await.as_ref(), + body::to_bytes(resp.take_body()).await.unwrap().as_ref(), br#"["v1","v2","v3"]"# ); @@ -910,7 +495,7 @@ mod tests { let ct = resp.headers().get(CONTENT_TYPE).unwrap(); assert_eq!(ct, HeaderValue::from_static("text/json")); assert_eq!( - read_body(resp.take_body()).await.as_ref(), + body::to_bytes(resp.take_body()).await.unwrap().as_ref(), br#"["v1","v2","v3"]"# ); } @@ -922,7 +507,7 @@ mod tests { ); assert_eq!( - read_body(resp.take_body()).await.as_ref(), + body::to_bytes(resp.take_body()).await.unwrap().as_ref(), br#"{"test-key":"test-value"}"# ); } diff --git a/actix-http/src/http_codes.rs b/src/response/http_codes.rs similarity index 90% rename from actix-http/src/http_codes.rs rename to src/response/http_codes.rs index dc4f964de..d67ef3f92 100644 --- a/actix-http/src/http_codes.rs +++ b/src/response/http_codes.rs @@ -1,24 +1,19 @@ //! Status code based HTTP response builders. -#![allow(non_upper_case_globals)] +use actix_http::http::StatusCode; -use http::StatusCode; - -use crate::{ - body::Body, - response::{Response, ResponseBuilder}, -}; +use crate::{HttpResponse, HttpResponseBuilder}; macro_rules! static_resp { ($name:ident, $status:expr) => { #[allow(non_snake_case, missing_docs)] - pub fn $name() -> ResponseBuilder { - ResponseBuilder::new($status) + pub fn $name() -> HttpResponseBuilder { + HttpResponseBuilder::new($status) } }; } -impl Response { +impl HttpResponse { static_resp!(Continue, StatusCode::CONTINUE); static_resp!(SwitchingProtocols, StatusCode::SWITCHING_PROTOCOLS); static_resp!(Processing, StatusCode::PROCESSING); @@ -92,13 +87,13 @@ impl Response { #[cfg(test)] mod tests { - use crate::body::Body; - use crate::response::Response; - use http::StatusCode; + use crate::dev::Body; + use crate::http::StatusCode; + use crate::HttpResponse; #[test] fn test_build() { - let resp = Response::Ok().body(Body::Empty); + let resp = HttpResponse::Ok().body(Body::Empty); assert_eq!(resp.status(), StatusCode::OK); } } diff --git a/src/response/mod.rs b/src/response/mod.rs new file mode 100644 index 000000000..8401db9d2 --- /dev/null +++ b/src/response/mod.rs @@ -0,0 +1,10 @@ +mod builder; +mod http_codes; +#[allow(clippy::module_inception)] +mod response; + +pub use self::builder::HttpResponseBuilder; +pub use self::response::HttpResponse; + +#[cfg(feature = "cookies")] +pub use self::response::CookieIter; diff --git a/src/response/response.rs b/src/response/response.rs new file mode 100644 index 000000000..31868fe0b --- /dev/null +++ b/src/response/response.rs @@ -0,0 +1,330 @@ +use std::{ + cell::{Ref, RefMut}, + fmt, + future::Future, + mem, + pin::Pin, + task::{Context, Poll}, +}; + +use actix_http::{ + body::{Body, MessageBody, ResponseBody}, + http::{header::HeaderMap, StatusCode}, + Extensions, Response, ResponseHead, +}; + +#[cfg(feature = "cookies")] +use { + actix_http::http::{ + header::{self, HeaderValue}, + Error as HttpError, + }, + cookie::Cookie, +}; + +use crate::{error::Error, HttpResponseBuilder}; + +/// An HTTP Response +pub struct HttpResponse { + res: Response, + error: Option, +} + +impl HttpResponse { + /// Create HTTP response builder with specific status. + #[inline] + pub fn build(status: StatusCode) -> HttpResponseBuilder { + HttpResponseBuilder::new(status) + } + + /// Create a response. + #[inline] + pub fn new(status: StatusCode) -> Self { + Self { + res: Response::new(status), + error: None, + } + } + + /// 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), + } + } + + /// Convert response to response with body + pub fn into_body(self) -> HttpResponse { + HttpResponse { + res: self.res.into_body(), + error: self.error, + } + } +} + +impl HttpResponse { + /// Constructs a response with body + #[inline] + pub fn with_body(status: StatusCode, body: B) -> Self { + Self { + res: Response::with_body(status, body), + error: None, + } + } + + /// Returns a reference to response head. + #[inline] + pub fn head(&self) -> &ResponseHead { + self.res.head() + } + + /// Returns a mutable reference to response head. + #[inline] + pub fn head_mut(&mut self) -> &mut ResponseHead { + self.res.head_mut() + } + + /// The source `error` for this response + #[inline] + pub fn error(&self) -> Option<&Error> { + self.error.as_ref() + } + + /// Get the response status code + #[inline] + pub fn status(&self) -> StatusCode { + self.res.status() + } + + /// Set the `StatusCode` for this response + #[inline] + pub fn status_mut(&mut self) -> &mut StatusCode { + self.res.status_mut() + } + + /// Get the headers from the response + #[inline] + pub fn headers(&self) -> &HeaderMap { + self.res.headers() + } + + /// Get a mutable reference to the headers + #[inline] + pub fn headers_mut(&mut self) -> &mut HeaderMap { + self.res.headers_mut() + } + + /// Get an iterator for the cookies set by this response. + #[cfg(feature = "cookies")] + pub fn cookies(&self) -> CookieIter<'_> { + CookieIter { + iter: self.headers().get_all(header::SET_COOKIE), + } + } + + /// Add a cookie to this response + #[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()) + } + + /// Remove all cookies with the given name from this response. Returns + /// the number of cookies removed. + #[cfg(feature = "cookies")] + pub fn del_cookie(&mut self, name: &str) -> usize { + let headers = self.headers_mut(); + + let vals: Vec = headers + .get_all(header::SET_COOKIE) + .map(|v| v.to_owned()) + .collect(); + + 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) { + if c.name() == name { + count += 1; + continue; + } + } + } + + // put set-cookie header head back if it does not validate + headers.append(header::SET_COOKIE, v); + } + + count + } + + /// Connection upgrade status + #[inline] + pub fn upgrade(&self) -> bool { + self.res.upgrade() + } + + /// Keep-alive status for this connection + pub fn keep_alive(&self) -> bool { + self.res.keep_alive() + } + + /// Responses extensions + #[inline] + pub fn extensions(&self) -> Ref<'_, Extensions> { + self.res.extensions() + } + + /// Mutable reference to a the response's extensions + #[inline] + pub fn extensions_mut(&mut self) -> RefMut<'_, Extensions> { + self.res.extensions_mut() + } + + /// Get body of this response + #[inline] + pub fn body(&self) -> &ResponseBody { + self.res.body() + } + + /// Set a body + pub fn set_body(self, body: B2) -> HttpResponse { + HttpResponse { + res: self.res.set_body(body), + error: None, + // error: self.error, ?? + } + } + + /// Split response and body + pub fn into_parts(self) -> (HttpResponse<()>, ResponseBody) { + let (head, body) = self.res.into_parts(); + + ( + HttpResponse { + res: head, + error: None, + }, + body, + ) + } + + /// Drop request's body + pub fn drop_body(self) -> HttpResponse<()> { + HttpResponse { + res: self.res.drop_body(), + error: None, + } + } + + /// Set a body and return previous body value + pub fn map_body(self, f: F) -> HttpResponse + where + F: FnOnce(&mut ResponseHead, ResponseBody) -> ResponseBody, + { + HttpResponse { + res: self.res.map_body(f), + error: self.error, + } + } + + /// Extract response body + pub fn take_body(&mut self) -> ResponseBody { + self.res.take_body() + } +} + +impl fmt::Debug for HttpResponse { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("HttpResponse") + .field("error", &self.error) + .field("res", &self.res) + .finish() + } +} + +impl From> for HttpResponse { + fn from(res: Response) -> Self { + HttpResponse { res, error: None } + } +} + +impl From for HttpResponse { + fn from(err: Error) -> Self { + HttpResponse::from_error(err) + } +} + +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 { + // eprintln!("impl From> for Response let Some(err)"); + // return Response::from_error(err).into_body(); + // } + + res.res + } +} + +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(Ok(Response::from_error(err).into_body())); + } + + Poll::Ready(Ok(mem::replace( + &mut self.res, + Response::new(StatusCode::default()), + ))) + } +} + +#[cfg(feature = "cookies")] +pub struct CookieIter<'a> { + iter: header::GetAll<'a>, +} + +#[cfg(feature = "cookies")] +impl<'a> Iterator for CookieIter<'a> { + type Item = Cookie<'a>; + + #[inline] + fn next(&mut self) -> Option> { + for v in self.iter.by_ref() { + if let Ok(c) = Cookie::parse_encoded(v.to_str().ok()?) { + return Some(c); + } + } + None + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::http::header::{HeaderValue, COOKIE}; + + #[test] + fn test_debug() { + let resp = HttpResponse::Ok() + .append_header((COOKIE, HeaderValue::from_static("cookie1=value1; "))) + .append_header((COOKIE, HeaderValue::from_static("cookie2=value2; "))) + .finish(); + let dbg = format!("{:?}", resp); + assert!(dbg.contains("HttpResponse")); + } +} From 387c229f281271e7d701a0ff5d09e68a282e9988 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Wed, 14 Apr 2021 02:12:47 +0100 Subject: [PATCH 153/339] move response builder code to own file --- actix-http/src/body/mod.rs | 2 +- actix-http/src/lib.rs | 4 +- actix-http/src/response.rs | 412 +---------------------- actix-http/src/response_builder.rs | 503 +++++++++++++++++++++++++++++ 4 files changed, 517 insertions(+), 404 deletions(-) create mode 100644 actix-http/src/response_builder.rs diff --git a/actix-http/src/body/mod.rs b/actix-http/src/body/mod.rs index c298dda11..f26d6a8cf 100644 --- a/actix-http/src/body/mod.rs +++ b/actix-http/src/body/mod.rs @@ -56,7 +56,7 @@ pub async fn to_bytes(body: impl MessageBody) -> Result { let body = body.as_mut(); match ready!(body.poll_next(cx)) { - Some(Ok(bytes)) => buf.extend(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/lib.rs b/actix-http/src/lib.rs index 3125e3874..8674834e0 100644 --- a/actix-http/src/lib.rs +++ b/actix-http/src/lib.rs @@ -42,6 +42,7 @@ mod message; mod payload; mod request; mod response; +mod response_builder; mod service; mod time_parser; @@ -59,7 +60,8 @@ pub use self::http_message::HttpMessage; pub use self::message::{Message, RequestHead, RequestHeadType, ResponseHead}; pub use self::payload::{Payload, PayloadStream}; pub use self::request::Request; -pub use self::response::{Response, ResponseBuilder}; +pub use self::response::{Response}; +pub use self::response_builder::{ResponseBuilder}; pub use self::service::HttpService; pub mod http { diff --git a/actix-http/src/response.rs b/actix-http/src/response.rs index 7f73538fc..a3ab1175c 100644 --- a/actix-http/src/response.rs +++ b/actix-http/src/response.rs @@ -1,4 +1,4 @@ -//! HTTP responses. +//! HTTP response. use std::{ cell::{Ref, RefMut}, @@ -10,22 +10,21 @@ use std::{ }; use bytes::{Bytes, BytesMut}; -use futures_core::Stream; use crate::{ - body::{Body, BodyStream, MessageBody, ResponseBody}, + body::{Body, MessageBody, ResponseBody}, error::Error, extensions::Extensions, - header::{IntoHeaderPair, IntoHeaderValue}, - http::{header, Error as HttpError, HeaderMap, StatusCode}, - message::{BoxedResponseHead, ConnectionType, ResponseHead}, + http::{HeaderMap, StatusCode}, + message::{BoxedResponseHead, ResponseHead}, + ResponseBuilder, }; -/// An HTTP Response +/// An HTTP response. pub struct Response { - head: BoxedResponseHead, - body: ResponseBody, - error: Option, + pub(crate) head: BoxedResponseHead, + pub(crate) body: ResponseBody, + pub(crate) error: Option, } impl Response { @@ -273,295 +272,6 @@ impl Future for Response { } } -/// An HTTP response builder. -/// -/// This type can be used to construct an instance of `Response` through a builder-like pattern. -pub struct ResponseBuilder { - head: Option, - err: Option, -} - -impl ResponseBuilder { - #[inline] - /// Create response builder - pub fn new(status: StatusCode) -> Self { - ResponseBuilder { - head: Some(BoxedResponseHead::new(status)), - err: None, - } - } - - /// Set HTTP status code of this response. - #[inline] - pub fn status(&mut self, status: StatusCode) -> &mut Self { - if let Some(parts) = parts(&mut self.head, &self.err) { - parts.status = status; - } - self - } - - /// Insert a header, replacing any that were set with an equivalent field name. - /// - /// ``` - /// # use actix_http::Response; - /// use actix_http::http::{header, StatusCode}; - /// - /// Response::build(StatusCode::OK) - /// .insert_header((header::CONTENT_TYPE, mime::APPLICATION_JSON)) - /// .insert_header(("X-TEST", "value")) - /// .finish(); - /// ``` - pub fn insert_header(&mut self, header: H) -> &mut Self - where - H: IntoHeaderPair, - { - if let Some(parts) = parts(&mut self.head, &self.err) { - match header.try_into_header_pair() { - Ok((key, value)) => { - parts.headers.insert(key, value); - } - Err(e) => self.err = Some(e.into()), - }; - } - - self - } - - /// Append a header, keeping any that were set with an equivalent field name. - /// - /// ``` - /// # use actix_http::Response; - /// use actix_http::http::{header, StatusCode}; - /// - /// Response::build(StatusCode::OK) - /// .append_header((header::CONTENT_TYPE, mime::APPLICATION_JSON)) - /// .append_header(("X-TEST", "value1")) - /// .append_header(("X-TEST", "value2")) - /// .finish(); - /// ``` - pub fn append_header(&mut self, header: H) -> &mut Self - where - H: IntoHeaderPair, - { - if let Some(parts) = parts(&mut self.head, &self.err) { - match header.try_into_header_pair() { - Ok((key, value)) => parts.headers.append(key, value), - Err(e) => self.err = Some(e.into()), - }; - } - - self - } - - /// Set the custom reason for the response. - #[inline] - pub fn reason(&mut self, reason: &'static str) -> &mut Self { - if let Some(parts) = parts(&mut self.head, &self.err) { - parts.reason = Some(reason); - } - self - } - - /// Set connection type to KeepAlive - #[inline] - pub fn keep_alive(&mut self) -> &mut Self { - if let Some(parts) = parts(&mut self.head, &self.err) { - parts.set_connection_type(ConnectionType::KeepAlive); - } - self - } - - /// Set connection type to Upgrade - #[inline] - pub fn upgrade(&mut self, value: V) -> &mut Self - where - V: IntoHeaderValue, - { - if let Some(parts) = parts(&mut self.head, &self.err) { - parts.set_connection_type(ConnectionType::Upgrade); - } - - if let Ok(value) = value.try_into_value() { - self.insert_header((header::UPGRADE, value)); - } - - self - } - - /// Force close connection, even if it is marked as keep-alive - #[inline] - pub fn force_close(&mut self) -> &mut Self { - if let Some(parts) = parts(&mut self.head, &self.err) { - parts.set_connection_type(ConnectionType::Close); - } - self - } - - /// Disable chunked transfer encoding for HTTP/1.1 streaming responses. - #[inline] - pub fn no_chunking(&mut self, len: u64) -> &mut Self { - let mut buf = itoa::Buffer::new(); - self.insert_header((header::CONTENT_LENGTH, buf.format(len))); - - if let Some(parts) = parts(&mut self.head, &self.err) { - parts.no_chunking(true); - } - self - } - - /// Set response content type. - #[inline] - pub fn content_type(&mut self, value: V) -> &mut Self - where - V: IntoHeaderValue, - { - if let Some(parts) = parts(&mut self.head, &self.err) { - match value.try_into_value() { - Ok(value) => { - parts.headers.insert(header::CONTENT_TYPE, value); - } - Err(e) => self.err = Some(e.into()), - }; - } - 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() - } - - /// Set a body and generate `Response`. - /// - /// `ResponseBuilder` can not be used after this call. - #[inline] - pub fn body>(&mut self, body: B) -> Response { - self.message_body(body.into()) - } - - /// Set a body and generate `Response`. - /// - /// `ResponseBuilder` can not be used after this call. - pub fn message_body(&mut self, body: B) -> Response { - if let Some(e) = self.err.take() { - return Response::from(Error::from(e)).into_body(); - } - - let response = self.head.take().expect("cannot reuse response builder"); - - Response { - head: response, - body: ResponseBody::Body(body), - error: None, - } - } - - /// Set a streaming body and generate `Response`. - /// - /// `ResponseBuilder` can not be used after this call. - #[inline] - pub fn streaming(&mut self, stream: S) -> Response - where - S: Stream> + Unpin + 'static, - E: Into + 'static, - { - self.body(Body::from_message(BodyStream::new(stream))) - } - - /// Set an empty body and generate `Response` - /// - /// `ResponseBuilder` can not be used after this call. - #[inline] - pub fn finish(&mut self) -> Response { - self.body(Body::Empty) - } - - /// This method construct new `ResponseBuilder` - pub fn take(&mut self) -> ResponseBuilder { - ResponseBuilder { - head: self.head.take(), - err: self.err.take(), - } - } -} - -#[inline] -fn parts<'a>( - parts: &'a mut Option, - err: &Option, -) -> Option<&'a mut ResponseHead> { - if err.is_some() { - return None; - } - parts.as_mut().map(|r| &mut **r) -} - -/// Convert `Response` to a `ResponseBuilder`. Body get dropped. -impl From> for ResponseBuilder { - fn from(res: Response) -> ResponseBuilder { - ResponseBuilder { - head: Some(res.head), - err: None, - } - } -} - -/// Convert `ResponseHead` to a `ResponseBuilder` -impl<'a> From<&'a ResponseHead> for ResponseBuilder { - fn from(head: &'a ResponseHead) -> ResponseBuilder { - let mut msg = BoxedResponseHead::new(head.status); - msg.version = head.version; - msg.reason = head.reason; - - for (k, v) in head.headers.iter() { - msg.headers.append(k.clone(), v.clone()); - } - - msg.no_chunking(!head.chunked()); - - ResponseBuilder { - head: Some(msg), - err: None, - } - } -} - -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(); - - let res = writeln!( - f, - "\nResponseBuilder {:?} {}{}", - head.version, - head.status, - head.reason.unwrap_or(""), - ); - let _ = writeln!(f, " headers:"); - for (key, val) in head.headers.iter() { - let _ = writeln!(f, " {:?}: {:?}", key, val); - } - res - } -} - /// Helper converters impl>, E: Into> From> for Response { fn from(res: Result) -> Self { @@ -630,7 +340,7 @@ impl From for Response { mod tests { use super::*; use crate::body::Body; - use crate::http::header::{HeaderName, HeaderValue, CONTENT_TYPE, COOKIE}; + use crate::http::header::{HeaderValue, CONTENT_TYPE, COOKIE}; #[test] fn test_debug() { @@ -642,40 +352,6 @@ mod tests { assert!(dbg.contains("Response")); } - #[test] - fn test_basic_builder() { - let resp = Response::build(StatusCode::OK) - .insert_header(("X-TEST", "value")) - .finish(); - assert_eq!(resp.status(), StatusCode::OK); - } - - #[test] - fn test_upgrade() { - let resp = Response::build(StatusCode::OK) - .upgrade("websocket") - .finish(); - assert!(resp.upgrade()); - assert_eq!( - resp.headers().get(header::UPGRADE).unwrap(), - HeaderValue::from_static("websocket") - ); - } - - #[test] - fn test_force_close() { - let resp = Response::build(StatusCode::OK).force_close().finish(); - assert!(!resp.keep_alive()) - } - - #[test] - fn test_content_type() { - let resp = Response::build(StatusCode::OK) - .content_type("text/plain") - .body(Body::Empty); - assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "text/plain") - } - #[test] fn test_into_response() { let resp: Response = "test".into(); @@ -745,72 +421,4 @@ mod tests { assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.body().get_ref(), b"test"); } - - #[test] - fn test_into_builder() { - let mut resp: Response = "test".into(); - assert_eq!(resp.status(), StatusCode::OK); - - resp.headers_mut().insert( - HeaderName::from_static("cookie"), - HeaderValue::from_static("cookie1=val100"), - ); - - let mut builder: ResponseBuilder = resp.into(); - let resp = builder.status(StatusCode::BAD_REQUEST).finish(); - assert_eq!(resp.status(), StatusCode::BAD_REQUEST); - - let cookie = resp.headers().get_all("Cookie").next().unwrap(); - assert_eq!(cookie.to_str().unwrap(), "cookie1=val100"); - } - - #[test] - fn response_builder_header_insert_kv() { - let mut res = Response::build(StatusCode::OK); - res.insert_header(("Content-Type", "application/octet-stream")); - let res = res.finish(); - - assert_eq!( - res.headers().get("Content-Type"), - Some(&HeaderValue::from_static("application/octet-stream")) - ); - } - - #[test] - fn response_builder_header_insert_typed() { - let mut res = Response::build(StatusCode::OK); - res.insert_header((header::CONTENT_TYPE, mime::APPLICATION_OCTET_STREAM)); - let res = res.finish(); - - assert_eq!( - res.headers().get("Content-Type"), - Some(&HeaderValue::from_static("application/octet-stream")) - ); - } - - #[test] - fn response_builder_header_append_kv() { - let mut res = Response::build(StatusCode::OK); - res.append_header(("Content-Type", "application/octet-stream")); - res.append_header(("Content-Type", "application/json")); - let res = res.finish(); - - let headers: Vec<_> = res.headers().get_all("Content-Type").cloned().collect(); - assert_eq!(headers.len(), 2); - assert!(headers.contains(&HeaderValue::from_static("application/octet-stream"))); - assert!(headers.contains(&HeaderValue::from_static("application/json"))); - } - - #[test] - fn response_builder_header_append_typed() { - let mut res = Response::build(StatusCode::OK); - res.append_header((header::CONTENT_TYPE, mime::APPLICATION_OCTET_STREAM)); - res.append_header((header::CONTENT_TYPE, mime::APPLICATION_JSON)); - let res = res.finish(); - - let headers: Vec<_> = res.headers().get_all("Content-Type").cloned().collect(); - assert_eq!(headers.len(), 2); - assert!(headers.contains(&HeaderValue::from_static("application/octet-stream"))); - assert!(headers.contains(&HeaderValue::from_static("application/json"))); - } } diff --git a/actix-http/src/response_builder.rs b/actix-http/src/response_builder.rs new file mode 100644 index 000000000..eab96b03d --- /dev/null +++ b/actix-http/src/response_builder.rs @@ -0,0 +1,503 @@ +//! HTTP response builder. + +use std::{ + cell::{Ref, RefMut}, + fmt, + future::Future, + pin::Pin, + str, + task::{Context, Poll}, +}; + +use bytes::Bytes; +use futures_core::Stream; + +use crate::{ + body::{Body, BodyStream, ResponseBody}, + error::Error, + extensions::Extensions, + header::{IntoHeaderPair, IntoHeaderValue}, + http::{header, Error as HttpError, StatusCode}, + message::{BoxedResponseHead, ConnectionType, ResponseHead}, + Response, +}; + +/// An HTTP response builder. +/// +/// This type can be used to construct an instance of `Response` using a builder pattern. +pub struct ResponseBuilder { + head: Option, + err: Option, +} + +impl ResponseBuilder { + #[inline] + /// Create response builder + pub fn new(status: StatusCode) -> Self { + ResponseBuilder { + head: Some(BoxedResponseHead::new(status)), + err: None, + } + } + + /// Set HTTP status code of this response. + #[inline] + pub fn status(&mut self, status: StatusCode) -> &mut Self { + if let Some(parts) = parts(&mut self.head, &self.err) { + parts.status = status; + } + self + } + + /// Insert a header, replacing any that were set with an equivalent field name. + /// + /// ``` + /// # use actix_http::Response; + /// use actix_http::http::{header, StatusCode}; + /// + /// Response::build(StatusCode::OK) + /// .insert_header((header::CONTENT_TYPE, mime::APPLICATION_JSON)) + /// .insert_header(("X-TEST", "value")) + /// .finish(); + /// ``` + pub fn insert_header(&mut self, header: H) -> &mut Self + where + H: IntoHeaderPair, + { + if let Some(parts) = parts(&mut self.head, &self.err) { + match header.try_into_header_pair() { + Ok((key, value)) => { + parts.headers.insert(key, value); + } + Err(e) => self.err = Some(e.into()), + }; + } + + self + } + + /// Append a header, keeping any that were set with an equivalent field name. + /// + /// ``` + /// # use actix_http::Response; + /// use actix_http::http::{header, StatusCode}; + /// + /// Response::build(StatusCode::OK) + /// .append_header((header::CONTENT_TYPE, mime::APPLICATION_JSON)) + /// .append_header(("X-TEST", "value1")) + /// .append_header(("X-TEST", "value2")) + /// .finish(); + /// ``` + pub fn append_header(&mut self, header: H) -> &mut Self + where + H: IntoHeaderPair, + { + if let Some(parts) = parts(&mut self.head, &self.err) { + match header.try_into_header_pair() { + Ok((key, value)) => parts.headers.append(key, value), + Err(e) => self.err = Some(e.into()), + }; + } + + self + } + + /// Set the custom reason for the response. + #[inline] + pub fn reason(&mut self, reason: &'static str) -> &mut Self { + if let Some(parts) = parts(&mut self.head, &self.err) { + parts.reason = Some(reason); + } + self + } + + /// Set connection type to KeepAlive + #[inline] + pub fn keep_alive(&mut self) -> &mut Self { + if let Some(parts) = parts(&mut self.head, &self.err) { + parts.set_connection_type(ConnectionType::KeepAlive); + } + self + } + + /// Set connection type to Upgrade + #[inline] + pub fn upgrade(&mut self, value: V) -> &mut Self + where + V: IntoHeaderValue, + { + if let Some(parts) = parts(&mut self.head, &self.err) { + parts.set_connection_type(ConnectionType::Upgrade); + } + + if let Ok(value) = value.try_into_value() { + self.insert_header((header::UPGRADE, value)); + } + + self + } + + /// Force close connection, even if it is marked as keep-alive + #[inline] + pub fn force_close(&mut self) -> &mut Self { + if let Some(parts) = parts(&mut self.head, &self.err) { + parts.set_connection_type(ConnectionType::Close); + } + self + } + + /// Disable chunked transfer encoding for HTTP/1.1 streaming responses. + #[inline] + pub fn no_chunking(&mut self, len: u64) -> &mut Self { + let mut buf = itoa::Buffer::new(); + self.insert_header((header::CONTENT_LENGTH, buf.format(len))); + + if let Some(parts) = parts(&mut self.head, &self.err) { + parts.no_chunking(true); + } + self + } + + /// Set response content type. + #[inline] + pub fn content_type(&mut self, value: V) -> &mut Self + where + V: IntoHeaderValue, + { + if let Some(parts) = parts(&mut self.head, &self.err) { + match value.try_into_value() { + Ok(value) => { + parts.headers.insert(header::CONTENT_TYPE, value); + } + Err(e) => self.err = Some(e.into()), + }; + } + 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() + } + + /// Set a body and generate `Response`. + /// + /// `ResponseBuilder` can not be used after this call. + #[inline] + pub fn body>(&mut self, body: B) -> Response { + self.message_body(body.into()) + } + + /// Set a body and generate `Response`. + /// + /// `ResponseBuilder` can not be used after this call. + pub fn message_body(&mut self, body: B) -> Response { + if let Some(e) = self.err.take() { + return Response::from(Error::from(e)).into_body(); + } + + let response = self.head.take().expect("cannot reuse response builder"); + + Response { + head: response, + body: ResponseBody::Body(body), + error: None, + } + } + + /// Set a streaming body and generate `Response`. + /// + /// `ResponseBuilder` can not be used after this call. + #[inline] + pub fn streaming(&mut self, stream: S) -> Response + where + S: Stream> + Unpin + 'static, + E: Into + 'static, + { + self.body(Body::from_message(BodyStream::new(stream))) + } + + /// Set an empty body and generate `Response` + /// + /// `ResponseBuilder` can not be used after this call. + #[inline] + pub fn finish(&mut self) -> Response { + self.body(Body::Empty) + } + + /// This method construct new `ResponseBuilder` + pub fn take(&mut self) -> ResponseBuilder { + ResponseBuilder { + head: self.head.take(), + err: self.err.take(), + } + } +} + +#[inline] +fn parts<'a>( + parts: &'a mut Option, + err: &Option, +) -> Option<&'a mut ResponseHead> { + if err.is_some() { + return None; + } + parts.as_mut().map(|r| &mut **r) +} + +/// Convert `Response` to a `ResponseBuilder`. Body get dropped. +impl From> for ResponseBuilder { + fn from(res: Response) -> ResponseBuilder { + ResponseBuilder { + head: Some(res.head), + err: None, + } + } +} + +/// Convert `ResponseHead` to a `ResponseBuilder` +impl<'a> From<&'a ResponseHead> for ResponseBuilder { + fn from(head: &'a ResponseHead) -> ResponseBuilder { + let mut msg = BoxedResponseHead::new(head.status); + msg.version = head.version; + msg.reason = head.reason; + + for (k, v) in head.headers.iter() { + msg.headers.append(k.clone(), v.clone()); + } + + msg.no_chunking(!head.chunked()); + + ResponseBuilder { + head: Some(msg), + err: None, + } + } +} + +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(); + + let res = writeln!( + f, + "\nResponseBuilder {:?} {}{}", + head.version, + head.status, + head.reason.unwrap_or(""), + ); + let _ = writeln!(f, " headers:"); + for (key, val) in head.headers.iter() { + let _ = writeln!(f, " {:?}: {:?}", key, val); + } + res + } +} + +#[cfg(test)] +mod tests { + use bytes::BytesMut; + + use super::*; + use crate::body::Body; + use crate::http::header::{HeaderName, HeaderValue, CONTENT_TYPE, COOKIE}; + + #[test] + fn test_debug() { + let resp = Response::build(StatusCode::OK) + .append_header((COOKIE, HeaderValue::from_static("cookie1=value1; "))) + .append_header((COOKIE, HeaderValue::from_static("cookie2=value2; "))) + .finish(); + let dbg = format!("{:?}", resp); + assert!(dbg.contains("Response")); + } + + #[test] + fn test_basic_builder() { + let resp = Response::build(StatusCode::OK) + .insert_header(("X-TEST", "value")) + .finish(); + assert_eq!(resp.status(), StatusCode::OK); + } + + #[test] + fn test_upgrade() { + let resp = Response::build(StatusCode::OK) + .upgrade("websocket") + .finish(); + assert!(resp.upgrade()); + assert_eq!( + resp.headers().get(header::UPGRADE).unwrap(), + HeaderValue::from_static("websocket") + ); + } + + #[test] + fn test_force_close() { + let resp = Response::build(StatusCode::OK).force_close().finish(); + assert!(!resp.keep_alive()) + } + + #[test] + fn test_content_type() { + let resp = Response::build(StatusCode::OK) + .content_type("text/plain") + .body(Body::Empty); + assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "text/plain") + } + + #[test] + fn test_into_response() { + let resp: Response = "test".into(); + assert_eq!(resp.status(), StatusCode::OK); + assert_eq!( + resp.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"); + + let resp: Response = b"test".as_ref().into(); + assert_eq!(resp.status(), StatusCode::OK); + assert_eq!( + resp.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"); + + let resp: Response = "test".to_owned().into(); + assert_eq!(resp.status(), StatusCode::OK); + assert_eq!( + resp.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"); + + let resp: Response = (&"test".to_owned()).into(); + assert_eq!(resp.status(), StatusCode::OK); + assert_eq!( + resp.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"); + + let b = Bytes::from_static(b"test"); + let resp: Response = b.into(); + assert_eq!(resp.status(), StatusCode::OK); + assert_eq!( + resp.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"); + + let b = Bytes::from_static(b"test"); + let resp: Response = b.into(); + assert_eq!(resp.status(), StatusCode::OK); + assert_eq!( + resp.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"); + + let b = BytesMut::from("test"); + let resp: Response = b.into(); + assert_eq!(resp.status(), StatusCode::OK); + assert_eq!( + resp.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"); + } + + #[test] + fn test_into_builder() { + let mut resp: Response = "test".into(); + assert_eq!(resp.status(), StatusCode::OK); + + resp.headers_mut().insert( + HeaderName::from_static("cookie"), + HeaderValue::from_static("cookie1=val100"), + ); + + let mut builder: ResponseBuilder = resp.into(); + let resp = builder.status(StatusCode::BAD_REQUEST).finish(); + assert_eq!(resp.status(), StatusCode::BAD_REQUEST); + + let cookie = resp.headers().get_all("Cookie").next().unwrap(); + assert_eq!(cookie.to_str().unwrap(), "cookie1=val100"); + } + + #[test] + fn response_builder_header_insert_kv() { + let mut res = Response::build(StatusCode::OK); + res.insert_header(("Content-Type", "application/octet-stream")); + let res = res.finish(); + + assert_eq!( + res.headers().get("Content-Type"), + Some(&HeaderValue::from_static("application/octet-stream")) + ); + } + + #[test] + fn response_builder_header_insert_typed() { + let mut res = Response::build(StatusCode::OK); + res.insert_header((header::CONTENT_TYPE, mime::APPLICATION_OCTET_STREAM)); + let res = res.finish(); + + assert_eq!( + res.headers().get("Content-Type"), + Some(&HeaderValue::from_static("application/octet-stream")) + ); + } + + #[test] + fn response_builder_header_append_kv() { + let mut res = Response::build(StatusCode::OK); + res.append_header(("Content-Type", "application/octet-stream")); + res.append_header(("Content-Type", "application/json")); + let res = res.finish(); + + let headers: Vec<_> = res.headers().get_all("Content-Type").cloned().collect(); + assert_eq!(headers.len(), 2); + assert!(headers.contains(&HeaderValue::from_static("application/octet-stream"))); + assert!(headers.contains(&HeaderValue::from_static("application/json"))); + } + + #[test] + fn response_builder_header_append_typed() { + let mut res = Response::build(StatusCode::OK); + res.append_header((header::CONTENT_TYPE, mime::APPLICATION_OCTET_STREAM)); + res.append_header((header::CONTENT_TYPE, mime::APPLICATION_JSON)); + let res = res.finish(); + + let headers: Vec<_> = res.headers().get_all("Content-Type").cloned().collect(); + assert_eq!(headers.len(), 2); + assert!(headers.contains(&HeaderValue::from_static("application/octet-stream"))); + assert!(headers.contains(&HeaderValue::from_static("application/json"))); + } +} From 5202bf03c170023cf2c20a22e00d74f36cf73097 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Wed, 14 Apr 2021 02:45:58 +0100 Subject: [PATCH 154/339] add some doc examples to response builder --- actix-http/src/response_builder.rs | 149 +++++++++++------------------ 1 file changed, 58 insertions(+), 91 deletions(-) diff --git a/actix-http/src/response_builder.rs b/actix-http/src/response_builder.rs index eab96b03d..70870267e 100644 --- a/actix-http/src/response_builder.rs +++ b/actix-http/src/response_builder.rs @@ -24,15 +24,44 @@ use crate::{ /// An HTTP response builder. /// -/// This type can be used to construct an instance of `Response` using a builder pattern. +/// Used to construct an instance of `Response` using a builder pattern. Response builders are often +/// created using [`Response::build`]. +/// +/// # Examples +/// ``` +/// use actix_http::{Response, ResponseBuilder, body, http::StatusCode, http::header}; +/// +/// # actix_rt::System::new().block_on(async { +/// let mut res: Response<_> = Response::build(StatusCode::OK) +/// .content_type(mime::APPLICATION_JSON) +/// .insert_header((header::SERVER, "my-app/1.0")) +/// .append_header((header::SET_COOKIE, "a=1")) +/// .append_header((header::SET_COOKIE, "b=2")) +/// .body("1234"); +/// +/// assert_eq!(res.status(), StatusCode::OK); +/// assert_eq!(body::to_bytes(res.take_body()).await.unwrap(), &b"1234"[..]); +/// +/// assert!(res.headers().contains_key("server")); +/// assert_eq!(res.headers().get_all("set-cookie").count(), 2); +/// # }) +/// ``` pub struct ResponseBuilder { head: Option, err: Option, } impl ResponseBuilder { - #[inline] /// Create response builder + /// + /// # Examples + /// ``` + /// use actix_http::{Response, ResponseBuilder, http::StatusCode}; + /// + /// let res: Response<_> = ResponseBuilder::default().finish(); + /// assert_eq!(res.status(), StatusCode::OK); + /// ``` + #[inline] pub fn new(status: StatusCode) -> Self { ResponseBuilder { head: Some(BoxedResponseHead::new(status)), @@ -41,6 +70,14 @@ impl ResponseBuilder { } /// Set HTTP status code of this response. + /// + /// # Examples + /// ``` + /// use actix_http::{ResponseBuilder, http::StatusCode}; + /// + /// let res = ResponseBuilder::default().status(StatusCode::NOT_FOUND).finish(); + /// assert_eq!(res.status(), StatusCode::NOT_FOUND); + /// ``` #[inline] pub fn status(&mut self, status: StatusCode) -> &mut Self { if let Some(parts) = parts(&mut self.head, &self.err) { @@ -51,14 +88,17 @@ impl ResponseBuilder { /// Insert a header, replacing any that were set with an equivalent field name. /// + /// # Examples /// ``` - /// # use actix_http::Response; - /// use actix_http::http::{header, StatusCode}; + /// use actix_http::{ResponseBuilder, http::header}; /// - /// Response::build(StatusCode::OK) + /// let res = ResponseBuilder::default() /// .insert_header((header::CONTENT_TYPE, mime::APPLICATION_JSON)) /// .insert_header(("X-TEST", "value")) /// .finish(); + /// + /// 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 @@ -78,15 +118,18 @@ impl ResponseBuilder { /// Append a header, keeping any that were set with an equivalent field name. /// + /// # Examples /// ``` - /// # use actix_http::Response; - /// use actix_http::http::{header, StatusCode}; + /// use actix_http::{ResponseBuilder, http::header}; /// - /// Response::build(StatusCode::OK) + /// let res = ResponseBuilder::default() /// .append_header((header::CONTENT_TYPE, mime::APPLICATION_JSON)) /// .append_header(("X-TEST", "value1")) /// .append_header(("X-TEST", "value2")) /// .finish(); + /// + /// 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 @@ -254,6 +297,12 @@ fn parts<'a>( parts.as_mut().map(|r| &mut **r) } +impl Default for ResponseBuilder { + fn default() -> Self { + Self::new(StatusCode::OK) + } +} + /// Convert `Response` to a `ResponseBuilder`. Body get dropped. impl From> for ResponseBuilder { fn from(res: Response) -> ResponseBuilder { @@ -313,21 +362,9 @@ impl fmt::Debug for ResponseBuilder { #[cfg(test)] mod tests { - use bytes::BytesMut; - use super::*; use crate::body::Body; - use crate::http::header::{HeaderName, HeaderValue, CONTENT_TYPE, COOKIE}; - - #[test] - fn test_debug() { - let resp = Response::build(StatusCode::OK) - .append_header((COOKIE, HeaderValue::from_static("cookie1=value1; "))) - .append_header((COOKIE, HeaderValue::from_static("cookie2=value2; "))) - .finish(); - let dbg = format!("{:?}", resp); - assert!(dbg.contains("Response")); - } + use crate::http::header::{HeaderName, HeaderValue, CONTENT_TYPE}; #[test] fn test_basic_builder() { @@ -363,76 +400,6 @@ mod tests { assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "text/plain") } - #[test] - fn test_into_response() { - let resp: Response = "test".into(); - assert_eq!(resp.status(), StatusCode::OK); - assert_eq!( - resp.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"); - - let resp: Response = b"test".as_ref().into(); - assert_eq!(resp.status(), StatusCode::OK); - assert_eq!( - resp.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"); - - let resp: Response = "test".to_owned().into(); - assert_eq!(resp.status(), StatusCode::OK); - assert_eq!( - resp.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"); - - let resp: Response = (&"test".to_owned()).into(); - assert_eq!(resp.status(), StatusCode::OK); - assert_eq!( - resp.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"); - - let b = Bytes::from_static(b"test"); - let resp: Response = b.into(); - assert_eq!(resp.status(), StatusCode::OK); - assert_eq!( - resp.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"); - - let b = Bytes::from_static(b"test"); - let resp: Response = b.into(); - assert_eq!(resp.status(), StatusCode::OK); - assert_eq!( - resp.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"); - - let b = BytesMut::from("test"); - let resp: Response = b.into(); - assert_eq!(resp.status(), StatusCode::OK); - assert_eq!( - resp.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"); - } - #[test] fn test_into_builder() { let mut resp: Response = "test".into(); From 1bfdfd1f4174c097e6fd75fcff394484b2074dc2 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Wed, 14 Apr 2021 02:57:28 +0100 Subject: [PATCH 155/339] implement parts as assoc method --- actix-http/src/response_builder.rs | 52 ++++++++++++++---------------- 1 file changed, 25 insertions(+), 27 deletions(-) diff --git a/actix-http/src/response_builder.rs b/actix-http/src/response_builder.rs index 70870267e..4d8cb4429 100644 --- a/actix-http/src/response_builder.rs +++ b/actix-http/src/response_builder.rs @@ -80,7 +80,7 @@ impl ResponseBuilder { /// ``` #[inline] pub fn status(&mut self, status: StatusCode) -> &mut Self { - if let Some(parts) = parts(&mut self.head, &self.err) { + if let Some(parts) = self.inner() { parts.status = status; } self @@ -104,7 +104,7 @@ impl ResponseBuilder { where H: IntoHeaderPair, { - if let Some(parts) = parts(&mut self.head, &self.err) { + if let Some(parts) = self.inner() { match header.try_into_header_pair() { Ok((key, value)) => { parts.headers.insert(key, value); @@ -135,7 +135,7 @@ impl ResponseBuilder { where H: IntoHeaderPair, { - if let Some(parts) = parts(&mut self.head, &self.err) { + if let Some(parts) = self.inner() { match header.try_into_header_pair() { Ok((key, value)) => parts.headers.append(key, value), Err(e) => self.err = Some(e.into()), @@ -148,7 +148,7 @@ impl ResponseBuilder { /// Set the custom reason for the response. #[inline] pub fn reason(&mut self, reason: &'static str) -> &mut Self { - if let Some(parts) = parts(&mut self.head, &self.err) { + if let Some(parts) = self.inner() { parts.reason = Some(reason); } self @@ -157,7 +157,7 @@ impl ResponseBuilder { /// Set connection type to KeepAlive #[inline] pub fn keep_alive(&mut self) -> &mut Self { - if let Some(parts) = parts(&mut self.head, &self.err) { + if let Some(parts) = self.inner() { parts.set_connection_type(ConnectionType::KeepAlive); } self @@ -169,7 +169,7 @@ impl ResponseBuilder { where V: IntoHeaderValue, { - if let Some(parts) = parts(&mut self.head, &self.err) { + if let Some(parts) = self.inner() { parts.set_connection_type(ConnectionType::Upgrade); } @@ -183,7 +183,7 @@ impl ResponseBuilder { /// Force close connection, even if it is marked as keep-alive #[inline] pub fn force_close(&mut self) -> &mut Self { - if let Some(parts) = parts(&mut self.head, &self.err) { + if let Some(parts) = self.inner() { parts.set_connection_type(ConnectionType::Close); } self @@ -195,7 +195,7 @@ impl ResponseBuilder { let mut buf = itoa::Buffer::new(); self.insert_header((header::CONTENT_LENGTH, buf.format(len))); - if let Some(parts) = parts(&mut self.head, &self.err) { + if let Some(parts) = self.inner() { parts.no_chunking(true); } self @@ -207,7 +207,7 @@ impl ResponseBuilder { where V: IntoHeaderValue, { - if let Some(parts) = parts(&mut self.head, &self.err) { + if let Some(parts) = self.inner() { match value.try_into_value() { Ok(value) => { parts.headers.insert(header::CONTENT_TYPE, value); @@ -232,17 +232,17 @@ impl ResponseBuilder { head.extensions.borrow_mut() } - /// Set a body and generate `Response`. + /// Generate response with a wrapped body. /// - /// `ResponseBuilder` can not be used after this call. + /// This `ResponseBuilder` will be left in a useless state. #[inline] pub fn body>(&mut self, body: B) -> Response { self.message_body(body.into()) } - /// Set a body and generate `Response`. + /// Generate response with a body. /// - /// `ResponseBuilder` can not be used after this call. + /// This `ResponseBuilder` will be left in a useless state. pub fn message_body(&mut self, body: B) -> Response { if let Some(e) = self.err.take() { return Response::from(Error::from(e)).into_body(); @@ -257,9 +257,9 @@ impl ResponseBuilder { } } - /// Set a streaming body and generate `Response`. + /// Generate response with a streaming body. /// - /// `ResponseBuilder` can not be used after this call. + /// This `ResponseBuilder` will be left in a useless state. #[inline] pub fn streaming(&mut self, stream: S) -> Response where @@ -269,32 +269,30 @@ impl ResponseBuilder { self.body(Body::from_message(BodyStream::new(stream))) } - /// Set an empty body and generate `Response` + /// Generate response with an empty body. /// - /// `ResponseBuilder` can not be used after this call. + /// This `ResponseBuilder` will be left in a useless state. #[inline] pub fn finish(&mut self) -> Response { self.body(Body::Empty) } - /// This method construct new `ResponseBuilder` + /// Create an owned `ResponseBuilder`, leaving the original in a useless state. pub fn take(&mut self) -> ResponseBuilder { ResponseBuilder { head: self.head.take(), err: self.err.take(), } } -} -#[inline] -fn parts<'a>( - parts: &'a mut Option, - err: &Option, -) -> Option<&'a mut ResponseHead> { - if err.is_some() { - return None; + /// Get access to the inner response head if there has been no error. + fn inner(&mut self) -> Option<&mut ResponseHead> { + if self.err.is_some() { + return None; + } + + self.head.as_mut().map(|r| &mut **r) } - parts.as_mut().map(|r| &mut **r) } impl Default for ResponseBuilder { From 037ac80a32d8361eb66596b013d8df02bae6a920 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Wed, 14 Apr 2021 03:23:15 +0100 Subject: [PATCH 156/339] document messagebody trait items --- actix-http/src/body/message_body.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/actix-http/src/body/message_body.rs b/actix-http/src/body/message_body.rs index ea2cfd22d..894a5fa98 100644 --- a/actix-http/src/body/message_body.rs +++ b/actix-http/src/body/message_body.rs @@ -12,10 +12,12 @@ use crate::error::Error; use super::BodySize; -/// Type that implement this trait can be streamed to a peer. +/// An interface for response bodies. pub trait MessageBody { + /// Body size hint. fn size(&self) -> BodySize; + /// Attempt to pull out the next chunk of body bytes. fn poll_next( self: Pin<&mut Self>, cx: &mut Context<'_>, From a9f26286f9bb60d88b6899cf91d2f868bb6585f4 Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Tue, 13 Apr 2021 21:20:45 -0700 Subject: [PATCH 157/339] reduce branches in h1 dispatcher poll_keepalive (#2089) --- actix-http/src/h1/dispatcher.rs | 24 +++++++----------------- actix-http/src/lib.rs | 4 ++-- 2 files changed, 9 insertions(+), 19 deletions(-) diff --git a/actix-http/src/h1/dispatcher.rs b/actix-http/src/h1/dispatcher.rs index e775846e9..3b272f0fb 100644 --- a/actix-http/src/h1/dispatcher.rs +++ b/actix-http/src/h1/dispatcher.rs @@ -649,11 +649,6 @@ where // go into Some> branch this.ka_timer.set(Some(sleep_until(deadline))); return self.poll_keepalive(cx); - } else { - this.flags.insert(Flags::READ_DISCONNECT); - if let Some(mut payload) = this.payload.take() { - payload.set_error(PayloadError::Incomplete(None)); - } } } } @@ -683,19 +678,14 @@ where } } else { // timeout on first request (slow request) return 408 - if !this.flags.contains(Flags::STARTED) { - trace!("Slow request timeout"); - let _ = self.as_mut().send_response( - Response::new(StatusCode::REQUEST_TIMEOUT) - .drop_body(), - ResponseBody::Other(Body::Empty), - ); - this = self.project(); - } else { - trace!("Keep-alive connection timeout"); - } + trace!("Slow request timeout"); + let _ = self.as_mut().send_response( + Response::new(StatusCode::REQUEST_TIMEOUT) + .drop_body(), + ResponseBody::Other(Body::Empty), + ); + this = self.project(); this.flags.insert(Flags::STARTED | Flags::SHUTDOWN); - this.state.set(State::None); } // still have unfinished task. try to reset and register keep-alive. } else if let Some(deadline) = diff --git a/actix-http/src/lib.rs b/actix-http/src/lib.rs index 8674834e0..4547f3ef2 100644 --- a/actix-http/src/lib.rs +++ b/actix-http/src/lib.rs @@ -60,8 +60,8 @@ pub use self::http_message::HttpMessage; pub use self::message::{Message, RequestHead, RequestHeadType, ResponseHead}; 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::response::Response; +pub use self::response_builder::ResponseBuilder; pub use self::service::HttpService; pub mod http { From ff65f1d006d771a7e91212460639193e3463b5dd Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Wed, 14 Apr 2021 06:07:59 +0100 Subject: [PATCH 158/339] non exhaustive http errors (#2161) --- actix-http/CHANGES.md | 2 + actix-http/Cargo.toml | 1 + actix-http/src/error.rs | 507 +++++++++------------------------------- 3 files changed, 108 insertions(+), 402 deletions(-) diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 17ca7340f..84d6617f7 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -10,6 +10,7 @@ * 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] @@ -28,6 +29,7 @@ [#2152]: https://github.com/actix/actix-web/pull/2152 [#2159]: https://github.com/actix/actix-web/pull/2159 [#2158]: https://github.com/actix/actix-web/pull/2158 +[#2161]: https://github.com/actix/actix-web/pull/2161 ## 3.0.0-beta.5 - 2021-04-02 diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 4bef9e37c..361cae62f 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -62,6 +62,7 @@ local-channel = "0.1" once_cell = "1.5" log = "0.4" mime = "0.3" +paste = "1" percent-encoding = "2.1" pin-project = "1.0.0" pin-project-lite = "0.2" diff --git a/actix-http/src/error.rs b/actix-http/src/error.rs index 705ae7f06..68ad709a1 100644 --- a/actix-http/src/error.rs +++ b/actix-http/src/error.rs @@ -140,8 +140,8 @@ impl From for Error { } } -#[derive(Debug, Display)] -#[display(fmt = "UnknownError")] +#[derive(Debug, Display, Error)] +#[display(fmt = "Unknown Error")] struct UnitError; /// Returns [`StatusCode::INTERNAL_SERVER_ERROR`] for [`UnitError`]. @@ -190,38 +190,47 @@ impl ResponseError for header::InvalidHeaderValue { } } -/// A set of errors that can occur during parsing HTTP streams -#[derive(Debug, Display)] +/// A set of errors that can occur during parsing HTTP streams. +#[derive(Debug, Display, Error)] +#[non_exhaustive] pub enum ParseError { /// An invalid `Method`, such as `GE.T`. #[display(fmt = "Invalid Method specified")] Method, + /// An invalid `Uri`, such as `exam ple.domain`. #[display(fmt = "Uri error: {}", _0)] Uri(InvalidUri), + /// An invalid `HttpVersion`, such as `HTP/1.1` #[display(fmt = "Invalid HTTP version specified")] Version, + /// An invalid `Header`. #[display(fmt = "Invalid Header provided")] Header, + /// A message head is too large to be reasonable. #[display(fmt = "Message head is too large")] TooLarge, + /// A message reached EOF, but is not complete. #[display(fmt = "Message is incomplete")] Incomplete, + /// An invalid `Status`, such as `1337 ELITE`. #[display(fmt = "Invalid Status provided")] Status, + /// A timeout occurred waiting for an IO event. #[allow(dead_code)] #[display(fmt = "Timeout")] Timeout, - /// 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), + /// Parsing a field as string failed #[display(fmt = "UTF8 error: {}", _0)] Utf8(Utf8Error), @@ -273,17 +282,16 @@ impl From for ParseError { } /// A set of errors that can occur running blocking tasks in thread pool. -#[derive(Debug, Display)] +#[derive(Debug, Display, Error)] #[display(fmt = "Blocking thread pool is gone")] pub struct BlockingError; -impl std::error::Error for BlockingError {} - /// `InternalServerError` for `BlockingError` impl ResponseError for BlockingError {} -#[derive(Display, Debug)] -/// A set of errors that can occur during payload parsing +/// A set of errors that can occur during payload parsing. +#[derive(Debug, Display)] +#[non_exhaustive] pub enum PayloadError { /// A payload reached EOF, but is not complete. #[display( @@ -367,8 +375,9 @@ impl ResponseError for PayloadError { } } -#[derive(Debug, Display, From)] -/// A set of errors that can occur during dispatching HTTP requests +/// A set of errors that can occur during dispatching HTTP requests. +#[derive(Debug, Display, Error, From)] +#[non_exhaustive] pub enum DispatchError { /// Service error Service(Error), @@ -414,8 +423,9 @@ pub enum DispatchError { Unknown, } -/// A set of error that can occur during parsing content type -#[derive(Debug, PartialEq, Display, Error)] +/// A set of error that can occur during parsing content type. +#[derive(Debug, Display, Error)] +#[non_exhaustive] pub enum ContentTypeError { /// Can not parse content type #[display(fmt = "Can not parse content type")] @@ -426,6 +436,22 @@ 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) + } + } + } + } +} + /// Return `BadRequest` for `ContentTypeError` impl ResponseError for ContentTypeError { fn status_code(&self) -> StatusCode { @@ -533,395 +559,72 @@ where } } -/// Helper function that creates wrapper of any error and generate *BAD -/// REQUEST* response. -#[allow(non_snake_case)] -pub fn ErrorBadRequest(err: T) -> Error -where - T: fmt::Debug + fmt::Display + 'static, -{ - InternalError::new(err, StatusCode::BAD_REQUEST).into() +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() + } + } + } } -/// Helper function that creates wrapper of any error and generate -/// *UNAUTHORIZED* response. -#[allow(non_snake_case)] -pub fn ErrorUnauthorized(err: T) -> Error -where - T: fmt::Debug + fmt::Display + 'static, -{ - InternalError::new(err, StatusCode::UNAUTHORIZED).into() -} - -/// Helper function that creates wrapper of any error and generate -/// *PAYMENT_REQUIRED* response. -#[allow(non_snake_case)] -pub fn ErrorPaymentRequired(err: T) -> Error -where - T: fmt::Debug + fmt::Display + 'static, -{ - InternalError::new(err, StatusCode::PAYMENT_REQUIRED).into() -} - -/// Helper function that creates wrapper of any error and generate *FORBIDDEN* -/// response. -#[allow(non_snake_case)] -pub fn ErrorForbidden(err: T) -> Error -where - T: fmt::Debug + fmt::Display + 'static, -{ - InternalError::new(err, StatusCode::FORBIDDEN).into() -} - -/// Helper function that creates wrapper of any error and generate *NOT FOUND* -/// response. -#[allow(non_snake_case)] -pub fn ErrorNotFound(err: T) -> Error -where - T: fmt::Debug + fmt::Display + 'static, -{ - InternalError::new(err, StatusCode::NOT_FOUND).into() -} - -/// Helper function that creates wrapper of any error and generate *METHOD NOT -/// ALLOWED* response. -#[allow(non_snake_case)] -pub fn ErrorMethodNotAllowed(err: T) -> Error -where - T: fmt::Debug + fmt::Display + 'static, -{ - InternalError::new(err, StatusCode::METHOD_NOT_ALLOWED).into() -} - -/// Helper function that creates wrapper of any error and generate *NOT -/// ACCEPTABLE* response. -#[allow(non_snake_case)] -pub fn ErrorNotAcceptable(err: T) -> Error -where - T: fmt::Debug + fmt::Display + 'static, -{ - InternalError::new(err, StatusCode::NOT_ACCEPTABLE).into() -} - -/// Helper function that creates wrapper of any error and generate *PROXY -/// AUTHENTICATION REQUIRED* response. -#[allow(non_snake_case)] -pub fn ErrorProxyAuthenticationRequired(err: T) -> Error -where - T: fmt::Debug + fmt::Display + 'static, -{ - InternalError::new(err, StatusCode::PROXY_AUTHENTICATION_REQUIRED).into() -} - -/// Helper function that creates wrapper of any error and generate *REQUEST -/// TIMEOUT* response. -#[allow(non_snake_case)] -pub fn ErrorRequestTimeout(err: T) -> Error -where - T: fmt::Debug + fmt::Display + 'static, -{ - InternalError::new(err, StatusCode::REQUEST_TIMEOUT).into() -} - -/// Helper function that creates wrapper of any error and generate *CONFLICT* -/// response. -#[allow(non_snake_case)] -pub fn ErrorConflict(err: T) -> Error -where - T: fmt::Debug + fmt::Display + 'static, -{ - InternalError::new(err, StatusCode::CONFLICT).into() -} - -/// Helper function that creates wrapper of any error and generate *GONE* -/// response. -#[allow(non_snake_case)] -pub fn ErrorGone(err: T) -> Error -where - T: fmt::Debug + fmt::Display + 'static, -{ - InternalError::new(err, StatusCode::GONE).into() -} - -/// Helper function that creates wrapper of any error and generate *LENGTH -/// REQUIRED* response. -#[allow(non_snake_case)] -pub fn ErrorLengthRequired(err: T) -> Error -where - T: fmt::Debug + fmt::Display + 'static, -{ - InternalError::new(err, StatusCode::LENGTH_REQUIRED).into() -} - -/// Helper function that creates wrapper of any error and generate -/// *PAYLOAD TOO LARGE* response. -#[allow(non_snake_case)] -pub fn ErrorPayloadTooLarge(err: T) -> Error -where - T: fmt::Debug + fmt::Display + 'static, -{ - InternalError::new(err, StatusCode::PAYLOAD_TOO_LARGE).into() -} - -/// Helper function that creates wrapper of any error and generate -/// *URI TOO LONG* response. -#[allow(non_snake_case)] -pub fn ErrorUriTooLong(err: T) -> Error -where - T: fmt::Debug + fmt::Display + 'static, -{ - InternalError::new(err, StatusCode::URI_TOO_LONG).into() -} - -/// Helper function that creates wrapper of any error and generate -/// *UNSUPPORTED MEDIA TYPE* response. -#[allow(non_snake_case)] -pub fn ErrorUnsupportedMediaType(err: T) -> Error -where - T: fmt::Debug + fmt::Display + 'static, -{ - InternalError::new(err, StatusCode::UNSUPPORTED_MEDIA_TYPE).into() -} - -/// Helper function that creates wrapper of any error and generate -/// *RANGE NOT SATISFIABLE* response. -#[allow(non_snake_case)] -pub fn ErrorRangeNotSatisfiable(err: T) -> Error -where - T: fmt::Debug + fmt::Display + 'static, -{ - InternalError::new(err, StatusCode::RANGE_NOT_SATISFIABLE).into() -} - -/// Helper function that creates wrapper of any error and generate -/// *IM A TEAPOT* response. -#[allow(non_snake_case)] -pub fn ErrorImATeapot(err: T) -> Error -where - T: fmt::Debug + fmt::Display + 'static, -{ - InternalError::new(err, StatusCode::IM_A_TEAPOT).into() -} - -/// Helper function that creates wrapper of any error and generate -/// *MISDIRECTED REQUEST* response. -#[allow(non_snake_case)] -pub fn ErrorMisdirectedRequest(err: T) -> Error -where - T: fmt::Debug + fmt::Display + 'static, -{ - InternalError::new(err, StatusCode::MISDIRECTED_REQUEST).into() -} - -/// Helper function that creates wrapper of any error and generate -/// *UNPROCESSABLE ENTITY* response. -#[allow(non_snake_case)] -pub fn ErrorUnprocessableEntity(err: T) -> Error -where - T: fmt::Debug + fmt::Display + 'static, -{ - InternalError::new(err, StatusCode::UNPROCESSABLE_ENTITY).into() -} - -/// Helper function that creates wrapper of any error and generate -/// *LOCKED* response. -#[allow(non_snake_case)] -pub fn ErrorLocked(err: T) -> Error -where - T: fmt::Debug + fmt::Display + 'static, -{ - InternalError::new(err, StatusCode::LOCKED).into() -} - -/// Helper function that creates wrapper of any error and generate -/// *FAILED DEPENDENCY* response. -#[allow(non_snake_case)] -pub fn ErrorFailedDependency(err: T) -> Error -where - T: fmt::Debug + fmt::Display + 'static, -{ - InternalError::new(err, StatusCode::FAILED_DEPENDENCY).into() -} - -/// Helper function that creates wrapper of any error and generate -/// *UPGRADE REQUIRED* response. -#[allow(non_snake_case)] -pub fn ErrorUpgradeRequired(err: T) -> Error -where - T: fmt::Debug + fmt::Display + 'static, -{ - InternalError::new(err, StatusCode::UPGRADE_REQUIRED).into() -} - -/// Helper function that creates wrapper of any error and generate -/// *PRECONDITION FAILED* response. -#[allow(non_snake_case)] -pub fn ErrorPreconditionFailed(err: T) -> Error -where - T: fmt::Debug + fmt::Display + 'static, -{ - InternalError::new(err, StatusCode::PRECONDITION_FAILED).into() -} - -/// Helper function that creates wrapper of any error and generate -/// *PRECONDITION REQUIRED* response. -#[allow(non_snake_case)] -pub fn ErrorPreconditionRequired(err: T) -> Error -where - T: fmt::Debug + fmt::Display + 'static, -{ - InternalError::new(err, StatusCode::PRECONDITION_REQUIRED).into() -} - -/// Helper function that creates wrapper of any error and generate -/// *TOO MANY REQUESTS* response. -#[allow(non_snake_case)] -pub fn ErrorTooManyRequests(err: T) -> Error -where - T: fmt::Debug + fmt::Display + 'static, -{ - InternalError::new(err, StatusCode::TOO_MANY_REQUESTS).into() -} - -/// Helper function that creates wrapper of any error and generate -/// *REQUEST HEADER FIELDS TOO LARGE* response. -#[allow(non_snake_case)] -pub fn ErrorRequestHeaderFieldsTooLarge(err: T) -> Error -where - T: fmt::Debug + fmt::Display + 'static, -{ - InternalError::new(err, StatusCode::REQUEST_HEADER_FIELDS_TOO_LARGE).into() -} - -/// Helper function that creates wrapper of any error and generate -/// *UNAVAILABLE FOR LEGAL REASONS* response. -#[allow(non_snake_case)] -pub fn ErrorUnavailableForLegalReasons(err: T) -> Error -where - T: fmt::Debug + fmt::Display + 'static, -{ - InternalError::new(err, StatusCode::UNAVAILABLE_FOR_LEGAL_REASONS).into() -} - -/// Helper function that creates wrapper of any error and generate -/// *EXPECTATION FAILED* response. -#[allow(non_snake_case)] -pub fn ErrorExpectationFailed(err: T) -> Error -where - T: fmt::Debug + fmt::Display + 'static, -{ - InternalError::new(err, StatusCode::EXPECTATION_FAILED).into() -} - -/// Helper function that creates wrapper of any error and -/// generate *INTERNAL SERVER ERROR* response. -#[allow(non_snake_case)] -pub fn ErrorInternalServerError(err: T) -> Error -where - T: fmt::Debug + fmt::Display + 'static, -{ - InternalError::new(err, StatusCode::INTERNAL_SERVER_ERROR).into() -} - -/// Helper function that creates wrapper of any error and -/// generate *NOT IMPLEMENTED* response. -#[allow(non_snake_case)] -pub fn ErrorNotImplemented(err: T) -> Error -where - T: fmt::Debug + fmt::Display + 'static, -{ - InternalError::new(err, StatusCode::NOT_IMPLEMENTED).into() -} - -/// Helper function that creates wrapper of any error and -/// generate *BAD GATEWAY* response. -#[allow(non_snake_case)] -pub fn ErrorBadGateway(err: T) -> Error -where - T: fmt::Debug + fmt::Display + 'static, -{ - InternalError::new(err, StatusCode::BAD_GATEWAY).into() -} - -/// Helper function that creates wrapper of any error and -/// generate *SERVICE UNAVAILABLE* response. -#[allow(non_snake_case)] -pub fn ErrorServiceUnavailable(err: T) -> Error -where - T: fmt::Debug + fmt::Display + 'static, -{ - InternalError::new(err, StatusCode::SERVICE_UNAVAILABLE).into() -} - -/// Helper function that creates wrapper of any error and -/// generate *GATEWAY TIMEOUT* response. -#[allow(non_snake_case)] -pub fn ErrorGatewayTimeout(err: T) -> Error -where - T: fmt::Debug + fmt::Display + 'static, -{ - InternalError::new(err, StatusCode::GATEWAY_TIMEOUT).into() -} - -/// Helper function that creates wrapper of any error and -/// generate *HTTP VERSION NOT SUPPORTED* response. -#[allow(non_snake_case)] -pub fn ErrorHttpVersionNotSupported(err: T) -> Error -where - T: fmt::Debug + fmt::Display + 'static, -{ - InternalError::new(err, StatusCode::HTTP_VERSION_NOT_SUPPORTED).into() -} - -/// Helper function that creates wrapper of any error and -/// generate *VARIANT ALSO NEGOTIATES* response. -#[allow(non_snake_case)] -pub fn ErrorVariantAlsoNegotiates(err: T) -> Error -where - T: fmt::Debug + fmt::Display + 'static, -{ - InternalError::new(err, StatusCode::VARIANT_ALSO_NEGOTIATES).into() -} - -/// Helper function that creates wrapper of any error and -/// generate *INSUFFICIENT STORAGE* response. -#[allow(non_snake_case)] -pub fn ErrorInsufficientStorage(err: T) -> Error -where - T: fmt::Debug + fmt::Display + 'static, -{ - InternalError::new(err, StatusCode::INSUFFICIENT_STORAGE).into() -} - -/// Helper function that creates wrapper of any error and -/// generate *LOOP DETECTED* response. -#[allow(non_snake_case)] -pub fn ErrorLoopDetected(err: T) -> Error -where - T: fmt::Debug + fmt::Display + 'static, -{ - InternalError::new(err, StatusCode::LOOP_DETECTED).into() -} - -/// Helper function that creates wrapper of any error and -/// generate *NOT EXTENDED* response. -#[allow(non_snake_case)] -pub fn ErrorNotExtended(err: T) -> Error -where - T: fmt::Debug + fmt::Display + 'static, -{ - InternalError::new(err, StatusCode::NOT_EXTENDED).into() -} - -/// Helper function that creates wrapper of any error and -/// generate *NETWORK AUTHENTICATION REQUIRED* response. -#[allow(non_snake_case)] -pub fn ErrorNetworkAuthenticationRequired(err: T) -> Error -where - T: fmt::Debug + fmt::Display + 'static, -{ - InternalError::new(err, StatusCode::NETWORK_AUTHENTICATION_REQUIRED).into() -} +error_helper!(ErrorBadRequest, BAD_REQUEST); +error_helper!(ErrorUnauthorized, UNAUTHORIZED); +error_helper!(ErrorPaymentRequired, PAYMENT_REQUIRED); +error_helper!(ErrorForbidden, FORBIDDEN); +error_helper!(ErrorNotFound, NOT_FOUND); +error_helper!(ErrorMethodNotAllowed, METHOD_NOT_ALLOWED); +error_helper!(ErrorNotAcceptable, NOT_ACCEPTABLE); +error_helper!( + ErrorProxyAuthenticationRequired, + PROXY_AUTHENTICATION_REQUIRED +); +error_helper!(ErrorRequestTimeout, REQUEST_TIMEOUT); +error_helper!(ErrorConflict, CONFLICT); +error_helper!(ErrorGone, GONE); +error_helper!(ErrorLengthRequired, LENGTH_REQUIRED); +error_helper!(ErrorPayloadTooLarge, PAYLOAD_TOO_LARGE); +error_helper!(ErrorUriTooLong, URI_TOO_LONG); +error_helper!(ErrorUnsupportedMediaType, UNSUPPORTED_MEDIA_TYPE); +error_helper!(ErrorRangeNotSatisfiable, RANGE_NOT_SATISFIABLE); +error_helper!(ErrorImATeapot, IM_A_TEAPOT); +error_helper!(ErrorMisdirectedRequest, MISDIRECTED_REQUEST); +error_helper!(ErrorUnprocessableEntity, UNPROCESSABLE_ENTITY); +error_helper!(ErrorLocked, LOCKED); +error_helper!(ErrorFailedDependency, FAILED_DEPENDENCY); +error_helper!(ErrorUpgradeRequired, UPGRADE_REQUIRED); +error_helper!(ErrorPreconditionFailed, PRECONDITION_FAILED); +error_helper!(ErrorPreconditionRequired, PRECONDITION_REQUIRED); +error_helper!(ErrorTooManyRequests, TOO_MANY_REQUESTS); +error_helper!( + ErrorRequestHeaderFieldsTooLarge, + REQUEST_HEADER_FIELDS_TOO_LARGE +); +error_helper!( + ErrorUnavailableForLegalReasons, + UNAVAILABLE_FOR_LEGAL_REASONS +); +error_helper!(ErrorExpectationFailed, EXPECTATION_FAILED); +error_helper!(ErrorInternalServerError, INTERNAL_SERVER_ERROR); +error_helper!(ErrorNotImplemented, NOT_IMPLEMENTED); +error_helper!(ErrorBadGateway, BAD_GATEWAY); +error_helper!(ErrorServiceUnavailable, SERVICE_UNAVAILABLE); +error_helper!(ErrorGatewayTimeout, GATEWAY_TIMEOUT); +error_helper!(ErrorHttpVersionNotSupported, HTTP_VERSION_NOT_SUPPORTED); +error_helper!(ErrorVariantAlsoNegotiates, VARIANT_ALSO_NEGOTIATES); +error_helper!(ErrorInsufficientStorage, INSUFFICIENT_STORAGE); +error_helper!(ErrorLoopDetected, LOOP_DETECTED); +error_helper!(ErrorNotExtended, NOT_EXTENDED); +error_helper!( + ErrorNetworkAuthenticationRequired, + NETWORK_AUTHENTICATION_REQUIRED +); #[cfg(test)] mod tests { From 64bed506c25ffa7f45a9fe2f16f4286edb17fd62 Mon Sep 17 00:00:00 2001 From: "D.Loh" Date: Thu, 15 Apr 2021 11:11:30 -0700 Subject: [PATCH 159/339] chore: update benchmaks to round 20 (#2163) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 508736279..c85c0652f 100644 --- a/README.md +++ b/README.md @@ -90,7 +90,7 @@ You may consider checking out ## Benchmarks One of the fastest web frameworks available according to the -[TechEmpower Framework Benchmark](https://www.techempower.com/benchmarks/#section=data-r19). +[TechEmpower Framework Benchmark](https://www.techempower.com/benchmarks/#section=data-r20&test=composite). ## License From 845c02cb86f3ce5ff210a530855d86e1b5471c60 Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Thu, 15 Apr 2021 16:54:51 -0700 Subject: [PATCH 160/339] Add responder impl for Cow (#2164) --- actix-http/src/body/body.rs | 16 ++++++- src/responder.rs | 85 +++++++++++++++++++------------------ 2 files changed, 57 insertions(+), 44 deletions(-) diff --git a/actix-http/src/body/body.rs b/actix-http/src/body/body.rs index 5fc461d41..4fe18338a 100644 --- a/actix-http/src/body/body.rs +++ b/actix-http/src/body/body.rs @@ -1,4 +1,5 @@ use std::{ + borrow::Cow, fmt, mem, pin::Pin, task::{Context, Poll}, @@ -118,12 +119,23 @@ impl From for Body { } } -impl<'a> From<&'a String> for Body { - fn from(s: &'a String) -> Body { +impl From<&'_ String> for Body { + fn from(s: &String) -> Body { Body::Bytes(Bytes::copy_from_slice(AsRef::<[u8]>::as_ref(&s))) } } +impl From> for Body { + fn from(s: Cow<'_, str>) -> Body { + match s { + Cow::Owned(s) => Body::from(s), + Cow::Borrowed(s) => { + Body::Bytes(Bytes::copy_from_slice(AsRef::<[u8]>::as_ref(s))) + } + } + } +} + impl From for Body { fn from(s: Bytes) -> Body { Body::Bytes(s) diff --git a/src/responder.rs b/src/responder.rs index 2348e9276..7b8288ed8 100644 --- a/src/responder.rs +++ b/src/responder.rs @@ -1,4 +1,4 @@ -use std::fmt; +use std::{borrow::Cow, fmt}; use actix_http::{ body::Body, @@ -117,53 +117,29 @@ impl Responder for (T, StatusCode) { } } -impl Responder for &'static str { - fn respond_to(self, _: &HttpRequest) -> HttpResponse { - HttpResponse::Ok() - .content_type(mime::TEXT_PLAIN_UTF_8) - .body(self) - } +macro_rules! impl_responder { + ($res: ty, $ct: path) => { + impl Responder for $res { + fn respond_to(self, _: &HttpRequest) -> HttpResponse { + HttpResponse::Ok().content_type($ct).body(self) + } + } + }; } -impl Responder for &'static [u8] { - fn respond_to(self, _: &HttpRequest) -> HttpResponse { - HttpResponse::Ok() - .content_type(mime::APPLICATION_OCTET_STREAM) - .body(self) - } -} +impl_responder!(&'static str, mime::TEXT_PLAIN_UTF_8); -impl Responder for String { - fn respond_to(self, _: &HttpRequest) -> HttpResponse { - HttpResponse::Ok() - .content_type(mime::TEXT_PLAIN_UTF_8) - .body(self) - } -} +impl_responder!(String, mime::TEXT_PLAIN_UTF_8); -impl<'a> Responder for &'a String { - fn respond_to(self, _: &HttpRequest) -> HttpResponse { - HttpResponse::Ok() - .content_type(mime::TEXT_PLAIN_UTF_8) - .body(self) - } -} +impl_responder!(&'_ String, mime::TEXT_PLAIN_UTF_8); -impl Responder for Bytes { - fn respond_to(self, _: &HttpRequest) -> HttpResponse { - HttpResponse::Ok() - .content_type(mime::APPLICATION_OCTET_STREAM) - .body(self) - } -} +impl_responder!(Cow<'_, str>, mime::TEXT_PLAIN_UTF_8); -impl Responder for BytesMut { - fn respond_to(self, _: &HttpRequest) -> HttpResponse { - HttpResponse::Ok() - .content_type(mime::APPLICATION_OCTET_STREAM) - .body(self) - } -} +impl_responder!(&'static [u8], mime::APPLICATION_OCTET_STREAM); + +impl_responder!(Bytes, mime::APPLICATION_OCTET_STREAM); + +impl_responder!(BytesMut, mime::APPLICATION_OCTET_STREAM); /// Allows overriding status code and headers for a responder. pub struct CustomResponder { @@ -358,6 +334,31 @@ pub(crate) mod tests { HeaderValue::from_static("text/plain; charset=utf-8") ); + 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"); + assert_eq!( + resp.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") + ); + + let resp = Cow::Borrowed("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(), + 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"); From 8d88a0a9af232f3d554002b20f9fa61e18e6aa6c Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Thu, 15 Apr 2021 22:05:06 +0100 Subject: [PATCH 161/339] rename header generator macros --- 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 | 9 +- src/http/header/allow.rs | 10 +- src/http/header/cache_control.rs | 6 +- 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 | 5 +- src/http/header/macros.rs | 300 ++++++++++++++++++++++ src/http/header/mod.rs | 330 +------------------------ 20 files changed, 395 insertions(+), 397 deletions(-) create mode 100644 src/http/header/macros.rs diff --git a/src/http/header/accept.rs b/src/http/header/accept.rs index 1ec94e353..1b6a963da 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::header! { +crate::__define_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::header! { test_accept { // Tests from the RFC - test_header!( + crate::__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()), ]))); - test_header!( + crate::__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::header! { qitem("text/x-c".parse().unwrap()), ]))); // Custom tests - test_header!( + crate::__common_header_test!( test3, vec![b"text/plain; charset=utf-8"], Some(Accept(vec![ qitem(mime::TEXT_PLAIN_UTF_8), ]))); - test_header!( + crate::__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 9932ac57a..2c6a0b9f6 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::header! { +crate::__define_common_header! { /// `Accept-Charset` header, defined in /// [RFC7231](http://tools.ietf.org/html/rfc7231#section-5.3.3) /// @@ -57,6 +57,6 @@ crate::header! { test_accept_charset { // Test case from RFC - test_header!(test1, vec![b"iso-8859-5, unicode-1-1;q=0.8"]); + crate::__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 e59351708..734a435b3 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 - test_header!(test1, vec![b"compress, gzip"]); - test_header!(test2, vec![b""], Some(AcceptEncoding(vec![]))); - test_header!(test3, vec![b"*"]); + 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"*"]); // Note: Removed quality 1 from gzip - test_header!(test4, vec![b"compress;q=0.5, gzip"]); + crate::__common_header_test!(test4, vec![b"compress;q=0.5, gzip"]); // Note: Removed quality 1 from gzip - test_header!(test5, vec![b"gzip, identity; q=0.5, *;q=0"]); + crate::__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 2963844af..5fab4f79c 100644 --- a/src/http/header/accept_language.rs +++ b/src/http/header/accept_language.rs @@ -1,7 +1,8 @@ -use super::{QualityItem, ACCEPT_LANGUAGE}; use language_tags::LanguageTag; -crate::header! { +use super::{QualityItem, ACCEPT_LANGUAGE}; + +crate::__define_common_header! { /// `Accept-Language` header, defined in /// [RFC7231](http://tools.ietf.org/html/rfc7231#section-5.3.5) /// @@ -56,9 +57,9 @@ crate::header! { test_accept_language { // From the RFC - test_header!(test1, vec![b"da, en-gb;q=0.8, en;q=0.7"]); + crate::__common_header_test!(test1, vec![b"da, en-gb;q=0.8, en;q=0.7"]); // Own test - test_header!( + crate::__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 e1f2bb4b6..15a627b8f 100644 --- a/src/http/header/allow.rs +++ b/src/http/header/allow.rs @@ -1,7 +1,7 @@ -use actix_http::http::Method; use crate::http::header; +use actix_http::http::Method; -crate::header! { +crate::__define_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::header! { test_allow { // From the RFC - test_header!( + crate::__common_header_test!( test1, vec![b"GET, HEAD, PUT"], Some(HeaderField(vec![Method::GET, Method::HEAD, Method::PUT]))); // Own tests - test_header!( + crate::__common_header_test!( test2, vec![b"OPTIONS, GET, PUT, POST, DELETE, HEAD, TRACE, CONNECT, PATCH"], Some(HeaderField(vec![ @@ -67,7 +67,7 @@ crate::header! { Method::TRACE, Method::CONNECT, Method::PATCH]))); - test_header!( + crate::__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 6f020a931..891ba7c79 100644 --- a/src/http/header/cache_control.rs +++ b/src/http/header/cache_control.rs @@ -51,9 +51,9 @@ use crate::http::header; #[derive(PartialEq, Clone, Debug)] pub struct CacheControl(pub Vec); -__hyper__deref!(CacheControl => Vec); +crate::__common_header_deref!(CacheControl => Vec); -//TODO: this could just be the header! macro +// TODO: this could just be the __define_common_header! macro impl Header for CacheControl { fn name() -> header::HeaderName { header::CACHE_CONTROL @@ -75,7 +75,7 @@ impl Header for CacheControl { impl fmt::Display for CacheControl { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt_comma_delimited(f, &self[..]) + fmt_comma_delimited(f, &self.0[..]) } } diff --git a/src/http/header/content_language.rs b/src/http/header/content_language.rs index 5dd8f72a5..41e6d9eff 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::header! { +crate::__define_common_header! { /// `Content-Language` header, defined in /// [RFC7231](https://tools.ietf.org/html/rfc7231#section-3.1.3.2) /// @@ -52,7 +52,7 @@ crate::header! { (ContentLanguage, CONTENT_LANGUAGE) => (QualityItem)+ test_content_language { - test_header!(test1, vec![b"da"]); - test_header!(test2, vec![b"mi, en"]); + crate::__common_header_test!(test1, vec![b"da"]); + crate::__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 dfcf24251..e3a8450cb 100644 --- a/src/http/header/content_range.rs +++ b/src/http/header/content_range.rs @@ -6,65 +6,65 @@ use super::{ HeaderValue, IntoHeaderValue, InvalidHeaderValue, Writer, CONTENT_RANGE, }; -crate::header! { +crate::__define_common_header! { /// `Content-Range` header, defined in /// [RFC7233](http://tools.ietf.org/html/rfc7233#section-4.2) (ContentRange, CONTENT_RANGE) => [ContentRangeSpec] test_content_range { - test_header!(test_bytes, + crate::__common_header_test!(test_bytes, vec![b"bytes 0-499/500"], Some(ContentRange(ContentRangeSpec::Bytes { range: Some((0, 499)), instance_length: Some(500) }))); - test_header!(test_bytes_unknown_len, + crate::__common_header_test!(test_bytes_unknown_len, vec![b"bytes 0-499/*"], Some(ContentRange(ContentRangeSpec::Bytes { range: Some((0, 499)), instance_length: None }))); - test_header!(test_bytes_unknown_range, + crate::__common_header_test!(test_bytes_unknown_range, vec![b"bytes */500"], Some(ContentRange(ContentRangeSpec::Bytes { range: None, instance_length: Some(500) }))); - test_header!(test_unregistered, + crate::__common_header_test!(test_unregistered, vec![b"seconds 1-2"], Some(ContentRange(ContentRangeSpec::Unregistered { unit: "seconds".to_owned(), resp: "1-2".to_owned() }))); - test_header!(test_no_len, + crate::__common_header_test!(test_no_len, vec![b"bytes 0-499"], None::); - test_header!(test_only_unit, + crate::__common_header_test!(test_only_unit, vec![b"bytes"], None::); - test_header!(test_end_less_than_start, + crate::__common_header_test!(test_end_less_than_start, vec![b"bytes 499-0/500"], None::); - test_header!(test_blank, + crate::__common_header_test!(test_blank, vec![b""], None::); - test_header!(test_bytes_many_spaces, + crate::__common_header_test!(test_bytes_many_spaces, vec![b"bytes 1-2/500 3"], None::); - test_header!(test_bytes_many_slashes, + crate::__common_header_test!(test_bytes_many_slashes, vec![b"bytes 1-2/500/600"], None::); - test_header!(test_bytes_many_dashes, + crate::__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 a85e64ba9..65cb2a986 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::header! { +crate::__define_common_header! { /// `Content-Type` header, defined in /// [RFC7231](http://tools.ietf.org/html/rfc7231#section-3.1.1.5) /// @@ -52,7 +52,7 @@ crate::header! { (ContentType, CONTENT_TYPE) => [Mime] test_content_type { - test_header!( + crate::__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 faceefb4f..982a1455c 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::header! { +crate::__define_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::header! { (Date, DATE) => [HttpDate] test_date { - test_header!(test1, vec![b"Tue, 15 Nov 1994 08:12:31 GMT"]); + crate::__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 8972564d0..b121fe26f 100644 --- a/src/http/header/etag.rs +++ b/src/http/header/etag.rs @@ -1,6 +1,6 @@ use super::{EntityTag, ETAG}; -crate::header! { +crate::__define_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::header! { test_etag { // From the RFC - test_header!(test1, + crate::__common_header_test!(test1, vec![b"\"xyzzy\""], Some(ETag(EntityTag::new(false, "xyzzy".to_owned())))); - test_header!(test2, + crate::__common_header_test!(test2, vec![b"W/\"xyzzy\""], Some(ETag(EntityTag::new(true, "xyzzy".to_owned())))); - test_header!(test3, + crate::__common_header_test!(test3, vec![b"\"\""], Some(ETag(EntityTag::new(false, "".to_owned())))); // Own tests - test_header!(test4, + crate::__common_header_test!(test4, vec![b"\"foobar\""], Some(ETag(EntityTag::new(false, "foobar".to_owned())))); - test_header!(test5, + crate::__common_header_test!(test5, vec![b"\"\""], Some(ETag(EntityTag::new(false, "".to_owned())))); - test_header!(test6, + crate::__common_header_test!(test6, vec![b"W/\"weak-etag\""], Some(ETag(EntityTag::new(true, "weak-etag".to_owned())))); - test_header!(test7, + crate::__common_header_test!(test7, vec![b"W/\"\x65\x62\""], Some(ETag(EntityTag::new(true, "\u{0065}\u{0062}".to_owned())))); - test_header!(test8, + crate::__common_header_test!(test8, vec![b"W/\"\""], Some(ETag(EntityTag::new(true, "".to_owned())))); - test_header!(test9, + crate::__common_header_test!(test9, vec![b"no-dquotes"], None::); - test_header!(test10, + crate::__common_header_test!(test10, vec![b"w/\"the-first-w-is-case-sensitive\""], None::); - test_header!(test11, + crate::__common_header_test!(test11, vec![b""], None::); - test_header!(test12, + crate::__common_header_test!(test12, vec![b"\"unmatched-dquotes1"], None::); - test_header!(test13, + crate::__common_header_test!(test13, vec![b"unmatched-dquotes2\""], None::); - test_header!(test14, + crate::__common_header_test!(test14, vec![b"matched-\"dquotes\""], None::); - test_header!(test15, + crate::__common_header_test!(test15, vec![b"\""], None::); } diff --git a/src/http/header/expires.rs b/src/http/header/expires.rs index 1c306cae0..759e7d280 100644 --- a/src/http/header/expires.rs +++ b/src/http/header/expires.rs @@ -1,6 +1,6 @@ use super::{HttpDate, EXPIRES}; -crate::header! { +crate::__define_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::header! { test_expires { // Test case from RFC - test_header!(test1, vec![b"Thu, 01 Dec 1994 16:00:00 GMT"]); + crate::__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 80699e39c..d4402715d 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::header! { +crate::__define_common_header! { /// `If-Match` header, defined in /// [RFC7232](https://tools.ietf.org/html/rfc7232#section-3.1) /// @@ -53,18 +53,18 @@ crate::header! { (IfMatch, IF_MATCH) => {Any / (EntityTag)+} test_if_match { - test_header!( + crate::__common_header_test!( test1, vec![b"\"xyzzy\""], Some(HeaderField::Items( vec![EntityTag::new(false, "xyzzy".to_owned())]))); - test_header!( + crate::__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())]))); - test_header!(test3, vec![b"*"], Some(IfMatch::Any)); + crate::__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 d777e0c5c..ba393032d 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::header! { +crate::__define_common_header! { /// `If-Modified-Since` header, defined in /// [RFC7232](http://tools.ietf.org/html/rfc7232#section-3.3) /// @@ -36,6 +36,6 @@ crate::header! { test_if_modified_since { // Test case from RFC - test_header!(test1, vec![b"Sat, 29 Oct 1994 19:43:31 GMT"]); + crate::__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 a5c06b374..f16b196cc 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::header! { +crate::__define_common_header! { /// `If-None-Match` header, defined in /// [RFC7232](https://tools.ietf.org/html/rfc7232#section-3.2) /// @@ -55,11 +55,11 @@ crate::header! { (IfNoneMatch, IF_NONE_MATCH) => {Any / (EntityTag)+} test_if_none_match { - test_header!(test1, vec![b"\"xyzzy\""]); - test_header!(test2, vec![b"W/\"xyzzy\""]); - test_header!(test3, vec![b"\"xyzzy\", \"r2d2xxxx\", \"c3piozzzz\""]); - test_header!(test4, vec![b"W/\"xyzzy\", W/\"r2d2xxxx\", W/\"c3piozzzz\""]); - test_header!(test5, vec![b"*"]); + 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"*"]); } } diff --git a/src/http/header/if_range.rs b/src/http/header/if_range.rs index f34332f22..80e0642bc 100644 --- a/src/http/header/if_range.rs +++ b/src/http/header/if_range.rs @@ -115,7 +115,7 @@ mod test_if_range { use crate::http::header::*; use std::str; - test_header!(test1, vec![b"Sat, 29 Oct 1994 19:43:31 GMT"]); - test_header!(test2, vec![b"\"abc\""]); - test_header!(test3, vec![b"this-is-invalid"], None::); + 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::); } diff --git a/src/http/header/if_unmodified_since.rs b/src/http/header/if_unmodified_since.rs index 8887982aa..26b16b513 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::header! { +crate::__define_common_header! { /// `If-Unmodified-Since` header, defined in /// [RFC7232](http://tools.ietf.org/html/rfc7232#section-3.4) /// @@ -37,6 +37,6 @@ crate::header! { test_if_unmodified_since { // Test case from RFC - test_header!(test1, vec![b"Sat, 29 Oct 1994 19:43:31 GMT"]); + crate::__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 9ed6fcf69..0de2fc06b 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::header! { +crate::__define_common_header! { /// `Last-Modified` header, defined in /// [RFC7232](http://tools.ietf.org/html/rfc7232#section-2.2) /// @@ -36,5 +36,6 @@ crate::header! { test_last_modified { // Test case from RFC - test_header!(test1, vec![b"Sat, 29 Oct 1994 19:43:31 GMT"]);} + crate::__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 new file mode 100644 index 000000000..1718a8663 --- /dev/null +++ b/src/http/header/macros.rs @@ -0,0 +1,300 @@ +#[doc(hidden)] +#[macro_export] +macro_rules! __common_header_deref { + ($from:ty => $to:ty) => { + impl ::std::ops::Deref for $from { + type Target = $to; + + #[inline] + fn deref(&self) -> &$to { + &self.0 + } + } + + impl ::std::ops::DerefMut for $from { + #[inline] + fn deref_mut(&mut self) -> &mut $to { + &mut self.0 + } + } + }; +} + +#[doc(hidden)] +#[macro_export] +macro_rules! __common_header_test_module { + ($id:ident, $tm:ident{$($tf:item)*}) => { + #[allow(unused_imports)] + #[cfg(test)] + mod $tm { + use std::str; + use actix_http::http::Method; + use mime::*; + use $crate::http::header::*; + use super::$id as HeaderField; + $($tf)* + } + } +} + +#[doc(hidden)] +#[macro_export] +macro_rules! __common_header_test { + ($id:ident, $raw:expr) => { + #[test] + fn $id() { + use actix_http::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(' ') + .map(|x| x.to_owned()) + .collect(); + let expected_cmp: Vec = expected + .to_ascii_lowercase() + .split(' ') + .map(|x| x.to_owned()) + .collect(); + assert_eq!(result_cmp.concat(), expected_cmp.concat()); + } + }; + ($id:ident, $raw:expr, $typed:expr) => { + #[test] + fn $id() { + use actix_http::test; + + let a: Vec> = $raw.iter().map(|x| x.to_vec()).collect(); + let mut req = test::TestRequest::default(); + for item in a { + req.insert_header((HeaderField::name(), item)); + } + 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 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(", "); + joined.push_str(s); + } + assert_eq!(format!("{}", typed.unwrap()), joined); + } + } + }; +} + +#[doc(hidden)] +#[macro_export] +macro_rules! __define_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 + // $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)] + pub struct $id(pub Vec<$item>); + crate::__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 + { + $crate::http::header::from_comma_delimited( + msg.headers().get_all(Self::name())).map($id) + } + } + impl std::fmt::Display for $id { + #[inline] + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> ::std::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; + 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::__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 + { + $crate::http::header::from_comma_delimited( + msg.headers().get_all(Self::name())).map($id) + } + } + impl std::fmt::Display for $id { + #[inline] + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::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; + 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::__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) + } + } + impl std::fmt::Display for $id { + #[inline] + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Display::fmt(&self.0, f) + } + } + 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> { + self.0.try_into_value() + } + } + }; + // List header, one or more items with "*" option + ($(#[$a:meta])*($id:ident, $name:expr) => {Any / ($item:ty)+}) => { + $(#[$a])* + #[derive(Clone, Debug, PartialEq)] + pub enum $id { + /// Any value is a match + Any, + /// 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 { + Ok($id::Any) + } else { + Ok($id::Items( + $crate::http::header::from_comma_delimited( + msg.headers().get_all(Self::name()))?)) + } + } + } + impl std::fmt::Display for $id { + #[inline] + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match *self { + $id::Any => f.write_str("*"), + $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; + let mut writer = $crate::http::header::Writer::new(); + let _ = write!(&mut writer, "{}", self); + $crate::http::header::HeaderValue::from_maybe_shared(writer.take()) + } + } + }; + + // optional test module + ($(#[$a:meta])*($id:ident, $name:expr) => ($item:ty)* $tm:ident{$($tf:item)*}) => { + crate::__define_common_header! { + $(#[$a])* + ($id, $name) => ($item)* + } + + crate::__common_header_test_module! { $id, $tm { $($tf)* }} + }; + ($(#[$a:meta])*($id:ident, $n:expr) => ($item:ty)+ $tm:ident{$($tf:item)*}) => { + crate::__define_common_header! { + $(#[$a])* + ($id, $n) => ($item)+ + } + + crate::__common_header_test_module! { $id, $tm { $($tf)* }} + }; + ($(#[$a:meta])*($id:ident, $name:expr) => [$item:ty] $tm:ident{$($tf:item)*}) => { + crate::__define_common_header! { + $(#[$a])* ($id, $name) => [$item] + } + + crate::__common_header_test_module! { $id, $tm { $($tf)* }} + }; + ($(#[$a:meta])*($id:ident, $name:expr) => {Any / ($item:ty)+} $tm:ident{$($tf:item)*}) => { + crate::__define_common_header! { + $(#[$a])* + ($id, $name) => {Any / ($item)+} + } + + crate::__common_header_test_module! { $id, $tm { $($tf)* }} + }; +} diff --git a/src/http/header/mod.rs b/src/http/header/mod.rs index a1c405344..0e5651a77 100644 --- a/src/http/header/mod.rs +++ b/src/http/header/mod.rs @@ -2,28 +2,26 @@ //! //! ## Mime //! -//! Several header fields use MIME values for their contents. Keeping with the -//! strongly-typed theme, the [mime] crate -//! is used, such as `ContentType(pub Mime)`. -#![cfg_attr(rustfmt, rustfmt_skip)] +//! 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; -use bytes::{BytesMut, Bytes}; -pub use actix_http::http::header::*; pub use self::accept_charset::AcceptCharset; +pub use actix_http::http::header::*; //pub use self::accept_encoding::AcceptEncoding; pub use self::accept::Accept; pub use self::accept_language::AcceptLanguage; pub use self::allow::Allow; pub use self::cache_control::{CacheControl, CacheDirective}; -pub use self::content_disposition::{ - ContentDisposition, DispositionParam, DispositionType, -}; +pub use self::content_disposition::{ContentDisposition, DispositionParam, DispositionType}; pub use self::content_language::ContentLanguage; pub use self::content_range::{ContentRange, ContentRangeSpec}; pub use self::content_type::ContentType; pub use self::date::Date; +pub use self::encoding::Encoding; +pub use self::entity::EntityTag; pub use self::etag::ETag; pub use self::expires::Expires; pub use self::if_match::IfMatch; @@ -32,10 +30,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::encoding::Encoding; -pub use self::entity::EntityTag; //pub use self::range::{Range, ByteRangeSpec}; -pub(crate) use actix_http::http::header::{fmt_comma_delimited, from_comma_delimited, from_one_raw_str}; +pub(crate) use actix_http::http::header::{ + fmt_comma_delimited, from_comma_delimited, from_one_raw_str, +}; #[derive(Debug, Default)] struct Writer { @@ -65,309 +63,6 @@ impl fmt::Write for Writer { } } - -#[doc(hidden)] -#[macro_export] -macro_rules! __hyper__deref { - ($from:ty => $to:ty) => { - impl ::std::ops::Deref for $from { - type Target = $to; - - #[inline] - fn deref(&self) -> &$to { - &self.0 - } - } - - impl ::std::ops::DerefMut for $from { - #[inline] - fn deref_mut(&mut self) -> &mut $to { - &mut self.0 - } - } - }; -} - -#[doc(hidden)] -#[macro_export] -macro_rules! __hyper__tm { - ($id:ident, $tm:ident{$($tf:item)*}) => { - #[allow(unused_imports)] - #[cfg(test)] - mod $tm{ - use std::str; - use actix_http::http::Method; - use mime::*; - use $crate::http::header::*; - use super::$id as HeaderField; - $($tf)* - } - - } -} - -#[doc(hidden)] -#[macro_export] -macro_rules! test_header { - ($id:ident, $raw:expr) => { - #[test] - fn $id() { - use actix_http::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(' ') - .map(|x| x.to_owned()) - .collect(); - let expected_cmp: Vec = expected - .to_ascii_lowercase() - .split(' ') - .map(|x| x.to_owned()) - .collect(); - assert_eq!(result_cmp.concat(), expected_cmp.concat()); - } - }; - ($id:ident, $raw:expr, $typed:expr) => { - #[test] - fn $id() { - use actix_http::test; - - let a: Vec> = $raw.iter().map(|x| x.to_vec()).collect(); - let mut req = test::TestRequest::default(); - for item in a { - req.insert_header((HeaderField::name(), item)); - } - 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 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(", "); - joined.push_str(s); - } - assert_eq!(format!("{}", typed.unwrap()), joined); - } - } - }; -} - -#[doc(hidden)] -#[macro_export] -macro_rules! header { - // $a: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)] - pub struct $id(pub Vec<$item>); - __hyper__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 - { - $crate::http::header::from_comma_delimited( - msg.headers().get_all(Self::name())).map($id) - } - } - impl std::fmt::Display for $id { - #[inline] - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> ::std::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; - 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>); - __hyper__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 - { - $crate::http::header::from_comma_delimited( - msg.headers().get_all(Self::name())).map($id) - } - } - impl std::fmt::Display for $id { - #[inline] - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::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; - 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); - __hyper__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) - } - } - impl std::fmt::Display for $id { - #[inline] - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - std::fmt::Display::fmt(&self.0, f) - } - } - 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> { - self.0.try_into_value() - } - } - }; - // List header, one or more items with "*" option - ($(#[$a:meta])*($id:ident, $name:expr) => {Any / ($item:ty)+}) => { - $(#[$a])* - #[derive(Clone, Debug, PartialEq)] - pub enum $id { - /// Any value is a match - Any, - /// 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 { - Ok($id::Any) - } else { - Ok($id::Items( - $crate::http::header::from_comma_delimited( - msg.headers().get_all(Self::name()))?)) - } - } - } - impl std::fmt::Display for $id { - #[inline] - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match *self { - $id::Any => f.write_str("*"), - $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; - let mut writer = $crate::http::header::Writer::new(); - let _ = write!(&mut writer, "{}", self); - $crate::http::header::HeaderValue::from_maybe_shared(writer.take()) - } - } - }; - - // optional test module - ($(#[$a:meta])*($id:ident, $name:expr) => ($item:ty)* $tm:ident{$($tf:item)*}) => { - header! { - $(#[$a])* - ($id, $name) => ($item)* - } - - __hyper__tm! { $id, $tm { $($tf)* }} - }; - ($(#[$a:meta])*($id:ident, $n:expr) => ($item:ty)+ $tm:ident{$($tf:item)*}) => { - header! { - $(#[$a])* - ($id, $n) => ($item)+ - } - - __hyper__tm! { $id, $tm { $($tf)* }} - }; - ($(#[$a:meta])*($id:ident, $name:expr) => [$item:ty] $tm:ident{$($tf:item)*}) => { - header! { - $(#[$a])* ($id, $name) => [$item] - } - - __hyper__tm! { $id, $tm { $($tf)* }} - }; - ($(#[$a:meta])*($id:ident, $name:expr) => {Any / ($item:ty)+} $tm:ident{$($tf:item)*}) => { - header! { - $(#[$a])* - ($id, $name) => {Any / ($item)+} - } - - __hyper__tm! { $id, $tm { $($tf)* }} - }; -} - mod accept_charset; // mod accept_encoding; mod accept; @@ -379,6 +74,8 @@ mod content_language; mod content_range; mod content_type; mod date; +mod encoding; +mod entity; mod etag; mod expires; mod if_match; @@ -387,5 +84,4 @@ mod if_none_match; mod if_range; mod if_unmodified_since; mod last_modified; -mod encoding; -mod entity; +mod macros; From d8f56eee3ece2595032512e294c5ead2a7ad359c Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Fri, 16 Apr 2021 20:28:21 +0100 Subject: [PATCH 162/339] bump service to stable v2 --- Cargo.toml | 2 +- actix-files/Cargo.toml | 2 +- actix-http-test/Cargo.toml | 2 +- actix-http/Cargo.toml | 2 +- actix-http/src/h1/service.rs | 42 ++++++++------- actix-http/src/h2/service.rs | 60 ++++++++++------------ actix-http/src/service.rs | 71 +++++++++++++------------- actix-test/Cargo.toml | 2 +- awc/Cargo.toml | 2 +- awc/tests/test_client.rs | 12 ++--- awc/tests/test_ssl_client.rs | 4 +- src/http/header/cache_control.rs | 8 +-- src/http/header/content_disposition.rs | 46 +++++++---------- src/http/header/content_range.rs | 7 +-- src/http/header/if_range.rs | 14 +++-- src/server.rs | 42 +++++++-------- 16 files changed, 144 insertions(+), 174 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index b8b18a84c..c01111020 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -61,7 +61,7 @@ actix-macros = "0.2.0" actix-router = "0.2.7" actix-rt = "2.2" actix-server = "2.0.0-beta.3" -actix-service = "2.0.0-beta.4" +actix-service = "2.0.0" actix-utils = "3.0.0-beta.4" actix-tls = { version = "3.0.0-beta.5", default-features = false, optional = true } diff --git a/actix-files/Cargo.toml b/actix-files/Cargo.toml index 84d8dd958..3b25806ad 100644 --- a/actix-files/Cargo.toml +++ b/actix-files/Cargo.toml @@ -18,7 +18,7 @@ path = "src/lib.rs" [dependencies] actix-web = { version = "4.0.0-beta.5", default-features = false } -actix-service = "2.0.0-beta.4" +actix-service = "2.0.0" actix-utils = "3.0.0-beta.4" askama_escape = "0.10" diff --git a/actix-http-test/Cargo.toml b/actix-http-test/Cargo.toml index e1dd4c4e8..836510dce 100644 --- a/actix-http-test/Cargo.toml +++ b/actix-http-test/Cargo.toml @@ -29,7 +29,7 @@ default = [] openssl = ["tls-openssl", "awc/openssl"] [dependencies] -actix-service = "2.0.0-beta.4" +actix-service = "2.0.0" actix-codec = "0.4.0-beta.1" actix-tls = "3.0.0-beta.5" actix-utils = "3.0.0-beta.4" diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 361cae62f..13557c6a9 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -38,7 +38,7 @@ compress = ["flate2", "brotli2"] trust-dns = ["trust-dns-resolver"] [dependencies] -actix-service = "2.0.0-beta.4" +actix-service = "2.0.0" actix-codec = "0.4.0-beta.1" actix-utils = "3.0.0-beta.4" actix-rt = "2.2" diff --git a/actix-http/src/h1/service.rs b/actix-http/src/h1/service.rs index a98f6bd0a..916643a18 100644 --- a/actix-http/src/h1/service.rs +++ b/actix-http/src/h1/service.rs @@ -5,7 +5,9 @@ use std::{fmt, net}; use actix_codec::{AsyncRead, AsyncWrite, Framed}; use actix_rt::net::TcpStream; -use actix_service::{pipeline_factory, IntoServiceFactory, Service, ServiceFactory}; +use actix_service::{ + fn_service, IntoServiceFactory, Service, ServiceFactory, ServiceFactoryExt as _, +}; use actix_utils::future::ready; use futures_core::future::LocalBoxFuture; @@ -82,7 +84,7 @@ where Error = DispatchError, InitError = (), > { - pipeline_factory(|io: TcpStream| { + fn_service(|io: TcpStream| { let peer_addr = io.peer_addr().ok(); ready(Ok((io, peer_addr))) }) @@ -132,16 +134,14 @@ mod openssl { Error = TlsError, InitError = (), > { - pipeline_factory( - Acceptor::new(acceptor) - .map_err(TlsError::Tls) - .map_init_err(|_| panic!()), - ) - .and_then(|io: TlsStream| { - let peer_addr = io.get_ref().peer_addr().ok(); - ready(Ok((io, peer_addr))) - }) - .and_then(self.map_err(TlsError::Service)) + Acceptor::new(acceptor) + .map_err(TlsError::Tls) + .map_init_err(|_| panic!()) + .and_then(|io: TlsStream| { + let peer_addr = io.get_ref().peer_addr().ok(); + ready(Ok((io, peer_addr))) + }) + .and_then(self.map_err(TlsError::Service)) } } } @@ -190,16 +190,14 @@ mod rustls { Error = TlsError, InitError = (), > { - pipeline_factory( - Acceptor::new(config) - .map_err(TlsError::Tls) - .map_init_err(|_| panic!()), - ) - .and_then(|io: TlsStream| { - let peer_addr = io.get_ref().0.peer_addr().ok(); - ready(Ok((io, peer_addr))) - }) - .and_then(self.map_err(TlsError::Service)) + Acceptor::new(config) + .map_err(TlsError::Tls) + .map_init_err(|_| panic!()) + .and_then(|io: TlsStream| { + let peer_addr = io.get_ref().0.peer_addr().ok(); + ready(Ok((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 8f202e752..1a0b8c7f5 100644 --- a/actix-http/src/h2/service.rs +++ b/actix-http/src/h2/service.rs @@ -7,8 +7,8 @@ use std::{net, rc::Rc}; use actix_codec::{AsyncRead, AsyncWrite}; use actix_rt::net::TcpStream; use actix_service::{ - fn_factory, fn_service, pipeline_factory, IntoServiceFactory, Service, - ServiceFactory, + fn_factory, fn_service, IntoServiceFactory, Service, ServiceFactory, + ServiceFactoryExt as _, }; use actix_utils::future::ready; use bytes::Bytes; @@ -81,12 +81,12 @@ where Error = DispatchError, InitError = S::InitError, > { - pipeline_factory(fn_factory(|| { + fn_factory(|| { ready(Ok::<_, S::InitError>(fn_service(|io: TcpStream| { let peer_addr = io.peer_addr().ok(); ready(Ok::<_, DispatchError>((io, peer_addr))) }))) - })) + }) .and_then(self) } } @@ -119,20 +119,18 @@ mod openssl { Error = TlsError, InitError = S::InitError, > { - pipeline_factory( - 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))) - }, - ))) - })) - .and_then(self.map_err(TlsError::Service)) + 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))) + }, + ))) + })) + .and_then(self.map_err(TlsError::Service)) } } } @@ -168,20 +166,18 @@ mod rustls { let protos = vec!["h2".to_string().into()]; config.set_protocols(&protos); - pipeline_factory( - 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))) - }, - ))) - })) - .and_then(self.map_err(TlsError::Service)) + 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))) + }, + ))) + })) + .and_then(self.map_err(TlsError::Service)) } } } diff --git a/actix-http/src/service.rs b/actix-http/src/service.rs index 67a3ec42e..ff4b49f1d 100644 --- a/actix-http/src/service.rs +++ b/actix-http/src/service.rs @@ -10,7 +10,9 @@ use std::{ use actix_codec::{AsyncRead, AsyncWrite, Framed}; use actix_rt::net::TcpStream; -use actix_service::{pipeline_factory, IntoServiceFactory, Service, ServiceFactory}; +use actix_service::{ + fn_service, IntoServiceFactory, Service, ServiceFactory, ServiceFactoryExt as _, +}; use bytes::Bytes; use futures_core::{future::LocalBoxFuture, ready}; use h2::server::{handshake, Handshake}; @@ -180,7 +182,7 @@ where Error = DispatchError, InitError = (), > { - pipeline_factory(|io: TcpStream| async { + fn_service(|io: TcpStream| async { let peer_addr = io.peer_addr().ok(); Ok((io, Protocol::Http1, peer_addr)) }) @@ -232,25 +234,23 @@ mod openssl { Error = TlsError, InitError = (), > { - pipeline_factory( - Acceptor::new(acceptor) - .map_err(TlsError::Tls) - .map_init_err(|_| panic!()), - ) - .and_then(|io: TlsStream| async { - let proto = if let Some(protos) = io.ssl().selected_alpn_protocol() { - if protos.windows(2).any(|window| window == b"h2") { - Protocol::Http2 + Acceptor::new(acceptor) + .map_err(TlsError::Tls) + .map_init_err(|_| panic!()) + .and_then(|io: TlsStream| async { + let proto = if let Some(protos) = io.ssl().selected_alpn_protocol() { + if protos.windows(2).any(|window| window == b"h2") { + Protocol::Http2 + } else { + Protocol::Http1 + } } else { Protocol::Http1 - } - } else { - Protocol::Http1 - }; - let peer_addr = io.get_ref().peer_addr().ok(); - Ok((io, proto, peer_addr)) - }) - .and_then(self.map_err(TlsError::Service)) + }; + let peer_addr = io.get_ref().peer_addr().ok(); + Ok((io, proto, peer_addr)) + }) + .and_then(self.map_err(TlsError::Service)) } } } @@ -304,25 +304,24 @@ mod rustls { let protos = vec!["h2".to_string().into(), "http/1.1".to_string().into()]; config.set_protocols(&protos); - pipeline_factory( - 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() { - if protos.windows(2).any(|window| window == b"h2") { - Protocol::Http2 + 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() + { + if protos.windows(2).any(|window| window == b"h2") { + Protocol::Http2 + } else { + Protocol::Http1 + } } else { Protocol::Http1 - } - } else { - Protocol::Http1 - }; - let peer_addr = io.get_ref().0.peer_addr().ok(); - Ok((io, proto, peer_addr)) - }) - .and_then(self.map_err(TlsError::Service)) + }; + let peer_addr = io.get_ref().0.peer_addr().ok(); + Ok((io, proto, peer_addr)) + }) + .and_then(self.map_err(TlsError::Service)) } } } diff --git a/actix-test/Cargo.toml b/actix-test/Cargo.toml index db7a50c5e..730fb7bed 100644 --- a/actix-test/Cargo.toml +++ b/actix-test/Cargo.toml @@ -22,7 +22,7 @@ openssl = ["tls-openssl", "actix-http/openssl"] actix-codec = "0.4.0-beta.1" actix-http = "3.0.0-beta.5" actix-http-test = { version = "3.0.0-beta.4", features = [] } -actix-service = "2.0.0-beta.4" +actix-service = "2.0.0" actix-utils = "3.0.0-beta.2" actix-web = { version = "4.0.0-beta.5", default-features = false, features = ["cookies"] } actix-rt = "2.1" diff --git a/awc/Cargo.toml b/awc/Cargo.toml index 27d8bdfbc..28d93ad36 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -48,7 +48,7 @@ trust-dns = ["actix-http/trust-dns"] [dependencies] actix-codec = "0.4.0-beta.1" -actix-service = "2.0.0-beta.4" +actix-service = "2.0.0" actix-http = "3.0.0-beta.5" actix-rt = { version = "2.1", default-features = false } diff --git a/awc/tests/test_client.rs b/awc/tests/test_client.rs index f1d29f0bc..615789fb3 100644 --- a/awc/tests/test_client.rs +++ b/awc/tests/test_client.rs @@ -20,7 +20,7 @@ use actix_http::{ HttpService, }; use actix_http_test::test_server; -use actix_service::{map_config, pipeline_factory}; +use actix_service::{fn_service, map_config, ServiceFactoryExt as _}; use actix_web::{ dev::{AppConfig, BodyEncoding}, http::header, @@ -239,7 +239,7 @@ async fn test_connection_reuse() { let srv = test_server(move || { let num2 = num2.clone(); - pipeline_factory(move |io| { + fn_service(move |io| { num2.fetch_add(1, Ordering::Relaxed); ok(io) }) @@ -276,7 +276,7 @@ async fn test_connection_force_close() { let srv = test_server(move || { let num2 = num2.clone(); - pipeline_factory(move |io| { + fn_service(move |io| { num2.fetch_add(1, Ordering::Relaxed); ok(io) }) @@ -313,7 +313,7 @@ async fn test_connection_server_close() { let srv = test_server(move || { let num2 = num2.clone(); - pipeline_factory(move |io| { + fn_service(move |io| { num2.fetch_add(1, Ordering::Relaxed); ok(io) }) @@ -353,7 +353,7 @@ async fn test_connection_wait_queue() { let srv = test_server(move || { let num2 = num2.clone(); - pipeline_factory(move |io| { + fn_service(move |io| { num2.fetch_add(1, Ordering::Relaxed); ok(io) }) @@ -401,7 +401,7 @@ async fn test_connection_wait_queue_force_close() { let srv = test_server(move || { let num2 = num2.clone(); - pipeline_factory(move |io| { + fn_service(move |io| { num2.fetch_add(1, Ordering::Relaxed); ok(io) }) diff --git a/awc/tests/test_ssl_client.rs b/awc/tests/test_ssl_client.rs index 502223401..57305e49a 100644 --- a/awc/tests/test_ssl_client.rs +++ b/awc/tests/test_ssl_client.rs @@ -7,7 +7,7 @@ use std::sync::Arc; use actix_http::HttpService; use actix_http_test::test_server; -use actix_service::{map_config, pipeline_factory, ServiceFactoryExt}; +use actix_service::{map_config, fn_service, ServiceFactoryExt}; use actix_utils::future::ok; use actix_web::http::Version; use actix_web::{dev::AppConfig, web, App, HttpResponse}; @@ -48,7 +48,7 @@ async fn test_connection_reuse_h2() { let srv = test_server(move || { let num2 = num2.clone(); - pipeline_factory(move |io| { + fn_service(move |io| { num2.fetch_add(1, Ordering::Relaxed); ok(io) }) diff --git a/src/http/header/cache_control.rs b/src/http/header/cache_control.rs index 891ba7c79..620c576ae 100644 --- a/src/http/header/cache_control.rs +++ b/src/http/header/cache_control.rs @@ -1,9 +1,7 @@ use std::fmt::{self, Write}; use std::str::FromStr; -use super::{ - fmt_comma_delimited, from_comma_delimited, Header, IntoHeaderValue, Writer, -}; +use super::{fmt_comma_delimited, from_comma_delimited, Header, IntoHeaderValue, Writer}; use crate::http::header; @@ -176,9 +174,7 @@ impl FromStr for CacheDirective { ("max-stale", secs) => secs.parse().map(MaxStale).map_err(Some), ("min-fresh", secs) => secs.parse().map(MinFresh).map_err(Some), ("s-maxage", secs) => secs.parse().map(SMaxAge).map_err(Some), - (left, right) => { - Ok(Extension(left.to_owned(), Some(right.to_owned()))) - } + (left, right) => Ok(Extension(left.to_owned(), Some(right.to_owned()))), } } Some(_) => Err(None), diff --git a/src/http/header/content_disposition.rs b/src/http/header/content_disposition.rs index 3dea0997b..509d68968 100644 --- a/src/http/header/content_disposition.rs +++ b/src/http/header/content_disposition.rs @@ -10,8 +10,8 @@ use once_cell::sync::Lazy; use regex::Regex; use std::fmt::{self, Write}; -use crate::http::header; use super::{ExtendedValue, Header, IntoHeaderValue, Writer}; +use crate::http::header; /// Split at the index of the first `needle` if it exists or at the end. fn split_once(haystack: &str, needle: char) -> (&str, &str) { @@ -403,11 +403,7 @@ impl ContentDisposition { /// Returns `true` if it is [`Ext`](DispositionType::Ext) and the `disp_type` matches. pub fn is_ext>(&self, disp_type: T) -> bool { match self.disposition { - DispositionType::Ext(ref t) - if t.eq_ignore_ascii_case(disp_type.as_ref()) => - { - true - } + DispositionType::Ext(ref t) if t.eq_ignore_ascii_case(disp_type.as_ref()) => true, _ => false, } } @@ -521,7 +517,8 @@ 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()); + 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) => { @@ -618,8 +615,8 @@ mod tests { charset: Charset::Ext(String::from("UTF-8")), language_tag: None, value: vec![ - 0xc2, 0xa3, 0x20, b'a', b'n', b'd', 0x20, 0xe2, 0x82, 0xac, 0x20, - b'r', b'a', b't', b'e', b's', + 0xc2, 0xa3, 0x20, b'a', b'n', b'd', 0x20, 0xe2, 0x82, 0xac, 0x20, b'r', + b'a', b't', b'e', b's', ], })], }; @@ -635,8 +632,8 @@ mod tests { charset: Charset::Ext(String::from("UTF-8")), language_tag: None, value: vec![ - 0xc2, 0xa3, 0x20, b'a', b'n', b'd', 0x20, 0xe2, 0x82, 0xac, 0x20, - b'r', b'a', b't', b'e', b's', + 0xc2, 0xa3, 0x20, b'a', b'n', b'd', 0x20, 0xe2, 0x82, 0xac, 0x20, b'r', + b'a', b't', b'e', b's', ], })], }; @@ -698,26 +695,22 @@ mod tests { #[test] fn test_from_raw_only_disp() { - let a = ContentDisposition::from_raw(&HeaderValue::from_static("attachment")) - .unwrap(); + let a = ContentDisposition::from_raw(&HeaderValue::from_static("attachment")).unwrap(); let b = ContentDisposition { disposition: DispositionType::Attachment, parameters: vec![], }; assert_eq!(a, b); - let a = - ContentDisposition::from_raw(&HeaderValue::from_static("inline ;")).unwrap(); + let a = ContentDisposition::from_raw(&HeaderValue::from_static("inline ;")).unwrap(); let b = ContentDisposition { disposition: DispositionType::Inline, parameters: vec![], }; assert_eq!(a, b); - let a = ContentDisposition::from_raw(&HeaderValue::from_static( - "unknown-disp-param", - )) - .unwrap(); + let a = ContentDisposition::from_raw(&HeaderValue::from_static("unknown-disp-param")) + .unwrap(); let b = ContentDisposition { disposition: DispositionType::Ext(String::from("unknown-disp-param")), parameters: vec![], @@ -756,8 +749,8 @@ mod tests { Mainstream browsers like Firefox (gecko) and Chrome use UTF-8 directly as above. (And now, only UTF-8 is handled by this implementation.) */ - let a = HeaderValue::from_str("form-data; name=upload; filename=\"文件.webp\"") - .unwrap(); + let a = + HeaderValue::from_str("form-data; name=upload; filename=\"文件.webp\"").unwrap(); let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); let b = ContentDisposition { disposition: DispositionType::FormData, @@ -808,8 +801,7 @@ mod tests { #[test] fn test_from_raw_semicolon() { - let a = - HeaderValue::from_static("form-data; filename=\"A semicolon here;.pdf\""); + let a = HeaderValue::from_static("form-data; filename=\"A semicolon here;.pdf\""); let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); let b = ContentDisposition { disposition: DispositionType::FormData, @@ -845,9 +837,8 @@ mod tests { }; assert_eq!(a, b); - let a = HeaderValue::from_static( - "form-data; name=photo; filename=\"%74%65%73%74.png\"", - ); + let a = + HeaderValue::from_static("form-data; name=photo; filename=\"%74%65%73%74.png\""); let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); let b = ContentDisposition { disposition: DispositionType::FormData, @@ -892,8 +883,7 @@ mod tests { #[test] fn test_display_extended() { - let as_string = - "attachment; filename*=UTF-8'en'%C2%A3%20and%20%E2%82%AC%20rates"; + let as_string = "attachment; filename*=UTF-8'en'%C2%A3%20and%20%E2%82%AC%20rates"; let a = HeaderValue::from_static(as_string); let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); let display_rendered = format!("{}", a); diff --git a/src/http/header/content_range.rs b/src/http/header/content_range.rs index e3a8450cb..ba0d51742 100644 --- a/src/http/header/content_range.rs +++ b/src/http/header/content_range.rs @@ -1,10 +1,8 @@ use std::fmt::{self, Display, Write}; use std::str::FromStr; +use super::{HeaderValue, IntoHeaderValue, InvalidHeaderValue, Writer, CONTENT_RANGE}; use crate::error::ParseError; -use super::{ - HeaderValue, IntoHeaderValue, InvalidHeaderValue, Writer, CONTENT_RANGE, -}; crate::__define_common_header! { /// `Content-Range` header, defined in @@ -141,8 +139,7 @@ impl FromStr for ContentRangeSpec { } else { let (first_byte, last_byte) = split_in_two(range, '-').ok_or(ParseError::Header)?; - let first_byte = - first_byte.parse().map_err(|_| ParseError::Header)?; + let first_byte = first_byte.parse().map_err(|_| ParseError::Header)?; let last_byte = last_byte.parse().map_err(|_| ParseError::Header)?; if last_byte < first_byte { return Err(ParseError::Header); diff --git a/src/http/header/if_range.rs b/src/http/header/if_range.rs index 80e0642bc..9612405e8 100644 --- a/src/http/header/if_range.rs +++ b/src/http/header/if_range.rs @@ -1,11 +1,11 @@ use std::fmt::{self, Display, Write}; -use crate::http::header; -use crate::error::ParseError; use super::{ - from_one_raw_str, EntityTag, Header, HeaderName, HeaderValue, HttpDate, - IntoHeaderValue, InvalidHeaderValue, Writer, + from_one_raw_str, EntityTag, Header, HeaderName, HeaderValue, HttpDate, IntoHeaderValue, + InvalidHeaderValue, Writer, }; +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) @@ -76,13 +76,11 @@ impl Header for IfRange { where T: HttpMessage, { - let etag: Result = - from_one_raw_str(msg.headers().get(&header::IF_RANGE)); + let etag: Result = from_one_raw_str(msg.headers().get(&header::IF_RANGE)); if let Ok(etag) = etag { return Ok(IfRange::EntityTag(etag)); } - let date: Result = - from_one_raw_str(msg.headers().get(&header::IF_RANGE)); + let date: Result = from_one_raw_str(msg.headers().get(&header::IF_RANGE)); if let Ok(date) = date { return Ok(IfRange::Date(date)); } diff --git a/src/server.rs b/src/server.rs index e3a9f6e01..6577f4d1f 100644 --- a/src/server.rs +++ b/src/server.rs @@ -489,7 +489,7 @@ where pub fn listen_uds(mut self, lst: std::os::unix::net::UnixListener) -> io::Result { use actix_http::Protocol; use actix_rt::net::UnixStream; - use actix_service::pipeline_factory; + use actix_service::{fn_service, ServiceFactoryExt as _}; let cfg = self.config.clone(); let factory = self.factory.clone(); @@ -511,22 +511,19 @@ where socket_addr, ); - pipeline_factory(|io: UnixStream| async { Ok((io, Protocol::Http1, None)) }) - .and_then({ - let svc = HttpService::build() - .keep_alive(c.keep_alive) - .client_timeout(c.client_timeout); + fn_service(|io: UnixStream| async { Ok((io, Protocol::Http1, None)) }).and_then({ + let 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 - }; + 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 + }; - svc.finish(map_config(factory(), move |_| config.clone())) - }) + svc.finish(map_config(factory(), move |_| config.clone())) + }) })?; Ok(self) } @@ -539,7 +536,7 @@ where { use actix_http::Protocol; use actix_rt::net::UnixStream; - use actix_service::pipeline_factory; + use actix_service::{fn_service, ServiceFactoryExt as _}; let cfg = self.config.clone(); let factory = self.factory.clone(); @@ -560,13 +557,12 @@ where c.host.clone().unwrap_or_else(|| format!("{}", socket_addr)), socket_addr, ); - pipeline_factory(|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())), - ) + 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())), + ) }, )?; Ok(self) From 2449f2555cec8fc90c91a4ac1b160f96436b11ce Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Fri, 16 Apr 2021 20:48:37 +0100 Subject: [PATCH 163/339] missed one pipeline_factory --- awc/tests/test_rustls_client.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/awc/tests/test_rustls_client.rs b/awc/tests/test_rustls_client.rs index 080eaf792..bc811c046 100644 --- a/awc/tests/test_rustls_client.rs +++ b/awc/tests/test_rustls_client.rs @@ -12,7 +12,7 @@ use std::{ use actix_http::HttpService; use actix_http_test::test_server; -use actix_service::{map_config, pipeline_factory, ServiceFactoryExt}; +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}; @@ -57,7 +57,7 @@ async fn test_connection_reuse_h2() { let srv = test_server(move || { let num2 = num2.clone(); - pipeline_factory(move |io| { + fn_service(move |io| { num2.fetch_add(1, Ordering::Relaxed); ok(io) }) From 879a4cbcd8fc2d65e3961738904c35f41f0e4c55 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Fri, 16 Apr 2021 23:21:02 +0100 Subject: [PATCH 164/339] re-export ready boilerplate macros in dev --- actix-web-codegen/tests/test_macro.rs | 26 ++++++++++++++------------ src/lib.rs | 2 +- src/middleware/compat.rs | 4 +--- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/actix-web-codegen/tests/test_macro.rs b/actix-web-codegen/tests/test_macro.rs index b983e6b1d..6b08c409c 100644 --- a/actix-web-codegen/tests/test_macro.rs +++ b/actix-web-codegen/tests/test_macro.rs @@ -1,11 +1,15 @@ use std::future::Future; -use std::task::{Context, Poll}; use actix_utils::future::{ok, Ready}; -use actix_web::dev::{Service, ServiceRequest, ServiceResponse, Transform}; -use actix_web::http::header::{HeaderName, HeaderValue}; -use actix_web::http::StatusCode; -use actix_web::{http, web::Path, App, Error, HttpResponse, Responder}; +use actix_web::{ + dev::{Service, ServiceRequest, ServiceResponse, Transform}, + http::{ + self, + header::{HeaderName, HeaderValue}, + StatusCode, + }, + web, App, Error, HttpResponse, Responder, +}; use actix_web_codegen::{connect, delete, get, head, options, patch, post, put, route, trace}; use futures_core::future::LocalBoxFuture; @@ -66,17 +70,17 @@ fn auto_sync() -> impl Future> { } #[put("/test/{param}")] -async fn put_param_test(_: Path) -> impl Responder { +async fn put_param_test(_: web::Path) -> impl Responder { HttpResponse::Created() } #[delete("/test/{param}")] -async fn delete_param_test(_: Path) -> impl Responder { +async fn delete_param_test(_: web::Path) -> impl Responder { HttpResponse::NoContent() } #[get("/test/{param}")] -async fn get_param_test(_: Path) -> impl Responder { +async fn get_param_test(_: web::Path) -> impl Responder { HttpResponse::Ok() } @@ -125,9 +129,7 @@ where type Error = Error; type Future = LocalBoxFuture<'static, Result>; - fn poll_ready(&self, cx: &mut Context<'_>) -> Poll> { - self.service.poll_ready(cx) - } + actix_web::dev::forward_ready!(service); fn call(&self, req: ServiceRequest) -> Self::Future { let fut = self.service.call(req); @@ -144,7 +146,7 @@ where } #[get("/test/wrap", wrap = "ChangeStatusCode")] -async fn get_wrap(_: Path) -> impl Responder { +async fn get_wrap(_: web::Path) -> impl Responder { // panic!("actually never gets called because path failed to extract"); HttpResponse::Ok() } diff --git a/src/lib.rs b/src/lib.rs index 54db969df..cf1bfa590 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -145,7 +145,7 @@ 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::{Service, Transform}; + pub use actix_service::{always_ready, forward_ready, Service, Transform}; pub(crate) fn insert_slash(mut patterns: Vec) -> Vec { for path in &mut patterns { diff --git a/src/middleware/compat.rs b/src/middleware/compat.rs index 0d197ba80..0e3a4f2b7 100644 --- a/src/middleware/compat.rs +++ b/src/middleware/compat.rs @@ -80,9 +80,7 @@ where type Error = Error; type Future = CompatMiddlewareFuture; - fn poll_ready(&self, cx: &mut Context<'_>) -> Poll> { - self.service.poll_ready(cx).map_err(From::from) - } + actix_service::forward_ready!(service); fn call(&self, req: Req) -> Self::Future { let fut = self.service.call(req); From 5747f8473689b46bb46d6ad9c314263cc59680fa Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sat, 17 Apr 2021 02:07:33 +0100 Subject: [PATCH 165/339] bump utils to stable v3 --- Cargo.toml | 2 +- actix-files/Cargo.toml | 2 +- actix-http-test/Cargo.toml | 2 +- actix-http/Cargo.toml | 2 +- actix-multipart/Cargo.toml | 2 +- actix-test/Cargo.toml | 2 +- actix-web-codegen/Cargo.toml | 2 +- awc/Cargo.toml | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index c01111020..447a0b197 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -62,7 +62,7 @@ actix-router = "0.2.7" actix-rt = "2.2" actix-server = "2.0.0-beta.3" actix-service = "2.0.0" -actix-utils = "3.0.0-beta.4" +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" diff --git a/actix-files/Cargo.toml b/actix-files/Cargo.toml index 3b25806ad..b28c0c62d 100644 --- a/actix-files/Cargo.toml +++ b/actix-files/Cargo.toml @@ -19,7 +19,7 @@ path = "src/lib.rs" [dependencies] actix-web = { version = "4.0.0-beta.5", default-features = false } actix-service = "2.0.0" -actix-utils = "3.0.0-beta.4" +actix-utils = "3.0.0" askama_escape = "0.10" bitflags = "1" diff --git a/actix-http-test/Cargo.toml b/actix-http-test/Cargo.toml index 836510dce..2374984a2 100644 --- a/actix-http-test/Cargo.toml +++ b/actix-http-test/Cargo.toml @@ -32,7 +32,7 @@ openssl = ["tls-openssl", "awc/openssl"] actix-service = "2.0.0" actix-codec = "0.4.0-beta.1" actix-tls = "3.0.0-beta.5" -actix-utils = "3.0.0-beta.4" +actix-utils = "3.0.0" actix-rt = "2.2" actix-server = "2.0.0-beta.3" awc = { version = "3.0.0-beta.4", default-features = false } diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 13557c6a9..6189a1ae6 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -40,7 +40,7 @@ trust-dns = ["trust-dns-resolver"] [dependencies] actix-service = "2.0.0" actix-codec = "0.4.0-beta.1" -actix-utils = "3.0.0-beta.4" +actix-utils = "3.0.0" actix-rt = "2.2" actix-tls = { version = "3.0.0-beta.5", features = ["accept", "connect"] } diff --git a/actix-multipart/Cargo.toml b/actix-multipart/Cargo.toml index 77558fcc5..c4cb42450 100644 --- a/actix-multipart/Cargo.toml +++ b/actix-multipart/Cargo.toml @@ -17,7 +17,7 @@ path = "src/lib.rs" [dependencies] actix-web = { version = "4.0.0-beta.5", default-features = false } -actix-utils = "3.0.0-beta.4" +actix-utils = "3.0.0" bytes = "1" derive_more = "0.99.5" diff --git a/actix-test/Cargo.toml b/actix-test/Cargo.toml index 730fb7bed..e280516c3 100644 --- a/actix-test/Cargo.toml +++ b/actix-test/Cargo.toml @@ -23,7 +23,7 @@ actix-codec = "0.4.0-beta.1" actix-http = "3.0.0-beta.5" actix-http-test = { version = "3.0.0-beta.4", features = [] } actix-service = "2.0.0" -actix-utils = "3.0.0-beta.2" +actix-utils = "3.0.0" actix-web = { version = "4.0.0-beta.5", default-features = false, features = ["cookies"] } actix-rt = "2.1" awc = { version = "3.0.0-beta.4", default-features = false, features = ["cookies"] } diff --git a/actix-web-codegen/Cargo.toml b/actix-web-codegen/Cargo.toml index 04c988392..653ce038a 100644 --- a/actix-web-codegen/Cargo.toml +++ b/actix-web-codegen/Cargo.toml @@ -21,7 +21,7 @@ proc-macro2 = "1" [dev-dependencies] actix-rt = "2.2" actix-test = "0.1.0-beta.1" -actix-utils = "3.0.0-beta.4" +actix-utils = "3.0.0" actix-web = "4.0.0-beta.5" futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] } diff --git a/awc/Cargo.toml b/awc/Cargo.toml index 28d93ad36..e1d2699df 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -73,7 +73,7 @@ tls-rustls = { version = "0.19.0", package = "rustls", optional = true, features actix-web = { version = "4.0.0-beta.5", features = ["openssl"] } actix-http = { version = "3.0.0-beta.5", features = ["openssl"] } actix-http-test = { version = "3.0.0-beta.4", features = ["openssl"] } -actix-utils = "3.0.0-beta.4" +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.1", features = ["openssl", "rustls"] } From f743e885a33720adbc2bdca4808184d0a5868911 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sat, 17 Apr 2021 15:24:18 +0100 Subject: [PATCH 166/339] prepare http release 3.0.0-beta.6 --- 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 ++-- 9 files changed, 13 insertions(+), 10 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 447a0b197..a17de52c0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -66,7 +66,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.5" +actix-http = "3.0.0-beta.6" ahash = "0.7" bytes = "1" diff --git a/actix-http-test/Cargo.toml b/actix-http-test/Cargo.toml index 2374984a2..bfce32540 100644 --- a/actix-http-test/Cargo.toml +++ b/actix-http-test/Cargo.toml @@ -52,4 +52,4 @@ tls-openssl = { version = "0.10.9", package = "openssl", optional = true } [dev-dependencies] actix-web = { version = "4.0.0-beta.5", default-features = false, features = ["cookies"] } -actix-http = "3.0.0-beta.5" +actix-http = "3.0.0-beta.6" diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 84d6617f7..6cf9ff276 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -1,6 +1,9 @@ # Changes ## Unreleased - 2021-xx-xx + + +## 3.0.0-beta.6 - 2021-04-17 ### Added * `impl MessageBody for Pin>`. [#2152] * `Response::{ok, bad_request, not_found, internal_server_error}`. [#2159] diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 6189a1ae6..61f60b79e 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-http" -version = "3.0.0-beta.5" +version = "3.0.0-beta.6" 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 50a17a02f..87eb38e5d 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.5)](https://docs.rs/actix-http/3.0.0-beta.5) +[![Documentation](https://docs.rs/actix-http/badge.svg?version=3.0.0-beta.6)](https://docs.rs/actix-http/3.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) ![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.5/status.svg)](https://deps.rs/crate/actix-http/3.0.0-beta.5) +[![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) [![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/Cargo.toml b/actix-multipart/Cargo.toml index c4cb42450..449e3121f 100644 --- a/actix-multipart/Cargo.toml +++ b/actix-multipart/Cargo.toml @@ -31,6 +31,6 @@ twoway = "0.2" [dev-dependencies] actix-rt = "2.2" -actix-http = "3.0.0-beta.5" +actix-http = "3.0.0-beta.6" tokio = { version = "1", features = ["sync"] } tokio-stream = "0.1" diff --git a/actix-test/Cargo.toml b/actix-test/Cargo.toml index e280516c3..b021db00e 100644 --- a/actix-test/Cargo.toml +++ b/actix-test/Cargo.toml @@ -20,7 +20,7 @@ openssl = ["tls-openssl", "actix-http/openssl"] [dependencies] actix-codec = "0.4.0-beta.1" -actix-http = "3.0.0-beta.5" +actix-http = "3.0.0-beta.6" actix-http-test = { version = "3.0.0-beta.4", features = [] } 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 a539227e4..4871b470d 100644 --- a/actix-web-actors/Cargo.toml +++ b/actix-web-actors/Cargo.toml @@ -18,7 +18,7 @@ path = "src/lib.rs" [dependencies] actix = { version = "0.11.0-beta.3", default-features = false } actix-codec = "0.4.0-beta.1" -actix-http = "3.0.0-beta.5" +actix-http = "3.0.0-beta.6" actix-web = { version = "4.0.0-beta.5", default-features = false } bytes = "1" diff --git a/awc/Cargo.toml b/awc/Cargo.toml index e1d2699df..29f76371c 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -49,7 +49,7 @@ trust-dns = ["actix-http/trust-dns"] [dependencies] actix-codec = "0.4.0-beta.1" actix-service = "2.0.0" -actix-http = "3.0.0-beta.5" +actix-http = "3.0.0-beta.6" actix-rt = { version = "2.1", default-features = false } base64 = "0.13" @@ -71,7 +71,7 @@ tls-rustls = { version = "0.19.0", package = "rustls", optional = true, features [dev-dependencies] actix-web = { version = "4.0.0-beta.5", features = ["openssl"] } -actix-http = { version = "3.0.0-beta.5", features = ["openssl"] } +actix-http = { version = "3.0.0-beta.6", 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" From b2d6b6a70c815689945256b508109b57bdff1d2a Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sat, 17 Apr 2021 15:28:13 +0100 Subject: [PATCH 167/339] prepare web release 4.0.0-beta.6 --- CHANGES.md | 3 +++ Cargo.toml | 11 ++++------- actix-files/Cargo.toml | 4 ++-- actix-http-test/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 +- 9 files changed, 15 insertions(+), 15 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index bf8fc9424..eb5130b99 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,9 @@ # Changes ## Unreleased - 2021-xx-xx + + +## 4.0.0-beta.6 - 2021-04-17 ### Added * `HttpResponse` and `HttpResponseBuilder` structs. [#2065] diff --git a/Cargo.toml b/Cargo.toml index a17de52c0..24d8f54fb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,16 +1,13 @@ [package] name = "actix-web" -version = "4.0.0-beta.5" +version = "4.0.0-beta.6" authors = ["Nikolay Kim "] description = "Actix Web is a powerful, pragmatic, and extremely fast web framework for Rust" -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/" categories = ["network-programming", "asynchronous", - "web-programming::http-server", - "web-programming::websocket"] + "web-programming::http-server", "web-programming::websocket"] +homepage = "https://actix.rs" +repository = "https://github.com/actix/actix-web" license = "MIT OR Apache-2.0" edition = "2018" diff --git a/actix-files/Cargo.toml b/actix-files/Cargo.toml index b28c0c62d..9d87b839b 100644 --- a/actix-files/Cargo.toml +++ b/actix-files/Cargo.toml @@ -17,7 +17,7 @@ name = "actix_files" path = "src/lib.rs" [dependencies] -actix-web = { version = "4.0.0-beta.5", default-features = false } +actix-web = { version = "4.0.0-beta.6", default-features = false } actix-service = "2.0.0" actix-utils = "3.0.0" @@ -34,5 +34,5 @@ percent-encoding = "2.1" [dev-dependencies] actix-rt = "2.2" -actix-web = "4.0.0-beta.5" +actix-web = "4.0.0-beta.6" actix-test = "0.1.0-beta.1" diff --git a/actix-http-test/Cargo.toml b/actix-http-test/Cargo.toml index bfce32540..38e5c4639 100644 --- a/actix-http-test/Cargo.toml +++ b/actix-http-test/Cargo.toml @@ -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.5", default-features = false, features = ["cookies"] } +actix-web = { version = "4.0.0-beta.6", default-features = false, features = ["cookies"] } actix-http = "3.0.0-beta.6" diff --git a/actix-multipart/Cargo.toml b/actix-multipart/Cargo.toml index 449e3121f..fd9a8d529 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.5", default-features = false } +actix-web = { version = "4.0.0-beta.6", default-features = false } actix-utils = "3.0.0" bytes = "1" diff --git a/actix-test/Cargo.toml b/actix-test/Cargo.toml index b021db00e..4c4c7cf5b 100644 --- a/actix-test/Cargo.toml +++ b/actix-test/Cargo.toml @@ -24,7 +24,7 @@ actix-http = "3.0.0-beta.6" 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.5", default-features = false, features = ["cookies"] } +actix-web = { version = "4.0.0-beta.6", default-features = false, features = ["cookies"] } actix-rt = "2.1" awc = { version = "3.0.0-beta.4", default-features = false, features = ["cookies"] } diff --git a/actix-web-actors/Cargo.toml b/actix-web-actors/Cargo.toml index 4871b470d..f6514ffb1 100644 --- a/actix-web-actors/Cargo.toml +++ b/actix-web-actors/Cargo.toml @@ -19,7 +19,7 @@ path = "src/lib.rs" actix = { version = "0.11.0-beta.3", default-features = false } actix-codec = "0.4.0-beta.1" actix-http = "3.0.0-beta.6" -actix-web = { version = "4.0.0-beta.5", default-features = false } +actix-web = { version = "4.0.0-beta.6", default-features = false } bytes = "1" bytestring = "1" diff --git a/actix-web-codegen/Cargo.toml b/actix-web-codegen/Cargo.toml index 653ce038a..7eb1b28fc 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.1" actix-utils = "3.0.0" -actix-web = "4.0.0-beta.5" +actix-web = "4.0.0-beta.6" futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] } trybuild = "1" diff --git a/awc/Cargo.toml b/awc/Cargo.toml index 29f76371c..efc63053a 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -70,7 +70,7 @@ 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.5", features = ["openssl"] } +actix-web = { version = "4.0.0-beta.6", features = ["openssl"] } actix-http = { version = "3.0.0-beta.6", features = ["openssl"] } actix-http-test = { version = "3.0.0-beta.4", features = ["openssl"] } actix-utils = "3.0.0" From 5a162932f3ce79ad81381eccdab38dd4cef68f0c Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sat, 17 Apr 2021 15:30:31 +0100 Subject: [PATCH 168/339] prepare awc release 3.0.0-beta.5 --- Cargo.toml | 28 ++++++++++++++++------------ actix-http-test/Cargo.toml | 2 +- actix-test/Cargo.toml | 2 +- actix-web-actors/Cargo.toml | 2 +- awc/Cargo.toml | 8 +++----- 5 files changed, 22 insertions(+), 20 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 24d8f54fb..2f80fc398 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,8 +4,12 @@ version = "4.0.0-beta.6" authors = ["Nikolay Kim "] 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"] +categories = [ + "network-programming", + "asynchronous", + "web-programming::http-server", + "web-programming::websocket" +] homepage = "https://actix.rs" repository = "https://github.com/actix/actix-web" license = "MIT OR Apache-2.0" @@ -21,15 +25,15 @@ path = "src/lib.rs" [workspace] members = [ - ".", - "awc", - "actix-http", - "actix-files", - "actix-multipart", - "actix-web-actors", - "actix-web-codegen", - "actix-http-test", - "actix-test", + ".", + "awc", + "actix-http", + "actix-files", + "actix-multipart", + "actix-web-actors", + "actix-web-codegen", + "actix-http-test", + "actix-test", ] # enable when MSRV is 1.51+ # resolver = "2" @@ -90,7 +94,7 @@ url = "2.1" [dev-dependencies] actix-test = { version = "0.1.0-beta.1", features = ["openssl", "rustls"] } -awc = { version = "3.0.0-beta.4", features = ["openssl"] } +awc = { version = "3.0.0-beta.5", features = ["openssl"] } brotli2 = "0.3.2" criterion = "0.3" diff --git a/actix-http-test/Cargo.toml b/actix-http-test/Cargo.toml index 38e5c4639..05a0e022a 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.4", default-features = false } +awc = { version = "3.0.0-beta.5", default-features = false } base64 = "0.13" bytes = "1" diff --git a/actix-test/Cargo.toml b/actix-test/Cargo.toml index 4c4c7cf5b..86d06dbd2 100644 --- a/actix-test/Cargo.toml +++ b/actix-test/Cargo.toml @@ -26,7 +26,7 @@ actix-service = "2.0.0" actix-utils = "3.0.0" actix-web = { version = "4.0.0-beta.6", default-features = false, features = ["cookies"] } actix-rt = "2.1" -awc = { version = "3.0.0-beta.4", default-features = false, features = ["cookies"] } +awc = { version = "3.0.0-beta.5", 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 f6514ffb1..192cb3a11 100644 --- a/actix-web-actors/Cargo.toml +++ b/actix-web-actors/Cargo.toml @@ -31,6 +31,6 @@ tokio = { version = "1", features = ["sync"] } actix-rt = "2.2" actix-test = "0.1.0-beta.1" -awc = { version = "3.0.0-beta.4", default-features = false } +awc = { version = "3.0.0-beta.5", default-features = false } env_logger = "0.8" futures-util = { version = "0.3.7", default-features = false } diff --git a/awc/Cargo.toml b/awc/Cargo.toml index efc63053a..40ec4e448 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -1,22 +1,20 @@ [package] name = "awc" -version = "3.0.0-beta.4" +version = "3.0.0-beta.5" authors = [ "Nikolay Kim ", "fakeshadow <24548779@qq.com>", ] description = "Async HTTP and WebSocket client library built on the Actix ecosystem" -readme = "README.md" keywords = ["actix", "http", "framework", "async", "web"] -homepage = "https://actix.rs" -repository = "https://github.com/actix/actix-web.git" -documentation = "https://docs.rs/awc/" categories = [ "network-programming", "asynchronous", "web-programming::http-client", "web-programming::websocket", ] +homepage = "https://actix.rs" +repository = "https://github.com/actix/actix-web" license = "MIT OR Apache-2.0" edition = "2018" From f462aaa7b6129b633fa53935d0ee54dbc01b70ea Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sat, 17 Apr 2021 15:53:54 +0100 Subject: [PATCH 169/339] prepare actix-test release 0.1.0-beta.2 --- 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/CHANGES.md | 3 +++ awc/Cargo.toml | 2 +- 8 files changed, 13 insertions(+), 6 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 2f80fc398..e482dec27 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -93,7 +93,7 @@ time = { version = "0.2.23", default-features = false, features = ["std"] } url = "2.1" [dev-dependencies] -actix-test = { version = "0.1.0-beta.1", features = ["openssl", "rustls"] } +actix-test = { version = "0.1.0-beta.2", features = ["openssl", "rustls"] } awc = { version = "3.0.0-beta.5", features = ["openssl"] } brotli2 = "0.3.2" diff --git a/actix-files/Cargo.toml b/actix-files/Cargo.toml index 9d87b839b..b97badd3e 100644 --- a/actix-files/Cargo.toml +++ b/actix-files/Cargo.toml @@ -35,4 +35,4 @@ percent-encoding = "2.1" [dev-dependencies] actix-rt = "2.2" actix-web = "4.0.0-beta.6" -actix-test = "0.1.0-beta.1" +actix-test = "0.1.0-beta.2" diff --git a/actix-test/CHANGES.md b/actix-test/CHANGES.md index 70e643ba7..d57f8bf4a 100644 --- a/actix-test/CHANGES.md +++ b/actix-test/CHANGES.md @@ -3,6 +3,10 @@ ## Unreleased - 2021-xx-xx +## 0.1.0-beta.2 - 2021-04-17 +* * 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] diff --git a/actix-test/Cargo.toml b/actix-test/Cargo.toml index 86d06dbd2..4c7e89f31 100644 --- a/actix-test/Cargo.toml +++ b/actix-test/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-test" -version = "0.1.0-beta.1" +version = "0.1.0-beta.2" authors = [ "Nikolay Kim ", "Rob Ede ", diff --git a/actix-web-actors/Cargo.toml b/actix-web-actors/Cargo.toml index 192cb3a11..ade2795cb 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.1" +actix-test = "0.1.0-beta.2" awc = { version = "3.0.0-beta.5", default-features = false } env_logger = "0.8" diff --git a/actix-web-codegen/Cargo.toml b/actix-web-codegen/Cargo.toml index 7eb1b28fc..db4f8430c 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.1" +actix-test = "0.1.0-beta.2" actix-utils = "3.0.0" actix-web = "4.0.0-beta.6" diff --git a/awc/CHANGES.md b/awc/CHANGES.md index a0bfcac86..b2e0ff78d 100644 --- a/awc/CHANGES.md +++ b/awc/CHANGES.md @@ -1,6 +1,9 @@ # Changes ## Unreleased - 2021-xx-xx + + +## 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 40ec4e448..5591048bc 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -74,7 +74,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.1", features = ["openssl", "rustls"] } +actix-test = { version = "0.1.0-beta.2", features = ["openssl", "rustls"] } brotli2 = "0.3.2" env_logger = "0.8" From 26e9c806264447d15bb71ad75da1b78a254f9aef Mon Sep 17 00:00:00 2001 From: Ibraheem Ahmed Date: Sun, 18 Apr 2021 18:34:51 -0400 Subject: [PATCH 170/339] Named file service (#2135) --- actix-files/src/files.rs | 12 ++++++ actix-files/src/lib.rs | 74 +++++++++++++++++++++++++++++++++ actix-files/src/named.rs | 80 +++++++++++++++++++++++++++++++++++- awc/tests/test_ssl_client.rs | 2 +- 4 files changed, 165 insertions(+), 3 deletions(-) diff --git a/actix-files/src/files.rs b/actix-files/src/files.rs index ff4241340..9b24c4b21 100644 --- a/actix-files/src/files.rs +++ b/actix-files/src/files.rs @@ -199,6 +199,18 @@ impl Files { } /// Sets default handler which is used when no matched file could be found. + /// + /// For example, you could set a fall back static file handler: + /// ```rust + /// use actix_files::{Files, NamedFile}; + /// + /// # fn run() -> Result<(), actix_web::Error> { + /// let files = Files::new("/", "./static") + /// .index_file("index.html") + /// .default_handler(NamedFile::open("./static/404.html")?); + /// # Ok(()) + /// # } + /// ``` pub fn default_handler(mut self, f: F) -> Self where F: IntoServiceFactory, diff --git a/actix-files/src/lib.rs b/actix-files/src/lib.rs index e9b55e87e..34b02581e 100644 --- a/actix-files/src/lib.rs +++ b/actix-files/src/lib.rs @@ -755,6 +755,80 @@ mod tests { assert_eq!(res.status(), StatusCode::OK); } + #[actix_rt::test] + async fn test_serve_named_file() { + let srv = + test::init_service(App::new().service(NamedFile::open("Cargo.toml").unwrap())) + .await; + + let req = TestRequest::get().uri("/Cargo.toml").to_request(); + let res = test::call_service(&srv, req).await; + assert_eq!(res.status(), StatusCode::OK); + + let bytes = test::read_body(res).await; + let data = Bytes::from(fs::read("Cargo.toml").unwrap()); + assert_eq!(bytes, data); + + let req = TestRequest::get().uri("/test/unknown").to_request(); + let res = test::call_service(&srv, req).await; + assert_eq!(res.status(), StatusCode::NOT_FOUND); + } + + #[actix_rt::test] + async fn test_serve_named_file_prefix() { + let srv = test::init_service( + App::new() + .service(web::scope("/test").service(NamedFile::open("Cargo.toml").unwrap())), + ) + .await; + + let req = TestRequest::get().uri("/test/Cargo.toml").to_request(); + let res = test::call_service(&srv, req).await; + assert_eq!(res.status(), StatusCode::OK); + + let bytes = test::read_body(res).await; + let data = Bytes::from(fs::read("Cargo.toml").unwrap()); + assert_eq!(bytes, data); + + let req = TestRequest::get().uri("/Cargo.toml").to_request(); + let res = test::call_service(&srv, req).await; + assert_eq!(res.status(), StatusCode::NOT_FOUND); + } + + #[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; + + for route in ["/foobar", "/baz", "/"].iter() { + let req = TestRequest::get().uri(route).to_request(); + let res = test::call_service(&srv, req).await; + assert_eq!(res.status(), StatusCode::OK); + + let bytes = test::read_body(res).await; + let data = Bytes::from(fs::read("Cargo.toml").unwrap()); + assert_eq!(bytes, data); + } + } + + #[actix_rt::test] + async fn test_default_handler_named_file() { + let st = Files::new("/", ".") + .default_handler(NamedFile::open("Cargo.toml").unwrap()) + .new_service(()) + .await + .unwrap(); + let req = TestRequest::with_uri("/missing").to_srv_request(); + let resp = test::call_service(&st, req).await; + + assert_eq!(resp.status(), StatusCode::OK); + let bytes = test::read_body(resp).await; + let data = Bytes::from(fs::read("Cargo.toml").unwrap()); + assert_eq!(bytes, data); + } + #[actix_rt::test] async fn test_symlinks() { let srv = test::init_service(App::new().service(Files::new("test", "."))).await; diff --git a/actix-files/src/named.rs b/actix-files/src/named.rs index 2846646a2..519234f0d 100644 --- a/actix-files/src/named.rs +++ b/actix-files/src/named.rs @@ -1,3 +1,6 @@ +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}; @@ -8,14 +11,14 @@ use std::time::{SystemTime, UNIX_EPOCH}; use std::os::unix::fs::MetadataExt; use actix_web::{ - dev::{BodyEncoding, SizedStream}, + dev::{BodyEncoding, ServiceRequest, ServiceResponse, SizedStream}, http::{ header::{ self, Charset, ContentDisposition, DispositionParam, DispositionType, ExtendedValue, }, ContentEncoding, StatusCode, }, - HttpMessage, HttpRequest, HttpResponse, Responder, + Error, HttpMessage, HttpRequest, HttpResponse, Responder, }; use bitflags::bitflags; use mime_guess::from_path; @@ -39,6 +42,29 @@ impl Default for Flags { } /// A file with an associated name. +/// +/// `NamedFile` can be registered as services: +/// ``` +/// use actix_web::App; +/// use actix_files::NamedFile; +/// +/// # fn run() -> Result<(), Box> { +/// let app = App::new() +/// .service(NamedFile::open("./static/index.html")?); +/// # Ok(()) +/// # } +/// ``` +/// +/// They can also be returned from handlers: +/// ``` +/// use actix_web::{Responder, get}; +/// use actix_files::NamedFile; +/// +/// #[get("/")] +/// async fn index() -> impl Responder { +/// NamedFile::open("./static/index.html") +/// } +/// ``` #[derive(Debug)] pub struct NamedFile { path: PathBuf, @@ -480,3 +506,53 @@ impl Responder for NamedFile { self.into_response(req) } } + +impl ServiceFactory for NamedFile { + type Response = ServiceResponse; + type Error = Error; + type Config = (); + type InitError = (); + type Service = NamedFileService; + type Future = Ready>; + + fn new_service(&self, _: ()) -> Self::Future { + ok(NamedFileService { + path: self.path.clone(), + }) + } +} + +#[doc(hidden)] +#[derive(Debug)] +pub struct NamedFileService { + path: PathBuf, +} + +impl Service for NamedFileService { + type Response = ServiceResponse; + type Error = Error; + type Future = Ready>; + + 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)), + ) + } +} + +impl HttpServiceFactory for NamedFile { + fn register(self, config: &mut AppService) { + config.register_service( + ResourceDef::root_prefix(self.path.to_string_lossy().as_ref()), + None, + self, + None, + ) + } +} diff --git a/awc/tests/test_ssl_client.rs b/awc/tests/test_ssl_client.rs index 57305e49a..811efd4bc 100644 --- a/awc/tests/test_ssl_client.rs +++ b/awc/tests/test_ssl_client.rs @@ -7,7 +7,7 @@ use std::sync::Arc; use actix_http::HttpService; use actix_http_test::test_server; -use actix_service::{map_config, fn_service, ServiceFactoryExt}; +use actix_service::{fn_service, map_config, ServiceFactoryExt}; use actix_utils::future::ok; use actix_web::http::Version; use actix_web::{dev::AppConfig, web, App, HttpResponse}; From 8ffb1f201199e914196ff7e4080eedd7b5606571 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Mon, 19 Apr 2021 02:11:07 +0100 Subject: [PATCH 171/339] update files changelog --- actix-files/CHANGES.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/actix-files/CHANGES.md b/actix-files/CHANGES.md index 004479183..6c28e42d0 100644 --- a/actix-files/CHANGES.md +++ b/actix-files/CHANGES.md @@ -1,8 +1,10 @@ # Changes ## Unreleased - 2021-xx-xx +* `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] +[#2135]: https://github.com/actix/actix-web/pull/2135 [#2156]: https://github.com/actix/actix-web/pull/2156 From 35f818841012012855c3644a75cf641bd6c96182 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Mon, 19 Apr 2021 02:24:20 +0100 Subject: [PATCH 172/339] restore cookie methods on ServiceRequest --- actix-http/src/http_message.rs | 16 ++++++++++------ src/service.rs | 12 ++++++++++++ 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/actix-http/src/http_message.rs b/actix-http/src/http_message.rs index 0f4e347e0..ccaa320fa 100644 --- a/actix-http/src/http_message.rs +++ b/actix-http/src/http_message.rs @@ -1,14 +1,18 @@ -use std::cell::{Ref, RefMut}; -use std::str; +use std::{ + cell::{Ref, RefMut}, + str, +}; use encoding_rs::{Encoding, UTF_8}; use http::header; use mime::Mime; -use crate::error::{ContentTypeError, ParseError}; -use crate::extensions::Extensions; -use crate::header::{Header, HeaderMap}; -use crate::payload::Payload; +use crate::{ + error::{ContentTypeError, ParseError}, + header::{Header, HeaderMap}, + payload::Payload, + Extensions, +}; /// Trait that implements general purpose operations on HTTP messages. pub trait HttpMessage: Sized { diff --git a/src/service.rs b/src/service.rs index 9765343c1..dd3597efb 100644 --- a/src/service.rs +++ b/src/service.rs @@ -9,6 +9,7 @@ use actix_http::{ }; use actix_router::{IntoPattern, Path, Resource, ResourceDef, Url}; use actix_service::{IntoServiceFactory, ServiceFactory}; +use cookie::{Cookie, ParseError as CookieParseError}; use crate::dev::insert_slash; use crate::guard::Guard; @@ -244,6 +245,17 @@ impl ServiceRequest { None } + #[cfg(feature = "cookies")] + pub fn cookies(&self) -> Result>>, CookieParseError> { + self.req.cookies() + } + + /// Return request cookie. + #[cfg(feature = "cookies")] + pub fn cookie(&self, name: &str) -> Option> { + self.req.cookie(name) + } + /// Set request payload. pub fn set_payload(&mut self, payload: Payload) { self.payload = payload; From b9dbc58e20a0b28ae8d543a581509b607c26a510 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Mon, 19 Apr 2021 02:31:11 +0100 Subject: [PATCH 173/339] content disposition methods take impl AsRef --- CHANGES.md | 1 + src/http/header/content_disposition.rs | 19 +++++++++---------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index eb5130b99..bea9ab935 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -9,6 +9,7 @@ ### 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 diff --git a/src/http/header/content_disposition.rs b/src/http/header/content_disposition.rs index 509d68968..bff4e49a8 100644 --- a/src/http/header/content_disposition.rs +++ b/src/http/header/content_disposition.rs @@ -401,11 +401,11 @@ impl ContentDisposition { } /// Returns `true` if it is [`Ext`](DispositionType::Ext) and the `disp_type` matches. - pub fn is_ext>(&self, disp_type: T) -> bool { - match self.disposition { - DispositionType::Ext(ref t) if t.eq_ignore_ascii_case(disp_type.as_ref()) => true, - _ => false, - } + pub fn is_ext(&self, disp_type: impl AsRef) -> bool { + matches!( + self.disposition, + DispositionType::Ext(ref t) if t.eq_ignore_ascii_case(disp_type.as_ref()) + ) } /// Return the value of *name* if exists. @@ -430,7 +430,7 @@ impl ContentDisposition { } /// Return the value of the parameter which the `name` matches. - pub fn get_unknown>(&self, name: T) -> Option<&str> { + pub fn get_unknown(&self, name: impl AsRef) -> Option<&str> { let name = name.as_ref(); self.parameters .iter() @@ -439,7 +439,7 @@ impl ContentDisposition { } /// Return the value of the extended parameter which the `name` matches. - pub fn get_unknown_ext>(&self, name: T) -> Option<&ExtendedValue> { + pub fn get_unknown_ext(&self, name: impl AsRef) -> Option<&ExtendedValue> { let name = name.as_ref(); self.parameters .iter() @@ -552,9 +552,8 @@ impl fmt::Display for ContentDisposition { #[cfg(test)] mod tests { use super::{ContentDisposition, DispositionParam, DispositionType}; - use crate::http::header::Charset; - use crate::http::header::{ExtendedValue, HeaderValue}; - + use crate::http::header::{Charset, ExtendedValue, HeaderValue}; + #[test] fn test_from_raw_basic() { assert!(ContentDisposition::from_raw(&HeaderValue::from_static("")).is_err()); From db97974dc1caf2810bdbf235b2fe1a7e7ce74619 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Mon, 19 Apr 2021 03:29:38 +0100 Subject: [PATCH 174/339] make some http re-exports more accessible (#2171) --- actix-http/CHANGES.md | 13 +++++++++++ actix-http/src/error.rs | 5 +++-- actix-http/src/header/mod.rs | 30 +++++++++++++++++++++++--- actix-http/src/lib.rs | 8 ++++++- actix-http/src/message.rs | 15 +++++++------ actix-http/src/response_builder.rs | 10 ++++----- actix-test/CHANGES.md | 2 +- src/http/header/content_disposition.rs | 2 +- src/service.rs | 1 + 9 files changed, 66 insertions(+), 20 deletions(-) diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 6cf9ff276..e89208748 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -1,6 +1,19 @@ # Changes ## Unreleased - 2021-xx-xx +### Added +* 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] + +### Changed +* `header` mod is now public. [#2171] +* `uri` mod is now public. [#2171] + +### Removed +* Stop re-exporting `http` crate's `HeaderMap` types in addition to ours. [#2171] + +[#2171]: https://github.com/actix/actix-web/pull/2171 ## 3.0.0-beta.6 - 2021-04-17 diff --git a/actix-http/src/error.rs b/actix-http/src/error.rs index 68ad709a1..cd2917c93 100644 --- a/actix-http/src/error.rs +++ b/actix-http/src/error.rs @@ -10,12 +10,13 @@ use std::{ use bytes::BytesMut; use derive_more::{Display, Error, From}; -use http::uri::InvalidUri; -use http::{header, Error as HttpError, StatusCode}; +use http::{header, uri::InvalidUri, StatusCode}; use serde::de::value::Error as DeError; use crate::{body::Body, helpers::Writer, Response, ResponseBuilder}; +pub use http::Error as HttpError; + /// A specialized [`std::result::Result`] /// for actix web operations /// diff --git a/actix-http/src/header/mod.rs b/actix-http/src/header/mod.rs index a6056ace4..18494f555 100644 --- a/actix-http/src/header/mod.rs +++ b/actix-http/src/header/mod.rs @@ -1,9 +1,33 @@ -//! Typed HTTP headers, pre-defined `HeaderName`s, traits for parsing and conversion, and other -//! header utility methods. +//! Pre-defined `HeaderName`s, traits for parsing and conversion, and other header utility methods. use percent_encoding::{AsciiSet, CONTROLS}; -pub use http::header::*; +// re-export from http except header map related items +pub use http::header::{ + HeaderName, HeaderValue, InvalidHeaderName, InvalidHeaderValue, ToStrError, +}; + +// re-export const header names +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, + 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, +}; use crate::error::ParseError; use crate::HttpMessage; diff --git a/actix-http/src/lib.rs b/actix-http/src/lib.rs index 4547f3ef2..82d0415c2 100644 --- a/actix-http/src/lib.rs +++ b/actix-http/src/lib.rs @@ -35,7 +35,7 @@ mod config; #[cfg(feature = "compress")] pub mod encoding; mod extensions; -mod header; +pub mod header; mod helpers; mod http_message; mod message; @@ -56,7 +56,9 @@ pub use self::builder::HttpServiceBuilder; pub use self::config::{KeepAlive, ServiceConfig}; pub use self::error::{Error, ResponseError, Result}; 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::payload::{Payload, PayloadStream}; pub use self::request::Request; @@ -64,6 +66,10 @@ pub use self::response::Response; pub use self::response_builder::ResponseBuilder; 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. diff --git a/actix-http/src/message.rs b/actix-http/src/message.rs index c89f5311a..8cb99d43a 100644 --- a/actix-http/src/message.rs +++ b/actix-http/src/message.rs @@ -1,12 +1,15 @@ -use std::cell::{Ref, RefCell, RefMut}; -use std::net; -use std::rc::Rc; +use std::{ + cell::{Ref, RefCell, RefMut}, + net, + rc::Rc, +}; use bitflags::bitflags; -use crate::extensions::Extensions; -use crate::header::HeaderMap; -use crate::http::{header, Method, StatusCode, Uri, Version}; +use crate::{ + header::{self, HeaderMap}, + Extensions, Method, StatusCode, Uri, Version, +}; /// Represents various types of connection #[derive(Copy, Clone, PartialEq, Debug)] diff --git a/actix-http/src/response_builder.rs b/actix-http/src/response_builder.rs index 4d8cb4429..0105f70cf 100644 --- a/actix-http/src/response_builder.rs +++ b/actix-http/src/response_builder.rs @@ -14,12 +14,10 @@ use futures_core::Stream; use crate::{ body::{Body, BodyStream, ResponseBody}, - error::Error, - extensions::Extensions, - header::{IntoHeaderPair, IntoHeaderValue}, - http::{header, Error as HttpError, StatusCode}, + error::{Error, HttpError}, + header::{self, IntoHeaderPair, IntoHeaderValue}, message::{BoxedResponseHead, ConnectionType, ResponseHead}, - Response, + Extensions, Response, StatusCode, }; /// An HTTP response builder. @@ -291,7 +289,7 @@ impl ResponseBuilder { return None; } - self.head.as_mut().map(|r| &mut **r) + self.head.as_deref_mut() } } diff --git a/actix-test/CHANGES.md b/actix-test/CHANGES.md index d57f8bf4a..2276fe745 100644 --- a/actix-test/CHANGES.md +++ b/actix-test/CHANGES.md @@ -4,7 +4,7 @@ ## 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 diff --git a/src/http/header/content_disposition.rs b/src/http/header/content_disposition.rs index bff4e49a8..71c610157 100644 --- a/src/http/header/content_disposition.rs +++ b/src/http/header/content_disposition.rs @@ -553,7 +553,7 @@ impl fmt::Display for ContentDisposition { mod tests { use super::{ContentDisposition, DispositionParam, DispositionType}; use crate::http::header::{Charset, ExtendedValue, HeaderValue}; - + #[test] fn test_from_raw_basic() { assert!(ContentDisposition::from_raw(&HeaderValue::from_static("")).is_err()); diff --git a/src/service.rs b/src/service.rs index dd3597efb..f6d1f9ebf 100644 --- a/src/service.rs +++ b/src/service.rs @@ -9,6 +9,7 @@ use actix_http::{ }; 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; From 52bb2b5daf30cc409386e7aaeee07b9c90dd6b4e Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Mon, 19 Apr 2021 03:42:53 +0100 Subject: [PATCH 175/339] hide downcast macros --- actix-http/src/macros.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/actix-http/src/macros.rs b/actix-http/src/macros.rs index 714629b43..7cf0e288b 100644 --- a/actix-http/src/macros.rs +++ b/actix-http/src/macros.rs @@ -1,4 +1,5 @@ #[macro_export] +#[doc(hidden)] macro_rules! downcast_get_type_id { () => { /// A helper method to get the type ID of the type @@ -25,6 +26,7 @@ macro_rules! downcast_get_type_id { } //Generate implementation for dyn $name +#[doc(hidden)] #[macro_export] macro_rules! downcast { ($name:ident) => { From 2aa674c1fdb121cc6330f65b2bb18b4b4fa139e8 Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Mon, 19 Apr 2021 15:15:57 -0700 Subject: [PATCH 176/339] Fix perf drop in HttpResponseBuilder (#2174) --- src/response/builder.rs | 32 +++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/src/response/builder.rs b/src/response/builder.rs index 2c04f3f64..8b3c0f10d 100644 --- a/src/response/builder.rs +++ b/src/response/builder.rs @@ -32,7 +32,7 @@ use crate::{ /// /// This type can be used to construct an instance of `Response` through a builder-like pattern. pub struct HttpResponseBuilder { - head: Option, + res: Option>, err: Option, #[cfg(feature = "cookies")] cookies: Option, @@ -43,7 +43,7 @@ impl HttpResponseBuilder { /// Create response builder pub fn new(status: StatusCode) -> Self { Self { - head: Some(ResponseHead::new(status)), + res: Some(Response::new(status)), err: None, #[cfg(feature = "cookies")] cookies: None, @@ -291,15 +291,19 @@ impl HttpResponseBuilder { /// Responses extensions #[inline] pub fn extensions(&self) -> Ref<'_, Extensions> { - let head = self.head.as_ref().expect("cannot reuse response builder"); - head.extensions() + self.res + .as_ref() + .expect("cannot reuse response builder") + .extensions() } /// 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_mut() + self.res + .as_mut() + .expect("cannot reuse response builder") + .extensions_mut() } /// Set a body and generate `Response`. @@ -318,12 +322,14 @@ impl HttpResponseBuilder { return HttpResponse::from_error(Error::from(err)).into_body(); } - // allow unused mut when cookies feature is disabled - #[allow(unused_mut)] - let mut head = self.head.take().expect("cannot reuse response builder"); + let res = self + .res + .take() + .expect("cannot reuse response builder") + .set_body(body); - let mut res = HttpResponse::with_body(StatusCode::OK, body); - *res.head_mut() = head; + #[allow(unused_mut)] + let mut res = HttpResponse::from(res); #[cfg(feature = "cookies")] if let Some(ref jar) = self.cookies { @@ -383,7 +389,7 @@ impl HttpResponseBuilder { /// This method construct new `HttpResponseBuilder` pub fn take(&mut self) -> Self { Self { - head: self.head.take(), + res: self.res.take(), err: self.err.take(), #[cfg(feature = "cookies")] cookies: self.cookies.take(), @@ -396,7 +402,7 @@ impl HttpResponseBuilder { return None; } - self.head.as_mut() + self.res.as_mut().map(|res| res.head_mut()) } } From 427fe6bd82e70921e9353636bb5096c549ec59cc Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Mon, 19 Apr 2021 21:12:52 +0100 Subject: [PATCH 177/339] improve responseerror trait docs --- actix-http/src/error.rs | 20 ++++++++++---------- actix-http/src/helpers.rs | 1 + 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/actix-http/src/error.rs b/actix-http/src/error.rs index cd2917c93..39ffa29e7 100644 --- a/actix-http/src/error.rs +++ b/actix-http/src/error.rs @@ -17,12 +17,10 @@ use crate::{body::Body, helpers::Writer, Response, ResponseBuilder}; pub use http::Error as HttpError; -/// A specialized [`std::result::Result`] -/// for actix web operations +/// A specialized [`std::result::Result`] for Actix Web operations. /// -/// This typedef is generally used to avoid writing out -/// `actix_http::error::Error` directly and is otherwise a direct mapping to -/// `Result`. +/// This typedef is generally used to avoid writing out `actix_http::error::Error` directly and is +/// otherwise a direct mapping to `Result`. pub type Result = std::result::Result; /// General purpose actix web error. @@ -51,18 +49,20 @@ impl Error { } } -/// Error that can be converted to `Response` +/// Errors that can generate responses. pub trait ResponseError: fmt::Debug + fmt::Display { - /// Response's status code + /// Returns appropriate status code for error. /// - /// Internal server error is generated by default. + /// 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 } - /// Create response for error + /// Creates full response for error. /// - /// Internal server error is generated by default. + /// 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(); diff --git a/actix-http/src/helpers.rs b/actix-http/src/helpers.rs index 74188717d..b00ca307e 100644 --- a/actix-http/src/helpers.rs +++ b/actix-http/src/helpers.rs @@ -41,6 +41,7 @@ pub fn write_content_length(n: u64, buf: &mut B) { buf.put_slice(b"\r\n"); } +// TODO: bench why this is needed pub(crate) struct Writer<'a, B>(pub &'a mut B); impl<'a, B> io::Write for Writer<'a, B> From 6a9c4f102657f35b7aba86ea8eb3ff4e3dee009a Mon Sep 17 00:00:00 2001 From: Ibraheem Ahmed Date: Tue, 20 Apr 2021 14:57:27 -0400 Subject: [PATCH 178/339] update awc docs link, formatting (#2180) --- README.md | 2 +- src/lib.rs | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index c85c0652f..60ec57c60 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,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/actix-web/latest/actix_web/client/index.html) +* Includes an async [HTTP client](https://docs.rs/awc/) * Runs on stable Rust 1.46+ ## Documentation diff --git a/src/lib.rs b/src/lib.rs index cf1bfa590..4d0ad26ed 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -30,16 +30,16 @@ //! //! To get started navigating the API docs, you may consider looking at the following pages first: //! -//! * [App]: This struct represents an Actix Web application and is used to +//! * [`App`]: This struct represents an Actix Web application and is used to //! configure routes and other common application settings. //! -//! * [HttpServer]: This struct represents an HTTP server instance and is +//! * [`HttpServer`]: This struct represents an HTTP server instance and is //! used to instantiate and configure servers. //! -//! * [web]: This module provides essential types for route registration as well as +//! * [`web`]: This module provides essential types for route registration as well as //! common utilities for request handlers. //! -//! * [HttpRequest] and [HttpResponse]: These +//! * [`HttpRequest`] and [`HttpResponse`]: These //! structs represent HTTP requests and responses and expose methods for creating, inspecting, //! and otherwise utilizing them. //! @@ -55,7 +55,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://actix.rs/actix-web/actix_web/client/index.html) +//! * Includes an async [HTTP client](https://docs.rs/awc/) //! * Runs on stable Rust 1.46+ //! //! ## Crate Features From a7cd4e85cf75e2cdf6fc54d91ef3c60967e64288 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Wed, 21 Apr 2021 11:14:22 +0100 Subject: [PATCH 179/339] use stable codec 0.4.0 --- 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 e482dec27..714da13a0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -57,7 +57,7 @@ openssl = ["actix-http/openssl", "actix-tls/accept", "actix-tls/openssl"] rustls = ["actix-http/rustls", "actix-tls/accept", "actix-tls/rustls"] [dependencies] -actix-codec = "0.4.0-beta.1" +actix-codec = "0.4.0" actix-macros = "0.2.0" actix-router = "0.2.7" actix-rt = "2.2" diff --git a/actix-http-test/Cargo.toml b/actix-http-test/Cargo.toml index 05a0e022a..88b4bd04a 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-beta.1" +actix-codec = "0.4.0" actix-tls = "3.0.0-beta.5" actix-utils = "3.0.0" actix-rt = "2.2" diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 61f60b79e..55dc6d05e 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -39,7 +39,7 @@ trust-dns = ["trust-dns-resolver"] [dependencies] actix-service = "2.0.0" -actix-codec = "0.4.0-beta.1" +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"] } diff --git a/actix-test/Cargo.toml b/actix-test/Cargo.toml index 4c7e89f31..23f3650cd 100644 --- a/actix-test/Cargo.toml +++ b/actix-test/Cargo.toml @@ -19,7 +19,7 @@ rustls = ["tls-rustls", "actix-http/rustls"] openssl = ["tls-openssl", "actix-http/openssl"] [dependencies] -actix-codec = "0.4.0-beta.1" +actix-codec = "0.4.0" actix-http = "3.0.0-beta.6" actix-http-test = { version = "3.0.0-beta.4", features = [] } actix-service = "2.0.0" diff --git a/actix-web-actors/Cargo.toml b/actix-web-actors/Cargo.toml index ade2795cb..b653dd92a 100644 --- a/actix-web-actors/Cargo.toml +++ b/actix-web-actors/Cargo.toml @@ -17,7 +17,7 @@ path = "src/lib.rs" [dependencies] actix = { version = "0.11.0-beta.3", default-features = false } -actix-codec = "0.4.0-beta.1" +actix-codec = "0.4.0" actix-http = "3.0.0-beta.6" actix-web = { version = "4.0.0-beta.6", default-features = false } diff --git a/awc/Cargo.toml b/awc/Cargo.toml index 5591048bc..c8a184513 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -45,7 +45,7 @@ cookies = ["cookie"] trust-dns = ["actix-http/trust-dns"] [dependencies] -actix-codec = "0.4.0-beta.1" +actix-codec = "0.4.0" actix-service = "2.0.0" actix-http = "3.0.0-beta.6" actix-rt = { version = "2.1", default-features = false } From 07036b56407c4abdd3034ee5594a43a0ea57cd0e Mon Sep 17 00:00:00 2001 From: Ibraheem Ahmed Date: Thu, 22 Apr 2021 08:54:29 -0400 Subject: [PATCH 180/339] static form extract future (#2181) --- src/types/form.rs | 53 ++++++++++++++++++++++++++++++++++------------- 1 file changed, 39 insertions(+), 14 deletions(-) diff --git a/src/types/form.rs b/src/types/form.rs index 9d2311a45..d1deac937 100644 --- a/src/types/form.rs +++ b/src/types/form.rs @@ -12,7 +12,7 @@ use std::{ use actix_http::Payload; use bytes::BytesMut; use encoding_rs::{Encoding, UTF_8}; -use futures_core::future::LocalBoxFuture; +use futures_core::{future::LocalBoxFuture, ready}; use futures_util::{FutureExt as _, StreamExt as _}; use serde::{de::DeserializeOwned, Serialize}; @@ -123,11 +123,10 @@ where { type Config = FormConfig; type Error = Error; - type Future = LocalBoxFuture<'static, Result>; + type Future = FormExtractFut; #[inline] fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future { - let req2 = req.clone(); let (limit, err_handler) = req .app_data::() .or_else(|| { @@ -137,16 +136,42 @@ where .map(|c| (c.limit, c.err_handler.clone())) .unwrap_or((16384, None)); - UrlEncoded::new(req, payload) - .limit(limit) - .map(move |res| match res { - Err(err) => match err_handler { - Some(err_handler) => Err((err_handler)(err, &req2)), - None => Err(err.into()), - }, - Ok(item) => Ok(Form(item)), - }) - .boxed_local() + FormExtractFut { + fut: UrlEncoded::new(req, payload).limit(limit), + req: req.clone(), + err_handler, + } + } +} + +type FormErrHandler = Option Error>>; + +pub struct FormExtractFut { + fut: UrlEncoded, + err_handler: FormErrHandler, + req: HttpRequest, +} + +impl Future for FormExtractFut +where + T: DeserializeOwned + 'static, +{ + type Output = Result, Error>; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let this = self.get_mut(); + + let res = ready!(Pin::new(&mut this.fut).poll(cx)); + + let res = match res { + Err(err) => match &this.err_handler { + Some(err_handler) => Err((err_handler)(err, &this.req)), + None => Err(err.into()), + }, + Ok(item) => Ok(Form(item)), + }; + + Poll::Ready(res) } } @@ -193,7 +218,7 @@ impl Responder for Form { #[derive(Clone)] pub struct FormConfig { limit: usize, - err_handler: Option Error>>, + err_handler: FormErrHandler, } impl FormConfig { From f44a0bc159680f5c65c14bbf7947768272818276 Mon Sep 17 00:00:00 2001 From: tglman Date: Thu, 22 Apr 2021 18:13:13 +0100 Subject: [PATCH 181/339] add support of filtering guards in Files of actix-files (#2046) Co-authored-by: Rob Ede --- actix-files/CHANGES.md | 3 ++ actix-files/src/files.rs | 49 ++++++++++++++++--- .../tests/fixtures/guards/first/index.txt | 1 + .../tests/fixtures/guards/second/index.txt | 1 + actix-files/tests/guard.rs | 36 ++++++++++++++ src/guard.rs | 8 +++ 6 files changed, 91 insertions(+), 7 deletions(-) create mode 100644 actix-files/tests/fixtures/guards/first/index.txt create mode 100644 actix-files/tests/fixtures/guards/second/index.txt create mode 100644 actix-files/tests/guard.rs diff --git a/actix-files/CHANGES.md b/actix-files/CHANGES.md index 6c28e42d0..0586a2fd3 100644 --- a/actix-files/CHANGES.md +++ b/actix-files/CHANGES.md @@ -11,6 +11,9 @@ ## 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/src/files.rs b/actix-files/src/files.rs index 9b24c4b21..00e4dd5d1 100644 --- a/actix-files/src/files.rs +++ b/actix-files/src/files.rs @@ -37,7 +37,8 @@ pub struct Files { renderer: Rc, mime_override: Option>, file_flags: named::Flags, - guards: Option>, + use_guards: Option>, + guards: Vec>>, hidden_files: bool, } @@ -59,12 +60,12 @@ impl Clone for Files { file_flags: self.file_flags, path: self.path.clone(), mime_override: self.mime_override.clone(), + use_guards: self.use_guards.clone(), guards: self.guards.clone(), hidden_files: self.hidden_files, } } } - impl Files { /// Create new `Files` instance for a specified base directory. /// @@ -104,7 +105,8 @@ impl Files { renderer: Rc::new(directory_listing), mime_override: None, file_flags: named::Flags::default(), - guards: None, + use_guards: None, + guards: Vec::new(), hidden_files: false, } } @@ -185,7 +187,28 @@ impl Files { /// Default behaviour allows GET and HEAD. #[inline] pub fn use_guards(mut self, guards: G) -> Self { - self.guards = Some(Rc::new(guards)); + self.use_guards = Some(Rc::new(guards)); + self + } + + /// Add match guard to use on directory listings and files. + /// + /// ```rust + /// use actix_web::{guard, App}; + /// use actix_files::Files; + /// + /// + /// fn main() { + /// let app = App::new() + /// .service( + /// Files::new("/","/my/site/files") + /// .guard(guard::Header("Host", "example.com")) + /// ); + /// } + /// ``` + #[inline] + pub fn guard(mut self, guard: G) -> Self { + self.guards.push(Box::new(Rc::new(guard))); self } @@ -238,7 +261,19 @@ impl Files { } impl HttpServiceFactory for Files { - fn register(self, config: &mut AppService) { + fn register(mut self, config: &mut AppService) { + let guards = if self.guards.is_empty() { + None + } else { + let guards = std::mem::take(&mut self.guards); + Some( + guards + .into_iter() + .map(|guard| -> Box { guard }) + .collect::>(), + ) + }; + if self.default.borrow().is_none() { *self.default.borrow_mut() = Some(config.default_service()); } @@ -249,7 +284,7 @@ impl HttpServiceFactory for Files { ResourceDef::prefix(&self.path) }; - config.register_service(rdef, None, self, None) + config.register_service(rdef, guards, self, None) } } @@ -271,7 +306,7 @@ impl ServiceFactory for Files { renderer: self.renderer.clone(), mime_override: self.mime_override.clone(), file_flags: self.file_flags, - guards: self.guards.clone(), + guards: self.use_guards.clone(), hidden_files: self.hidden_files, }; diff --git a/actix-files/tests/fixtures/guards/first/index.txt b/actix-files/tests/fixtures/guards/first/index.txt new file mode 100644 index 000000000..fe4f02ad0 --- /dev/null +++ b/actix-files/tests/fixtures/guards/first/index.txt @@ -0,0 +1 @@ +first \ No newline at end of file diff --git a/actix-files/tests/fixtures/guards/second/index.txt b/actix-files/tests/fixtures/guards/second/index.txt new file mode 100644 index 000000000..2147e4188 --- /dev/null +++ b/actix-files/tests/fixtures/guards/second/index.txt @@ -0,0 +1 @@ +second \ No newline at end of file diff --git a/actix-files/tests/guard.rs b/actix-files/tests/guard.rs new file mode 100644 index 000000000..8b1785e7f --- /dev/null +++ b/actix-files/tests/guard.rs @@ -0,0 +1,36 @@ +use actix_files::Files; +use actix_web::{ + guard::Host, + http::StatusCode, + test::{self, TestRequest}, + App, +}; +use bytes::Bytes; + +#[actix_rt::test] +async fn test_guard_filter() { + let srv = test::init_service( + App::new() + .service(Files::new("/", "./tests/fixtures/guards/first").guard(Host("first.com"))) + .service( + Files::new("/", "./tests/fixtures/guards/second").guard(Host("second.com")), + ), + ) + .await; + + let req = TestRequest::with_uri("/index.txt") + .append_header(("Host", "first.com")) + .to_request(); + let res = test::call_service(&srv, req).await; + + assert_eq!(res.status(), StatusCode::OK); + assert_eq!(test::read_body(res).await, Bytes::from("first")); + + let req = TestRequest::with_uri("/index.txt") + .append_header(("Host", "second.com")) + .to_request(); + let res = test::call_service(&srv, req).await; + + assert_eq!(res.status(), StatusCode::OK); + assert_eq!(test::read_body(res).await, Bytes::from("second")); +} diff --git a/src/guard.rs b/src/guard.rs index 3a1f5bb14..c71d64a29 100644 --- a/src/guard.rs +++ b/src/guard.rs @@ -26,6 +26,8 @@ //! ``` #![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; @@ -40,6 +42,12 @@ pub trait Guard { fn check(&self, request: &RequestHead) -> bool; } +impl Guard for Rc { + fn check(&self, request: &RequestHead) -> bool { + self.deref().check(request) + } +} + /// Create guard object for supplied function. /// /// ``` From 75867bd073e4bc16b416bc10e13a185c3cfcf14a Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Thu, 22 Apr 2021 18:31:21 +0100 Subject: [PATCH 182/339] clean up files service docs and rename method follow on from #2046 --- actix-files/src/files.rs | 61 ++++++++++++++++++++++------------------ actix-files/src/lib.rs | 2 +- 2 files changed, 35 insertions(+), 28 deletions(-) diff --git a/actix-files/src/files.rs b/actix-files/src/files.rs index 00e4dd5d1..aeb75e116 100644 --- a/actix-files/src/files.rs +++ b/actix-files/src/files.rs @@ -158,7 +158,6 @@ impl Files { /// Specifies whether to use ETag or not. /// /// Default is true. - #[inline] pub fn use_etag(mut self, value: bool) -> Self { self.file_flags.set(named::Flags::ETAG, value); self @@ -167,7 +166,6 @@ impl Files { /// Specifies whether to use Last-Modified or not. /// /// Default is true. - #[inline] pub fn use_last_modified(mut self, value: bool) -> Self { self.file_flags.set(named::Flags::LAST_MD, value); self @@ -176,46 +174,55 @@ impl Files { /// Specifies whether text responses should signal a UTF-8 encoding. /// /// Default is false (but will default to true in a future version). - #[inline] pub fn prefer_utf8(mut self, value: bool) -> Self { self.file_flags.set(named::Flags::PREFER_UTF8, value); self } - /// Specifies custom guards to use for directory listings and files. + /// Adds a routing guard. /// - /// Default behaviour allows GET and HEAD. - #[inline] - pub fn use_guards(mut self, guards: G) -> Self { - self.use_guards = Some(Rc::new(guards)); - self - } - - /// Add match guard to use on directory listings and files. + /// Use this to allow multiple chained file services that respond to strictly different + /// properties of a request. Due to the way routing works, if a guard check returns true and the + /// request starts being handled by the file service, it will not be able to back-out and try + /// the next service, you will simply get a 404 (or 405) error response. /// - /// ```rust - /// use actix_web::{guard, App}; + /// To allow `POST` requests to retrieve files, see [`Files::use_guards`]. + /// + /// # Examples + /// ``` + /// use actix_web::{guard::Header, App}; /// use actix_files::Files; /// - /// - /// fn main() { - /// let app = App::new() - /// .service( - /// Files::new("/","/my/site/files") - /// .guard(guard::Header("Host", "example.com")) - /// ); - /// } + /// App::new().service( + /// Files::new("/","/my/site/files") + /// .guard(Header("Host", "example.com")) + /// ); /// ``` - #[inline] pub fn guard(mut self, guard: G) -> Self { self.guards.push(Box::new(Rc::new(guard))); self } + /// Specifies guard to check before fetching directory listings or files. + /// + /// Note that this guard has no effect on routing; it's main use is to guard on the request's + /// method just before serving the file, only allowing `GET` and `HEAD` requests by default. + /// See [`Files::guard`] for routing guards. + pub fn method_guard(mut self, guard: G) -> Self { + self.use_guards = Some(Rc::new(guard)); + self + } + + #[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) + } + /// Disable `Content-Disposition` header. /// /// By default Content-Disposition` header is enabled. - #[inline] pub fn disable_content_disposition(mut self) -> Self { self.file_flags.remove(named::Flags::CONTENT_DISPOSITION); self @@ -223,8 +230,9 @@ impl Files { /// Sets default handler which is used when no matched file could be found. /// - /// For example, you could set a fall back static file handler: - /// ```rust + /// # Examples + /// Setting a fallback static file handler: + /// ``` /// use actix_files::{Files, NamedFile}; /// /// # fn run() -> Result<(), actix_web::Error> { @@ -253,7 +261,6 @@ impl Files { } /// Enables serving hidden files and directories, allowing a leading dots in url fragments. - #[inline] pub fn use_hidden_files(mut self) -> Self { self.hidden_files = true; self diff --git a/actix-files/src/lib.rs b/actix-files/src/lib.rs index 34b02581e..0384ff2b0 100644 --- a/actix-files/src/lib.rs +++ b/actix-files/src/lib.rs @@ -532,7 +532,7 @@ mod tests { #[actix_rt::test] async fn test_files_guards() { let srv = test::init_service( - App::new().service(Files::new("/", ".").use_guards(guard::Post())), + App::new().service(Files::new("/", ".").method_guard(guard::Post())), ) .await; From 6a29a50f25fd103c61e93d97dbee5745640bb911 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Thu, 22 Apr 2021 18:37:45 +0100 Subject: [PATCH 183/339] files doc wording --- actix-files/src/files.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/actix-files/src/files.rs b/actix-files/src/files.rs index aeb75e116..8e28cb45e 100644 --- a/actix-files/src/files.rs +++ b/actix-files/src/files.rs @@ -81,10 +81,9 @@ impl Files { /// If the mount path is set as the root path `/`, services registered after this one will /// be inaccessible. Register more specific handlers and services first. /// - /// `Files` uses a threadpool for blocking filesystem operations. By default, the pool uses a - /// max number of threads equal to `512 * HttpServer::worker`. Real time thread count are - /// adjusted with work load. More threads would spawn when need and threads goes idle for a - /// period of time would be de-spawned. + /// `Files` utilizes the existing Tokio thread-pool for blocking filesystem operations. + /// The number of running threads is adjusted over time as needed, up to a maximum of 512 times + /// the number of server [workers](HttpServer::workers), by default. pub fn new>(mount_path: &str, serve_from: T) -> Files { let orig_dir = serve_from.into(); let dir = match orig_dir.canonicalize() { From 1fcf92e11f1b18053ecc1df13be2d79e0efc1ca9 Mon Sep 17 00:00:00 2001 From: Voldracarno Draconor Date: Wed, 28 Apr 2021 02:23:12 +0200 Subject: [PATCH 184/339] Update dependency "language-tags" (#2188) --- CHANGES.md | 2 ++ Cargo.toml | 2 +- actix-http/CHANGES.md | 1 + actix-http/Cargo.toml | 2 +- src/http/header/accept_language.rs | 14 +++++--------- src/http/header/content_language.rs | 12 +++++------- 6 files changed, 15 insertions(+), 18 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index bea9ab935..6fe0174ad 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,8 @@ # Changes ## Unreleased - 2021-xx-xx +### Changed +* Update `language-tags` to `0.3`. ## 4.0.0-beta.6 - 2021-04-17 diff --git a/Cargo.toml b/Cargo.toml index 714da13a0..75b5e3a8e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -78,7 +78,7 @@ 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" -language-tags = "0.2" +language-tags = "0.3" once_cell = "1.5" log = "0.4" mime = "0.3" diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index e89208748..f1d24c3c3 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -9,6 +9,7 @@ ### Changed * `header` mod is now public. [#2171] * `uri` mod is now public. [#2171] +* Update `language-tags` to `0.3`. ### 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 55dc6d05e..638557807 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -57,7 +57,7 @@ h2 = "0.3.1" http = "0.2.2" httparse = "1.3" itoa = "0.4" -language-tags = "0.2" +language-tags = "0.3" local-channel = "0.1" once_cell = "1.5" log = "0.4" diff --git a/src/http/header/accept_language.rs b/src/http/header/accept_language.rs index 5fab4f79c..034946d4d 100644 --- a/src/http/header/accept_language.rs +++ b/src/http/header/accept_language.rs @@ -24,14 +24,11 @@ crate::__define_common_header! { /// # Examples /// /// ``` - /// use language_tags::langtag; /// use actix_web::HttpResponse; /// use actix_web::http::header::{AcceptLanguage, LanguageTag, qitem}; /// /// let mut builder = HttpResponse::Ok(); - /// let mut langtag: LanguageTag = Default::default(); - /// langtag.language = Some("en".to_owned()); - /// langtag.region = Some("US".to_owned()); + /// let langtag = LanguageTag::parse("en-US").unwrap(); /// builder.insert_header( /// AcceptLanguage(vec![ /// qitem(langtag), @@ -40,16 +37,15 @@ crate::__define_common_header! { /// ``` /// /// ``` - /// use language_tags::langtag; /// use actix_web::HttpResponse; - /// use actix_web::http::header::{AcceptLanguage, QualityItem, q, qitem}; + /// use actix_web::http::header::{AcceptLanguage, LanguageTag, QualityItem, q, qitem}; /// /// let mut builder = HttpResponse::Ok(); /// builder.insert_header( /// AcceptLanguage(vec![ - /// qitem(langtag!(da)), - /// QualityItem::new(langtag!(en;;;GB), q(800)), - /// QualityItem::new(langtag!(en), q(700)), + /// qitem(LanguageTag::parse("da").unwrap()), + /// QualityItem::new(LanguageTag::parse("en-GB").unwrap(), q(800)), + /// QualityItem::new(LanguageTag::parse("en").unwrap(), q(700)), /// ]) /// ); /// ``` diff --git a/src/http/header/content_language.rs b/src/http/header/content_language.rs index 41e6d9eff..c2469edd1 100644 --- a/src/http/header/content_language.rs +++ b/src/http/header/content_language.rs @@ -24,28 +24,26 @@ crate::__define_common_header! { /// # Examples /// /// ``` - /// use language_tags::langtag; /// use actix_web::HttpResponse; - /// use actix_web::http::header::{ContentLanguage, qitem}; + /// use actix_web::http::header::{ContentLanguage, LanguageTag, qitem}; /// /// let mut builder = HttpResponse::Ok(); /// builder.insert_header( /// ContentLanguage(vec![ - /// qitem(langtag!(en)), + /// qitem(LanguageTag::parse("en").unwrap()), /// ]) /// ); /// ``` /// /// ``` - /// use language_tags::langtag; /// use actix_web::HttpResponse; - /// use actix_web::http::header::{ContentLanguage, qitem}; + /// use actix_web::http::header::{ContentLanguage, LanguageTag, qitem}; /// /// let mut builder = HttpResponse::Ok(); /// builder.insert_header( /// ContentLanguage(vec![ - /// qitem(langtag!(da)), - /// qitem(langtag!(en;;;GB)), + /// qitem(LanguageTag::parse("da").unwrap()), + /// qitem(LanguageTag::parse("en-GB").unwrap()), /// ]) /// ); /// ``` From 3a0fb3f89e82fa3287d9741533e4d8dfd38c1548 Mon Sep 17 00:00:00 2001 From: Ibraheem Ahmed Date: Fri, 30 Apr 2021 22:02:56 -0400 Subject: [PATCH 185/339] Static either extract future (#2184) --- src/types/either.rs | 130 +++++++++++++++++++++++++++++++++++--------- 1 file changed, 103 insertions(+), 27 deletions(-) diff --git a/src/types/either.rs b/src/types/either.rs index 210495e47..d3b003587 100644 --- a/src/types/either.rs +++ b/src/types/either.rs @@ -1,8 +1,14 @@ //! For either helper, see [`Either`]. +use std::{ + future::Future, + mem, + pin::Pin, + task::{Context, Poll}, +}; + use bytes::Bytes; -use futures_core::future::LocalBoxFuture; -use futures_util::{FutureExt as _, TryFutureExt as _}; +use futures_core::ready; use crate::{ dev, @@ -180,41 +186,111 @@ where R: FromRequest + 'static, { type Error = EitherExtractError; - type Future = LocalBoxFuture<'static, Result>; + type Future = EitherExtractFut; type Config = (); fn from_request(req: &HttpRequest, payload: &mut dev::Payload) -> Self::Future { - let req2 = req.clone(); - - Bytes::from_request(req, payload) - .map_err(EitherExtractError::Bytes) - .and_then(|bytes| bytes_to_l_or_r(req2, bytes)) - .boxed_local() + EitherExtractFut { + req: req.clone(), + state: EitherExtractState::Bytes { + bytes: Bytes::from_request(req, payload), + }, + } } } -async fn bytes_to_l_or_r( - req: HttpRequest, - bytes: Bytes, -) -> Result, EitherExtractError> +#[pin_project::pin_project] +pub struct EitherExtractFut where - L: FromRequest + 'static, - R: FromRequest + 'static, + R: FromRequest, + L: FromRequest, { - let fallback = bytes.clone(); - let a_err; + req: HttpRequest, + #[pin] + state: EitherExtractState, +} - let mut pl = payload_from_bytes(bytes); - match L::from_request(&req, &mut pl).await { - Ok(a_data) => return Ok(Either::Left(a_data)), - // store A's error for returning if B also fails - Err(err) => a_err = err, - }; +#[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, + }, +} - let mut pl = payload_from_bytes(fallback); - match R::from_request(&req, &mut pl).await { - Ok(b_data) => return Ok(Either::Right(b_data)), - Err(b_err) => Err(EitherExtractError::Extract(a_err, b_err)), +impl Future for EitherExtractFut +where + L: FromRequest, + R: FromRequest, + LF: Future> + 'static, + RF: Future> + 'static, + LE: Into, + RE: Into, +{ + type Output = Result, EitherExtractError>; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let mut this = self.project(); + let ready = loop { + let next = match this.state.as_mut().project() { + EitherExtractProj::Bytes { bytes } => { + let res = ready!(bytes.poll(cx)); + match res { + Ok(bytes) => { + let fallback = bytes.clone(); + let left = + L::from_request(&this.req, &mut payload_from_bytes(bytes)); + EitherExtractState::Left { left, fallback } + } + Err(err) => break Err(EitherExtractError::Bytes(err)), + } + } + EitherExtractProj::Left { left, fallback } => { + let res = ready!(left.poll(cx)); + match res { + Ok(extracted) => break Ok(Either::Left(extracted)), + Err(left_err) => { + let right = R::from_request( + &this.req, + &mut payload_from_bytes(mem::take(fallback)), + ); + EitherExtractState::Right { + left_err: Some(left_err), + right, + } + } + } + } + EitherExtractProj::Right { right, left_err } => { + let res = ready!(right.poll(cx)); + match res { + Ok(data) => break Ok(Either::Right(data)), + Err(err) => { + break Err(EitherExtractError::Extract( + left_err.take().unwrap(), + err, + )); + } + } + } + }; + this.state.set(next); + }; + Poll::Ready(ready) } } From c17662fe39523ce4230234004c572618932be2c7 Mon Sep 17 00:00:00 2001 From: Luca Palmieri Date: Mon, 3 May 2021 00:58:14 +0100 Subject: [PATCH 186/339] Reduce the level of the emitted log line from `error` to `debug`. (#2196) Co-authored-by: Rob Ede --- actix-http/CHANGES.md | 2 ++ actix-http/src/response.rs | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index f1d24c3c3..cc6330288 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -10,11 +10,13 @@ * `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`. [#2196] ### Removed * Stop re-exporting `http` crate's `HeaderMap` types in addition to ours. [#2171] [#2171]: https://github.com/actix/actix-web/pull/2171 +[#2196]: https://github.com/actix/actix-web/pull/2196 ## 3.0.0-beta.6 - 2021-04-17 diff --git a/actix-http/src/response.rs b/actix-http/src/response.rs index a3ab1175c..e11ceb18f 100644 --- a/actix-http/src/response.rs +++ b/actix-http/src/response.rs @@ -78,7 +78,7 @@ impl Response { pub fn from_error(error: Error) -> Response { let mut resp = error.as_response_error().error_response(); if resp.head.status == StatusCode::INTERNAL_SERVER_ERROR { - error!("Internal Server Error: {:?}", error); + debug!("Internal Server Error: {:?}", error); } resp.error = Some(error); resp From dd1a3e7675df26748d070e94912ebe1587cb9241 Mon Sep 17 00:00:00 2001 From: Aaron Hill Date: Wed, 5 May 2021 06:16:12 -0400 Subject: [PATCH 187/339] Fix loophole in soundness of `__private_get_type_id__` (#2199) --- actix-http/src/macros.rs | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/actix-http/src/macros.rs b/actix-http/src/macros.rs index 7cf0e288b..be8e63d6e 100644 --- a/actix-http/src/macros.rs +++ b/actix-http/src/macros.rs @@ -15,8 +15,15 @@ macro_rules! downcast_get_type_id { /// 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) -> (std::any::TypeId, PrivateHelper) + fn __private_get_type_id__( + &self, + _: PrivateHelper, + ) -> (std::any::TypeId, PrivateHelper) where Self: 'static, { @@ -39,7 +46,9 @@ macro_rules! downcast { impl dyn $name + 'static { /// Downcasts generic body to a specific type. pub fn downcast_ref(&self) -> Option<&T> { - if self.__private_get_type_id__().0 == std::any::TypeId::of::() { + 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 @@ -53,7 +62,9 @@ macro_rules! downcast { /// Downcasts a generic body to a mutable specific type. pub fn downcast_mut(&mut self) -> Option<&mut T> { - if self.__private_get_type_id__().0 == std::any::TypeId::of::() { + 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 From ddaf8c3e4379d915ff9ba05bcf2d55227c21ab8e Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Wed, 5 May 2021 18:36:02 +0100 Subject: [PATCH 188/339] add associated error type to MessageBody (#2183) --- actix-http/CHANGES.md | 4 + actix-http/src/body/body.rs | 75 +++++++++++++++++-- actix-http/src/body/body_stream.rs | 4 +- actix-http/src/body/message_body.rs | 105 +++++++++++++++++++++++---- actix-http/src/body/mod.rs | 10 ++- actix-http/src/body/response_body.rs | 25 +++++-- actix-http/src/body/sized_stream.rs | 4 +- actix-http/src/builder.rs | 8 +- actix-http/src/client/connection.rs | 5 +- actix-http/src/client/h1proto.rs | 9 ++- actix-http/src/client/h2proto.rs | 30 +++++--- actix-http/src/encoding/encoder.rs | 85 +++++++++++++++++++--- actix-http/src/error.rs | 6 +- actix-http/src/h1/dispatcher.rs | 32 ++++++++ actix-http/src/h1/service.rs | 22 ++++++ actix-http/src/h1/utils.rs | 2 + actix-http/src/h2/dispatcher.rs | 7 ++ actix-http/src/h2/service.rs | 13 ++++ actix-http/src/response.rs | 6 +- actix-http/src/service.rs | 31 +++++++- actix-test/src/lib.rs | 2 + src/middleware/compat.rs | 6 +- src/middleware/logger.rs | 13 +++- src/response/response.rs | 6 +- src/server.rs | 1 + src/service.rs | 6 +- src/test.rs | 4 + 27 files changed, 447 insertions(+), 74 deletions(-) diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index cc6330288..f398b1c92 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -2,11 +2,13 @@ ## Unreleased - 2021-xx-xx ### Added +* `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] ### Changed +* The `MessageBody` trait now has an associated `Error` type. [#2183] * `header` mod is now public. [#2171] * `uri` mod is now public. [#2171] * Update `language-tags` to `0.3`. @@ -14,8 +16,10 @@ ### Removed * Stop re-exporting `http` crate's `HeaderMap` types in addition to ours. [#2171] +* Down-casting for `MessageBody` types. [#2183] [#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 diff --git a/actix-http/src/body/body.rs b/actix-http/src/body/body.rs index 4fe18338a..4c95bd31a 100644 --- a/actix-http/src/body/body.rs +++ b/actix-http/src/body/body.rs @@ -1,16 +1,17 @@ use std::{ borrow::Cow, + error::Error as StdError, fmt, mem, pin::Pin, task::{Context, Poll}, }; use bytes::{Bytes, BytesMut}; -use futures_core::Stream; +use futures_core::{ready, Stream}; use crate::error::Error; -use super::{BodySize, BodyStream, MessageBody, SizedStream}; +use super::{BodySize, BodyStream, MessageBody, MessageBodyMapErr, SizedStream}; /// Represents various types of HTTP message body. // #[deprecated(since = "4.0.0", note = "Use body types directly.")] @@ -25,7 +26,7 @@ pub enum Body { Bytes(Bytes), /// Generic message body. - Message(Pin>), + Message(BoxAnyBody), } impl Body { @@ -35,12 +36,18 @@ impl Body { } /// Create body from generic message body. - pub fn from_message(body: B) -> Body { - Body::Message(Box::pin(body)) + pub fn from_message(body: B) -> Body + where + B: MessageBody + 'static, + B::Error: Into>, + { + Self::Message(BoxAnyBody::from_body(body)) } } impl MessageBody for Body { + type Error = Error; + fn size(&self) -> BodySize { match self { Body::None => BodySize::None, @@ -53,7 +60,7 @@ impl MessageBody for Body { fn poll_next( self: Pin<&mut Self>, cx: &mut Context<'_>, - ) -> Poll>> { + ) -> Poll>> { match self.get_mut() { Body::None => Poll::Ready(None), Body::Empty => Poll::Ready(None), @@ -65,7 +72,13 @@ impl MessageBody for Body { Poll::Ready(Some(Ok(mem::take(bin)))) } } - Body::Message(body) => body.as_mut().poll_next(cx), + + // TODO: MSRV 1.51: poll_map_err + Body::Message(body) => match ready!(body.as_pin_mut().poll_next(cx)) { + Some(Err(err)) => Poll::Ready(Some(Err(err.into()))), + Some(Ok(val)) => Poll::Ready(Some(Ok(val))), + None => Poll::Ready(None), + }, } } } @@ -166,3 +179,51 @@ where Body::from_message(s) } } + +/// A boxed message body with boxed errors. +pub struct BoxAnyBody(Pin>>>); + +impl BoxAnyBody { + /// 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 BoxAnyBody { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("BoxAnyBody(dyn MessageBody)") + } +} + +impl MessageBody for BoxAnyBody { + type Error = Error; + + fn size(&self) -> BodySize { + self.0.size() + } + + fn poll_next( + 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(err.into()))), + 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 b81aeb4c1..ebe872022 100644 --- a/actix-http/src/body/body_stream.rs +++ b/actix-http/src/body/body_stream.rs @@ -36,6 +36,8 @@ where S: Stream>, E: Into, { + type Error = Error; + fn size(&self) -> BodySize { BodySize::Stream } @@ -48,7 +50,7 @@ where fn poll_next( mut self: Pin<&mut Self>, cx: &mut Context<'_>, - ) -> Poll>> { + ) -> Poll>> { loop { let stream = self.as_mut().project().stream; diff --git a/actix-http/src/body/message_body.rs b/actix-http/src/body/message_body.rs index 894a5fa98..2d2642ba7 100644 --- a/actix-http/src/body/message_body.rs +++ b/actix-http/src/body/message_body.rs @@ -1,12 +1,15 @@ //! [`MessageBody`] trait and foreign implementations. use std::{ + convert::Infallible, mem, pin::Pin, task::{Context, Poll}, }; use bytes::{Bytes, BytesMut}; +use futures_core::ready; +use pin_project_lite::pin_project; use crate::error::Error; @@ -14,6 +17,8 @@ use super::BodySize; /// An interface for response bodies. pub trait MessageBody { + type Error; + /// Body size hint. fn size(&self) -> BodySize; @@ -21,14 +26,12 @@ pub trait MessageBody { fn poll_next( self: Pin<&mut Self>, cx: &mut Context<'_>, - ) -> Poll>>; - - downcast_get_type_id!(); + ) -> Poll>>; } -downcast!(MessageBody); - impl MessageBody for () { + type Error = Infallible; + fn size(&self) -> BodySize { BodySize::Empty } @@ -36,12 +39,18 @@ impl MessageBody for () { fn poll_next( self: Pin<&mut Self>, _: &mut Context<'_>, - ) -> Poll>> { + ) -> Poll>> { Poll::Ready(None) } } -impl MessageBody for Box { +impl MessageBody for Box +where + B: MessageBody + Unpin, + B::Error: Into, +{ + type Error = B::Error; + fn size(&self) -> BodySize { self.as_ref().size() } @@ -49,12 +58,18 @@ impl MessageBody for Box { fn poll_next( self: Pin<&mut Self>, cx: &mut Context<'_>, - ) -> Poll>> { + ) -> Poll>> { Pin::new(self.get_mut().as_mut()).poll_next(cx) } } -impl MessageBody for Pin> { +impl MessageBody for Pin> +where + B: MessageBody, + B::Error: Into, +{ + type Error = B::Error; + fn size(&self) -> BodySize { self.as_ref().size() } @@ -62,12 +77,14 @@ impl MessageBody for Pin> { fn poll_next( mut self: Pin<&mut Self>, cx: &mut Context<'_>, - ) -> Poll>> { + ) -> Poll>> { self.as_mut().poll_next(cx) } } impl MessageBody for Bytes { + type Error = Infallible; + fn size(&self) -> BodySize { BodySize::Sized(self.len() as u64) } @@ -75,7 +92,7 @@ impl MessageBody for Bytes { fn poll_next( self: Pin<&mut Self>, _: &mut Context<'_>, - ) -> Poll>> { + ) -> Poll>> { if self.is_empty() { Poll::Ready(None) } else { @@ -85,6 +102,8 @@ impl MessageBody for Bytes { } impl MessageBody for BytesMut { + type Error = Infallible; + fn size(&self) -> BodySize { BodySize::Sized(self.len() as u64) } @@ -92,7 +111,7 @@ impl MessageBody for BytesMut { fn poll_next( self: Pin<&mut Self>, _: &mut Context<'_>, - ) -> Poll>> { + ) -> Poll>> { if self.is_empty() { Poll::Ready(None) } else { @@ -102,6 +121,8 @@ impl MessageBody for BytesMut { } impl MessageBody for &'static str { + type Error = Infallible; + fn size(&self) -> BodySize { BodySize::Sized(self.len() as u64) } @@ -109,7 +130,7 @@ impl MessageBody for &'static str { fn poll_next( self: Pin<&mut Self>, _: &mut Context<'_>, - ) -> Poll>> { + ) -> Poll>> { if self.is_empty() { Poll::Ready(None) } else { @@ -121,6 +142,8 @@ impl MessageBody for &'static str { } impl MessageBody for Vec { + type Error = Infallible; + fn size(&self) -> BodySize { BodySize::Sized(self.len() as u64) } @@ -128,7 +151,7 @@ impl MessageBody for Vec { fn poll_next( self: Pin<&mut Self>, _: &mut Context<'_>, - ) -> Poll>> { + ) -> Poll>> { if self.is_empty() { Poll::Ready(None) } else { @@ -138,6 +161,8 @@ impl MessageBody for Vec { } impl MessageBody for String { + type Error = Infallible; + fn size(&self) -> BodySize { BodySize::Sized(self.len() as u64) } @@ -145,7 +170,7 @@ impl MessageBody for String { fn poll_next( self: Pin<&mut Self>, _: &mut Context<'_>, - ) -> Poll>> { + ) -> Poll>> { if self.is_empty() { Poll::Ready(None) } else { @@ -155,3 +180,53 @@ impl MessageBody for String { } } } + +pin_project! { + pub(crate) struct MessageBodyMapErr { + #[pin] + body: B, + mapper: Option, + } +} + +impl MessageBodyMapErr +where + B: MessageBody, + F: FnOnce(B::Error) -> E, +{ + pub(crate) fn new(body: B, mapper: F) -> Self { + Self { + body, + mapper: Some(mapper), + } + } +} + +impl MessageBody for MessageBodyMapErr +where + B: MessageBody, + F: FnOnce(B::Error) -> E, +{ + type Error = E; + + fn size(&self) -> BodySize { + self.body.size() + } + + fn poll_next( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + ) -> Poll>> { + let this = self.as_mut().project(); + + match ready!(this.body.poll_next(cx)) { + Some(Err(err)) => { + let f = self.as_mut().project().mapper.take().unwrap(); + let mapped_err = (f)(err); + Poll::Ready(Some(Err(mapped_err))) + } + Some(Ok(val)) => Poll::Ready(Some(Ok(val))), + None => Poll::Ready(None), + } + } +} diff --git a/actix-http/src/body/mod.rs b/actix-http/src/body/mod.rs index f26d6a8cf..d21cca60b 100644 --- a/actix-http/src/body/mod.rs +++ b/actix-http/src/body/mod.rs @@ -15,9 +15,10 @@ mod response_body; mod size; mod sized_stream; -pub use self::body::Body; +pub use self::body::{Body, BoxAnyBody}; 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; @@ -41,7 +42,7 @@ pub use self::sized_stream::SizedStream; /// assert_eq!(bytes, b"123"[..]); /// # } /// ``` -pub async fn to_bytes(body: impl MessageBody) -> Result { +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::Sized(size) => size as usize, @@ -237,10 +238,13 @@ mod tests { ); } + // 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 resp_body: &mut dyn MessageBody = &mut body; + // 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(); diff --git a/actix-http/src/body/response_body.rs b/actix-http/src/body/response_body.rs index b27112475..855c742f2 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::Stream; +use futures_core::{ready, Stream}; use pin_project::pin_project; use crate::error::Error; @@ -43,7 +43,13 @@ impl ResponseBody { } } -impl MessageBody for ResponseBody { +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(), @@ -54,12 +60,16 @@ impl MessageBody for ResponseBody { fn poll_next( self: Pin<&mut Self>, cx: &mut Context<'_>, - ) -> Poll>> { + ) -> Poll>> { Stream::poll_next(self, cx) } } -impl Stream for ResponseBody { +impl Stream for ResponseBody +where + B: MessageBody, + B::Error: Into, +{ type Item = Result; fn poll_next( @@ -67,7 +77,12 @@ impl Stream for ResponseBody { cx: &mut Context<'_>, ) -> Poll> { match self.project() { - ResponseBodyProj::Body(body) => body.poll_next(cx), + // 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::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 f0332fc8f..4af132389 100644 --- a/actix-http/src/body/sized_stream.rs +++ b/actix-http/src/body/sized_stream.rs @@ -36,6 +36,8 @@ impl MessageBody for SizedStream where S: Stream>, { + type Error = Error; + fn size(&self) -> BodySize { BodySize::Sized(self.size as u64) } @@ -48,7 +50,7 @@ where fn poll_next( mut self: Pin<&mut Self>, cx: &mut Context<'_>, - ) -> Poll>> { + ) -> Poll>> { loop { let stream = self.as_mut().project().stream; diff --git a/actix-http/src/builder.rs b/actix-http/src/builder.rs index 623bfdda2..660cd9817 100644 --- a/actix-http/src/builder.rs +++ b/actix-http/src/builder.rs @@ -202,11 +202,13 @@ where /// Finish service configuration and create a HTTP service for HTTP/2 protocol. pub fn h2(self, service: F) -> H2Service where - B: MessageBody + 'static, F: IntoServiceFactory, 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,11 +225,13 @@ where /// Finish service configuration and create `HttpService` instance. pub fn finish(self, service: F) -> HttpService where - B: MessageBody + 'static, F: IntoServiceFactory, 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/client/connection.rs b/actix-http/src/client/connection.rs index 0e3e97f3f..a30f651ca 100644 --- a/actix-http/src/client/connection.rs +++ b/actix-http/src/client/connection.rs @@ -12,10 +12,10 @@ use bytes::Bytes; use futures_core::future::LocalBoxFuture; use h2::client::SendRequest; -use crate::body::MessageBody; use crate::h1::ClientCodec; use crate::message::{RequestHeadType, ResponseHead}; use crate::payload::Payload; +use crate::{body::MessageBody, Error}; use super::error::SendRequestError; use super::pool::Acquired; @@ -256,8 +256,9 @@ where body: RB, ) -> LocalBoxFuture<'static, Result<(ResponseHead, Payload), SendRequestError>> where - RB: MessageBody + 'static, H: Into + 'static, + RB: MessageBody + 'static, + RB::Error: Into, { Box::pin(async move { match self { diff --git a/actix-http/src/client/h1proto.rs b/actix-http/src/client/h1proto.rs index fa4469d35..65a30748c 100644 --- a/actix-http/src/client/h1proto.rs +++ b/actix-http/src/client/h1proto.rs @@ -11,7 +11,6 @@ use bytes::{Bytes, BytesMut}; use futures_core::{ready, Stream}; use futures_util::SinkExt as _; -use crate::error::PayloadError; use crate::h1; use crate::http::{ header::{HeaderMap, IntoHeaderValue, EXPECT, HOST}, @@ -19,6 +18,7 @@ use crate::http::{ }; use crate::message::{RequestHeadType, ResponseHead}; use crate::payload::Payload; +use crate::{error::PayloadError, Error}; use super::connection::{ConnectionIo, H1Connection}; use super::error::{ConnectError, SendRequestError}; @@ -32,6 +32,7 @@ pub(crate) async fn send_request( where Io: ConnectionIo, B: MessageBody, + B::Error: Into, { // set request host header if !head.as_ref().headers.contains_key(HOST) @@ -154,6 +155,7 @@ pub(crate) async fn send_body( where Io: ConnectionIo, B: MessageBody, + B::Error: Into, { actix_rt::pin!(body); @@ -161,9 +163,10 @@ where while !eof { while !eof && !framed.as_ref().is_write_buf_full() { match poll_fn(|cx| body.as_mut().poll_next(cx)).await { - Some(result) => { - framed.as_mut().write(h1::Message::Chunk(Some(result?)))?; + Some(Ok(chunk)) => { + framed.as_mut().write(h1::Message::Chunk(Some(chunk)))?; } + Some(Err(err)) => return Err(err.into().into()), None => { eof = true; framed.as_mut().write(h1::Message::Chunk(None))?; diff --git a/actix-http/src/client/h2proto.rs b/actix-http/src/client/h2proto.rs index 8cb2e2522..cf423ef12 100644 --- a/actix-http/src/client/h2proto.rs +++ b/actix-http/src/client/h2proto.rs @@ -9,14 +9,19 @@ use h2::{ use http::header::{HeaderValue, CONNECTION, CONTENT_LENGTH, TRANSFER_ENCODING}; use http::{request::Request, Method, Version}; -use crate::body::{BodySize, MessageBody}; -use crate::header::HeaderMap; -use crate::message::{RequestHeadType, ResponseHead}; -use crate::payload::Payload; +use crate::{ + body::{BodySize, MessageBody}, + header::HeaderMap, + message::{RequestHeadType, ResponseHead}, + payload::Payload, + Error, +}; -use super::config::ConnectorConfig; -use super::connection::{ConnectionIo, H2Connection}; -use super::error::SendRequestError; +use super::{ + config::ConnectorConfig, + connection::{ConnectionIo, H2Connection}, + error::SendRequestError, +}; pub(crate) async fn send_request( mut io: H2Connection, @@ -26,6 +31,7 @@ pub(crate) async fn send_request( where Io: ConnectionIo, B: MessageBody, + B::Error: Into, { trace!("Sending client request: {:?} {:?}", head, body.size()); @@ -125,10 +131,14 @@ where Ok((head, payload)) } -async fn send_body( +async fn send_body( body: B, mut send: SendStream, -) -> Result<(), SendRequestError> { +) -> Result<(), SendRequestError> +where + B: MessageBody, + B::Error: Into, +{ let mut buf = None; actix_rt::pin!(body); loop { @@ -138,7 +148,7 @@ async fn send_body( send.reserve_capacity(b.len()); buf = Some(b); } - Some(Err(e)) => return Err(e.into()), + Some(Err(e)) => return Err(e.into().into()), None => { if let Err(e) = send.send_data(Bytes::new(), true) { return Err(e.into()); diff --git a/actix-http/src/encoding/encoder.rs b/actix-http/src/encoding/encoder.rs index add6ee980..b8bc8b68d 100644 --- a/actix-http/src/encoding/encoder.rs +++ b/actix-http/src/encoding/encoder.rs @@ -1,6 +1,7 @@ //! Stream encoders. use std::{ + error::Error as StdError, future::Future, io::{self, Write as _}, pin::Pin, @@ -10,12 +11,13 @@ 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; use crate::{ - body::{Body, BodySize, MessageBody, ResponseBody}, + body::{Body, BodySize, BoxAnyBody, MessageBody, ResponseBody}, http::{ header::{ContentEncoding, CONTENT_ENCODING}, HeaderValue, StatusCode, @@ -92,10 +94,16 @@ impl Encoder { enum EncoderBody { Bytes(Bytes), Stream(#[pin] B), - BoxedStream(Pin>), + BoxedStream(BoxAnyBody), } -impl MessageBody for EncoderBody { +impl MessageBody for EncoderBody +where + B: MessageBody, + B::Error: Into, +{ + type Error = EncoderError; + fn size(&self) -> BodySize { match self { EncoderBody::Bytes(ref b) => b.size(), @@ -107,7 +115,7 @@ impl MessageBody for EncoderBody { fn poll_next( self: Pin<&mut Self>, cx: &mut Context<'_>, - ) -> Poll>> { + ) -> Poll>> { match self.project() { EncoderBodyProj::Bytes(b) => { if b.is_empty() { @@ -116,13 +124,32 @@ impl MessageBody for EncoderBody { Poll::Ready(Some(Ok(std::mem::take(b)))) } } - EncoderBodyProj::Stream(b) => b.poll_next(cx), - EncoderBodyProj::BoxedStream(ref mut b) => b.as_mut().poll_next(cx), + // 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::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(Ok(val)) => Poll::Ready(Some(Ok(val))), + None => Poll::Ready(None), + } + } } } } -impl MessageBody for Encoder { +impl MessageBody for Encoder +where + B: MessageBody, + B::Error: Into, +{ + type Error = EncoderError; + fn size(&self) -> BodySize { if self.encoder.is_none() { self.body.size() @@ -134,7 +161,7 @@ impl MessageBody for Encoder { fn poll_next( self: Pin<&mut Self>, cx: &mut Context<'_>, - ) -> Poll>> { + ) -> Poll>> { let mut this = self.project(); loop { if *this.eof { @@ -142,8 +169,9 @@ impl MessageBody for Encoder { } if let Some(ref mut fut) = this.fut { - let mut encoder = - ready!(Pin::new(fut).poll(cx)).map_err(|_| BlockingError)??; + let mut encoder = ready!(Pin::new(fut).poll(cx)) + .map_err(|_| EncoderError::Blocking(BlockingError))? + .map_err(EncoderError::Io)?; let chunk = encoder.take(); *this.encoder = Some(encoder); @@ -162,7 +190,7 @@ impl MessageBody for Encoder { Some(Ok(chunk)) => { if let Some(mut encoder) = this.encoder.take() { if chunk.len() < MAX_CHUNK_SIZE_ENCODE_IN_PLACE { - encoder.write(&chunk)?; + encoder.write(&chunk).map_err(EncoderError::Io)?; let chunk = encoder.take(); *this.encoder = Some(encoder); @@ -182,7 +210,7 @@ impl MessageBody for Encoder { None => { if let Some(encoder) = this.encoder.take() { - let chunk = encoder.finish()?; + let chunk = encoder.finish().map_err(EncoderError::Io)?; if chunk.is_empty() { return Poll::Ready(None); } else { @@ -281,3 +309,36 @@ impl ContentEncoder { } } } + +#[derive(Debug, Display)] +#[non_exhaustive] +pub enum EncoderError { + #[display(fmt = "body")] + Body(E), + + #[display(fmt = "boxed")] + Boxed(Error), + + #[display(fmt = "blocking")] + Blocking(BlockingError), + + #[display(fmt = "io")] + Io(io::Error), +} + +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(), + } + } +} diff --git a/actix-http/src/error.rs b/actix-http/src/error.rs index 39ffa29e7..c92f9076d 100644 --- a/actix-http/src/error.rs +++ b/actix-http/src/error.rs @@ -2,6 +2,7 @@ use std::{ cell::RefCell, + error::Error as StdError, fmt, io::{self, Write as _}, str::Utf8Error, @@ -105,8 +106,7 @@ impl From<()> for Error { impl From for Error { fn from(_: std::convert::Infallible) -> Self { - // `std::convert::Infallible` indicates an error - // that will never happen + // hint that an error that will never happen unreachable!() } } @@ -145,6 +145,8 @@ impl From for Error { #[display(fmt = "Unknown Error")] struct UnitError; +impl ResponseError for Box {} + /// Returns [`StatusCode::INTERNAL_SERVER_ERROR`] for [`UnitError`]. impl ResponseError for UnitError {} diff --git a/actix-http/src/h1/dispatcher.rs b/actix-http/src/h1/dispatcher.rs index 3b272f0fb..7ab89ba87 100644 --- a/actix-http/src/h1/dispatcher.rs +++ b/actix-http/src/h1/dispatcher.rs @@ -51,9 +51,13 @@ pub struct Dispatcher where S: Service, S::Error: Into, + B: MessageBody, + B::Error: Into, + X: Service, X::Error: Into, + U: Service<(Request, Framed), Response = ()>, U::Error: fmt::Display, { @@ -69,9 +73,13 @@ enum DispatcherState where S: Service, S::Error: Into, + B: MessageBody, + B::Error: Into, + X: Service, X::Error: Into, + U: Service<(Request, Framed), Response = ()>, U::Error: fmt::Display, { @@ -84,9 +92,13 @@ struct InnerDispatcher where S: Service, S::Error: Into, + B: MessageBody, + B::Error: Into, + X: Service, X::Error: Into, + U: Service<(Request, Framed), Response = ()>, U::Error: fmt::Display, { @@ -122,7 +134,9 @@ enum State where S: Service, X: Service, + B: MessageBody, + B::Error: Into, { None, ExpectCall(#[pin] X::Future), @@ -133,8 +147,11 @@ where impl State where S: Service, + X: Service, + B: MessageBody, + B::Error: Into, { fn is_empty(&self) -> bool { matches!(self, State::None) @@ -150,12 +167,17 @@ enum PollResponse { impl Dispatcher where T: AsyncRead + AsyncWrite + Unpin, + S: Service, S::Error: Into, S::Response: Into>, + B: MessageBody, + B::Error: Into, + X: Service, X::Error: Into, + U: Service<(Request, Framed), Response = ()>, U::Error: fmt::Display, { @@ -206,12 +228,17 @@ where impl InnerDispatcher where T: AsyncRead + AsyncWrite + Unpin, + S: Service, S::Error: Into, S::Response: Into>, + B: MessageBody, + B::Error: Into, + X: Service, X::Error: Into, + U: Service<(Request, Framed), Response = ()>, U::Error: fmt::Display, { @@ -817,12 +844,17 @@ where impl Future for Dispatcher where T: AsyncRead + AsyncWrite + Unpin, + S: Service, S::Error: Into, S::Response: Into>, + B: MessageBody, + B::Error: Into, + X: Service, X::Error: Into, + U: Service<(Request, Framed), Response = ()>, U::Error: fmt::Display, { diff --git a/actix-http/src/h1/service.rs b/actix-http/src/h1/service.rs index 916643a18..1ab85cbf3 100644 --- a/actix-http/src/h1/service.rs +++ b/actix-http/src/h1/service.rs @@ -64,11 +64,15 @@ where S::Error: Into, S::InitError: fmt::Debug, S::Response: Into>, + B: MessageBody, + B::Error: Into, + X: ServiceFactory, X::Future: 'static, X::Error: Into, X::InitError: fmt::Debug, + U: ServiceFactory<(Request, Framed), Config = (), Response = ()>, U::Future: 'static, U::Error: fmt::Display + Into, @@ -109,11 +113,15 @@ mod openssl { S::Error: Into, S::InitError: fmt::Debug, S::Response: Into>, + B: MessageBody, + B::Error: Into, + X: ServiceFactory, X::Future: 'static, X::Error: Into, X::InitError: fmt::Debug, + U: ServiceFactory< (Request, Framed, Codec>), Config = (), @@ -165,11 +173,15 @@ mod rustls { S::Error: Into, S::InitError: fmt::Debug, S::Response: Into>, + B: MessageBody, + B::Error: Into, + X: ServiceFactory, X::Future: 'static, X::Error: Into, X::InitError: fmt::Debug, + U: ServiceFactory< (Request, Framed, Codec>), Config = (), @@ -253,16 +265,21 @@ impl ServiceFactory<(T, Option)> for H1Service where T: AsyncRead + AsyncWrite + Unpin + 'static, + S: ServiceFactory, S::Future: 'static, S::Error: Into, S::Response: Into>, S::InitError: fmt::Debug, + B: MessageBody, + B::Error: Into, + X: ServiceFactory, X::Future: 'static, X::Error: Into, X::InitError: fmt::Debug, + U: ServiceFactory<(Request, Framed), Config = (), Response = ()>, U::Future: 'static, U::Error: fmt::Display + Into, @@ -319,12 +336,17 @@ impl Service<(T, Option)> for HttpServiceHandler where T: AsyncRead + AsyncWrite + Unpin, + S: Service, S::Error: Into, S::Response: Into>, + B: MessageBody, + B::Error: Into, + X: Service, X::Error: Into, + U: Service<(Request, Framed), Response = ()>, U::Error: fmt::Display + Into, { diff --git a/actix-http/src/h1/utils.rs b/actix-http/src/h1/utils.rs index 9e9c57137..73f01e913 100644 --- a/actix-http/src/h1/utils.rs +++ b/actix-http/src/h1/utils.rs @@ -22,6 +22,7 @@ pub struct SendResponse { impl SendResponse where B: MessageBody, + B::Error: Into, { pub fn new(framed: Framed, response: Response) -> Self { let (res, body) = response.into_parts(); @@ -38,6 +39,7 @@ impl Future for SendResponse where T: AsyncRead + AsyncWrite + Unpin, B: MessageBody + Unpin, + B::Error: Into, { type Output = Result, Error>; diff --git a/actix-http/src/h2/dispatcher.rs b/actix-http/src/h2/dispatcher.rs index 87dd66fe7..07636470b 100644 --- a/actix-http/src/h2/dispatcher.rs +++ b/actix-http/src/h2/dispatcher.rs @@ -69,11 +69,14 @@ where impl Future for Dispatcher where T: AsyncRead + AsyncWrite + Unpin, + S: Service, S::Error: Into + 'static, S::Future: 'static, S::Response: Into> + 'static, + B: MessageBody + 'static, + B::Error: Into, { type Output = Result<(), DispatchError>; @@ -140,7 +143,9 @@ where F: Future>, E: Into, I: Into>, + B: MessageBody, + B::Error: Into, { fn prepare_response( &self, @@ -216,7 +221,9 @@ where F: Future>, E: Into, I: Into>, + B: MessageBody, + B::Error: Into, { type Output = (); diff --git a/actix-http/src/h2/service.rs b/actix-http/src/h2/service.rs index 1a0b8c7f5..a75abef7d 100644 --- a/actix-http/src/h2/service.rs +++ b/actix-http/src/h2/service.rs @@ -40,7 +40,9 @@ where 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>( @@ -69,7 +71,9 @@ where S::Error: Into + 'static, S::Response: Into> + 'static, >::Future: 'static, + B: MessageBody + 'static, + B::Error: Into, { /// Create plain TCP based service pub fn tcp( @@ -106,7 +110,9 @@ mod openssl { S::Error: Into + 'static, S::Response: Into> + 'static, >::Future: 'static, + B: MessageBody + 'static, + B::Error: Into, { /// Create OpenSSL based service pub fn openssl( @@ -150,7 +156,9 @@ mod rustls { S::Error: Into + 'static, S::Response: Into> + 'static, >::Future: 'static, + B: MessageBody + 'static, + B::Error: Into, { /// Create Rustls based service pub fn rustls( @@ -185,12 +193,15 @@ mod rustls { impl ServiceFactory<(T, Option)> for H2Service where T: AsyncRead + AsyncWrite + Unpin + 'static, + S: ServiceFactory, S::Future: 'static, S::Error: Into + 'static, S::Response: Into> + 'static, >::Future: 'static, + B: MessageBody + 'static, + B::Error: Into, { type Response = (); type Error = DispatchError; @@ -252,6 +263,7 @@ where S::Future: 'static, S::Response: Into> + 'static, B: MessageBody + 'static, + B::Error: Into, { type Response = (); type Error = DispatchError; @@ -316,6 +328,7 @@ where S::Future: 'static, S::Response: Into> + 'static, B: MessageBody, + B::Error: Into, { type Output = Result<(), DispatchError>; diff --git a/actix-http/src/response.rs b/actix-http/src/response.rs index e11ceb18f..da5c7e000 100644 --- a/actix-http/src/response.rs +++ b/actix-http/src/response.rs @@ -242,7 +242,11 @@ impl Response { } } -impl fmt::Debug for Response { +impl fmt::Debug for Response +where + B: MessageBody, + B::Error: Into, +{ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let res = writeln!( f, diff --git a/actix-http/src/service.rs b/actix-http/src/service.rs index ff4b49f1d..d25a67a19 100644 --- a/actix-http/src/service.rs +++ b/actix-http/src/service.rs @@ -59,6 +59,7 @@ where S::Response: Into> + 'static, >::Future: 'static, B: MessageBody + 'static, + B::Error: Into, { /// Create new `HttpService` instance. pub fn new>(service: F) -> Self { @@ -157,6 +158,7 @@ where >::Future: 'static, B: MessageBody + 'static, + B::Error: Into, X: ServiceFactory, X::Future: 'static, @@ -208,6 +210,7 @@ mod openssl { >::Future: 'static, B: MessageBody + 'static, + B::Error: Into, X: ServiceFactory, X::Future: 'static, @@ -275,6 +278,7 @@ mod rustls { >::Future: 'static, B: MessageBody + 'static, + B::Error: Into, X: ServiceFactory, X::Future: 'static, @@ -339,6 +343,7 @@ where >::Future: 'static, B: MessageBody + 'static, + B::Error: Into, X: ServiceFactory, X::Future: 'static, @@ -465,13 +470,18 @@ impl Service<(T, Protocol, Option)> for HttpServiceHandler where T: AsyncRead + AsyncWrite + Unpin, + S: Service, S::Error: Into + 'static, S::Future: 'static, S::Response: Into> + 'static, + B: MessageBody + 'static, + B::Error: Into, + X: Service, X::Error: Into, + U: Service<(Request, Framed), Response = ()>, U::Error: fmt::Display + Into, { @@ -522,13 +532,18 @@ where #[pin_project(project = StateProj)] enum State where + T: AsyncRead + AsyncWrite + Unpin, + S: Service, S::Future: 'static, S::Error: Into, - T: AsyncRead + AsyncWrite + Unpin, + B: MessageBody, + B::Error: Into, + X: Service, X::Error: Into, + U: Service<(Request, Framed), Response = ()>, U::Error: fmt::Display, { @@ -549,13 +564,18 @@ where pub struct HttpServiceHandlerResponse where T: AsyncRead + AsyncWrite + Unpin, + S: Service, S::Error: Into + 'static, S::Future: 'static, S::Response: Into> + 'static, - B: MessageBody + 'static, + + B: MessageBody, + B::Error: Into, + X: Service, X::Error: Into, + U: Service<(Request, Framed), Response = ()>, U::Error: fmt::Display, { @@ -566,13 +586,18 @@ where impl Future for HttpServiceHandlerResponse where T: AsyncRead + AsyncWrite + Unpin, + S: Service, S::Error: Into + 'static, S::Future: 'static, S::Response: Into> + 'static, - B: MessageBody, + + B: MessageBody + 'static, + B::Error: Into, + X: Service, X::Error: Into, + U: Service<(Request, Framed), Response = ()>, U::Error: fmt::Display, { diff --git a/actix-test/src/lib.rs b/actix-test/src/lib.rs index 8fab33289..5d85c2687 100644 --- a/actix-test/src/lib.rs +++ b/actix-test/src/lib.rs @@ -86,6 +86,7 @@ where S::Response: Into> + 'static, >::Future: 'static, B: MessageBody + 'static, + B::Error: Into, { start_with(TestServerConfig::default(), factory) } @@ -125,6 +126,7 @@ where S::Response: Into> + 'static, >::Future: 'static, B: MessageBody + 'static, + B::Error: Into, { let (tx, rx) = mpsc::channel(); diff --git a/src/middleware/compat.rs b/src/middleware/compat.rs index 0e3a4f2b7..3a85591da 100644 --- a/src/middleware/compat.rs +++ b/src/middleware/compat.rs @@ -113,7 +113,11 @@ pub trait MapServiceResponseBody { fn map_body(self) -> ServiceResponse; } -impl MapServiceResponseBody for ServiceResponse { +impl MapServiceResponseBody for ServiceResponse +where + B: MessageBody + Unpin + 'static, + B::Error: Into, +{ fn map_body(self) -> ServiceResponse { self.map_body(|_, body| ResponseBody::Other(Body::from_message(body))) } diff --git a/src/middleware/logger.rs b/src/middleware/logger.rs index 40ed9258f..8a60d6c70 100644 --- a/src/middleware/logger.rs +++ b/src/middleware/logger.rs @@ -22,10 +22,9 @@ use time::OffsetDateTime; use crate::{ dev::{BodySize, MessageBody, ResponseBody}, - error::{Error, Result}, http::{HeaderName, StatusCode}, service::{ServiceRequest, ServiceResponse}, - HttpResponse, + Error, HttpResponse, Result, }; /// Middleware for logging request and response summaries to the terminal. @@ -327,7 +326,13 @@ impl PinnedDrop for StreamLog { } } -impl MessageBody for StreamLog { +impl MessageBody for StreamLog +where + B: MessageBody, + B::Error: Into, +{ + type Error = Error; + fn size(&self) -> BodySize { self.body.size() } @@ -335,7 +340,7 @@ impl MessageBody for StreamLog { fn poll_next( self: Pin<&mut Self>, cx: &mut Context<'_>, - ) -> Poll>> { + ) -> Poll>> { let this = self.project(); match this.body.poll_next(cx) { Poll::Ready(Some(Ok(chunk))) => { diff --git a/src/response/response.rs b/src/response/response.rs index 31868fe0b..6e09a0136 100644 --- a/src/response/response.rs +++ b/src/response/response.rs @@ -243,7 +243,11 @@ impl HttpResponse { } } -impl fmt::Debug for 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") .field("error", &self.error) diff --git a/src/server.rs b/src/server.rs index 6577f4d1f..6e11c642f 100644 --- a/src/server.rs +++ b/src/server.rs @@ -81,6 +81,7 @@ where 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 { diff --git a/src/service.rs b/src/service.rs index f6d1f9ebf..0c03f84ad 100644 --- a/src/service.rs +++ b/src/service.rs @@ -443,7 +443,11 @@ impl From> for Response { } } -impl fmt::Debug for ServiceResponse { +impl fmt::Debug for ServiceResponse +where + B: MessageBody, + B::Error: Into, +{ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let res = writeln!( f, diff --git a/src/test.rs b/src/test.rs index c2e456e58..9fe5e9b5d 100644 --- a/src/test.rs +++ b/src/test.rs @@ -151,6 +151,7 @@ pub async fn read_response(app: &S, req: Request) -> Bytes where S: Service, Error = Error>, B: MessageBody + Unpin, + B::Error: Into, { let mut resp = app .call(req) @@ -196,6 +197,7 @@ where pub async fn read_body(mut res: ServiceResponse) -> Bytes where B: MessageBody + Unpin, + B::Error: Into, { let mut body = res.take_body(); let mut bytes = BytesMut::new(); @@ -245,6 +247,7 @@ where pub async fn read_body_json(res: ServiceResponse) -> T where B: MessageBody + Unpin, + B::Error: Into, T: DeserializeOwned, { let body = read_body(res).await; @@ -306,6 +309,7 @@ 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; From 7d1d5c8acdcd99162273b4c44ec42dddcea42f8e Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Fri, 7 May 2021 01:35:04 +0800 Subject: [PATCH 189/339] Expose SererBuilder::worker_max_blocking_threads (#2200) --- CHANGES.md | 5 +++++ src/server.rs | 10 ++++++++++ 2 files changed, 15 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index 6fe0174ad..ead69f293 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,9 +1,14 @@ # Changes ## Unreleased - 2021-xx-xx +### Added +* `HttpServer::worker_max_blocking_threads` for setting block thread pool. [#2200] + ### Changed * Update `language-tags` to `0.3`. +[#2200]: https://github.com/actix/actix-web/pull/2200 + ## 4.0.0-beta.6 - 2021-04-17 ### Added diff --git a/src/server.rs b/src/server.rs index 6e11c642f..44ae6f880 100644 --- a/src/server.rs +++ b/src/server.rs @@ -174,6 +174,16 @@ where self } + /// Set max number of threads for each worker's blocking task thread pool. + /// + /// One thread pool is set up **per worker**; not shared across workers. + /// + /// By default set to 512 / workers. + pub fn worker_max_blocking_threads(mut self, num: usize) -> Self { + self.builder = self.builder.worker_max_blocking_threads(num); + self + } + /// Set server keep-alive setting. /// /// By default keep alive is set to a 5 seconds. From 947caa3599bd842973e1615a71b6fc9073f23dbd Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Thu, 6 May 2021 20:24:18 +0100 Subject: [PATCH 190/339] examples use info log level by default --- actix-files/src/files.rs | 1 + actix-http/examples/echo.rs | 5 ++--- actix-http/examples/echo2.rs | 5 ++--- actix-http/examples/hello-world.rs | 5 ++--- actix-http/examples/ws.rs | 5 ++--- 5 files changed, 9 insertions(+), 12 deletions(-) diff --git a/actix-files/src/files.rs b/actix-files/src/files.rs index 8e28cb45e..b2d69612c 100644 --- a/actix-files/src/files.rs +++ b/actix-files/src/files.rs @@ -66,6 +66,7 @@ impl Clone for Files { } } } + impl Files { /// Create new `Files` instance for a specified base directory. /// diff --git a/actix-http/examples/echo.rs b/actix-http/examples/echo.rs index b2cdb0be1..54a71a106 100644 --- a/actix-http/examples/echo.rs +++ b/actix-http/examples/echo.rs @@ -1,4 +1,4 @@ -use std::{env, io}; +use std::io; use actix_http::{http::StatusCode, Error, HttpService, Request, Response}; use actix_server::Server; @@ -9,8 +9,7 @@ use log::info; #[actix_rt::main] async fn main() -> io::Result<()> { - env::set_var("RUST_LOG", "echo=info"); - env_logger::init(); + env_logger::init_from_env(env_logger::Env::new().default_filter_or("info")); Server::build() .bind("echo", "127.0.0.1:8080", || { diff --git a/actix-http/examples/echo2.rs b/actix-http/examples/echo2.rs index 9acf4bbae..3974cf20b 100644 --- a/actix-http/examples/echo2.rs +++ b/actix-http/examples/echo2.rs @@ -1,4 +1,4 @@ -use std::{env, io}; +use std::io; use actix_http::{body::Body, http::HeaderValue, http::StatusCode}; use actix_http::{Error, HttpService, Request, Response}; @@ -21,8 +21,7 @@ async fn handle_request(mut req: Request) -> Result, Error> { #[actix_rt::main] async fn main() -> io::Result<()> { - env::set_var("RUST_LOG", "echo=info"); - env_logger::init(); + env_logger::init_from_env(env_logger::Env::new().default_filter_or("info")); Server::build() .bind("echo", "127.0.0.1:8080", || { diff --git a/actix-http/examples/hello-world.rs b/actix-http/examples/hello-world.rs index 85994556d..d51de6f4e 100644 --- a/actix-http/examples/hello-world.rs +++ b/actix-http/examples/hello-world.rs @@ -1,4 +1,4 @@ -use std::{env, io}; +use std::io; use actix_http::{http::StatusCode, HttpService, Response}; use actix_server::Server; @@ -8,8 +8,7 @@ use log::info; #[actix_rt::main] async fn main() -> io::Result<()> { - env::set_var("RUST_LOG", "hello_world=info"); - env_logger::init(); + env_logger::init_from_env(env_logger::Env::new().default_filter_or("info")); Server::build() .bind("hello-world", "127.0.0.1:8080", || { diff --git a/actix-http/examples/ws.rs b/actix-http/examples/ws.rs index af66f7d71..1c6f7474c 100644 --- a/actix-http/examples/ws.rs +++ b/actix-http/examples/ws.rs @@ -4,7 +4,7 @@ extern crate tls_rustls as rustls; use std::{ - env, io, + io, pin::Pin, task::{Context, Poll}, time::Duration, @@ -20,8 +20,7 @@ use futures_core::{ready, Stream}; #[actix_rt::main] async fn main() -> io::Result<()> { - env::set_var("RUST_LOG", "actix=info,h2_ws=info"); - env_logger::init(); + env_logger::init_from_env(env_logger::Env::new().default_filter_or("info")); Server::build() .bind("tcp", ("127.0.0.1", 8080), || { From a9dc1586a0935c48c3f841761bf81c43ca9e2651 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Fri, 7 May 2021 10:14:25 +0100 Subject: [PATCH 191/339] remove rogue eprintln --- src/response/builder.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/response/builder.rs b/src/response/builder.rs index 8b3c0f10d..80086bfd3 100644 --- a/src/response/builder.rs +++ b/src/response/builder.rs @@ -422,7 +422,6 @@ impl Future for HttpResponseBuilder { type Output = Result; fn poll(mut self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll { - eprintln!("httpresponse future error"); Poll::Ready(Ok(self.finish())) } } From 900c9e270ebf612fbd4aefdc33f7476d1bfdafe4 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sun, 9 May 2021 20:12:48 +0100 Subject: [PATCH 192/339] remove responsebody indirection from response (#2201) --- .github/workflows/ci.yml | 2 +- CHANGES.md | 8 ++ actix-files/src/service.rs | 3 +- actix-http/CHANGES.md | 10 +- actix-http/examples/ws.rs | 2 +- actix-http/src/body/mod.rs | 9 -- actix-http/src/error.rs | 11 +- actix-http/src/h1/dispatcher.rs | 103 ++++++++++++++---- actix-http/src/h1/encoder.rs | 3 +- actix-http/src/h1/utils.rs | 17 ++- actix-http/src/h2/dispatcher.rs | 69 ++++++++++-- actix-http/src/header/shared/charset.rs | 2 +- actix-http/src/lib.rs | 2 +- actix-http/src/message.rs | 10 +- actix-http/src/response.rs | 134 +++++++++--------------- actix-http/src/response_builder.rs | 18 ++-- actix-http/tests/test_ws.rs | 2 +- src/app_service.rs | 3 +- src/error.rs | 9 +- src/lib.rs | 3 +- src/middleware/compat.rs | 7 +- src/middleware/compress.rs | 14 +-- src/middleware/logger.rs | 25 ++--- src/responder.rs | 18 +++- src/response/builder.rs | 31 +++--- src/response/response.rs | 35 +++---- src/scope.rs | 8 +- src/service.rs | 41 ++++---- src/test.rs | 31 ++++-- src/types/json.rs | 6 +- tests/test_server.rs | 8 +- 31 files changed, 381 insertions(+), 263 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3aac6efa8..585b3f497 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -86,7 +86,7 @@ jobs: uses: actions-rs/cargo@v1 with: command: test - args: -v --workspace --all-features --no-fail-fast -- --nocapture + args: --workspace --all-features --no-fail-fast -- --nocapture --skip=test_h2_content_length --skip=test_reading_deflate_encoding_large_random_rustls diff --git a/CHANGES.md b/CHANGES.md index ead69f293..162f9f61b 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -5,9 +5,17 @@ * `HttpServer::worker_max_blocking_threads` for setting block thread pool. [#2200] ### Changed +* `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] + +### 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 ## 4.0.0-beta.6 - 2021-04-17 diff --git a/actix-files/src/service.rs b/actix-files/src/service.rs index dc51ada18..31e1434bd 100644 --- a/actix-files/src/service.rs +++ b/actix-files/src/service.rs @@ -96,8 +96,7 @@ impl Service for FilesService { return Box::pin(ok(req.into_response( HttpResponse::Found() .insert_header((header::LOCATION, redirect_to)) - .body("") - .into_body(), + .finish(), ))); } diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index f398b1c92..29bc74fe3 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -6,21 +6,29 @@ * 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] ### Changed * The `MessageBody` trait now has an associated `Error` type. [#2183] +* 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`. [#2196] +* 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] ### Removed * Stop re-exporting `http` crate's `HeaderMap` types in addition to ours. [#2171] * Down-casting for `MessageBody` types. [#2183] +* `error::Result` alias. [#2201] +* `impl Future` for `Response`. [#2201] +* `Response::take_body` and old `Response::into_body` method that casted body type. [#2201] [#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 ## 3.0.0-beta.6 - 2021-04-17 diff --git a/actix-http/examples/ws.rs b/actix-http/examples/ws.rs index 1c6f7474c..d3cedf870 100644 --- a/actix-http/examples/ws.rs +++ b/actix-http/examples/ws.rs @@ -40,7 +40,7 @@ async fn handler(req: Request) -> Result>, Error> // handshake will always fail under HTTP/2 log::info!("responding"); - Ok(res.message_body(BodyStream::new(Heartbeat::new(ws::Codec::new())))) + Ok(res.message_body(BodyStream::new(Heartbeat::new(ws::Codec::new())))?) } struct Heartbeat { diff --git a/actix-http/src/body/mod.rs b/actix-http/src/body/mod.rs index d21cca60b..cdfcd226b 100644 --- a/actix-http/src/body/mod.rs +++ b/actix-http/src/body/mod.rs @@ -86,15 +86,6 @@ mod tests { } } - impl ResponseBody { - pub(crate) fn get_ref(&self) -> &[u8] { - match *self { - ResponseBody::Body(ref b) => b.get_ref(), - ResponseBody::Other(ref b) => b.get_ref(), - } - } - } - #[actix_rt::test] async fn test_static_str() { assert_eq!(Body::from("").size(), BodySize::Sized(0)); diff --git a/actix-http/src/error.rs b/actix-http/src/error.rs index c92f9076d..20b2a2d75 100644 --- a/actix-http/src/error.rs +++ b/actix-http/src/error.rs @@ -18,12 +18,6 @@ use crate::{body::Body, helpers::Writer, Response, ResponseBuilder}; pub use http::Error as HttpError; -/// A specialized [`std::result::Result`] for Actix Web operations. -/// -/// This typedef is generally used to avoid writing out `actix_http::error::Error` directly and is -/// otherwise a direct mapping to `Result`. -pub type Result = std::result::Result; - /// General purpose actix web error. /// /// An actix web error is used to carry errors from `std::error` @@ -470,9 +464,8 @@ impl ResponseError for ContentTypeError { /// /// ``` /// # use std::io; -/// # use actix_http::*; -/// -/// fn index(req: Request) -> Result<&'static str> { +/// # use actix_http::{error, Request}; +/// fn index(req: Request) -> Result<&'static str, actix_http::Error> { /// Err(error::ErrorBadRequest(io::Error::new(io::ErrorKind::Other, "error"))) /// } /// ``` diff --git a/actix-http/src/h1/dispatcher.rs b/actix-http/src/h1/dispatcher.rs index 7ab89ba87..574f0b2a9 100644 --- a/actix-http/src/h1/dispatcher.rs +++ b/actix-http/src/h1/dispatcher.rs @@ -17,7 +17,7 @@ use futures_core::ready; use log::{error, trace}; use pin_project::pin_project; -use crate::body::{Body, BodySize, MessageBody, ResponseBody}; +use crate::body::{Body, BodySize, MessageBody}; use crate::config::ServiceConfig; use crate::error::{DispatchError, Error}; use crate::error::{ParseError, PayloadError}; @@ -141,7 +141,8 @@ where None, ExpectCall(#[pin] X::Future), ServiceCall(#[pin] S::Future), - SendPayload(#[pin] ResponseBody), + SendPayload(#[pin] B), + SendErrorPayload(#[pin] Body), } impl State @@ -295,11 +296,11 @@ where io.poll_flush(cx) } - fn send_response( + fn send_response_inner( self: Pin<&mut Self>, message: Response<()>, - body: ResponseBody, - ) -> Result<(), DispatchError> { + body: &impl MessageBody, + ) -> Result { let size = body.size(); let mut this = self.project(); this.codec @@ -312,10 +313,35 @@ where })?; this.flags.set(Flags::KEEPALIVE, this.codec.keepalive()); - match size { - BodySize::None | BodySize::Empty => this.state.set(State::None), - _ => this.state.set(State::SendPayload(body)), + + Ok(size) + } + + fn send_response( + mut self: Pin<&mut Self>, + message: Response<()>, + body: B, + ) -> Result<(), DispatchError> { + let size = self.as_mut().send_response_inner(message, &body)?; + let state = match size { + BodySize::None | BodySize::Empty => State::None, + _ => State::SendPayload(body), }; + self.project().state.set(state); + Ok(()) + } + + fn send_error_response( + mut self: Pin<&mut Self>, + message: Response<()>, + body: Body, + ) -> Result<(), DispatchError> { + let size = self.as_mut().send_response_inner(message, &body)?; + let state = match size { + BodySize::None | BodySize::Empty => State::None, + _ => State::SendErrorPayload(body), + }; + self.project().state.set(state); Ok(()) } @@ -353,8 +379,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_response(res, ResponseBody::Other(Body::Empty))?; + self.as_mut().send_error_response(res, Body::Empty)?; } // return with upgrade request and poll it exclusively. @@ -376,7 +401,7 @@ where Poll::Ready(Err(err)) => { let res = Response::from_error(err.into()); let (res, body) = res.replace_body(()); - self.as_mut().send_response(res, body.into_body())?; + self.as_mut().send_error_response(res, body)?; } // service call pending and could be waiting for more chunk messages. @@ -392,6 +417,41 @@ where }, StateProj::SendPayload(mut stream) => { + // 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) { + Poll::Ready(Some(Ok(item))) => { + this.codec.encode( + Message::Chunk(Some(item)), + &mut this.write_buf, + )?; + } + + Poll::Ready(None) => { + this.codec + .encode(Message::Chunk(None), &mut this.write_buf)?; + // payload stream finished. + // set state to None and handle next message + this.state.set(State::None); + continue 'res; + } + + Poll::Ready(Some(Err(err))) => { + return Err(DispatchError::Service(err.into())) + } + + Poll::Pending => return Ok(PollResponse::DoNothing), + } + } + // buffer is beyond max size. + // return and try to write the whole buffer to io stream. + return Ok(PollResponse::DrainWriteBuf); + } + + StateProj::SendErrorPayload(mut stream) => { + // 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 { @@ -433,12 +493,14 @@ where let fut = this.flow.service.call(req); this.state.set(State::ServiceCall(fut)); } + // send expect error as response Poll::Ready(Err(err)) => { let res = Response::from_error(err.into()); let (res, body) = res.replace_body(()); - self.as_mut().send_response(res, body.into_body())?; + self.as_mut().send_error_response(res, body)?; } + // expect must be solved before progress can be made. Poll::Pending => return Ok(PollResponse::DoNothing), }, @@ -486,7 +548,7 @@ where Poll::Ready(Err(err)) => { let res = Response::from_error(err.into()); let (res, body) = res.replace_body(()); - return self.send_response(res, body.into_body()); + return self.send_error_response(res, body); } } } @@ -506,7 +568,7 @@ where Poll::Ready(Err(err)) => { let res = Response::from_error(err.into()); let (res, body) = res.replace_body(()); - self.send_response(res, body.into_body()) + self.send_error_response(res, body) } }; } @@ -626,8 +688,10 @@ where } // Requests overflow buffer size should be responded with 431 this.messages.push_back(DispatcherMessage::Error( - Response::new(StatusCode::REQUEST_HEADER_FIELDS_TOO_LARGE) - .drop_body(), + Response::with_body( + StatusCode::REQUEST_HEADER_FIELDS_TOO_LARGE, + (), + ), )); this.flags.insert(Flags::READ_DISCONNECT); *this.error = Some(ParseError::TooLarge.into()); @@ -706,10 +770,9 @@ where } else { // timeout on first request (slow request) return 408 trace!("Slow request timeout"); - let _ = self.as_mut().send_response( - Response::new(StatusCode::REQUEST_TIMEOUT) - .drop_body(), - ResponseBody::Other(Body::Empty), + let _ = self.as_mut().send_error_response( + Response::with_body(StatusCode::REQUEST_TIMEOUT, ()), + Body::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 4e9903284..eaabcb687 100644 --- a/actix-http/src/h1/encoder.rs +++ b/actix-http/src/h1/encoder.rs @@ -630,8 +630,7 @@ mod tests { async fn test_no_content_length() { let mut bytes = BytesMut::with_capacity(2048); - let mut res: Response<()> = - Response::new(StatusCode::SWITCHING_PROTOCOLS).into_body::<()>(); + let mut res = Response::with_body(StatusCode::SWITCHING_PROTOCOLS, ()); res.headers_mut().insert(DATE, HeaderValue::from_static("")); res.headers_mut() .insert(CONTENT_LENGTH, HeaderValue::from_static("0")); diff --git a/actix-http/src/h1/utils.rs b/actix-http/src/h1/utils.rs index 73f01e913..90e44daa4 100644 --- a/actix-http/src/h1/utils.rs +++ b/actix-http/src/h1/utils.rs @@ -4,7 +4,7 @@ use std::task::{Context, Poll}; use actix_codec::{AsyncRead, AsyncWrite, Framed}; -use crate::body::{BodySize, MessageBody, ResponseBody}; +use crate::body::{BodySize, MessageBody}; use crate::error::Error; use crate::h1::{Codec, Message}; use crate::response::Response; @@ -14,7 +14,7 @@ use crate::response::Response; pub struct SendResponse { res: Option, BodySize)>>, #[pin] - body: Option>, + body: Option, #[pin] framed: Option>, } @@ -62,7 +62,18 @@ where .unwrap() .is_write_buf_full() { - match this.body.as_mut().as_pin_mut().unwrap().poll_next(cx)? { + 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(None) => Poll::Ready(None), + Poll::Pending => Poll::Pending, + }; + + match next { Poll::Ready(item) => { // body is done when item is None body_done = item.is_none(); diff --git a/actix-http/src/h2/dispatcher.rs b/actix-http/src/h2/dispatcher.rs index 07636470b..5be172aaf 100644 --- a/actix-http/src/h2/dispatcher.rs +++ b/actix-http/src/h2/dispatcher.rs @@ -12,7 +12,7 @@ use h2::{ use http::header::{HeaderValue, CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCODING}; use log::{error, trace}; -use crate::body::{BodySize, MessageBody, ResponseBody}; +use crate::body::{Body, BodySize, MessageBody}; use crate::config::ServiceConfig; use crate::error::{DispatchError, Error}; use crate::message::ResponseHead; @@ -135,7 +135,8 @@ struct ServiceResponse { #[pin_project::pin_project(project = ServiceResponseStateProj)] enum ServiceResponseState { ServiceCall(#[pin] F, Option>), - SendPayload(SendStream, #[pin] ResponseBody), + SendPayload(SendStream, #[pin] B), + SendErrorPayload(SendStream, #[pin] Body), } impl ServiceResponse @@ -280,9 +281,8 @@ where if size.is_eof() { Poll::Ready(()) } else { - this.state.set(ServiceResponseState::SendPayload( - stream, - body.into_body(), + this.state.set(ServiceResponseState::SendErrorPayload( + stream, body, )); self.poll(cx) } @@ -331,8 +331,65 @@ where *this.buffer = Some(chunk); } + Some(Err(err)) => { + error!( + "Response payload stream error: {:?}", + err.into() + ); + + return Poll::Ready(()); + } + }, + } + } + } + + ServiceResponseStateProj::SendErrorPayload(ref mut stream, ref mut body) => { + // TODO: de-dupe impl with SendPayload + + loop { + match this.buffer { + Some(ref mut buffer) => match ready!(stream.poll_capacity(cx)) { + None => return Poll::Ready(()), + + Some(Ok(cap)) => { + let len = buffer.len(); + let bytes = buffer.split_to(cmp::min(cap, len)); + + if let Err(e) = stream.send_data(bytes, false) { + warn!("{:?}", e); + return Poll::Ready(()); + } else if !buffer.is_empty() { + let cap = cmp::min(buffer.len(), CHUNK_SIZE); + stream.reserve_capacity(cap); + } else { + this.buffer.take(); + } + } + Some(Err(e)) => { - error!("Response payload stream error: {:?}", e); + warn!("{:?}", e); + return Poll::Ready(()); + } + }, + + None => match ready!(body.as_mut().poll_next(cx)) { + None => { + if let Err(e) = stream.send_data(Bytes::new(), true) { + warn!("{:?}", e); + } + return Poll::Ready(()); + } + + Some(Ok(chunk)) => { + stream + .reserve_capacity(cmp::min(chunk.len(), CHUNK_SIZE)); + *this.buffer = Some(chunk); + } + + Some(Err(err)) => { + error!("Response payload stream error: {:?}", err); + return Poll::Ready(()); } }, diff --git a/actix-http/src/header/shared/charset.rs b/actix-http/src/header/shared/charset.rs index 36bdbf7e2..b482f6bce 100644 --- a/actix-http/src/header/shared/charset.rs +++ b/actix-http/src/header/shared/charset.rs @@ -104,7 +104,7 @@ impl Display for Charset { impl FromStr for Charset { type Err = crate::Error; - fn from_str(s: &str) -> crate::Result { + fn from_str(s: &str) -> Result { Ok(match s.to_ascii_uppercase().as_ref() { "US-ASCII" => Us_Ascii, "ISO-8859-1" => Iso_8859_1, diff --git a/actix-http/src/lib.rs b/actix-http/src/lib.rs index 82d0415c2..7c2c3b4e3 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, Result}; +pub use self::error::{Error, ResponseError}; pub use self::extensions::Extensions; pub use self::header::ContentEncoding; pub use self::http_message::HttpMessage; diff --git a/actix-http/src/message.rs b/actix-http/src/message.rs index 8cb99d43a..0a3f3a915 100644 --- a/actix-http/src/message.rs +++ b/actix-http/src/message.rs @@ -293,14 +293,14 @@ impl ResponseHead { } } - #[inline] /// Check if keep-alive is enabled + #[inline] pub fn keep_alive(&self) -> bool { self.connection_type() == ConnectionType::KeepAlive } - #[inline] /// Check upgrade status of this message + #[inline] pub fn upgrade(&self) -> bool { self.connection_type() == ConnectionType::Upgrade } @@ -389,12 +389,6 @@ impl BoxedResponseHead { pub fn new(status: StatusCode) -> Self { RESPONSE_POOL.with(|p| p.get_message(status)) } - - pub(crate) fn take(&mut self) -> Self { - BoxedResponseHead { - head: self.head.take(), - } - } } impl std::ops::Deref for BoxedResponseHead { diff --git a/actix-http/src/response.rs b/actix-http/src/response.rs index da5c7e000..1b3f68505 100644 --- a/actix-http/src/response.rs +++ b/actix-http/src/response.rs @@ -2,17 +2,13 @@ use std::{ cell::{Ref, RefMut}, - fmt, - future::Future, - pin::Pin, - str, - task::{Context, Poll}, + fmt, str, }; use bytes::{Bytes, BytesMut}; use crate::{ - body::{Body, MessageBody, ResponseBody}, + body::{Body, MessageBody}, error::Error, extensions::Extensions, http::{HeaderMap, StatusCode}, @@ -23,22 +19,22 @@ use crate::{ /// An HTTP response. pub struct Response { pub(crate) head: BoxedResponseHead, - pub(crate) body: ResponseBody, + pub(crate) body: B, pub(crate) error: Option, } impl Response { - /// Constructs a response + /// Constructs a new response with default body. #[inline] pub fn new(status: StatusCode) -> Response { Response { head: BoxedResponseHead::new(status), - body: ResponseBody::Body(Body::Empty), + body: Body::Empty, error: None, } } - /// Create HTTP response builder with specific status. + /// Constructs a new response builder. #[inline] pub fn build(status: StatusCode) -> ResponseBuilder { ResponseBuilder::new(status) @@ -47,25 +43,25 @@ impl Response { // just a couple frequently used shortcuts // this list should not grow larger than a few - /// Creates a new response with status 200 OK. + /// Constructs a new response with status 200 OK. #[inline] pub fn ok() -> Response { Response::new(StatusCode::OK) } - /// Creates a new response with status 400 Bad Request. + /// Constructs a new response with status 400 Bad Request. #[inline] pub fn bad_request() -> Response { Response::new(StatusCode::BAD_REQUEST) } - /// Creates a new response with status 404 Not Found. + /// Constructs a new response with status 404 Not Found. #[inline] pub fn not_found() -> Response { Response::new(StatusCode::NOT_FOUND) } - /// Creates a new response with status 500 Internal Server Error. + /// Constructs a new response with status 500 Internal Server Error. #[inline] pub fn internal_server_error() -> Response { Response::new(StatusCode::INTERNAL_SERVER_ERROR) @@ -73,7 +69,7 @@ impl Response { // end shortcuts - /// Constructs an error response + /// Constructs a new response from an error. #[inline] pub fn from_error(error: Error) -> Response { let mut resp = error.as_response_error().error_response(); @@ -83,162 +79,142 @@ impl Response { resp.error = Some(error); resp } - - /// Convert response to response with body - pub fn into_body(self) -> Response { - let b = match self.body { - ResponseBody::Body(b) => b, - ResponseBody::Other(b) => b, - }; - Response { - head: self.head, - error: self.error, - body: ResponseBody::Other(b), - } - } } impl Response { - /// Constructs a response with body + /// Constructs a new response with given body. #[inline] pub fn with_body(status: StatusCode, body: B) -> Response { Response { head: BoxedResponseHead::new(status), - body: ResponseBody::Body(body), + body: body, error: None, } } + /// Returns a reference to the head of this response. #[inline] - /// Http message part of the response pub fn head(&self) -> &ResponseHead { &*self.head } + /// Returns a mutable reference to the head of this response. #[inline] - /// Mutable reference to a HTTP message part of the response pub fn head_mut(&mut self) -> &mut ResponseHead { &mut *self.head } - /// The source `error` for this response + /// Returns the source `error` for this response, if one is set. #[inline] pub fn error(&self) -> Option<&Error> { self.error.as_ref() } - /// Get the response status code + /// Returns the status code of this response. #[inline] pub fn status(&self) -> StatusCode { self.head.status } - /// Set the `StatusCode` for this response + /// Returns a mutable reference the status code of this response. #[inline] pub fn status_mut(&mut self) -> &mut StatusCode { &mut self.head.status } - /// Get the headers from the response + /// Returns a reference to response headers. #[inline] pub fn headers(&self) -> &HeaderMap { &self.head.headers } - /// Get a mutable reference to the headers + /// Returns a mutable reference to response headers. #[inline] pub fn headers_mut(&mut self) -> &mut HeaderMap { &mut self.head.headers } - /// Connection upgrade status + /// Returns true if connection upgrade is enabled. #[inline] pub fn upgrade(&self) -> bool { self.head.upgrade() } - /// Keep-alive status for this connection + /// Returns true if keep-alive is enabled. pub fn keep_alive(&self) -> bool { self.head.keep_alive() } - /// Responses extensions + /// Returns a reference to the extensions of this response. #[inline] pub fn extensions(&self) -> Ref<'_, Extensions> { self.head.extensions.borrow() } - /// Mutable reference to a the response's extensions + /// Returns a mutable reference to the extensions of this response. #[inline] pub fn extensions_mut(&mut self) -> RefMut<'_, Extensions> { self.head.extensions.borrow_mut() } - /// Get body of this response + /// Returns a reference to the body of this response. #[inline] - pub fn body(&self) -> &ResponseBody { + pub fn body(&self) -> &B { &self.body } - /// Set a body + /// Sets new body. pub fn set_body(self, body: B2) -> Response { Response { head: self.head, - body: ResponseBody::Body(body), + body, error: None, } } - /// Split response and body - pub fn into_parts(self) -> (Response<()>, ResponseBody) { - ( - Response { - head: self.head, - body: ResponseBody::Body(()), - error: self.error, - }, - self.body, - ) - } - - /// Drop request's body + /// Drops body and returns new response. pub fn drop_body(self) -> Response<()> { - Response { - head: self.head, - body: ResponseBody::Body(()), - error: None, - } + self.set_body(()) } - /// Set a body and return previous body value - pub(crate) fn replace_body(self, body: B2) -> (Response, ResponseBody) { + /// Sets new body, returning new response and previous body value. + pub(crate) fn replace_body(self, body: B2) -> (Response, B) { ( Response { head: self.head, - body: ResponseBody::Body(body), + body, error: self.error, }, self.body, ) } - /// Set a body and return previous body value + /// Returns split head and body. + /// + /// # Implementation Notes + /// Due to internal performance optimisations, 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(()) + } + + /// Returns new response with mapped body. pub fn map_body(mut self, f: F) -> Response where - F: FnOnce(&mut ResponseHead, ResponseBody) -> ResponseBody, + F: FnOnce(&mut ResponseHead, B) -> B2, { let body = f(&mut self.head, self.body); Response { - body, head: self.head, + body, error: self.error, } } - /// Extract response body - pub fn take_body(&mut self) -> ResponseBody { - self.body.take_body() + /// Returns body, consuming this response. + pub fn into_body(self) -> B { + self.body } } @@ -264,19 +240,13 @@ where } } -impl Future for Response { - type Output = Result, Error>; - - fn poll(mut self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll { - Poll::Ready(Ok(Response { - head: self.head.take(), - body: self.body.take_body(), - error: self.error.take(), - })) +impl Default for Response { + #[inline] + fn default() -> Response { + Response::with_body(StatusCode::default(), B::default()) } } -/// Helper converters impl>, E: Into> From> for Response { fn from(res: Result) -> Self { match res { diff --git a/actix-http/src/response_builder.rs b/actix-http/src/response_builder.rs index 0105f70cf..3fb94dad5 100644 --- a/actix-http/src/response_builder.rs +++ b/actix-http/src/response_builder.rs @@ -13,7 +13,7 @@ use bytes::Bytes; use futures_core::Stream; use crate::{ - body::{Body, BodyStream, ResponseBody}, + body::{Body, BodyStream}, error::{Error, HttpError}, header::{self, IntoHeaderPair, IntoHeaderValue}, message::{BoxedResponseHead, ConnectionType, ResponseHead}, @@ -38,10 +38,11 @@ use crate::{ /// .body("1234"); /// /// assert_eq!(res.status(), StatusCode::OK); -/// assert_eq!(body::to_bytes(res.take_body()).await.unwrap(), &b"1234"[..]); /// /// assert!(res.headers().contains_key("server")); /// assert_eq!(res.headers().get_all("set-cookie").count(), 2); +/// +/// assert_eq!(body::to_bytes(res.into_body()).await.unwrap(), &b"1234"[..]); /// # }) /// ``` pub struct ResponseBuilder { @@ -236,23 +237,24 @@ impl ResponseBuilder { #[inline] pub fn body>(&mut self, body: B) -> Response { self.message_body(body.into()) + .unwrap_or_else(Response::from_error) } /// Generate response with a body. /// /// This `ResponseBuilder` will be left in a useless state. - pub fn message_body(&mut self, body: B) -> Response { - if let Some(e) = self.err.take() { - return Response::from(Error::from(e)).into_body(); + pub fn message_body(&mut self, body: B) -> Result, Error> { + if let Some(err) = self.err.take() { + return Err(err.into()); } let response = self.head.take().expect("cannot reuse response builder"); - Response { + Ok(Response { head: response, - body: ResponseBody::Body(body), + body, error: None, - } + }) } /// Generate response with a streaming body. diff --git a/actix-http/tests/test_ws.rs b/actix-http/tests/test_ws.rs index 72870bab5..bf1ca9385 100644 --- a/actix-http/tests/test_ws.rs +++ b/actix-http/tests/test_ws.rs @@ -52,7 +52,7 @@ where fn call(&self, (req, mut framed): (Request, Framed)) -> Self::Future { let fut = async move { - let res = ws::handshake(req.head()).unwrap().message_body(()); + let res = ws::handshake(req.head()).unwrap().message_body(()).unwrap(); framed .send((res, body::BodySize::None).into()) diff --git a/src/app_service.rs b/src/app_service.rs index 32c779a32..ca6f36202 100644 --- a/src/app_service.rs +++ b/src/app_service.rs @@ -166,8 +166,7 @@ impl AppInitServiceState { Rc::new(AppInitServiceState { rmap, config, - // TODO: AppConfig can be used to pass user defined HttpRequestPool - // capacity. + // TODO: AppConfig can be used to pass user defined HttpRequestPool capacity. pool: HttpRequestPool::default(), }) } diff --git a/src/error.rs b/src/error.rs index cc1a055b8..a5a245693 100644 --- a/src/error.rs +++ b/src/error.rs @@ -9,6 +9,11 @@ use url::ParseError as UrlParseError; use crate::http::StatusCode; +/// 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; + /// Errors which can occur when attempting to generate resource uri. #[derive(Debug, PartialEq, Display, Error, From)] #[non_exhaustive] @@ -26,7 +31,6 @@ pub enum UrlGenerationError { ParseError(UrlParseError), } -/// `InternalServerError` for `UrlGeneratorError` impl ResponseError for UrlGenerationError {} /// A set of errors that can occur during parsing urlencoded payloads @@ -70,7 +74,6 @@ pub enum UrlencodedError { Payload(PayloadError), } -/// Return `BadRequest` for `UrlencodedError` impl ResponseError for UrlencodedError { fn status_code(&self) -> StatusCode { match self { @@ -149,7 +152,6 @@ pub enum QueryPayloadError { Deserialize(serde::de::value::Error), } -/// Return `BadRequest` for `QueryPayloadError` impl ResponseError for QueryPayloadError { fn status_code(&self) -> StatusCode { StatusCode::BAD_REQUEST @@ -177,7 +179,6 @@ pub enum ReadlinesError { ContentTypeError(ContentTypeError), } -/// Return `BadRequest` for `ReadlinesError` impl ResponseError for ReadlinesError { fn status_code(&self) -> StatusCode { match *self { diff --git a/src/lib.rs b/src/lib.rs index 4d0ad26ed..96e6ecbf8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -97,7 +97,7 @@ pub(crate) mod types; pub mod web; pub use actix_http::Response as BaseHttpResponse; -pub use actix_http::{body, Error, HttpMessage, ResponseError, Result}; +pub use actix_http::{body, Error, HttpMessage, ResponseError}; #[doc(inline)] pub use actix_rt as rt; pub use actix_web_codegen::*; @@ -105,6 +105,7 @@ pub use actix_web_codegen::*; pub use cookie; pub use crate::app::App; +pub use crate::error::Result; pub use crate::extract::FromRequest; pub use crate::request::HttpRequest; pub use crate::resource::Resource; diff --git a/src/middleware/compat.rs b/src/middleware/compat.rs index 3a85591da..4f2f2a504 100644 --- a/src/middleware/compat.rs +++ b/src/middleware/compat.rs @@ -1,12 +1,13 @@ //! For middleware documentation, see [`Compat`]. use std::{ + error::Error as StdError, future::Future, pin::Pin, task::{Context, Poll}, }; -use actix_http::body::{Body, MessageBody, ResponseBody}; +use actix_http::body::{Body, MessageBody}; use actix_service::{Service, Transform}; use futures_core::{future::LocalBoxFuture, ready}; @@ -116,10 +117,10 @@ pub trait MapServiceResponseBody { impl MapServiceResponseBody for ServiceResponse where B: MessageBody + Unpin + 'static, - B::Error: Into, + B::Error: Into>, { fn map_body(self) -> ServiceResponse { - self.map_body(|_, body| ResponseBody::Other(Body::from_message(body))) + self.map_body(|_, body| Body::from_message(body)) } } diff --git a/src/middleware/compress.rs b/src/middleware/compress.rs index 6a56e6de0..f8514c7cc 100644 --- a/src/middleware/compress.rs +++ b/src/middleware/compress.rs @@ -10,7 +10,7 @@ use std::{ }; use actix_http::{ - body::MessageBody, + body::{MessageBody, ResponseBody}, encoding::Encoder, http::header::{ContentEncoding, ACCEPT_ENCODING}, Error, @@ -59,7 +59,7 @@ where B: MessageBody, S: Service, Error = Error>, { - type Response = ServiceResponse>; + type Response = ServiceResponse>>; type Error = Error; type Transform = CompressMiddleware; type InitError = (); @@ -83,7 +83,7 @@ where B: MessageBody, S: Service, Error = Error>, { - type Response = ServiceResponse>; + type Response = ServiceResponse>>; type Error = Error; type Future = CompressResponse; @@ -127,7 +127,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(); @@ -140,9 +140,9 @@ where *this.encoding }; - Poll::Ready(Ok( - resp.map_body(move |head, body| Encoder::response(enc, head, body)) - )) + Poll::Ready(Ok(resp.map_body(move |head, body| { + Encoder::response(enc, head, ResponseBody::Body(body)) + }))) } Err(e) => Poll::Ready(Err(e)), } diff --git a/src/middleware/logger.rs b/src/middleware/logger.rs index 8a60d6c70..bbb0e3dc4 100644 --- a/src/middleware/logger.rs +++ b/src/middleware/logger.rs @@ -21,7 +21,7 @@ use regex::{Regex, RegexSet}; use time::OffsetDateTime; use crate::{ - dev::{BodySize, MessageBody, ResponseBody}, + dev::{BodySize, MessageBody}, http::{HeaderName, StatusCode}, service::{ServiceRequest, ServiceResponse}, Error, HttpResponse, Result, @@ -289,13 +289,11 @@ where let time = *this.time; let format = this.format.take(); - Poll::Ready(Ok(res.map_body(move |_, body| { - ResponseBody::Body(StreamLog { - body, - time, - format, - size: 0, - }) + Poll::Ready(Ok(res.map_body(move |_, body| StreamLog { + body, + time, + format, + size: 0, }))) } } @@ -305,7 +303,7 @@ use pin_project::{pin_project, pinned_drop}; #[pin_project(PinnedDrop)] pub struct StreamLog { #[pin] - body: ResponseBody, + body: B, format: Option, size: usize, time: OffsetDateTime, @@ -342,12 +340,15 @@ where cx: &mut Context<'_>, ) -> Poll>> { let this = self.project(); - match this.body.poll_next(cx) { - Poll::Ready(Some(Ok(chunk))) => { + + // TODO: MSRV 1.51: poll_map_err + match ready!(this.body.poll_next(cx)) { + Some(Ok(chunk)) => { *this.size += chunk.len(); Poll::Ready(Some(Ok(chunk))) } - val => val, + Some(Err(err)) => Poll::Ready(Some(Err(err.into()))), + None => Poll::Ready(None), } } } diff --git a/src/responder.rs b/src/responder.rs index 7b8288ed8..2393d046b 100644 --- a/src/responder.rs +++ b/src/responder.rs @@ -264,7 +264,7 @@ pub(crate) mod tests { let resp = srv.call(req).await.unwrap(); assert_eq!(resp.status(), StatusCode::OK); match resp.response().body() { - ResponseBody::Body(Body::Bytes(ref b)) => { + Body::Bytes(ref b) => { let bytes = b.clone(); assert_eq!(bytes, Bytes::from_static(b"some")); } @@ -277,16 +277,28 @@ pub(crate) mod tests { fn body(&self) -> &Body; } + impl BodyTest for Body { + fn bin_ref(&self) -> &[u8] { + match self { + Body::Bytes(ref bin) => &bin, + _ => unreachable!("bug in test impl"), + } + } + fn body(&self) -> &Body { + self + } + } + impl BodyTest for ResponseBody { fn bin_ref(&self) -> &[u8] { match self { ResponseBody::Body(ref b) => match b { Body::Bytes(ref bin) => &bin, - _ => panic!(), + _ => unreachable!("bug in test impl"), }, ResponseBody::Other(ref b) => match b { Body::Bytes(ref bin) => &bin, - _ => panic!(), + _ => unreachable!("bug in test impl"), }, } } diff --git a/src/response/builder.rs b/src/response/builder.rs index 80086bfd3..b9a10c56b 100644 --- a/src/response/builder.rs +++ b/src/response/builder.rs @@ -310,16 +310,19 @@ impl HttpResponseBuilder { /// /// `HttpResponseBuilder` can not be used after this call. #[inline] - pub fn body>(&mut self, body: B) -> HttpResponse { - self.message_body(body.into()) + pub fn body>(&mut self, body: B) -> HttpResponse { + match self.message_body(body.into()) { + Ok(res) => res, + Err(err) => HttpResponse::from_error(err), + } } /// Set a body and generate `Response`. /// /// `HttpResponseBuilder` can not be used after this call. - pub fn message_body(&mut self, body: B) -> HttpResponse { + pub fn message_body(&mut self, body: B) -> Result, Error> { if let Some(err) = self.err.take() { - return HttpResponse::from_error(Error::from(err)).into_body(); + return Err(err.into()); } let res = self @@ -336,12 +339,12 @@ impl HttpResponseBuilder { for cookie in jar.delta() { match HeaderValue::from_str(&cookie.to_string()) { Ok(val) => res.headers_mut().append(header::SET_COOKIE, val), - Err(err) => return HttpResponse::from_error(Error::from(err)).into_body(), + Err(err) => return Err(err.into()), }; } } - res + Ok(res) } /// Set a streaming body and generate `Response`. @@ -477,42 +480,42 @@ mod tests { #[actix_rt::test] async fn test_json() { - let mut resp = HttpResponse::Ok().json(vec!["v1", "v2", "v3"]); + let resp = HttpResponse::Ok().json(vec!["v1", "v2", "v3"]); let ct = resp.headers().get(CONTENT_TYPE).unwrap(); assert_eq!(ct, HeaderValue::from_static("application/json")); assert_eq!( - body::to_bytes(resp.take_body()).await.unwrap().as_ref(), + body::to_bytes(resp.into_body()).await.unwrap().as_ref(), br#"["v1","v2","v3"]"# ); - let mut resp = HttpResponse::Ok().json(&["v1", "v2", "v3"]); + let resp = HttpResponse::Ok().json(&["v1", "v2", "v3"]); let ct = resp.headers().get(CONTENT_TYPE).unwrap(); assert_eq!(ct, HeaderValue::from_static("application/json")); assert_eq!( - body::to_bytes(resp.take_body()).await.unwrap().as_ref(), + body::to_bytes(resp.into_body()).await.unwrap().as_ref(), br#"["v1","v2","v3"]"# ); // content type override - let mut resp = HttpResponse::Ok() + let resp = HttpResponse::Ok() .insert_header((CONTENT_TYPE, "text/json")) .json(&vec!["v1", "v2", "v3"]); let ct = resp.headers().get(CONTENT_TYPE).unwrap(); assert_eq!(ct, HeaderValue::from_static("text/json")); assert_eq!( - body::to_bytes(resp.take_body()).await.unwrap().as_ref(), + body::to_bytes(resp.into_body()).await.unwrap().as_ref(), br#"["v1","v2","v3"]"# ); } #[actix_rt::test] async fn test_serde_json_in_body() { - let mut resp = HttpResponse::Ok().body( + let resp = HttpResponse::Ok().body( serde_json::to_vec(&serde_json::json!({ "test-key": "test-value" })).unwrap(), ); assert_eq!( - body::to_bytes(resp.take_body()).await.unwrap().as_ref(), + body::to_bytes(resp.into_body()).await.unwrap().as_ref(), br#"{"test-key":"test-value"}"# ); } diff --git a/src/response/response.rs b/src/response/response.rs index 6e09a0136..194e2dff8 100644 --- a/src/response/response.rs +++ b/src/response/response.rs @@ -8,7 +8,7 @@ use std::{ }; use actix_http::{ - body::{Body, MessageBody, ResponseBody}, + body::{Body, MessageBody}, http::{header::HeaderMap, StatusCode}, Extensions, Response, ResponseHead, }; @@ -27,7 +27,7 @@ use crate::{error::Error, HttpResponseBuilder}; /// An HTTP Response pub struct HttpResponse { res: Response, - error: Option, + pub(crate) error: Option, } impl HttpResponse { @@ -56,14 +56,6 @@ impl HttpResponse { error: Some(error), } } - - /// Convert response to response with body - pub fn into_body(self) -> HttpResponse { - HttpResponse { - res: self.res.into_body(), - error: self.error, - } - } } impl HttpResponse { @@ -192,7 +184,7 @@ impl HttpResponse { /// Get body of this response #[inline] - pub fn body(&self) -> &ResponseBody { + pub fn body(&self) -> &B { self.res.body() } @@ -206,7 +198,7 @@ impl HttpResponse { } /// Split response and body - pub fn into_parts(self) -> (HttpResponse<()>, ResponseBody) { + pub fn into_parts(self) -> (HttpResponse<()>, B) { let (head, body) = self.res.into_parts(); ( @@ -229,7 +221,7 @@ impl HttpResponse { /// Set a body and return previous body value pub fn map_body(self, f: F) -> HttpResponse where - F: FnOnce(&mut ResponseHead, ResponseBody) -> ResponseBody, + F: FnOnce(&mut ResponseHead, B) -> B2, { HttpResponse { res: self.res.map_body(f), @@ -238,8 +230,8 @@ impl HttpResponse { } /// Extract response body - pub fn take_body(&mut self) -> ResponseBody { - self.res.take_body() + pub fn into_body(self) -> B { + self.res.into_body() } } @@ -274,20 +266,25 @@ impl From> for Response { // TODO: expose cause somewhere? // if let Some(err) = res.error { - // eprintln!("impl From> for Response let Some(err)"); - // return Response::from_error(err).into_body(); + // return Response::from_error(err); // } res.res } } -impl Future for HttpResponse { +// 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. +// +// 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(Ok(Response::from_error(err).into_body())); + return Poll::Ready(Err(err)); } Poll::Ready(Ok(mem::replace( diff --git a/src/scope.rs b/src/scope.rs index 3be6adb0c..412c01d95 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -578,7 +578,7 @@ mod tests { use actix_utils::future::ok; use bytes::Bytes; - use crate::dev::{Body, ResponseBody}; + use crate::dev::Body; use crate::http::{header, HeaderValue, Method, StatusCode}; use crate::middleware::DefaultHeaders; use crate::service::ServiceRequest; @@ -748,7 +748,7 @@ mod tests { assert_eq!(resp.status(), StatusCode::OK); match resp.response().body() { - ResponseBody::Body(Body::Bytes(ref b)) => { + Body::Bytes(ref b) => { let bytes = b.clone(); assert_eq!(bytes, Bytes::from_static(b"project: project1")); } @@ -849,7 +849,7 @@ mod tests { assert_eq!(resp.status(), StatusCode::CREATED); match resp.response().body() { - ResponseBody::Body(Body::Bytes(ref b)) => { + Body::Bytes(ref b) => { let bytes = b.clone(); assert_eq!(bytes, Bytes::from_static(b"project: project_1")); } @@ -877,7 +877,7 @@ mod tests { assert_eq!(resp.status(), StatusCode::CREATED); match resp.response().body() { - ResponseBody::Body(Body::Bytes(ref b)) => { + Body::Bytes(ref b) => { let bytes = b.clone(); assert_eq!(bytes, Bytes::from_static(b"project: test - 1")); } diff --git a/src/service.rs b/src/service.rs index 0c03f84ad..b7f244797 100644 --- a/src/service.rs +++ b/src/service.rs @@ -2,7 +2,7 @@ use std::cell::{Ref, RefMut}; use std::rc::Rc; use std::{fmt, net}; -use actix_http::body::{Body, MessageBody, ResponseBody}; +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, @@ -110,9 +110,9 @@ impl ServiceRequest { /// Create service response for error #[inline] - pub fn error_response>(self, err: E) -> ServiceResponse { + pub fn error_response>(self, err: E) -> ServiceResponse { let res = HttpResponse::from_error(err.into()); - ServiceResponse::new(self.req, res.into_body()) + ServiceResponse::new(self.req, res) } /// This method returns reference to the request head @@ -335,22 +335,24 @@ pub struct ServiceResponse { response: HttpResponse, } +impl ServiceResponse { + /// Create service response from the error + pub fn from_err>(err: E, request: HttpRequest) -> Self { + let response = HttpResponse::from_error(err.into()); + ServiceResponse { request, response } + } +} + impl ServiceResponse { /// Create service response instance pub fn new(request: HttpRequest, response: HttpResponse) -> Self { ServiceResponse { request, response } } - /// Create service response from the error - pub fn from_err>(err: E, request: HttpRequest) -> Self { - let response = HttpResponse::from_error(err.into()).into_body(); - ServiceResponse { request, response } - } - /// Create service response for error #[inline] - pub fn error_response>(self, err: E) -> Self { - Self::from_err(err, self.request) + pub fn error_response>(self, err: E) -> ServiceResponse { + ServiceResponse::from_err(err, self.request) } /// Create service response @@ -396,23 +398,18 @@ impl ServiceResponse { } /// Execute closure and in case of error convert it to response. - pub fn checked_expr(mut self, f: F) -> Self + pub fn checked_expr(mut self, f: F) -> Result where F: FnOnce(&mut Self) -> Result<(), E>, E: Into, { - match f(&mut self) { - Ok(_) => self, - Err(err) => { - let res = HttpResponse::from_error(err.into()); - ServiceResponse::new(self.request, res.into_body()) - } - } + f(&mut self).map_err(Into::into)?; + Ok(self) } /// Extract response body - pub fn take_body(&mut self) -> ResponseBody { - self.response.take_body() + pub fn into_body(self) -> B { + self.response.into_body() } } @@ -420,7 +417,7 @@ impl ServiceResponse { /// Set a new body pub fn map_body(self, f: F) -> ServiceResponse where - F: FnOnce(&mut ResponseHead, ResponseBody) -> ResponseBody, + F: FnOnce(&mut ResponseHead, B) -> B2, { let response = self.response.map_body(f); diff --git a/src/test.rs b/src/test.rs index 9fe5e9b5d..de97dc8aa 100644 --- a/src/test.rs +++ b/src/test.rs @@ -4,13 +4,14 @@ use std::{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, }; use actix_router::{Path, ResourceDef, Url}; use actix_service::{IntoService, IntoServiceFactory, Service, ServiceFactory}; -use actix_utils::future::ok; +use actix_utils::future::{ok, poll_fn}; use futures_core::Stream; use futures_util::StreamExt as _; use serde::{de::DeserializeOwned, Serialize}; @@ -153,16 +154,17 @@ where B: MessageBody + Unpin, B::Error: Into, { - let mut resp = app + let resp = app .call(req) .await .unwrap_or_else(|e| panic!("read_response failed at application call: {}", e)); - let mut body = resp.take_body(); + let body = resp.into_body(); let mut bytes = BytesMut::new(); - while let Some(item) = body.next().await { - bytes.extend_from_slice(&item.unwrap()); + 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() @@ -194,16 +196,19 @@ where /// assert_eq!(result, Bytes::from_static(b"welcome!")); /// } /// ``` -pub async fn read_body(mut res: ServiceResponse) -> Bytes +pub async fn read_body(res: ServiceResponse) -> Bytes where B: MessageBody + Unpin, B::Error: Into, { - let mut body = res.take_body(); + let body = res.into_body(); let mut bytes = BytesMut::new(); - while let Some(item) = body.next().await { - bytes.extend_from_slice(&item.unwrap()); + + 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() } @@ -271,6 +276,14 @@ where 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 /// /// ``` diff --git a/src/types/json.rs b/src/types/json.rs index 322e5cbf3..5762c6428 100644 --- a/src/types/json.rs +++ b/src/types/json.rs @@ -435,7 +435,7 @@ mod tests { header::{self, CONTENT_LENGTH, CONTENT_TYPE}, StatusCode, }, - test::{load_stream, TestRequest}, + test::{load_body, TestRequest}, }; #[derive(Serialize, Deserialize, PartialEq, Debug)] @@ -492,10 +492,10 @@ mod tests { .to_http_parts(); let s = Json::::from_request(&req, &mut pl).await; - let mut resp = HttpResponse::from_error(s.err().unwrap()); + let resp = HttpResponse::from_error(s.err().unwrap()); assert_eq!(resp.status(), StatusCode::BAD_REQUEST); - let body = load_stream(resp.take_body()).await.unwrap(); + let body = load_body(resp.into_body()).await.unwrap(); let msg: MyObject = serde_json::from_slice(&body).unwrap(); assert_eq!(msg.name, "invalid request"); } diff --git a/tests/test_server.rs b/tests/test_server.rs index 2760cc7fb..756c180fc 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -32,7 +32,7 @@ use rand::{distributions::Alphanumeric, Rng}; use actix_web::dev::BodyEncoding; use actix_web::middleware::{Compress, NormalizePath, TrailingSlash}; -use actix_web::{dev, web, App, Error, HttpResponse}; +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 \ @@ -160,9 +160,7 @@ async fn test_body_gzip2() { let srv = actix_test::start_with(actix_test::config().h1(), || { App::new() .wrap(Compress::new(ContentEncoding::Gzip)) - .service(web::resource("/").route(web::to(|| { - HttpResponse::Ok().body(STR).into_body::() - }))) + .service(web::resource("/").route(web::to(|| HttpResponse::Ok().body(STR)))) }); let mut response = srv @@ -903,7 +901,7 @@ async fn test_normalize() { let srv = actix_test::start_with(actix_test::config().h1(), || { App::new() .wrap(NormalizePath::new(TrailingSlash::Trim)) - .service(web::resource("/one").route(web::to(|| HttpResponse::Ok().finish()))) + .service(web::resource("/one").route(web::to(|| HttpResponse::Ok()))) }); let response = srv.get("/one/").send().await.unwrap(); From f55e8d7a11b23a2be97a93371e79b754fa60647d Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sun, 9 May 2021 03:42:33 +0100 Subject: [PATCH 193/339] remove error field from response --- actix-files/src/files.rs | 2 +- actix-http/src/response.rs | 15 +-------------- actix-http/src/response_builder.rs | 9 ++------- 3 files changed, 4 insertions(+), 22 deletions(-) diff --git a/actix-files/src/files.rs b/actix-files/src/files.rs index b2d69612c..25706a232 100644 --- a/actix-files/src/files.rs +++ b/actix-files/src/files.rs @@ -84,7 +84,7 @@ impl Files { /// /// `Files` utilizes the existing Tokio thread-pool for blocking filesystem operations. /// The number of running threads is adjusted over time as needed, up to a maximum of 512 times - /// the number of server [workers](HttpServer::workers), by default. + /// the number of server [workers](actix_web::HttpServer::workers), by default. pub fn new>(mount_path: &str, serve_from: T) -> Files { let orig_dir = serve_from.into(); let dir = match orig_dir.canonicalize() { diff --git a/actix-http/src/response.rs b/actix-http/src/response.rs index 1b3f68505..4f603956e 100644 --- a/actix-http/src/response.rs +++ b/actix-http/src/response.rs @@ -20,7 +20,6 @@ use crate::{ pub struct Response { pub(crate) head: BoxedResponseHead, pub(crate) body: B, - pub(crate) error: Option, } impl Response { @@ -30,7 +29,6 @@ impl Response { Response { head: BoxedResponseHead::new(status), body: Body::Empty, - error: None, } } @@ -72,11 +70,10 @@ impl Response { /// Constructs a new response from an error. #[inline] pub fn from_error(error: Error) -> Response { - let mut resp = error.as_response_error().error_response(); + let resp = error.as_response_error().error_response(); if resp.head.status == StatusCode::INTERNAL_SERVER_ERROR { debug!("Internal Server Error: {:?}", error); } - resp.error = Some(error); resp } } @@ -88,7 +85,6 @@ impl Response { Response { head: BoxedResponseHead::new(status), body: body, - error: None, } } @@ -104,12 +100,6 @@ impl Response { &mut *self.head } - /// Returns the source `error` for this response, if one is set. - #[inline] - pub fn error(&self) -> Option<&Error> { - self.error.as_ref() - } - /// Returns the status code of this response. #[inline] pub fn status(&self) -> StatusCode { @@ -168,7 +158,6 @@ impl Response { Response { head: self.head, body, - error: None, } } @@ -183,7 +172,6 @@ impl Response { Response { head: self.head, body, - error: self.error, }, self.body, ) @@ -208,7 +196,6 @@ impl Response { Response { head: self.head, body, - error: self.error, } } diff --git a/actix-http/src/response_builder.rs b/actix-http/src/response_builder.rs index 3fb94dad5..df9079d70 100644 --- a/actix-http/src/response_builder.rs +++ b/actix-http/src/response_builder.rs @@ -248,13 +248,8 @@ impl ResponseBuilder { return Err(err.into()); } - let response = self.head.take().expect("cannot reuse response builder"); - - Ok(Response { - head: response, - body, - error: None, - }) + let head = self.head.take().expect("cannot reuse response builder"); + Ok(Response { head, body }) } /// Generate response with a streaming body. From 4903950b221b1f0ee15c1ff2d7c5e4a606715559 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sun, 9 May 2021 03:44:14 +0100 Subject: [PATCH 194/339] update changelog --- actix-http/CHANGES.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 29bc74fe3..31ad277de 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -22,6 +22,7 @@ * 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] @@ -29,6 +30,7 @@ [#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 ## 3.0.0-beta.6 - 2021-04-17 From f277b128b65c874b46ff897e4b770f8a713b88a4 Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Thu, 13 May 2021 19:24:32 +0800 Subject: [PATCH 195/339] cleanup ws test (#2213) --- actix-http/tests/test_ws.rs | 182 +++++++++++++++--------------------- 1 file changed, 76 insertions(+), 106 deletions(-) diff --git a/actix-http/tests/test_ws.rs b/actix-http/tests/test_ws.rs index bf1ca9385..b17d4211f 100644 --- a/actix-http/tests/test_ws.rs +++ b/actix-http/tests/test_ws.rs @@ -1,193 +1,163 @@ -use std::cell::Cell; -use std::future::Future; -use std::marker::PhantomData; -use std::pin::Pin; -use std::sync::{Arc, Mutex}; -use std::task::{Context, Poll}; +use std::{ + cell::Cell, + task::{Context, Poll}, +}; use actix_codec::{AsyncRead, AsyncWrite, Framed}; -use actix_http::{body, h1, ws, Error, HttpService, Request, Response}; +use actix_http::{ + body::BodySize, + h1, + ws::{self, CloseCode, Frame, Item, Message}, + Error, HttpService, Request, Response, +}; use actix_http_test::test_server; use actix_service::{fn_factory, Service}; -use actix_utils::future; use bytes::Bytes; +use futures_core::future::LocalBoxFuture; use futures_util::{SinkExt as _, StreamExt as _}; -use crate::ws::Dispatcher; +#[derive(Clone)] +struct WsService(Cell); -struct WsService(Arc, Cell)>>); - -impl WsService { +impl WsService { fn new() -> Self { - WsService(Arc::new(Mutex::new((PhantomData, Cell::new(false))))) + WsService(Cell::new(false)) } fn set_polled(&self) { - *self.0.lock().unwrap().1.get_mut() = true; + self.0.set(true); } fn was_polled(&self) -> bool { - self.0.lock().unwrap().1.get() + self.0.get() } } -impl Clone for WsService { - fn clone(&self) -> Self { - WsService(self.0.clone()) - } -} - -impl Service<(Request, Framed)> for WsService +impl Service<(Request, Framed)> for WsService where T: AsyncRead + AsyncWrite + Unpin + 'static, { type Response = (); type Error = Error; - type Future = Pin>>>; + type Future = LocalBoxFuture<'static, Result>; - fn poll_ready(&self, _ctx: &mut Context<'_>) -> Poll> { + fn poll_ready(&self, _: &mut Context<'_>) -> Poll> { self.set_polled(); Poll::Ready(Ok(())) } fn call(&self, (req, mut framed): (Request, Framed)) -> Self::Future { - let fut = async move { - let res = ws::handshake(req.head()).unwrap().message_body(()).unwrap(); + assert!(self.was_polled()); - framed - .send((res, body::BodySize::None).into()) - .await - .unwrap(); + Box::pin(async move { + let res = ws::handshake(req.head())?.message_body(())?; - Dispatcher::with(framed.replace_codec(ws::Codec::new()), service) - .await - .map_err(|_| panic!()) - }; + framed.send((res, BodySize::None).into()).await?; - Box::pin(fut) + let framed = framed.replace_codec(ws::Codec::new()); + + ws::Dispatcher::with(framed, service).await?; + + Ok(()) + }) } } -async fn service(msg: ws::Frame) -> Result { +async fn service(msg: Frame) -> Result { let msg = match msg { - ws::Frame::Ping(msg) => ws::Message::Pong(msg), - ws::Frame::Text(text) => { - ws::Message::Text(String::from_utf8_lossy(&text).into_owned().into()) + Frame::Ping(msg) => Message::Pong(msg), + Frame::Text(text) => { + Message::Text(String::from_utf8_lossy(&text).into_owned().into()) } - ws::Frame::Binary(bin) => ws::Message::Binary(bin), - ws::Frame::Continuation(item) => ws::Message::Continuation(item), - ws::Frame::Close(reason) => ws::Message::Close(reason), - _ => panic!(), + 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)), }; + Ok(msg) } #[actix_rt::test] async fn test_simple() { - let ws_service = WsService::new(); - let mut srv = test_server({ - let ws_service = ws_service.clone(); - move || { - let ws_service = ws_service.clone(); - HttpService::build() - .upgrade(fn_factory(move || future::ok::<_, ()>(ws_service.clone()))) - .finish(|_| future::ok::<_, ()>(Response::not_found())) - .tcp() - } + let mut srv = test_server(|| { + HttpService::build() + .upgrade(fn_factory(|| async { Ok::<_, ()>(WsService::new()) })) + .finish(|_| async { Ok::<_, ()>(Response::not_found()) }) + .tcp() }) .await; // client service let mut framed = srv.ws().await.unwrap(); - framed.send(ws::Message::Text("text".into())).await.unwrap(); - let (item, mut framed) = framed.into_future().await; - assert_eq!( - item.unwrap().unwrap(), - ws::Frame::Text(Bytes::from_static(b"text")) - ); + framed.send(Message::Text("text".into())).await.unwrap(); + + let item = framed.next().await.unwrap().unwrap(); + assert_eq!(item, Frame::Text(Bytes::from_static(b"text"))); + + framed.send(Message::Binary("text".into())).await.unwrap(); + + let item = framed.next().await.unwrap().unwrap(); + assert_eq!(item, Frame::Binary(Bytes::from_static(&b"text"[..]))); + + framed.send(Message::Ping("text".into())).await.unwrap(); + let item = framed.next().await.unwrap().unwrap(); + assert_eq!(item, Frame::Pong("text".to_string().into())); framed - .send(ws::Message::Binary("text".into())) + .send(Message::Continuation(Item::FirstText("text".into()))) .await .unwrap(); - let (item, mut framed) = framed.into_future().await; + let item = framed.next().await.unwrap().unwrap(); assert_eq!( - item.unwrap().unwrap(), - ws::Frame::Binary(Bytes::from_static(&b"text"[..])) - ); - - framed.send(ws::Message::Ping("text".into())).await.unwrap(); - let (item, mut framed) = framed.into_future().await; - assert_eq!( - item.unwrap().unwrap(), - ws::Frame::Pong("text".to_string().into()) - ); - - framed - .send(ws::Message::Continuation(ws::Item::FirstText( - "text".into(), - ))) - .await - .unwrap(); - let (item, mut framed) = framed.into_future().await; - assert_eq!( - item.unwrap().unwrap(), - ws::Frame::Continuation(ws::Item::FirstText(Bytes::from_static(b"text"))) + item, + Frame::Continuation(Item::FirstText(Bytes::from_static(b"text"))) ); assert!(framed - .send(ws::Message::Continuation(ws::Item::FirstText( - "text".into() - ))) + .send(Message::Continuation(Item::FirstText("text".into()))) .await .is_err()); assert!(framed - .send(ws::Message::Continuation(ws::Item::FirstBinary( - "text".into() - ))) + .send(Message::Continuation(Item::FirstBinary("text".into()))) .await .is_err()); framed - .send(ws::Message::Continuation(ws::Item::Continue("text".into()))) + .send(Message::Continuation(Item::Continue("text".into()))) .await .unwrap(); - let (item, mut framed) = framed.into_future().await; + let item = framed.next().await.unwrap().unwrap(); assert_eq!( - item.unwrap().unwrap(), - ws::Frame::Continuation(ws::Item::Continue(Bytes::from_static(b"text"))) + item, + Frame::Continuation(Item::Continue(Bytes::from_static(b"text"))) ); framed - .send(ws::Message::Continuation(ws::Item::Last("text".into()))) + .send(Message::Continuation(Item::Last("text".into()))) .await .unwrap(); - let (item, mut framed) = framed.into_future().await; + let item = framed.next().await.unwrap().unwrap(); assert_eq!( - item.unwrap().unwrap(), - ws::Frame::Continuation(ws::Item::Last(Bytes::from_static(b"text"))) + item, + Frame::Continuation(Item::Last(Bytes::from_static(b"text"))) ); assert!(framed - .send(ws::Message::Continuation(ws::Item::Continue("text".into()))) + .send(Message::Continuation(Item::Continue("text".into()))) .await .is_err()); assert!(framed - .send(ws::Message::Continuation(ws::Item::Last("text".into()))) + .send(Message::Continuation(Item::Last("text".into()))) .await .is_err()); framed - .send(ws::Message::Close(Some(ws::CloseCode::Normal.into()))) + .send(Message::Close(Some(CloseCode::Normal.into()))) .await .unwrap(); - let (item, _framed) = framed.into_future().await; - assert_eq!( - item.unwrap().unwrap(), - ws::Frame::Close(Some(ws::CloseCode::Normal.into())) - ); - - assert!(ws_service.was_polled()); + let item = framed.next().await.unwrap().unwrap(); + assert_eq!(item, Frame::Close(Some(CloseCode::Normal.into()))); } From 2a8c650f2c4ccc5cafcfba01e85109a0388e604f Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Fri, 14 May 2021 16:40:00 +0100 Subject: [PATCH 196/339] move internalerror to actix web (#2215) --- Cargo.toml | 1 + actix-http/CHANGES.md | 4 + actix-http/Cargo.toml | 1 - actix-http/src/body/body.rs | 93 ++++----- actix-http/src/body/mod.rs | 2 +- actix-http/src/error.rs | 311 +------------------------------ actix-http/src/helpers.rs | 3 +- actix-http/tests/test_client.rs | 17 +- actix-http/tests/test_openssl.rs | 17 +- actix-http/tests/test_rustls.rs | 20 +- actix-http/tests/test_server.rs | 41 ++-- src/data.rs | 13 +- src/error/internal.rs | 304 ++++++++++++++++++++++++++++++ src/{error.rs => error/mod.rs} | 4 + src/request_data.rs | 4 +- src/responder.rs | 3 +- src/types/path.rs | 8 +- src/types/payload.rs | 7 +- 18 files changed, 456 insertions(+), 397 deletions(-) create mode 100644 src/error/internal.rs rename src/{error.rs => error/mod.rs} (99%) diff --git a/Cargo.toml b/Cargo.toml index 75b5e3a8e..5aa302333 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -82,6 +82,7 @@ language-tags = "0.3" once_cell = "1.5" log = "0.4" mime = "0.3" +paste = "1" pin-project = "1.0.0" regex = "1.4" serde = { version = "1.0", features = ["derive"] } diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 31ad277de..ec7d8ee3b 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -2,6 +2,7 @@ ## Unreleased - 2021-xx-xx ### 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] @@ -25,12 +26,15 @@ * 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 [#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 ## 3.0.0-beta.6 - 2021-04-17 diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 638557807..1f7df39a6 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -62,7 +62,6 @@ local-channel = "0.1" once_cell = "1.5" log = "0.4" mime = "0.3" -paste = "1" percent-encoding = "2.1" pin-project = "1.0.0" pin-project-lite = "0.2" diff --git a/actix-http/src/body/body.rs b/actix-http/src/body/body.rs index 4c95bd31a..3fda8ae11 100644 --- a/actix-http/src/body/body.rs +++ b/actix-http/src/body/body.rs @@ -13,9 +13,10 @@ use crate::error::Error; use super::{BodySize, BodyStream, MessageBody, MessageBodyMapErr, SizedStream}; +pub type Body = AnyBody; + /// Represents various types of HTTP message body. -// #[deprecated(since = "4.0.0", note = "Use body types directly.")] -pub enum Body { +pub enum AnyBody { /// Empty response. `Content-Length` header is not set. None, @@ -29,14 +30,14 @@ pub enum Body { Message(BoxAnyBody), } -impl Body { +impl AnyBody { /// Create body from slice (copy) - pub fn from_slice(s: &[u8]) -> Body { - Body::Bytes(Bytes::copy_from_slice(s)) + 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) -> Body + pub fn from_message(body: B) -> Self where B: MessageBody + 'static, B::Error: Into>, @@ -45,15 +46,15 @@ impl Body { } } -impl MessageBody for Body { +impl MessageBody for AnyBody { type Error = Error; fn size(&self) -> BodySize { match self { - Body::None => BodySize::None, - Body::Empty => BodySize::Empty, - Body::Bytes(ref bin) => BodySize::Sized(bin.len() as u64), - Body::Message(ref body) => body.size(), + AnyBody::None => BodySize::None, + AnyBody::Empty => BodySize::Empty, + AnyBody::Bytes(ref bin) => BodySize::Sized(bin.len() as u64), + AnyBody::Message(ref body) => body.size(), } } @@ -62,9 +63,9 @@ impl MessageBody for Body { cx: &mut Context<'_>, ) -> Poll>> { match self.get_mut() { - Body::None => Poll::Ready(None), - Body::Empty => Poll::Ready(None), - Body::Bytes(ref mut bin) => { + AnyBody::None => Poll::Ready(None), + AnyBody::Empty => Poll::Ready(None), + AnyBody::Bytes(ref mut bin) => { let len = bin.len(); if len == 0 { Poll::Ready(None) @@ -74,7 +75,7 @@ impl MessageBody for Body { } // TODO: MSRV 1.51: poll_map_err - Body::Message(body) => match ready!(body.as_pin_mut().poll_next(cx)) { + AnyBody::Message(body) => match ready!(body.as_pin_mut().poll_next(cx)) { Some(Err(err)) => Poll::Ready(Some(Err(err.into()))), Some(Ok(val)) => Poll::Ready(Some(Ok(val))), None => Poll::Ready(None), @@ -83,100 +84,100 @@ impl MessageBody for Body { } } -impl PartialEq for Body { +impl PartialEq for AnyBody { fn eq(&self, other: &Body) -> bool { match *self { - Body::None => matches!(*other, Body::None), - Body::Empty => matches!(*other, Body::Empty), - Body::Bytes(ref b) => match *other { - Body::Bytes(ref b2) => b == b2, + 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, }, - Body::Message(_) => false, + AnyBody::Message(_) => false, } } } -impl fmt::Debug for Body { +impl fmt::Debug for AnyBody { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match *self { - Body::None => write!(f, "Body::None"), - Body::Empty => write!(f, "Body::Empty"), - Body::Bytes(ref b) => write!(f, "Body::Bytes({:?})", b), - Body::Message(_) => write!(f, "Body::Message(_)"), + 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(_)"), } } } -impl From<&'static str> for Body { +impl From<&'static str> for AnyBody { fn from(s: &'static str) -> Body { - Body::Bytes(Bytes::from_static(s.as_ref())) + AnyBody::Bytes(Bytes::from_static(s.as_ref())) } } -impl From<&'static [u8]> for Body { +impl From<&'static [u8]> for AnyBody { fn from(s: &'static [u8]) -> Body { - Body::Bytes(Bytes::from_static(s)) + AnyBody::Bytes(Bytes::from_static(s)) } } -impl From> for Body { +impl From> for AnyBody { fn from(vec: Vec) -> Body { - Body::Bytes(Bytes::from(vec)) + AnyBody::Bytes(Bytes::from(vec)) } } -impl From for Body { +impl From for AnyBody { fn from(s: String) -> Body { s.into_bytes().into() } } -impl From<&'_ String> for Body { +impl From<&'_ String> for AnyBody { fn from(s: &String) -> Body { - Body::Bytes(Bytes::copy_from_slice(AsRef::<[u8]>::as_ref(&s))) + AnyBody::Bytes(Bytes::copy_from_slice(AsRef::<[u8]>::as_ref(&s))) } } -impl From> for Body { +impl From> for AnyBody { fn from(s: Cow<'_, str>) -> Body { match s { - Cow::Owned(s) => Body::from(s), + Cow::Owned(s) => AnyBody::from(s), Cow::Borrowed(s) => { - Body::Bytes(Bytes::copy_from_slice(AsRef::<[u8]>::as_ref(s))) + AnyBody::Bytes(Bytes::copy_from_slice(AsRef::<[u8]>::as_ref(s))) } } } } -impl From for Body { +impl From for AnyBody { fn from(s: Bytes) -> Body { - Body::Bytes(s) + AnyBody::Bytes(s) } } -impl From for Body { +impl From for AnyBody { fn from(s: BytesMut) -> Body { - Body::Bytes(s.freeze()) + AnyBody::Bytes(s.freeze()) } } -impl From> for Body +impl From> for AnyBody where S: Stream> + 'static, { fn from(s: SizedStream) -> Body { - Body::from_message(s) + AnyBody::from_message(s) } } -impl From> for Body +impl From> for AnyBody where S: Stream> + 'static, E: Into + 'static, { fn from(s: BodyStream) -> Body { - Body::from_message(s) + AnyBody::from_message(s) } } diff --git a/actix-http/src/body/mod.rs b/actix-http/src/body/mod.rs index cdfcd226b..11aff039e 100644 --- a/actix-http/src/body/mod.rs +++ b/actix-http/src/body/mod.rs @@ -15,7 +15,7 @@ mod response_body; mod size; mod sized_stream; -pub use self::body::{Body, BoxAnyBody}; +pub use self::body::{AnyBody, Body, BoxAnyBody}; pub use self::body_stream::BodyStream; pub use self::message_body::MessageBody; pub(crate) use self::message_body::MessageBodyMapErr; diff --git a/actix-http/src/error.rs b/actix-http/src/error.rs index 20b2a2d75..92efd572d 100644 --- a/actix-http/src/error.rs +++ b/actix-http/src/error.rs @@ -1,7 +1,6 @@ //! Error and Result module use std::{ - cell::RefCell, error::Error as StdError, fmt, io::{self, Write as _}, @@ -14,7 +13,7 @@ use derive_more::{Display, Error, From}; use http::{header, uri::InvalidUri, StatusCode}; use serde::de::value::Error as DeError; -use crate::{body::Body, helpers::Writer, Response, ResponseBuilder}; +use crate::{body::Body, helpers::Writer, Response}; pub use http::Error as HttpError; @@ -121,20 +120,6 @@ impl From for Error { } } -/// Convert Response to a Error -impl From> for Error { - fn from(res: Response) -> Error { - InternalError::from_response("", res).into() - } -} - -/// Convert ResponseBuilder to a Error -impl From for Error { - fn from(mut res: ResponseBuilder) -> Error { - InternalError::from_response("", res.finish()).into() - } -} - #[derive(Debug, Display, Error)] #[display(fmt = "Unknown Error")] struct UnitError; @@ -449,179 +434,12 @@ mod content_type_test_impls { } } -/// Return `BadRequest` for `ContentTypeError` impl ResponseError for ContentTypeError { fn status_code(&self) -> StatusCode { StatusCode::BAD_REQUEST } } -/// Helper type that can wrap any error and generate custom response. -/// -/// In following example any `io::Error` will be converted into "BAD REQUEST" -/// response as opposite to *INTERNAL SERVER ERROR* which is defined by -/// default. -/// -/// ``` -/// # use std::io; -/// # use actix_http::{error, Request}; -/// fn index(req: Request) -> Result<&'static str, actix_http::Error> { -/// Err(error::ErrorBadRequest(io::Error::new(io::ErrorKind::Other, "error"))) -/// } -/// ``` -pub struct InternalError { - cause: T, - status: InternalErrorType, -} - -enum InternalErrorType { - Status(StatusCode), - Response(RefCell>>), -} - -impl InternalError { - /// Create `InternalError` instance - pub fn new(cause: T, status: StatusCode) -> Self { - InternalError { - cause, - status: InternalErrorType::Status(status), - } - } - - /// Create `InternalError` with predefined `Response`. - pub fn from_response(cause: T, response: Response) -> Self { - InternalError { - cause, - status: InternalErrorType::Response(RefCell::new(Some(response))), - } - } -} - -impl fmt::Debug for InternalError -where - T: fmt::Debug + 'static, -{ - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Debug::fmt(&self.cause, f) - } -} - -impl fmt::Display for InternalError -where - T: fmt::Display + 'static, -{ - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Display::fmt(&self.cause, f) - } -} - -impl ResponseError for InternalError -where - T: fmt::Debug + fmt::Display + 'static, -{ - fn status_code(&self) -> StatusCode { - match self.status { - InternalErrorType::Status(st) => st, - InternalErrorType::Response(ref resp) => { - if let Some(resp) = resp.borrow().as_ref() { - resp.head().status - } else { - StatusCode::INTERNAL_SERVER_ERROR - } - } - } - } - - fn error_response(&self) -> Response { - match self.status { - InternalErrorType::Status(st) => { - let mut res = Response::new(st); - let mut buf = BytesMut::new(); - let _ = write!(Writer(&mut buf), "{}", self); - res.headers_mut().insert( - header::CONTENT_TYPE, - header::HeaderValue::from_static("text/plain; charset=utf-8"), - ); - res.set_body(Body::from(buf)) - } - InternalErrorType::Response(ref resp) => { - if let Some(resp) = resp.borrow_mut().take() { - resp - } else { - Response::new(StatusCode::INTERNAL_SERVER_ERROR) - } - } - } - } -} - -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() - } - } - } -} - -error_helper!(ErrorBadRequest, BAD_REQUEST); -error_helper!(ErrorUnauthorized, UNAUTHORIZED); -error_helper!(ErrorPaymentRequired, PAYMENT_REQUIRED); -error_helper!(ErrorForbidden, FORBIDDEN); -error_helper!(ErrorNotFound, NOT_FOUND); -error_helper!(ErrorMethodNotAllowed, METHOD_NOT_ALLOWED); -error_helper!(ErrorNotAcceptable, NOT_ACCEPTABLE); -error_helper!( - ErrorProxyAuthenticationRequired, - PROXY_AUTHENTICATION_REQUIRED -); -error_helper!(ErrorRequestTimeout, REQUEST_TIMEOUT); -error_helper!(ErrorConflict, CONFLICT); -error_helper!(ErrorGone, GONE); -error_helper!(ErrorLengthRequired, LENGTH_REQUIRED); -error_helper!(ErrorPayloadTooLarge, PAYLOAD_TOO_LARGE); -error_helper!(ErrorUriTooLong, URI_TOO_LONG); -error_helper!(ErrorUnsupportedMediaType, UNSUPPORTED_MEDIA_TYPE); -error_helper!(ErrorRangeNotSatisfiable, RANGE_NOT_SATISFIABLE); -error_helper!(ErrorImATeapot, IM_A_TEAPOT); -error_helper!(ErrorMisdirectedRequest, MISDIRECTED_REQUEST); -error_helper!(ErrorUnprocessableEntity, UNPROCESSABLE_ENTITY); -error_helper!(ErrorLocked, LOCKED); -error_helper!(ErrorFailedDependency, FAILED_DEPENDENCY); -error_helper!(ErrorUpgradeRequired, UPGRADE_REQUIRED); -error_helper!(ErrorPreconditionFailed, PRECONDITION_FAILED); -error_helper!(ErrorPreconditionRequired, PRECONDITION_REQUIRED); -error_helper!(ErrorTooManyRequests, TOO_MANY_REQUESTS); -error_helper!( - ErrorRequestHeaderFieldsTooLarge, - REQUEST_HEADER_FIELDS_TOO_LARGE -); -error_helper!( - ErrorUnavailableForLegalReasons, - UNAVAILABLE_FOR_LEGAL_REASONS -); -error_helper!(ErrorExpectationFailed, EXPECTATION_FAILED); -error_helper!(ErrorInternalServerError, INTERNAL_SERVER_ERROR); -error_helper!(ErrorNotImplemented, NOT_IMPLEMENTED); -error_helper!(ErrorBadGateway, BAD_GATEWAY); -error_helper!(ErrorServiceUnavailable, SERVICE_UNAVAILABLE); -error_helper!(ErrorGatewayTimeout, GATEWAY_TIMEOUT); -error_helper!(ErrorHttpVersionNotSupported, HTTP_VERSION_NOT_SUPPORTED); -error_helper!(ErrorVariantAlsoNegotiates, VARIANT_ALSO_NEGOTIATES); -error_helper!(ErrorInsufficientStorage, INSUFFICIENT_STORAGE); -error_helper!(ErrorLoopDetected, LOOP_DETECTED); -error_helper!(ErrorNotExtended, NOT_EXTENDED); -error_helper!( - ErrorNetworkAuthenticationRequired, - NETWORK_AUTHENTICATION_REQUIRED -); - #[cfg(test)] mod tests { use super::*; @@ -718,13 +536,6 @@ mod tests { from!(httparse::Error::Version => ParseError::Version); } - #[test] - fn test_internal_error() { - let err = InternalError::from_response(ParseError::Method, Response::ok()); - let resp: Response = err.error_response(); - assert_eq!(resp.status(), StatusCode::OK); - } - #[test] fn test_error_casting() { let err = PayloadError::Overflow; @@ -734,124 +545,4 @@ mod tests { let not_err = resp_err.downcast_ref::(); assert!(not_err.is_none()); } - - #[test] - fn test_error_helpers() { - let res: Response = ErrorBadRequest("err").into(); - assert_eq!(res.status(), StatusCode::BAD_REQUEST); - - let res: Response = ErrorUnauthorized("err").into(); - assert_eq!(res.status(), StatusCode::UNAUTHORIZED); - - let res: Response = ErrorPaymentRequired("err").into(); - assert_eq!(res.status(), StatusCode::PAYMENT_REQUIRED); - - let res: Response = ErrorForbidden("err").into(); - assert_eq!(res.status(), StatusCode::FORBIDDEN); - - let res: Response = ErrorNotFound("err").into(); - assert_eq!(res.status(), StatusCode::NOT_FOUND); - - let res: Response = ErrorMethodNotAllowed("err").into(); - assert_eq!(res.status(), StatusCode::METHOD_NOT_ALLOWED); - - let res: Response = ErrorNotAcceptable("err").into(); - assert_eq!(res.status(), StatusCode::NOT_ACCEPTABLE); - - let res: Response = ErrorProxyAuthenticationRequired("err").into(); - assert_eq!(res.status(), StatusCode::PROXY_AUTHENTICATION_REQUIRED); - - let res: Response = ErrorRequestTimeout("err").into(); - assert_eq!(res.status(), StatusCode::REQUEST_TIMEOUT); - - let res: Response = ErrorConflict("err").into(); - assert_eq!(res.status(), StatusCode::CONFLICT); - - let res: Response = ErrorGone("err").into(); - assert_eq!(res.status(), StatusCode::GONE); - - let res: Response = ErrorLengthRequired("err").into(); - assert_eq!(res.status(), StatusCode::LENGTH_REQUIRED); - - let res: Response = ErrorPreconditionFailed("err").into(); - assert_eq!(res.status(), StatusCode::PRECONDITION_FAILED); - - let res: Response = ErrorPayloadTooLarge("err").into(); - assert_eq!(res.status(), StatusCode::PAYLOAD_TOO_LARGE); - - let res: Response = ErrorUriTooLong("err").into(); - assert_eq!(res.status(), StatusCode::URI_TOO_LONG); - - let res: Response = ErrorUnsupportedMediaType("err").into(); - assert_eq!(res.status(), StatusCode::UNSUPPORTED_MEDIA_TYPE); - - let res: Response = ErrorRangeNotSatisfiable("err").into(); - assert_eq!(res.status(), StatusCode::RANGE_NOT_SATISFIABLE); - - let res: Response = ErrorExpectationFailed("err").into(); - assert_eq!(res.status(), StatusCode::EXPECTATION_FAILED); - - let res: Response = ErrorImATeapot("err").into(); - assert_eq!(res.status(), StatusCode::IM_A_TEAPOT); - - let res: Response = ErrorMisdirectedRequest("err").into(); - assert_eq!(res.status(), StatusCode::MISDIRECTED_REQUEST); - - let res: Response = ErrorUnprocessableEntity("err").into(); - assert_eq!(res.status(), StatusCode::UNPROCESSABLE_ENTITY); - - let res: Response = ErrorLocked("err").into(); - assert_eq!(res.status(), StatusCode::LOCKED); - - let res: Response = ErrorFailedDependency("err").into(); - assert_eq!(res.status(), StatusCode::FAILED_DEPENDENCY); - - let res: Response = ErrorUpgradeRequired("err").into(); - assert_eq!(res.status(), StatusCode::UPGRADE_REQUIRED); - - let res: Response = ErrorPreconditionRequired("err").into(); - assert_eq!(res.status(), StatusCode::PRECONDITION_REQUIRED); - - let res: Response = ErrorTooManyRequests("err").into(); - assert_eq!(res.status(), StatusCode::TOO_MANY_REQUESTS); - - let res: Response = ErrorRequestHeaderFieldsTooLarge("err").into(); - assert_eq!(res.status(), StatusCode::REQUEST_HEADER_FIELDS_TOO_LARGE); - - let res: Response = ErrorUnavailableForLegalReasons("err").into(); - assert_eq!(res.status(), StatusCode::UNAVAILABLE_FOR_LEGAL_REASONS); - - let res: Response = ErrorInternalServerError("err").into(); - assert_eq!(res.status(), StatusCode::INTERNAL_SERVER_ERROR); - - let res: Response = ErrorNotImplemented("err").into(); - assert_eq!(res.status(), StatusCode::NOT_IMPLEMENTED); - - let res: Response = ErrorBadGateway("err").into(); - assert_eq!(res.status(), StatusCode::BAD_GATEWAY); - - let res: Response = ErrorServiceUnavailable("err").into(); - assert_eq!(res.status(), StatusCode::SERVICE_UNAVAILABLE); - - let res: Response = ErrorGatewayTimeout("err").into(); - assert_eq!(res.status(), StatusCode::GATEWAY_TIMEOUT); - - let res: Response = ErrorHttpVersionNotSupported("err").into(); - assert_eq!(res.status(), StatusCode::HTTP_VERSION_NOT_SUPPORTED); - - let res: Response = ErrorVariantAlsoNegotiates("err").into(); - assert_eq!(res.status(), StatusCode::VARIANT_ALSO_NEGOTIATES); - - let res: Response = ErrorInsufficientStorage("err").into(); - assert_eq!(res.status(), StatusCode::INSUFFICIENT_STORAGE); - - let res: Response = ErrorLoopDetected("err").into(); - assert_eq!(res.status(), StatusCode::LOOP_DETECTED); - - let res: Response = ErrorNotExtended("err").into(); - assert_eq!(res.status(), StatusCode::NOT_EXTENDED); - - let res: Response = ErrorNetworkAuthenticationRequired("err").into(); - assert_eq!(res.status(), StatusCode::NETWORK_AUTHENTICATION_REQUIRED); - } } diff --git a/actix-http/src/helpers.rs b/actix-http/src/helpers.rs index b00ca307e..34bb989f9 100644 --- a/actix-http/src/helpers.rs +++ b/actix-http/src/helpers.rs @@ -41,7 +41,8 @@ pub fn write_content_length(n: u64, buf: &mut B) { buf.put_slice(b"\r\n"); } -// TODO: bench why this is needed +// 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); impl<'a, B> io::Write for Writer<'a, B> diff --git a/actix-http/tests/test_client.rs b/actix-http/tests/test_client.rs index 0a06d90e5..4bd7dbe14 100644 --- a/actix-http/tests/test_client.rs +++ b/actix-http/tests/test_client.rs @@ -1,10 +1,11 @@ use actix_http::{ - error, http, http::StatusCode, HttpMessage, HttpService, Request, Response, + http, http::StatusCode, HttpMessage, HttpService, Request, Response, ResponseError, }; use actix_http_test::test_server; use actix_service::ServiceFactoryExt; use actix_utils::future; use bytes::Bytes; +use derive_more::{Display, Error}; use futures_util::StreamExt as _; const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ @@ -92,6 +93,16 @@ async fn test_with_query_parameter() { assert!(response.status().is_success()); } +#[derive(Debug, Display, Error)] +#[display(fmt = "expect failed")] +struct ExpectFailed; + +impl ResponseError for ExpectFailed { + fn status_code(&self) -> StatusCode { + StatusCode::EXPECTATION_FAILED + } +} + #[actix_rt::test] async fn test_h1_expect() { let srv = test_server(move || { @@ -100,7 +111,7 @@ async fn test_h1_expect() { if req.headers().contains_key("AUTH") { Ok(req) } else { - Err(error::ErrorExpectationFailed("expect failed")) + Err(ExpectFailed) } }) .h1(|req: Request| async move { @@ -134,7 +145,7 @@ async fn test_h1_expect() { let response = request.send_body("expect body").await.unwrap(); assert_eq!(response.status(), StatusCode::EXPECTATION_FAILED); - // test exepct would continue + // test expect would continue let request = srv .request(http::Method::GET, srv.url("/")) .insert_header(("Expect", "100-continue")) diff --git a/actix-http/tests/test_openssl.rs b/actix-http/tests/test_openssl.rs index 7cbd58518..d3a3bea3b 100644 --- a/actix-http/tests/test_openssl.rs +++ b/actix-http/tests/test_openssl.rs @@ -6,17 +6,18 @@ use std::io; use actix_http::{ body::{Body, SizedStream}, - error::{ErrorBadRequest, PayloadError}, + error::PayloadError, http::{ header::{self, HeaderName, HeaderValue}, Method, StatusCode, Version, }, - Error, HttpMessage, HttpService, Request, Response, + Error, HttpMessage, HttpService, Request, Response, ResponseError, }; use actix_http_test::test_server; use actix_service::{fn_service, ServiceFactoryExt}; 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 openssl::{ @@ -401,11 +402,21 @@ async fn test_h2_response_http_error_handling() { assert_eq!(bytes, Bytes::from_static(b"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 + } +} + #[actix_rt::test] async fn test_h2_service_error() { let mut srv = test_server(move || { HttpService::build() - .h2(|_| err::, Error>(ErrorBadRequest("error"))) + .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 a122ab847..2382d1ad3 100644 --- a/actix-http/tests/test_rustls.rs +++ b/actix-http/tests/test_rustls.rs @@ -4,18 +4,18 @@ extern crate tls_rustls as rustls; use actix_http::{ body::{Body, SizedStream}, - error::{self, PayloadError}, + error::PayloadError, http::{ header::{self, HeaderName, HeaderValue}, Method, StatusCode, Version, }, - Error, HttpService, Request, Response, + Error, HttpService, Request, Response, ResponseError, }; use actix_http_test::test_server; use actix_service::{fn_factory_with_config, fn_service}; 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::{ @@ -417,11 +417,21 @@ async fn test_h2_response_http_error_handling() { assert_eq!(bytes, Bytes::from_static(b"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 + } +} + #[actix_rt::test] async fn test_h2_service_error() { let mut srv = test_server(move || { HttpService::build() - .h2(|_| err::, Error>(error::ErrorBadRequest("error"))) + .h2(|_| err::, _>(BadRequest)) .rustls(tls_config()) }) .await; @@ -438,7 +448,7 @@ async fn test_h2_service_error() { async fn test_h1_service_error() { let mut srv = test_server(move || { HttpService::build() - .h1(|_| err::, Error>(error::ErrorBadRequest("error"))) + .h1(|_| err::, _>(BadRequest)) .rustls(tls_config()) }) .await; diff --git a/actix-http/tests/test_server.rs b/actix-http/tests/test_server.rs index 9b8b039c3..abfda249c 100644 --- a/actix-http/tests/test_server.rs +++ b/actix-http/tests/test_server.rs @@ -2,23 +2,22 @@ use std::io::{Read, Write}; use std::time::Duration; use std::{net, thread}; +use actix_http::{ + body::{Body, SizedStream}, + http::{self, header, StatusCode}, + Error, HttpService, KeepAlive, Request, Response, +}; +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 regex::Regex; -use actix_http::HttpMessage; -use actix_http::{ - body::{Body, SizedStream}, - error, - http::{self, header, StatusCode}, - Error, HttpService, KeepAlive, Request, Response, -}; - #[actix_rt::test] async fn test_h1() { let srv = test_server(|| { @@ -58,6 +57,16 @@ async fn test_h1_2() { assert!(response.status().is_success()); } +#[derive(Debug, Display, Error)] +#[display(fmt = "expect failed")] +struct ExpectFailed; + +impl ResponseError for ExpectFailed { + fn status_code(&self) -> StatusCode { + StatusCode::PRECONDITION_FAILED + } +} + #[actix_rt::test] async fn test_expect_continue() { let srv = test_server(|| { @@ -66,7 +75,7 @@ async fn test_expect_continue() { if req.head().uri.query() == Some("yes=") { ok(req) } else { - err(error::ErrorPreconditionFailed("error")) + err(ExpectFailed) } })) .finish(|_| ok::<_, ()>(Response::ok())) @@ -96,7 +105,7 @@ async fn test_expect_continue_h1() { if req.head().uri.query() == Some("yes=") { ok(req) } else { - err(error::ErrorPreconditionFailed("error")) + err(ExpectFailed) } }) })) @@ -647,11 +656,21 @@ async fn test_h1_response_http_error_handling() { assert_eq!(bytes, Bytes::from_static(b"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 + } +} + #[actix_rt::test] async fn test_h1_service_error() { let mut srv = test_server(|| { HttpService::build() - .h1(|_| err::, _>(error::ErrorBadRequest("error"))) + .h1(|_| err::, _>(BadRequest)) .tcp() }) .await; diff --git a/src/data.rs b/src/data.rs index bd9b88301..d63c15580 100644 --- a/src/data.rs +++ b/src/data.rs @@ -1,16 +1,13 @@ -use std::any::type_name; -use std::ops::Deref; -use std::sync::Arc; +use std::{any::type_name, ops::Deref, sync::Arc}; -use actix_http::error::{Error, ErrorInternalServerError}; -use actix_http::Extensions; +use actix_http::{error::Error, Extensions}; use actix_utils::future::{err, ok, Ready}; use futures_core::future::LocalBoxFuture; use serde::Serialize; -use crate::dev::Payload; -use crate::extract::FromRequest; -use crate::request::HttpRequest; +use crate::{ + dev::Payload, error::ErrorInternalServerError, extract::FromRequest, request::HttpRequest, +}; /// Data factory. pub(crate) trait DataFactory { diff --git a/src/error/internal.rs b/src/error/internal.rs new file mode 100644 index 000000000..23b7dc31e --- /dev/null +++ b/src/error/internal.rs @@ -0,0 +1,304 @@ +use std::{cell::RefCell, fmt, io::Write as _}; + +use actix_http::{body::Body, header, Response, StatusCode}; +use bytes::{BufMut as _, BytesMut}; + +use crate::{Error, HttpResponse, ResponseError}; + +/// Wraps errors to alter the generated response status code. +/// +/// In following example, the `io::Error` is wrapped into `ErrorBadRequest` which will generate a +/// response with the 400 Bad Request status code instead of the usual status code generated by +/// an `io::Error`. +/// +/// # Examples +/// ``` +/// # use std::io; +/// # use actix_web::{error, HttpRequest}; +/// async fn handler_error() -> Result { +/// let err = io::Error::new(io::ErrorKind::Other, "error"); +/// Err(error::ErrorBadRequest(err)) +/// } +/// ``` +pub struct InternalError { + cause: T, + status: InternalErrorType, +} + +enum InternalErrorType { + Status(StatusCode), + Response(RefCell>), +} + +impl InternalError { + /// Constructs an `InternalError` with given status code. + pub fn new(cause: T, status: StatusCode) -> Self { + InternalError { + cause, + status: InternalErrorType::Status(status), + } + } + + /// Constructs an `InternalError` with pre-defined response. + pub fn from_response(cause: T, response: HttpResponse) -> Self { + InternalError { + cause, + status: InternalErrorType::Response(RefCell::new(Some(response))), + } + } +} + +impl fmt::Debug for InternalError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.cause.fmt(f) + } +} + +impl fmt::Display for InternalError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.cause.fmt(f) + } +} + +impl ResponseError for InternalError +where + T: fmt::Debug + fmt::Display, +{ + fn status_code(&self) -> StatusCode { + match self.status { + InternalErrorType::Status(st) => st, + InternalErrorType::Response(ref resp) => { + if let Some(resp) = resp.borrow().as_ref() { + resp.head().status + } else { + StatusCode::INTERNAL_SERVER_ERROR + } + } + } + } + + fn error_response(&self) -> Response { + match self.status { + InternalErrorType::Status(status) => { + let mut res = Response::new(status); + 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(Body::from(buf.into_inner())).into() + } + + InternalErrorType::Response(ref resp) => { + if let Some(resp) = resp.borrow_mut().take() { + resp.into() + } else { + Response::new(StatusCode::INTERNAL_SERVER_ERROR) + } + } + } + } +} + +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() + } + } + } +} + +error_helper!(ErrorBadRequest, BAD_REQUEST); +error_helper!(ErrorUnauthorized, UNAUTHORIZED); +error_helper!(ErrorPaymentRequired, PAYMENT_REQUIRED); +error_helper!(ErrorForbidden, FORBIDDEN); +error_helper!(ErrorNotFound, NOT_FOUND); +error_helper!(ErrorMethodNotAllowed, METHOD_NOT_ALLOWED); +error_helper!(ErrorNotAcceptable, NOT_ACCEPTABLE); +error_helper!( + ErrorProxyAuthenticationRequired, + PROXY_AUTHENTICATION_REQUIRED +); +error_helper!(ErrorRequestTimeout, REQUEST_TIMEOUT); +error_helper!(ErrorConflict, CONFLICT); +error_helper!(ErrorGone, GONE); +error_helper!(ErrorLengthRequired, LENGTH_REQUIRED); +error_helper!(ErrorPayloadTooLarge, PAYLOAD_TOO_LARGE); +error_helper!(ErrorUriTooLong, URI_TOO_LONG); +error_helper!(ErrorUnsupportedMediaType, UNSUPPORTED_MEDIA_TYPE); +error_helper!(ErrorRangeNotSatisfiable, RANGE_NOT_SATISFIABLE); +error_helper!(ErrorImATeapot, IM_A_TEAPOT); +error_helper!(ErrorMisdirectedRequest, MISDIRECTED_REQUEST); +error_helper!(ErrorUnprocessableEntity, UNPROCESSABLE_ENTITY); +error_helper!(ErrorLocked, LOCKED); +error_helper!(ErrorFailedDependency, FAILED_DEPENDENCY); +error_helper!(ErrorUpgradeRequired, UPGRADE_REQUIRED); +error_helper!(ErrorPreconditionFailed, PRECONDITION_FAILED); +error_helper!(ErrorPreconditionRequired, PRECONDITION_REQUIRED); +error_helper!(ErrorTooManyRequests, TOO_MANY_REQUESTS); +error_helper!( + ErrorRequestHeaderFieldsTooLarge, + REQUEST_HEADER_FIELDS_TOO_LARGE +); +error_helper!( + ErrorUnavailableForLegalReasons, + UNAVAILABLE_FOR_LEGAL_REASONS +); +error_helper!(ErrorExpectationFailed, EXPECTATION_FAILED); +error_helper!(ErrorInternalServerError, INTERNAL_SERVER_ERROR); +error_helper!(ErrorNotImplemented, NOT_IMPLEMENTED); +error_helper!(ErrorBadGateway, BAD_GATEWAY); +error_helper!(ErrorServiceUnavailable, SERVICE_UNAVAILABLE); +error_helper!(ErrorGatewayTimeout, GATEWAY_TIMEOUT); +error_helper!(ErrorHttpVersionNotSupported, HTTP_VERSION_NOT_SUPPORTED); +error_helper!(ErrorVariantAlsoNegotiates, VARIANT_ALSO_NEGOTIATES); +error_helper!(ErrorInsufficientStorage, INSUFFICIENT_STORAGE); +error_helper!(ErrorLoopDetected, LOOP_DETECTED); +error_helper!(ErrorNotExtended, NOT_EXTENDED); +error_helper!( + ErrorNetworkAuthenticationRequired, + NETWORK_AUTHENTICATION_REQUIRED +); + +#[cfg(test)] +mod tests { + use actix_http::{error::ParseError, Response}; + + use super::*; + + #[test] + fn test_internal_error() { + let err = InternalError::from_response(ParseError::Method, HttpResponse::Ok().finish()); + let resp: Response = err.error_response(); + assert_eq!(resp.status(), StatusCode::OK); + } + + #[test] + fn test_error_helpers() { + let res: Response = ErrorBadRequest("err").into(); + assert_eq!(res.status(), StatusCode::BAD_REQUEST); + + let res: Response = ErrorUnauthorized("err").into(); + assert_eq!(res.status(), StatusCode::UNAUTHORIZED); + + let res: Response = ErrorPaymentRequired("err").into(); + assert_eq!(res.status(), StatusCode::PAYMENT_REQUIRED); + + let res: Response = ErrorForbidden("err").into(); + assert_eq!(res.status(), StatusCode::FORBIDDEN); + + let res: Response = ErrorNotFound("err").into(); + assert_eq!(res.status(), StatusCode::NOT_FOUND); + + let res: Response = ErrorMethodNotAllowed("err").into(); + assert_eq!(res.status(), StatusCode::METHOD_NOT_ALLOWED); + + let res: Response = ErrorNotAcceptable("err").into(); + assert_eq!(res.status(), StatusCode::NOT_ACCEPTABLE); + + let res: Response = ErrorProxyAuthenticationRequired("err").into(); + assert_eq!(res.status(), StatusCode::PROXY_AUTHENTICATION_REQUIRED); + + let res: Response = ErrorRequestTimeout("err").into(); + assert_eq!(res.status(), StatusCode::REQUEST_TIMEOUT); + + let res: Response = ErrorConflict("err").into(); + assert_eq!(res.status(), StatusCode::CONFLICT); + + let res: Response = ErrorGone("err").into(); + assert_eq!(res.status(), StatusCode::GONE); + + let res: Response = ErrorLengthRequired("err").into(); + assert_eq!(res.status(), StatusCode::LENGTH_REQUIRED); + + let res: Response = ErrorPreconditionFailed("err").into(); + assert_eq!(res.status(), StatusCode::PRECONDITION_FAILED); + + let res: Response = ErrorPayloadTooLarge("err").into(); + assert_eq!(res.status(), StatusCode::PAYLOAD_TOO_LARGE); + + let res: Response = ErrorUriTooLong("err").into(); + assert_eq!(res.status(), StatusCode::URI_TOO_LONG); + + let res: Response = ErrorUnsupportedMediaType("err").into(); + assert_eq!(res.status(), StatusCode::UNSUPPORTED_MEDIA_TYPE); + + let res: Response = ErrorRangeNotSatisfiable("err").into(); + assert_eq!(res.status(), StatusCode::RANGE_NOT_SATISFIABLE); + + let res: Response = ErrorExpectationFailed("err").into(); + assert_eq!(res.status(), StatusCode::EXPECTATION_FAILED); + + let res: Response = ErrorImATeapot("err").into(); + assert_eq!(res.status(), StatusCode::IM_A_TEAPOT); + + let res: Response = ErrorMisdirectedRequest("err").into(); + assert_eq!(res.status(), StatusCode::MISDIRECTED_REQUEST); + + let res: Response = ErrorUnprocessableEntity("err").into(); + assert_eq!(res.status(), StatusCode::UNPROCESSABLE_ENTITY); + + let res: Response = ErrorLocked("err").into(); + assert_eq!(res.status(), StatusCode::LOCKED); + + let res: Response = ErrorFailedDependency("err").into(); + assert_eq!(res.status(), StatusCode::FAILED_DEPENDENCY); + + let res: Response = ErrorUpgradeRequired("err").into(); + assert_eq!(res.status(), StatusCode::UPGRADE_REQUIRED); + + let res: Response = ErrorPreconditionRequired("err").into(); + assert_eq!(res.status(), StatusCode::PRECONDITION_REQUIRED); + + let res: Response = ErrorTooManyRequests("err").into(); + assert_eq!(res.status(), StatusCode::TOO_MANY_REQUESTS); + + let res: Response = ErrorRequestHeaderFieldsTooLarge("err").into(); + assert_eq!(res.status(), StatusCode::REQUEST_HEADER_FIELDS_TOO_LARGE); + + let res: Response = ErrorUnavailableForLegalReasons("err").into(); + assert_eq!(res.status(), StatusCode::UNAVAILABLE_FOR_LEGAL_REASONS); + + let res: Response = ErrorInternalServerError("err").into(); + assert_eq!(res.status(), StatusCode::INTERNAL_SERVER_ERROR); + + let res: Response = ErrorNotImplemented("err").into(); + assert_eq!(res.status(), StatusCode::NOT_IMPLEMENTED); + + let res: Response = ErrorBadGateway("err").into(); + assert_eq!(res.status(), StatusCode::BAD_GATEWAY); + + let res: Response = ErrorServiceUnavailable("err").into(); + assert_eq!(res.status(), StatusCode::SERVICE_UNAVAILABLE); + + let res: Response = ErrorGatewayTimeout("err").into(); + assert_eq!(res.status(), StatusCode::GATEWAY_TIMEOUT); + + let res: Response = ErrorHttpVersionNotSupported("err").into(); + assert_eq!(res.status(), StatusCode::HTTP_VERSION_NOT_SUPPORTED); + + let res: Response = ErrorVariantAlsoNegotiates("err").into(); + assert_eq!(res.status(), StatusCode::VARIANT_ALSO_NEGOTIATES); + + let res: Response = ErrorInsufficientStorage("err").into(); + assert_eq!(res.status(), StatusCode::INSUFFICIENT_STORAGE); + + let res: Response = ErrorLoopDetected("err").into(); + assert_eq!(res.status(), StatusCode::LOOP_DETECTED); + + let res: Response = ErrorNotExtended("err").into(); + assert_eq!(res.status(), StatusCode::NOT_EXTENDED); + + let res: Response = ErrorNetworkAuthenticationRequired("err").into(); + assert_eq!(res.status(), StatusCode::NETWORK_AUTHENTICATION_REQUIRED); + } +} diff --git a/src/error.rs b/src/error/mod.rs similarity index 99% rename from src/error.rs rename to src/error/mod.rs index a5a245693..7be9f501b 100644 --- a/src/error.rs +++ b/src/error/mod.rs @@ -9,6 +9,10 @@ use url::ParseError as UrlParseError; use crate::http::StatusCode; +mod internal; + +pub use self::internal::*; + /// A convenience [`Result`](std::result::Result) for Actix Web operations. /// /// This type alias is generally used to avoid writing out `actix_http::Error` directly. diff --git a/src/request_data.rs b/src/request_data.rs index 60471cbf9..559d6ecbf 100644 --- a/src/request_data.rs +++ b/src/request_data.rs @@ -1,9 +1,9 @@ use std::{any::type_name, ops::Deref}; -use actix_http::error::{Error, ErrorInternalServerError}; +use actix_http::error::Error; use actix_utils::future::{err, ok, Ready}; -use crate::{dev::Payload, FromRequest, HttpRequest}; +use crate::{dev::Payload, error::ErrorInternalServerError, FromRequest, HttpRequest}; /// Request-local data extractor. /// diff --git a/src/responder.rs b/src/responder.rs index 2393d046b..8bf8d9ea0 100644 --- a/src/responder.rs +++ b/src/responder.rs @@ -2,12 +2,11 @@ use std::{borrow::Cow, fmt}; use actix_http::{ body::Body, - error::InternalError, http::{header::IntoHeaderPair, Error as HttpError, HeaderMap, StatusCode}, }; use bytes::{Bytes, BytesMut}; -use crate::{Error, HttpRequest, HttpResponse, HttpResponseBuilder}; +use crate::{error::InternalError, Error, HttpRequest, HttpResponse, HttpResponseBuilder}; /// Trait implemented by types that can be converted to an HTTP response. /// diff --git a/src/types/path.rs b/src/types/path.rs index 50e2cb510..59a107a7e 100644 --- a/src/types/path.rs +++ b/src/types/path.rs @@ -2,12 +2,16 @@ use std::{fmt, ops, sync::Arc}; -use actix_http::error::{Error, ErrorNotFound}; +use actix_http::error::Error; use actix_router::PathDeserializer; use actix_utils::future::{ready, Ready}; use serde::de; -use crate::{dev::Payload, error::PathError, FromRequest, HttpRequest}; +use crate::{ + dev::Payload, + error::{ErrorNotFound, PathError}, + FromRequest, HttpRequest, +}; /// Extract typed data from request path segments. /// diff --git a/src/types/payload.rs b/src/types/payload.rs index f88800855..d69e0a126 100644 --- a/src/types/payload.rs +++ b/src/types/payload.rs @@ -7,14 +7,17 @@ use std::{ task::{Context, Poll}, }; -use actix_http::error::{ErrorBadRequest, PayloadError}; +use actix_http::error::PayloadError; use actix_utils::future::{ready, Either, Ready}; use bytes::{Bytes, BytesMut}; use encoding_rs::{Encoding, UTF_8}; use futures_core::{ready, stream::Stream}; use mime::Mime; -use crate::{dev, http::header, web, Error, FromRequest, HttpMessage, HttpRequest}; +use crate::{ + dev, error::ErrorBadRequest, http::header, web, Error, FromRequest, HttpMessage, + HttpRequest, +}; /// Extract a request's raw payload stream. /// From b1de196509b12a9b263490ae008bb6568a29c765 Mon Sep 17 00:00:00 2001 From: Keita Nonaka Date: Sat, 15 May 2021 09:13:33 +0900 Subject: [PATCH 197/339] Fix clippy warnings (#2217) --- actix-files/src/files.rs | 6 +++--- actix-http/src/response.rs | 2 +- benches/server.rs | 2 +- tests/test_server.rs | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/actix-files/src/files.rs b/actix-files/src/files.rs index 25706a232..fc8fd2531 100644 --- a/actix-files/src/files.rs +++ b/actix-files/src/files.rs @@ -38,7 +38,7 @@ pub struct Files { mime_override: Option>, file_flags: named::Flags, use_guards: Option>, - guards: Vec>>, + guards: Vec>, hidden_files: bool, } @@ -199,7 +199,7 @@ impl Files { /// ); /// ``` pub fn guard(mut self, guard: G) -> Self { - self.guards.push(Box::new(Rc::new(guard))); + self.guards.push(Rc::new(guard)); self } @@ -276,7 +276,7 @@ impl HttpServiceFactory for Files { Some( guards .into_iter() - .map(|guard| -> Box { guard }) + .map(|guard| -> Box { Box::new(guard) }) .collect::>(), ) }; diff --git a/actix-http/src/response.rs b/actix-http/src/response.rs index 4f603956e..419f6b88e 100644 --- a/actix-http/src/response.rs +++ b/actix-http/src/response.rs @@ -84,7 +84,7 @@ impl Response { pub fn with_body(status: StatusCode, body: B) -> Response { Response { head: BoxedResponseHead::new(status), - body: body, + body, } } diff --git a/benches/server.rs b/benches/server.rs index c6695817f..139e24abd 100644 --- a/benches/server.rs +++ b/benches/server.rs @@ -1,4 +1,4 @@ -use actix_web::{test, web, App, HttpResponse}; +use actix_web::{web, App, HttpResponse}; use awc::Client; use criterion::{criterion_group, criterion_main, Criterion}; use futures_util::future::join_all; diff --git a/tests/test_server.rs b/tests/test_server.rs index 756c180fc..c341aa0ce 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -901,7 +901,7 @@ async fn test_normalize() { let srv = actix_test::start_with(actix_test::config().h1(), || { App::new() .wrap(NormalizePath::new(TrailingSlash::Trim)) - .service(web::resource("/one").route(web::to(|| HttpResponse::Ok()))) + .service(web::resource("/one").route(web::to(HttpResponse::Ok))) }); let response = srv.get("/one/").send().await.unwrap(); From 4598a7c0ccd594deb483c743a031eb3d4acad72e Mon Sep 17 00:00:00 2001 From: Yuki Okushi Date: Tue, 25 May 2021 00:09:38 +0900 Subject: [PATCH 198/339] Only run UI tests on MSRV (#2232) --- actix-web-codegen/tests/trybuild.rs | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/actix-web-codegen/tests/trybuild.rs b/actix-web-codegen/tests/trybuild.rs index afbe7b728..12e848cf3 100644 --- a/actix-web-codegen/tests/trybuild.rs +++ b/actix-web-codegen/tests/trybuild.rs @@ -1,3 +1,4 @@ +#[rustversion::stable(1.46)] // MSRV #[test] fn compile_macros() { let t = trybuild::TestCases::new(); @@ -12,11 +13,3 @@ fn compile_macros() { t.pass("tests/trybuild/docstring-ok.rs"); } - -// #[rustversion::not(nightly)] -// fn skip_on_nightly(t: &trybuild::TestCases) { -// -// } - -// #[rustversion::nightly] -// fn skip_on_nightly(_t: &trybuild::TestCases) {} From bb7d33c9d41acef3b8d57dafc167c4077875374c Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Tue, 25 May 2021 10:21:20 +0800 Subject: [PATCH 199/339] refactor h2 dispatcher to async/await.reduce duplicate code (#2211) --- actix-http/src/h2/dispatcher.rs | 520 ++++++++++++-------------------- 1 file changed, 195 insertions(+), 325 deletions(-) diff --git a/actix-http/src/h2/dispatcher.rs b/actix-http/src/h2/dispatcher.rs index 5be172aaf..baff20e51 100644 --- a/actix-http/src/h2/dispatcher.rs +++ b/actix-http/src/h2/dispatcher.rs @@ -1,20 +1,26 @@ -use std::task::{Context, Poll}; -use std::{cmp, future::Future, marker::PhantomData, net, pin::Pin, rc::Rc}; +use std::{ + cmp, + future::Future, + marker::PhantomData, + net, + pin::Pin, + rc::Rc, + task::{Context, Poll}, +}; use actix_codec::{AsyncRead, AsyncWrite}; use actix_service::Service; +use actix_utils::future::poll_fn; use bytes::{Bytes, BytesMut}; use futures_core::ready; -use h2::{ - server::{Connection, SendResponse}, - SendStream, -}; +use h2::server::{Connection, SendResponse}; use http::header::{HeaderValue, CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCODING}; use log::{error, trace}; +use pin_project_lite::pin_project; -use crate::body::{Body, BodySize, MessageBody}; +use crate::body::{BodySize, MessageBody}; use crate::config::ServiceConfig; -use crate::error::{DispatchError, Error}; +use crate::error::Error; use crate::message::ResponseHead; use crate::payload::Payload; use crate::request::Request; @@ -24,30 +30,19 @@ use crate::OnConnectData; const CHUNK_SIZE: usize = 16_384; -/// Dispatcher for HTTP/2 protocol. -#[pin_project::pin_project] -pub struct Dispatcher -where - T: AsyncRead + AsyncWrite + Unpin, - S: Service, - B: MessageBody, -{ - flow: Rc>, - connection: Connection, - on_connect_data: OnConnectData, - config: ServiceConfig, - peer_addr: Option, - _phantom: PhantomData, +pin_project! { + /// Dispatcher for HTTP/2 protocol. + pub struct Dispatcher { + flow: Rc>, + connection: Connection, + on_connect_data: OnConnectData, + config: ServiceConfig, + peer_addr: Option, + _phantom: PhantomData, + } } -impl Dispatcher -where - T: AsyncRead + AsyncWrite + Unpin, - S: Service, - S::Error: Into, - S::Response: Into>, - B: MessageBody, -{ +impl Dispatcher { pub(crate) fn new( flow: Rc>, connection: Connection, @@ -55,7 +50,7 @@ where config: ServiceConfig, peer_addr: Option, ) -> Self { - Dispatcher { + Self { flow, config, peer_addr, @@ -71,331 +66,206 @@ where T: AsyncRead + AsyncWrite + Unpin, S: Service, - S::Error: Into + 'static, + S::Error: Into, S::Future: 'static, - S::Response: Into> + 'static, + S::Response: Into>, - B: MessageBody + 'static, + B: MessageBody, B::Error: Into, { - type Output = Result<(), DispatchError>; + type Output = Result<(), crate::error::DispatchError>; #[inline] fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { let this = self.get_mut(); - loop { - match ready!(Pin::new(&mut this.connection).poll_accept(cx)) { - None => return Poll::Ready(Ok(())), + 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); - Some(Err(err)) => return Poll::Ready(Err(err.into())), + 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; - Some(Ok((req, res))) => { - 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); + // merge on_connect_ext data into request extensions + this.on_connect_data.merge_into(&mut req); - 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 fut = this.flow.service.call(req); + let config = this.config.clone(); - // merge on_connect_ext data into request extensions - this.on_connect_data.merge_into(&mut req); + // 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::from_error(err.into()); + handle_response(res, tx, config).await + } + }; - let svc = ServiceResponse { - state: ServiceResponseState::ServiceCall( - this.flow.service.call(req), - Some(res), - ), - config: this.config.clone(), - buffer: None, - _phantom: PhantomData, - }; + // 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) + } + } + } + }); + } - actix_rt::spawn(svc); + Poll::Ready(Ok(())) + } +} + +enum DispatchError { + SendResponse(h2::Error), + SendData(h2::Error), + ResponseBody(Error), +} + +async fn handle_response( + res: Response, + mut tx: SendResponse, + config: ServiceConfig, +) -> Result<(), DispatchError> +where + B: MessageBody, + B::Error: Into, +{ + let (res, body) = res.replace_body(()); + + // prepare response. + let mut size = body.size(); + let res = prepare_response(config, res.head(), &mut size); + let eof = size.is_eof(); + + // send response head and return on eof. + let mut stream = tx + .send_response(res, eof) + .map_err(DispatchError::SendResponse)?; + + if eof { + return Ok(()); + } + + // 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 { + // reserve enough space and wait for stream ready. + stream.reserve_capacity(cmp::min(chunk.len(), 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)?; + + let len = chunk.len(); + let bytes = chunk.split_to(cmp::min(cap, len)); + + stream + .send_data(bytes, false) + .map_err(DispatchError::SendData)?; + + // Current chuck completely sent. break send loop and poll next one. + if chunk.is_empty() { + break 'send; + } } } } } + + // response body streaming finished. send end of stream and return. + stream + .send_data(Bytes::new(), true) + .map_err(DispatchError::SendData)?; + + Ok(()) } -#[pin_project::pin_project] -struct ServiceResponse { - #[pin] - state: ServiceResponseState, +fn prepare_response( config: ServiceConfig, - buffer: Option, - _phantom: PhantomData<(I, E)>, -} + head: &ResponseHead, + size: &mut BodySize, +) -> http::Response<()> { + let mut has_date = false; + let mut skip_len = size != &BodySize::Stream; -#[pin_project::pin_project(project = ServiceResponseStateProj)] -enum ServiceResponseState { - ServiceCall(#[pin] F, Option>), - SendPayload(SendStream, #[pin] B), - SendErrorPayload(SendStream, #[pin] Body), -} + let mut res = http::Response::new(()); + *res.status_mut() = head.status; + *res.version_mut() = http::Version::HTTP_2; -impl ServiceResponse -where - F: Future>, - E: Into, - I: Into>, + // Content length + match head.status { + http::StatusCode::NO_CONTENT + | http::StatusCode::CONTINUE + | http::StatusCode::PROCESSING => *size = BodySize::None, + http::StatusCode::SWITCHING_PROTOCOLS => { + skip_len = true; + *size = BodySize::Stream; + } + _ => {} + } - B: MessageBody, - B::Error: Into, -{ - fn prepare_response( - &self, - head: &ResponseHead, - size: &mut BodySize, - ) -> http::Response<()> { - let mut has_date = false; - let mut skip_len = size != &BodySize::Stream; + let _ = match size { + BodySize::None | BodySize::Stream => None, + BodySize::Empty => res + .headers_mut() + .insert(CONTENT_LENGTH, HeaderValue::from_static("0")), + BodySize::Sized(len) => { + let mut buf = itoa::Buffer::new(); - let mut res = http::Response::new(()); - *res.status_mut() = head.status; - *res.version_mut() = http::Version::HTTP_2; + res.headers_mut().insert( + CONTENT_LENGTH, + HeaderValue::from_str(buf.format(*len)).unwrap(), + ) + } + }; - // Content length - match head.status { - http::StatusCode::NO_CONTENT - | http::StatusCode::CONTINUE - | http::StatusCode::PROCESSING => *size = BodySize::None, - http::StatusCode::SWITCHING_PROTOCOLS => { - skip_len = true; - *size = BodySize::Stream; - } + // copy headers + 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 + // omit HTTP/1.x only headers + CONNECTION | TRANSFER_ENCODING => continue, + CONTENT_LENGTH if skip_len => continue, + DATE => has_date = true, _ => {} } - let _ = match size { - BodySize::None | BodySize::Stream => None, - BodySize::Empty => res - .headers_mut() - .insert(CONTENT_LENGTH, HeaderValue::from_static("0")), - BodySize::Sized(len) => { - let mut buf = itoa::Buffer::new(); - - res.headers_mut().insert( - CONTENT_LENGTH, - HeaderValue::from_str(buf.format(*len)).unwrap(), - ) - } - }; - - // copy headers - 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 - // omit HTTP/1.x only headers - CONNECTION | TRANSFER_ENCODING => continue, - CONTENT_LENGTH if skip_len => continue, - DATE => has_date = true, - _ => {} - } - - res.headers_mut().append(key, value.clone()); - } - - // set date header - if !has_date { - let mut bytes = BytesMut::with_capacity(29); - self.config.set_date_header(&mut bytes); - res.headers_mut().insert( - DATE, - // SAFETY: serialized date-times are known ASCII strings - unsafe { HeaderValue::from_maybe_shared_unchecked(bytes.freeze()) }, - ); - } - - res + res.headers_mut().append(key, value.clone()); } -} -impl Future for ServiceResponse -where - F: Future>, - E: Into, - I: Into>, - - B: MessageBody, - B::Error: Into, -{ - type Output = (); - - fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - let mut this = self.as_mut().project(); - - match this.state.project() { - ServiceResponseStateProj::ServiceCall(call, send) => { - match ready!(call.poll(cx)) { - Ok(res) => { - let (res, body) = res.into().replace_body(()); - - let mut send = send.take().unwrap(); - let mut size = body.size(); - let h2_res = - self.as_mut().prepare_response(res.head(), &mut size); - this = self.as_mut().project(); - - let stream = match send.send_response(h2_res, size.is_eof()) { - Err(e) => { - trace!("Error sending HTTP/2 response: {:?}", e); - return Poll::Ready(()); - } - Ok(stream) => stream, - }; - - if size.is_eof() { - Poll::Ready(()) - } else { - this.state - .set(ServiceResponseState::SendPayload(stream, body)); - self.poll(cx) - } - } - - Err(err) => { - let res = Response::from_error(err.into()); - let (res, body) = res.replace_body(()); - - let mut send = send.take().unwrap(); - let mut size = body.size(); - let h2_res = - self.as_mut().prepare_response(res.head(), &mut size); - this = self.as_mut().project(); - - let stream = match send.send_response(h2_res, size.is_eof()) { - Err(e) => { - trace!("Error sending HTTP/2 response: {:?}", e); - return Poll::Ready(()); - } - Ok(stream) => stream, - }; - - if size.is_eof() { - Poll::Ready(()) - } else { - this.state.set(ServiceResponseState::SendErrorPayload( - stream, body, - )); - self.poll(cx) - } - } - } - } - - ServiceResponseStateProj::SendPayload(ref mut stream, ref mut body) => { - loop { - match this.buffer { - Some(ref mut buffer) => match ready!(stream.poll_capacity(cx)) { - None => return Poll::Ready(()), - - Some(Ok(cap)) => { - let len = buffer.len(); - let bytes = buffer.split_to(cmp::min(cap, len)); - - if let Err(e) = stream.send_data(bytes, false) { - warn!("{:?}", e); - return Poll::Ready(()); - } else if !buffer.is_empty() { - let cap = cmp::min(buffer.len(), CHUNK_SIZE); - stream.reserve_capacity(cap); - } else { - this.buffer.take(); - } - } - - Some(Err(e)) => { - warn!("{:?}", e); - return Poll::Ready(()); - } - }, - - None => match ready!(body.as_mut().poll_next(cx)) { - None => { - if let Err(e) = stream.send_data(Bytes::new(), true) { - warn!("{:?}", e); - } - return Poll::Ready(()); - } - - Some(Ok(chunk)) => { - stream - .reserve_capacity(cmp::min(chunk.len(), CHUNK_SIZE)); - *this.buffer = Some(chunk); - } - - Some(Err(err)) => { - error!( - "Response payload stream error: {:?}", - err.into() - ); - - return Poll::Ready(()); - } - }, - } - } - } - - ServiceResponseStateProj::SendErrorPayload(ref mut stream, ref mut body) => { - // TODO: de-dupe impl with SendPayload - - loop { - match this.buffer { - Some(ref mut buffer) => match ready!(stream.poll_capacity(cx)) { - None => return Poll::Ready(()), - - Some(Ok(cap)) => { - let len = buffer.len(); - let bytes = buffer.split_to(cmp::min(cap, len)); - - if let Err(e) = stream.send_data(bytes, false) { - warn!("{:?}", e); - return Poll::Ready(()); - } else if !buffer.is_empty() { - let cap = cmp::min(buffer.len(), CHUNK_SIZE); - stream.reserve_capacity(cap); - } else { - this.buffer.take(); - } - } - - Some(Err(e)) => { - warn!("{:?}", e); - return Poll::Ready(()); - } - }, - - None => match ready!(body.as_mut().poll_next(cx)) { - None => { - if let Err(e) = stream.send_data(Bytes::new(), true) { - warn!("{:?}", e); - } - return Poll::Ready(()); - } - - Some(Ok(chunk)) => { - stream - .reserve_capacity(cmp::min(chunk.len(), CHUNK_SIZE)); - *this.buffer = Some(chunk); - } - - Some(Err(err)) => { - error!("Response payload stream error: {:?}", err); - - return Poll::Ready(()); - } - }, - } - } - } - } + // set date header + if !has_date { + let mut bytes = BytesMut::with_capacity(29); + config.set_date_header(&mut bytes); + res.headers_mut().insert( + DATE, + // SAFETY: serialized date-times are known ASCII strings + unsafe { HeaderValue::from_maybe_shared_unchecked(bytes.freeze()) }, + ); } + + res } From 3847429d00e9b256dc1d1fc2cfea414e367c3410 Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Wed, 26 May 2021 12:41:48 +0800 Subject: [PATCH 200/339] Response::from_error take impl Into (#2214) --- actix-http/src/h1/dispatcher.rs | 8 ++++---- actix-http/src/response.rs | 3 ++- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/actix-http/src/h1/dispatcher.rs b/actix-http/src/h1/dispatcher.rs index 574f0b2a9..c81d0b3bc 100644 --- a/actix-http/src/h1/dispatcher.rs +++ b/actix-http/src/h1/dispatcher.rs @@ -399,7 +399,7 @@ where // send service call error as response Poll::Ready(Err(err)) => { - let res = Response::from_error(err.into()); + let res = Response::from_error(err); let (res, body) = res.replace_body(()); self.as_mut().send_error_response(res, body)?; } @@ -496,7 +496,7 @@ where // send expect error as response Poll::Ready(Err(err)) => { - let res = Response::from_error(err.into()); + let res = Response::from_error(err); let (res, body) = res.replace_body(()); self.as_mut().send_error_response(res, body)?; } @@ -546,7 +546,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.into()); + let res = Response::from_error(err); let (res, body) = res.replace_body(()); return self.send_error_response(res, body); } @@ -566,7 +566,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.into()); + let res = Response::from_error(err); let (res, body) = res.replace_body(()); self.send_error_response(res, body) } diff --git a/actix-http/src/response.rs b/actix-http/src/response.rs index 419f6b88e..bcfa65732 100644 --- a/actix-http/src/response.rs +++ b/actix-http/src/response.rs @@ -69,7 +69,8 @@ impl Response { /// Constructs a new response from an error. #[inline] - pub fn from_error(error: Error) -> Response { + 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); From e5b713b04a5bb79d5857eebc56489ac761e5eb59 Mon Sep 17 00:00:00 2001 From: Ali MJ Al-Nasrawy Date: Wed, 26 May 2021 12:42:29 +0300 Subject: [PATCH 201/339] files: Fix `redirect_to_slash_directory()` when used with `show_files_listing()` (#2225) --- actix-files/CHANGES.md | 2 ++ actix-files/src/lib.rs | 15 ++++++++++++++- actix-files/src/service.rs | 23 +++++++++++++---------- 3 files changed, 29 insertions(+), 11 deletions(-) diff --git a/actix-files/CHANGES.md b/actix-files/CHANGES.md index 0586a2fd3..12b70c135 100644 --- a/actix-files/CHANGES.md +++ b/actix-files/CHANGES.md @@ -3,9 +3,11 @@ ## Unreleased - 2021-xx-xx * `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] [#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 ## 0.6.0-beta.4 - 2021-04-02 diff --git a/actix-files/src/lib.rs b/actix-files/src/lib.rs index 0384ff2b0..aa5960b5b 100644 --- a/actix-files/src/lib.rs +++ b/actix-files/src/lib.rs @@ -632,7 +632,7 @@ mod tests { #[actix_rt::test] async fn test_redirect_to_slash_directory() { - // should not redirect if no index + // should not redirect if no index and files listing is disabled let srv = test::init_service( App::new().service(Files::new("/", ".").redirect_to_slash_directory()), ) @@ -654,6 +654,19 @@ mod tests { let resp = test::call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::FOUND); + // should redirect if files listing is enabled + let srv = test::init_service( + App::new().service( + Files::new("/", ".") + .show_files_listing() + .redirect_to_slash_directory(), + ), + ) + .await; + let req = TestRequest::with_uri("/tests").to_request(); + let resp = test::call_service(&srv, req).await; + assert_eq!(resp.status(), StatusCode::FOUND); + // should not redirect if the path is wrong let req = TestRequest::with_uri("/not_existing").to_request(); let resp = test::call_service(&srv, req).await; diff --git a/actix-files/src/service.rs b/actix-files/src/service.rs index 31e1434bd..831115dc6 100644 --- a/actix-files/src/service.rs +++ b/actix-files/src/service.rs @@ -89,17 +89,20 @@ impl Service for FilesService { } 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(), + ))); + } + if let Some(ref redir_index) = self.index { - if self.redirect_to_slash && !req.path().ends_with('/') { - let redirect_to = format!("{}/", req.path()); - - return Box::pin(ok(req.into_response( - HttpResponse::Found() - .insert_header((header::LOCATION, redirect_to)) - .finish(), - ))); - } - let path = path.join(redir_index); match NamedFile::open(path) { From 136dac135245daec70b6b68a116dca9132b3680f Mon Sep 17 00:00:00 2001 From: James Wright Date: Thu, 3 Jun 2021 03:28:09 +0100 Subject: [PATCH 202/339] 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 203/339] 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 204/339] 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 205/339] 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 206/339] 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 207/339] 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 208/339] 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 209/339] 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 210/339] 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 211/339] 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 212/339] 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 213/339] 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 214/339] 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 215/339] 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 216/339] 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 217/339] 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 218/339] 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 219/339] 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 220/339] 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 221/339] 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 222/339] 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 223/339] 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 224/339] 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 225/339] 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 226/339] 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 227/339] `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 228/339] 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 229/339] 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 230/339] 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 231/339] 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 232/339] 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 233/339] 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 234/339] 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 235/339] 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 236/339] 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 237/339] 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 238/339] 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 239/339] 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 240/339] 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 241/339] 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 242/339] 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 243/339] 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 244/339] 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 245/339] 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 246/339] 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 247/339] 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 248/339] 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 249/339] 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 250/339] 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 251/339] 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 252/339] [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 253/339] 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 254/339] 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 255/339] 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 256/339] 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 257/339] "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 258/339] 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 259/339] 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 260/339] 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 261/339] 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 262/339] 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 263/339] 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 264/339] 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 265/339] 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 266/339] 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 267/339] 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 268/339] 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 269/339] 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 270/339] 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 271/339] 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 272/339] 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 273/339] 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 274/339] 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 275/339] 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 276/339] 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 277/339] 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 278/339] 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 279/339] 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 280/339] 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 281/339] 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 282/339] 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 283/339] 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 284/339] 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 285/339] 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 286/339] 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 287/339] 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 288/339] 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 289/339] 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 290/339] 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 291/339] 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 292/339] 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 293/339] 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 294/339] 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 295/339] 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 296/339] 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 297/339] 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 298/339] 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 299/339] 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 300/339] 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 301/339] 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 302/339] 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 303/339] 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 304/339] 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 305/339] 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 306/339] 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 307/339] 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 308/339] 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 309/339] 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 310/339] 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 311/339] 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 312/339] 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 313/339] 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 314/339] 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 315/339] 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 316/339] 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 317/339] 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 318/339] 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 319/339] 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 320/339] 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 321/339] 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 322/339] 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 323/339] 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 324/339] 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 325/339] 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 326/339] 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 327/339] 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 328/339] 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 329/339] 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 330/339] 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 331/339] 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 332/339] 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 333/339] 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 334/339] 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 335/339] 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 336/339] 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 337/339] 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 338/339] 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 339/339] 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,