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 001/428] 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 002/428] 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 003/428] 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 004/428] 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 005/428] 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 006/428] 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 007/428] 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 008/428] 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 009/428] 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 010/428] 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 011/428] 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 012/428] 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 013/428] 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 014/428] 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 015/428] 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 016/428] 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 017/428] 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 018/428] 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 019/428] 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 020/428] 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 021/428] 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 022/428] 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 023/428] 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 024/428] 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 025/428] 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 026/428] 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 027/428] 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 028/428] 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 029/428] 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 030/428] 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 031/428] 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 032/428] 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 033/428] 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 034/428] 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 035/428] 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 036/428] 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 037/428] 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 038/428] 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 039/428] 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 040/428] 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 041/428] 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 042/428] 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 043/428] 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 044/428] 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 045/428] 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 046/428] 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 047/428] 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 048/428] 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 049/428] 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 050/428] 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 051/428] 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 052/428] 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.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 097/428] 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 098/428] 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 099/428] 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 100/428] 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 101/428] 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 102/428] 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 103/428] 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 104/428] 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 105/428] 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 106/428] 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 107/428] 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 108/428] 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 109/428] 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 110/428] 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 111/428] 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 112/428] 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 113/428] 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 114/428] 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 115/428] 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 116/428] 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 117/428] 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 118/428] 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 119/428] 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 120/428] 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 121/428] 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 122/428] 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 123/428] 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 124/428] 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 125/428] 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 126/428] 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 127/428] 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 128/428] 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 129/428] 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 130/428] 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 131/428] 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 132/428] 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 133/428] 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 134/428] 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 135/428] 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 136/428] 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 137/428] 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 138/428] 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 139/428] 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 140/428] 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 141/428] 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 142/428] 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 143/428] 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 144/428] 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 145/428] 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 146/428] 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 147/428] 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 148/428] 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 149/428] 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 150/428] 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 151/428] 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 152/428] 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 153/428] 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 154/428] 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 155/428] 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 156/428] 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 157/428] 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 158/428] 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 159/428] 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 160/428] 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 161/428] 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 162/428] 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 163/428] 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 164/428] 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 165/428] 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 166/428] 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 167/428] 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 168/428] 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 169/428] 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 170/428] 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 171/428] 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 172/428] 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 173/428] 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 174/428] 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 175/428] 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 176/428] 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 177/428] 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 178/428] 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 179/428] 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 180/428] 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 181/428] 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 182/428] 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 183/428] 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 184/428] 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 185/428] 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 186/428] 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 187/428] `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 188/428] 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 189/428] 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 190/428] 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 191/428] 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 192/428] 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 193/428] 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 194/428] 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 195/428] 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 196/428] 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 197/428] 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 198/428] 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 199/428] 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 200/428] 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 201/428] 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 202/428] 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 203/428] 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 204/428] 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 205/428] 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 206/428] 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 207/428] 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 208/428] 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 209/428] 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 210/428] 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 211/428] 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 212/428] [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 213/428] 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 214/428] 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 215/428] 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 216/428] 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 217/428] "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 218/428] 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 219/428] 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 220/428] 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 221/428] 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 222/428] 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 223/428] 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 224/428] 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 225/428] 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 226/428] 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 227/428] 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 228/428] 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 229/428] 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 230/428] 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 231/428] 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 232/428] 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 233/428] 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 234/428] 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 235/428] 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 236/428] 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 237/428] 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 238/428] 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 239/428] 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 240/428] 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 241/428] 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 242/428] 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 243/428] 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 244/428] 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 245/428] 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 246/428] 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 247/428] 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 248/428] 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 249/428] 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 250/428] 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 251/428] 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 252/428] 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 253/428] 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 254/428] 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 255/428] 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 256/428] 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 257/428] 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 258/428] 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 259/428] 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 260/428] 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 261/428] 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 262/428] 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 263/428] 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 264/428] 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 265/428] 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 266/428] 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 267/428] 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 268/428] 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 269/428] 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 270/428] 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 271/428] 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 272/428] 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 273/428] 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 274/428] 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 275/428] 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 276/428] 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 277/428] 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 278/428] 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 279/428] 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 280/428] 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 281/428] 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 282/428] 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 283/428] 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 284/428] 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 285/428] 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 286/428] 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 287/428] 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 288/428] 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 289/428] 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 290/428] 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 291/428] 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 292/428] 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 293/428] 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 294/428] 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 295/428] 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 296/428] 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 297/428] 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 298/428] 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 299/428] undeprecate `App::data_factory` (#2484) --- CHANGES.md | 2 ++ src/app.rs | 4 ---- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 2dc45c3ed..2b5a71001 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -10,6 +10,7 @@ ### Changed * Rename `Accept::{mime_precedence => ranked}`. [#2480] * Rename `Accept::{mime_preference => preference}`. [#2480] +* Un-deprecate `App::data_factory`. [#2484] ### Fixed * Accept wildcard `*` items in `AcceptLanguage`. [#2480] @@ -19,6 +20,7 @@ [#2468]: https://github.com/actix/actix-web/pull/2468 [#2480]: https://github.com/actix/actix-web/pull/2480 [#2482]: https://github.com/actix/actix-web/pull/2482 +[#2484]: https://github.com/actix/actix-web/pull/2484 ## 4.0.0-beta.13 - 2021-11-30 diff --git a/src/app.rs b/src/app.rs index 36063ec79..efc108cb9 100644 --- a/src/app.rs +++ b/src/app.rs @@ -140,10 +140,6 @@ where /// Add application data factory. This function is similar to `.data()` but it accepts a /// "data factory". Data values are constructed asynchronously during application /// initialization, before the server starts accepting requests. - #[deprecated( - since = "4.0.0", - note = "Construct data value before starting server and use `.app_data(Data::new(val))` instead." - )] pub fn data_factory(mut self, data: F) -> Self where F: Fn() -> Out + 'static, From 4c9ca7196dab4c745024be19c600630a76388ee4 Mon Sep 17 00:00:00 2001 From: Mohammed Sazid Al Rashid Date: Sun, 5 Dec 2021 04:32:44 +0600 Subject: [PATCH 300/428] Add `WsResponseBuilder` to build web socket session response (#1920) Co-authored-by: Rob Ede --- actix-http/CHANGES.md | 4 + actix-http/src/ws/codec.rs | 12 +- actix-web-actors/CHANGES.md | 4 + actix-web-actors/src/ws.rs | 256 ++++++++++++++++++++++++++---- actix-web-actors/tests/test_ws.rs | 201 +++++++++++++++-------- 5 files changed, 379 insertions(+), 98 deletions(-) diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 23c15296a..773f1ff39 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -9,6 +9,8 @@ * `body::None` struct. [#2468] * Impl `MessageBody` for `bytestring::ByteString`. [#2468] * `impl Clone for ws::HandshakeError`. [#2468] +* `#[must_use]` for `ws::Codec` to prevent subtle bugs. [#1920] +* `impl Default ` for `ws::Codec`. [#1920] ### Changed * Rename `body::BoxBody::{from_body => new}`. [#2468] @@ -24,9 +26,11 @@ * `impl Future` for `ResponseBuilder`. [#2468] * Remove unnecessary `MessageBody` bound on types passed to `body::AnyBody::new`. [#2468] * Move `body::AnyBody` to `awc`. Replaced with `EitherBody` and `BoxBody`. [#2468] +* `impl Copy` for `ws::Codec`. [#1920] [#2483]: https://github.com/actix/actix-web/pull/2483 [#2468]: https://github.com/actix/actix-web/pull/2468 +[#1920]: https://github.com/actix/actix-web/pull/1920 ## 3.0.0-beta.14 - 2021-11-30 diff --git a/actix-http/src/ws/codec.rs b/actix-http/src/ws/codec.rs index 8655216fa..d80613e5f 100644 --- a/actix-http/src/ws/codec.rs +++ b/actix-http/src/ws/codec.rs @@ -63,8 +63,8 @@ pub enum Item { Last(Bytes), } -#[derive(Debug, Copy, Clone)] /// WebSocket protocol codec. +#[derive(Debug, Clone)] pub struct Codec { flags: Flags, max_size: usize, @@ -89,7 +89,8 @@ impl Codec { /// Set max frame size. /// - /// By default max size is set to 64kB. + /// By default max size is set to 64KiB. + #[must_use = "This returns the a new Codec, without modifying the original."] pub fn max_size(mut self, size: usize) -> Self { self.max_size = size; self @@ -98,12 +99,19 @@ impl Codec { /// Set decoder to client mode. /// /// By default decoder works in server mode. + #[must_use = "This returns the a new Codec, without modifying the original."] pub fn client_mode(mut self) -> Self { self.flags.remove(Flags::SERVER); self } } +impl Default for Codec { + fn default() -> Self { + Self::new() + } +} + impl Encoder for Codec { type Error = ProtocolError; diff --git a/actix-web-actors/CHANGES.md b/actix-web-actors/CHANGES.md index e3693f0f6..898098ed8 100644 --- a/actix-web-actors/CHANGES.md +++ b/actix-web-actors/CHANGES.md @@ -1,8 +1,12 @@ # Changes ## Unreleased - 2021-xx-xx +* Add `ws:WsResponseBuilder` for building WebSocket session response. [#1920] +* Deprecate `ws::{start_with_addr, start_with_protocols}`. [#1920] * Minimum supported Rust version (MSRV) is now 1.52. +[#1920]: https://github.com/actix/actix-web/pull/1920 + ## 4.0.0-beta.7 - 2021-09-09 * Minimum supported Rust version (MSRV) is now 1.51. diff --git a/actix-web-actors/src/ws.rs b/actix-web-actors/src/ws.rs index b6323c2ed..c41268b01 100644 --- a/actix-web-actors/src/ws.rs +++ b/actix-web-actors/src/ws.rs @@ -1,20 +1,24 @@ //! Websocket integration. -use std::future::Future; -use std::io; -use std::pin::Pin; -use std::task::{Context, Poll}; -use std::{collections::VecDeque, convert::TryFrom}; - -use actix::dev::{ - AsyncContextParts, ContextFut, ContextParts, Envelope, Mailbox, StreamHandler, ToEnvelope, +use std::{ + collections::VecDeque, + convert::TryFrom, + future::Future, + io, mem, + pin::Pin, + task::{Context, Poll}, }; -use actix::fut::ActorFuture; + use actix::{ + dev::{ + AsyncContextParts, ContextFut, ContextParts, Envelope, Mailbox, StreamHandler, + ToEnvelope, + }, + fut::ActorFuture, Actor, ActorContext, ActorState, Addr, AsyncContext, Handler, Message as ActixMessage, SpawnHandle, }; -use actix_codec::{Decoder, Encoder}; +use actix_codec::{Decoder as _, Encoder as _}; pub use actix_http::ws::{ CloseCode, CloseReason, Frame, HandshakeError, Message, ProtocolError, }; @@ -31,9 +35,188 @@ use bytes::{Bytes, BytesMut}; use bytestring::ByteString; use futures_core::Stream; use pin_project_lite::pin_project; -use tokio::sync::oneshot::Sender; +use tokio::sync::oneshot; + +/// Builder for Websocket session response. +/// +/// # Examples +/// +/// Create a Websocket session response with default configuration. +/// ```ignore +/// WsResponseBuilder::new(WsActor, &req, stream).start() +/// ``` +/// +/// Create a Websocket session with a specific max frame size, [`Codec`], and protocols. +/// ```ignore +/// const MAX_FRAME_SIZE: usize = 16_384; // 16KiB +/// +/// ws::WsResponseBuilder::new(WsActor, &req, stream) +/// .codec(Codec::new()) +/// .protocols(&["A", "B"]) +/// .frame_size(MAX_FRAME_SIZE) +/// .start() +/// ``` +pub struct WsResponseBuilder<'a, A, T> +where + A: Actor> + StreamHandler>, + T: Stream> + 'static, +{ + actor: A, + req: &'a HttpRequest, + stream: T, + codec: Option, + protocols: Option<&'a [&'a str]>, + frame_size: Option, +} + +impl<'a, A, T> WsResponseBuilder<'a, A, T> +where + A: Actor> + StreamHandler>, + T: Stream> + 'static, +{ + /// Construct a new `WsResponseBuilder` with actor, request, and payload stream. + /// + /// For usage example, see docs on [`WsResponseBuilder`] struct. + pub fn new(actor: A, req: &'a HttpRequest, stream: T) -> Self { + WsResponseBuilder { + actor, + req, + stream, + codec: None, + protocols: None, + frame_size: None, + } + } + + /// Set the protocols for the session. + pub fn protocols(mut self, protocols: &'a [&'a str]) -> Self { + self.protocols = Some(protocols); + self + } + + /// Set the max frame size for each message (in bytes). + /// + /// **Note**: This will override any given [`Codec`]'s max frame size. + pub fn frame_size(mut self, frame_size: usize) -> Self { + self.frame_size = Some(frame_size); + self + } + + /// Set the [`Codec`] for the session. If [`Self::frame_size`] is also set, the given + /// [`Codec`]'s max frame size will be overridden. + pub fn codec(mut self, codec: Codec) -> Self { + self.codec = Some(codec); + self + } + + fn handshake_resp(&self) -> Result { + match self.protocols { + Some(protocols) => handshake_with_protocols(self.req, protocols), + None => handshake(self.req), + } + } + + fn set_frame_size(&mut self) { + if let Some(frame_size) = self.frame_size { + match &mut self.codec { + Some(codec) => { + // modify existing codec's max frame size + let orig_codec = mem::take(codec); + *codec = orig_codec.max_size(frame_size); + } + + None => { + // create a new codec with the given size + self.codec = Some(Codec::new().max_size(frame_size)); + } + } + } + } + + /// Create a new Websocket context from an actor, request stream, and codec. + /// + /// Returns a pair, where the first item is an addr for the created actor, and the second item + /// is a stream intended to be set as part of the response + /// via [`HttpResponseBuilder::streaming()`]. + fn create_with_codec_addr( + actor: A, + stream: S, + codec: Codec, + ) -> (Addr
, impl Stream>) + where + A: StreamHandler>, + S: Stream> + 'static, + { + let mb = Mailbox::default(); + let mut ctx = WebsocketContext { + inner: ContextParts::new(mb.sender_producer()), + messages: VecDeque::new(), + }; + ctx.add_stream(WsStream::new(stream, codec.clone())); + + let addr = ctx.address(); + + (addr, WebsocketContextFut::new(ctx, actor, mb, codec)) + } + + /// Perform WebSocket handshake and start actor. + /// + /// `req` is an [`HttpRequest`] that should be requesting a websocket protocol change. + /// `stream` should be a [`Bytes`] stream (such as `actix_web::web::Payload`) that contains a + /// stream of the body request. + /// + /// If there is a problem with the handshake, an error is returned. + /// + /// If successful, consume the [`WsResponseBuilder`] and return a [`HttpResponse`] wrapped in + /// a [`Result`]. + pub fn start(mut self) -> Result { + let mut res = self.handshake_resp()?; + self.set_frame_size(); + + match self.codec { + Some(codec) => { + let out_stream = WebsocketContext::with_codec(self.actor, self.stream, codec); + Ok(res.streaming(out_stream)) + } + None => { + let out_stream = WebsocketContext::create(self.actor, self.stream); + Ok(res.streaming(out_stream)) + } + } + } + + /// Perform WebSocket handshake and start actor. + /// + /// `req` is an [`HttpRequest`] that should be requesting a websocket protocol change. + /// `stream` should be a [`Bytes`] stream (such as `actix_web::web::Payload`) that contains a + /// stream of the body request. + /// + /// If there is a problem with the handshake, an error is returned. + /// + /// If successful, returns a pair where the first item is an address for the created actor and + /// the second item is the [`HttpResponse`] that should be returned from the websocket request. + pub fn start_with_addr(mut self) -> Result<(Addr, HttpResponse), Error> { + let mut res = self.handshake_resp()?; + self.set_frame_size(); + + match self.codec { + Some(codec) => { + let (addr, out_stream) = + Self::create_with_codec_addr(self.actor, self.stream, codec); + Ok((addr, res.streaming(out_stream))) + } + None => { + let (addr, out_stream) = + WebsocketContext::create_with_addr(self.actor, self.stream); + Ok((addr, res.streaming(out_stream))) + } + } + } +} /// Perform WebSocket handshake and start actor. +/// +/// To customize options, see [`WsResponseBuilder`]. pub fn start(actor: A, req: &HttpRequest, stream: T) -> Result where A: Actor> + StreamHandler>, @@ -45,15 +228,15 @@ where /// Perform WebSocket handshake and start actor. /// -/// `req` is an HTTP Request that should be requesting a websocket protocol -/// change. `stream` should be a `Bytes` stream (such as -/// `actix_web::web::Payload`) that contains a stream of the body request. +/// `req` is an HTTP Request that should be requesting a websocket protocol change. `stream` should +/// be a `Bytes` stream (such as `actix_web::web::Payload`) that contains a stream of the +/// body request. /// /// If there is a problem with the handshake, an error is returned. /// -/// If successful, returns a pair where the first item is an address for the -/// created actor and the second item is the response that should be returned -/// from the WebSocket request. +/// If successful, returns a pair where the first item is an address for the created actor and the +/// second item is the response that should be returned from the WebSocket request. +#[deprecated(since = "4.0.0", note = "Prefer `WsResponseBuilder::start_with_addr`.")] pub fn start_with_addr( actor: A, req: &HttpRequest, @@ -71,6 +254,10 @@ where /// Do WebSocket handshake and start ws actor. /// /// `protocols` is a sequence of known protocols. +#[deprecated( + since = "4.0.0", + note = "Prefer `WsResponseBuilder` for setting protocols." +)] pub fn start_with_protocols( actor: A, protocols: &[&str], @@ -87,20 +274,19 @@ where /// Prepare WebSocket handshake response. /// -/// This function returns handshake `HttpResponse`, ready to send to peer. -/// It does not perform any IO. +/// This function returns handshake `HttpResponse`, ready to send to peer. It does not perform +/// any IO. pub fn handshake(req: &HttpRequest) -> Result { handshake_with_protocols(req, &[]) } /// Prepare WebSocket handshake response. /// -/// This function returns handshake `HttpResponse`, ready to send to peer. -/// It does not perform any IO. +/// This function returns handshake `HttpResponse`, ready to send to peer. It does not perform +/// any IO. /// -/// `protocols` is a sequence of known protocols. On successful handshake, -/// the returned response headers contain the first protocol in this list -/// which the server also knows. +/// `protocols` is a sequence of known protocols. On successful handshake, the returned response +/// headers contain the first protocol in this list which the server also knows. pub fn handshake_with_protocols( req: &HttpRequest, protocols: &[&str], @@ -247,8 +433,8 @@ impl WebsocketContext where A: Actor, { + /// Create a new Websocket context from a request and an actor. #[inline] - /// Create a new Websocket context from a request and an actor pub fn create(actor: A, stream: S) -> impl Stream> where A: StreamHandler>, @@ -258,12 +444,11 @@ where stream } - #[inline] /// Create a new Websocket context from a request and an actor. /// - /// Returns a pair, where the first item is an addr for the created actor, - /// and the second item is a stream intended to be set as part of the - /// response via `HttpResponseBuilder::streaming()`. + /// Returns a pair, where the first item is an addr for the created actor, and the second item + /// is a stream intended to be set as part of the response + /// via [`HttpResponseBuilder::streaming()`]. pub fn create_with_addr( actor: A, stream: S, @@ -284,7 +469,6 @@ where (addr, WebsocketContextFut::new(ctx, actor, mb, Codec::new())) } - #[inline] /// Create a new Websocket context from a request, an actor, and a codec pub fn with_codec( actor: A, @@ -300,7 +484,7 @@ where inner: ContextParts::new(mb.sender_producer()), messages: VecDeque::new(), }; - ctx.add_stream(WsStream::new(stream, codec)); + ctx.add_stream(WsStream::new(stream, codec.clone())); WebsocketContextFut::new(ctx, actor, mb, codec) } @@ -458,12 +642,13 @@ where M: ActixMessage + Send + 'static, M::Result: Send, { - fn pack(msg: M, tx: Option>) -> Envelope { + fn pack(msg: M, tx: Option>) -> Envelope { Envelope::new(msg, tx) } } pin_project! { + #[derive(Debug)] struct WsStream { #[pin] stream: S, @@ -549,9 +734,12 @@ where #[cfg(test)] mod tests { + use actix_web::{ + http::{header, Method}, + test::TestRequest, + }; + use super::*; - use actix_web::http::{header, Method}; - use actix_web::test::TestRequest; #[test] fn test_handshake() { diff --git a/actix-web-actors/tests/test_ws.rs b/actix-web-actors/tests/test_ws.rs index e481b2839..a9eb37699 100644 --- a/actix-web-actors/tests/test_ws.rs +++ b/actix-web-actors/tests/test_ws.rs @@ -1,11 +1,9 @@ use actix::prelude::*; -use actix_web::{ - http::{header, StatusCode}, - web, App, HttpRequest, HttpResponse, -}; -use actix_web_actors::*; +use actix_http::ws::Codec; +use actix_web::{web, App, HttpRequest}; +use actix_web_actors::ws; use bytes::Bytes; -use futures_util::{SinkExt as _, StreamExt as _}; +use futures_util::{SinkExt, StreamExt}; struct Ws; @@ -15,37 +13,34 @@ impl Actor for Ws { impl StreamHandler> for Ws { fn handle(&mut self, msg: Result, ctx: &mut Self::Context) { - match msg.unwrap() { - ws::Message::Ping(msg) => ctx.pong(&msg), - ws::Message::Text(text) => ctx.text(text), - ws::Message::Binary(bin) => ctx.binary(bin), - ws::Message::Close(reason) => ctx.close(reason), - _ => {} + match msg { + Ok(ws::Message::Ping(msg)) => ctx.pong(&msg), + Ok(ws::Message::Text(text)) => ctx.text(text), + Ok(ws::Message::Binary(bin)) => ctx.binary(bin), + Ok(ws::Message::Close(reason)) => ctx.close(reason), + _ => ctx.close(Some(ws::CloseCode::Error.into())), } } } -#[actix_rt::test] -async fn test_simple() { - let mut srv = actix_test::start(|| { - App::new().service(web::resource("/").to( - |req: HttpRequest, stream: web::Payload| async move { ws::start(Ws, &req, stream) }, - )) - }); +const MAX_FRAME_SIZE: usize = 10_000; +const DEFAULT_FRAME_SIZE: usize = 10; +async fn common_test_code(mut srv: actix_test::TestServer, frame_size: usize) { // client service let mut framed = srv.ws().await.unwrap(); - framed.send(ws::Message::Text("text".into())).await.unwrap(); + framed.send(ws::Message::Text("text".into())).await.unwrap(); let item = framed.next().await.unwrap().unwrap(); assert_eq!(item, ws::Frame::Text(Bytes::from_static(b"text"))); + let bytes = Bytes::from(vec![0; frame_size]); framed - .send(ws::Message::Binary("text".into())) + .send(ws::Message::Binary(bytes.clone())) .await .unwrap(); let item = framed.next().await.unwrap().unwrap(); - assert_eq!(item, ws::Frame::Binary(Bytes::from_static(b"text"))); + assert_eq!(item, ws::Frame::Binary(bytes)); framed.send(ws::Message::Ping("text".into())).await.unwrap(); let item = framed.next().await.unwrap().unwrap(); @@ -55,55 +50,137 @@ async fn test_simple() { .send(ws::Message::Close(Some(ws::CloseCode::Normal.into()))) .await .unwrap(); - let item = framed.next().await.unwrap().unwrap(); assert_eq!(item, ws::Frame::Close(Some(ws::CloseCode::Normal.into()))); } #[actix_rt::test] -async fn test_with_credentials() { - let mut srv = actix_test::start(|| { +async fn simple_builder() { + let srv = actix_test::start(|| { App::new().service(web::resource("/").to( |req: HttpRequest, stream: web::Payload| async move { - if req.headers().contains_key("Authorization") { - ws::start(Ws, &req, stream) - } else { - Ok(HttpResponse::new(StatusCode::UNAUTHORIZED)) - } + ws::WsResponseBuilder::new(Ws, &req, stream).start() }, )) }); - // client service without credentials - match srv.ws().await { - Ok(_) => panic!("WebSocket client without credentials should panic"), - Err(awc::error::WsClientError::InvalidResponseStatus(status)) => { - assert_eq!(status, StatusCode::UNAUTHORIZED); - } - Err(e) => panic!("Invalid error from WebSocket client: {}", e), - } - - let headers = srv.client_headers().unwrap(); - headers.insert( - header::AUTHORIZATION, - header::HeaderValue::from_static("Bearer Something"), - ); - - // client service with credentials - let client = srv.ws(); - - let mut framed = client.await.unwrap(); - - framed.send(ws::Message::Text("text".into())).await.unwrap(); - - let item = framed.next().await.unwrap().unwrap(); - assert_eq!(item, ws::Frame::Text(Bytes::from_static(b"text"))); - - framed - .send(ws::Message::Close(Some(ws::CloseCode::Normal.into()))) - .await - .unwrap(); - - let item = framed.next().await.unwrap().unwrap(); - assert_eq!(item, ws::Frame::Close(Some(ws::CloseCode::Normal.into()))); + common_test_code(srv, DEFAULT_FRAME_SIZE).await; +} + +#[actix_rt::test] +async fn builder_with_frame_size() { + let srv = actix_test::start(|| { + App::new().service(web::resource("/").to( + |req: HttpRequest, stream: web::Payload| async move { + ws::WsResponseBuilder::new(Ws, &req, stream) + .frame_size(MAX_FRAME_SIZE) + .start() + }, + )) + }); + + common_test_code(srv, MAX_FRAME_SIZE).await; +} + +#[actix_rt::test] +async fn builder_with_frame_size_exceeded() { + const MAX_FRAME_SIZE: usize = 64; + + let mut srv = actix_test::start(|| { + App::new().service(web::resource("/").to( + |req: HttpRequest, stream: web::Payload| async move { + ws::WsResponseBuilder::new(Ws, &req, stream) + .frame_size(MAX_FRAME_SIZE) + .start() + }, + )) + }); + + // client service + let mut framed = srv.ws().await.unwrap(); + + // create a request with a frame size larger than expected + let bytes = Bytes::from(vec![0; MAX_FRAME_SIZE + 1]); + framed.send(ws::Message::Binary(bytes)).await.unwrap(); + + let frame = framed.next().await.unwrap().unwrap(); + let close_reason = match frame { + ws::Frame::Close(Some(reason)) => reason, + _ => panic!("close frame expected"), + }; + assert_eq!(close_reason.code, ws::CloseCode::Error); +} + +#[actix_rt::test] +async fn builder_with_codec() { + let srv = actix_test::start(|| { + App::new().service(web::resource("/").to( + |req: HttpRequest, stream: web::Payload| async move { + ws::WsResponseBuilder::new(Ws, &req, stream) + .codec(Codec::new()) + .start() + }, + )) + }); + + common_test_code(srv, DEFAULT_FRAME_SIZE).await; +} + +#[actix_rt::test] +async fn builder_with_protocols() { + let srv = actix_test::start(|| { + App::new().service(web::resource("/").to( + |req: HttpRequest, stream: web::Payload| async move { + ws::WsResponseBuilder::new(Ws, &req, stream) + .protocols(&["A", "B"]) + .start() + }, + )) + }); + + common_test_code(srv, DEFAULT_FRAME_SIZE).await; +} + +#[actix_rt::test] +async fn builder_with_codec_and_frame_size() { + let srv = actix_test::start(|| { + App::new().service(web::resource("/").to( + |req: HttpRequest, stream: web::Payload| async move { + ws::WsResponseBuilder::new(Ws, &req, stream) + .codec(Codec::new()) + .frame_size(MAX_FRAME_SIZE) + .start() + }, + )) + }); + + common_test_code(srv, DEFAULT_FRAME_SIZE).await; +} + +#[actix_rt::test] +async fn builder_full() { + let srv = actix_test::start(|| { + App::new().service(web::resource("/").to( + |req: HttpRequest, stream: web::Payload| async move { + ws::WsResponseBuilder::new(Ws, &req, stream) + .frame_size(MAX_FRAME_SIZE) + .codec(Codec::new()) + .protocols(&["A", "B"]) + .start() + }, + )) + }); + + common_test_code(srv, MAX_FRAME_SIZE).await; +} + +#[actix_rt::test] +async fn simple_start() { + let srv = actix_test::start(|| { + App::new().service(web::resource("/").to( + |req: HttpRequest, stream: web::Payload| async move { ws::start(Ws, &req, stream) }, + )) + }); + + common_test_code(srv, DEFAULT_FRAME_SIZE).await; } From d89c706cd6653b9ce14ecbed4237435209e85f7b Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sun, 5 Dec 2021 00:02:25 +0000 Subject: [PATCH 301/428] re-instate Range typed header (#2485) Co-authored-by: RideWindX --- CHANGES.md | 2 + src/http/header/mod.rs | 6 +- src/http/header/range.rs | 319 +++++++++++++++++++++------------------ 3 files changed, 179 insertions(+), 148 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 2b5a71001..78aa729df 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -4,6 +4,7 @@ ### Added * Methods on `AcceptLanguage`: `ranked` and `preference`. [#2480] * `AcceptEncoding` typed header. [#2482] +* `Range` typed header. [#2485] * `HttpResponse::map_into_{left,right}_body` and `HttpResponse::map_into_boxed_body`. [#2468] * `ServiceResponse::map_into_{left,right}_body` and `HttpResponse::map_into_boxed_body`. [#2468] @@ -21,6 +22,7 @@ [#2480]: https://github.com/actix/actix-web/pull/2480 [#2482]: https://github.com/actix/actix-web/pull/2482 [#2484]: https://github.com/actix/actix-web/pull/2484 +[#2485]: https://github.com/actix/actix-web/pull/2485 ## 4.0.0-beta.13 - 2021-11-30 diff --git a/src/http/header/mod.rs b/src/http/header/mod.rs index 98548dadd..07b7592d7 100644 --- a/src/http/header/mod.rs +++ b/src/http/header/mod.rs @@ -40,7 +40,7 @@ mod if_unmodified_since; mod last_modified; mod macros; mod preference; -// mod range; +mod range; #[cfg(test)] pub(crate) use macros::common_header_test; @@ -68,7 +68,7 @@ pub use self::if_range::IfRange; pub use self::if_unmodified_since::IfUnmodifiedSince; pub use self::last_modified::LastModified; pub use self::preference::Preference; -//pub use self::range::{Range, ByteRangeSpec}; +pub use self::range::{ByteRangeSpec, Range}; /// Format writer ([`fmt::Write`]) for a [`BytesMut`]. #[derive(Debug, Default)] @@ -77,10 +77,12 @@ struct Writer { } impl Writer { + /// Constructs new bytes writer. pub fn new() -> Writer { Writer::default() } + /// Splits bytes out of writer, leaving writer buffer empty. pub fn take(&mut self) -> Bytes { self.buf.split().freeze() } diff --git a/src/http/header/range.rs b/src/http/header/range.rs index 11006ffff..c1d60f1ee 100644 --- a/src/http/header/range.rs +++ b/src/http/header/range.rs @@ -1,11 +1,12 @@ -// TODO: reinstate module - use std::{ - fmt::{self, Display}, + cmp, + fmt::{self, Display, Write}, str::FromStr, }; -use super::{parsing::from_one_raw_str, Header, Raw}; +use actix_http::{error::ParseError, header, HttpMessage}; + +use super::{Header, HeaderName, HeaderValue, IntoHeaderValue, InvalidHeaderValue, Writer}; /// `Range` header, defined /// in [RFC 7233 ยง3.1](https://datatracker.ietf.org/doc/html/rfc7233#section-3.1) @@ -16,7 +17,7 @@ use super::{parsing::from_one_raw_str, Header, Raw}; /// /// # ABNF /// ```plain -/// Range = byte-ranges-specifier / other-ranges-specifier +/// Range = byte-ranges-specifier / other-ranges-specifier /// other-ranges-specifier = other-range-unit "=" other-range-set /// other-range-set = 1*VCHAR /// @@ -25,13 +26,15 @@ use super::{parsing::from_one_raw_str, Header, Raw}; /// byte-ranges-specifier = bytes-unit "=" byte-range-set /// byte-range-set = 1#(byte-range-spec / suffix-byte-range-spec) /// byte-range-spec = first-byte-pos "-" [last-byte-pos] +/// suffix-byte-range-spec = "-" suffix-length +/// suffix-length = 1*DIGIT /// first-byte-pos = 1*DIGIT /// last-byte-pos = 1*DIGIT /// ``` /// /// # Example Values /// * `bytes=1000-` -/// * `bytes=-2000` +/// * `bytes=-50` /// * `bytes=0-1,30-40` /// * `bytes=0-10,20-90,-100` /// * `custom_unit=0-123` @@ -39,81 +42,81 @@ use super::{parsing::from_one_raw_str, Header, Raw}; /// /// # Examples /// ``` -/// use hyper::header::{Headers, Range, ByteRangeSpec}; +/// use actix_web::http::header::{Range, ByteRangeSpec}; +/// use actix_web::HttpResponse; /// -/// let mut headers = Headers::new(); -/// headers.set(Range::Bytes( -/// vec![ByteRangeSpec::FromTo(1, 100), ByteRangeSpec::AllFrom(200)] +/// let mut builder = HttpResponse::Ok(); +/// builder.insert_header(Range::Bytes( +/// vec![ByteRangeSpec::FromTo(1, 100), ByteRangeSpec::From(200)] /// )); -/// -/// headers.clear(); -/// headers.set(Range::Unregistered("letters".to_owned(), "a-f".to_owned())); -/// ``` -/// -/// ``` -/// use hyper::header::{Headers, Range}; -/// -/// let mut headers = Headers::new(); -/// headers.set(Range::bytes(1, 100)); -/// -/// headers.clear(); -/// headers.set(Range::bytes_multi(vec![(1, 100), (200, 300)])); +/// builder.insert_header(Range::Unregistered("letters".to_owned(), "a-f".to_owned())); +/// builder.insert_header(Range::bytes(1, 100)); +/// builder.insert_header(Range::bytes_multi(vec![(1, 100), (200, 300)])); /// ``` #[derive(PartialEq, Clone, Debug)] pub enum Range { - /// Byte range + /// Byte range. Bytes(Vec), - /// Custom range, with unit not registered at IANA + /// Custom range, with unit not registered at IANA. + /// /// (`other-range-unit`: String , `other-range-set`: String) Unregistered(String, String), } -/// Each `Range::Bytes` header can contain one or more `ByteRangeSpecs`. -/// Each `ByteRangeSpec` defines a range of bytes to fetch -#[derive(PartialEq, Clone, Debug)] +/// A range of bytes to fetch. +/// +/// Each [`Range::Bytes`] header can contain one or more `ByteRangeSpec`s. +#[derive(Debug, Clone, PartialEq, Eq)] pub enum ByteRangeSpec { - /// Get all bytes between x and y ("x-y") + /// All bytes from `x` to `y`, inclusive. + /// + /// Serialized as `x-y`. + /// + /// Example: `bytes=500-999` would represent the second 500 bytes. FromTo(u64, u64), - /// Get all bytes starting from x ("x-") - AllFrom(u64), + /// All bytes starting from `x`, inclusive. + /// + /// Serialized as `x-`. + /// + /// Example: For a file of 1000 bytes, `bytes=950-` would represent bytes 950-999, inclusive. + From(u64), - /// Get last x bytes ("-x") + /// The last `y` bytes, inclusive. + /// + /// Using the spec terminology, this is `suffix-byte-range-spec`. Serialized as `-y`. + /// + /// Example: For a file of 1000 bytes, `bytes=-50` is equivalent to `bytes=950-`. Last(u64), } impl ByteRangeSpec { - /// Given the full length of the entity, attempt to normalize the byte range - /// into an satisfiable end-inclusive (from, to) range. + /// Given the full length of the entity, attempt to normalize the byte range into an satisfiable + /// end-inclusive `(from, to)` range. /// - /// The resulting range is guaranteed to be a satisfiable range within the - /// bounds of `0 <= from <= to < full_length`. + /// The resulting range is guaranteed to be a satisfiable range within the bounds + /// of `0 <= from <= to < full_length`. /// - /// If the byte range is deemed unsatisfiable, `None` is returned. - /// An unsatisfiable range is generally cause for a server to either reject - /// the client request with a `416 Range Not Satisfiable` status code, or to - /// simply ignore the range header and serve the full entity using a `200 - /// OK` status code. + /// If the byte range is deemed unsatisfiable, `None` is returned. An unsatisfiable range is + /// generally cause for a server to either reject the client request with a + /// `416 Range Not Satisfiable` status code, or to simply ignore the range header and serve the + /// full entity using a `200 OK` status code. /// - /// This function closely follows [RFC 7233 ยง2.1]. - /// As such, it considers ranges to be satisfiable if they meet the - /// following conditions: + /// This function closely follows [RFC 7233 ยง2.1]. As such, it considers ranges to be + /// satisfiable if they meet the following conditions: /// - /// > If a valid byte-range-set includes at least one byte-range-spec with - /// a first-byte-pos that is less than the current length of the - /// representation, or at least one suffix-byte-range-spec with a - /// non-zero suffix-length, then the byte-range-set is satisfiable. - /// Otherwise, the byte-range-set is unsatisfiable. + /// > If a valid byte-range-set includes at least one byte-range-spec with a first-byte-pos that + /// is less than the current length of the representation, or at least one + /// suffix-byte-range-spec with a non-zero suffix-length, then the byte-range-set + /// is satisfiable. Otherwise, the byte-range-set is unsatisfiable. /// /// The function also computes remainder ranges based on the RFC: /// - /// > If the last-byte-pos value is - /// absent, or if the value is greater than or equal to the current - /// length of the representation data, the byte range is interpreted as - /// the remainder of the representation (i.e., the server replaces the - /// value of last-byte-pos with a value that is one less than the current - /// length of the selected representation). + /// > If the last-byte-pos value is absent, or if the value is greater than or equal to the + /// current length of the representation data, the byte range is interpreted as the remainder + /// of the representation (i.e., the server replaces the value of last-byte-pos with a value + /// that is one less than the current length of the selected representation). /// /// [RFC 7233 ยง2.1]: https://datatracker.ietf.org/doc/html/rfc7233 pub fn to_satisfiable_range(&self, full_length: u64) -> Option<(u64, u64)> { @@ -121,26 +124,28 @@ impl ByteRangeSpec { if full_length == 0 { return None; } - match self { - &ByteRangeSpec::FromTo(from, to) => { + + match *self { + ByteRangeSpec::FromTo(from, to) => { if from < full_length && from <= to { - Some((from, ::std::cmp::min(to, full_length - 1))) + Some((from, cmp::min(to, full_length - 1))) } else { None } } - &ByteRangeSpec::AllFrom(from) => { + + ByteRangeSpec::From(from) => { if from < full_length { Some((from, full_length - 1)) } else { None } } - &ByteRangeSpec::Last(last) => { + + ByteRangeSpec::Last(last) => { if last > 0 { - // From the RFC: If the selected representation is shorter - // than the specified suffix-length, - // the entire representation is used. + // From the RFC: If the selected representation is shorter than the specified + // suffix-length, the entire representation is used. if last > full_length { Some((0, full_length - 1)) } else { @@ -155,48 +160,53 @@ impl ByteRangeSpec { } impl Range { - /// Get the most common byte range header ("bytes=from-to") + /// Constructs a common byte range header. + /// + /// Eg: `bytes=from-to` pub fn bytes(from: u64, to: u64) -> Range { Range::Bytes(vec![ByteRangeSpec::FromTo(from, to)]) } - /// Get byte range header with multiple subranges - /// ("bytes=from1-to1,from2-to2,fromX-toX") + /// Constructs a byte range header with multiple subranges. + /// + /// Eg: `bytes=from1-to1,from2-to2,fromX-toX` pub fn bytes_multi(ranges: Vec<(u64, u64)>) -> Range { Range::Bytes( ranges - .iter() - .map(|r| ByteRangeSpec::FromTo(r.0, r.1)) + .into_iter() + .map(|(from, to)| ByteRangeSpec::FromTo(from, to)) .collect(), ) } } impl fmt::Display for ByteRangeSpec { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match *self { ByteRangeSpec::FromTo(from, to) => write!(f, "{}-{}", from, to), ByteRangeSpec::Last(pos) => write!(f, "-{}", pos), - ByteRangeSpec::AllFrom(pos) => write!(f, "{}-", pos), + ByteRangeSpec::From(pos) => write!(f, "{}-", pos), } } } impl fmt::Display for Range { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match *self { - Range::Bytes(ref ranges) => { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Range::Bytes(ranges) => { write!(f, "bytes=")?; for (i, range) in ranges.iter().enumerate() { if i != 0 { f.write_str(",")?; } + Display::fmt(range, f)?; } Ok(()) } - Range::Unregistered(ref unit, ref range_str) => { + + Range::Unregistered(unit, range_str) => { write!(f, "{}={}", unit, range_str) } } @@ -204,89 +214,118 @@ impl fmt::Display for Range { } impl FromStr for Range { - type Err = ::Error; + type Err = ParseError; - fn from_str(s: &str) -> ::Result { - let mut iter = s.splitn(2, '='); + fn from_str(s: &str) -> Result { + let (unit, val) = s.split_once('=').ok_or(ParseError::Header)?; - match (iter.next(), iter.next()) { - (Some("bytes"), Some(ranges)) => { + match (unit, val) { + ("bytes", ranges) => { let ranges = from_comma_delimited(ranges); + if ranges.is_empty() { - return Err(::Error::Header); + return Err(ParseError::Header); } + Ok(Range::Bytes(ranges)) } - (Some(unit), Some(range_str)) if unit != "" && range_str != "" => { - Ok(Range::Unregistered(unit.to_owned(), range_str.to_owned())) - } - _ => Err(::Error::Header), + + (_, "") => Err(ParseError::Header), + ("", _) => Err(ParseError::Header), + + (unit, range_str) => Ok(Range::Unregistered(unit.to_owned(), range_str.to_owned())), } } } impl FromStr for ByteRangeSpec { - type Err = ::Error; + type Err = ParseError; - fn from_str(s: &str) -> ::Result { - let mut parts = s.splitn(2, '-'); + fn from_str(s: &str) -> Result { + let (start, end) = s.split_once('-').ok_or(ParseError::Header)?; - match (parts.next(), parts.next()) { - (Some(""), Some(end)) => end + match (start, end) { + ("", end) => end .parse() - .or(Err(::Error::Header)) + .or(Err(ParseError::Header)) .map(ByteRangeSpec::Last), - (Some(start), Some("")) => start + + (start, "") => start .parse() - .or(Err(::Error::Header)) - .map(ByteRangeSpec::AllFrom), - (Some(start), Some(end)) => match (start.parse(), end.parse()) { + .or(Err(ParseError::Header)) + .map(ByteRangeSpec::From), + + (start, end) => match (start.parse(), end.parse()) { (Ok(start), Ok(end)) if start <= end => Ok(ByteRangeSpec::FromTo(start, end)), - _ => Err(::Error::Header), + _ => Err(ParseError::Header), }, - _ => Err(::Error::Header), } } } impl Header for Range { - fn header_name() -> &'static str { - static NAME: &'static str = "Range"; - NAME + fn name() -> HeaderName { + header::RANGE } - fn parse_header(raw: &Raw) -> ::Result { - from_one_raw_str(raw) + #[inline] + fn parse(msg: &T) -> Result { + header::from_one_raw_str(msg.headers().get(&Self::name())) } +} - fn fmt_header(&self, f: &mut ::header::Formatter) -> fmt::Result { - f.fmt_line(self) +impl IntoHeaderValue for Range { + type Error = InvalidHeaderValue; + + fn try_into_value(self) -> Result { + let mut wrt = Writer::new(); + let _ = write!(wrt, "{}", self); + HeaderValue::from_maybe_shared(wrt.take()) } } +/// Parses 0 or more items out of a comma delimited string, ignoring invalid items. +fn from_comma_delimited(s: &str) -> Vec { + s.split(',') + .filter_map(|x| match x.trim() { + "" => None, + y => Some(y), + }) + .filter_map(|x| x.parse().ok()) + .collect() +} + #[cfg(test)] mod tests { + use actix_http::{test::TestRequest, Request}; + use super::*; + fn req(s: &str) -> Request { + TestRequest::default() + .insert_header((header::RANGE, s)) + .finish() + } + #[test] fn test_parse_bytes_range_valid() { - let r: Range = Header::parse_header(&"bytes=1-100".into()).unwrap(); - let r2: Range = Header::parse_header(&"bytes=1-100,-".into()).unwrap(); + let r: Range = Header::parse(&req("bytes=1-100")).unwrap(); + let r2: Range = Header::parse(&req("bytes=1-100,-")).unwrap(); let r3 = Range::bytes(1, 100); assert_eq!(r, r2); assert_eq!(r2, r3); - let r: Range = Header::parse_header(&"bytes=1-100,200-".into()).unwrap(); - let r2: Range = Header::parse_header(&"bytes= 1-100 , 101-xxx, 200- ".into()).unwrap(); + let r: Range = Header::parse(&req("bytes=1-100,200-")).unwrap(); + let r2: Range = Header::parse(&req("bytes= 1-100 , 101-xxx, 200- ")).unwrap(); let r3 = Range::Bytes(vec![ ByteRangeSpec::FromTo(1, 100), - ByteRangeSpec::AllFrom(200), + ByteRangeSpec::From(200), ]); assert_eq!(r, r2); assert_eq!(r2, r3); - let r: Range = Header::parse_header(&"bytes=1-100,-100".into()).unwrap(); - let r2: Range = Header::parse_header(&"bytes=1-100, ,,-100".into()).unwrap(); + let r: Range = Header::parse(&req("bytes=1-100,-100")).unwrap(); + let r2: Range = Header::parse(&req("bytes=1-100, ,,-100")).unwrap(); let r3 = Range::Bytes(vec![ ByteRangeSpec::FromTo(1, 100), ByteRangeSpec::Last(100), @@ -294,71 +333,65 @@ mod tests { assert_eq!(r, r2); assert_eq!(r2, r3); - let r: Range = Header::parse_header(&"custom=1-100,-100".into()).unwrap(); + let r: Range = Header::parse(&req("custom=1-100,-100")).unwrap(); let r2 = Range::Unregistered("custom".to_owned(), "1-100,-100".to_owned()); assert_eq!(r, r2); } #[test] fn test_parse_unregistered_range_valid() { - let r: Range = Header::parse_header(&"custom=1-100,-100".into()).unwrap(); + let r: Range = Header::parse(&req("custom=1-100,-100")).unwrap(); let r2 = Range::Unregistered("custom".to_owned(), "1-100,-100".to_owned()); assert_eq!(r, r2); - let r: Range = Header::parse_header(&"custom=abcd".into()).unwrap(); + let r: Range = Header::parse(&req("custom=abcd")).unwrap(); let r2 = Range::Unregistered("custom".to_owned(), "abcd".to_owned()); assert_eq!(r, r2); - let r: Range = Header::parse_header(&"custom=xxx-yyy".into()).unwrap(); + let r: Range = Header::parse(&req("custom=xxx-yyy")).unwrap(); let r2 = Range::Unregistered("custom".to_owned(), "xxx-yyy".to_owned()); assert_eq!(r, r2); } #[test] fn test_parse_invalid() { - let r: ::Result = Header::parse_header(&"bytes=1-a,-".into()); + let r: Result = Header::parse(&req("bytes=1-a,-")); assert_eq!(r.ok(), None); - let r: ::Result = Header::parse_header(&"bytes=1-2-3".into()); + let r: Result = Header::parse(&req("bytes=1-2-3")); assert_eq!(r.ok(), None); - let r: ::Result = Header::parse_header(&"abc".into()); + let r: Result = Header::parse(&req("abc")); assert_eq!(r.ok(), None); - let r: ::Result = Header::parse_header(&"bytes=1-100=".into()); + let r: Result = Header::parse(&req("bytes=1-100=")); assert_eq!(r.ok(), None); - let r: ::Result = Header::parse_header(&"bytes=".into()); + let r: Result = Header::parse(&req("bytes=")); assert_eq!(r.ok(), None); - let r: ::Result = Header::parse_header(&"custom=".into()); + let r: Result = Header::parse(&req("custom=")); assert_eq!(r.ok(), None); - let r: ::Result = Header::parse_header(&"=1-100".into()); + let r: Result = Header::parse(&req("=1-100")); assert_eq!(r.ok(), None); } #[test] fn test_fmt() { - use header::Headers; - - let mut headers = Headers::new(); - - headers.set(Range::Bytes(vec![ + let range = Range::Bytes(vec![ ByteRangeSpec::FromTo(0, 1000), - ByteRangeSpec::AllFrom(2000), - ])); - assert_eq!(&headers.to_string(), "Range: bytes=0-1000,2000-\r\n"); + ByteRangeSpec::From(2000), + ]); + assert_eq!(&range.to_string(), "bytes=0-1000,2000-"); - headers.clear(); - headers.set(Range::Bytes(vec![])); + let range = Range::Bytes(vec![]); - assert_eq!(&headers.to_string(), "Range: bytes=\r\n"); + assert_eq!(&range.to_string(), "bytes="); - headers.clear(); - headers.set(Range::Unregistered("custom".to_owned(), "1-xxx".to_owned())); + let range = Range::Unregistered("custom".to_owned(), "1-xxx".to_owned()); - assert_eq!(&headers.to_string(), "Range: custom=1-xxx\r\n"); + assert_eq!(&range.to_string(), "custom=1-xxx"); } #[test] @@ -379,17 +412,11 @@ mod tests { assert_eq!(None, ByteRangeSpec::FromTo(2, 1).to_satisfiable_range(3)); assert_eq!(None, ByteRangeSpec::FromTo(0, 0).to_satisfiable_range(0)); - assert_eq!( - Some((0, 2)), - ByteRangeSpec::AllFrom(0).to_satisfiable_range(3) - ); - assert_eq!( - Some((2, 2)), - ByteRangeSpec::AllFrom(2).to_satisfiable_range(3) - ); - assert_eq!(None, ByteRangeSpec::AllFrom(3).to_satisfiable_range(3)); - assert_eq!(None, ByteRangeSpec::AllFrom(5).to_satisfiable_range(3)); - assert_eq!(None, ByteRangeSpec::AllFrom(0).to_satisfiable_range(0)); + assert_eq!(Some((0, 2)), ByteRangeSpec::From(0).to_satisfiable_range(3)); + assert_eq!(Some((2, 2)), ByteRangeSpec::From(2).to_satisfiable_range(3)); + assert_eq!(None, ByteRangeSpec::From(3).to_satisfiable_range(3)); + assert_eq!(None, ByteRangeSpec::From(5).to_satisfiable_range(3)); + assert_eq!(None, ByteRangeSpec::From(0).to_satisfiable_range(0)); assert_eq!(Some((1, 2)), ByteRangeSpec::Last(2).to_satisfiable_range(3)); assert_eq!(Some((2, 2)), ByteRangeSpec::Last(1).to_satisfiable_range(3)); From e1a2d9c60684820723a539f4005f3af35281884a Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sun, 5 Dec 2021 03:38:08 +0000 Subject: [PATCH 302/428] `Quality` / `QualityItem` improvements (#2486) --- .cargo/config.toml | 2 + actix-http/CHANGES.md | 6 + actix-http/Cargo.toml | 4 + actix-http/benches/quality-value.rs | 90 ++++++++ actix-http/src/header/mod.rs | 7 +- actix-http/src/header/shared/extended.rs | 2 +- actix-http/src/header/shared/mod.rs | 4 +- actix-http/src/header/shared/quality.rs | 208 ++++++++++++++++++ actix-http/src/header/shared/quality_item.rs | 216 ++++++------------- actix-http/src/header/utils.rs | 5 +- actix-multipart/src/server.rs | 11 +- src/http/header/accept.rs | 74 +++---- src/http/header/accept_charset.rs | 12 +- src/http/header/accept_encoding.rs | 24 ++- src/http/header/accept_language.rs | 76 ++++--- src/http/header/content_language.rs | 10 +- 16 files changed, 494 insertions(+), 257 deletions(-) create mode 100644 actix-http/benches/quality-value.rs create mode 100644 actix-http/src/header/shared/quality.rs diff --git a/.cargo/config.toml b/.cargo/config.toml index 606c30de7..4425e0dda 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -5,8 +5,10 @@ lint-all = "clippy --workspace --all-features --tests --examples --bins -- -Dcli # lib checking ci-check-min = "hack --workspace check --no-default-features" ci-check-default = "hack --workspace check" +ci-check-default-tests = "check --workspace --tests" ci-check-all-feature-powerset="hack --workspace --feature-powerset --skip=__compress,io-uring check" ci-check-all-feature-powerset-linux="hack --workspace --feature-powerset --skip=__compress check" # testing +ci-doctest-default = "test --workspace --doc --no-fail-fast -- --nocapture" ci-doctest = "test --workspace --all-features --doc --no-fail-fast -- --nocapture" diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 773f1ff39..877380581 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -11,6 +11,9 @@ * `impl Clone for ws::HandshakeError`. [#2468] * `#[must_use]` for `ws::Codec` to prevent subtle bugs. [#1920] * `impl Default ` for `ws::Codec`. [#1920] +* `header::QualityItem::{max, min}`. [#2486] +* `header::Quality::{MAX, MIN}`. [#2486] +* `impl Display` for `header::Quality`. [#2486] ### Changed * Rename `body::BoxBody::{from_body => new}`. [#2468] @@ -27,10 +30,13 @@ * Remove unnecessary `MessageBody` bound on types passed to `body::AnyBody::new`. [#2468] * Move `body::AnyBody` to `awc`. Replaced with `EitherBody` and `BoxBody`. [#2468] * `impl Copy` for `ws::Codec`. [#1920] +* `header::qitem` helper. Replaced with `header::QualityItem::max` [#2486] +* `impl TryFrom` for `header::Quality` [#2486] [#2483]: https://github.com/actix/actix-web/pull/2483 [#2468]: https://github.com/actix/actix-web/pull/2468 [#1920]: https://github.com/actix/actix-web/pull/1920 +[#2486]: https://github.com/actix/actix-web/pull/2486 ## 3.0.0-beta.14 - 2021-11-30 diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index f8b15df75..967f04d03 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -112,3 +112,7 @@ harness = false [[bench]] name = "uninit-headers" harness = false + +[[bench]] +name = "quality-value" +harness = false diff --git a/actix-http/benches/quality-value.rs b/actix-http/benches/quality-value.rs new file mode 100644 index 000000000..31b67f999 --- /dev/null +++ b/actix-http/benches/quality-value.rs @@ -0,0 +1,90 @@ +use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion}; + +const CODES: &[u16] = &[0, 1000, 201, 800, 550]; + +fn bench_quality_display_impls(c: &mut Criterion) { + let mut group = c.benchmark_group("quality value display impls"); + + for i in CODES.iter() { + group.bench_with_input(BenchmarkId::new("New (fast?)", i), i, |b, &i| { + b.iter(|| _new::Quality(i).to_string()) + }); + + group.bench_with_input(BenchmarkId::new("Naive", i), i, |b, &i| { + b.iter(|| _naive::Quality(i).to_string()) + }); + } + + group.finish(); +} + +criterion_group!(benches, bench_quality_display_impls); +criterion_main!(benches); + +mod _new { + use std::fmt; + + pub struct Quality(pub(crate) u16); + + impl fmt::Display for Quality { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self.0 { + 0 => f.write_str("0"), + 1000 => f.write_str("1"), + + // some number in the range 1โ€“999 + x => { + f.write_str("0.")?; + + // this implementation avoids string allocation otherwise required + // for `.trim_end_matches('0')` + + if x < 10 { + f.write_str("00")?; + // 0 is handled so it's not possible to have a trailing 0, we can just return + itoa::fmt(f, x) + } else if x < 100 { + f.write_str("0")?; + if x % 10 == 0 { + // trailing 0, divide by 10 and write + itoa::fmt(f, x / 10) + } else { + itoa::fmt(f, x) + } + } else { + // x is in range 101โ€“999 + + if x % 100 == 0 { + // two trailing 0s, divide by 100 and write + itoa::fmt(f, x / 100) + } else if x % 10 == 0 { + // one trailing 0, divide by 10 and write + itoa::fmt(f, x / 10) + } else { + itoa::fmt(f, x) + } + } + } + } + } + } +} + +mod _naive { + use std::fmt; + + pub struct Quality(pub(crate) u16); + + impl fmt::Display for Quality { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self.0 { + 0 => f.write_str("0"), + 1000 => f.write_str("1"), + + x => { + write!(f, "{}", format!("{:03}", x).trim_end_matches('0')) + } + } + } + } +} diff --git a/actix-http/src/header/mod.rs b/actix-http/src/header/mod.rs index 308cb0123..381842e74 100644 --- a/actix-http/src/header/mod.rs +++ b/actix-http/src/header/mod.rs @@ -38,13 +38,14 @@ pub mod map; mod shared; mod utils; -#[doc(hidden)] -pub use self::shared::*; - pub use self::as_name::AsHeaderName; pub use self::into_pair::IntoHeaderPair; pub use self::into_value::IntoHeaderValue; pub use self::map::HeaderMap; +pub use self::shared::{ + parse_extended_value, q, Charset, ContentEncoding, ExtendedValue, HttpDate, + LanguageTag, Quality, QualityItem, +}; pub use self::utils::{ fmt_comma_delimited, from_comma_delimited, from_one_raw_str, http_percent_encode, }; diff --git a/actix-http/src/header/shared/extended.rs b/actix-http/src/header/shared/extended.rs index b2cf1d754..60f2d359e 100644 --- a/actix-http/src/header/shared/extended.rs +++ b/actix-http/src/header/shared/extended.rs @@ -1,4 +1,4 @@ -// Originally from hyper v0.11.27 src/header/parsing.rs +//! Originally taken from `hyper::header::parsing`. use std::{fmt, str::FromStr}; diff --git a/actix-http/src/header/shared/mod.rs b/actix-http/src/header/shared/mod.rs index 274e13146..257e54d7a 100644 --- a/actix-http/src/header/shared/mod.rs +++ b/actix-http/src/header/shared/mod.rs @@ -4,11 +4,13 @@ mod charset; mod content_encoding; mod extended; mod http_date; +mod quality; mod quality_item; pub use self::charset::Charset; pub use self::content_encoding::ContentEncoding; pub use self::extended::{parse_extended_value, ExtendedValue}; pub use self::http_date::HttpDate; -pub use self::quality_item::{q, qitem, Quality, QualityItem}; +pub use self::quality::{q, Quality}; +pub use self::quality_item::QualityItem; pub use language_tags::LanguageTag; diff --git a/actix-http/src/header/shared/quality.rs b/actix-http/src/header/shared/quality.rs new file mode 100644 index 000000000..5321c754d --- /dev/null +++ b/actix-http/src/header/shared/quality.rs @@ -0,0 +1,208 @@ +use std::{ + convert::{TryFrom, TryInto}, + fmt, +}; + +use derive_more::{Display, Error}; + +const MAX_QUALITY_INT: u16 = 1000; +const MAX_QUALITY_FLOAT: f32 = 1.0; + +/// Represents a quality used in q-factor values. +/// +/// The default value is equivalent to `q=1.0` (the [max](Self::MAX) value). +/// +/// # Implementation notes +/// The quality value is defined as a number between 0.0 and 1.0 with three decimal places. +/// This means there are 1001 possible values. Since floating point numbers are not exact and the +/// smallest floating point data type (`f32`) consumes four bytes, we use an `u16` value to store +/// the quality internally. +/// +/// [RFC 7231 ยง5.3.1] gives more information on quality values in HTTP header fields. +/// +/// # Examples +/// ``` +/// use actix_http::header::{Quality, q}; +/// assert_eq!(q(1.0), Quality::MAX); +/// +/// assert_eq!(q(0.42).to_string(), "0.42"); +/// assert_eq!(q(1.0).to_string(), "1"); +/// assert_eq!(Quality::MIN.to_string(), "0"); +/// ``` +/// +/// [RFC 7231 ยง5.3.1]: https://datatracker.ietf.org/doc/html/rfc7231#section-5.3.1 +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub struct Quality(pub(super) u16); + +impl Quality { + /// The maximum quality value, equivalent to `q=1.0`. + pub const MAX: Quality = Quality(MAX_QUALITY_INT); + + /// The minimum quality value, equivalent to `q=0.0`. + pub const MIN: Quality = Quality(0); + + /// Converts a float in the range 0.0โ€“1.0 to a `Quality`. + /// + /// Intentionally private. External uses should rely on the `TryFrom` impl. + /// + /// # Panics + /// Panics in debug mode when value is not in the range 0.0 <= n <= 1.0. + fn from_f32(value: f32) -> Self { + // Check that `value` is within range should be done before calling this method. + // Just in case, this debug_assert should catch if we were forgetful. + debug_assert!( + (0.0f32..=1.0f32).contains(&value), + "q value must be between 0.0 and 1.0" + ); + + Quality((value * MAX_QUALITY_INT as f32) as u16) + } +} + +/// The default value is [`Quality::MAX`]. +impl Default for Quality { + fn default() -> Quality { + Quality::MAX + } +} + +impl fmt::Display for Quality { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self.0 { + 0 => f.write_str("0"), + MAX_QUALITY_INT => f.write_str("1"), + + // some number in the range 1โ€“999 + x => { + f.write_str("0.")?; + + // This implementation avoids string allocation for removing trailing zeroes. + // In benchmarks it is twice as fast as approach using something like + // `format!("{}").trim_end_matches('0')` for non-fast-path quality values. + + if x < 10 { + // x in is range 1โ€“9 + + f.write_str("00")?; + + // 0 is already handled so it's not possible to have a trailing 0 in this range + // we can just write the integer + itoa::fmt(f, x) + } else if x < 100 { + // x in is range 10โ€“99 + + f.write_str("0")?; + + if x % 10 == 0 { + // trailing 0, divide by 10 and write + itoa::fmt(f, x / 10) + } else { + itoa::fmt(f, x) + } + } else { + // x is in range 100โ€“999 + + if x % 100 == 0 { + // two trailing 0s, divide by 100 and write + itoa::fmt(f, x / 100) + } else if x % 10 == 0 { + // one trailing 0, divide by 10 and write + itoa::fmt(f, x / 10) + } else { + itoa::fmt(f, x) + } + } + } + } + } +} + +#[derive(Debug, Clone, Display, Error)] +#[display(fmt = "quality out of bounds")] +#[non_exhaustive] +pub struct QualityOutOfBounds; + +impl TryFrom for Quality { + type Error = QualityOutOfBounds; + + #[inline] + fn try_from(value: f32) -> Result { + if (0.0..=MAX_QUALITY_FLOAT).contains(&value) { + Ok(Quality::from_f32(value)) + } else { + Err(QualityOutOfBounds) + } + } +} + +/// Convenience function to create a [`Quality`] from an `f32` (0.0โ€“1.0). +/// +/// Not recommended for use with user input. Rely on the `TryFrom` impls where possible. +/// +/// # Panics +/// Panics if value is out of range. +/// +/// # Examples +/// ``` +/// # use actix_http::header::{q, Quality}; +/// let q1 = q(1.0); +/// assert_eq!(q1, Quality::MAX); +/// +/// let q2 = q(0.0); +/// assert_eq!(q2, Quality::MIN); +/// +/// let q3 = q(0.42); +/// ``` +/// +/// An out-of-range `f32` quality will panic. +/// ```should_panic +/// # use actix_http::header::q; +/// let _q2 = q(1.42); +/// ``` +#[inline] +pub fn q(quality: T) -> Quality +where + T: TryInto, + T::Error: fmt::Debug, +{ + quality.try_into().expect("quality value was out of bounds") +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn q_helper() { + assert_eq!(q(0.5), Quality(500)); + } + + #[test] + fn display_output() { + assert_eq!(q(0.0).to_string(), "0"); + assert_eq!(q(1.0).to_string(), "1"); + assert_eq!(q(0.001).to_string(), "0.001"); + assert_eq!(q(0.5).to_string(), "0.5"); + assert_eq!(q(0.22).to_string(), "0.22"); + assert_eq!(q(0.123).to_string(), "0.123"); + assert_eq!(q(0.999).to_string(), "0.999"); + + for x in 0..=1000 { + // if trailing zeroes are handled correctly, we would not expect the serialized length + // to ever exceed "0." + 3 decimal places = 5 in length + assert!(q(x as f32 / 1000.0).to_string().len() <= 5); + } + } + + #[test] + #[should_panic] + fn negative_quality() { + q(-1.0); + } + + #[test] + #[should_panic] + fn quality_out_of_bounds() { + q(2.0); + } +} diff --git a/actix-http/src/header/shared/quality_item.rs b/actix-http/src/header/shared/quality_item.rs index a109b44ea..9354915ad 100644 --- a/actix-http/src/header/shared/quality_item.rs +++ b/actix-http/src/header/shared/quality_item.rs @@ -1,85 +1,36 @@ -use std::{ - cmp, - convert::{TryFrom, TryInto}, - fmt, str, -}; - -use derive_more::{Display, Error}; +use std::{cmp, convert::TryFrom as _, fmt, str}; use crate::error::ParseError; -const MAX_QUALITY: u16 = 1000; -const MAX_FLOAT_QUALITY: f32 = 1.0; - -/// Represents a quality used in quality values. -/// -/// Can be created with the [`q`] function. -/// -/// # Implementation notes -/// -/// The quality value is defined as a number between 0 and 1 with three decimal -/// places. This means there are 1001 possible values. Since floating point -/// numbers are not exact and the smallest floating point data type (`f32`) -/// consumes four bytes, hyper uses an `u16` value to store the -/// quality internally. For performance reasons you may set quality directly to -/// a value between 0 and 1000 e.g. `Quality(532)` matches the quality -/// `q=0.532`. -/// -/// [RFC 7231 ยง5.3.1](https://datatracker.ietf.org/doc/html/rfc7231#section-5.3.1) gives more -/// information on quality values in HTTP header fields. -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] -pub struct Quality(u16); - -impl Quality { - /// # Panics - /// Panics in debug mode when value is not in the range 0.0 <= n <= 1.0. - fn from_f32(value: f32) -> Self { - // Check that `value` is within range should be done before calling this method. - // Just in case, this debug_assert should catch if we were forgetful. - debug_assert!( - (0.0f32..=1.0f32).contains(&value), - "q value must be between 0.0 and 1.0" - ); - - Quality((value * MAX_QUALITY as f32) as u16) - } -} - -impl Default for Quality { - fn default() -> Quality { - Quality(MAX_QUALITY) - } -} - -#[derive(Debug, Clone, Display, Error)] -pub struct QualityOutOfBounds; - -impl TryFrom for Quality { - type Error = QualityOutOfBounds; - - fn try_from(value: u16) -> Result { - if (0..=MAX_QUALITY).contains(&value) { - Ok(Quality(value)) - } else { - Err(QualityOutOfBounds) - } - } -} - -impl TryFrom for Quality { - type Error = QualityOutOfBounds; - - fn try_from(value: f32) -> Result { - if (0.0..=MAX_FLOAT_QUALITY).contains(&value) { - Ok(Quality::from_f32(value)) - } else { - Err(QualityOutOfBounds) - } - } -} +use super::Quality; /// Represents an item with a quality value as defined /// in [RFC 7231 ยง5.3.1](https://datatracker.ietf.org/doc/html/rfc7231#section-5.3.1). +/// +/// # Parsing and Formatting +/// This wrapper be used to parse header value items that have a q-factor annotation as well as +/// serialize items with a their q-factor. +/// +/// # Ordering +/// Since this context of use for this type is header value items, ordering is defined for +/// `QualityItem`s but _only_ considers the item's quality. Order of appearance should be used as +/// the secondary sorting parameter; i.e., a stable sort over the quality values will produce a +/// correctly sorted sequence. +/// +/// # Examples +/// ``` +/// # use actix_http::header::{QualityItem, q}; +/// let q_item: QualityItem = "hello;q=0.3".parse().unwrap(); +/// assert_eq!(&q_item.item, "hello"); +/// assert_eq!(q_item.quality, q(0.3)); +/// +/// // note that format is normalized compared to parsed item +/// assert_eq!(q_item.to_string(), "hello; q=0.3"); +/// +/// // item with q=0.3 is greater than item with q=0.1 +/// let q_item_fallback: QualityItem = "abc;q=0.1".parse().unwrap(); +/// assert!(q_item > q_item_fallback); +/// ``` #[derive(Debug, Clone, PartialEq, Eq)] pub struct QualityItem { /// The wrapped contents of the field. @@ -93,12 +44,22 @@ impl QualityItem { /// Constructs a new `QualityItem` from an item and a quality value. /// /// The item can be of any type. The quality should be a value in the range [0, 1]. - pub fn new(item: T, quality: Quality) -> QualityItem { + pub fn new(item: T, quality: Quality) -> Self { QualityItem { item, quality } } + + /// Constructs a new `QualityItem` from an item, using the maximum q-value. + pub fn max(item: T) -> Self { + Self::new(item, Quality::MAX) + } + + /// Constructs a new `QualityItem` from an item, using the minimum q-value. + pub fn min(item: T) -> Self { + Self::new(item, Quality::MIN) + } } -impl cmp::PartialOrd for QualityItem { +impl PartialOrd for QualityItem { fn partial_cmp(&self, other: &QualityItem) -> Option { self.quality.partial_cmp(&other.quality) } @@ -108,10 +69,12 @@ impl fmt::Display for QualityItem { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Display::fmt(&self.item, f)?; - match self.quality.0 { - MAX_QUALITY => Ok(()), - 0 => f.write_str("; q=0"), - x => write!(f, "; q=0.{}", format!("{:03}", x).trim_end_matches('0')), + match self.quality { + // q-factor value is implied for max value + Quality::MAX => Ok(()), + + Quality::MIN => f.write_str("; q=0"), + q => write!(f, "; q={}", q), } } } @@ -119,78 +82,58 @@ impl fmt::Display for QualityItem { impl str::FromStr for QualityItem { type Err = ParseError; - fn from_str(qitem_str: &str) -> Result { - if !qitem_str.is_ascii() { + fn from_str(q_item_str: &str) -> Result { + if !q_item_str.is_ascii() { return Err(ParseError::Header); } - // Set defaults used if parsing fails. - let mut raw_item = qitem_str; - let mut quality = 1f32; + // set defaults used if quality-item parsing fails, i.e., item has no q attribute + let mut raw_item = q_item_str; + let mut quality = Quality::MAX; - // TODO: MSRV(1.52): use rsplit_once - let parts: Vec<_> = qitem_str.rsplitn(2, ';').map(str::trim).collect(); + let parts = q_item_str + .rsplit_once(';') + .map(|(item, q_attr)| (item.trim(), q_attr.trim())); - if parts.len() == 2 { + if let Some((val, q_attr)) = parts { // example for item with q-factor: // - // gzip; q=0.65 - // ^^^^^^ parts[0] - // ^^ start - // ^^^^ q_val - // ^^^^ parts[1] + // gzip;q=0.65 + // ^^^^ val + // ^^^^^^ q_attr + // ^^ q + // ^^^^ q_val - if parts[0].len() < 2 { + if q_attr.len() < 2 { // Can't possibly be an attribute since an attribute needs at least a name followed // by an equals sign. And bare identifiers are forbidden. return Err(ParseError::Header); } - let start = &parts[0][0..2]; + let q = &q_attr[0..2]; - if start == "q=" || start == "Q=" { - let q_val = &parts[0][2..]; + if q == "q=" || q == "Q=" { + let q_val = &q_attr[2..]; if q_val.len() > 5 { // longer than 5 indicates an over-precise q-factor return Err(ParseError::Header); } let q_value = q_val.parse::().map_err(|_| ParseError::Header)?; + let q_value = + Quality::try_from(q_value).map_err(|_| ParseError::Header)?; - if (0f32..=1f32).contains(&q_value) { - quality = q_value; - raw_item = parts[1]; - } else { - return Err(ParseError::Header); - } + quality = q_value; + raw_item = val; } } let item = raw_item.parse::().map_err(|_| ParseError::Header)?; - // we already checked above that the quality is within range - Ok(QualityItem::new(item, Quality::from_f32(quality))) + Ok(QualityItem::new(item, quality)) } } -/// Convenience function to wrap a value in a `QualityItem` -/// Sets `q` to the default 1.0 -pub fn qitem(item: T) -> QualityItem { - QualityItem::new(item, Quality::default()) -} - -/// Convenience function to create a `Quality` from a float or integer. -/// -/// Implemented for `u16` and `f32`. Panics if value is out of range. -pub fn q(val: T) -> Quality -where - T: TryInto, - T::Error: fmt::Debug, -{ - // TODO: on next breaking change, handle unwrap differently - val.try_into().unwrap() -} - #[cfg(test)] mod tests { use super::*; @@ -245,7 +188,7 @@ mod tests { #[test] fn test_quality_item_fmt_q_1() { use Encoding::*; - let x = qitem(Chunked); + let x = QualityItem::max(Chunked); assert_eq!(format!("{}", x), "chunked"); } #[test] @@ -344,25 +287,8 @@ mod tests { fn test_quality_item_ordering() { let x: QualityItem = "gzip; q=0.5".parse().ok().unwrap(); let y: QualityItem = "gzip; q=0.273".parse().ok().unwrap(); - let comparision_result: bool = x.gt(&y); - assert!(comparision_result) - } - - #[test] - fn test_quality() { - assert_eq!(q(0.5), Quality(500)); - } - - #[test] - #[should_panic] - fn test_quality_invalid() { - q(-1.0); - } - - #[test] - #[should_panic] - fn test_quality_invalid2() { - q(2.0); + let comparison_result: bool = x.gt(&y); + assert!(comparison_result) } #[test] diff --git a/actix-http/src/header/utils.rs b/actix-http/src/header/utils.rs index a23f5b751..f4f34d347 100644 --- a/actix-http/src/header/utils.rs +++ b/actix-http/src/header/utils.rs @@ -65,8 +65,9 @@ where Ok(()) } -/// Percent encode a sequence of bytes with a character set defined in -/// +/// Percent encode a sequence of bytes with a character set defined in [RFC 5987 ยง3.2]. +/// +/// [RFC 5987 ยง3.2]: https://datatracker.ietf.org/doc/html/rfc5987#section-3.2 #[inline] pub fn http_percent_encode(f: &mut fmt::Formatter<'_>, bytes: &[u8]) -> fmt::Result { let encoded = percent_encoding::percent_encode(bytes, HTTP_VALUE); diff --git a/actix-multipart/src/server.rs b/actix-multipart/src/server.rs index 23397b7ee..8eabcee10 100644 --- a/actix-multipart/src/server.rs +++ b/actix-multipart/src/server.rs @@ -435,10 +435,10 @@ impl Field { /// Returns the field's Content-Disposition. /// - /// Per [RFC 7578 ยง4.2]: 'Each part MUST contain a Content-Disposition header field where the - /// disposition type is "form-data". The Content-Disposition header field MUST also contain an - /// additional parameter of "name"; the value of the "name" parameter is the original field name - /// from the form.' + /// Per [RFC 7578 ยง4.2]: "Each part MUST contain a Content-Disposition header field where the + /// disposition type is `form-data`. The Content-Disposition header field MUST also contain an + /// additional parameter of `name`; the value of the `name` parameter is the original field name + /// from the form." /// /// This crate validates that it exists before returning a `Field`. As such, it is safe to /// unwrap `.content_disposition().get_name()`. The [name](Self::name) method is provided as @@ -451,7 +451,8 @@ impl Field { /// Returns the field's name. /// - /// See [content_disposition] regarding guarantees about + /// See [content_disposition](Self::content_disposition) regarding guarantees about existence of + /// the name field. pub fn name(&self) -> &str { self.content_disposition() .get_name() diff --git a/src/http/header/accept.rs b/src/http/header/accept.rs index 70e4118cf..c61e6ab49 100644 --- a/src/http/header/accept.rs +++ b/src/http/header/accept.rs @@ -2,7 +2,7 @@ use std::cmp::Ordering; use mime::Mime; -use super::{qitem, QualityItem}; +use super::QualityItem; use crate::http::header; crate::http::header::common_header! { @@ -34,46 +34,40 @@ crate::http::header::common_header! { /// # Examples /// ``` /// use actix_web::HttpResponse; - /// use actix_web::http::header::{Accept, qitem}; + /// use actix_web::http::header::{Accept, QualityItem}; /// /// let mut builder = HttpResponse::Ok(); /// builder.insert_header( /// Accept(vec![ - /// qitem(mime::TEXT_HTML), + /// QualityItem::max(mime::TEXT_HTML), /// ]) /// ); /// ``` /// /// ``` /// use actix_web::HttpResponse; - /// use actix_web::http::header::{Accept, qitem}; + /// use actix_web::http::header::{Accept, QualityItem}; /// /// let mut builder = HttpResponse::Ok(); /// builder.insert_header( /// Accept(vec![ - /// qitem(mime::APPLICATION_JSON), + /// QualityItem::max(mime::APPLICATION_JSON), /// ]) /// ); /// ``` /// /// ``` /// use actix_web::HttpResponse; - /// use actix_web::http::header::{Accept, QualityItem, q, qitem}; + /// use actix_web::http::header::{Accept, QualityItem, q}; /// /// let mut builder = HttpResponse::Ok(); /// builder.insert_header( /// Accept(vec![ - /// qitem(mime::TEXT_HTML), - /// qitem("application/xhtml+xml".parse().unwrap()), - /// QualityItem::new( - /// mime::TEXT_XML, - /// q(900) - /// ), - /// qitem("image/webp".parse().unwrap()), - /// QualityItem::new( - /// mime::STAR_STAR, - /// q(800) - /// ), + /// QualityItem::max(mime::TEXT_HTML), + /// QualityItem::max("application/xhtml+xml".parse().unwrap()), + /// QualityItem::new(mime::TEXT_XML, q(0.9)), + /// QualityItem::max("image/webp".parse().unwrap()), + /// QualityItem::new(mime::STAR_STAR, q(0.8)), /// ]) /// ); /// ``` @@ -85,20 +79,20 @@ crate::http::header::common_header! { test1, vec![b"audio/*; q=0.2, audio/basic"], Some(Accept(vec![ - QualityItem::new("audio/*".parse().unwrap(), q(200)), - qitem("audio/basic".parse().unwrap()), + QualityItem::new("audio/*".parse().unwrap(), q(0.2)), + QualityItem::max("audio/basic".parse().unwrap()), ]))); crate::http::header::common_header_test!( test2, vec![b"text/plain; q=0.5, text/html, text/x-dvi; q=0.8, text/x-c"], Some(Accept(vec![ - QualityItem::new(mime::TEXT_PLAIN, q(500)), - qitem(mime::TEXT_HTML), + QualityItem::new(mime::TEXT_PLAIN, q(0.5)), + QualityItem::max(mime::TEXT_HTML), QualityItem::new( "text/x-dvi".parse().unwrap(), - q(800)), - qitem("text/x-c".parse().unwrap()), + q(0.8)), + QualityItem::max("text/x-c".parse().unwrap()), ]))); // Custom tests @@ -106,14 +100,14 @@ crate::http::header::common_header! { test3, vec![b"text/plain; charset=utf-8"], Some(Accept(vec![ - qitem(mime::TEXT_PLAIN_UTF_8), + QualityItem::max(mime::TEXT_PLAIN_UTF_8), ]))); crate::http::header::common_header_test!( test4, vec![b"text/plain; charset=utf-8; q=0.5"], Some(Accept(vec![ QualityItem::new(mime::TEXT_PLAIN_UTF_8, - q(500)), + q(0.5)), ]))); #[test] @@ -130,27 +124,27 @@ crate::http::header::common_header! { impl Accept { /// Construct `Accept: */*`. pub fn star() -> Accept { - Accept(vec![qitem(mime::STAR_STAR)]) + Accept(vec![QualityItem::max(mime::STAR_STAR)]) } /// Construct `Accept: application/json`. pub fn json() -> Accept { - Accept(vec![qitem(mime::APPLICATION_JSON)]) + Accept(vec![QualityItem::max(mime::APPLICATION_JSON)]) } /// Construct `Accept: text/*`. pub fn text() -> Accept { - Accept(vec![qitem(mime::TEXT_STAR)]) + Accept(vec![QualityItem::max(mime::TEXT_STAR)]) } /// Construct `Accept: image/*`. pub fn image() -> Accept { - Accept(vec![qitem(mime::IMAGE_STAR)]) + Accept(vec![QualityItem::max(mime::IMAGE_STAR)]) } /// Construct `Accept: text/html`. pub fn html() -> Accept { - Accept(vec![qitem(mime::TEXT_HTML)]) + Accept(vec![QualityItem::max(mime::TEXT_HTML)]) } /// Returns a sorted list of mime types from highest to lowest preference, accounting for @@ -213,10 +207,10 @@ impl Accept { /// /// [q-factor weighting]: https://datatracker.ietf.org/doc/html/rfc7231#section-5.3.2 pub fn preference(&self) -> Mime { - use actix_http::header::q; + use actix_http::header::Quality; let mut max_item = None; - let mut max_pref = q(0); + let mut max_pref = Quality::MIN; // uses manual max lookup loop since we want the first occurrence in the case of same // preference but `Iterator::max_by_key` would give us the last occurrence @@ -244,11 +238,11 @@ mod tests { let test = Accept(vec![]); assert!(test.ranked().is_empty()); - let test = Accept(vec![qitem(mime::APPLICATION_JSON)]); + let test = Accept(vec![QualityItem::max(mime::APPLICATION_JSON)]); assert_eq!(test.ranked(), vec!(mime::APPLICATION_JSON)); let test = Accept(vec![ - qitem(mime::TEXT_HTML), + QualityItem::max(mime::TEXT_HTML), "application/xhtml+xml".parse().unwrap(), QualityItem::new("application/xml".parse().unwrap(), q(0.9)), QualityItem::new(mime::STAR_STAR, q(0.8)), @@ -264,9 +258,9 @@ mod tests { ); let test = Accept(vec![ - qitem(mime::STAR_STAR), - qitem(mime::IMAGE_STAR), - qitem(mime::IMAGE_PNG), + QualityItem::max(mime::STAR_STAR), + QualityItem::max(mime::IMAGE_STAR), + QualityItem::max(mime::IMAGE_PNG), ]); assert_eq!( test.ranked(), @@ -277,7 +271,7 @@ mod tests { #[test] fn preference_selection() { let test = Accept(vec![ - qitem(mime::TEXT_HTML), + QualityItem::max(mime::TEXT_HTML), "application/xhtml+xml".parse().unwrap(), QualityItem::new("application/xml".parse().unwrap(), q(0.9)), QualityItem::new(mime::STAR_STAR, q(0.8)), @@ -286,9 +280,9 @@ mod tests { let test = Accept(vec![ QualityItem::new("video/*".parse().unwrap(), q(0.8)), - qitem(mime::IMAGE_PNG), + QualityItem::max(mime::IMAGE_PNG), QualityItem::new(mime::STAR_STAR, q(0.5)), - qitem(mime::IMAGE_SVG), + QualityItem::max(mime::IMAGE_SVG), QualityItem::new(mime::IMAGE_STAR, q(0.8)), ]); assert_eq!(test.preference(), mime::IMAGE_PNG); diff --git a/src/http/header/accept_charset.rs b/src/http/header/accept_charset.rs index 5577ab604..c8b918c91 100644 --- a/src/http/header/accept_charset.rs +++ b/src/http/header/accept_charset.rs @@ -22,11 +22,11 @@ crate::http::header::common_header! { /// # Examples /// ``` /// use actix_web::HttpResponse; - /// use actix_web::http::header::{AcceptCharset, Charset, qitem}; + /// use actix_web::http::header::{AcceptCharset, Charset, QualityItem}; /// /// let mut builder = HttpResponse::Ok(); /// builder.insert_header( - /// AcceptCharset(vec![qitem(Charset::Us_Ascii)]) + /// AcceptCharset(vec![QualityItem::max(Charset::Us_Ascii)]) /// ); /// ``` /// @@ -37,19 +37,19 @@ crate::http::header::common_header! { /// let mut builder = HttpResponse::Ok(); /// builder.insert_header( /// AcceptCharset(vec![ - /// QualityItem::new(Charset::Us_Ascii, q(900)), - /// QualityItem::new(Charset::Iso_8859_10, q(200)), + /// QualityItem::new(Charset::Us_Ascii, q(0.9)), + /// QualityItem::new(Charset::Iso_8859_10, q(0.2)), /// ]) /// ); /// ``` /// /// ``` /// use actix_web::HttpResponse; - /// use actix_web::http::header::{AcceptCharset, Charset, qitem}; + /// use actix_web::http::header::{AcceptCharset, Charset, QualityItem}; /// /// let mut builder = HttpResponse::Ok(); /// builder.insert_header( - /// AcceptCharset(vec![qitem(Charset::Ext("utf-8".to_owned()))]) + /// AcceptCharset(vec![QualityItem::max(Charset::Ext("utf-8".to_owned()))]) /// ); /// ``` (AcceptCharset, ACCEPT_CHARSET) => (QualityItem)+ diff --git a/src/http/header/accept_encoding.rs b/src/http/header/accept_encoding.rs index 85cd0a4f7..828a0533c 100644 --- a/src/http/header/accept_encoding.rs +++ b/src/http/header/accept_encoding.rs @@ -29,36 +29,38 @@ common_header! { /// # Examples /// ``` /// use actix_web::HttpResponse; - /// use actix_web::http::header::{AcceptEncoding, Encoding, qitem}; + /// use actix_web::http::header::{AcceptEncoding, Encoding, QualityItem}; /// /// let mut builder = HttpResponse::Ok(); /// builder.insert_header( - /// AcceptEncoding(vec![qitem(Encoding::Chunked)]) + /// AcceptEncoding(vec![QualityItem::max(Encoding::Chunked)]) /// ); /// ``` + /// /// ``` /// use actix_web::HttpResponse; - /// use actix_web::http::header::{AcceptEncoding, Encoding, qitem}; + /// use actix_web::http::header::{AcceptEncoding, Encoding, QualityItem}; /// /// let mut builder = HttpResponse::Ok(); /// builder.insert_header( /// AcceptEncoding(vec![ - /// qitem(Encoding::Chunked), - /// qitem(Encoding::Gzip), - /// qitem(Encoding::Deflate), + /// QualityItem::max(Encoding::Chunked), + /// QualityItem::max(Encoding::Gzip), + /// QualityItem::max(Encoding::Deflate), /// ]) /// ); /// ``` + /// /// ``` /// use actix_web::HttpResponse; - /// use actix_web::http::header::{AcceptEncoding, Encoding, QualityItem, q, qitem}; + /// use actix_web::http::header::{AcceptEncoding, Encoding, QualityItem, q}; /// /// let mut builder = HttpResponse::Ok(); /// builder.insert_header( /// AcceptEncoding(vec![ - /// qitem(Encoding::Chunked), - /// QualityItem::new(Encoding::Gzip, q(600)), - /// QualityItem::new(Encoding::EncodingExt("*".to_owned()), q(0)), + /// QualityItem::max(Encoding::Chunked), + /// QualityItem::new(Encoding::Gzip, q(0.60)), + /// QualityItem::min(Encoding::EncodingExt("*".to_owned())), /// ]) /// ); /// ``` @@ -77,3 +79,5 @@ common_header! { common_header_test!(test5, vec![b"gzip, identity; q=0.5, *;q=0"]); } } + +// TODO: shortcut for EncodingExt(*) = Any diff --git a/src/http/header/accept_language.rs b/src/http/header/accept_language.rs index 229f95ef1..011257b87 100644 --- a/src/http/header/accept_language.rs +++ b/src/http/header/accept_language.rs @@ -1,6 +1,6 @@ use language_tags::LanguageTag; -use super::{common_header, Preference, QualityItem}; +use super::{common_header, Preference, Quality, QualityItem}; use crate::http::header; common_header! { @@ -32,26 +32,26 @@ common_header! { /// # Examples /// ``` /// use actix_web::HttpResponse; - /// use actix_web::http::header::{AcceptLanguage, qitem}; + /// use actix_web::http::header::{AcceptLanguage, QualityItem}; /// /// let mut builder = HttpResponse::Ok(); /// builder.insert_header( /// AcceptLanguage(vec![ - /// qitem("en-US".parse().unwrap()) + /// QualityItem::max("en-US".parse().unwrap()) /// ]) /// ); /// ``` /// /// ``` /// use actix_web::HttpResponse; - /// use actix_web::http::header::{AcceptLanguage, QualityItem, q, qitem}; + /// use actix_web::http::header::{AcceptLanguage, QualityItem, q}; /// /// let mut builder = HttpResponse::Ok(); /// builder.insert_header( /// AcceptLanguage(vec![ - /// qitem("da".parse().unwrap()), - /// QualityItem::new("en-GB".parse().unwrap(), q(800)), - /// QualityItem::new("en".parse().unwrap(), q(700)), + /// QualityItem::max("da".parse().unwrap()), + /// QualityItem::new("en-GB".parse().unwrap(), q(0.8)), + /// QualityItem::new("en".parse().unwrap(), q(0.7)), /// ]) /// ); /// ``` @@ -72,9 +72,9 @@ common_header! { not_ordered_by_weight, vec![b"en-US, en; q=0.5, fr"], Some(AcceptLanguage(vec![ - qitem("en-US".parse().unwrap()), - QualityItem::new("en".parse().unwrap(), q(500)), - qitem("fr".parse().unwrap()), + QualityItem::max("en-US".parse().unwrap()), + QualityItem::new("en".parse().unwrap(), q(0.5)), + QualityItem::max("fr".parse().unwrap()), ])) ); @@ -82,11 +82,11 @@ common_header! { has_wildcard, vec![b"fr-CH, fr; q=0.9, en; q=0.8, de; q=0.7, *; q=0.5"], Some(AcceptLanguage(vec![ - qitem("fr-CH".parse().unwrap()), - QualityItem::new("fr".parse().unwrap(), q(900)), - QualityItem::new("en".parse().unwrap(), q(800)), - QualityItem::new("de".parse().unwrap(), q(700)), - QualityItem::new("*".parse().unwrap(), q(500)), + QualityItem::max("fr-CH".parse().unwrap()), + QualityItem::new("fr".parse().unwrap(), q(0.9)), + QualityItem::new("en".parse().unwrap(), q(0.8)), + QualityItem::new("de".parse().unwrap(), q(0.7)), + QualityItem::new("*".parse().unwrap(), q(0.5)), ])) ); } @@ -122,10 +122,8 @@ impl AcceptLanguage { /// /// [q-factor weighting]: https://datatracker.ietf.org/doc/html/rfc7231#section-5.3.2 pub fn preference(&self) -> Preference { - use actix_http::header::q; - let mut max_item = None; - let mut max_pref = q(0); + let mut max_pref = Quality::MIN; // uses manual max lookup loop since we want the first occurrence in the case of same // preference but `Iterator::max_by_key` would give us the last occurrence @@ -153,15 +151,15 @@ mod tests { let test = AcceptLanguage(vec![]); assert!(test.ranked().is_empty()); - let test = AcceptLanguage(vec![qitem("fr-CH".parse().unwrap())]); + let test = AcceptLanguage(vec![QualityItem::max("fr-CH".parse().unwrap())]); assert_eq!(test.ranked(), vec!("fr-CH".parse().unwrap())); let test = AcceptLanguage(vec![ - QualityItem::new("fr".parse().unwrap(), q(900)), - QualityItem::new("fr-CH".parse().unwrap(), q(1000)), - QualityItem::new("en".parse().unwrap(), q(800)), - QualityItem::new("*".parse().unwrap(), q(500)), - QualityItem::new("de".parse().unwrap(), q(700)), + QualityItem::new("fr".parse().unwrap(), q(0.900)), + QualityItem::new("fr-CH".parse().unwrap(), q(1.0)), + QualityItem::new("en".parse().unwrap(), q(0.800)), + QualityItem::new("*".parse().unwrap(), q(0.500)), + QualityItem::new("de".parse().unwrap(), q(0.700)), ]); assert_eq!( test.ranked(), @@ -175,11 +173,11 @@ mod tests { ); let test = AcceptLanguage(vec![ - qitem("fr".parse().unwrap()), - qitem("fr-CH".parse().unwrap()), - qitem("en".parse().unwrap()), - qitem("*".parse().unwrap()), - qitem("de".parse().unwrap()), + QualityItem::max("fr".parse().unwrap()), + QualityItem::max("fr-CH".parse().unwrap()), + QualityItem::max("en".parse().unwrap()), + QualityItem::max("*".parse().unwrap()), + QualityItem::max("de".parse().unwrap()), ]); assert_eq!( test.ranked(), @@ -196,11 +194,11 @@ mod tests { #[test] fn preference_selection() { let test = AcceptLanguage(vec![ - QualityItem::new("fr".parse().unwrap(), q(900)), - QualityItem::new("fr-CH".parse().unwrap(), q(1000)), - QualityItem::new("en".parse().unwrap(), q(800)), - QualityItem::new("*".parse().unwrap(), q(500)), - QualityItem::new("de".parse().unwrap(), q(700)), + QualityItem::new("fr".parse().unwrap(), q(0.900)), + QualityItem::new("fr-CH".parse().unwrap(), q(1.0)), + QualityItem::new("en".parse().unwrap(), q(0.800)), + QualityItem::new("*".parse().unwrap(), q(0.500)), + QualityItem::new("de".parse().unwrap(), q(0.700)), ]); assert_eq!( test.preference(), @@ -208,11 +206,11 @@ mod tests { ); let test = AcceptLanguage(vec![ - qitem("fr".parse().unwrap()), - qitem("fr-CH".parse().unwrap()), - qitem("en".parse().unwrap()), - qitem("*".parse().unwrap()), - qitem("de".parse().unwrap()), + QualityItem::max("fr".parse().unwrap()), + QualityItem::max("fr-CH".parse().unwrap()), + QualityItem::max("en".parse().unwrap()), + QualityItem::max("*".parse().unwrap()), + QualityItem::max("de".parse().unwrap()), ]); assert_eq!( test.preference(), diff --git a/src/http/header/content_language.rs b/src/http/header/content_language.rs index 39ca8da56..ff317e1de 100644 --- a/src/http/header/content_language.rs +++ b/src/http/header/content_language.rs @@ -23,25 +23,25 @@ common_header! { /// # Examples /// ``` /// use actix_web::HttpResponse; - /// use actix_web::http::header::{ContentLanguage, LanguageTag, qitem}; + /// use actix_web::http::header::{ContentLanguage, LanguageTag, QualityItem}; /// /// let mut builder = HttpResponse::Ok(); /// builder.insert_header( /// ContentLanguage(vec![ - /// qitem(LanguageTag::parse("en").unwrap()), + /// QualityItem::max(LanguageTag::parse("en").unwrap()), /// ]) /// ); /// ``` /// /// ``` /// use actix_web::HttpResponse; - /// use actix_web::http::header::{ContentLanguage, LanguageTag, qitem}; + /// use actix_web::http::header::{ContentLanguage, LanguageTag, QualityItem}; /// /// let mut builder = HttpResponse::Ok(); /// builder.insert_header( /// ContentLanguage(vec![ - /// qitem(LanguageTag::parse("da").unwrap()), - /// qitem(LanguageTag::parse("en-GB").unwrap()), + /// QualityItem::max(LanguageTag::parse("da").unwrap()), + /// QualityItem::max(LanguageTag::parse("en-GB").unwrap()), /// ]) /// ); /// ``` From 59be0c65c6b4766355d8bf08de27531d03730bdd Mon Sep 17 00:00:00 2001 From: Ali MJ Al-Nasrawy Date: Sun, 5 Dec 2021 07:39:18 +0300 Subject: [PATCH 303/428] disallow query or fragements in `url_for` constructions (#2430) Co-authored-by: Rob Ede --- CHANGES.md | 2 ++ src/error/mod.rs | 8 +++--- src/request.rs | 46 +++++++++++++++++------------ src/rmap.rs | 75 +++++++++++++++++++++++++++++++++++++++--------- 4 files changed, 96 insertions(+), 35 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 78aa729df..1b108fee6 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -12,12 +12,14 @@ * Rename `Accept::{mime_precedence => ranked}`. [#2480] * Rename `Accept::{mime_preference => preference}`. [#2480] * Un-deprecate `App::data_factory`. [#2484] +* `HttpRequest::url_for` no longer constructs URLs with query or fragment components. [#2430] ### Fixed * Accept wildcard `*` items in `AcceptLanguage`. [#2480] * Re-exports `dev::{BodySize, MessageBody, SizedStream}`. They are exposed through the `body` module. [#2468] * Typed headers containing lists that require one or more items now enforce this minimum. [#2482] +[#2430]: https://github.com/actix/actix-web/pull/2430 [#2468]: https://github.com/actix/actix-web/pull/2468 [#2480]: https://github.com/actix/actix-web/pull/2480 [#2482]: https://github.com/actix/actix-web/pull/2482 diff --git a/src/error/mod.rs b/src/error/mod.rs index 3ccd5bba6..46d0dccc6 100644 --- a/src/error/mod.rs +++ b/src/error/mod.rs @@ -29,15 +29,15 @@ pub type Result = std::result::Result; #[derive(Debug, PartialEq, Display, Error, From)] #[non_exhaustive] pub enum UrlGenerationError { - /// Resource not found + /// Resource not found. #[display(fmt = "Resource not found")] ResourceNotFound, - /// Not all path pattern covered - #[display(fmt = "Not all path pattern covered")] + /// Not all URL parameters covered. + #[display(fmt = "Not all URL parameters covered")] NotEnoughElements, - /// URL parse error + /// URL parse error. #[display(fmt = "{}", _0)] ParseError(UrlParseError), } diff --git a/src/request.rs b/src/request.rs index 0027f9b4b..58222da47 100644 --- a/src/request.rs +++ b/src/request.rs @@ -100,7 +100,7 @@ impl HttpRequest { &self.head().headers } - /// The target path of this Request. + /// The target path of this request. #[inline] pub fn path(&self) -> &str { self.head().uri.path() @@ -108,18 +108,22 @@ impl HttpRequest { /// The query string in the URL. /// - /// E.g., id=10 + /// Example: `id=10` #[inline] pub fn query_string(&self) -> &str { self.uri().query().unwrap_or_default() } - /// Get a reference to the Path parameters. + /// Returns a reference to the URL parameters container. /// - /// Params is a container for url parameters. - /// A variable segment is specified in the form `{identifier}`, - /// where the identifier can be used later in a request handler to - /// access the matched value for that segment. + /// A url parameter is specified in the form `{identifier}`, where the identifier can be used + /// later in a request handler to access the matched value for that parameter. + /// + /// # Percent Encoding and URL Parameters + /// Because each URL parameter is able to capture multiple path segments, both `["%2F", "%25"]` + /// found in the request URI are not decoded into `["/", "%"]` in order to preserve path + /// segment boundaries. If a url parameter is expected to contain these characters, then it is + /// on the user to decode them. #[inline] pub fn match_info(&self) -> &Path { &self.inner.path @@ -161,23 +165,29 @@ impl HttpRequest { self.head().extensions_mut() } - /// Generate url for named resource + /// Generates URL for a named resource. /// + /// This substitutes in sequence all URL parameters that appear in the resource itself and in + /// parent [scopes](crate::web::scope), if any. + /// + /// It is worth noting that the characters `['/', '%']` are not escaped and therefore a single + /// URL parameter may expand into multiple path segments and `elements` can be percent-encoded + /// beforehand without worrying about double encoding. Any other character that is not valid in + /// a URL path context is escaped using percent-encoding. + /// + /// # Examples /// ``` /// # use actix_web::{web, App, HttpRequest, HttpResponse}; - /// # /// fn index(req: HttpRequest) -> HttpResponse { - /// let url = req.url_for("foo", &["1", "2", "3"]); // <- generate url for "foo" resource + /// let url = req.url_for("foo", &["1", "2", "3"]); // <- generate URL for "foo" resource /// HttpResponse::Ok().into() /// } /// - /// fn main() { - /// let app = App::new() - /// .service(web::resource("/test/{one}/{two}/{three}") - /// .name("foo") // <- set resource name, then it could be used in `url_for` - /// .route(web::get().to(|| HttpResponse::Ok())) - /// ); - /// } + /// let app = App::new() + /// .service(web::resource("/test/{one}/{two}/{three}") + /// .name("foo") // <- set resource name so it can be used in `url_for` + /// .route(web::get().to(|| HttpResponse::Ok())) + /// ); /// ``` pub fn url_for(&self, name: &str, elements: U) -> Result where @@ -196,8 +206,8 @@ impl HttpRequest { self.url_for(name, &NO_PARAMS) } - #[inline] /// Get a reference to a `ResourceMap` of current application. + #[inline] pub fn resource_map(&self) -> &ResourceMap { self.app_state().rmap() } diff --git a/src/rmap.rs b/src/rmap.rs index 8466eda28..432eaf83c 100644 --- a/src/rmap.rs +++ b/src/rmap.rs @@ -1,12 +1,14 @@ -use std::cell::RefCell; -use std::rc::{Rc, Weak}; +use std::{ + borrow::Cow, + cell::RefCell, + rc::{Rc, Weak}, +}; use actix_router::ResourceDef; use ahash::AHashMap; use url::Url; -use crate::error::UrlGenerationError; -use crate::request::HttpRequest; +use crate::{error::UrlGenerationError, request::HttpRequest}; #[derive(Clone, Debug)] pub struct ResourceMap { @@ -102,17 +104,28 @@ impl ResourceMap { }) .ok_or(UrlGenerationError::NotEnoughElements)?; - if path.starts_with('/') { + let (base, path): (Cow<'_, _>, _) = if path.starts_with('/') { + // build full URL from connection info parts and resource path let conn = req.connection_info(); - Ok(Url::parse(&format!( - "{}://{}{}", - conn.scheme(), - conn.host(), - path - ))?) + let base = format!("{}://{}", conn.scheme(), conn.host()); + (Cow::Owned(base), path.as_str()) } else { - Ok(Url::parse(&path)?) - } + // external resource; third slash would be the root slash in the path + let third_slash_index = path + .char_indices() + .filter_map(|(i, c)| (c == '/').then(|| i)) + .nth(2) + .unwrap_or_else(|| path.len()); + + ( + Cow::Borrowed(&path[..third_slash_index]), + &path[third_slash_index..], + ) + }; + + let mut url = Url::parse(&base)?; + url.set_path(path); + Ok(url) } pub fn has_resource(&self, path: &str) -> bool { @@ -406,6 +419,42 @@ mod tests { assert!(rmap.url_for(&req, "missing", &["u123"]).is_err()); } + #[test] + fn url_for_parser() { + let mut root = ResourceMap::new(ResourceDef::prefix("")); + + let mut rdef_1 = ResourceDef::new("/{var}"); + rdef_1.set_name("internal"); + + let mut rdef_2 = ResourceDef::new("http://host.dom/{var}"); + rdef_2.set_name("external.1"); + + let mut rdef_3 = ResourceDef::new("{var}"); + rdef_3.set_name("external.2"); + + root.add(&mut rdef_1, None); + root.add(&mut rdef_2, None); + root.add(&mut rdef_3, None); + let rmap = Rc::new(root); + ResourceMap::finish(&rmap); + + let mut req = crate::test::TestRequest::default(); + req.set_server_hostname("localhost:8888"); + let req = req.to_http_request(); + + const INPUT: &[&str] = &["a/../quick brown%20fox/%nan?query#frag"]; + const OUTPUT: &str = "/quick%20brown%20fox/%nan%3Fquery%23frag"; + + let url = rmap.url_for(&req, "internal", INPUT).unwrap(); + assert_eq!(url.path(), OUTPUT); + + let url = rmap.url_for(&req, "external.1", INPUT).unwrap(); + assert_eq!(url.path(), OUTPUT); + + assert!(rmap.url_for(&req, "external.2", INPUT).is_err()); + assert!(rmap.url_for(&req, "external.2", &[""]).is_err()); + } + #[test] fn external_resource_with_no_name() { let mut root = ResourceMap::new(ResourceDef::prefix("")); From 2d053b703616e19764f4c5c735f282dc0aaaafea Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sun, 5 Dec 2021 14:37:20 +0000 Subject: [PATCH 304/428] remove `actix_http::http` module (#2488) --- actix-files/src/named.rs | 5 ++-- actix-http-test/src/lib.rs | 3 ++- actix-http/CHANGES.md | 6 +++-- actix-http/src/encoding/decoder.rs | 2 +- actix-http/src/encoding/encoder.rs | 9 +++---- actix-http/src/h1/decoder.rs | 2 +- actix-http/src/h1/dispatcher.rs | 3 +-- actix-http/src/h1/encoder.rs | 6 +++-- actix-http/src/header/map.rs | 42 +++++++++++++++--------------- actix-http/src/lib.rs | 20 -------------- actix-http/src/response.rs | 7 +++-- actix-http/src/response_builder.rs | 16 +++++------- actix-http/tests/test_client.rs | 2 +- actix-http/tests/test_openssl.rs | 7 ++--- actix-http/tests/test_rustls.rs | 7 ++--- actix-http/tests/test_server.rs | 5 ++-- actix-test/src/lib.rs | 5 +--- actix-web-actors/src/ws.rs | 10 +++---- awc/src/builder.rs | 6 ++++- awc/src/client/error.rs | 2 +- awc/src/client/h1proto.rs | 7 ++--- awc/src/error.rs | 5 ++-- awc/src/frozen.rs | 5 ++-- awc/src/lib.rs | 8 +++--- awc/src/middleware/redirect.rs | 11 +++----- awc/src/request.rs | 10 +++---- awc/src/response.rs | 5 ++-- awc/src/sender.rs | 8 +++--- awc/src/test.rs | 9 +++---- awc/src/ws.rs | 21 ++++++++------- awc/tests/test_client.rs | 17 ++++-------- src/app.rs | 9 ++++--- src/extract.rs | 4 +-- src/guard.rs | 36 ++++++++++++------------- src/http/header/allow.rs | 2 +- src/http/header/macros.rs | 2 +- src/http/header/mod.rs | 2 +- src/http/mod.rs | 5 +++- src/middleware/compress.rs | 4 +-- src/middleware/condition.rs | 5 +++- src/middleware/default_headers.rs | 6 ++--- src/middleware/err_handlers.rs | 12 ++++++--- src/middleware/logger.rs | 5 ++-- src/middleware/normalize.rs | 2 +- src/request.rs | 6 ++--- src/resource.rs | 7 +++-- src/responder.rs | 10 +++++-- src/response/builder.rs | 10 +++---- src/response/http_codes.rs | 2 +- src/response/response.rs | 8 +++--- src/route.rs | 2 +- src/scope.rs | 7 +++-- src/service.rs | 5 ++-- src/test.rs | 7 +++-- src/types/query.rs | 6 ++--- src/web.rs | 2 +- tests/test_server.rs | 6 ++--- 57 files changed, 209 insertions(+), 234 deletions(-) diff --git a/actix-files/src/named.rs b/actix-files/src/named.rs index 89775c6b3..0848543a8 100644 --- a/actix-files/src/named.rs +++ b/actix-files/src/named.rs @@ -19,9 +19,10 @@ use actix_web::{ }, http::{ header::{ - self, Charset, ContentDisposition, DispositionParam, DispositionType, ExtendedValue, + self, Charset, ContentDisposition, ContentEncoding, DispositionParam, + DispositionType, ExtendedValue, }, - ContentEncoding, StatusCode, + StatusCode, }, Error, HttpMessage, HttpRequest, HttpResponse, Responder, }; diff --git a/actix-http-test/src/lib.rs b/actix-http-test/src/lib.rs index 7f55a0bf4..ff86e565a 100644 --- a/actix-http-test/src/lib.rs +++ b/actix-http-test/src/lib.rs @@ -13,7 +13,8 @@ use actix_codec::{AsyncRead, AsyncWrite, Framed}; use actix_rt::{net::TcpStream, System}; use actix_server::{Server, ServiceFactory}; use awc::{ - error::PayloadError, http::HeaderMap, ws, Client, ClientRequest, ClientResponse, Connector, + error::PayloadError, http::header::HeaderMap, ws, Client, ClientRequest, ClientResponse, + Connector, }; use bytes::Bytes; use futures_core::stream::Stream; diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 877380581..1a59b233a 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -30,13 +30,15 @@ * Remove unnecessary `MessageBody` bound on types passed to `body::AnyBody::new`. [#2468] * Move `body::AnyBody` to `awc`. Replaced with `EitherBody` and `BoxBody`. [#2468] * `impl Copy` for `ws::Codec`. [#1920] -* `header::qitem` helper. Replaced with `header::QualityItem::max` [#2486] -* `impl TryFrom` for `header::Quality` [#2486] +* `header::qitem` helper. Replaced with `header::QualityItem::max`. [#2486] +* `impl TryFrom` for `header::Quality`. [#2486] +* `http` module. Most everything it contained is exported at the crate root. [#2488] [#2483]: https://github.com/actix/actix-web/pull/2483 [#2468]: https://github.com/actix/actix-web/pull/2468 [#1920]: https://github.com/actix/actix-web/pull/1920 [#2486]: https://github.com/actix/actix-web/pull/2486 +[#2488]: https://github.com/actix/actix-web/pull/2488 ## 3.0.0-beta.14 - 2021-11-30 diff --git a/actix-http/src/encoding/decoder.rs b/actix-http/src/encoding/decoder.rs index c32983fc7..afe4c6e13 100644 --- a/actix-http/src/encoding/decoder.rs +++ b/actix-http/src/encoding/decoder.rs @@ -23,7 +23,7 @@ use zstd::stream::write::Decoder as ZstdDecoder; use crate::{ encoding::Writer, error::{BlockingError, PayloadError}, - http::header::{ContentEncoding, HeaderMap, CONTENT_ENCODING}, + header::{ContentEncoding, HeaderMap, CONTENT_ENCODING}, }; const MAX_CHUNK_SIZE_DECODE_IN_PLACE: usize = 2049; diff --git a/actix-http/src/encoding/encoder.rs b/actix-http/src/encoding/encoder.rs index 49e5663dc..350e7f062 100644 --- a/actix-http/src/encoding/encoder.rs +++ b/actix-http/src/encoding/encoder.rs @@ -27,11 +27,8 @@ use super::Writer; use crate::{ body::{BodySize, MessageBody}, error::BlockingError, - http::{ - header::{ContentEncoding, CONTENT_ENCODING}, - HeaderValue, StatusCode, - }, - ResponseHead, + header::{self, ContentEncoding, HeaderValue, CONTENT_ENCODING}, + ResponseHead, StatusCode, }; const MAX_CHUNK_SIZE_ENCODE_IN_PLACE: usize = 1024; @@ -222,7 +219,7 @@ where fn update_head(encoding: ContentEncoding, head: &mut ResponseHead) { head.headers_mut().insert( - CONTENT_ENCODING, + header::CONTENT_ENCODING, HeaderValue::from_static(encoding.as_str()), ); } diff --git a/actix-http/src/h1/decoder.rs b/actix-http/src/h1/decoder.rs index f25c35a76..a4db19669 100644 --- a/actix-http/src/h1/decoder.rs +++ b/actix-http/src/h1/decoder.rs @@ -511,7 +511,7 @@ mod tests { use super::*; use crate::{ error::ParseError, - http::header::{HeaderName, SET_COOKIE}, + header::{HeaderName, SET_COOKIE}, HttpMessage as _, }; diff --git a/actix-http/src/h1/dispatcher.rs b/actix-http/src/h1/dispatcher.rs index 6695d1bf3..3c36e7367 100644 --- a/actix-http/src/h1/dispatcher.rs +++ b/actix-http/src/h1/dispatcher.rs @@ -1037,9 +1037,8 @@ mod tests { use crate::{ error::Error, h1::{ExpectHandler, UpgradeHandler}, - http::Method, test::{TestBuffer, TestSeqBuffer}, - HttpMessage, KeepAlive, + HttpMessage, KeepAlive, Method, }; fn find_slice(haystack: &[u8], needle: &[u8], from: usize) -> Option { diff --git a/actix-http/src/h1/encoder.rs b/actix-http/src/h1/encoder.rs index 60880cd7d..fccd5da46 100644 --- a/actix-http/src/h1/encoder.rs +++ b/actix-http/src/h1/encoder.rs @@ -531,8 +531,10 @@ mod tests { use http::header::AUTHORIZATION; use super::*; - use crate::http::header::{HeaderValue, CONTENT_TYPE}; - use crate::RequestHead; + use crate::{ + header::{HeaderValue, CONTENT_TYPE}, + RequestHead, + }; #[test] fn test_chunked_te() { diff --git a/actix-http/src/header/map.rs b/actix-http/src/header/map.rs index dd852b021..7b18be991 100644 --- a/actix-http/src/header/map.rs +++ b/actix-http/src/header/map.rs @@ -14,7 +14,7 @@ use crate::header::AsHeaderName; /// /// # Examples /// ``` -/// use actix_http::http::{header, HeaderMap, HeaderValue}; +/// use actix_http::header::{self, HeaderMap, HeaderValue}; /// /// let mut map = HeaderMap::new(); /// @@ -75,7 +75,7 @@ impl HeaderMap { /// /// # Examples /// ``` - /// # use actix_http::http::HeaderMap; + /// # use actix_http::header::HeaderMap; /// let map = HeaderMap::new(); /// /// assert!(map.is_empty()); @@ -92,7 +92,7 @@ impl HeaderMap { /// /// # Examples /// ``` - /// # use actix_http::http::HeaderMap; + /// # use actix_http::header::HeaderMap; /// let map = HeaderMap::with_capacity(16); /// /// assert!(map.is_empty()); @@ -139,7 +139,7 @@ impl HeaderMap { /// /// # Examples /// ``` - /// # use actix_http::http::{header, HeaderMap, HeaderValue}; + /// # use actix_http::header::{self, HeaderMap, HeaderValue}; /// let mut map = HeaderMap::new(); /// assert_eq!(map.len(), 0); /// @@ -162,7 +162,7 @@ impl HeaderMap { /// /// # Examples /// ``` - /// # use actix_http::http::{header, HeaderMap, HeaderValue}; + /// # use actix_http::header::{self, HeaderMap, HeaderValue}; /// let mut map = HeaderMap::new(); /// assert_eq!(map.len_keys(), 0); /// @@ -181,7 +181,7 @@ impl HeaderMap { /// /// # Examples /// ``` - /// # use actix_http::http::{header, HeaderMap, HeaderValue}; + /// # use actix_http::header::{self, HeaderMap, HeaderValue}; /// let mut map = HeaderMap::new(); /// assert!(map.is_empty()); /// @@ -198,7 +198,7 @@ impl HeaderMap { /// /// # Examples /// ``` - /// # use actix_http::http::{header, HeaderMap, HeaderValue}; + /// # use actix_http::header::{self, HeaderMap, HeaderValue}; /// let mut map = HeaderMap::new(); /// /// map.insert(header::ACCEPT, HeaderValue::from_static("text/plain")); @@ -231,7 +231,7 @@ impl HeaderMap { /// /// # Examples /// ``` - /// # use actix_http::http::{header, HeaderMap, HeaderValue}; + /// # use actix_http::header::{self, HeaderMap, HeaderValue}; /// let mut map = HeaderMap::new(); /// /// map.insert(header::SET_COOKIE, HeaderValue::from_static("one=1")); @@ -264,7 +264,7 @@ impl HeaderMap { /// /// # Examples /// ``` - /// # use actix_http::http::{header, HeaderMap, HeaderValue}; + /// # use actix_http::header::{self, HeaderMap, HeaderValue}; /// let mut map = HeaderMap::new(); /// /// map.insert(header::SET_COOKIE, HeaderValue::from_static("one=1")); @@ -293,7 +293,7 @@ impl HeaderMap { /// /// # Examples /// ``` - /// # use actix_http::http::{header, HeaderMap, HeaderValue}; + /// # use actix_http::header::{self, HeaderMap, HeaderValue}; /// let mut map = HeaderMap::new(); /// /// let mut none_iter = map.get_all(header::ORIGIN); @@ -319,7 +319,7 @@ impl HeaderMap { /// /// # Examples /// ``` - /// # use actix_http::http::{header, HeaderMap, HeaderValue}; + /// # use actix_http::header::{self, HeaderMap, HeaderValue}; /// let mut map = HeaderMap::new(); /// assert!(!map.contains_key(header::ACCEPT)); /// @@ -342,7 +342,7 @@ impl HeaderMap { /// /// # Examples /// ``` - /// # use actix_http::http::{header, HeaderMap, HeaderValue}; + /// # use actix_http::header::{self, HeaderMap, HeaderValue}; /// let mut map = HeaderMap::new(); /// /// map.insert(header::ACCEPT, HeaderValue::from_static("text/plain")); @@ -359,7 +359,7 @@ impl HeaderMap { /// A convenience method is provided on the returned iterator to check if the insertion replaced /// any values. /// ``` - /// # use actix_http::http::{header, HeaderMap, HeaderValue}; + /// # use actix_http::header::{self, HeaderMap, HeaderValue}; /// let mut map = HeaderMap::new(); /// /// let removed = map.insert(header::ACCEPT, HeaderValue::from_static("text/plain")); @@ -381,7 +381,7 @@ impl HeaderMap { /// /// # Examples /// ``` - /// # use actix_http::http::{header, HeaderMap, HeaderValue}; + /// # use actix_http::header::{self, HeaderMap, HeaderValue}; /// let mut map = HeaderMap::new(); /// /// map.append(header::HOST, HeaderValue::from_static("example.com")); @@ -411,7 +411,7 @@ impl HeaderMap { /// /// # Examples /// ``` - /// # use actix_http::http::{header, HeaderMap, HeaderValue}; + /// # use actix_http::header::{self, HeaderMap, HeaderValue}; /// let mut map = HeaderMap::new(); /// /// map.append(header::SET_COOKIE, HeaderValue::from_static("one=1")); @@ -430,7 +430,7 @@ impl HeaderMap { /// A convenience method is provided on the returned iterator to check if the `remove` call /// actually removed any values. /// ``` - /// # use actix_http::http::{header, HeaderMap, HeaderValue}; + /// # use actix_http::header::{self, HeaderMap, HeaderValue}; /// let mut map = HeaderMap::new(); /// /// let removed = map.remove("accept"); @@ -459,7 +459,7 @@ impl HeaderMap { /// /// # Examples /// ``` - /// # use actix_http::http::HeaderMap; + /// # use actix_http::header::HeaderMap; /// let map = HeaderMap::with_capacity(16); /// /// assert!(map.is_empty()); @@ -479,7 +479,7 @@ impl HeaderMap { /// /// # Examples /// ``` - /// # use actix_http::http::HeaderMap; + /// # use actix_http::header::HeaderMap; /// let mut map = HeaderMap::with_capacity(2); /// assert!(map.capacity() >= 2); /// @@ -499,7 +499,7 @@ impl HeaderMap { /// /// # Examples /// ``` - /// # use actix_http::http::{header, HeaderMap, HeaderValue}; + /// # use actix_http::header::{self, HeaderMap, HeaderValue}; /// let mut map = HeaderMap::new(); /// /// let mut iter = map.iter(); @@ -531,7 +531,7 @@ impl HeaderMap { /// /// # Examples /// ``` - /// # use actix_http::http::{header, HeaderMap, HeaderValue}; + /// # use actix_http::header::{self, HeaderMap, HeaderValue}; /// let mut map = HeaderMap::new(); /// /// let mut iter = map.keys(); @@ -559,7 +559,7 @@ impl HeaderMap { /// Keeps the allocated memory for reuse. /// # Examples /// ``` - /// # use actix_http::http::{header, HeaderMap, HeaderValue}; + /// # use actix_http::header::{self, HeaderMap, HeaderValue}; /// let mut map = HeaderMap::new(); /// /// let mut iter = map.drain(); diff --git a/actix-http/src/lib.rs b/actix-http/src/lib.rs index bfb6b8c55..aeba3da36 100644 --- a/actix-http/src/lib.rs +++ b/actix-http/src/lib.rs @@ -67,26 +67,6 @@ pub use self::service::HttpService; pub use ::http::{uri, uri::Uri}; pub use ::http::{Method, StatusCode, Version}; -// TODO: deprecate this mish-mash of random items -pub mod http { - //! Various HTTP related types. - - // re-exports - pub use http::header::{HeaderName, HeaderValue}; - pub use http::uri::PathAndQuery; - pub use http::{uri, Error, Uri}; - pub use http::{Method, StatusCode, Version}; - - pub use crate::header::HeaderMap; - - /// A collection of HTTP headers and helpers. - pub mod header { - pub use crate::header::*; - } - pub use crate::header::ContentEncoding; - pub use crate::message::ConnectionType; -} - /// A major HTTP protocol version. #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] #[non_exhaustive] diff --git a/actix-http/src/response.rs b/actix-http/src/response.rs index ad41094ae..ee7e38913 100644 --- a/actix-http/src/response.rs +++ b/actix-http/src/response.rs @@ -11,10 +11,9 @@ use bytestring::ByteString; use crate::{ body::{BoxBody, MessageBody}, extensions::Extensions, - header::{self, IntoHeaderValue}, - http::{HeaderMap, StatusCode}, + header::{self, HeaderMap, IntoHeaderValue}, message::{BoxedResponseHead, ResponseHead}, - Error, ResponseBuilder, + Error, ResponseBuilder, StatusCode, }; /// An HTTP response. @@ -323,7 +322,7 @@ mod tests { use super::*; use crate::{ body::to_bytes, - http::header::{HeaderValue, CONTENT_TYPE, COOKIE}, + header::{HeaderValue, CONTENT_TYPE, COOKIE}, }; #[test] diff --git a/actix-http/src/response_builder.rs b/actix-http/src/response_builder.rs index 0537112d5..f11f89219 100644 --- a/actix-http/src/response_builder.rs +++ b/actix-http/src/response_builder.rs @@ -20,7 +20,7 @@ use crate::{ /// /// # Examples /// ``` -/// use actix_http::{Response, ResponseBuilder, body, http::StatusCode, http::header}; +/// use actix_http::{Response, ResponseBuilder, StatusCode, body, header}; /// /// # actix_rt::System::new().block_on(async { /// let mut res: Response<_> = Response::build(StatusCode::OK) @@ -47,9 +47,7 @@ impl ResponseBuilder { /// Create response builder /// /// # Examples - /// ``` - /// use actix_http::{Response, ResponseBuilder, http::StatusCode}; - /// + // /// use actix_http::{Response, ResponseBuilder, StatusCode};, / `` /// let res: Response<_> = ResponseBuilder::default().finish(); /// assert_eq!(res.status(), StatusCode::OK); /// ``` @@ -64,9 +62,7 @@ impl ResponseBuilder { /// Set HTTP status code of this response. /// /// # Examples - /// ``` - /// use actix_http::{ResponseBuilder, http::StatusCode}; - /// + // /// use actix_http::{ResponseBuilder, StatusCode};, / `` /// let res = ResponseBuilder::default().status(StatusCode::NOT_FOUND).finish(); /// assert_eq!(res.status(), StatusCode::NOT_FOUND); /// ``` @@ -82,7 +78,7 @@ impl ResponseBuilder { /// /// # Examples /// ``` - /// use actix_http::{ResponseBuilder, http::header}; + /// use actix_http::{ResponseBuilder, header}; /// /// let res = ResponseBuilder::default() /// .insert_header((header::CONTENT_TYPE, mime::APPLICATION_JSON)) @@ -112,7 +108,7 @@ impl ResponseBuilder { /// /// # Examples /// ``` - /// use actix_http::{ResponseBuilder, http::header}; + /// use actix_http::{ResponseBuilder, header}; /// /// let res = ResponseBuilder::default() /// .append_header((header::CONTENT_TYPE, mime::APPLICATION_JSON)) @@ -335,7 +331,7 @@ mod tests { use bytes::Bytes; use super::*; - use crate::http::header::{HeaderName, HeaderValue, CONTENT_TYPE}; + use crate::header::{HeaderName, HeaderValue, CONTENT_TYPE}; #[test] fn test_basic_builder() { diff --git a/actix-http/tests/test_client.rs b/actix-http/tests/test_client.rs index 4c923873f..acbdc8e83 100644 --- a/actix-http/tests/test_client.rs +++ b/actix-http/tests/test_client.rs @@ -1,7 +1,7 @@ use std::convert::Infallible; use actix_http::{ - body::BoxBody, http, http::StatusCode, HttpMessage, HttpService, Request, Response, + body::BoxBody, HttpMessage, HttpService, Request, Response, StatusCode, }; use actix_http_test::test_server; use actix_service::ServiceFactoryExt; diff --git a/actix-http/tests/test_openssl.rs b/actix-http/tests/test_openssl.rs index 86ee17c74..6f68cc04d 100644 --- a/actix-http/tests/test_openssl.rs +++ b/actix-http/tests/test_openssl.rs @@ -7,11 +7,8 @@ use std::{convert::Infallible, io}; use actix_http::{ body::{BodyStream, BoxBody, SizedStream}, error::PayloadError, - http::{ - header::{self, HeaderValue}, - Method, StatusCode, Version, - }, - Error, HttpMessage, HttpService, Request, Response, + header::{self, HeaderValue}, + Error, HttpMessage, HttpService, Method, Request, Response, StatusCode, Version, }; use actix_http_test::test_server; use actix_service::{fn_service, ServiceFactoryExt}; diff --git a/actix-http/tests/test_rustls.rs b/actix-http/tests/test_rustls.rs index 873752779..1fc3bdf49 100644 --- a/actix-http/tests/test_rustls.rs +++ b/actix-http/tests/test_rustls.rs @@ -12,11 +12,8 @@ use std::{ use actix_http::{ body::{BodyStream, BoxBody, SizedStream}, error::PayloadError, - http::{ - header::{self, HeaderName, HeaderValue}, - Method, StatusCode, Version, - }, - Error, HttpService, Request, Response, + header::{self, HeaderName, HeaderValue}, + Error, HttpService, Method, Request, Response, StatusCode, Version, }; use actix_http_test::test_server; use actix_service::{fn_factory_with_config, fn_service}; diff --git a/actix-http/tests/test_server.rs b/actix-http/tests/test_server.rs index adf2a28ca..e6733b29b 100644 --- a/actix-http/tests/test_server.rs +++ b/actix-http/tests/test_server.rs @@ -7,8 +7,7 @@ use std::{ use actix_http::{ body::{self, BodyStream, BoxBody, SizedStream}, - header, http, Error, HttpMessage, HttpService, KeepAlive, Request, Response, - StatusCode, + header, Error, HttpMessage, HttpService, KeepAlive, Request, Response, StatusCode, }; use actix_http_test::test_server; use actix_rt::time::sleep; @@ -383,7 +382,7 @@ async fn test_http1_keepalive_disabled() { #[actix_rt::test] async fn test_content_length() { - use actix_http::http::{ + use actix_http::{ header::{HeaderName, HeaderValue}, StatusCode, }; diff --git a/actix-test/src/lib.rs b/actix-test/src/lib.rs index 1decd6e98..7e493ce71 100644 --- a/actix-test/src/lib.rs +++ b/actix-test/src/lib.rs @@ -35,10 +35,7 @@ use std::{fmt, net, thread, time::Duration}; use actix_codec::{AsyncRead, AsyncWrite, Framed}; pub use actix_http::test::TestBuffer; -use actix_http::{ - http::{HeaderMap, Method}, - ws, HttpService, Request, Response, -}; +use actix_http::{header::HeaderMap, ws, HttpService, Method, Request, Response}; use actix_service::{map_config, IntoServiceFactory, ServiceFactory, ServiceFactoryExt as _}; use actix_web::{ body::MessageBody, diff --git a/actix-web-actors/src/ws.rs b/actix-web-actors/src/ws.rs index c41268b01..6fde10192 100644 --- a/actix-web-actors/src/ws.rs +++ b/actix-web-actors/src/ws.rs @@ -19,16 +19,16 @@ use actix::{ SpawnHandle, }; use actix_codec::{Decoder as _, Encoder as _}; +use actix_http::ws::{hash_key, Codec}; pub use actix_http::ws::{ CloseCode, CloseReason, Frame, HandshakeError, Message, ProtocolError, }; -use actix_http::{ - http::HeaderValue, - ws::{hash_key, Codec}, -}; use actix_web::{ error::{Error, PayloadError}, - http::{header, Method, StatusCode}, + http::{ + header::{self, HeaderValue}, + Method, StatusCode, + }, HttpRequest, HttpResponse, HttpResponseBuilder, }; use bytes::{Bytes, BytesMut}; diff --git a/awc/src/builder.rs b/awc/src/builder.rs index 70a28c419..43e5c0def 100644 --- a/awc/src/builder.rs +++ b/awc/src/builder.rs @@ -1,6 +1,10 @@ use std::{convert::TryFrom, fmt, net::IpAddr, rc::Rc, time::Duration}; -use actix_http::http::{self, header, Error as HttpError, HeaderMap, HeaderName, Uri}; +use actix_http::{ + error::HttpError, + header::{self, HeaderMap, HeaderName}, + Uri, +}; use actix_rt::net::{ActixStream, TcpStream}; use actix_service::{boxed, Service}; diff --git a/awc/src/client/error.rs b/awc/src/client/error.rs index d351b5d5e..9f290c5c0 100644 --- a/awc/src/client/error.rs +++ b/awc/src/client/error.rs @@ -2,7 +2,7 @@ use std::{fmt, io}; use derive_more::{Display, From}; -use actix_http::{error::ParseError, http::Error as HttpError}; +use actix_http::error::{HttpError, ParseError}; #[cfg(feature = "openssl")] use actix_tls::accept::openssl::reexports::Error as OpensslError; diff --git a/awc/src/client/h1proto.rs b/awc/src/client/h1proto.rs index b26a97eeb..c8b9a3fae 100644 --- a/awc/src/client/h1proto.rs +++ b/awc/src/client/h1proto.rs @@ -9,11 +9,8 @@ use actix_http::{ body::{BodySize, MessageBody}, error::PayloadError, h1, - http::{ - header::{HeaderMap, IntoHeaderValue, EXPECT, HOST}, - StatusCode, - }, - Payload, RequestHeadType, ResponseHead, + header::{HeaderMap, IntoHeaderValue, EXPECT, HOST}, + Payload, RequestHeadType, ResponseHead, StatusCode, }; use actix_utils::future::poll_fn; use bytes::buf::BufMut; diff --git a/awc/src/error.rs b/awc/src/error.rs index 726e1a506..c1d855053 100644 --- a/awc/src/error.rs +++ b/awc/src/error.rs @@ -1,9 +1,10 @@ //! HTTP client errors pub use actix_http::{ - error::PayloadError, - http::{header::HeaderValue, Error as HttpError, StatusCode}, + error::{HttpError, PayloadError}, + header::HeaderValue, ws::{HandshakeError as WsHandshakeError, ProtocolError as WsProtocolError}, + StatusCode, }; use derive_more::{Display, From}; diff --git a/awc/src/frozen.rs b/awc/src/frozen.rs index 472397359..7497f85c8 100644 --- a/awc/src/frozen.rs +++ b/awc/src/frozen.rs @@ -5,8 +5,9 @@ use futures_core::Stream; use serde::Serialize; use actix_http::{ - http::{header::IntoHeaderValue, Error as HttpError, HeaderMap, HeaderName, Method, Uri}, - RequestHead, + error::HttpError, + header::{HeaderMap, HeaderName, IntoHeaderValue}, + Method, RequestHead, Uri, }; use crate::{ diff --git a/awc/src/lib.rs b/awc/src/lib.rs index 2f4183120..0cb6c7f4f 100644 --- a/awc/src/lib.rs +++ b/awc/src/lib.rs @@ -117,7 +117,8 @@ mod sender; pub mod test; pub mod ws; -pub use actix_http::http; +// TODO: hmmmmmm +pub use actix_http as http; #[cfg(feature = "cookies")] pub use cookie; @@ -131,10 +132,7 @@ pub use self::sender::SendClientRequest; use std::{convert::TryFrom, rc::Rc, time::Duration}; -use actix_http::{ - http::{Error as HttpError, HeaderMap, Method, Uri}, - RequestHead, -}; +use actix_http::{error::HttpError, header::HeaderMap, Method, RequestHead, Uri}; use actix_rt::net::TcpStream; use actix_service::Service; diff --git a/awc/src/middleware/redirect.rs b/awc/src/middleware/redirect.rs index 89cff22cd..0fde48074 100644 --- a/awc/src/middleware/redirect.rs +++ b/awc/src/middleware/redirect.rs @@ -7,10 +7,7 @@ use std::{ task::{Context, Poll}, }; -use actix_http::{ - http::{header, Method, StatusCode, Uri}, - RequestHead, RequestHeadType, -}; +use actix_http::{header, Method, RequestHead, RequestHeadType, StatusCode, Uri}; use actix_service::Service; use bytes::Bytes; use futures_core::ready; @@ -284,12 +281,12 @@ fn remove_sensitive_headers(headers: &mut header::HeaderMap, prev_uri: &Uri, nex #[cfg(test)] mod tests { + use std::str::FromStr; + use actix_web::{web, App, Error, HttpRequest, HttpResponse}; use super::*; - use crate::http::HeaderValue; - use crate::ClientBuilder; - use std::str::FromStr; + use crate::{http::header::HeaderValue, ClientBuilder}; #[actix_rt::test] async fn test_basic_redirect() { diff --git a/awc/src/request.rs b/awc/src/request.rs index d26b703f6..3e1f83a82 100644 --- a/awc/src/request.rs +++ b/awc/src/request.rs @@ -5,11 +5,9 @@ use futures_core::Stream; use serde::Serialize; use actix_http::{ - http::{ - header::{self, IntoHeaderPair}, - ConnectionType, Error as HttpError, HeaderMap, HeaderValue, Method, Uri, Version, - }, - RequestHead, + error::HttpError, + header::{self, HeaderMap, HeaderValue, IntoHeaderPair}, + ConnectionType, Method, RequestHead, Uri, Version, }; use crate::{ @@ -539,7 +537,7 @@ impl fmt::Debug for ClientRequest { mod tests { use std::time::SystemTime; - use actix_http::http::header::HttpDate; + use actix_http::header::HttpDate; use super::*; use crate::Client; diff --git a/awc/src/response.rs b/awc/src/response.rs index a966edd08..fefebd0a0 100644 --- a/awc/src/response.rs +++ b/awc/src/response.rs @@ -10,9 +10,8 @@ use std::{ }; use actix_http::{ - error::PayloadError, - http::{header, HeaderMap, StatusCode, Version}, - Extensions, HttpMessage, Payload, PayloadStream, ResponseHead, + error::PayloadError, header, header::HeaderMap, Extensions, HttpMessage, Payload, + PayloadStream, ResponseHead, StatusCode, Version, }; use actix_rt::time::{sleep, Sleep}; use bytes::{Bytes, BytesMut}; diff --git a/awc/src/sender.rs b/awc/src/sender.rs index 51fce1913..1faf6140a 100644 --- a/awc/src/sender.rs +++ b/awc/src/sender.rs @@ -9,10 +9,8 @@ use std::{ use actix_http::{ body::BodyStream, - http::{ - header::{self, HeaderMap, HeaderName, IntoHeaderValue}, - Error as HttpError, - }, + error::HttpError, + header::{self, HeaderMap, HeaderName, IntoHeaderValue}, RequestHead, RequestHeadType, }; use actix_rt::time::{sleep, Sleep}; @@ -22,7 +20,7 @@ use futures_core::Stream; use serde::Serialize; #[cfg(feature = "__compress")] -use actix_http::{encoding::Decoder, http::header::ContentEncoding, Payload, PayloadStream}; +use actix_http::{encoding::Decoder, header::ContentEncoding, Payload, PayloadStream}; use crate::{ any_body::AnyBody, diff --git a/awc/src/test.rs b/awc/src/test.rs index 1abe78811..4a5c8e7ea 100644 --- a/awc/src/test.rs +++ b/awc/src/test.rs @@ -1,7 +1,6 @@ //! Test helpers for actix http client to use during testing. -use actix_http::http::header::IntoHeaderPair; -use actix_http::http::{StatusCode, Version}; -use actix_http::{h1, Payload, ResponseHead}; + +use actix_http::{h1, header::IntoHeaderPair, Payload, ResponseHead, StatusCode, Version}; use bytes::Bytes; #[cfg(feature = "cookies")] @@ -89,7 +88,7 @@ impl TestResponse { #[cfg(feature = "cookies")] for cookie in self.cookies.delta() { - use actix_http::http::header::{self, HeaderValue}; + use actix_http::header::{self, HeaderValue}; head.headers.insert( header::SET_COOKIE, @@ -109,7 +108,7 @@ impl TestResponse { mod tests { use std::time::SystemTime; - use actix_http::http::header::HttpDate; + use actix_http::header::HttpDate; use super::*; use crate::{cookie, http::header}; diff --git a/awc/src/ws.rs b/awc/src/ws.rs index e2f1f86d0..f0d421dbc 100644 --- a/awc/src/ws.rs +++ b/awc/src/ws.rs @@ -26,9 +26,7 @@ //! } //! ``` -use std::convert::TryFrom; -use std::net::SocketAddr; -use std::{fmt, str}; +use std::{convert::TryFrom, fmt, net::SocketAddr, str}; use actix_codec::Framed; use actix_http::{ws, Payload, RequestHead}; @@ -37,14 +35,19 @@ use actix_service::Service; pub use actix_http::ws::{CloseCode, CloseReason, Codec, Frame, Message}; -use crate::connect::{BoxedSocket, ConnectRequest}; +use crate::{ + connect::{BoxedSocket, ConnectRequest}, + error::{HttpError, InvalidUrl, SendRequestError, WsClientError}, + http::{ + header::{self, HeaderName, HeaderValue, IntoHeaderValue, AUTHORIZATION}, + ConnectionType, Method, StatusCode, Uri, Version, + }, + response::ClientResponse, + ClientConfig, +}; + #[cfg(feature = "cookies")] use crate::cookie::{Cookie, CookieJar}; -use crate::error::{InvalidUrl, SendRequestError, WsClientError}; -use crate::http::header::{self, HeaderName, HeaderValue, IntoHeaderValue, AUTHORIZATION}; -use crate::http::{ConnectionType, Error as HttpError, Method, StatusCode, Uri, Version}; -use crate::response::ClientResponse; -use crate::ClientConfig; /// WebSocket connection. pub struct WebsocketsRequest { diff --git a/awc/tests/test_client.rs b/awc/tests/test_client.rs index 5abb63e39..c453a768d 100644 --- a/awc/tests/test_client.rs +++ b/awc/tests/test_client.rs @@ -21,10 +21,7 @@ use brotli2::write::BrotliEncoder; #[cfg(feature = "compress-gzip")] use flate2::{read::GzDecoder, write::GzEncoder, Compression}; -use actix_http::{ - http::{self, StatusCode}, - HttpService, -}; +use actix_http::{ContentEncoding, HttpService, StatusCode}; use actix_http_test::test_server; use actix_service::{fn_service, map_config, ServiceFactoryExt as _}; use actix_web::{ @@ -647,9 +644,7 @@ async fn test_client_brotli_encoding_large_random() { async fn test_client_deflate_encoding() { let srv = actix_test::start(|| { App::new().default_service(web::to(|body: Bytes| { - HttpResponse::Ok() - .encoding(http::ContentEncoding::Br) - .body(body) + HttpResponse::Ok().encoding(ContentEncoding::Br).body(body) })) }); @@ -672,9 +667,7 @@ async fn test_client_deflate_encoding_large_random() { let srv = actix_test::start(|| { App::new().default_service(web::to(|body: Bytes| { - HttpResponse::Ok() - .encoding(http::ContentEncoding::Br) - .body(body) + HttpResponse::Ok().encoding(ContentEncoding::Br).body(body) })) }); @@ -692,7 +685,7 @@ async fn test_client_streaming_explicit() { let srv = actix_test::start(|| { App::new().default_service(web::to(|body: web::Payload| { HttpResponse::Ok() - .encoding(http::ContentEncoding::Identity) + .encoding(ContentEncoding::Identity) .streaming(body) })) }); @@ -717,7 +710,7 @@ async fn test_body_streaming_implicit() { }); HttpResponse::Ok() - .encoding(http::ContentEncoding::Gzip) + .encoding(ContentEncoding::Gzip) .streaming(Box::pin(body)) })) }); diff --git a/src/app.rs b/src/app.rs index efc108cb9..a27dd54a6 100644 --- a/src/app.rs +++ b/src/app.rs @@ -353,7 +353,7 @@ where /// ``` /// use actix_service::Service; /// use actix_web::{middleware, web, App}; - /// use actix_web::http::{header::CONTENT_TYPE, HeaderValue}; + /// use actix_web::http::header::{CONTENT_TYPE, HeaderValue}; /// /// async fn index() -> &'static str { /// "Welcome!" @@ -410,7 +410,7 @@ where /// ``` /// use actix_service::Service; /// use actix_web::{web, App}; - /// use actix_web::http::{header::CONTENT_TYPE, HeaderValue}; + /// use actix_web::http::header::{CONTENT_TYPE, HeaderValue}; /// /// async fn index() -> &'static str { /// "Welcome!" @@ -494,7 +494,10 @@ mod tests { use bytes::Bytes; use super::*; - use crate::http::{header, HeaderValue, Method, StatusCode}; + use crate::http::{ + header::{self, HeaderValue}, + Method, StatusCode, + }; use crate::middleware::DefaultHeaders; use crate::service::ServiceRequest; use crate::test::{call_service, init_service, read_body, try_init_service, TestRequest}; diff --git a/src/extract.rs b/src/extract.rs index bb2dabb9f..f74a0a54e 100644 --- a/src/extract.rs +++ b/src/extract.rs @@ -7,7 +7,7 @@ use std::{ task::{Context, Poll}, }; -use actix_http::http::{Method, Uri}; +use actix_http::{Method, Uri}; use actix_utils::future::{ok, Ready}; use futures_core::ready; use pin_project_lite::pin_project; @@ -402,7 +402,7 @@ mod tuple_from_req { #[cfg(test)] mod tests { - use actix_http::http::header; + use actix_http::header; use bytes::Bytes; use serde::Deserialize; diff --git a/src/guard.rs b/src/guard.rs index c71d64a29..a5770df89 100644 --- a/src/guard.rs +++ b/src/guard.rs @@ -24,13 +24,13 @@ //! ); //! } //! ``` -#![allow(non_snake_case)] -use std::convert::TryFrom; -use std::ops::Deref; -use std::rc::Rc; -use actix_http::http::{self, header, uri::Uri}; -use actix_http::RequestHead; +#![allow(non_snake_case)] + +use std::rc::Rc; +use std::{convert::TryFrom, ops::Deref}; + +use actix_http::{header, uri::Uri, Method as HttpMethod, RequestHead}; /// Trait defines resource guards. Guards are used for route selection. /// @@ -186,7 +186,7 @@ impl Guard for NotGuard { /// HTTP method guard. #[doc(hidden)] -pub struct MethodGuard(http::Method); +pub struct MethodGuard(HttpMethod); impl Guard for MethodGuard { fn check(&self, request: &RequestHead) -> bool { @@ -196,51 +196,51 @@ impl Guard for MethodGuard { /// Guard to match *GET* HTTP method. pub fn Get() -> MethodGuard { - MethodGuard(http::Method::GET) + MethodGuard(HttpMethod::GET) } /// Predicate to match *POST* HTTP method. pub fn Post() -> MethodGuard { - MethodGuard(http::Method::POST) + MethodGuard(HttpMethod::POST) } /// Predicate to match *PUT* HTTP method. pub fn Put() -> MethodGuard { - MethodGuard(http::Method::PUT) + MethodGuard(HttpMethod::PUT) } /// Predicate to match *DELETE* HTTP method. pub fn Delete() -> MethodGuard { - MethodGuard(http::Method::DELETE) + MethodGuard(HttpMethod::DELETE) } /// Predicate to match *HEAD* HTTP method. pub fn Head() -> MethodGuard { - MethodGuard(http::Method::HEAD) + MethodGuard(HttpMethod::HEAD) } /// Predicate to match *OPTIONS* HTTP method. pub fn Options() -> MethodGuard { - MethodGuard(http::Method::OPTIONS) + MethodGuard(HttpMethod::OPTIONS) } /// Predicate to match *CONNECT* HTTP method. pub fn Connect() -> MethodGuard { - MethodGuard(http::Method::CONNECT) + MethodGuard(HttpMethod::CONNECT) } /// Predicate to match *PATCH* HTTP method. pub fn Patch() -> MethodGuard { - MethodGuard(http::Method::PATCH) + MethodGuard(HttpMethod::PATCH) } /// Predicate to match *TRACE* HTTP method. pub fn Trace() -> MethodGuard { - MethodGuard(http::Method::TRACE) + MethodGuard(HttpMethod::TRACE) } /// Predicate to match specified HTTP method. -pub fn Method(method: http::Method) -> MethodGuard { +pub fn Method(method: HttpMethod) -> MethodGuard { MethodGuard(method) } @@ -331,7 +331,7 @@ impl Guard for HostGuard { #[cfg(test)] mod tests { - use actix_http::http::{header, Method}; + use actix_http::{header, Method}; use super::*; use crate::test::TestRequest; diff --git a/src/http/header/allow.rs b/src/http/header/allow.rs index c8cc153e8..d0ef96486 100644 --- a/src/http/header/allow.rs +++ b/src/http/header/allow.rs @@ -1,4 +1,4 @@ -use actix_http::http::Method; +use actix_http::Method; use crate::http::header; diff --git a/src/http/header/macros.rs b/src/http/header/macros.rs index 3f530658c..ca3792a37 100644 --- a/src/http/header/macros.rs +++ b/src/http/header/macros.rs @@ -6,7 +6,7 @@ macro_rules! common_header_test_module { use ::core::str; - use ::actix_http::{http::Method, test}; + use ::actix_http::{Method, test}; use ::mime::*; use $crate::http::header::{self, *}; diff --git a/src/http/header/mod.rs b/src/http/header/mod.rs index 07b7592d7..9807d5f5e 100644 --- a/src/http/header/mod.rs +++ b/src/http/header/mod.rs @@ -15,7 +15,7 @@ use bytes::{Bytes, BytesMut}; // - header map // - the few typed headers from actix-http // - header parsing utils -pub use actix_http::http::header::*; +pub use actix_http::header::*; mod accept; mod accept_charset; diff --git a/src/http/mod.rs b/src/http/mod.rs index fa28a5fa9..bbd94a60f 100644 --- a/src/http/mod.rs +++ b/src/http/mod.rs @@ -1,2 +1,5 @@ +//! Various HTTP related types. + pub mod header; -pub use actix_http::http::*; + +pub use actix_http::{uri, ConnectionType, Error, Method, StatusCode, Uri, Version}; diff --git a/src/middleware/compress.rs b/src/middleware/compress.rs index d017e9a5a..af4a107e3 100644 --- a/src/middleware/compress.rs +++ b/src/middleware/compress.rs @@ -2,7 +2,7 @@ use std::{ cmp, - convert::TryFrom, + convert::TryFrom as _, future::Future, marker::PhantomData, pin::Pin, @@ -12,7 +12,7 @@ use std::{ use actix_http::{ body::{EitherBody, MessageBody}, encoding::Encoder, - http::header::{ContentEncoding, ACCEPT_ENCODING}, + header::{ContentEncoding, ACCEPT_ENCODING}, StatusCode, }; use actix_service::{Service, Transform}; diff --git a/src/middleware/condition.rs b/src/middleware/condition.rs index d1ba7ee4d..a7777a96b 100644 --- a/src/middleware/condition.rs +++ b/src/middleware/condition.rs @@ -102,7 +102,10 @@ mod tests { use crate::{ dev::{ServiceRequest, ServiceResponse}, error::Result, - http::{header::CONTENT_TYPE, HeaderValue, StatusCode}, + http::{ + header::{HeaderValue, CONTENT_TYPE}, + StatusCode, + }, middleware::err_handlers::*, test::{self, TestRequest}, HttpResponse, diff --git a/src/middleware/default_headers.rs b/src/middleware/default_headers.rs index 426810247..dceca44c2 100644 --- a/src/middleware/default_headers.rs +++ b/src/middleware/default_headers.rs @@ -9,16 +9,14 @@ use std::{ task::{Context, Poll}, }; +use actix_http::error::HttpError; use actix_utils::future::{ready, Ready}; use futures_core::ready; use pin_project_lite::pin_project; use crate::{ dev::{Service, Transform}, - http::{ - header::{HeaderName, HeaderValue, CONTENT_TYPE}, - Error as HttpError, HeaderMap, - }, + http::header::{HeaderMap, HeaderName, HeaderValue, CONTENT_TYPE}, service::{ServiceRequest, ServiceResponse}, Error, }; diff --git a/src/middleware/err_handlers.rs b/src/middleware/err_handlers.rs index 1a834c1e8..756da30c3 100644 --- a/src/middleware/err_handlers.rs +++ b/src/middleware/err_handlers.rs @@ -37,19 +37,20 @@ type ErrorHandler = dyn Fn(ServiceResponse) -> Result(mut res: dev::ServiceResponse) -> Result> { /// res.response_mut() /// .headers_mut() -/// .insert(http::header::CONTENT_TYPE, http::HeaderValue::from_static("Error")); +/// .insert(header::CONTENT_TYPE, header::HeaderValue::from_static("Error")); /// Ok(ErrorHandlerResponse::Response(res)) /// } /// /// let app = App::new() /// .wrap( /// ErrorHandlers::new() -/// .handler(http::StatusCode::INTERNAL_SERVER_ERROR, render_500), +/// .handler(StatusCode::INTERNAL_SERVER_ERROR, render_500), /// ) /// .service(web::resource("/test") /// .route(web::get().to(|| HttpResponse::Ok())) @@ -182,7 +183,10 @@ mod tests { use futures_util::future::FutureExt as _; use super::*; - use crate::http::{header::CONTENT_TYPE, HeaderValue, StatusCode}; + use crate::http::{ + header::{HeaderValue, CONTENT_TYPE}, + StatusCode, + }; use crate::test::{self, TestRequest}; use crate::HttpResponse; diff --git a/src/middleware/logger.rs b/src/middleware/logger.rs index f89b13a1c..74daa26d5 100644 --- a/src/middleware/logger.rs +++ b/src/middleware/logger.rs @@ -23,7 +23,7 @@ use time::{format_description::well_known::Rfc3339, OffsetDateTime}; use crate::{ body::{BodySize, MessageBody}, - http::HeaderName, + http::header::HeaderName, service::{ServiceRequest, ServiceResponse}, Error, HttpResponse, Result, }; @@ -126,7 +126,8 @@ impl Logger { /// /// # Example /// ``` - /// # use actix_web::{http::HeaderValue, middleware::Logger}; + /// # use actix_web::http::{header::HeaderValue}; + /// # use actix_web::middleware::Logger; /// # fn parse_jwt_id (_req: Option<&HeaderValue>) -> String { "jwt_uid".to_owned() } /// Logger::new("example %{JWT_ID}xi") /// .custom_request_replace("JWT_ID", |req| parse_jwt_id(req.headers().get("Authorization"))); diff --git a/src/middleware/normalize.rs b/src/middleware/normalize.rs index 8ad0bb3f0..18dcaeefa 100644 --- a/src/middleware/normalize.rs +++ b/src/middleware/normalize.rs @@ -1,6 +1,6 @@ //! For middleware documentation, see [`NormalizePath`]. -use actix_http::http::{PathAndQuery, Uri}; +use actix_http::uri::{PathAndQuery, Uri}; use actix_service::{Service, Transform}; use actix_utils::future::{ready, Ready}; use bytes::Bytes; diff --git a/src/request.rs b/src/request.rs index 58222da47..f04d47c6f 100644 --- a/src/request.rs +++ b/src/request.rs @@ -6,8 +6,8 @@ use std::{ }; use actix_http::{ - http::{HeaderMap, Method, Uri, Version}, - Extensions, HttpMessage, Message, Payload, RequestHead, + header::HeaderMap, Extensions, HttpMessage, Message, Method, Payload, RequestHead, Uri, + Version, }; use actix_router::{Path, Url}; use actix_utils::future::{ok, Ready}; @@ -266,7 +266,7 @@ impl HttpRequest { /// Load request cookies. #[cfg(feature = "cookies")] pub fn cookies(&self) -> Result>>, CookieParseError> { - use actix_http::http::header::COOKIE; + use actix_http::header::COOKIE; if self.extensions().get::().is_none() { let mut cookies = Vec::new(); diff --git a/src/resource.rs b/src/resource.rs index fc417bac2..420374a86 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -298,7 +298,7 @@ where /// ``` /// use actix_service::Service; /// use actix_web::{web, App}; - /// use actix_web::http::{header::CONTENT_TYPE, HeaderValue}; + /// use actix_web::http::header::{CONTENT_TYPE, HeaderValue}; /// /// async fn index() -> &'static str { /// "Welcome!" @@ -508,7 +508,10 @@ mod tests { use crate::{ guard, - http::{header, HeaderValue, Method, StatusCode}, + http::{ + header::{self, HeaderValue}, + Method, StatusCode, + }, middleware::DefaultHeaders, service::{ServiceRequest, ServiceResponse}, test::{call_service, init_service, TestRequest}, diff --git a/src/responder.rs b/src/responder.rs index 9d8a0e8ed..e72739a71 100644 --- a/src/responder.rs +++ b/src/responder.rs @@ -2,7 +2,10 @@ use std::borrow::Cow; use actix_http::{ body::{BoxBody, EitherBody, MessageBody}, - http::{header::IntoHeaderPair, Error as HttpError, HeaderMap, StatusCode}, + error::HttpError, + header::HeaderMap, + header::IntoHeaderPair, + StatusCode, }; use bytes::{Bytes, BytesMut}; @@ -280,7 +283,10 @@ pub(crate) mod tests { use super::*; use crate::{ error, - http::{header::CONTENT_TYPE, HeaderValue, StatusCode}, + http::{ + header::{HeaderValue, CONTENT_TYPE}, + StatusCode, + }, test::{assert_body_eq, init_service, TestRequest}, web, App, }; diff --git a/src/response/builder.rs b/src/response/builder.rs index b5bef2e99..50e23f81b 100644 --- a/src/response/builder.rs +++ b/src/response/builder.rs @@ -8,18 +8,16 @@ use std::{ use actix_http::{ body::{BodyStream, BoxBody, MessageBody}, - http::{ - header::{self, HeaderName, IntoHeaderPair, IntoHeaderValue}, - ConnectionType, Error as HttpError, StatusCode, - }, - Extensions, Response, ResponseHead, + error::HttpError, + header::{self, HeaderName, IntoHeaderPair, IntoHeaderValue}, + ConnectionType, Extensions, Response, ResponseHead, StatusCode, }; use bytes::Bytes; use futures_core::Stream; use serde::Serialize; #[cfg(feature = "cookies")] -use actix_http::http::header::HeaderValue; +use actix_http::header::HeaderValue; #[cfg(feature = "cookies")] use cookie::{Cookie, CookieJar}; diff --git a/src/response/http_codes.rs b/src/response/http_codes.rs index 44ddb78f9..986735346 100644 --- a/src/response/http_codes.rs +++ b/src/response/http_codes.rs @@ -1,6 +1,6 @@ //! Status code based HTTP response builders. -use actix_http::http::StatusCode; +use actix_http::StatusCode; use crate::{HttpResponse, HttpResponseBuilder}; diff --git a/src/response/response.rs b/src/response/response.rs index 97de21e42..1900dd845 100644 --- a/src/response/response.rs +++ b/src/response/response.rs @@ -9,15 +9,15 @@ use std::{ use actix_http::{ body::{BoxBody, EitherBody, MessageBody}, - http::{header::HeaderMap, StatusCode}, - Extensions, Response, ResponseHead, + header::HeaderMap, + Extensions, Response, ResponseHead, StatusCode, }; #[cfg(feature = "cookies")] use { - actix_http::http::{ + actix_http::{ + error::HttpError, header::{self, HeaderValue}, - Error as HttpError, }, cookie::Cookie, }; diff --git a/src/route.rs b/src/route.rs index 1eb323068..4447bff50 100644 --- a/src/route.rs +++ b/src/route.rs @@ -1,6 +1,6 @@ use std::{future::Future, mem, rc::Rc}; -use actix_http::http::Method; +use actix_http::Method; use actix_service::{ boxed::{self, BoxService}, fn_service, Service, ServiceFactory, ServiceFactoryExt, diff --git a/src/scope.rs b/src/scope.rs index ff013671b..ad102b66b 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -347,7 +347,7 @@ where /// ``` /// use actix_service::Service; /// use actix_web::{web, App}; - /// use actix_web::http::{header::CONTENT_TYPE, HeaderValue}; + /// use actix_web::http::header::{CONTENT_TYPE, HeaderValue}; /// /// async fn index() -> &'static str { /// "Welcome!" @@ -587,7 +587,10 @@ mod tests { use crate::{ guard, - http::{header, HeaderValue, Method, StatusCode}, + http::{ + header::{self, HeaderValue}, + Method, StatusCode, + }, middleware::DefaultHeaders, service::{ServiceRequest, ServiceResponse}, test::{assert_body_eq, call_service, init_service, read_body, TestRequest}, diff --git a/src/service.rs b/src/service.rs index df9e809e4..4185d6018 100644 --- a/src/service.rs +++ b/src/service.rs @@ -6,8 +6,9 @@ use std::{ use actix_http::{ body::{BoxBody, EitherBody, MessageBody}, - http::{HeaderMap, Method, StatusCode, Uri, Version}, - Extensions, HttpMessage, Payload, PayloadStream, RequestHead, Response, ResponseHead, + header::HeaderMap, + Extensions, HttpMessage, Method, Payload, PayloadStream, RequestHead, Response, + ResponseHead, StatusCode, Uri, Version, }; use actix_router::{IntoPatterns, Path, Patterns, Resource, ResourceDef, Url}; use actix_service::{ diff --git a/src/test.rs b/src/test.rs index 2cd01039d..07d2d16b6 100644 --- a/src/test.rs +++ b/src/test.rs @@ -4,9 +4,8 @@ use std::{borrow::Cow, net::SocketAddr, rc::Rc}; pub use actix_http::test::TestBuffer; use actix_http::{ - http::{header::IntoHeaderPair, Method, StatusCode, Uri, Version}, - test::TestRequest as HttpTestRequest, - Extensions, Request, + header::IntoHeaderPair, test::TestRequest as HttpTestRequest, Extensions, Method, Request, + StatusCode, Uri, Version, }; use actix_router::{Path, ResourceDef, Url}; use actix_service::{IntoService, IntoServiceFactory, Service, ServiceFactory}; @@ -547,7 +546,7 @@ impl TestRequest { #[cfg(feature = "cookies")] { - use actix_http::http::header::{HeaderValue, COOKIE}; + use actix_http::header::{HeaderValue, COOKIE}; let cookie: String = self .cookies diff --git a/src/types/query.rs b/src/types/query.rs index 9fac21173..97d17123d 100644 --- a/src/types/query.rs +++ b/src/types/query.rs @@ -185,14 +185,12 @@ impl QueryConfig { #[cfg(test)] mod tests { - use actix_http::http::StatusCode; + use actix_http::StatusCode; use derive_more::Display; use serde::Deserialize; use super::*; - use crate::error::InternalError; - use crate::test::TestRequest; - use crate::HttpResponse; + use crate::{error::InternalError, test::TestRequest, HttpResponse}; #[derive(Deserialize, Debug, Display)] struct Id { diff --git a/src/web.rs b/src/web.rs index b58adc2f8..16dbace60 100644 --- a/src/web.rs +++ b/src/web.rs @@ -2,7 +2,7 @@ use std::{error::Error as StdError, future::Future}; -use actix_http::http::Method; +use actix_http::Method; use actix_router::IntoPatterns; pub use bytes::{Buf, BufMut, Bytes, BytesMut}; diff --git a/tests/test_server.rs b/tests/test_server.rs index a850f228d..51a78eb28 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -10,7 +10,7 @@ use std::{ task::{Context, Poll}, }; -use actix_http::http::header::{ +use actix_http::header::{ ContentEncoding, ACCEPT_ENCODING, CONTENT_ENCODING, CONTENT_LENGTH, TRANSFER_ENCODING, }; use brotli2::write::{BrotliDecoder, BrotliEncoder}; @@ -902,7 +902,7 @@ async fn test_brotli_encoding_large_openssl() { actix_test::start_with(actix_test::config().openssl(openssl_config()), move || { App::new().service(web::resource("/").route(web::to(|bytes: Bytes| { HttpResponse::Ok() - .encoding(actix_web::http::ContentEncoding::Identity) + .encoding(ContentEncoding::Identity) .body(bytes) }))) }); @@ -970,7 +970,7 @@ mod plus_rustls { let srv = actix_test::start_with(actix_test::config().rustls(tls_config()), || { App::new().service(web::resource("/").route(web::to(|bytes: Bytes| { HttpResponse::Ok() - .encoding(actix_web::http::ContentEncoding::Identity) + .encoding(ContentEncoding::Identity) .body(bytes) }))) }); From 627c0dc22fa580cc36dbb0a1d1b186468fd5a103 Mon Sep 17 00:00:00 2001 From: Ali MJ Al-Nasrawy Date: Sun, 5 Dec 2021 19:19:08 +0300 Subject: [PATCH 305/428] workaround rustdoc bug for Error (#2489) --- src/error/mod.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/error/mod.rs b/src/error/mod.rs index 46d0dccc6..90c2c9a61 100644 --- a/src/error/mod.rs +++ b/src/error/mod.rs @@ -1,6 +1,12 @@ //! Error and Result module -pub use actix_http::error::*; +/// This is meant to be a glob import of the whole error module, but rustdoc can't handle +/// shadowing `Error` type, so it is expanded manually. +/// See https://github.com/rust-lang/rust/issues/83375 +pub use actix_http::error::{ + BlockingError, ContentTypeError, DispatchError, HttpError, ParseError, PayloadError, +}; + use derive_more::{Display, Error, From}; use serde_json::error::Error as JsonError; use serde_urlencoded::de::Error as FormDeError; From c596f573a6bdde10e7cb12256e9ed05eea4bab9b Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sun, 5 Dec 2021 21:25:15 +0000 Subject: [PATCH 306/428] bump actix-server to rc.1 --- Cargo.toml | 2 +- actix-http-test/Cargo.toml | 2 +- actix-http/Cargo.toml | 2 +- awc/Cargo.toml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 425bdbbb3..cee0680a5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -72,7 +72,7 @@ experimental-io-uring = ["actix-server/io-uring"] actix-codec = "0.4.1" actix-macros = "0.2.3" actix-rt = "2.3" -actix-server = "2.0.0-beta.9" +actix-server = "2.0.0-rc.1" actix-service = "2.0.0" actix-utils = "3.0.0" actix-tls = { version = "3.0.0-rc.1", default-features = false, optional = true } diff --git a/actix-http-test/Cargo.toml b/actix-http-test/Cargo.toml index 8d347d4e9..7a22cbcc1 100644 --- a/actix-http-test/Cargo.toml +++ b/actix-http-test/Cargo.toml @@ -34,7 +34,7 @@ actix-codec = "0.4.1" actix-tls = "3.0.0-rc.1" actix-utils = "3.0.0" actix-rt = "2.2" -actix-server = "2.0.0-beta.9" +actix-server = "2.0.0-rc.1" awc = { version = "3.0.0-beta.11", default-features = false } base64 = "0.13" diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 967f04d03..87669aeb1 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -81,7 +81,7 @@ flate2 = { version = "1.0.13", optional = true } zstd = { version = "0.9", optional = true } [dev-dependencies] -actix-server = "2.0.0-beta.9" +actix-server = "2.0.0-rc.1" actix-http-test = { version = "3.0.0-beta.7", features = ["openssl"] } actix-tls = { version = "3.0.0-rc.1", features = ["openssl"] } async-stream = "0.3" diff --git a/awc/Cargo.toml b/awc/Cargo.toml index fc60f5edb..836241d46 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -97,7 +97,7 @@ actix-web = { version = "4.0.0-beta.11", features = ["openssl"] } actix-http = { version = "3.0.0-beta.14", features = ["openssl"] } actix-http-test = { version = "3.0.0-beta.7", features = ["openssl"] } actix-utils = "3.0.0" -actix-server = "2.0.0-beta.9" +actix-server = "2.0.0-rc.1" actix-tls = { version = "3.0.0-rc.1", features = ["openssl", "rustls"] } actix-test = { version = "0.1.0-beta.7", features = ["openssl", "rustls"] } From bed72d9bb7db33b94018cf65d7fc286e4dad1f76 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sun, 5 Dec 2021 23:23:36 +0000 Subject: [PATCH 307/428] fix examples --- actix-http/examples/echo.rs | 2 +- actix-http/examples/echo2.rs | 4 ++-- actix-http/examples/hello-world.rs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/actix-http/examples/echo.rs b/actix-http/examples/echo.rs index 6cfe3a675..5ff2bcc89 100644 --- a/actix-http/examples/echo.rs +++ b/actix-http/examples/echo.rs @@ -1,6 +1,6 @@ use std::io; -use actix_http::{http::StatusCode, Error, HttpService, Request, Response}; +use actix_http::{Error, HttpService, Request, Response, StatusCode}; use actix_server::Server; use bytes::BytesMut; use futures_util::StreamExt as _; diff --git a/actix-http/examples/echo2.rs b/actix-http/examples/echo2.rs index 6092c01ce..487b8d8d1 100644 --- a/actix-http/examples/echo2.rs +++ b/actix-http/examples/echo2.rs @@ -1,8 +1,8 @@ use std::io; use actix_http::{ - body::MessageBody, http::HeaderValue, http::StatusCode, Error, HttpService, Request, - Response, + body::MessageBody, header::HeaderValue, Error, HttpService, Request, Response, + StatusCode, }; use actix_server::Server; use bytes::BytesMut; diff --git a/actix-http/examples/hello-world.rs b/actix-http/examples/hello-world.rs index 9a593c66a..3678774b8 100644 --- a/actix-http/examples/hello-world.rs +++ b/actix-http/examples/hello-world.rs @@ -1,6 +1,6 @@ use std::{convert::Infallible, io}; -use actix_http::{http::StatusCode, HttpService, Response}; +use actix_http::{HttpService, Response, StatusCode}; use actix_server::Server; use http::header::HeaderValue; From 606a371ec3dd28391fd4c1b8fce5a50d5650dd7c Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Mon, 6 Dec 2021 17:14:56 +0000 Subject: [PATCH 308/428] improve Data docs --- src/data.rs | 50 +++++++++++++++++++++++++++++++------------------- 1 file changed, 31 insertions(+), 19 deletions(-) diff --git a/src/data.rs b/src/data.rs index b29e4ecf4..ef077e87c 100644 --- a/src/data.rs +++ b/src/data.rs @@ -31,41 +31,53 @@ pub(crate) type FnDataFactory = /// server constructs an application instance for each thread, thus application data must be /// constructed multiple times. If you want to share data between different threads, a shareable /// object should be used, e.g. `Send + Sync`. Application data does not need to be `Send` -/// or `Sync`. Internally `Data` uses `Arc`. +/// or `Sync`. Internally `Data` contains an `Arc`. /// -/// If route data is not set for a handler, using `Data` extractor would cause *Internal -/// Server Error* response. +/// If route data is not set for a handler, using `Data` extractor would cause a `500 Internal +/// Server Error` response. /// -// TODO: document `dyn T` functionality through converting an Arc -// TODO: note equivalence of req.app_data> and Data extractor -// TODO: note that data must be inserted using Data in order to extract it +/// # Unsized Data +/// For types that are unsized, most commonly `dyn T`, `Data` can wrap these types by first +/// constructing an `Arc` and using the `From` implementation to convert it. +/// +/// ``` +/// # use std::{fmt::Display, sync::Arc}; +/// # use actix_web::web::Data; +/// let displayable_arc: Arc = Arc::new(42usize); +/// let displayable_data: Data = Data::from(displayable_arc); +/// ``` /// /// # Examples /// ``` /// use std::sync::Mutex; -/// use actix_web::{web, App, HttpResponse, Responder}; +/// use actix_web::{App, HttpRequest, HttpResponse, Responder, web::{self, Data}}; /// /// struct MyData { /// counter: usize, /// } /// /// /// Use the `Data` extractor to access data in a handler. -/// async fn index(data: web::Data>) -> impl Responder { -/// let mut data = data.lock().unwrap(); -/// data.counter += 1; +/// async fn index(data: Data>) -> impl Responder { +/// let mut my_data = data.lock().unwrap(); +/// my_data.counter += 1; /// HttpResponse::Ok() /// } /// -/// fn main() { -/// let data = web::Data::new(Mutex::new(MyData{ counter: 0 })); -/// -/// let app = App::new() -/// // Store `MyData` in application storage. -/// .app_data(data.clone()) -/// .service( -/// web::resource("/index.html").route( -/// web::get().to(index))); +/// /// Alteratively, use the `HttpRequest::app_data` method to access data in a handler. +/// async fn index_alt(req: HttpRequest) -> impl Responder { +/// let data = req.app_data::>>().unwrap(); +/// let mut my_data = data.lock().unwrap(); +/// my_data.counter += 1; +/// HttpResponse::Ok() /// } +/// +/// let data = Data::new(Mutex::new(MyData { counter: 0 })); +/// +/// let app = App::new() +/// // Store `MyData` in application storage. +/// .app_data(Data::clone(&data)) +/// .route("/index.html", web::get().to(index)) +/// .route("/index-alt.html", web::get().to(index_alt)); /// ``` #[derive(Debug)] pub struct Data(Arc); From 9587261c2099ea46182147c7f8807f8b1055448e Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Tue, 7 Dec 2021 15:31:15 +0000 Subject: [PATCH 309/428] add fakeshadow's actix-web in actix-http example --- actix-http/Cargo.toml | 3 ++- actix-http/examples/actix-web.rs | 26 ++++++++++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) create mode 100644 actix-http/examples/actix-web.rs diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 87669aeb1..6216af3d1 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -84,6 +84,7 @@ zstd = { version = "0.9", optional = true } actix-server = "2.0.0-rc.1" actix-http-test = { version = "3.0.0-beta.7", features = ["openssl"] } actix-tls = { version = "3.0.0-rc.1", features = ["openssl"] } +actix-web = "4.0.0-beta.13" async-stream = "0.3" criterion = { version = "0.3", features = ["html_reports"] } env_logger = "0.9" @@ -95,7 +96,7 @@ serde_json = "1.0" static_assertions = "1" tls-openssl = { package = "openssl", version = "0.10.9" } tls-rustls = { package = "rustls", version = "0.20.0" } -tokio = { version = "1.2", features = ["net", "rt"] } +tokio = { version = "1.2", features = ["net", "rt", "macros"] } [[example]] name = "ws" diff --git a/actix-http/examples/actix-web.rs b/actix-http/examples/actix-web.rs new file mode 100644 index 000000000..f8226507f --- /dev/null +++ b/actix-http/examples/actix-web.rs @@ -0,0 +1,26 @@ +use actix_http::HttpService; +use actix_server::Server; +use actix_service::map_config; +use actix_web::{dev::AppConfig, get, App}; + +#[get("/")] +async fn index() -> &'static str { + "Hello, world. From Actix Web!" +} + +#[tokio::main(flavor = "current_thread")] +async fn main() -> std::io::Result<()> { + Server::build() + .bind("hello-world", "127.0.0.1:8080", || { + // construct actix-web app + let app = App::new().service(index); + + HttpService::build() + // pass the app to service builder + // map_config is used to map App's configuration to ServiceBuilder + .finish(map_config(app, |_| AppConfig::default())) + .tcp() + })? + .run() + .await +} From 6460e67f8469aae30799fd2b740d7c2ba9449941 Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Tue, 7 Dec 2021 23:53:04 +0800 Subject: [PATCH 310/428] remove generic body type in App. (#2493) --- src/app.rs | 53 +++++++++++++++++++++++++---------------------------- 1 file changed, 25 insertions(+), 28 deletions(-) diff --git a/src/app.rs b/src/app.rs index a27dd54a6..ab2081c18 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1,9 +1,6 @@ -use std::{cell::RefCell, fmt, future::Future, marker::PhantomData, rc::Rc}; +use std::{cell::RefCell, fmt, future::Future, rc::Rc}; -use actix_http::{ - body::{BoxBody, MessageBody}, - Extensions, Request, -}; +use actix_http::{body::MessageBody, Extensions, Request}; use actix_service::{ apply, apply_fn_factory, boxed, IntoServiceFactory, ServiceFactory, ServiceFactoryExt, Transform, @@ -26,7 +23,7 @@ use crate::{ /// Application builder - structure that follows the builder pattern /// for building application instances. -pub struct App { +pub struct App { endpoint: T, services: Vec>, default: Option>, @@ -34,10 +31,9 @@ pub struct App { data_factories: Vec, external: Vec, extensions: Extensions, - _phantom: PhantomData, } -impl App { +impl App { /// Create application builder. Application can be configured with a builder-like pattern. #[allow(clippy::new_without_default)] pub fn new() -> Self { @@ -51,22 +47,11 @@ impl App { factory_ref, external: Vec::new(), extensions: Extensions::new(), - _phantom: PhantomData, } } } -impl App -where - B: MessageBody, - T: ServiceFactory< - ServiceRequest, - Config = (), - Response = ServiceResponse, - Error = Error, - InitError = (), - >, -{ +impl App { /// Set application (root level) data. /// /// Application data stored with `App::app_data()` method is available through the @@ -365,7 +350,7 @@ where /// .route("/index.html", web::get().to(index)); /// } /// ``` - pub fn wrap( + pub fn wrap( self, mw: M, ) -> App< @@ -376,9 +361,16 @@ where Error = Error, InitError = (), >, - B1, > where + T: ServiceFactory< + ServiceRequest, + Response = ServiceResponse, + Error = Error, + Config = (), + InitError = (), + >, + B: MessageBody, M: Transform< T::Service, ServiceRequest, @@ -396,7 +388,6 @@ where factory_ref: self.factory_ref, external: self.external, extensions: self.extensions, - _phantom: PhantomData, } } @@ -431,7 +422,7 @@ where /// .route("/index.html", web::get().to(index)); /// } /// ``` - pub fn wrap_fn( + pub fn wrap_fn( self, mw: F, ) -> App< @@ -442,12 +433,19 @@ where Error = Error, InitError = (), >, - B1, > where - B1: MessageBody, + T: ServiceFactory< + ServiceRequest, + Response = ServiceResponse, + Error = Error, + Config = (), + InitError = (), + >, + B: MessageBody, F: Fn(ServiceRequest, &T::Service) -> R + Clone, R: Future, Error>>, + B1: MessageBody, { App { endpoint: apply_fn_factory(self.endpoint, mw), @@ -457,12 +455,11 @@ where factory_ref: self.factory_ref, external: self.external, extensions: self.extensions, - _phantom: PhantomData, } } } -impl IntoServiceFactory, Request> for App +impl IntoServiceFactory, Request> for App where B: MessageBody, T: ServiceFactory< From 069cf2da0792d7a2160e4d6a5345104c80aa7967 Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Wed, 8 Dec 2021 00:26:28 +0800 Subject: [PATCH 311/428] enable scope middleware with generic res body. (#2492) Co-authored-by: Rob Ede --- CHANGES.md | 4 ++++ src/middleware/compat.rs | 2 +- src/scope.rs | 49 ++++++++++++++++++++-------------------- 3 files changed, 30 insertions(+), 25 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 1b108fee6..2adf54d3d 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -13,6 +13,8 @@ * Rename `Accept::{mime_preference => preference}`. [#2480] * Un-deprecate `App::data_factory`. [#2484] * `HttpRequest::url_for` no longer constructs URLs with query or fragment components. [#2430] +* Remove `B` (body) type parameter on `App`. [#2493] +* Add `B` (body) type parameter on `Scope`. [#2492] ### Fixed * Accept wildcard `*` items in `AcceptLanguage`. [#2480] @@ -25,6 +27,8 @@ [#2482]: https://github.com/actix/actix-web/pull/2482 [#2484]: https://github.com/actix/actix-web/pull/2484 [#2485]: https://github.com/actix/actix-web/pull/2485 +[#2492]: https://github.com/actix/actix-web/pull/2492 +[#2493]: https://github.com/actix/actix-web/pull/2493 ## 4.0.0-beta.13 - 2021-11-30 diff --git a/src/middleware/compat.rs b/src/middleware/compat.rs index e6ef1806f..ed441f7b9 100644 --- a/src/middleware/compat.rs +++ b/src/middleware/compat.rs @@ -154,7 +154,7 @@ mod tests { let srv = init_service( App::new().service( web::scope("app") - .wrap(Compat::new(logger)) + .wrap(logger) .wrap(Compat::new(compress)) .service(web::resource("/test").route(web::get().to(HttpResponse::Ok))), ), diff --git a/src/scope.rs b/src/scope.rs index ad102b66b..74523cd94 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -1,6 +1,6 @@ -use std::{cell::RefCell, fmt, future::Future, mem, rc::Rc}; +use std::{cell::RefCell, fmt, future::Future, marker::PhantomData, mem, rc::Rc}; -use actix_http::Extensions; +use actix_http::{body::BoxBody, Extensions}; use actix_router::{ResourceDef, Router}; use actix_service::{ apply, apply_fn_factory, boxed, IntoServiceFactory, Service, ServiceFactory, @@ -52,7 +52,7 @@ type Guards = Vec>; /// * /{project_id}/path1 - responds to all http method /// * /{project_id}/path2 - `GET` requests /// * /{project_id}/path3 - `HEAD` requests -pub struct Scope { +pub struct Scope { endpoint: T, rdef: String, app_data: Option, @@ -61,6 +61,7 @@ pub struct Scope { default: Option>, external: Vec, factory_ref: Rc>>, + _phantom: PhantomData, } impl Scope { @@ -77,19 +78,21 @@ impl Scope { default: None, external: Vec::new(), factory_ref, + _phantom: Default::default(), } } } -impl Scope +impl Scope where T: ServiceFactory< ServiceRequest, Config = (), - Response = ServiceResponse, + Response = ServiceResponse, Error = Error, InitError = (), >, + B: 'static, { /// Add match guard to a scope. /// @@ -295,32 +298,29 @@ where self } - /// Registers middleware, in the form of a middleware component (type), - /// that runs during inbound processing in the request - /// life-cycle (request -> response), modifying request as - /// necessary, across all requests managed by the *Scope*. Scope-level - /// middleware is more limited in what it can modify, relative to Route or - /// Application level middleware, in that Scope-level middleware can not modify - /// ServiceResponse. + /// Registers middleware, in the form of a middleware component (type), that runs during inbound + /// processing in the request life-cycle (request -> response), modifying request as necessary, + /// across all requests managed by the *Scope*. /// /// Use middleware when you need to read or modify *every* request in some way. - pub fn wrap( + pub fn wrap( self, mw: M, ) -> Scope< impl ServiceFactory< ServiceRequest, Config = (), - Response = ServiceResponse, + Response = ServiceResponse, Error = Error, InitError = (), >, + B1, > where M: Transform< T::Service, ServiceRequest, - Response = ServiceResponse, + Response = ServiceResponse, Error = Error, InitError = (), >, @@ -334,16 +334,15 @@ where default: self.default, external: self.external, factory_ref: self.factory_ref, + _phantom: PhantomData, } } - /// Registers middleware, in the form of a closure, that runs during inbound - /// processing in the request life-cycle (request -> response), modifying - /// request as necessary, across all requests managed by the *Scope*. - /// Scope-level middleware is more limited in what it can modify, relative - /// to Route or Application level middleware, in that Scope-level middleware - /// can not modify ServiceResponse. + /// Registers middleware, in the form of a closure, that runs during inbound processing in the + /// request life-cycle (request -> response), modifying request as necessary, across all + /// requests managed by the *Scope*. /// + /// # Examples /// ``` /// use actix_service::Service; /// use actix_web::{web, App}; @@ -369,21 +368,22 @@ where /// .route("/index.html", web::get().to(index))); /// } /// ``` - pub fn wrap_fn( + pub fn wrap_fn( self, mw: F, ) -> Scope< impl ServiceFactory< ServiceRequest, Config = (), - Response = ServiceResponse, + Response = ServiceResponse, Error = Error, InitError = (), >, + B1, > where F: Fn(ServiceRequest, &T::Service) -> R + Clone, - R: Future>, + R: Future, Error>>, { Scope { endpoint: apply_fn_factory(self.endpoint, mw), @@ -394,6 +394,7 @@ where default: self.default, external: self.external, factory_ref: self.factory_ref, + _phantom: PhantomData, } } } From d35b7644dccacdc851cd7c52a7bde92a2cd4e86a Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Tue, 7 Dec 2021 17:23:34 +0000 Subject: [PATCH 312/428] add connection level data container (#2491) --- CHANGES.md | 2 ++ actix-http/CHANGES.md | 3 ++ actix-http/src/extensions.rs | 37 ++++----------------- actix-http/src/h1/dispatcher.rs | 56 +++++++++++++++++--------------- actix-http/src/h1/service.rs | 12 ++----- actix-http/src/h2/dispatcher.rs | 13 ++++---- actix-http/src/h2/service.rs | 13 +++++--- actix-http/src/lib.rs | 10 +----- actix-http/src/request.rs | 30 ++++++++++++++++- actix-http/src/service.rs | 18 ++++------ actix-http/tests/test_openssl.rs | 4 +-- actix-http/tests/test_server.rs | 4 +-- examples/on_connect.rs | 25 ++++++++++---- src/app_service.rs | 5 ++- src/request.rs | 17 ++++++++++ src/server.rs | 4 +-- src/service.rs | 18 ++++++---- src/test.rs | 6 ++-- 18 files changed, 152 insertions(+), 125 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 2adf54d3d..2ef1478dc 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -7,6 +7,7 @@ * `Range` typed header. [#2485] * `HttpResponse::map_into_{left,right}_body` and `HttpResponse::map_into_boxed_body`. [#2468] * `ServiceResponse::map_into_{left,right}_body` and `HttpResponse::map_into_boxed_body`. [#2468] +* Connection data set through the `HttpServer::on_connect` callback is now accessible only from the new `HttpRequest::conn_data()` and `ServiceRequest::conn_data()` methods. [#2491] ### Changed * Rename `Accept::{mime_precedence => ranked}`. [#2480] @@ -27,6 +28,7 @@ [#2482]: https://github.com/actix/actix-web/pull/2482 [#2484]: https://github.com/actix/actix-web/pull/2484 [#2485]: https://github.com/actix/actix-web/pull/2485 +[#2491]: https://github.com/actix/actix-web/pull/2491 [#2492]: https://github.com/actix/actix-web/pull/2492 [#2493]: https://github.com/actix/actix-web/pull/2493 diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 1a59b233a..f435784d8 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -14,6 +14,8 @@ * `header::QualityItem::{max, min}`. [#2486] * `header::Quality::{MAX, MIN}`. [#2486] * `impl Display` for `header::Quality`. [#2486] +* Connection data set through the `on_connect_ext` callbacks is now accessible only from the new `Request::conn_data()` method. [#2491] +* `Request::take_conn_data()`. [#2491] ### Changed * Rename `body::BoxBody::{from_body => new}`. [#2468] @@ -39,6 +41,7 @@ [#1920]: https://github.com/actix/actix-web/pull/1920 [#2486]: https://github.com/actix/actix-web/pull/2486 [#2488]: https://github.com/actix/actix-web/pull/2488 +[#2491]: https://github.com/actix/actix-web/pull/2491 ## 3.0.0-beta.14 - 2021-11-30 diff --git a/actix-http/src/extensions.rs b/actix-http/src/extensions.rs index 5fdcefd6d..164919d87 100644 --- a/actix-http/src/extensions.rs +++ b/actix-http/src/extensions.rs @@ -1,6 +1,6 @@ use std::{ any::{Any, TypeId}, - fmt, mem, + fmt, }; use ahash::AHashMap; @@ -10,8 +10,7 @@ use ahash::AHashMap; /// All entries into this map must be owned types (or static references). #[derive(Default)] pub struct Extensions { - /// Use FxHasher with a std HashMap with for faster - /// lookups on the small `TypeId` (u64 equivalent) keys. + /// Use AHasher with a std HashMap with for faster lookups on the small `TypeId` keys. map: AHashMap>, } @@ -123,11 +122,6 @@ impl Extensions { pub fn extend(&mut self, other: Extensions) { self.map.extend(other.map); } - - /// Sets (or overrides) items from `other` into this map. - pub(crate) fn drain_from(&mut self, other: &mut Self) { - self.map.extend(mem::take(&mut other.map)); - } } impl fmt::Debug for Extensions { @@ -179,6 +173,8 @@ mod tests { #[test] fn test_integers() { + static A: u32 = 8; + let mut map = Extensions::new(); map.insert::(8); @@ -191,6 +187,7 @@ mod tests { map.insert::(32); map.insert::(64); map.insert::(128); + map.insert::<&'static u32>(&A); assert!(map.get::().is_some()); assert!(map.get::().is_some()); assert!(map.get::().is_some()); @@ -201,6 +198,7 @@ mod tests { assert!(map.get::().is_some()); assert!(map.get::().is_some()); assert!(map.get::().is_some()); + assert!(map.get::<&'static u32>().is_some()); } #[test] @@ -279,27 +277,4 @@ mod tests { assert_eq!(extensions.get(), Some(&20u8)); assert_eq!(extensions.get_mut(), Some(&mut 20u8)); } - - #[test] - fn test_drain_from() { - let mut ext = Extensions::new(); - ext.insert(2isize); - - let mut more_ext = Extensions::new(); - - more_ext.insert(5isize); - more_ext.insert(5usize); - - assert_eq!(ext.get::(), Some(&2isize)); - assert_eq!(ext.get::(), None); - assert_eq!(more_ext.get::(), Some(&5isize)); - assert_eq!(more_ext.get::(), Some(&5usize)); - - ext.drain_from(&mut more_ext); - - assert_eq!(ext.get::(), Some(&5isize)); - assert_eq!(ext.get::(), Some(&5usize)); - assert_eq!(more_ext.get::(), None); - assert_eq!(more_ext.get::(), None); - } } diff --git a/actix-http/src/h1/dispatcher.rs b/actix-http/src/h1/dispatcher.rs index 3c36e7367..b11054307 100644 --- a/actix-http/src/h1/dispatcher.rs +++ b/actix-http/src/h1/dispatcher.rs @@ -22,7 +22,7 @@ use crate::{ config::ServiceConfig, error::{DispatchError, ParseError, PayloadError}, service::HttpFlow, - OnConnectData, Request, Response, StatusCode, + Extensions, OnConnectData, Request, Response, StatusCode, }; use super::{ @@ -100,9 +100,9 @@ where U::Error: fmt::Display, { flow: Rc>, - on_connect_data: OnConnectData, flags: Flags, peer_addr: Option, + conn_data: Option>, error: Option, #[pin] @@ -179,10 +179,10 @@ where /// Create HTTP/1 dispatcher. pub(crate) fn new( io: T, - config: ServiceConfig, flow: Rc>, - on_connect_data: OnConnectData, + config: ServiceConfig, peer_addr: Option, + conn_data: OnConnectData, ) -> Self { let flags = if config.keep_alive_enabled() { Flags::KEEPALIVE @@ -198,20 +198,23 @@ where Dispatcher { inner: DispatcherState::Normal(InnerDispatcher { - read_buf: BytesMut::with_capacity(HW_BUFFER_SIZE), - write_buf: BytesMut::with_capacity(HW_BUFFER_SIZE), - payload: None, - state: State::None, - error: None, - messages: VecDeque::new(), - io: Some(io), - codec: Codec::new(config), flow, - on_connect_data, flags, peer_addr, + conn_data: conn_data.0.map(Rc::new), + error: None, + + state: State::None, + payload: None, + messages: VecDeque::new(), + ka_expire, ka_timer, + + io: Some(io), + read_buf: BytesMut::with_capacity(HW_BUFFER_SIZE), + write_buf: BytesMut::with_capacity(HW_BUFFER_SIZE), + codec: Codec::new(config), }), #[cfg(test)] @@ -593,8 +596,7 @@ where Message::Item(mut req) => { req.head_mut().peer_addr = *this.peer_addr; - // merge on_connect_ext data into request extensions - this.on_connect_data.merge_into(&mut req); + req.conn_data = this.conn_data.as_ref().map(Rc::clone); match this.codec.message_type() { // Request is upgradable. add upgrade message and break. @@ -1100,10 +1102,10 @@ mod tests { let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new( buf, - ServiceConfig::default(), services, - OnConnectData::default(), + ServiceConfig::default(), None, + OnConnectData::default(), ); actix_rt::pin!(h1); @@ -1140,10 +1142,10 @@ mod tests { let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new( buf, - cfg, services, - OnConnectData::default(), + cfg, None, + OnConnectData::default(), ); actix_rt::pin!(h1); @@ -1194,10 +1196,10 @@ mod tests { let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new( buf, - cfg, services, - OnConnectData::default(), + cfg, None, + OnConnectData::default(), ); actix_rt::pin!(h1); @@ -1244,10 +1246,10 @@ mod tests { let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new( buf.clone(), - cfg, services, - OnConnectData::default(), + cfg, None, + OnConnectData::default(), ); buf.extend_read_buf( @@ -1316,10 +1318,10 @@ mod tests { let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new( buf.clone(), - cfg, services, - OnConnectData::default(), + cfg, None, + OnConnectData::default(), ); buf.extend_read_buf( @@ -1393,10 +1395,10 @@ mod tests { let h1 = Dispatcher::<_, _, _, _, TestUpgrade>::new( buf.clone(), - cfg, services, - OnConnectData::default(), + cfg, None, + OnConnectData::default(), ); buf.extend_read_buf( diff --git a/actix-http/src/h1/service.rs b/actix-http/src/h1/service.rs index 70e83901c..fd9635690 100644 --- a/actix-http/src/h1/service.rs +++ b/actix-http/src/h1/service.rs @@ -365,15 +365,7 @@ where } fn call(&self, (io, addr): (T, Option)) -> Self::Future { - let on_connect_data = - OnConnectData::from_io(&io, self.on_connect_ext.as_deref()); - - Dispatcher::new( - io, - self.cfg.clone(), - self.flow.clone(), - on_connect_data, - addr, - ) + let conn_data = OnConnectData::from_io(&io, self.on_connect_ext.as_deref()); + Dispatcher::new(io, self.flow.clone(), self.cfg.clone(), addr, conn_data) } } diff --git a/actix-http/src/h2/dispatcher.rs b/actix-http/src/h2/dispatcher.rs index 6d2f4579a..22eab6c28 100644 --- a/actix-http/src/h2/dispatcher.rs +++ b/actix-http/src/h2/dispatcher.rs @@ -27,7 +27,7 @@ use crate::{ body::{BodySize, BoxBody, MessageBody}, config::ServiceConfig, service::HttpFlow, - OnConnectData, Payload, Request, Response, ResponseHead, + Extensions, OnConnectData, Payload, Request, Response, ResponseHead, }; const CHUNK_SIZE: usize = 16_384; @@ -37,7 +37,7 @@ pin_project! { pub struct Dispatcher { flow: Rc>, connection: Connection, - on_connect_data: OnConnectData, + conn_data: Option>, config: ServiceConfig, peer_addr: Option, ping_pong: Option, @@ -50,11 +50,11 @@ where T: AsyncRead + AsyncWrite + Unpin, { pub(crate) fn new( - flow: Rc>, mut conn: Connection, - on_connect_data: OnConnectData, + flow: Rc>, config: ServiceConfig, peer_addr: Option, + conn_data: OnConnectData, timer: Option>>, ) -> Self { let ping_pong = config.keep_alive().map(|dur| H2PingPong { @@ -74,7 +74,7 @@ where config, peer_addr, connection: conn, - on_connect_data, + conn_data: conn_data.0.map(Rc::new), ping_pong, _phantom: PhantomData, } @@ -119,8 +119,7 @@ where head.headers = parts.headers.into(); head.peer_addr = this.peer_addr; - // merge on_connect_ext data into request extensions - this.on_connect_data.merge_into(&mut req); + req.conn_data = this.conn_data.as_ref().map(Rc::clone); let fut = this.flow.service.call(req); let config = this.config.clone(); diff --git a/actix-http/src/h2/service.rs b/actix-http/src/h2/service.rs index 8a9061b94..aa2a6cc69 100644 --- a/actix-http/src/h2/service.rs +++ b/actix-http/src/h2/service.rs @@ -1,7 +1,7 @@ use std::{ future::Future, marker::PhantomData, - net, + mem, net, pin::Pin, rc::Rc, task::{Context, Poll}, @@ -339,21 +339,24 @@ where ref mut srv, ref mut config, ref peer_addr, - ref mut on_connect_data, + ref mut conn_data, ref mut handshake, ) => match ready!(Pin::new(handshake).poll(cx)) { Ok((conn, timer)) => { - let on_connect_data = std::mem::take(on_connect_data); + let on_connect_data = mem::take(conn_data); + self.state = State::Incoming(Dispatcher::new( - srv.take().unwrap(), conn, - on_connect_data, + srv.take().unwrap(), config.take().unwrap(), *peer_addr, + on_connect_data, timer, )); + self.poll(cx) } + Err(err) => { trace!("H2 handshake error: {}", err); Poll::Ready(Err(err)) diff --git a/actix-http/src/lib.rs b/actix-http/src/lib.rs index aeba3da36..89ee139c0 100644 --- a/actix-http/src/lib.rs +++ b/actix-http/src/lib.rs @@ -92,19 +92,11 @@ impl OnConnectData { on_connect_ext: Option<&ConnectCallback>, ) -> Self { let ext = on_connect_ext.map(|handler| { - let mut extensions = Extensions::new(); + let mut extensions = Extensions::default(); handler(io, &mut extensions); extensions }); Self(ext) } - - /// Merge self into given request's extensions. - #[inline] - pub(crate) fn merge_into(&mut self, req: &mut Request) { - if let Some(ref mut ext) = self.0 { - req.head.extensions.get_mut().drain_from(ext); - } - } } diff --git a/actix-http/src/request.rs b/actix-http/src/request.rs index 401e9745c..78c0527b5 100644 --- a/actix-http/src/request.rs +++ b/actix-http/src/request.rs @@ -2,7 +2,9 @@ use std::{ cell::{Ref, RefMut}, - fmt, net, str, + fmt, net, + rc::Rc, + str, }; use http::{header, Method, Uri, Version}; @@ -19,6 +21,7 @@ use crate::{ pub struct Request

{ pub(crate) payload: Payload

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

HttpMessage for Request

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

Request

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

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

Request

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

fmt::Debug for Request

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

{ pub(crate) payload: Payload

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

HttpMessage for Request

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

HttpMessage for Request

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

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

Request

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

Request

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

Request

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

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

Request

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

Request

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

Request

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

fmt::Debug for Request

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

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

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

Request

{ /// Returns the request data container, leaving an empty one in it's place. pub fn take_req_data(&mut self) -> Extensions { - mem::take(&mut self.req_data.get_mut()) + mem::take(self.req_data.get_mut()) } } diff --git a/actix-http/src/response_builder.rs b/actix-http/src/responses/builder.rs similarity index 99% rename from actix-http/src/response_builder.rs rename to actix-http/src/responses/builder.rs index adbe86fca..5854863de 100644 --- a/actix-http/src/response_builder.rs +++ b/actix-http/src/responses/builder.rs @@ -9,8 +9,8 @@ use crate::{ body::{EitherBody, MessageBody}, error::{Error, HttpError}, header::{self, TryIntoHeaderPair, TryIntoHeaderValue}, - message::{BoxedResponseHead, ConnectionType, ResponseHead}, - Extensions, Response, StatusCode, + responses::{BoxedResponseHead, ResponseHead}, + ConnectionType, Extensions, Response, StatusCode, }; /// An HTTP response builder. diff --git a/actix-http/src/responses/head.rs b/actix-http/src/responses/head.rs new file mode 100644 index 000000000..78d9536e5 --- /dev/null +++ b/actix-http/src/responses/head.rs @@ -0,0 +1,208 @@ +//! Response head type and caching pool. + +use std::{ + cell::{Ref, RefCell, RefMut}, + ops, +}; + +use crate::{ + header::HeaderMap, message::Flags, ConnectionType, Extensions, StatusCode, Version, +}; + +thread_local! { + static RESPONSE_POOL: BoxedResponsePool = BoxedResponsePool::create(); +} + +#[derive(Debug)] +pub struct ResponseHead { + pub version: Version, + pub status: StatusCode, + pub headers: HeaderMap, + pub reason: Option<&'static str>, + pub(crate) extensions: RefCell, + flags: Flags, +} + +impl ResponseHead { + /// Create new instance of `ResponseHead` type + #[inline] + pub fn new(status: StatusCode) -> ResponseHead { + ResponseHead { + status, + version: Version::HTTP_11, + headers: HeaderMap::with_capacity(12), + reason: None, + flags: Flags::empty(), + extensions: RefCell::new(Extensions::new()), + } + } + + #[inline] + /// Read the message headers. + pub fn headers(&self) -> &HeaderMap { + &self.headers + } + + #[inline] + /// Mutable reference to the message headers. + pub fn headers_mut(&mut self) -> &mut HeaderMap { + &mut self.headers + } + + /// Message extensions + #[inline] + pub fn extensions(&self) -> Ref<'_, Extensions> { + self.extensions.borrow() + } + + /// Mutable reference to a the message's extensions + #[inline] + pub fn extensions_mut(&self) -> RefMut<'_, Extensions> { + self.extensions.borrow_mut() + } + + #[inline] + /// Set connection type of the message + pub fn set_connection_type(&mut self, ctype: ConnectionType) { + match ctype { + ConnectionType::Close => self.flags.insert(Flags::CLOSE), + ConnectionType::KeepAlive => self.flags.insert(Flags::KEEP_ALIVE), + ConnectionType::Upgrade => self.flags.insert(Flags::UPGRADE), + } + } + + #[inline] + pub fn connection_type(&self) -> ConnectionType { + if self.flags.contains(Flags::CLOSE) { + ConnectionType::Close + } else if self.flags.contains(Flags::KEEP_ALIVE) { + ConnectionType::KeepAlive + } else if self.flags.contains(Flags::UPGRADE) { + ConnectionType::Upgrade + } else if self.version < Version::HTTP_11 { + ConnectionType::Close + } else { + ConnectionType::KeepAlive + } + } + + /// Check if keep-alive is enabled + #[inline] + pub fn keep_alive(&self) -> bool { + self.connection_type() == ConnectionType::KeepAlive + } + + /// Check upgrade status of this message + #[inline] + pub fn upgrade(&self) -> bool { + self.connection_type() == ConnectionType::Upgrade + } + + /// Get custom reason for the response + #[inline] + pub fn reason(&self) -> &str { + self.reason.unwrap_or_else(|| { + self.status + .canonical_reason() + .unwrap_or("") + }) + } + + #[inline] + pub(crate) fn conn_type(&self) -> Option { + if self.flags.contains(Flags::CLOSE) { + Some(ConnectionType::Close) + } else if self.flags.contains(Flags::KEEP_ALIVE) { + Some(ConnectionType::KeepAlive) + } else if self.flags.contains(Flags::UPGRADE) { + Some(ConnectionType::Upgrade) + } else { + None + } + } + + #[inline] + /// Get response body chunking state + pub fn chunked(&self) -> bool { + !self.flags.contains(Flags::NO_CHUNKING) + } + + #[inline] + /// Set no chunking for payload + pub fn no_chunking(&mut self, val: bool) { + if val { + self.flags.insert(Flags::NO_CHUNKING); + } else { + self.flags.remove(Flags::NO_CHUNKING); + } + } +} + +pub(crate) struct BoxedResponseHead { + head: Option>, +} + +impl BoxedResponseHead { + /// Get new message from the pool of objects + pub fn new(status: StatusCode) -> Self { + RESPONSE_POOL.with(|p| p.get_message(status)) + } +} + +impl ops::Deref for BoxedResponseHead { + type Target = ResponseHead; + + fn deref(&self) -> &Self::Target { + self.head.as_ref().unwrap() + } +} + +impl ops::DerefMut for BoxedResponseHead { + fn deref_mut(&mut self) -> &mut Self::Target { + self.head.as_mut().unwrap() + } +} + +impl Drop for BoxedResponseHead { + fn drop(&mut self) { + if let Some(head) = self.head.take() { + RESPONSE_POOL.with(move |p| p.release(head)) + } + } +} + +/// Request's objects pool +#[doc(hidden)] +pub struct BoxedResponsePool(#[allow(clippy::vec_box)] RefCell>>); + +impl BoxedResponsePool { + fn create() -> BoxedResponsePool { + BoxedResponsePool(RefCell::new(Vec::with_capacity(128))) + } + + /// Get message from the pool + #[inline] + fn get_message(&self, status: StatusCode) -> BoxedResponseHead { + if let Some(mut head) = self.0.borrow_mut().pop() { + head.reason = None; + head.status = status; + head.headers.clear(); + head.flags = Flags::empty(); + BoxedResponseHead { head: Some(head) } + } else { + BoxedResponseHead { + head: Some(Box::new(ResponseHead::new(status))), + } + } + } + + /// Release request instance + #[inline] + fn release(&self, mut msg: Box) { + let pool = &mut self.0.borrow_mut(); + if pool.len() < 128 { + msg.extensions.get_mut().clear(); + pool.push(msg); + } + } +} diff --git a/actix-http/src/responses/mod.rs b/actix-http/src/responses/mod.rs new file mode 100644 index 000000000..899232b9f --- /dev/null +++ b/actix-http/src/responses/mod.rs @@ -0,0 +1,11 @@ +//! HTTP response. + +mod builder; +mod head; +#[allow(clippy::module_inception)] +mod response; + +pub use self::builder::ResponseBuilder; +pub(crate) use self::head::BoxedResponseHead; +pub use self::head::ResponseHead; +pub use self::response::Response; diff --git a/actix-http/src/response.rs b/actix-http/src/responses/response.rs similarity index 99% rename from actix-http/src/response.rs rename to actix-http/src/responses/response.rs index a0e6d9b7c..d781bdfaa 100644 --- a/actix-http/src/response.rs +++ b/actix-http/src/responses/response.rs @@ -12,8 +12,8 @@ use crate::{ body::{BoxBody, MessageBody}, extensions::Extensions, header::{self, HeaderMap, TryIntoHeaderValue}, - message::{BoxedResponseHead, ResponseHead}, - Error, ResponseBuilder, StatusCode, + responses::{BoxedResponseHead, ResponseBuilder, ResponseHead}, + Error, StatusCode, }; /// An HTTP response. diff --git a/actix-http/src/ws/mod.rs b/actix-http/src/ws/mod.rs index c23d4edfc..6491da149 100644 --- a/actix-http/src/ws/mod.rs +++ b/actix-http/src/ws/mod.rs @@ -9,7 +9,7 @@ use derive_more::{Display, Error, From}; use http::{header, Method, StatusCode}; use crate::body::BoxBody; -use crate::{header::HeaderValue, message::RequestHead, response::Response, ResponseBuilder}; +use crate::{header::HeaderValue, RequestHead, Response, ResponseBuilder}; mod codec; mod dispatcher; diff --git a/src/app_service.rs b/src/app_service.rs index 515693db4..4e84cb201 100644 --- a/src/app_service.rs +++ b/src/app_service.rs @@ -221,6 +221,7 @@ where req_data, ) }; + self.service.call(ServiceRequest::new(req, payload)) } } From 64c2e5e1cd4c3c2315139fafa7f7e28b2ac6de07 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Wed, 22 Dec 2021 07:16:07 +0000 Subject: [PATCH 364/428] remove crate level clippy lint --- actix-http/src/builder.rs | 1 + actix-http/src/lib.rs | 7 +++---- actix-http/src/message.rs | 1 + actix-http/src/payload.rs | 6 ++++-- actix-http/src/requests/request.rs | 1 + actix-http/src/responses/response.rs | 5 ++--- 6 files changed, 12 insertions(+), 9 deletions(-) diff --git a/actix-http/src/builder.rs b/actix-http/src/builder.rs index 1b5da20b6..408ee7924 100644 --- a/actix-http/src/builder.rs +++ b/actix-http/src/builder.rs @@ -36,6 +36,7 @@ where >::Future: 'static, { /// Create instance of `ServiceConfigBuilder` + #[allow(clippy::new_without_default)] pub fn new() -> Self { HttpServiceBuilder { keep_alive: KeepAlive::Timeout(5), diff --git a/actix-http/src/lib.rs b/actix-http/src/lib.rs index ae822a055..2b7bc730b 100644 --- a/actix-http/src/lib.rs +++ b/actix-http/src/lib.rs @@ -19,7 +19,6 @@ #![allow( clippy::type_complexity, clippy::too_many_arguments, - clippy::new_without_default, clippy::borrow_interior_mutable_const )] #![doc(html_logo_url = "https://actix.rs/img/logo.png")] @@ -28,6 +27,9 @@ #[macro_use] extern crate log; +pub use ::http::{uri, uri::Uri}; +pub use ::http::{Method, StatusCode, Version}; + pub mod body; mod builder; mod config; @@ -61,9 +63,6 @@ pub use self::requests::{Request, RequestHead, RequestHeadType}; pub use self::responses::{Response, ResponseBuilder, ResponseHead}; pub use self::service::HttpService; -pub use ::http::{uri, uri::Uri}; -pub use ::http::{Method, StatusCode, Version}; - /// A major HTTP protocol version. #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] #[non_exhaustive] diff --git a/actix-http/src/message.rs b/actix-http/src/message.rs index 2692a4bee..34213f68a 100644 --- a/actix-http/src/message.rs +++ b/actix-http/src/message.rs @@ -43,6 +43,7 @@ pub struct Message { impl Message { /// Get new message from the pool of objects + #[allow(clippy::new_without_default)] pub fn new() -> Self { T::with_pool(MessagePool::get_message) } diff --git a/actix-http/src/payload.rs b/actix-http/src/payload.rs index 89a1a2db1..69840e7c1 100644 --- a/actix-http/src/payload.rs +++ b/actix-http/src/payload.rs @@ -1,5 +1,7 @@ -use std::pin::Pin; -use std::task::{Context, Poll}; +use std::{ + pin::Pin, + task::{Context, Poll}, +}; use bytes::Bytes; use futures_core::Stream; diff --git a/actix-http/src/requests/request.rs b/actix-http/src/requests/request.rs index 74347fbc2..0254a8f11 100644 --- a/actix-http/src/requests/request.rs +++ b/actix-http/src/requests/request.rs @@ -59,6 +59,7 @@ impl From> for Request { impl Request { /// Create new Request instance + #[allow(clippy::new_without_default)] pub fn new() -> Request { Request { head: Message::new(), diff --git a/actix-http/src/responses/response.rs b/actix-http/src/responses/response.rs index d781bdfaa..ec9157afb 100644 --- a/actix-http/src/responses/response.rs +++ b/actix-http/src/responses/response.rs @@ -10,10 +10,9 @@ use bytestring::ByteString; use crate::{ body::{BoxBody, MessageBody}, - extensions::Extensions, header::{self, HeaderMap, TryIntoHeaderValue}, - responses::{BoxedResponseHead, ResponseBuilder, ResponseHead}, - Error, StatusCode, + responses::BoxedResponseHead, + Error, Extensions, ResponseBuilder, ResponseHead, StatusCode, }; /// An HTTP response. From f8488aff1e348fca884ebe0f58b90be82a24a2e5 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Wed, 22 Dec 2021 07:20:53 +0000 Subject: [PATCH 365/428] upstream changelog for v3.3.3 --- CHANGES.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index 857974d3f..77ab2e218 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -318,6 +318,14 @@ [#1875]: https://github.com/actix/actix-web/pull/1875 [#1878]: https://github.com/actix/actix-web/pull/1878 + +## 3.3.3 - 2021-12-18 +### Changed +* Soft-deprecate `NormalizePath::default()`, noting upcoming behavior change in v4. [#2529] + +[#2529]: https://github.com/actix/actix-web/pull/2529 + + ## 3.3.2 - 2020-12-01 ### Fixed * Removed an occasional `unwrap` on `None` panic in `NormalizePathNormalization`. [#1762] From 40a016207461d2ec2167f5614709da85994216e3 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Wed, 22 Dec 2021 07:58:37 +0000 Subject: [PATCH 366/428] add tests to scope and resource for returning from fns --- src/app.rs | 36 ++++++++++++++++++------------------ src/resource.rs | 32 +++++++++++++++++++++++++++++++- src/scope.rs | 31 ++++++++++++++++++++++++++++++- 3 files changed, 79 insertions(+), 20 deletions(-) diff --git a/src/app.rs b/src/app.rs index 6bccc1ff1..b4b952734 100644 --- a/src/app.rs +++ b/src/app.rs @@ -709,24 +709,24 @@ mod tests { assert_eq!(body, Bytes::from_static(b"https://youtube.com/watch/12345")); } - /// compile-only test for returning app type from function - pub fn foreign_app_type() -> App< - impl ServiceFactory< - ServiceRequest, - Response = ServiceResponse, - Config = (), - InitError = (), - Error = Error, - >, - > { - App::new() - // logger can be removed without affecting the return type - .wrap(crate::middleware::Logger::default()) - .route("/", web::to(|| async { "hello" })) - } - #[test] - fn return_foreign_app_type() { - let _app = foreign_app_type(); + fn can_be_returned_from_fn() { + /// compile-only test for returning app type from function + pub fn my_app() -> App< + impl ServiceFactory< + ServiceRequest, + Response = ServiceResponse, + Config = (), + InitError = (), + Error = Error, + >, + > { + App::new() + // logger can be removed without affecting the return type + .wrap(crate::middleware::Logger::default()) + .route("/", web::to(|| async { "hello" })) + } + + let _ = init_service(my_app()); } } diff --git a/src/resource.rs b/src/resource.rs index 53104930a..c13544063 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -505,18 +505,48 @@ mod tests { use actix_service::Service; use actix_utils::future::ok; + use super::*; use crate::{ guard, http::{ header::{self, HeaderValue}, Method, StatusCode, }, - middleware::DefaultHeaders, + middleware::{Compat, DefaultHeaders}, service::{ServiceRequest, ServiceResponse}, test::{call_service, init_service, TestRequest}, web, App, Error, HttpMessage, HttpResponse, }; + #[test] + fn can_be_returned_from_fn() { + fn my_resource() -> Resource { + web::resource("/test").route(web::get().to(|| async { "hello" })) + } + + fn my_compat_resource() -> Resource< + impl ServiceFactory< + ServiceRequest, + Config = (), + Response = ServiceResponse, + Error = Error, + InitError = (), + >, + > { + web::resource("/test-compat") + // .wrap_fn(|req, srv| { + // let fut = srv.call(req); + // async { Ok(fut.await?.map_into_right_body::<()>()) } + // }) + .wrap(Compat::new(DefaultHeaders::new())) + .route(web::get().to(|| async { "hello" })) + } + + App::new() + .service(my_resource()) + .service(my_compat_resource()); + } + #[actix_rt::test] async fn test_middleware() { let srv = init_service( diff --git a/src/scope.rs b/src/scope.rs index c35584770..35bbb50ba 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -586,18 +586,47 @@ mod tests { use actix_utils::future::ok; use bytes::Bytes; + use super::*; use crate::{ guard, http::{ header::{self, HeaderValue}, Method, StatusCode, }, - middleware::DefaultHeaders, + middleware::{Compat, DefaultHeaders}, service::{ServiceRequest, ServiceResponse}, test::{assert_body_eq, call_service, init_service, read_body, TestRequest}, web, App, HttpMessage, HttpRequest, HttpResponse, }; + #[test] + fn can_be_returned_from_fn() { + fn my_scope() -> Scope { + web::scope("/test") + .service(web::resource("").route(web::get().to(|| async { "hello" }))) + } + + fn my_compat_scope() -> Scope< + impl ServiceFactory< + ServiceRequest, + Config = (), + Response = ServiceResponse, + Error = Error, + InitError = (), + >, + > { + web::scope("/test-compat") + // .wrap_fn(|req, srv| { + // let fut = srv.call(req); + // async { Ok(fut.await?.map_into_right_body::<()>()) } + // }) + .wrap(Compat::new(DefaultHeaders::new())) + .service(web::resource("").route(web::get().to(|| async { "hello" }))) + } + + App::new().service(my_scope()).service(my_compat_scope()); + } + #[actix_rt::test] async fn test_scope() { let srv = From 1ea619f2a1722206cddf4af0a43715fc8202a06e Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Wed, 22 Dec 2021 08:17:35 +0000 Subject: [PATCH 367/428] use dash hyphenation in changelogs --- CHANGES.md | 551 ++++++++++++++++++----------------- actix-files/CHANGES.md | 102 +++---- actix-http-test/CHANGES.md | 76 ++--- actix-http/CHANGES.md | 544 +++++++++++++++++----------------- actix-multipart/CHANGES.md | 74 ++--- actix-router/CHANGES.md | 102 +++---- actix-test/CHANGES.md | 22 +- actix-web-actors/CHANGES.md | 60 ++-- actix-web-codegen/CHANGES.md | 52 ++-- awc/CHANGES.md | 170 +++++------ 10 files changed, 877 insertions(+), 876 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 77ab2e218..0458958c5 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -2,29 +2,29 @@ ## Unreleased - 2021-xx-xx - +- ## 4.0.0-beta.15 - 2021-12-17 ### Added * Method on `Responder` trait (`customize`) for customizing responders and `CustomizeResponder` struct. [#2510] * Implement `Debug` for `DefaultHeaders`. [#2510] ### Changed -* Align `DefaultHeader` method terminology, deprecating previous methods. [#2510] -* Response service types in `ErrorHandlers` middleware now use `ServiceResponse>` to allow changing the body type. [#2515] +- Align `DefaultHeader` method terminology, deprecating previous methods. [#2510] +- Response service types in `ErrorHandlers` middleware now use `ServiceResponse>` to allow changing the body type. [#2515] * Both variants in `ErrorHandlerResponse` now use `ServiceResponse>`. [#2515] * Rename `test::{default_service => simple_service}`. Old name is deprecated. [#2518] -* Rename `test::{read_response_json => call_and_read_body_json}`. Old name is deprecated. [#2518] -* Rename `test::{read_response => call_and_read_body}`. Old name is deprecated. [#2518] -* Relax body type and error bounds on test utilities. [#2518] - -### Removed -* Top-level `EitherExtractError` export. [#2510] -* Conversion implementations for `either` crate. [#2516] +- Rename `test::{read_response_json => call_and_read_body_json}`. Old name is deprecated. [#2518] +- Rename `test::{read_response => call_and_read_body}`. Old name is deprecated. [#2518] +- Relax body type and error bounds on test utilities. [#2518] +- +- # Removed +- Top-level `EitherExtractError` export. [#2510] +- Conversion implementations for `either` crate. [#2516] * `test::load_stream` and `test::load_body`; replace usage with `body::to_bytes`. [#2518] -[#2510]: https://github.com/actix/actix-web/pull/2510 -[#2515]: https://github.com/actix/actix-web/pull/2515 -[#2516]: https://github.com/actix/actix-web/pull/2516 +- 2510]: https://github.com/actix/actix-web/pull/2510 +- 2515]: https://github.com/actix/actix-web/pull/2515 +- 2516]: https://github.com/actix/actix-web/pull/2516 [#2518]: https://github.com/actix/actix-web/pull/2518 @@ -34,31 +34,31 @@ * `AcceptEncoding` typed header. [#2482] * `Range` typed header. [#2485] * `HttpResponse::map_into_{left,right}_body` and `HttpResponse::map_into_boxed_body`. [#2468] -* `ServiceResponse::map_into_{left,right}_body` and `HttpResponse::map_into_boxed_body`. [#2468] -* Connection data set through the `HttpServer::on_connect` callback is now accessible only from the new `HttpRequest::conn_data()` and `ServiceRequest::conn_data()` methods. [#2491] -* `HttpRequest::{req_data,req_data_mut}`. [#2487] -* `ServiceResponse::into_parts`. [#2499] - -### Changed -* Rename `Accept::{mime_precedence => ranked}`. [#2480] -* Rename `Accept::{mime_preference => preference}`. [#2480] +- `ServiceResponse::map_into_{left,right}_body` and `HttpResponse::map_into_boxed_body`. [#2468] +- Connection data set through the `HttpServer::on_connect` callback is now accessible only from the new `HttpRequest::conn_data()` and `ServiceRequest::conn_data()` methods. [#2491] +- `HttpRequest::{req_data,req_data_mut}`. [#2487] +- `ServiceResponse::into_parts`. [#2499] +- +- # Changed +- Rename `Accept::{mime_precedence => ranked}`. [#2480] +- Rename `Accept::{mime_preference => preference}`. [#2480] * Un-deprecate `App::data_factory`. [#2484] * `HttpRequest::url_for` no longer constructs URLs with query or fragment components. [#2430] -* Remove `B` (body) type parameter on `App`. [#2493] -* Add `B` (body) type parameter on `Scope`. [#2492] -* Request-local data container is no longer part of a `RequestHead`. Instead it is a distinct part of a `Request`. [#2487] - -### Fixed -* Accept wildcard `*` items in `AcceptLanguage`. [#2480] -* Re-exports `dev::{BodySize, MessageBody, SizedStream}`. They are exposed through the `body` module. [#2468] +- Remove `B` (body) type parameter on `App`. [#2493] +- Add `B` (body) type parameter on `Scope`. [#2492] +- Request-local data container is no longer part of a `RequestHead`. Instead it is a distinct part of a `Request`. [#2487] +- +- # Fixed +- Accept wildcard `*` items in `AcceptLanguage`. [#2480] +- Re-exports `dev::{BodySize, MessageBody, SizedStream}`. They are exposed through the `body` module. [#2468] * Typed headers containing lists that require one or more items now enforce this minimum. [#2482] -### Removed -* `ConnectionInfo::get`. [#2487] - +- # Removed +- `ConnectionInfo::get`. [#2487] +- [#2430]: https://github.com/actix/actix-web/pull/2430 [#2468]: https://github.com/actix/actix-web/pull/2468 -[#2480]: https://github.com/actix/actix-web/pull/2480 +- 2480]: https://github.com/actix/actix-web/pull/2480 [#2482]: https://github.com/actix/actix-web/pull/2482 [#2484]: https://github.com/actix/actix-web/pull/2484 [#2485]: https://github.com/actix/actix-web/pull/2485 @@ -75,20 +75,20 @@ [#2474]: https://github.com/actix/actix-web/pull/2474 - +- ## 4.0.0-beta.12 - 2021-11-22 ### Changed * Compress middleware's response type is now `AnyBody>`. [#2448] ### Fixed * Relax `Unpin` bound on `S` (stream) parameter of `HttpResponseBuilder::streaming`. [#2448] - +- ### Removed * `dev::ResponseBody` re-export; is function is replaced by the new `dev::AnyBody` enum. [#2446] - +- [#2446]: https://github.com/actix/actix-web/pull/2446 [#2448]: https://github.com/actix/actix-web/pull/2448 - +- ## 4.0.0-beta.11 - 2021-11-15 ### Added @@ -96,11 +96,11 @@ ### Changed * `ContentType::html` now produces `text/html; charset=utf-8` instead of `text/html`. [#2423] -* Update `actix-server` to `2.0.0-beta.9`. [#2442] +- Update `actix-server` to `2.0.0-beta.9`. [#2442] [#2423]: https://github.com/actix/actix-web/pull/2423 -[#2442]: https://github.com/actix/actix-web/pull/2442 - +- 2442]: https://github.com/actix/actix-web/pull/2442 +- ## 4.0.0-beta.10 - 2021-10-20 ### Added @@ -108,18 +108,18 @@ * `#[actix_web::test]` macro for setting up tests with a runtime. [#2409] ### Changed -* Associated type `FromRequest::Config` was removed. [#2233] -* Inner field made private on `web::Payload`. [#2384] +- Associated type `FromRequest::Config` was removed. [#2233] +- Inner field made private on `web::Payload`. [#2384] * `Data::into_inner` and `Data::get_ref` no longer requires `T: Sized`. [#2403] * Updated rustls to v0.20. [#2414] -* Minimum supported Rust version (MSRV) is now 1.52. - -### Removed -* Useless `ServiceResponse::checked_expr` method. [#2401] - +- Minimum supported Rust version (MSRV) is now 1.52. +- +- # Removed +- Useless `ServiceResponse::checked_expr` method. [#2401] +- [#2233]: https://github.com/actix/actix-web/pull/2233 [#2362]: https://github.com/actix/actix-web/pull/2362 -[#2384]: https://github.com/actix/actix-web/pull/2384 +- 2384]: https://github.com/actix/actix-web/pull/2384 [#2401]: https://github.com/actix/actix-web/pull/2401 [#2403]: https://github.com/actix/actix-web/pull/2403 [#2409]: https://github.com/actix/actix-web/pull/2409 @@ -132,17 +132,17 @@ ### Changed * Compress middleware will return 406 Not Acceptable when no content encoding is acceptable to the client. [#2344] -* Move `BaseHttpResponse` to `dev::Response`. [#2379] +- Move `BaseHttpResponse` to `dev::Response`. [#2379] * Enable `TestRequest::param` to accept more than just static strings. [#2172] * Minimum supported Rust version (MSRV) is now 1.51. - -### Fixed -* Fix quality parse error in Accept-Encoding header. [#2344] -* Re-export correct type at `web::HttpResponse`. [#2379] +- +- # Fixed +- Fix quality parse error in Accept-Encoding header. [#2344] +- Re-export correct type at `web::HttpResponse`. [#2379] [#2172]: https://github.com/actix/actix-web/pull/2172 -[#2325]: https://github.com/actix/actix-web/pull/2325 -[#2344]: https://github.com/actix/actix-web/pull/2344 +- 2325]: https://github.com/actix/actix-web/pull/2325 +- 2344]: https://github.com/actix/actix-web/pull/2344 [#2379]: https://github.com/actix/actix-web/pull/2379 @@ -152,18 +152,18 @@ * Add extractors for `Uri` and `Method`. [#2263] * Add extractors for `ConnectionInfo` and `PeerAddr`. [#2263] * Add `Route::service` for using hand-written services as handlers. [#2262] - -### Changed -* Change compression algorithm features flags. [#2250] -* Deprecate `App::data` and `App::data_factory`. [#2271] +- +- # Changed +- Change compression algorithm features flags. [#2250] +- Deprecate `App::data` and `App::data_factory`. [#2271] * Smarter extraction of `ConnectionInfo` parts. [#2282] -### Fixed -* Scope and Resource middleware can access data items set on their own layer. [#2288] - +- # Fixed +- Scope and Resource middleware can access data items set on their own layer. [#2288] +- [#2177]: https://github.com/actix/actix-web/pull/2177 [#2250]: https://github.com/actix/actix-web/pull/2250 -[#2271]: https://github.com/actix/actix-web/pull/2271 +- 2271]: https://github.com/actix/actix-web/pull/2271 [#2262]: https://github.com/actix/actix-web/pull/2262 [#2263]: https://github.com/actix/actix-web/pull/2263 [#2282]: https://github.com/actix/actix-web/pull/2282 @@ -176,23 +176,23 @@ ### Changed * Adjusted default JSON payload limit to 2MB (from 32kb) and included size and limits in the `JsonPayloadError::Overflow` error variant. [#2162] -[#2162]: (https://github.com/actix/actix-web/pull/2162) +- 2162]: (https://github.com/actix/actix-web/pull/2162) * `ServiceResponse::error_response` now uses body type of `Body`. [#2201] * `ServiceResponse::checked_expr` now returns a `Result`. [#2201] -* Update `language-tags` to `0.3`. +- Update `language-tags` to `0.3`. * `ServiceResponse::take_body`. [#2201] -* `ServiceResponse::map_body` closure receives and returns `B` instead of `ResponseBody` types. [#2201] -* All error trait bounds in server service builders have changed from `Into` to `Into>`. [#2253] -* All error trait bounds in message body and stream impls changed from `Into` to `Into>`. [#2253] -* `HttpServer::{listen_rustls(), bind_rustls()}` now honor the ALPN protocols in the configuation parameter. [#2226] -* `middleware::normalize` now will not try to normalize URIs with no valid path [#2246] - -### Removed -* `HttpResponse::take_body` and old `HttpResponse::into_body` method that casted body type. [#2201] - +- `ServiceResponse::map_body` closure receives and returns `B` instead of `ResponseBody` types. [#2201] +- All error trait bounds in server service builders have changed from `Into` to `Into>`. [#2253] +- All error trait bounds in message body and stream impls changed from `Into` to `Into>`. [#2253] +- `HttpServer::{listen_rustls(), bind_rustls()}` now honor the ALPN protocols in the configuation parameter. [#2226] +- `middleware::normalize` now will not try to normalize URIs with no valid path [#2246] +- +- # Removed +- `HttpResponse::take_body` and old `HttpResponse::into_body` method that casted body type. [#2201] +- [#2200]: https://github.com/actix/actix-web/pull/2200 [#2201]: https://github.com/actix/actix-web/pull/2201 -[#2253]: https://github.com/actix/actix-web/pull/2253 +- 2253]: https://github.com/actix/actix-web/pull/2253 [#2246]: https://github.com/actix/actix-web/pull/2246 @@ -202,11 +202,11 @@ ### Changed * Most error types are now marked `#[non_exhaustive]`. [#2148] -* Methods on `ContentDisposition` that took `T: AsRef` now take `impl AsRef`. +- Methods on `ContentDisposition` that took `T: AsRef` now take `impl AsRef`. [#2065]: https://github.com/actix/actix-web/pull/2065 -[#2148]: https://github.com/actix/actix-web/pull/2148 - +- 2148]: https://github.com/actix/actix-web/pull/2148 +- ## 4.0.0-beta.5 - 2021-04-02 ### Added @@ -214,20 +214,20 @@ * Added `TestServer::client_headers` method. [#2097] ### Fixed -* Double ampersand in Logger format is escaped correctly. [#2067] - +- Double ampersand in Logger format is escaped correctly. [#2067] +- ### Changed * `CustomResponder` would return error as `HttpResponse` when `CustomResponder::with_header` failed - instead of skipping. (Only the first error is kept when multiple error occur) [#2093] +- instead of skipping. (Only the first error is kept when multiple error occur) [#2093] ### Removed -* The `client` mod was removed. Clients should now use `awc` directly. +- The `client` mod was removed. Clients should now use `awc` directly. [871ca5e4](https://github.com/actix/actix-web/commit/871ca5e4ae2bdc22d1ea02701c2992fa8d04aed7) * Integration testing was moved to new `actix-test` crate. Namely these items from the `test` module: `TestServer`, `TestServerConfig`, `start`, `start_with`, and `unused_addr`. [#2112] - +- [#2067]: https://github.com/actix/actix-web/pull/2067 -[#2093]: https://github.com/actix/actix-web/pull/2093 +- 2093]: https://github.com/actix/actix-web/pull/2093 [#2094]: https://github.com/actix/actix-web/pull/2094 [#2097]: https://github.com/actix/actix-web/pull/2097 [#2112]: https://github.com/actix/actix-web/pull/2112 @@ -239,8 +239,8 @@ * `JsonBody::new` returns a default limit of 32kB to be consistent with `JsonConfig` and the default behaviour of the `web::Json` extractor. [#2010] -[#1981]: https://github.com/actix/actix-web/pull/1981 -[#2010]: https://github.com/actix/actix-web/pull/2010 +- 1981]: https://github.com/actix/actix-web/pull/1981 +- 2010]: https://github.com/actix/actix-web/pull/2010 ## 4.0.0-beta.3 - 2021-02-10 @@ -248,36 +248,36 @@ ## 4.0.0-beta.2 - 2021-02-10 -### Added +- # Added * The method `Either, web::Form>::into_inner()` which returns the inner type for whichever variant was created. Also works for `Either, web::Json>`. [#1894] * Add `services!` macro for helping register multiple services to `App`. [#1933] * Enable registering a vec of services of the same type to `App` [#1933] - +- ### Changed -* Rework `Responder` trait to be sync and returns `Response`/`HttpResponse` directly. - Making it simpler and more performant. [#1891] +- Rework `Responder` trait to be sync and returns `Response`/`HttpResponse` directly. +- Making it simpler and more performant. [#1891] * `ServiceRequest::into_parts` and `ServiceRequest::from_parts` can no longer fail. [#1893] * `ServiceRequest::from_request` can no longer fail. [#1893] -* Our `Either` type now uses `Left`/`Right` variants (instead of `A`/`B`) [#1894] +- Our `Either` type now uses `Left`/`Right` variants (instead of `A`/`B`) [#1894] * `test::{call_service, read_response, read_response_json, send_request}` take `&Service` - in argument [#1905] -* `App::wrap_fn`, `Resource::wrap_fn` and `Scope::wrap_fn` provide `&Service` in closure - argument. [#1905] -* `web::block` no longer requires the output is a Result. [#1957] +- in argument [#1905] +- `App::wrap_fn`, `Resource::wrap_fn` and `Scope::wrap_fn` provide `&Service` in closure +- argument. [#1905] +- `web::block` no longer requires the output is a Result. [#1957] -### Fixed +- # Fixed * Multiple calls to `App::data` with the same type now keeps the latest call's data. [#1906] - +- ### Removed * Public field of `web::Path` has been made private. [#1894] -* Public field of `web::Query` has been made private. [#1894] +- Public field of `web::Query` has been made private. [#1894] * `TestRequest::with_header`; use `TestRequest::default().insert_header()`. [#1869] * `AppService::set_service_data`; for custom HTTP service factories adding application data, use the - layered data model by calling `ServiceRequest::add_data_container` when handling - requests instead. [#1906] - -[#1891]: https://github.com/actix/actix-web/pull/1891 +- layered data model by calling `ServiceRequest::add_data_container` when handling +- requests instead. [#1906] +- +- 1891]: https://github.com/actix/actix-web/pull/1891 [#1893]: https://github.com/actix/actix-web/pull/1893 [#1894]: https://github.com/actix/actix-web/pull/1894 [#1869]: https://github.com/actix/actix-web/pull/1869 @@ -293,26 +293,26 @@ `Compress` to be used in `middleware::Condition` and `Resource`, `Scope` services. [#1865] ### Changed -* Update `actix-*` dependencies to tokio `1.0` based versions. [#1813] +- Update `actix-*` dependencies to tokio `1.0` based versions. [#1813] * Bumped `rand` to `0.8`. * Update `rust-tls` to `0.19`. [#1813] * Rename `Handler` to `HandlerService` and rename `Factory` to `Handler`. [#1852] -* The default `TrailingSlash` is now `Trim`, in line with existing documentation. See migration - guide for implications. [#1875] -* Rename `DefaultHeaders::{content_type => add_content_type}`. [#1875] -* MSRV is now 1.46.0. - +- The default `TrailingSlash` is now `Trim`, in line with existing documentation. See migration +- guide for implications. [#1875] +- Rename `DefaultHeaders::{content_type => add_content_type}`. [#1875] +- MSRV is now 1.46.0. +- ### Fixed -* Added the underlying parse error to `test::read_body_json`'s panic message. [#1812] - +- Added the underlying parse error to `test::read_body_json`'s panic message. [#1812] +- ### Removed * Public modules `middleware::{normalize, err_handlers}`. All necessary middleware structs are now - exposed directly by the `middleware` module. +- exposed directly by the `middleware` module. * Remove `actix-threadpool` as dependency. `actix_threadpool::BlockingError` error type can be imported from `actix_web::error` module. [#1878] - +- [#1812]: https://github.com/actix/actix-web/pull/1812 -[#1813]: https://github.com/actix/actix-web/pull/1813 +- 1813]: https://github.com/actix/actix-web/pull/1813 [#1852]: https://github.com/actix/actix-web/pull/1852 [#1865]: https://github.com/actix/actix-web/pull/1865 [#1875]: https://github.com/actix/actix-web/pull/1875 @@ -325,16 +325,16 @@ [#2529]: https://github.com/actix/actix-web/pull/2529 - +- ## 3.3.2 - 2020-12-01 ### Fixed * Removed an occasional `unwrap` on `None` panic in `NormalizePathNormalization`. [#1762] * Fix `match_pattern()` returning `None` for scope with empty path resource. [#1798] * Increase minimum `socket2` version. [#1803] -[#1762]: https://github.com/actix/actix-web/pull/1762 -[#1798]: https://github.com/actix/actix-web/pull/1798 -[#1803]: https://github.com/actix/actix-web/pull/1803 +- 1762]: https://github.com/actix/actix-web/pull/1762 +- 1798]: https://github.com/actix/actix-web/pull/1798 +- 1803]: https://github.com/actix/actix-web/pull/1803 ## 3.3.1 - 2020-11-29 @@ -342,15 +342,15 @@ ## 3.3.0 - 2020-11-25 -### Added +- # Added * Add `Either` extractor helper. [#1788] ### Changed * Upgrade `serde_urlencoded` to `0.7`. [#1773] - +- [#1773]: https://github.com/actix/actix-web/pull/1773 [#1788]: https://github.com/actix/actix-web/pull/1788 - +- ## 3.2.0 - 2020-10-30 ### Added @@ -358,17 +358,17 @@ * Add request-local data extractor `web::ReqData`. [#1748] * Add ability to register closure for request middleware logging. [#1749] * Add `app_data` to `ServiceConfig`. [#1757] -* Expose `on_connect` for access to the connection stream before request is handled. [#1754] - -### Changed -* Updated actix-web-codegen dependency for access to new `#[route(...)]` multi-method macro. -* Print non-configured `Data` type when attempting extraction. [#1743] +- Expose `on_connect` for access to the connection stream before request is handled. [#1754] +- +- # Changed +- Updated actix-web-codegen dependency for access to new `#[route(...)]` multi-method macro. +- Print non-configured `Data` type when attempting extraction. [#1743] * Re-export bytes::Buf{Mut} in web module. [#1750] * Upgrade `pin-project` to `1.0`. - -[#1723]: https://github.com/actix/actix-web/pull/1723 -[#1743]: https://github.com/actix/actix-web/pull/1743 -[#1748]: https://github.com/actix/actix-web/pull/1748 +- +- 1723]: https://github.com/actix/actix-web/pull/1723 +- 1743]: https://github.com/actix/actix-web/pull/1743 +- 1748]: https://github.com/actix/actix-web/pull/1748 [#1750]: https://github.com/actix/actix-web/pull/1750 [#1754]: https://github.com/actix/actix-web/pull/1754 [#1749]: https://github.com/actix/actix-web/pull/1749 @@ -380,13 +380,13 @@ to retain any trailing slashes. [#1695] * Remove bound `std::marker::Sized` from `web::Data` to support storing `Arc` via `web::Data::from` [#1710] - +- ### Fixed -* `ResourceMap` debug printing is no longer infinitely recursive. [#1708] +- `ResourceMap` debug printing is no longer infinitely recursive. [#1708] [#1695]: https://github.com/actix/actix-web/pull/1695 [#1708]: https://github.com/actix/actix-web/pull/1708 -[#1710]: https://github.com/actix/actix-web/pull/1710 +- 1710]: https://github.com/actix/actix-web/pull/1710 ## 3.0.2 - 2020-09-15 @@ -395,33 +395,33 @@ [#1678]: https://github.com/actix/actix-web/pull/1678 - +- ## 3.0.1 - 2020-09-13 ### Changed * `middleware::normalize::TrailingSlash` enum is now accessible. [#1673] [#1673]: https://github.com/actix/actix-web/pull/1673 - +- ## 3.0.0 - 2020-09-11 * No significant changes from `3.0.0-beta.4`. ## 3.0.0-beta.4 - 2020-09-09 -### Added +- # Added * `middleware::NormalizePath` now has configurable behavior for either always having a trailing slash, or as the new addition, always trimming trailing slashes. [#1639] ### Changed -* Update actix-codec and actix-utils dependencies. [#1634] +- Update actix-codec and actix-utils dependencies. [#1634] * `FormConfig` and `JsonConfig` configurations are now also considered when set using `App::data`. [#1641] * `HttpServer::maxconn` is renamed to the more expressive `HttpServer::max_connections`. [#1655] -* `HttpServer::maxconnrate` is renamed to the more expressive - `HttpServer::max_connection_rate`. [#1655] +- `HttpServer::maxconnrate` is renamed to the more expressive +- `HttpServer::max_connection_rate`. [#1655] -[#1639]: https://github.com/actix/actix-web/pull/1639 -[#1641]: https://github.com/actix/actix-web/pull/1641 +- 1639]: https://github.com/actix/actix-web/pull/1639 +- 1641]: https://github.com/actix/actix-web/pull/1641 [#1634]: https://github.com/actix/actix-web/pull/1634 [#1655]: https://github.com/actix/actix-web/pull/1655 @@ -431,22 +431,22 @@ ## 3.0.0-beta.2 - 2020-08-17 -### Changed +- # Changed * `PayloadConfig` is now also considered in `Bytes` and `String` extractors when set using `App::data`. [#1610] * `web::Path` now has a public representation: `web::Path(pub T)` that enables destructuring. [#1594] -* `ServiceRequest::app_data` allows retrieval of non-Data data without splitting into parts to +- `ServiceRequest::app_data` allows retrieval of non-Data data without splitting into parts to access `HttpRequest` which already allows this. [#1618] -* Re-export all error types from `awc`. [#1621] +- Re-export all error types from `awc`. [#1621] * MSRV is now 1.42.0. - +- ### Fixed -* Memory leak of app data in pooled requests. [#1609] - +- Memory leak of app data in pooled requests. [#1609] +- [#1594]: https://github.com/actix/actix-web/pull/1594 [#1609]: https://github.com/actix/actix-web/pull/1609 -[#1610]: https://github.com/actix/actix-web/pull/1610 +- 1610]: https://github.com/actix/actix-web/pull/1610 [#1618]: https://github.com/actix/actix-web/pull/1618 [#1621]: https://github.com/actix/actix-web/pull/1621 @@ -457,29 +457,29 @@ * `HttpRequest::match_pattern` and `ServiceRequest::match_pattern` for extracting the matched resource pattern. * `HttpRequest::match_name` and `ServiceRequest::match_name` for extracting matched resource name. - -### Changed +- +- # Changed * Fix actix_http::h1::dispatcher so it returns when HW_BUFFER_SIZE is reached. Should reduce peak memory consumption during large uploads. [#1550] -* Migrate cookie handling to `cookie` crate. Actix-web no longer requires `ring` dependency. +- Migrate cookie handling to `cookie` crate. Actix-web no longer requires `ring` dependency. * MSRV is now 1.41.1 -### Fixed -* `NormalizePath` improved consistency when path needs slashes added _and_ removed. - +- # Fixed +- `NormalizePath` improved consistency when path needs slashes added _and_ removed. +- ## 3.0.0-alpha.3 - 2020-05-21 -### Added +- # Added * Add option to create `Data` from `Arc` [#1509] ### Changed * Resources and Scopes can now access non-overridden data types set on App (or containing scopes) when setting their own data. [#1486] -* Fix audit issue logging by default peer address [#1485] +- Fix audit issue logging by default peer address [#1485] * Bump minimum supported Rust version to 1.40 * Replace deprecated `net2` crate with `socket2` - -[#1485]: https://github.com/actix/actix-web/pull/1485 -[#1509]: https://github.com/actix/actix-web/pull/1509 - +- +- 1485]: https://github.com/actix/actix-web/pull/1485 +- 1509]: https://github.com/actix/actix-web/pull/1509 +- ## [3.0.0-alpha.2] - 2020-05-08 ### Changed @@ -488,10 +488,10 @@ * Implement `std::error::Error` for our custom errors [#1422] * NormalizePath middleware now appends trailing / so that routes of form /example/ respond to /example requests. [#1433] * Remove the `failure` feature and support. - -[#1422]: https://github.com/actix/actix-web/pull/1422 -[#1433]: https://github.com/actix/actix-web/pull/1433 -[#1452]: https://github.com/actix/actix-web/pull/1452 +- +- 1422]: https://github.com/actix/actix-web/pull/1422 +- 1433]: https://github.com/actix/actix-web/pull/1433 +- 1452]: https://github.com/actix/actix-web/pull/1452 [#1486]: https://github.com/actix/actix-web/pull/1486 @@ -503,16 +503,16 @@ * Add convenience functions `test::read_body_json()` and `test::TestRequest::send_request()` for testing. ### Changed - -* Use `sha-1` crate instead of unmaintained `sha1` crate +- +- Use `sha-1` crate instead of unmaintained `sha1` crate * Skip empty chunks when returning response from a `Stream` [#1308] * Update the `time` dependency to 0.2.7 * Update `actix-tls` dependency to 2.0.0-alpha.1 -* Update `rustls` dependency to 0.17 - -[#1308]: https://github.com/actix/actix-web/pull/1308 - -## [2.0.0] - 2019-12-25 +- Update `rustls` dependency to 0.17 +- +- 1308]: https://github.com/actix/actix-web/pull/1308 +- +- [2.0.0] - 2019-12-25 ### Changed @@ -520,404 +520,405 @@ * Allow to gracefully stop test server via `TestServer::stop()` -* Allow to specify multi-patterns for resources +- Allow to specify multi-patterns for resources -## [2.0.0-rc] - 2019-12-20 +- [2.0.0-rc] - 2019-12-20 -### Changed +- # Changed * Move `BodyEncoding` to `dev` module #1220 * Allow to set `peer_addr` for TestRequest #1074 -* Make web::Data deref to Arc #1214 +- Make web::Data deref to Arc #1214 -* Rename `App::register_data()` to `App::app_data()` +- Rename `App::register_data()` to `App::app_data()` -* `HttpRequest::app_data()` returns `Option<&T>` instead of `Option<&Data>` +- `HttpRequest::app_data()` returns `Option<&T>` instead of `Option<&Data>` -### Fixed +- # Fixed -* Fix `AppConfig::secure()` is always false. #1202 +- Fix `AppConfig::secure()` is always false. #1202 ## [2.0.0-alpha.6] - 2019-12-15 - +- ### Fixed * Fixed compilation with default features off ## [2.0.0-alpha.5] - 2019-12-13 -### Added +- # Added * Add test server, `test::start()` and `test::start_with()` ## [2.0.0-alpha.4] - 2019-12-08 -### Deleted +- # Deleted * Delete HttpServer::run(), it is not useful with async/await ## [2.0.0-alpha.3] - 2019-12-07 -### Changed +- # Changed * Migrate to tokio 0.2 ## [2.0.0-alpha.1] - 2019-11-22 - +- ### Changed * Migrated to `std::future` * Remove implementation of `Responder` for `()`. (#1167) - +- ## [1.0.9] - 2019-11-14 - +- ### Added * Add `Payload::into_inner` method and make stored `def::Payload` public. (#1110) ### Changed -* Support `Host` guards when the `Host` header is unset (e.g. HTTP/2 requests) (#1129) +- Support `Host` guards when the `Host` header is unset (e.g. HTTP/2 requests) (#1129) ## [1.0.8] - 2019-09-25 - +- ### Added * Add `Scope::register_data` and `Resource::register_data` methods, parallel to `App::register_data`. * Add `middleware::Condition` that conditionally enables another middleware - +- * Allow to re-construct `ServiceRequest` from `HttpRequest` and `Payload` -* Add `HttpServer::listen_uds` for ability to listen on UDS FD rather than path, +- Add `HttpServer::listen_uds` for ability to listen on UDS FD rather than path, which is useful for example with systemd. - +- ### Changed - +- * Make UrlEncodedError::Overflow more informative * Use actix-testing for testing utils - +- ## [1.0.7] - 2019-08-29 - +- ### Fixed * Request Extensions leak #1062 ## [1.0.6] - 2019-08-28 - +- ### Added * Re-implement Host predicate (#989) * Form implements Responder, returning a `application/x-www-form-urlencoded` response -* Add `into_inner` to `Data` +- Add `into_inner` to `Data` -* Add `test::TestRequest::set_form()` convenience method to automatically serialize data and set +- Add `test::TestRequest::set_form()` convenience method to automatically serialize data and set the header in test requests. - +- ### Changed - +- * `Query` payload made `pub`. Allows user to pattern-match the payload. * Enable `rust-tls` feature for client #1045 -* Update serde_urlencoded to 0.6.1 - -* Update url to 2.1 +- Update serde_urlencoded to 0.6.1 +- Update url to 2.1 +- ## [1.0.5] - 2019-07-18 - +- ### Added * Unix domain sockets (HttpServer::bind_uds) #92 * Actix now logs errors resulting in "internal server error" responses always, with the `error` logging level - +- ### Fixed - +- * Restored logging of errors through the `Logger` middleware ## [1.0.4] - 2019-07-17 - +- ### Added * Add `Responder` impl for `(T, StatusCode) where T: Responder` * Allow to access app's resource map via `ServiceRequest::resource_map()` and `HttpRequest::resource_map()` methods. - +- ### Changed - +- * Upgrade `rand` dependency version to 0.7 ## [1.0.3] - 2019-06-28 - +- ### Added * Support asynchronous data factories #850 ### Changed -* Use `encoding_rs` crate instead of unmaintained `encoding` crate +- Use `encoding_rs` crate instead of unmaintained `encoding` crate ## [1.0.2] - 2019-06-17 - +- ### Changed * Move cors middleware to `actix-cors` crate. * Move identity middleware to `actix-identity` crate. - +- ## [1.0.1] - 2019-06-17 - +- ### Added * Add support for PathConfig #903 * Add `middleware::identity::RequestIdentity` trait to `get_identity` from `HttpMessage`. -### Changed +- # Changed -* Move cors middleware to `actix-cors` crate. +- Move cors middleware to `actix-cors` crate. * Move identity middleware to `actix-identity` crate. -* Disable default feature `secure-cookies`. +- Disable default feature `secure-cookies`. -* Allow to test an app that uses async actors #897 +- Allow to test an app that uses async actors #897 -* Re-apply patch from #637 #894 +- Re-apply patch from #637 #894 -### Fixed +- # Fixed -* HttpRequest::url_for is broken with nested scopes #915 +- HttpRequest::url_for is broken with nested scopes #915 ## [1.0.0] - 2019-06-05 - +- ### Added * Add `Scope::configure()` method. * Add `ServiceRequest::set_payload()` method. -* Add `test::TestRequest::set_json()` convenience method to automatically +- Add `test::TestRequest::set_json()` convenience method to automatically serialize data and set header in test requests. - +- * Add macros for head, options, trace, connect and patch http methods - +- ### Changed -* Drop an unnecessary `Option<_>` indirection around `ServerBuilder` from `HttpServer`. #863 +- Drop an unnecessary `Option<_>` indirection around `ServerBuilder` from `HttpServer`. #863 ### Fixed -* Fix Logger request time format, and use rfc3339. #867 +- Fix Logger request time format, and use rfc3339. #867 * Clear http requests pool on app service drop #860 - +- ## [1.0.0-rc] - 2019-05-18 - +- ### Added * Add `Query::from_query()` to extract parameters from a query string. #846 * `QueryConfig`, similar to `JsonConfig` for customizing error handling of query extractors. ### Changed - -* `JsonConfig` is now `Send + Sync`, this implies that `error_handler` must be `Send + Sync` too. +- +- `JsonConfig` is now `Send + Sync`, this implies that `error_handler` must be `Send + Sync` too. ### Fixed -* Codegen with parameters in the path only resolves the first registered endpoint #841 +- Codegen with parameters in the path only resolves the first registered endpoint #841 ## [1.0.0-beta.4] - 2019-05-12 - +- ### Added * Allow to set/override app data on scope level ### Changed -* `App::configure` take an `FnOnce` instead of `Fn` +- `App::configure` take an `FnOnce` instead of `Fn` * Upgrade actix-net crates -## [1.0.0-beta.3] - 2019-05-04 - +- [1.0.0-beta.3] - 2019-05-04 +- ### Added * Add helper function for executing futures `test::block_fn()` ### Changed -* Extractor configuration could be registered with `App::data()` +- Extractor configuration could be registered with `App::data()` or with `Resource::data()` #775 * Route data is unified with app data, `Route::data()` moved to resource - level to `Resource::data()` +- level to `Resource::data()` * CORS handling without headers #702 - +- * Allow constructing `Data` instances to avoid double `Arc` for `Send + Sync` types. -### Fixed +- # Fixed -* Fix `NormalizePath` middleware impl #806 +- Fix `NormalizePath` middleware impl #806 ### Deleted -* `App::data_factory()` is deleted. +- `App::data_factory()` is deleted. ## [1.0.0-beta.2] - 2019-04-24 - +- ### Added * Add raw services support via `web::service()` * Add helper functions for reading response body `test::read_body()` -* Add support for `remainder match` (i.e "/path/{tail}*") +- Add support for `remainder match` (i.e "/path/{tail}*") -* Extend `Responder` trait, allow to override status code and headers. +- Extend `Responder` trait, allow to override status code and headers. -* Store visit and login timestamp in the identity cookie #502 +- Store visit and login timestamp in the identity cookie #502 -### Changed +- # Changed -* `.to_async()` handler can return `Responder` type #792 +- `.to_async()` handler can return `Responder` type #792 ### Fixed -* Fix async web::Data factory handling +- Fix async web::Data factory handling ## [1.0.0-beta.1] - 2019-04-20 - +- ### Added * Add helper functions for reading test response body, `test::read_response()` and test::read_response_json()` * Add `.peer_addr()` #744 - +- * Add `NormalizePath` middleware -### Changed +- # Changed -* Rename `RouterConfig` to `ServiceConfig` +- Rename `RouterConfig` to `ServiceConfig` * Rename `test::call_success` to `test::call_service` -* Removed `ServiceRequest::from_parts()` as it is unsafe to create from parts. +- Removed `ServiceRequest::from_parts()` as it is unsafe to create from parts. -* `CookieIdentityPolicy::max_age()` accepts value in seconds +- `CookieIdentityPolicy::max_age()` accepts value in seconds -### Fixed +- # Fixed -* Fixed `TestRequest::app_data()` +- Fixed `TestRequest::app_data()` ## [1.0.0-alpha.6] - 2019-04-14 - +- ### Changed * Allow using any service as default service. * Remove generic type for request payload, always use default. -* Removed `Decompress` middleware. Bytes, String, Json, Form extractors +- Removed `Decompress` middleware. Bytes, String, Json, Form extractors automatically decompress payload. - +- * Make extractor config type explicit. Add `FromRequest::Config` associated type. - +- ## [1.0.0-alpha.5] - 2019-04-12 - +- ### Added * Added async io `TestBuffer` for testing. ### Deleted -* Removed native-tls support +- Removed native-tls support ## [1.0.0-alpha.4] - 2019-04-08 - +- ### Added * `App::configure()` allow to offload app configuration to different methods * Added `URLPath` option for logger -* Added `ServiceRequest::app_data()`, returns `Data` +- Added `ServiceRequest::app_data()`, returns `Data` -* Added `ServiceFromRequest::app_data()`, returns `Data` +- Added `ServiceFromRequest::app_data()`, returns `Data` -### Changed +- # Changed -* `FromRequest` trait refactoring +- `FromRequest` trait refactoring * Move multipart support to actix-multipart crate -### Fixed +- # Fixed -* Fix body propagation in Response::from_error. #760 +- Fix body propagation in Response::from_error. #760 ## [1.0.0-alpha.3] - 2019-04-02 - +- ### Changed * Renamed `TestRequest::to_service()` to `TestRequest::to_srv_request()` * Renamed `TestRequest::to_response()` to `TestRequest::to_srv_response()` -* Removed `Deref` impls +- Removed `Deref` impls -### Removed +- # Removed -* Removed unused `actix_web::web::md()` +- Removed unused `actix_web::web::md()` ## [1.0.0-alpha.2] - 2019-03-29 - +- ### Added * Rustls support ### Changed -* Use forked cookie +- Use forked cookie * Multipart::Field renamed to MultipartField -## [1.0.0-alpha.1] - 2019-03-28 +- [1.0.0-alpha.1] - 2019-03-28 -### Changed +- # Changed * Complete architecture re-design. * Return 405 response if no matching route found within resource #538 +- - diff --git a/actix-files/CHANGES.md b/actix-files/CHANGES.md index d6b39e28f..ef8eba0fc 100644 --- a/actix-files/CHANGES.md +++ b/actix-files/CHANGES.md @@ -4,42 +4,42 @@ ## 0.6.0-beta.10 - 2021-12-11 -* No significant changes since `0.6.0-beta.9`. +- No significant changes since `0.6.0-beta.9`. ## 0.6.0-beta.9 - 2021-11-22 -* Add crate feature `experimental-io-uring`, enabling async file I/O to be utilized. This feature is only available on Linux OSes with recent kernel versions. This feature is semver-exempt. [#2408] -* Add `NamedFile::open_async`. [#2408] -* Fix 304 Not Modified responses to omit the Content-Length header, as per the spec. [#2453] -* The `Responder` impl for `NamedFile` now has a boxed future associated type. [#2408] -* The `Service` impl for `NamedFileService` now has a boxed future associated type. [#2408] -* Add `impl Clone` for `FilesService`. [#2408] +- Add crate feature `experimental-io-uring`, enabling async file I/O to be utilized. This feature is only available on Linux OSes with recent kernel versions. This feature is semver-exempt. [#2408] +- Add `NamedFile::open_async`. [#2408] +- Fix 304 Not Modified responses to omit the Content-Length header, as per the spec. [#2453] +- The `Responder` impl for `NamedFile` now has a boxed future associated type. [#2408] +- The `Service` impl for `NamedFileService` now has a boxed future associated type. [#2408] +- Add `impl Clone` for `FilesService`. [#2408] [#2408]: https://github.com/actix/actix-web/pull/2408 [#2453]: https://github.com/actix/actix-web/pull/2453 ## 0.6.0-beta.8 - 2021-10-20 -* Minimum supported Rust version (MSRV) is now 1.52. +- Minimum supported Rust version (MSRV) is now 1.52. ## 0.6.0-beta.7 - 2021-09-09 -* Minimum supported Rust version (MSRV) is now 1.51. +- Minimum supported Rust version (MSRV) is now 1.51. ## 0.6.0-beta.6 - 2021-06-26 -* Added `Files::path_filter()`. [#2274] -* `Files::show_files_listing()` can now be used with `Files::index_file()` to show files listing as a fallback when the index file is not found. [#2228] +- Added `Files::path_filter()`. [#2274] +- `Files::show_files_listing()` can now be used with `Files::index_file()` to show files listing as a fallback when the index file is not found. [#2228] [#2274]: https://github.com/actix/actix-web/pull/2274 [#2228]: https://github.com/actix/actix-web/pull/2228 ## 0.6.0-beta.5 - 2021-06-17 -* `NamedFile` now implements `ServiceFactory` and `HttpServiceFactory` making it much more useful in routing. For example, it can be used directly as a default service. [#2135] -* For symbolic links, `Content-Disposition` header no longer shows the filename of the original file. [#2156] -* `Files::redirect_to_slash_directory()` now works as expected when used with `Files::show_files_listing()`. [#2225] -* `application/{javascript, json, wasm}` mime type now have `inline` disposition by default. [#2257] +- `NamedFile` now implements `ServiceFactory` and `HttpServiceFactory` making it much more useful in routing. For example, it can be used directly as a default service. [#2135] +- For symbolic links, `Content-Disposition` header no longer shows the filename of the original file. [#2156] +- `Files::redirect_to_slash_directory()` now works as expected when used with `Files::show_files_listing()`. [#2225] +- `application/{javascript, json, wasm}` mime type now have `inline` disposition by default. [#2257] [#2135]: https://github.com/actix/actix-web/pull/2135 [#2156]: https://github.com/actix/actix-web/pull/2156 @@ -48,130 +48,130 @@ ## 0.6.0-beta.4 - 2021-04-02 -* Add support for `.guard` in `Files` to selectively filter `Files` services. [#2046] +- Add support for `.guard` in `Files` to selectively filter `Files` services. [#2046] [#2046]: https://github.com/actix/actix-web/pull/2046 ## 0.6.0-beta.3 - 2021-03-09 -* No notable changes. +- No notable changes. ## 0.6.0-beta.2 - 2021-02-10 -* Fix If-Modified-Since and If-Unmodified-Since to not compare using sub-second timestamps. [#1887] -* Replace `v_htmlescape` with `askama_escape`. [#1953] +- Fix If-Modified-Since and If-Unmodified-Since to not compare using sub-second timestamps. [#1887] +- Replace `v_htmlescape` with `askama_escape`. [#1953] [#1887]: https://github.com/actix/actix-web/pull/1887 [#1953]: https://github.com/actix/actix-web/pull/1953 ## 0.6.0-beta.1 - 2021-01-07 -* `HttpRange::parse` now has its own error type. -* Update `bytes` to `1.0`. [#1813] +- `HttpRange::parse` now has its own error type. +- Update `bytes` to `1.0`. [#1813] [#1813]: https://github.com/actix/actix-web/pull/1813 ## 0.5.0 - 2020-12-26 -* Optionally support hidden files/directories. [#1811] +- Optionally support hidden files/directories. [#1811] [#1811]: https://github.com/actix/actix-web/pull/1811 ## 0.4.1 - 2020-11-24 -* Clarify order of parameters in `Files::new` and improve docs. +- Clarify order of parameters in `Files::new` and improve docs. ## 0.4.0 - 2020-10-06 -* Add `Files::prefer_utf8` option that adds UTF-8 charset on certain response types. [#1714] +- Add `Files::prefer_utf8` option that adds UTF-8 charset on certain response types. [#1714] [#1714]: https://github.com/actix/actix-web/pull/1714 ## 0.3.0 - 2020-09-11 -* No significant changes from 0.3.0-beta.1. +- No significant changes from 0.3.0-beta.1. ## 0.3.0-beta.1 - 2020-07-15 -* Update `v_htmlescape` to 0.10 -* Update `actix-web` and `actix-http` dependencies to beta.1 +- Update `v_htmlescape` to 0.10 +- Update `actix-web` and `actix-http` dependencies to beta.1 ## 0.3.0-alpha.1 - 2020-05-23 -* Update `actix-web` and `actix-http` dependencies to alpha -* Fix some typos in the docs -* Bump minimum supported Rust version to 1.40 -* Support sending Content-Length when Content-Range is specified [#1384] +- Update `actix-web` and `actix-http` dependencies to alpha +- Fix some typos in the docs +- Bump minimum supported Rust version to 1.40 +- Support sending Content-Length when Content-Range is specified [#1384] [#1384]: https://github.com/actix/actix-web/pull/1384 ## 0.2.1 - 2019-12-22 -* Use the same format for file URLs regardless of platforms +- Use the same format for file URLs regardless of platforms ## 0.2.0 - 2019-12-20 -* Fix BodyEncoding trait import #1220 +- Fix BodyEncoding trait import #1220 ## 0.2.0-alpha.1 - 2019-12-07 -* Migrate to `std::future` +- Migrate to `std::future` ## 0.1.7 - 2019-11-06 -* Add an additional `filename*` param in the `Content-Disposition` header of +- Add an additional `filename*` param in the `Content-Disposition` header of `actix_files::NamedFile` to be more compatible. (#1151) ## 0.1.6 - 2019-10-14 -* Add option to redirect to a slash-ended path `Files` #1132 +- Add option to redirect to a slash-ended path `Files` #1132 ## 0.1.5 - 2019-10-08 -* Bump up `mime_guess` crate version to 2.0.1 -* Bump up `percent-encoding` crate version to 2.1 -* Allow user defined request guards for `Files` #1113 +- Bump up `mime_guess` crate version to 2.0.1 +- Bump up `percent-encoding` crate version to 2.1 +- Allow user defined request guards for `Files` #1113 ## 0.1.4 - 2019-07-20 -* Allow to disable `Content-Disposition` header #686 +- Allow to disable `Content-Disposition` header #686 ## 0.1.3 - 2019-06-28 -* Do not set `Content-Length` header, let actix-http set it #930 +- Do not set `Content-Length` header, let actix-http set it #930 ## 0.1.2 - 2019-06-13 -* Content-Length is 0 for NamedFile HEAD request #914 -* Fix ring dependency from actix-web default features for #741 +- Content-Length is 0 for NamedFile HEAD request #914 +- Fix ring dependency from actix-web default features for #741 ## 0.1.1 - 2019-06-01 -* Static files are incorrectly served as both chunked and with length #812 +- Static files are incorrectly served as both chunked and with length #812 ## 0.1.0 - 2019-05-25 -* NamedFile last-modified check always fails due to nano-seconds in file modified date #820 +- NamedFile last-modified check always fails due to nano-seconds in file modified date #820 ## 0.1.0-beta.4 - 2019-05-12 -* Update actix-web to beta.4 +- Update actix-web to beta.4 ## 0.1.0-beta.1 - 2019-04-20 -* Update actix-web to beta.1 +- Update actix-web to beta.1 ## 0.1.0-alpha.6 - 2019-04-14 -* Update actix-web to alpha6 +- Update actix-web to alpha6 ## 0.1.0-alpha.4 - 2019-04-08 -* Update actix-web to alpha4 +- Update actix-web to alpha4 ## 0.1.0-alpha.2 - 2019-04-02 -* Add default handler support +- Add default handler support ## 0.1.0-alpha.1 - 2019-03-28 -* Initial impl +- Initial impl diff --git a/actix-http-test/CHANGES.md b/actix-http-test/CHANGES.md index 156012168..4e86e20e8 100644 --- a/actix-http-test/CHANGES.md +++ b/actix-http-test/CHANGES.md @@ -4,125 +4,125 @@ ## 3.0.0-beta.9 - 2021-12-11 -* No significant changes since `3.0.0-beta.8`. +- No significant changes since `3.0.0-beta.8`. ## 3.0.0-beta.8 - 2021-11-30 -* Update `actix-tls` to `3.0.0-rc.1`. [#2474] +- Update `actix-tls` to `3.0.0-rc.1`. [#2474] [#2474]: https://github.com/actix/actix-web/pull/2474 ## 3.0.0-beta.7 - 2021-11-22 -* Fix compatibility with experimental `io-uring` feature of `actix-rt`. [#2408] +- Fix compatibility with experimental `io-uring` feature of `actix-rt`. [#2408] [#2408]: https://github.com/actix/actix-web/pull/2408 ## 3.0.0-beta.6 - 2021-11-15 -* `TestServer::stop` is now async and will wait for the server and system to shutdown. [#2442] -* Update `actix-server` to `2.0.0-beta.9`. [#2442] -* Minimum supported Rust version (MSRV) is now 1.52. +- `TestServer::stop` is now async and will wait for the server and system to shutdown. [#2442] +- Update `actix-server` to `2.0.0-beta.9`. [#2442] +- Minimum supported Rust version (MSRV) is now 1.52. [#2442]: https://github.com/actix/actix-web/pull/2442 ## 3.0.0-beta.5 - 2021-09-09 -* Minimum supported Rust version (MSRV) is now 1.51. +- Minimum supported Rust version (MSRV) is now 1.51. ## 3.0.0-beta.4 - 2021-04-02 -* Added `TestServer::client_headers` method. [#2097] +- Added `TestServer::client_headers` method. [#2097] [#2097]: https://github.com/actix/actix-web/pull/2097 ## 3.0.0-beta.3 - 2021-03-09 -* No notable changes. +- No notable changes. ## 3.0.0-beta.2 - 2021-02-10 -* No notable changes. +- No notable changes. ## 3.0.0-beta.1 - 2021-01-07 -* Update `bytes` to `1.0`. [#1813] +- Update `bytes` to `1.0`. [#1813] [#1813]: https://github.com/actix/actix-web/pull/1813 ## 2.1.0 - 2020-11-25 -* Add ability to set address for `TestServer`. [#1645] -* Upgrade `base64` to `0.13`. -* Upgrade `serde_urlencoded` to `0.7`. [#1773] +- Add ability to set address for `TestServer`. [#1645] +- Upgrade `base64` to `0.13`. +- Upgrade `serde_urlencoded` to `0.7`. [#1773] [#1773]: https://github.com/actix/actix-web/pull/1773 [#1645]: https://github.com/actix/actix-web/pull/1645 ## 2.0.0 - 2020-09-11 -* Update actix-codec and actix-utils dependencies. +- Update actix-codec and actix-utils dependencies. ## 2.0.0-alpha.1 - 2020-05-23 -* Update the `time` dependency to 0.2.7 -* Update `actix-connect` dependency to 2.0.0-alpha.2 -* Make `test_server` `async` fn. -* Bump minimum supported Rust version to 1.40 -* Replace deprecated `net2` crate with `socket2` -* Update `base64` dependency to 0.12 -* Update `env_logger` dependency to 0.7 +- Update the `time` dependency to 0.2.7 +- Update `actix-connect` dependency to 2.0.0-alpha.2 +- Make `test_server` `async` fn. +- Bump minimum supported Rust version to 1.40 +- Replace deprecated `net2` crate with `socket2` +- Update `base64` dependency to 0.12 +- Update `env_logger` dependency to 0.7 ## 1.0.0 - 2019-12-13 -* Replaced `TestServer::start()` with `test_server()` +- Replaced `TestServer::start()` with `test_server()` ## 1.0.0-alpha.3 - 2019-12-07 -* Migrate to `std::future` +- Migrate to `std::future` ## 0.2.5 - 2019-09-17 -* Update serde_urlencoded to "0.6.1" -* Increase TestServerRuntime timeouts from 500ms to 3000ms -* Do not override current `System` +- Update serde_urlencoded to "0.6.1" +- Increase TestServerRuntime timeouts from 500ms to 3000ms +- Do not override current `System` ## 0.2.4 - 2019-07-18 -* Update actix-server to 0.6 +- Update actix-server to 0.6 ## 0.2.3 - 2019-07-16 -* Add `delete`, `options`, `patch` methods to `TestServerRunner` +- Add `delete`, `options`, `patch` methods to `TestServerRunner` ## 0.2.2 - 2019-06-16 -* Add .put() and .sput() methods +- Add .put() and .sput() methods ## 0.2.1 - 2019-06-05 -* Add license files +- Add license files ## 0.2.0 - 2019-05-12 -* Update awc and actix-http deps +- Update awc and actix-http deps ## 0.1.1 - 2019-04-24 -* Always make new connection for http client +- Always make new connection for http client ## 0.1.0 - 2019-04-16 -* No changes +- No changes ## 0.1.0-alpha.3 - 2019-04-02 -* Request functions accept path #743 +- Request functions accept path #743 ## 0.1.0-alpha.2 - 2019-03-29 -* Added TestServerRuntime::load_body() method -* Update actix-http and awc libraries +- Added TestServerRuntime::load_body() method +- Update actix-http and awc libraries ## 0.1.0-alpha.1 - 2019-03-28 -* Initial impl +- Initial impl diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index ad98d132a..3b45e934f 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -2,22 +2,22 @@ ## Unreleased - 2021-xx-xx ### Changes -* `HeaderMap::get_all` now returns a `std::slice::Iter`. [#2527] +- `HeaderMap::get_all` now returns a `std::slice::Iter`. [#2527] [#2527]: https://github.com/actix/actix-web/pull/2527 ## 3.0.0-beta.16 - 2021-12-17 ### Added -* New method on `MessageBody` trait, `try_into_bytes`, with default implementation, for optimizations on body types that complete in exactly one poll. Replaces `is_complete_body` and `take_complete_body`. [#2522] +- New method on `MessageBody` trait, `try_into_bytes`, with default implementation, for optimizations on body types that complete in exactly one poll. Replaces `is_complete_body` and `take_complete_body`. [#2522] ### Changed -* Rename trait `IntoHeaderPair => TryIntoHeaderPair`. [#2510] -* Rename `TryIntoHeaderPair::{try_into_header_pair => try_into_pair}`. [#2510] -* Rename trait `IntoHeaderValue => TryIntoHeaderValue`. [#2510] +- Rename trait `IntoHeaderPair => TryIntoHeaderPair`. [#2510] +- Rename `TryIntoHeaderPair::{try_into_header_pair => try_into_pair}`. [#2510] +- Rename trait `IntoHeaderValue => TryIntoHeaderValue`. [#2510] ### Removed -* `MessageBody::{is_complete_body,take_complete_body}`. [#2522] +- `MessageBody::{is_complete_body,take_complete_body}`. [#2522] [#2510]: https://github.com/actix/actix-web/pull/2510 [#2522]: https://github.com/actix/actix-web/pull/2522 @@ -25,43 +25,43 @@ ## 3.0.0-beta.15 - 2021-12-11 ### Added -* Add timeout for canceling HTTP/2 server side connection handshake. Default to 5 seconds. [#2483] -* HTTP/2 handshake timeout can be configured with `ServiceConfig::client_timeout`. [#2483] -* `Response::map_into_boxed_body`. [#2468] -* `body::EitherBody` enum. [#2468] -* `body::None` struct. [#2468] -* Impl `MessageBody` for `bytestring::ByteString`. [#2468] -* `impl Clone for ws::HandshakeError`. [#2468] -* `#[must_use]` for `ws::Codec` to prevent subtle bugs. [#1920] -* `impl Default ` for `ws::Codec`. [#1920] -* `header::QualityItem::{max, min}`. [#2486] -* `header::Quality::{MAX, MIN}`. [#2486] -* `impl Display` for `header::Quality`. [#2486] -* Connection data set through the `on_connect_ext` callbacks is now accessible only from the new `Request::conn_data()` method. [#2491] -* `Request::take_conn_data()`. [#2491] -* `Request::take_req_data()`. [#2487] -* `impl Clone` for `RequestHead`. [#2487] -* New methods on `MessageBody` trait, `is_complete_body` and `take_complete_body`, both with default implementations, for optimizations on body types that are done in exactly one poll/chunk. [#2497] -* New `boxed` method on `MessageBody` trait for wrapping body type. [#2520] +- Add timeout for canceling HTTP/2 server side connection handshake. Default to 5 seconds. [#2483] +- HTTP/2 handshake timeout can be configured with `ServiceConfig::client_timeout`. [#2483] +- `Response::map_into_boxed_body`. [#2468] +- `body::EitherBody` enum. [#2468] +- `body::None` struct. [#2468] +- Impl `MessageBody` for `bytestring::ByteString`. [#2468] +- `impl Clone for ws::HandshakeError`. [#2468] +- `#[must_use]` for `ws::Codec` to prevent subtle bugs. [#1920] +- `impl Default ` for `ws::Codec`. [#1920] +- `header::QualityItem::{max, min}`. [#2486] +- `header::Quality::{MAX, MIN}`. [#2486] +- `impl Display` for `header::Quality`. [#2486] +- Connection data set through the `on_connect_ext` callbacks is now accessible only from the new `Request::conn_data()` method. [#2491] +- `Request::take_conn_data()`. [#2491] +- `Request::take_req_data()`. [#2487] +- `impl Clone` for `RequestHead`. [#2487] +- New methods on `MessageBody` trait, `is_complete_body` and `take_complete_body`, both with default implementations, for optimizations on body types that are done in exactly one poll/chunk. [#2497] +- New `boxed` method on `MessageBody` trait for wrapping body type. [#2520] ### Changed -* Rename `body::BoxBody::{from_body => new}`. [#2468] -* Body type for `Responses` returned from `Response::{new, ok, etc...}` is now `BoxBody`. [#2468] -* The `Error` associated type on `MessageBody` type now requires `impl Error` (or similar). [#2468] -* Error types using in service builders now require `Into>`. [#2468] -* `From` implementations on error types now return a `Response`. [#2468] -* `ResponseBuilder::body(B)` now returns `Response>`. [#2468] -* `ResponseBuilder::finish()` now returns `Response>`. [#2468] +- Rename `body::BoxBody::{from_body => new}`. [#2468] +- Body type for `Responses` returned from `Response::{new, ok, etc...}` is now `BoxBody`. [#2468] +- The `Error` associated type on `MessageBody` type now requires `impl Error` (or similar). [#2468] +- Error types using in service builders now require `Into>`. [#2468] +- `From` implementations on error types now return a `Response`. [#2468] +- `ResponseBuilder::body(B)` now returns `Response>`. [#2468] +- `ResponseBuilder::finish()` now returns `Response>`. [#2468] ### Removed -* `ResponseBuilder::streaming`. [#2468] -* `impl Future` for `ResponseBuilder`. [#2468] -* Remove unnecessary `MessageBody` bound on types passed to `body::AnyBody::new`. [#2468] -* Move `body::AnyBody` to `awc`. Replaced with `EitherBody` and `BoxBody`. [#2468] -* `impl Copy` for `ws::Codec`. [#1920] -* `header::qitem` helper. Replaced with `header::QualityItem::max`. [#2486] -* `impl TryFrom` for `header::Quality`. [#2486] -* `http` module. Most everything it contained is exported at the crate root. [#2488] +- `ResponseBuilder::streaming`. [#2468] +- `impl Future` for `ResponseBuilder`. [#2468] +- Remove unnecessary `MessageBody` bound on types passed to `body::AnyBody::new`. [#2468] +- Move `body::AnyBody` to `awc`. Replaced with `EitherBody` and `BoxBody`. [#2468] +- `impl Copy` for `ws::Codec`. [#1920] +- `header::qitem` helper. Replaced with `header::QualityItem::max`. [#2486] +- `impl TryFrom` for `header::Quality`. [#2486] +- `http` module. Most everything it contained is exported at the crate root. [#2488] [#2483]: https://github.com/actix/actix-web/pull/2483 [#2468]: https://github.com/actix/actix-web/pull/2468 @@ -76,10 +76,10 @@ ## 3.0.0-beta.14 - 2021-11-30 ### Changed -* Guarantee ordering of `header::GetAll` iterator to be same as insertion order. [#2467] -* Expose `header::map` module. [#2467] -* Implement `ExactSizeIterator` and `FusedIterator` for all `HeaderMap` iterators. [#2470] -* Update `actix-tls` to `3.0.0-rc.1`. [#2474] +- Guarantee ordering of `header::GetAll` iterator to be same as insertion order. [#2467] +- Expose `header::map` module. [#2467] +- Implement `ExactSizeIterator` and `FusedIterator` for all `HeaderMap` iterators. [#2470] +- Update `actix-tls` to `3.0.0-rc.1`. [#2474] [#2467]: https://github.com/actix/actix-web/pull/2467 [#2470]: https://github.com/actix/actix-web/pull/2470 @@ -88,24 +88,24 @@ ## 3.0.0-beta.13 - 2021-11-22 ### Added -* `body::AnyBody::empty` for quickly creating an empty body. [#2446] -* `body::AnyBody::none` for quickly creating a "none" body. [#2456] -* `impl Clone` for `body::AnyBody where S: Clone`. [#2448] -* `body::AnyBody::into_boxed` for quickly converting to a type-erased, boxed body type. [#2448] +- `body::AnyBody::empty` for quickly creating an empty body. [#2446] +- `body::AnyBody::none` for quickly creating a "none" body. [#2456] +- `impl Clone` for `body::AnyBody where S: Clone`. [#2448] +- `body::AnyBody::into_boxed` for quickly converting to a type-erased, boxed body type. [#2448] ### Changed -* Rename `body::AnyBody::{Message => Body}`. [#2446] -* Rename `body::AnyBody::{from_message => new_boxed}`. [#2448] -* Rename `body::AnyBody::{from_slice => copy_from_slice}`. [#2448] -* Rename `body::{BoxAnyBody => BoxBody}`. [#2448] -* Change representation of `AnyBody` to include a type parameter in `Body` variant. Defaults to `BoxBody`. [#2448] -* `Encoder::response` now returns `AnyBody>`. [#2448] +- Rename `body::AnyBody::{Message => Body}`. [#2446] +- Rename `body::AnyBody::{from_message => new_boxed}`. [#2448] +- Rename `body::AnyBody::{from_slice => copy_from_slice}`. [#2448] +- Rename `body::{BoxAnyBody => BoxBody}`. [#2448] +- Change representation of `AnyBody` to include a type parameter in `Body` variant. Defaults to `BoxBody`. [#2448] +- `Encoder::response` now returns `AnyBody>`. [#2448] ### Removed -* `body::AnyBody::Empty`; an empty body can now only be represented as a zero-length `Bytes` variant. [#2446] -* `body::BodySize::Empty`; an empty body can now only be represented as a `Sized(0)` variant. [#2446] -* `EncoderError::Boxed`; it is no longer required. [#2446] -* `body::ResponseBody`; is function is replaced by the new `body::AnyBody` enum. [#2446] +- `body::AnyBody::Empty`; an empty body can now only be represented as a zero-length `Bytes` variant. [#2446] +- `body::BodySize::Empty`; an empty body can now only be represented as a `Sized(0)` variant. [#2446] +- `EncoderError::Boxed`; it is no longer required. [#2446] +- `body::ResponseBody`; is function is replaced by the new `body::AnyBody` enum. [#2446] [#2446]: https://github.com/actix/actix-web/pull/2446 [#2448]: https://github.com/actix/actix-web/pull/2448 @@ -114,11 +114,11 @@ ## 3.0.0-beta.12 - 2021-11-15 ### Changed -* Update `actix-server` to `2.0.0-beta.9`. [#2442] +- Update `actix-server` to `2.0.0-beta.9`. [#2442] ### Removed -* `client` module. [#2425] -* `trust-dns` feature. [#2425] +- `client` module. [#2425] +- `trust-dns` feature. [#2425] [#2425]: https://github.com/actix/actix-web/pull/2425 [#2442]: https://github.com/actix/actix-web/pull/2442 @@ -126,21 +126,21 @@ ## 3.0.0-beta.11 - 2021-10-20 ### Changed -* Updated rustls to v0.20. [#2414] -* Minimum supported Rust version (MSRV) is now 1.52. +- Updated rustls to v0.20. [#2414] +- Minimum supported Rust version (MSRV) is now 1.52. [#2414]: https://github.com/actix/actix-web/pull/2414 ## 3.0.0-beta.10 - 2021-09-09 ### Changed -* `ContentEncoding` is now marked `#[non_exhaustive]`. [#2377] -* Minimum supported Rust version (MSRV) is now 1.51. +- `ContentEncoding` is now marked `#[non_exhaustive]`. [#2377] +- Minimum supported Rust version (MSRV) is now 1.51. ### Fixed -* Remove slice creation pointing to potential uninitialized data on h1 encoder. [#2364] -* Remove `Into` bound on `Encoder` body types. [#2375] -* Fix quality parse error in Accept-Encoding header. [#2344] +- Remove slice creation pointing to potential uninitialized data on h1 encoder. [#2364] +- Remove `Into` bound on `Encoder` body types. [#2375] +- Fix quality parse error in Accept-Encoding header. [#2344] [#2364]: https://github.com/actix/actix-web/pull/2364 [#2375]: https://github.com/actix/actix-web/pull/2375 @@ -150,15 +150,15 @@ ## 3.0.0-beta.9 - 2021-08-09 ### Fixed -* Potential HTTP request smuggling vulnerabilities. [RUSTSEC-2021-0081](https://github.com/rustsec/advisory-db/pull/977) +- Potential HTTP request smuggling vulnerabilities. [RUSTSEC-2021-0081](https://github.com/rustsec/advisory-db/pull/977) ## 3.0.0-beta.8 - 2021-06-26 ### Changed -* Change compression algorithm features flags. [#2250] +- Change compression algorithm features flags. [#2250] ### Removed -* `downcast` and `downcast_get_type_id` macros. [#2291] +- `downcast` and `downcast_get_type_id` macros. [#2291] [#2291]: https://github.com/actix/actix-web/pull/2291 [#2250]: https://github.com/actix/actix-web/pull/2250 @@ -166,37 +166,37 @@ ## 3.0.0-beta.7 - 2021-06-17 ### Added -* Alias `body::Body` as `body::AnyBody`. [#2215] -* `BoxAnyBody`: a boxed message body with boxed errors. [#2183] -* Re-export `http` crate's `Error` type as `error::HttpError`. [#2171] -* Re-export `StatusCode`, `Method`, `Version` and `Uri` at the crate root. [#2171] -* Re-export `ContentEncoding` and `ConnectionType` at the crate root. [#2171] -* `Response::into_body` that consumes response and returns body type. [#2201] -* `impl Default` for `Response`. [#2201] -* Add zstd support for `ContentEncoding`. [#2244] +- Alias `body::Body` as `body::AnyBody`. [#2215] +- `BoxAnyBody`: a boxed message body with boxed errors. [#2183] +- Re-export `http` crate's `Error` type as `error::HttpError`. [#2171] +- Re-export `StatusCode`, `Method`, `Version` and `Uri` at the crate root. [#2171] +- Re-export `ContentEncoding` and `ConnectionType` at the crate root. [#2171] +- `Response::into_body` that consumes response and returns body type. [#2201] +- `impl Default` for `Response`. [#2201] +- Add zstd support for `ContentEncoding`. [#2244] ### Changed -* The `MessageBody` trait now has an associated `Error` type. [#2183] -* All error trait bounds in server service builders have changed from `Into` to `Into>`. [#2253] -* All error trait bounds in message body and stream impls changed from `Into` to `Into>`. [#2253] -* Places in `Response` where `ResponseBody` was received or returned now simply use `B`. [#2201] -* `header` mod is now public. [#2171] -* `uri` mod is now public. [#2171] -* Update `language-tags` to `0.3`. -* Reduce the level from `error` to `debug` for the log line that is emitted when a `500 Internal Server Error` is built using `HttpResponse::from_error`. [#2201] -* `ResponseBuilder::message_body` now returns a `Result`. [#2201] -* Remove `Unpin` bound on `ResponseBuilder::streaming`. [#2253] -* `HttpServer::{listen_rustls(), bind_rustls()}` now honor the ALPN protocols in the configuation parameter. [#2226] +- The `MessageBody` trait now has an associated `Error` type. [#2183] +- All error trait bounds in server service builders have changed from `Into` to `Into>`. [#2253] +- All error trait bounds in message body and stream impls changed from `Into` to `Into>`. [#2253] +- Places in `Response` where `ResponseBody` was received or returned now simply use `B`. [#2201] +- `header` mod is now public. [#2171] +- `uri` mod is now public. [#2171] +- Update `language-tags` to `0.3`. +- Reduce the level from `error` to `debug` for the log line that is emitted when a `500 Internal Server Error` is built using `HttpResponse::from_error`. [#2201] +- `ResponseBuilder::message_body` now returns a `Result`. [#2201] +- Remove `Unpin` bound on `ResponseBuilder::streaming`. [#2253] +- `HttpServer::{listen_rustls(), bind_rustls()}` now honor the ALPN protocols in the configuation parameter. [#2226] ### Removed -* Stop re-exporting `http` crate's `HeaderMap` types in addition to ours. [#2171] -* Down-casting for `MessageBody` types. [#2183] -* `error::Result` alias. [#2201] -* Error field from `Response` and `Response::error`. [#2205] -* `impl Future` for `Response`. [#2201] -* `Response::take_body` and old `Response::into_body` method that casted body type. [#2201] -* `InternalError` and all the error types it constructed. [#2215] -* Conversion (`impl Into`) of `Response` and `ResponseBuilder` to `Error`. [#2215] +- Stop re-exporting `http` crate's `HeaderMap` types in addition to ours. [#2171] +- Down-casting for `MessageBody` types. [#2183] +- `error::Result` alias. [#2201] +- Error field from `Response` and `Response::error`. [#2205] +- `impl Future` for `Response`. [#2201] +- `Response::take_body` and old `Response::into_body` method that casted body type. [#2201] +- `InternalError` and all the error types it constructed. [#2215] +- Conversion (`impl Into`) of `Response` and `ResponseBuilder` to `Error`. [#2215] [#2171]: https://github.com/actix/actix-web/pull/2171 [#2183]: https://github.com/actix/actix-web/pull/2183 @@ -211,27 +211,27 @@ ## 3.0.0-beta.6 - 2021-04-17 ### Added -* `impl MessageBody for Pin>`. [#2152] -* `Response::{ok, bad_request, not_found, internal_server_error}`. [#2159] -* Helper `body::to_bytes` for async collecting message body into Bytes. [#2158] +- `impl MessageBody for Pin>`. [#2152] +- `Response::{ok, bad_request, not_found, internal_server_error}`. [#2159] +- Helper `body::to_bytes` for async collecting message body into Bytes. [#2158] ### Changes -* The type parameter of `Response` no longer has a default. [#2152] -* The `Message` variant of `body::Body` is now `Pin>`. [#2152] -* `BodyStream` and `SizedStream` are no longer restricted to Unpin types. [#2152] -* Error enum types are marked `#[non_exhaustive]`. [#2161] +- The type parameter of `Response` no longer has a default. [#2152] +- The `Message` variant of `body::Body` is now `Pin>`. [#2152] +- `BodyStream` and `SizedStream` are no longer restricted to Unpin types. [#2152] +- Error enum types are marked `#[non_exhaustive]`. [#2161] ### Removed -* `cookies` feature flag. [#2065] -* Top-level `cookies` mod (re-export). [#2065] -* `HttpMessage` trait loses the `cookies` and `cookie` methods. [#2065] -* `impl ResponseError for CookieParseError`. [#2065] -* Deprecated methods on `ResponseBuilder`: `if_true`, `if_some`. [#2148] -* `ResponseBuilder::json`. [#2148] -* `ResponseBuilder::{set_header, header}`. [#2148] -* `impl From for Body`. [#2148] -* `Response::build_from`. [#2159] -* Most of the status code builders on `Response`. [#2159] +- `cookies` feature flag. [#2065] +- Top-level `cookies` mod (re-export). [#2065] +- `HttpMessage` trait loses the `cookies` and `cookie` methods. [#2065] +- `impl ResponseError for CookieParseError`. [#2065] +- Deprecated methods on `ResponseBuilder`: `if_true`, `if_some`. [#2148] +- `ResponseBuilder::json`. [#2148] +- `ResponseBuilder::{set_header, header}`. [#2148] +- `impl From for Body`. [#2148] +- `Response::build_from`. [#2159] +- Most of the status code builders on `Response`. [#2159] [#2065]: https://github.com/actix/actix-web/pull/2065 [#2148]: https://github.com/actix/actix-web/pull/2148 @@ -243,16 +243,16 @@ ## 3.0.0-beta.5 - 2021-04-02 ### Added -* `client::Connector::handshake_timeout` method for customizing TLS connection handshake timeout. [#2081] -* `client::ConnectorService` as `client::Connector::finish` method's return type [#2081] -* `client::ConnectionIo` trait alias [#2081] +- `client::Connector::handshake_timeout` method for customizing TLS connection handshake timeout. [#2081] +- `client::ConnectorService` as `client::Connector::finish` method's return type [#2081] +- `client::ConnectionIo` trait alias [#2081] ### Changed -* `client::Connector` type now only have one generic type for `actix_service::Service`. [#2063] +- `client::Connector` type now only have one generic type for `actix_service::Service`. [#2063] ### Removed -* Common typed HTTP headers were moved to actix-web. [2094] -* `ResponseError` impl for `actix_utils::timeout::TimeoutError`. [#2127] +- Common typed HTTP headers were moved to actix-web. [2094] +- `ResponseError` impl for `actix_utils::timeout::TimeoutError`. [#2127] [#2063]: https://github.com/actix/actix-web/pull/2063 [#2081]: https://github.com/actix/actix-web/pull/2081 @@ -262,13 +262,13 @@ ## 3.0.0-beta.4 - 2021-03-08 ### Changed -* Feature `cookies` is now optional and disabled by default. [#1981] -* `ws::hash_key` now returns array. [#2035] -* `ResponseBuilder::json` now takes `impl Serialize`. [#2052] +- Feature `cookies` is now optional and disabled by default. [#1981] +- `ws::hash_key` now returns array. [#2035] +- `ResponseBuilder::json` now takes `impl Serialize`. [#2052] ### Removed -* Re-export of `futures_channel::oneshot::Canceled` is removed from `error` mod. [#1994] -* `ResponseError` impl for `futures_channel::oneshot::Canceled` is removed. [#1994] +- Re-export of `futures_channel::oneshot::Canceled` is removed from `error` mod. [#1994] +- `ResponseError` impl for `futures_channel::oneshot::Canceled` is removed. [#1994] [#1981]: https://github.com/actix/actix-web/pull/1981 [#1994]: https://github.com/actix/actix-web/pull/1994 @@ -277,48 +277,48 @@ ## 3.0.0-beta.3 - 2021-02-10 -* No notable changes. +- No notable changes. ## 3.0.0-beta.2 - 2021-02-10 ### Added -* `TryIntoHeaderPair` trait that allows using typed and untyped headers in the same methods. [#1869] -* `ResponseBuilder::insert_header` method which allows using typed headers. [#1869] -* `ResponseBuilder::append_header` method which allows using typed headers. [#1869] -* `TestRequest::insert_header` method which allows using typed headers. [#1869] -* `ContentEncoding` implements all necessary header traits. [#1912] -* `HeaderMap::len_keys` has the behavior of the old `len` method. [#1964] -* `HeaderMap::drain` as an efficient draining iterator. [#1964] -* Implement `IntoIterator` for owned `HeaderMap`. [#1964] -* `trust-dns` optional feature to enable `trust-dns-resolver` as client dns resolver. [#1969] +- `TryIntoHeaderPair` trait that allows using typed and untyped headers in the same methods. [#1869] +- `ResponseBuilder::insert_header` method which allows using typed headers. [#1869] +- `ResponseBuilder::append_header` method which allows using typed headers. [#1869] +- `TestRequest::insert_header` method which allows using typed headers. [#1869] +- `ContentEncoding` implements all necessary header traits. [#1912] +- `HeaderMap::len_keys` has the behavior of the old `len` method. [#1964] +- `HeaderMap::drain` as an efficient draining iterator. [#1964] +- Implement `IntoIterator` for owned `HeaderMap`. [#1964] +- `trust-dns` optional feature to enable `trust-dns-resolver` as client dns resolver. [#1969] ### Changed -* `ResponseBuilder::content_type` now takes an `impl TryIntoHeaderValue` to support using typed +- `ResponseBuilder::content_type` now takes an `impl TryIntoHeaderValue` to support using typed `mime` types. [#1894] -* Renamed `TryIntoHeaderValue::{try_into => try_into_value}` to avoid ambiguity with std +- Renamed `TryIntoHeaderValue::{try_into => try_into_value}` to avoid ambiguity with std `TryInto` trait. [#1894] -* `Extensions::insert` returns Option of replaced item. [#1904] -* Remove `HttpResponseBuilder::json2()`. [#1903] -* Enable `HttpResponseBuilder::json()` to receive data by value and reference. [#1903] -* `client::error::ConnectError` Resolver variant contains `Box` type. [#1905] -* `client::ConnectorConfig` default timeout changed to 5 seconds. [#1905] -* Simplify `BlockingError` type to a unit struct. It's now only triggered when blocking thread pool +- `Extensions::insert` returns Option of replaced item. [#1904] +- Remove `HttpResponseBuilder::json2()`. [#1903] +- Enable `HttpResponseBuilder::json()` to receive data by value and reference. [#1903] +- `client::error::ConnectError` Resolver variant contains `Box` type. [#1905] +- `client::ConnectorConfig` default timeout changed to 5 seconds. [#1905] +- Simplify `BlockingError` type to a unit struct. It's now only triggered when blocking thread pool is dead. [#1957] -* `HeaderMap::len` now returns number of values instead of number of keys. [#1964] -* `HeaderMap::insert` now returns iterator of removed values. [#1964] -* `HeaderMap::remove` now returns iterator of removed values. [#1964] +- `HeaderMap::len` now returns number of values instead of number of keys. [#1964] +- `HeaderMap::insert` now returns iterator of removed values. [#1964] +- `HeaderMap::remove` now returns iterator of removed values. [#1964] ### Removed -* `ResponseBuilder::set`; use `ResponseBuilder::insert_header`. [#1869] -* `ResponseBuilder::set_header`; use `ResponseBuilder::insert_header`. [#1869] -* `ResponseBuilder::header`; use `ResponseBuilder::append_header`. [#1869] -* `TestRequest::with_hdr`; use `TestRequest::default().insert_header()`. [#1869] -* `TestRequest::with_header`; use `TestRequest::default().insert_header()`. [#1869] -* `actors` optional feature. [#1969] -* `ResponseError` impl for `actix::MailboxError`. [#1969] +- `ResponseBuilder::set`; use `ResponseBuilder::insert_header`. [#1869] +- `ResponseBuilder::set_header`; use `ResponseBuilder::insert_header`. [#1869] +- `ResponseBuilder::header`; use `ResponseBuilder::append_header`. [#1869] +- `TestRequest::with_hdr`; use `TestRequest::default().insert_header()`. [#1869] +- `TestRequest::with_header`; use `TestRequest::default().insert_header()`. [#1869] +- `actors` optional feature. [#1969] +- `ResponseError` impl for `actix::MailboxError`. [#1969] ### Documentation -* Vastly improve docs and add examples for `HeaderMap`. [#1964] +- Vastly improve docs and add examples for `HeaderMap`. [#1964] [#1869]: https://github.com/actix/actix-web/pull/1869 [#1894]: https://github.com/actix/actix-web/pull/1894 @@ -333,24 +333,24 @@ ## 3.0.0-beta.1 - 2021-01-07 ### Added -* Add `Http3` to `Protocol` enum for future compatibility and also mark `#[non_exhaustive]`. +- Add `Http3` to `Protocol` enum for future compatibility and also mark `#[non_exhaustive]`. ### Changed -* Update `actix-*` dependencies to tokio `1.0` based versions. [#1813] -* Bumped `rand` to `0.8`. -* Update `bytes` to `1.0`. [#1813] -* Update `h2` to `0.3`. [#1813] -* The `ws::Message::Text` enum variant now contains a `bytestring::ByteString`. [#1864] +- Update `actix-*` dependencies to tokio `1.0` based versions. [#1813] +- Bumped `rand` to `0.8`. +- Update `bytes` to `1.0`. [#1813] +- Update `h2` to `0.3`. [#1813] +- The `ws::Message::Text` enum variant now contains a `bytestring::ByteString`. [#1864] ### Removed -* Deprecated `on_connect` methods have been removed. Prefer the new +- Deprecated `on_connect` methods have been removed. Prefer the new `on_connect_ext` technique. [#1857] -* Remove `ResponseError` impl for `actix::actors::resolver::ResolverError` +- Remove `ResponseError` impl for `actix::actors::resolver::ResolverError` due to deprecate of resolver actor. [#1813] -* Remove `ConnectError::SslHandshakeError` and re-export of `HandshakeError`. +- Remove `ConnectError::SslHandshakeError` and re-export of `HandshakeError`. due to the removal of this type from `tokio-openssl` crate. openssl handshake error would return as `ConnectError::SslError`. [#1813] -* Remove `actix-threadpool` dependency. Use `actix_rt::task::spawn_blocking`. +- Remove `actix-threadpool` dependency. Use `actix_rt::task::spawn_blocking`. Due to this change `actix_threadpool::BlockingError` type is moved into `actix_http::error` module. [#1878] @@ -362,20 +362,20 @@ ## 2.2.1 - 2021-08-09 ### Fixed -* Potential HTTP request smuggling vulnerabilities. [RUSTSEC-2021-0081](https://github.com/rustsec/advisory-db/pull/977) +- Potential HTTP request smuggling vulnerabilities. [RUSTSEC-2021-0081](https://github.com/rustsec/advisory-db/pull/977) ## 2.2.0 - 2020-11-25 ### Added -* HttpResponse builders for 1xx status codes. [#1768] -* `Accept::mime_precedence` and `Accept::mime_preference`. [#1793] -* `TryFrom` and `TryFrom` for `http::header::Quality`. [#1797] +- HttpResponse builders for 1xx status codes. [#1768] +- `Accept::mime_precedence` and `Accept::mime_preference`. [#1793] +- `TryFrom` and `TryFrom` for `http::header::Quality`. [#1797] ### Fixed -* Started dropping `transfer-encoding: chunked` and `Content-Length` for 1XX and 204 responses. [#1767] +- Started dropping `transfer-encoding: chunked` and `Content-Length` for 1XX and 204 responses. [#1767] ### Changed -* Upgrade `serde_urlencoded` to `0.7`. [#1773] +- Upgrade `serde_urlencoded` to `0.7`. [#1773] [#1773]: https://github.com/actix/actix-web/pull/1773 [#1767]: https://github.com/actix/actix-web/pull/1767 @@ -386,12 +386,12 @@ ## 2.1.0 - 2020-10-30 ### Added -* Added more flexible `on_connect_ext` methods for on-connect handling. [#1754] +- Added more flexible `on_connect_ext` methods for on-connect handling. [#1754] ### Changed -* Upgrade `base64` to `0.13`. [#1744] -* Upgrade `pin-project` to `1.0`. [#1733] -* Deprecate `ResponseBuilder::{if_some, if_true}`. [#1760] +- Upgrade `base64` to `0.13`. [#1744] +- Upgrade `pin-project` to `1.0`. [#1733] +- Deprecate `ResponseBuilder::{if_some, if_true}`. [#1760] [#1760]: https://github.com/actix/actix-web/pull/1760 [#1754]: https://github.com/actix/actix-web/pull/1754 @@ -400,28 +400,28 @@ ## 2.0.0 - 2020-09-11 -* No significant changes from `2.0.0-beta.4`. +- No significant changes from `2.0.0-beta.4`. ## 2.0.0-beta.4 - 2020-09-09 ### Changed -* Update actix-codec and actix-utils dependencies. -* Update actix-connect and actix-tls dependencies. +- Update actix-codec and actix-utils dependencies. +- Update actix-connect and actix-tls dependencies. ## 2.0.0-beta.3 - 2020-08-14 ### Fixed -* Memory leak of `client::pool::ConnectorPoolSupport`. [#1626] +- Memory leak of `client::pool::ConnectorPoolSupport`. [#1626] [#1626]: https://github.com/actix/actix-web/pull/1626 ## 2.0.0-beta.2 - 2020-07-21 ### Fixed -* Potential UB in h1 decoder using uninitialized memory. [#1614] +- Potential UB in h1 decoder using uninitialized memory. [#1614] ### Changed -* Fix illegal chunked encoding. [#1615] +- Fix illegal chunked encoding. [#1615] [#1614]: https://github.com/actix/actix-web/pull/1614 [#1615]: https://github.com/actix/actix-web/pull/1615 @@ -429,10 +429,10 @@ ## 2.0.0-beta.1 - 2020-07-11 ### Changed -* Migrate cookie handling to `cookie` crate. [#1558] -* Update `sha-1` to 0.9. [#1586] -* Fix leak in client pool. [#1580] -* MSRV is now 1.41.1. +- Migrate cookie handling to `cookie` crate. [#1558] +- Update `sha-1` to 0.9. [#1586] +- Fix leak in client pool. [#1580] +- MSRV is now 1.41.1. [#1558]: https://github.com/actix/actix-web/pull/1558 [#1586]: https://github.com/actix/actix-web/pull/1586 @@ -441,15 +441,15 @@ ## 2.0.0-alpha.4 - 2020-05-21 ### Changed -* Bump minimum supported Rust version to 1.40 -* content_length function is removed, and you can set Content-Length by calling +- Bump minimum supported Rust version to 1.40 +- content_length function is removed, and you can set Content-Length by calling no_chunking function [#1439] -* `BodySize::Sized64` variant has been removed. `BodySize::Sized` now receives a +- `BodySize::Sized64` variant has been removed. `BodySize::Sized` now receives a `u64` instead of a `usize`. -* Update `base64` dependency to 0.12 +- Update `base64` dependency to 0.12 ### Fixed -* Support parsing of `SameSite=None` [#1503] +- Support parsing of `SameSite=None` [#1503] [#1439]: https://github.com/actix/actix-web/pull/1439 [#1503]: https://github.com/actix/actix-web/pull/1503 @@ -457,13 +457,13 @@ ## 2.0.0-alpha.3 - 2020-05-08 ### Fixed -* Correct spelling of ConnectError::Unresolved [#1487] -* Fix a mistake in the encoding of websocket continuation messages wherein +- Correct spelling of ConnectError::Unresolved [#1487] +- Fix a mistake in the encoding of websocket continuation messages wherein Item::FirstText and Item::FirstBinary are each encoded as the other. ### Changed -* Implement `std::error::Error` for our custom errors [#1422] -* Remove `failure` support for `ResponseError` since that crate +- Implement `std::error::Error` for our custom errors [#1422] +- Remove `failure` support for `ResponseError` since that crate will be deprecated in the near future. [#1422]: https://github.com/actix/actix-web/pull/1422 @@ -472,12 +472,12 @@ ## 2.0.0-alpha.2 - 2020-03-07 ### Changed -* Update `actix-connect` and `actix-tls` dependency to 2.0.0-alpha.1. [#1395] -* Change default initial window size and connection window size for HTTP2 to 2MB and 1MB +- Update `actix-connect` and `actix-tls` dependency to 2.0.0-alpha.1. [#1395] +- Change default initial window size and connection window size for HTTP2 to 2MB and 1MB respectively to improve download speed for awc when downloading large objects. [#1394] -* client::Connector accepts initial_window_size and initial_connection_window_size +- client::Connector accepts initial_window_size and initial_connection_window_size HTTP2 configuration. [#1394] -* client::Connector allowing to set max_http_version to limit HTTP version to be used. [#1394] +- client::Connector allowing to set max_http_version to limit HTTP version to be used. [#1394] [#1394]: https://github.com/actix/actix-web/pull/1394 [#1395]: https://github.com/actix/actix-web/pull/1395 @@ -485,61 +485,61 @@ ## 2.0.0-alpha.1 - 2020-02-27 ### Changed -* Update the `time` dependency to 0.2.7. -* Moved actors messages support from actix crate, enabled with feature `actors`. -* Breaking change: trait MessageBody requires Unpin and accepting `Pin<&mut Self>` instead of +- Update the `time` dependency to 0.2.7. +- Moved actors messages support from actix crate, enabled with feature `actors`. +- Breaking change: trait MessageBody requires Unpin and accepting `Pin<&mut Self>` instead of `&mut self` in the poll_next(). -* MessageBody is not implemented for &'static [u8] anymore. +- MessageBody is not implemented for &'static [u8] anymore. ### Fixed -* Allow `SameSite=None` cookies to be sent in a response. +- Allow `SameSite=None` cookies to be sent in a response. ## 1.0.1 - 2019-12-20 ### Fixed -* Poll upgrade service's readiness from HTTP service handlers -* Replace brotli with brotli2 #1224 +- Poll upgrade service's readiness from HTTP service handlers +- Replace brotli with brotli2 #1224 ## 1.0.0 - 2019-12-13 ### Added -* Add websockets continuation frame support +- Add websockets continuation frame support ### Changed -* Replace `flate2-xxx` features with `compress` +- Replace `flate2-xxx` features with `compress` ## 1.0.0-alpha.5 - 2019-12-09 ### Fixed -* Check `Upgrade` service readiness before calling it -* Fix buffer remaining capacity calculation +- Check `Upgrade` service readiness before calling it +- Fix buffer remaining capacity calculation ### Changed -* Websockets: Ping and Pong should have binary data #1049 +- Websockets: Ping and Pong should have binary data #1049 ## 1.0.0-alpha.4 - 2019-12-08 ### Added -* Add impl ResponseBuilder for Error +- Add impl ResponseBuilder for Error ### Changed -* Use rust based brotli compression library +- Use rust based brotli compression library ## 1.0.0-alpha.3 - 2019-12-07 ### Changed -* Migrate to tokio 0.2 -* Migrate to `std::future` +- Migrate to tokio 0.2 +- Migrate to `std::future` ## 0.2.11 - 2019-11-06 ### Added -* Add support for serde_json::Value to be passed as argument to ResponseBuilder.body() -* Add an additional `filename*` param in the `Content-Disposition` header of +- Add support for serde_json::Value to be passed as argument to ResponseBuilder.body() +- Add an additional `filename*` param in the `Content-Disposition` header of `actix_files::NamedFile` to be more compatible. (#1151) -* Allow to use `std::convert::Infallible` as `actix_http::error::Error` +- Allow to use `std::convert::Infallible` as `actix_http::error::Error` ### Fixed -* To be compatible with non-English error responses, `ResponseError` rendered with `text/plain; +- To be compatible with non-English error responses, `ResponseError` rendered with `text/plain; charset=utf-8` header [#1118] [#1878]: https://github.com/actix/actix-web/pull/1878 @@ -547,169 +547,169 @@ ## 0.2.10 - 2019-09-11 ### Added -* Add support for sending HTTP requests with `Rc` in addition to sending HTTP requests +- Add support for sending HTTP requests with `Rc` in addition to sending HTTP requests with `RequestHead` ### Fixed -* h2 will use error response #1080 -* on_connect result isn't added to request extensions for http2 requests #1009 +- h2 will use error response #1080 +- on_connect result isn't added to request extensions for http2 requests #1009 ## 0.2.9 - 2019-08-13 ### Changed -* Dropped the `byteorder`-dependency in favor of `stdlib`-implementation -* Update percent-encoding to 2.1 -* Update serde_urlencoded to 0.6.1 +- Dropped the `byteorder`-dependency in favor of `stdlib`-implementation +- Update percent-encoding to 2.1 +- Update serde_urlencoded to 0.6.1 ### Fixed -* Fixed a panic in the HTTP2 handshake in client HTTP requests (#1031) +- Fixed a panic in the HTTP2 handshake in client HTTP requests (#1031) ## 0.2.8 - 2019-08-01 ### Added -* Add `rustls` support -* Add `Clone` impl for `HeaderMap` +- Add `rustls` support +- Add `Clone` impl for `HeaderMap` ### Fixed -* awc client panic #1016 -* Invalid response with compression middleware enabled, but compression-related features +- awc client panic #1016 +- Invalid response with compression middleware enabled, but compression-related features disabled #997 ## 0.2.7 - 2019-07-18 ### Added -* Add support for downcasting response errors #986 +- Add support for downcasting response errors #986 ## 0.2.6 - 2019-07-17 ### Changed -* Replace `ClonableService` with local copy -* Upgrade `rand` dependency version to 0.7 +- Replace `ClonableService` with local copy +- Upgrade `rand` dependency version to 0.7 ## 0.2.5 - 2019-06-28 ### Added -* Add `on-connect` callback, `HttpServiceBuilder::on_connect()` #946 +- Add `on-connect` callback, `HttpServiceBuilder::on_connect()` #946 ### Changed -* Use `encoding_rs` crate instead of unmaintained `encoding` crate -* Add `Copy` and `Clone` impls for `ws::Codec` +- Use `encoding_rs` crate instead of unmaintained `encoding` crate +- Add `Copy` and `Clone` impls for `ws::Codec` ## 0.2.4 - 2019-06-16 ### Fixed -* Do not compress NoContent (204) responses #918 +- Do not compress NoContent (204) responses #918 ## 0.2.3 - 2019-06-02 ### Added -* Debug impl for ResponseBuilder -* From SizedStream and BodyStream for Body +- Debug impl for ResponseBuilder +- From SizedStream and BodyStream for Body ### Changed -* SizedStream uses u64 +- SizedStream uses u64 ## 0.2.2 - 2019-05-29 ### Fixed -* Parse incoming stream before closing stream on disconnect #868 +- Parse incoming stream before closing stream on disconnect #868 ## 0.2.1 - 2019-05-25 ### Fixed -* Handle socket read disconnect +- Handle socket read disconnect ## 0.2.0 - 2019-05-12 ### Changed -* Update actix-service to 0.4 -* Expect and upgrade services accept `ServerConfig` config. +- Update actix-service to 0.4 +- Expect and upgrade services accept `ServerConfig` config. ### Deleted -* `OneRequest` service +- `OneRequest` service ## 0.1.5 - 2019-05-04 ### Fixed -* Clean up response extensions in response pool #817 +- Clean up response extensions in response pool #817 ## 0.1.4 - 2019-04-24 ### Added -* Allow to render h1 request headers in `Camel-Case` +- Allow to render h1 request headers in `Camel-Case` ### Fixed -* Read until eof for http/1.0 responses #771 +- Read until eof for http/1.0 responses #771 ## 0.1.3 - 2019-04-23 ### Fixed -* Fix http client pool management -* Fix http client wait queue management #794 +- Fix http client pool management +- Fix http client wait queue management #794 ## 0.1.2 - 2019-04-23 ### Fixed -* Fix BorrowMutError panic in client connector #793 +- Fix BorrowMutError panic in client connector #793 ## 0.1.1 - 2019-04-19 ### Changed -* Cookie::max_age() accepts value in seconds -* Cookie::max_age_time() accepts value in time::Duration -* Allow to specify server address for client connector +- Cookie::max_age() accepts value in seconds +- Cookie::max_age_time() accepts value in time::Duration +- Allow to specify server address for client connector ## 0.1.0 - 2019-04-16 ### Added -* Expose peer addr via `Request::peer_addr()` and `RequestHead::peer_addr` +- Expose peer addr via `Request::peer_addr()` and `RequestHead::peer_addr` ### Changed -* `actix_http::encoding` always available -* use trust-dns-resolver 0.11.0 +- `actix_http::encoding` always available +- use trust-dns-resolver 0.11.0 ## 0.1.0-alpha.5 - 2019-04-12 ### Added -* Allow to use custom service for upgrade requests -* Added `h1::SendResponse` future. +- Allow to use custom service for upgrade requests +- Added `h1::SendResponse` future. ### Changed -* MessageBody::length() renamed to MessageBody::size() for consistency -* ws handshake verification functions take RequestHead instead of Request +- MessageBody::length() renamed to MessageBody::size() for consistency +- ws handshake verification functions take RequestHead instead of Request ## 0.1.0-alpha.4 - 2019-04-08 ### Added -* Allow to use custom `Expect` handler -* Add minimal `std::error::Error` impl for `Error` +- Allow to use custom `Expect` handler +- Add minimal `std::error::Error` impl for `Error` ### Changed -* Export IntoHeaderValue -* Render error and return as response body -* Use thread pool for response body compression +- Export IntoHeaderValue +- Render error and return as response body +- Use thread pool for response body compression ### Deleted -* Removed PayloadBuffer +- Removed PayloadBuffer ## 0.1.0-alpha.3 - 2019-04-02 ### Added -* Warn when an unsealed private cookie isn't valid UTF-8 +- Warn when an unsealed private cookie isn't valid UTF-8 ### Fixed -* Rust 1.31.0 compatibility -* Preallocate read buffer for h1 codec -* Detect socket disconnection during protocol selection +- Rust 1.31.0 compatibility +- Preallocate read buffer for h1 codec +- Detect socket disconnection during protocol selection ## 0.1.0-alpha.2 - 2019-03-29 ### Added -* Added ws::Message::Nop, no-op websockets message +- Added ws::Message::Nop, no-op websockets message ### Changed -* Do not use thread pool for decompression if chunk size is smaller than 2048. +- Do not use thread pool for decompression if chunk size is smaller than 2048. ## 0.1.0-alpha.1 - 2019-03-28 -* Initial impl +- Initial impl diff --git a/actix-multipart/CHANGES.md b/actix-multipart/CHANGES.md index 8d9c1640f..e58c3ee24 100644 --- a/actix-multipart/CHANGES.md +++ b/actix-multipart/CHANGES.md @@ -4,119 +4,119 @@ ## 0.4.0-beta.10 - 2021-12-11 -* No significant changes since `0.4.0-beta.9`. +- No significant changes since `0.4.0-beta.9`. ## 0.4.0-beta.9 - 2021-12-01 -* Polling `Field` after dropping `Multipart` now fails immediately instead of hanging forever. [#2463] +- Polling `Field` after dropping `Multipart` now fails immediately instead of hanging forever. [#2463] [#2463]: https://github.com/actix/actix-web/pull/2463 ## 0.4.0-beta.8 - 2021-11-22 -* Ensure a correct Content-Disposition header is included in every part of a multipart message. [#2451] -* Added `MultipartError::NoContentDisposition` variant. [#2451] -* Since Content-Disposition is now ensured, `Field::content_disposition` is now infallible. [#2451] -* Added `Field::name` method for getting the field name. [#2451] -* `MultipartError` now marks variants with inner errors as the source. [#2451] -* `MultipartError` is now marked as non-exhaustive. [#2451] +- Ensure a correct Content-Disposition header is included in every part of a multipart message. [#2451] +- Added `MultipartError::NoContentDisposition` variant. [#2451] +- Since Content-Disposition is now ensured, `Field::content_disposition` is now infallible. [#2451] +- Added `Field::name` method for getting the field name. [#2451] +- `MultipartError` now marks variants with inner errors as the source. [#2451] +- `MultipartError` is now marked as non-exhaustive. [#2451] [#2451]: https://github.com/actix/actix-web/pull/2451 ## 0.4.0-beta.7 - 2021-10-20 -* Minimum supported Rust version (MSRV) is now 1.52. +- Minimum supported Rust version (MSRV) is now 1.52. ## 0.4.0-beta.6 - 2021-09-09 -* Minimum supported Rust version (MSRV) is now 1.51. +- Minimum supported Rust version (MSRV) is now 1.51. ## 0.4.0-beta.5 - 2021-06-17 -* No notable changes. +- No notable changes. ## 0.4.0-beta.4 - 2021-04-02 -* No notable changes. +- No notable changes. ## 0.4.0-beta.3 - 2021-03-09 -* No notable changes. +- No notable changes. ## 0.4.0-beta.2 - 2021-02-10 -* No notable changes. +- No notable changes. ## 0.4.0-beta.1 - 2021-01-07 -* Fix multipart consuming payload before header checks. [#1513] -* Update `bytes` to `1.0`. [#1813] +- Fix multipart consuming payload before header checks. [#1513] +- Update `bytes` to `1.0`. [#1813] [#1813]: https://github.com/actix/actix-web/pull/1813 [#1513]: https://github.com/actix/actix-web/pull/1513 ## 0.3.0 - 2020-09-11 -* No significant changes from `0.3.0-beta.2`. +- No significant changes from `0.3.0-beta.2`. ## 0.3.0-beta.2 - 2020-09-10 -* Update `actix-*` dependencies to latest versions. +- Update `actix-*` dependencies to latest versions. ## 0.3.0-beta.1 - 2020-07-15 -* Update `actix-web` to 3.0.0-beta.1 +- Update `actix-web` to 3.0.0-beta.1 ## 0.3.0-alpha.1 - 2020-05-25 -* Update `actix-web` to 3.0.0-alpha.3 -* Bump minimum supported Rust version to 1.40 -* Minimize `futures` dependencies -* Remove the unused `time` dependency -* Fix missing `std::error::Error` implement for `MultipartError`. +- Update `actix-web` to 3.0.0-alpha.3 +- Bump minimum supported Rust version to 1.40 +- Minimize `futures` dependencies +- Remove the unused `time` dependency +- Fix missing `std::error::Error` implement for `MultipartError`. ## [0.2.0] - 2019-12-20 -* Release +- Release ## [0.2.0-alpha.4] - 2019-12-xx -* Multipart handling now handles Pending during read of boundary #1205 +- Multipart handling now handles Pending during read of boundary #1205 ## [0.2.0-alpha.2] - 2019-12-03 -* Migrate to `std::future` +- Migrate to `std::future` ## [0.1.4] - 2019-09-12 -* Multipart handling now parses requests which do not end in CRLF #1038 +- Multipart handling now parses requests which do not end in CRLF #1038 ## [0.1.3] - 2019-08-18 -* Fix ring dependency from actix-web default features for #741. +- Fix ring dependency from actix-web default features for #741. ## [0.1.2] - 2019-06-02 -* Fix boundary parsing #876 +- Fix boundary parsing #876 ## [0.1.1] - 2019-05-25 -* Fix disconnect handling #834 +- Fix disconnect handling #834 ## [0.1.0] - 2019-05-18 -* Release +- Release ## [0.1.0-beta.4] - 2019-05-12 -* Handle cancellation of uploads #736 +- Handle cancellation of uploads #736 -* Upgrade to actix-web 1.0.0-beta.4 +- Upgrade to actix-web 1.0.0-beta.4 ## [0.1.0-beta.1] - 2019-04-21 -* Do not support nested multipart +- Do not support nested multipart -* Split multipart support to separate crate +- Split multipart support to separate crate -* Optimize multipart handling #634, #769 +- Optimize multipart handling #634, #769 diff --git a/actix-router/CHANGES.md b/actix-router/CHANGES.md index d0ed55c88..0a6a56359 100644 --- a/actix-router/CHANGES.md +++ b/actix-router/CHANGES.md @@ -4,20 +4,20 @@ ## 0.5.0-beta.3 - 2021-12-17 -* Minimum supported Rust version (MSRV) is now 1.52. +- Minimum supported Rust version (MSRV) is now 1.52. ## 0.5.0-beta.2 - 2021-09-09 -* Introduce `ResourceDef::join`. [#380] -* Disallow prefix routes with tail segments. [#379] -* Enforce path separators on dynamic prefixes. [#378] -* Improve malformed path error message. [#384] -* Prefix segments now always end with with a segment delimiter or end-of-input. [#2355] -* Prefix segments with trailing slashes define a trailing empty segment. [#2355] -* Support multi-pattern prefixes and joins. [#2356] -* `ResourceDef::pattern` now returns the first pattern in multi-pattern resources. [#2356] -* Support `build_resource_path` on multi-pattern resources. [#2356] -* Minimum supported Rust version (MSRV) is now 1.51. +- Introduce `ResourceDef::join`. [#380] +- Disallow prefix routes with tail segments. [#379] +- Enforce path separators on dynamic prefixes. [#378] +- Improve malformed path error message. [#384] +- Prefix segments now always end with with a segment delimiter or end-of-input. [#2355] +- Prefix segments with trailing slashes define a trailing empty segment. [#2355] +- Support multi-pattern prefixes and joins. [#2356] +- `ResourceDef::pattern` now returns the first pattern in multi-pattern resources. [#2356] +- Support `build_resource_path` on multi-pattern resources. [#2356] +- Minimum supported Rust version (MSRV) is now 1.51. [#378]: https://github.com/actix/actix-net/pull/378 [#379]: https://github.com/actix/actix-net/pull/379 @@ -28,23 +28,23 @@ ## 0.5.0-beta.1 - 2021-07-20 -* Fix a bug in multi-patterns where static patterns are interpreted as regex. [#366] -* Introduce `ResourceDef::pattern_iter` to get an iterator over all patterns in a multi-pattern resource. [#373] -* Fix segment interpolation leaving `Path` in unintended state after matching. [#368] -* Fix `ResourceDef` `PartialEq` implementation. [#373] -* Re-work `IntoPatterns` trait, adding a `Patterns` enum. [#372] -* Implement `IntoPatterns` for `bytestring::ByteString`. [#372] -* Rename `Path::{len => segment_count}` to be more descriptive of it's purpose. [#370] -* Rename `ResourceDef::{resource_path => resource_path_from_iter}`. [#371] -* `ResourceDef::resource_path_from_iter` now takes an `IntoIterator`. [#373] -* Rename `ResourceDef::{resource_path_named => resource_path_from_map}`. [#371] -* Rename `ResourceDef::{is_prefix_match => find_match}`. [#373] -* Rename `ResourceDef::{match_path => capture_match_info}`. [#373] -* Rename `ResourceDef::{match_path_checked => capture_match_info_fn}`. [#373] -* Remove `ResourceDef::name_mut` and introduce `ResourceDef::set_name`. [#373] -* Rename `Router::{*_checked => *_fn}`. [#373] -* Return type of `ResourceDef::name` is now `Option<&str>`. [#373] -* Return type of `ResourceDef::pattern` is now `Option<&str>`. [#373] +- Fix a bug in multi-patterns where static patterns are interpreted as regex. [#366] +- Introduce `ResourceDef::pattern_iter` to get an iterator over all patterns in a multi-pattern resource. [#373] +- Fix segment interpolation leaving `Path` in unintended state after matching. [#368] +- Fix `ResourceDef` `PartialEq` implementation. [#373] +- Re-work `IntoPatterns` trait, adding a `Patterns` enum. [#372] +- Implement `IntoPatterns` for `bytestring::ByteString`. [#372] +- Rename `Path::{len => segment_count}` to be more descriptive of it's purpose. [#370] +- Rename `ResourceDef::{resource_path => resource_path_from_iter}`. [#371] +- `ResourceDef::resource_path_from_iter` now takes an `IntoIterator`. [#373] +- Rename `ResourceDef::{resource_path_named => resource_path_from_map}`. [#371] +- Rename `ResourceDef::{is_prefix_match => find_match}`. [#373] +- Rename `ResourceDef::{match_path => capture_match_info}`. [#373] +- Rename `ResourceDef::{match_path_checked => capture_match_info_fn}`. [#373] +- Remove `ResourceDef::name_mut` and introduce `ResourceDef::set_name`. [#373] +- Rename `Router::{*_checked => *_fn}`. [#373] +- Return type of `ResourceDef::name` is now `Option<&str>`. [#373] +- Return type of `ResourceDef::pattern` is now `Option<&str>`. [#373] [#368]: https://github.com/actix/actix-net/pull/368 [#366]: https://github.com/actix/actix-net/pull/366 @@ -56,10 +56,10 @@ ## 0.4.0 - 2021-06-06 -* When matching path parameters, `%25` is now kept in the percent-encoded form; no longer decoded to `%`. [#357] -* Path tail patterns now match new lines (`\n`) in request URL. [#360] -* Fixed a safety bug where `Path` could return a malformed string after percent decoding. [#359] -* Methods `Path::{add, add_static}` now take `impl Into>`. [#345] +- When matching path parameters, `%25` is now kept in the percent-encoded form; no longer decoded to `%`. [#357] +- Path tail patterns now match new lines (`\n`) in request URL. [#360] +- Fixed a safety bug where `Path` could return a malformed string after percent decoding. [#359] +- Methods `Path::{add, add_static}` now take `impl Into>`. [#345] [#345]: https://github.com/actix/actix-net/pull/345 [#357]: https://github.com/actix/actix-net/pull/357 @@ -68,68 +68,68 @@ ## 0.3.0 - 2019-12-31 -* Version was yanked previously. See https://crates.io/crates/actix-router/0.3.0 +- Version was yanked previously. See https://crates.io/crates/actix-router/0.3.0 ## 0.2.7 - 2021-02-06 -* Add `Router::recognize_checked` [#247] +- Add `Router::recognize_checked` [#247] [#247]: https://github.com/actix/actix-net/pull/247 ## 0.2.6 - 2021-01-09 -* Use `bytestring` version range compatible with Bytes v1.0. [#246] +- Use `bytestring` version range compatible with Bytes v1.0. [#246] [#246]: https://github.com/actix/actix-net/pull/246 ## 0.2.5 - 2020-09-20 -* Fix `from_hex()` method +- Fix `from_hex()` method ## 0.2.4 - 2019-12-31 -* Add `ResourceDef::resource_path_named()` path generation method +- Add `ResourceDef::resource_path_named()` path generation method ## 0.2.3 - 2019-12-25 -* Add impl `IntoPattern` for `&String` +- Add impl `IntoPattern` for `&String` ## 0.2.2 - 2019-12-25 -* Use `IntoPattern` for `RouterBuilder::path()` +- Use `IntoPattern` for `RouterBuilder::path()` ## 0.2.1 - 2019-12-25 -* Add `IntoPattern` trait -* Add multi-pattern resources +- Add `IntoPattern` trait +- Add multi-pattern resources ## 0.2.0 - 2019-12-07 -* Update http to 0.2 -* Update regex to 1.3 -* Use bytestring instead of string +- Update http to 0.2 +- Update regex to 1.3 +- Use bytestring instead of string ## 0.1.5 - 2019-05-15 -* Remove debug prints +- Remove debug prints ## 0.1.4 - 2019-05-15 -* Fix checked resource match +- Fix checked resource match ## 0.1.3 - 2019-04-22 -* Added support for `remainder match` (i.e "/path/{tail}*") +- Added support for `remainder match` (i.e "/path/{tail}*") ## 0.1.2 - 2019-04-07 -* Export `Quoter` type -* Allow to reset `Path` instance +- Export `Quoter` type +- Allow to reset `Path` instance ## 0.1.1 - 2019-04-03 -* Get dynamic segment by name instead of iterator. +- Get dynamic segment by name instead of iterator. ## 0.1.0 - 2019-03-09 -* Initial release +- Initial release diff --git a/actix-test/CHANGES.md b/actix-test/CHANGES.md index ef78ac54a..e3deeb3f4 100644 --- a/actix-test/CHANGES.md +++ b/actix-test/CHANGES.md @@ -4,46 +4,46 @@ ## 0.1.0-beta.9 - 2021-12-17 -* Re-export `actix_http::body::to_bytes`. [#2518] -* Update `actix_web::test` re-exports. [#2518] +- Re-export `actix_http::body::to_bytes`. [#2518] +- Update `actix_web::test` re-exports. [#2518] [#2518]: https://github.com/actix/actix-web/pull/2518 ## 0.1.0-beta.8 - 2021-12-11 -* No significant changes since `0.1.0-beta.7`. +- No significant changes since `0.1.0-beta.7`. ## 0.1.0-beta.7 - 2021-11-22 -* Fix compatibility with experimental `io-uring` feature of `actix-rt`. [#2408] +- Fix compatibility with experimental `io-uring` feature of `actix-rt`. [#2408] [#2408]: https://github.com/actix/actix-web/pull/2408 ## 0.1.0-beta.6 - 2021-11-15 -* No significant changes from `0.1.0-beta.5`. +- No significant changes from `0.1.0-beta.5`. ## 0.1.0-beta.5 - 2021-10-20 -* Updated rustls to v0.20. [#2414] -* Minimum supported Rust version (MSRV) is now 1.52. +- Updated rustls to v0.20. [#2414] +- Minimum supported Rust version (MSRV) is now 1.52. [#2414]: https://github.com/actix/actix-web/pull/2414 ## 0.1.0-beta.4 - 2021-09-09 -* Minimum supported Rust version (MSRV) is now 1.51. +- Minimum supported Rust version (MSRV) is now 1.51. ## 0.1.0-beta.3 - 2021-06-20 -* No significant changes from `0.1.0-beta.2`. +- No significant changes from `0.1.0-beta.2`. ## 0.1.0-beta.2 - 2021-04-17 -* No significant changes from `0.1.0-beta.1`. +- No significant changes from `0.1.0-beta.1`. ## 0.1.0-beta.1 - 2021-04-02 -* Move integration testing structs from `actix-web`. [#2112] +- Move integration testing structs from `actix-web`. [#2112] [#2112]: https://github.com/actix/actix-web/pull/2112 diff --git a/actix-web-actors/CHANGES.md b/actix-web-actors/CHANGES.md index d3078499c..6abfe2c61 100644 --- a/actix-web-actors/CHANGES.md +++ b/actix-web-actors/CHANGES.md @@ -4,105 +4,105 @@ ## 4.0.0-beta.8 - 2021-12-11 -* Add `ws:WsResponseBuilder` for building WebSocket session response. [#1920] -* Deprecate `ws::{start_with_addr, start_with_protocols}`. [#1920] -* Minimum supported Rust version (MSRV) is now 1.52. +- Add `ws:WsResponseBuilder` for building WebSocket session response. [#1920] +- Deprecate `ws::{start_with_addr, start_with_protocols}`. [#1920] +- Minimum supported Rust version (MSRV) is now 1.52. [#1920]: https://github.com/actix/actix-web/pull/1920 ## 4.0.0-beta.7 - 2021-09-09 -* Minimum supported Rust version (MSRV) is now 1.51. +- Minimum supported Rust version (MSRV) is now 1.51. ## 4.0.0-beta.6 - 2021-06-26 -* Update `actix` to `0.12`. [#2277] +- Update `actix` to `0.12`. [#2277] [#2277]: https://github.com/actix/actix-web/pull/2277 ## 4.0.0-beta.5 - 2021-06-17 -* No notable changes. +- No notable changes. ## 4.0.0-beta.4 - 2021-04-02 -* No notable changes. +- No notable changes. ## 4.0.0-beta.3 - 2021-03-09 -* No notable changes. +- No notable changes. ## 4.0.0-beta.2 - 2021-02-10 -* No notable changes. +- No notable changes. ## 4.0.0-beta.1 - 2021-01-07 -* Update `pin-project` to `1.0`. -* Update `bytes` to `1.0`. [#1813] -* `WebsocketContext::text` now takes an `Into`. [#1864] +- Update `pin-project` to `1.0`. +- Update `bytes` to `1.0`. [#1813] +- `WebsocketContext::text` now takes an `Into`. [#1864] [#1813]: https://github.com/actix/actix-web/pull/1813 [#1864]: https://github.com/actix/actix-web/pull/1864 ## 3.0.0 - 2020-09-11 -* No significant changes from `3.0.0-beta.2`. +- No significant changes from `3.0.0-beta.2`. ## 3.0.0-beta.2 - 2020-09-10 -* Update `actix-*` dependencies to latest versions. +- Update `actix-*` dependencies to latest versions. ## [3.0.0-beta.1] - 2020-xx-xx -* Update `actix-web` & `actix-http` dependencies to beta.1 -* Bump minimum supported Rust version to 1.40 +- Update `actix-web` & `actix-http` dependencies to beta.1 +- Bump minimum supported Rust version to 1.40 ## [3.0.0-alpha.1] - 2020-05-08 -* Update the actix-web dependency to 3.0.0-alpha.1 -* Update the actix dependency to 0.10.0-alpha.2 -* Update the actix-http dependency to 2.0.0-alpha.3 +- Update the actix-web dependency to 3.0.0-alpha.1 +- Update the actix dependency to 0.10.0-alpha.2 +- Update the actix-http dependency to 2.0.0-alpha.3 ## [2.0.0] - 2019-12-20 -* Release +- Release ## [2.0.0-alpha.1] - 2019-12-15 -* Migrate to actix-web 2.0.0 +- Migrate to actix-web 2.0.0 ## [1.0.4] - 2019-12-07 -* Allow comma-separated websocket subprotocols without spaces (#1172) +- Allow comma-separated websocket subprotocols without spaces (#1172) ## [1.0.3] - 2019-11-14 -* Update actix-web and actix-http dependencies +- Update actix-web and actix-http dependencies ## [1.0.2] - 2019-07-20 -* Add `ws::start_with_addr()`, returning the address of the created actor, along +- Add `ws::start_with_addr()`, returning the address of the created actor, along with the `HttpResponse`. -* Add support for specifying protocols on websocket handshake #835 +- Add support for specifying protocols on websocket handshake #835 ## [1.0.1] - 2019-06-28 -* Allow to use custom ws codec with `WebsocketContext` #925 +- Allow to use custom ws codec with `WebsocketContext` #925 ## [1.0.0] - 2019-05-29 -* Update actix-http and actix-web +- Update actix-http and actix-web ## [0.1.0-alpha.3] - 2019-04-02 -* Update actix-http and actix-web +- Update actix-http and actix-web ## [0.1.0-alpha.2] - 2019-03-29 -* Update actix-http and actix-web +- Update actix-http and actix-web ## [0.1.0-alpha.1] - 2019-03-28 -* Initial impl +- Initial impl diff --git a/actix-web-codegen/CHANGES.md b/actix-web-codegen/CHANGES.md index 309274563..0d881d303 100644 --- a/actix-web-codegen/CHANGES.md +++ b/actix-web-codegen/CHANGES.md @@ -4,101 +4,101 @@ ## 0.5.0-beta.6 - 2021-12-11 -* No significant changes since `0.5.0-beta.5`. +- No significant changes since `0.5.0-beta.5`. ## 0.5.0-beta.5 - 2021-10-20 -* Improve error recovery potential when macro input is invalid. [#2410] -* Add `#[actix_web::test]` macro for setting up tests with a runtime. [#2409] -* Minimum supported Rust version (MSRV) is now 1.52. +- Improve error recovery potential when macro input is invalid. [#2410] +- Add `#[actix_web::test]` macro for setting up tests with a runtime. [#2409] +- Minimum supported Rust version (MSRV) is now 1.52. [#2410]: https://github.com/actix/actix-web/pull/2410 [#2409]: https://github.com/actix/actix-web/pull/2409 ## 0.5.0-beta.4 - 2021-09-09 -* In routing macros, paths are now validated at compile time. [#2350] -* Minimum supported Rust version (MSRV) is now 1.51. +- In routing macros, paths are now validated at compile time. [#2350] +- Minimum supported Rust version (MSRV) is now 1.51. [#2350]: https://github.com/actix/actix-web/pull/2350 ## 0.5.0-beta.3 - 2021-06-17 -* No notable changes. +- No notable changes. ## 0.5.0-beta.2 - 2021-03-09 -* Preserve doc comments when using route macros. [#2022] -* Add `name` attribute to `route` macro. [#1934] +- Preserve doc comments when using route macros. [#2022] +- Add `name` attribute to `route` macro. [#1934] [#2022]: https://github.com/actix/actix-web/pull/2022 [#1934]: https://github.com/actix/actix-web/pull/1934 ## 0.5.0-beta.1 - 2021-02-10 -* Use new call signature for `System::new`. +- Use new call signature for `System::new`. ## 0.4.0 - 2020-09-20 -* Added compile success and failure testing. [#1677] -* Add `route` macro for supporting multiple HTTP methods guards. [#1674] +- Added compile success and failure testing. [#1677] +- Add `route` macro for supporting multiple HTTP methods guards. [#1674] [#1677]: https://github.com/actix/actix-web/pull/1677 [#1674]: https://github.com/actix/actix-web/pull/1674 ## 0.3.0 - 2020-09-11 -* No significant changes from `0.3.0-beta.1`. +- No significant changes from `0.3.0-beta.1`. ## 0.3.0-beta.1 - 2020-07-14 -* Add main entry-point macro that uses re-exported runtime. [#1559] +- Add main entry-point macro that uses re-exported runtime. [#1559] [#1559]: https://github.com/actix/actix-web/pull/1559 ## 0.2.2 - 2020-05-23 -* Add resource middleware on actix-web-codegen [#1467] +- Add resource middleware on actix-web-codegen [#1467] [#1467]: https://github.com/actix/actix-web/pull/1467 ## 0.2.1 - 2020-02-25 -* Add `#[allow(missing_docs)]` attribute to generated structs [#1368] -* Allow the handler function to be named as `config` [#1290] +- Add `#[allow(missing_docs)]` attribute to generated structs [#1368] +- Allow the handler function to be named as `config` [#1290] [#1368]: https://github.com/actix/actix-web/issues/1368 [#1290]: https://github.com/actix/actix-web/issues/1290 ## 0.2.0 - 2019-12-13 -* Generate code for actix-web 2.0 +- Generate code for actix-web 2.0 ## 0.1.3 - 2019-10-14 -* Bump up `syn` & `quote` to 1.0 -* Provide better error message +- Bump up `syn` & `quote` to 1.0 +- Provide better error message ## 0.1.2 - 2019-06-04 -* Add macros for head, options, trace, connect and patch http methods +- Add macros for head, options, trace, connect and patch http methods ## 0.1.1 - 2019-06-01 -* Add syn "extra-traits" feature +- Add syn "extra-traits" feature ## 0.1.0 - 2019-05-18 -* Release +- Release ## 0.1.0-beta.1 - 2019-04-20 -* Gen code for actix-web 1.0.0-beta.1 +- Gen code for actix-web 1.0.0-beta.1 ## 0.1.0-alpha.6 - 2019-04-14 -* Gen code for actix-web 1.0.0-alpha.6 +- Gen code for actix-web 1.0.0-alpha.6 ## 0.1.0-alpha.1 - 2019-03-28 -* Initial impl +- Initial impl diff --git a/awc/CHANGES.md b/awc/CHANGES.md index 7b822930c..b5144b7a2 100644 --- a/awc/CHANGES.md +++ b/awc/CHANGES.md @@ -1,75 +1,75 @@ # Changes ## Unreleased - 2021-xx-xx -* Rename `Connector::{ssl => openssl}`. [#2503] -* Improve `Client` instantiation efficiency when using `openssl` by only building connectors once. [#2503] +- Rename `Connector::{ssl => openssl}`. [#2503] +- Improve `Client` instantiation efficiency when using `openssl` by only building connectors once. [#2503] [#2503]: https://github.com/actix/actix-web/pull/2503 ## 3.0.0-beta.14 - 2021-12-17 -* Add `ClientBuilder::add_default_header` and deprecate `ClientBuilder::header`. [#2510] +- Add `ClientBuilder::add_default_header` and deprecate `ClientBuilder::header`. [#2510] [#2510]: https://github.com/actix/actix-web/pull/2510 ## 3.0.0-beta.13 - 2021-12-11 -* No significant changes since `3.0.0-beta.12`. +- No significant changes since `3.0.0-beta.12`. ## 3.0.0-beta.12 - 2021-11-30 -* Update `actix-tls` to `3.0.0-rc.1`. [#2474] +- Update `actix-tls` to `3.0.0-rc.1`. [#2474] [#2474]: https://github.com/actix/actix-web/pull/2474 ## 3.0.0-beta.11 - 2021-11-22 -* No significant changes from `3.0.0-beta.10`. +- No significant changes from `3.0.0-beta.10`. ## 3.0.0-beta.10 - 2021-11-15 -* No significant changes from `3.0.0-beta.9`. +- No significant changes from `3.0.0-beta.9`. ## 3.0.0-beta.9 - 2021-10-20 -* Updated rustls to v0.20. [#2414] +- Updated rustls to v0.20. [#2414] [#2414]: https://github.com/actix/actix-web/pull/2414 ## 3.0.0-beta.8 - 2021-09-09 ### Changed -* Send headers within the redirect requests. [#2310] +- Send headers within the redirect requests. [#2310] [#2310]: https://github.com/actix/actix-web/pull/2310 ## 3.0.0-beta.7 - 2021-06-26 ### Changed -* Change compression algorithm features flags. [#2250] +- Change compression algorithm features flags. [#2250] [#2250]: https://github.com/actix/actix-web/pull/2250 ## 3.0.0-beta.6 - 2021-06-17 -* No significant changes since 3.0.0-beta.5. +- No significant changes since 3.0.0-beta.5. ## 3.0.0-beta.5 - 2021-04-17 ### Removed -* Deprecated methods on `ClientRequest`: `if_true`, `if_some`. [#2148] +- Deprecated methods on `ClientRequest`: `if_true`, `if_some`. [#2148] [#2148]: https://github.com/actix/actix-web/pull/2148 ## 3.0.0-beta.4 - 2021-04-02 ### Added -* Add `Client::headers` to get default mut reference of `HeaderMap` of client object. [#2114] +- Add `Client::headers` to get default mut reference of `HeaderMap` of client object. [#2114] ### Changed -* `ConnectorService` type is renamed to `BoxConnectorService`. [#2081] -* Fix http/https encoding when enabling `compress` feature. [#2116] -* Rename `TestResponse::header` to `append_header`, `set` to `insert_header`. `TestResponse` header +- `ConnectorService` type is renamed to `BoxConnectorService`. [#2081] +- Fix http/https encoding when enabling `compress` feature. [#2116] +- Rename `TestResponse::header` to `append_header`, `set` to `insert_header`. `TestResponse` header methods now take `TryIntoHeaderPair` tuples. [#2094] [#2081]: https://github.com/actix/actix-web/pull/2081 @@ -80,16 +80,16 @@ ## 3.0.0-beta.3 - 2021-03-08 ### Added -* `ClientResponse::timeout` for set the timeout of collecting response body. [#1931] -* `ClientBuilder::local_address` for bind to a local ip address for this client. [#2024] +- `ClientResponse::timeout` for set the timeout of collecting response body. [#1931] +- `ClientBuilder::local_address` for bind to a local ip address for this client. [#2024] ### Changed -* Feature `cookies` is now optional and enabled by default. [#1981] -* `ClientBuilder::connector` method would take `actix_http::client::Connector` type. [#2008] -* Basic auth password now takes blank passwords as an empty string instead of Option. [#2050] +- Feature `cookies` is now optional and enabled by default. [#1981] +- `ClientBuilder::connector` method would take `actix_http::client::Connector` type. [#2008] +- Basic auth password now takes blank passwords as an empty string instead of Option. [#2050] ### Removed -* `ClientBuilder::default` function [#2008] +- `ClientBuilder::default` function [#2008] [#1931]: https://github.com/actix/actix-web/pull/1931 [#1981]: https://github.com/actix/actix-web/pull/1981 @@ -100,18 +100,18 @@ ## 3.0.0-beta.2 - 2021-02-10 ### Added -* `ClientRequest::insert_header` method which allows using typed headers. [#1869] -* `ClientRequest::append_header` method which allows using typed headers. [#1869] -* `trust-dns` optional feature to enable `trust-dns-resolver` as client dns resolver. [#1969] +- `ClientRequest::insert_header` method which allows using typed headers. [#1869] +- `ClientRequest::append_header` method which allows using typed headers. [#1869] +- `trust-dns` optional feature to enable `trust-dns-resolver` as client dns resolver. [#1969] ### Changed -* Relax default timeout for `Connector` to 5 seconds(original 1 second). [#1905] +- Relax default timeout for `Connector` to 5 seconds(original 1 second). [#1905] ### Removed -* `ClientRequest::set`; use `ClientRequest::insert_header`. [#1869] -* `ClientRequest::set_header`; use `ClientRequest::insert_header`. [#1869] -* `ClientRequest::set_header_if_none`; use `ClientRequest::insert_header_if_none`. [#1869] -* `ClientRequest::header`; use `ClientRequest::append_header`. [#1869] +- `ClientRequest::set`; use `ClientRequest::insert_header`. [#1869] +- `ClientRequest::set_header`; use `ClientRequest::insert_header`. [#1869] +- `ClientRequest::set_header_if_none`; use `ClientRequest::insert_header_if_none`. [#1869] +- `ClientRequest::header`; use `ClientRequest::append_header`. [#1869] [#1869]: https://github.com/actix/actix-web/pull/1869 [#1905]: https://github.com/actix/actix-web/pull/1905 @@ -120,32 +120,32 @@ ## 3.0.0-beta.1 - 2021-01-07 ### Changed -* Update `rand` to `0.8` -* Update `bytes` to `1.0`. [#1813] -* Update `rust-tls` to `0.19`. [#1813] +- Update `rand` to `0.8` +- Update `bytes` to `1.0`. [#1813] +- Update `rust-tls` to `0.19`. [#1813] [#1813]: https://github.com/actix/actix-web/pull/1813 ## 2.0.3 - 2020-11-29 ### Fixed -* Ensure `actix-http` dependency uses same `serde_urlencoded`. +- Ensure `actix-http` dependency uses same `serde_urlencoded`. ## 2.0.2 - 2020-11-25 ### Changed -* Upgrade `serde_urlencoded` to `0.7`. [#1773] +- Upgrade `serde_urlencoded` to `0.7`. [#1773] [#1773]: https://github.com/actix/actix-web/pull/1773 ## 2.0.1 - 2020-10-30 ### Changed -* Upgrade `base64` to `0.13`. [#1744] -* Deprecate `ClientRequest::{if_some, if_true}`. [#1760] +- Upgrade `base64` to `0.13`. [#1744] +- Deprecate `ClientRequest::{if_some, if_true}`. [#1760] ### Fixed -* Use `Accept-Encoding: identity` instead of `Accept-Encoding: br` when no compression feature +- Use `Accept-Encoding: identity` instead of `Accept-Encoding: br` when no compression feature is enabled [#1737] [#1737]: https://github.com/actix/actix-web/pull/1737 @@ -155,209 +155,209 @@ ## 2.0.0 - 2020-09-11 ### Changed -* `Client::build` was renamed to `Client::builder`. +- `Client::build` was renamed to `Client::builder`. ## 2.0.0-beta.4 - 2020-09-09 ### Changed -* Update actix-codec & actix-tls dependencies. +- Update actix-codec & actix-tls dependencies. ## 2.0.0-beta.3 - 2020-08-17 ### Changed -* Update `rustls` to 0.18 +- Update `rustls` to 0.18 ## 2.0.0-beta.2 - 2020-07-21 ### Changed -* Update `actix-http` dependency to 2.0.0-beta.2 +- Update `actix-http` dependency to 2.0.0-beta.2 ## [2.0.0-beta.1] - 2020-07-14 ### Changed -* Update `actix-http` dependency to 2.0.0-beta.1 +- Update `actix-http` dependency to 2.0.0-beta.1 ## [2.0.0-alpha.2] - 2020-05-21 ### Changed -* Implement `std::error::Error` for our custom errors [#1422] -* Bump minimum supported Rust version to 1.40 -* Update `base64` dependency to 0.12 +- Implement `std::error::Error` for our custom errors [#1422] +- Bump minimum supported Rust version to 1.40 +- Update `base64` dependency to 0.12 [#1422]: https://github.com/actix/actix-web/pull/1422 ## [2.0.0-alpha.1] - 2020-03-11 -* Update `actix-http` dependency to 2.0.0-alpha.2 -* Update `rustls` dependency to 0.17 -* ClientBuilder accepts initial_window_size and initial_connection_window_size HTTP2 configuration -* ClientBuilder allowing to set max_http_version to limit HTTP version to be used +- Update `actix-http` dependency to 2.0.0-alpha.2 +- Update `rustls` dependency to 0.17 +- ClientBuilder accepts initial_window_size and initial_connection_window_size HTTP2 configuration +- ClientBuilder allowing to set max_http_version to limit HTTP version to be used ## [1.0.1] - 2019-12-15 -* Fix compilation with default features off +- Fix compilation with default features off ## [1.0.0] - 2019-12-13 -* Release +- Release ## [1.0.0-alpha.3] -* Migrate to `std::future` +- Migrate to `std::future` ## [0.2.8] - 2019-11-06 -* Add support for setting query from Serialize type for client request. +- Add support for setting query from Serialize type for client request. ## [0.2.7] - 2019-09-25 ### Added -* Remaining getter methods for `ClientRequest`'s private `head` field #1101 +- Remaining getter methods for `ClientRequest`'s private `head` field #1101 ## [0.2.6] - 2019-09-12 ### Added -* Export frozen request related types. +- Export frozen request related types. ## [0.2.5] - 2019-09-11 ### Added -* Add `FrozenClientRequest` to support retries for sending HTTP requests +- Add `FrozenClientRequest` to support retries for sending HTTP requests ### Changed -* Ensure that the `Host` header is set when initiating a WebSocket client connection. +- Ensure that the `Host` header is set when initiating a WebSocket client connection. ## [0.2.4] - 2019-08-13 ### Changed -* Update percent-encoding to "2.1" +- Update percent-encoding to "2.1" -* Update serde_urlencoded to "0.6.1" +- Update serde_urlencoded to "0.6.1" ## [0.2.3] - 2019-08-01 ### Added -* Add `rustls` support +- Add `rustls` support ## [0.2.2] - 2019-07-01 ### Changed -* Always append a colon after username in basic auth +- Always append a colon after username in basic auth -* Upgrade `rand` dependency version to 0.7 +- Upgrade `rand` dependency version to 0.7 ## [0.2.1] - 2019-06-05 ### Added -* Add license files +- Add license files ## [0.2.0] - 2019-05-12 ### Added -* Allow to send headers in `Camel-Case` form. +- Allow to send headers in `Camel-Case` form. ### Changed -* Upgrade actix-http dependency. +- Upgrade actix-http dependency. ## [0.1.1] - 2019-04-19 ### Added -* Allow to specify server address for http and ws requests. +- Allow to specify server address for http and ws requests. ### Changed -* `ClientRequest::if_true()` and `ClientRequest::if_some()` use instance instead of ref +- `ClientRequest::if_true()` and `ClientRequest::if_some()` use instance instead of ref ## [0.1.0] - 2019-04-16 -* No changes +- No changes ## [0.1.0-alpha.6] - 2019-04-14 ### Changed -* Do not set default headers for websocket request +- Do not set default headers for websocket request ## [0.1.0-alpha.5] - 2019-04-12 ### Changed -* Do not set any default headers +- Do not set any default headers ### Added -* Add Debug impl for BoxedSocket +- Add Debug impl for BoxedSocket ## [0.1.0-alpha.4] - 2019-04-08 ### Changed -* Update actix-http dependency +- Update actix-http dependency ## [0.1.0-alpha.3] - 2019-04-02 ### Added -* Export `MessageBody` type +- Export `MessageBody` type -* `ClientResponse::json()` - Loads and parse `application/json` encoded body +- `ClientResponse::json()` - Loads and parse `application/json` encoded body ### Changed -* `ClientRequest::json()` accepts reference instead of object. +- `ClientRequest::json()` accepts reference instead of object. -* `ClientResponse::body()` does not consume response object. +- `ClientResponse::body()` does not consume response object. -* Renamed `ClientRequest::close_connection()` to `ClientRequest::force_close()` +- Renamed `ClientRequest::close_connection()` to `ClientRequest::force_close()` ## [0.1.0-alpha.2] - 2019-03-29 ### Added -* Per request and session wide request timeout. +- Per request and session wide request timeout. -* Session wide headers. +- Session wide headers. -* Session wide basic and bearer auth. +- Session wide basic and bearer auth. -* Re-export `actix_http::client::Connector`. +- Re-export `actix_http::client::Connector`. ### Changed -* Allow to override request's uri +- Allow to override request's uri -* Export `ws` sub-module with websockets related types +- Export `ws` sub-module with websockets related types ## [0.1.0-alpha.1] - 2019-03-28 -* Initial impl +- Initial impl From 212c6926f97f439c2fbbe41f6bd10211653540e2 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Wed, 22 Dec 2021 08:18:44 +0000 Subject: [PATCH 368/428] Revert "use dash hyphenation in changelogs" This reverts commit 1ea619f2a1722206cddf4af0a43715fc8202a06e. --- CHANGES.md | 551 +++++++++++++++++------------------ actix-files/CHANGES.md | 102 +++---- actix-http-test/CHANGES.md | 76 ++--- actix-http/CHANGES.md | 544 +++++++++++++++++----------------- actix-multipart/CHANGES.md | 74 ++--- actix-router/CHANGES.md | 102 +++---- actix-test/CHANGES.md | 22 +- actix-web-actors/CHANGES.md | 60 ++-- actix-web-codegen/CHANGES.md | 52 ++-- awc/CHANGES.md | 170 +++++------ 10 files changed, 876 insertions(+), 877 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 0458958c5..77ab2e218 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -2,29 +2,29 @@ ## Unreleased - 2021-xx-xx -- + ## 4.0.0-beta.15 - 2021-12-17 ### Added * Method on `Responder` trait (`customize`) for customizing responders and `CustomizeResponder` struct. [#2510] * Implement `Debug` for `DefaultHeaders`. [#2510] ### Changed -- Align `DefaultHeader` method terminology, deprecating previous methods. [#2510] -- Response service types in `ErrorHandlers` middleware now use `ServiceResponse>` to allow changing the body type. [#2515] +* Align `DefaultHeader` method terminology, deprecating previous methods. [#2510] +* Response service types in `ErrorHandlers` middleware now use `ServiceResponse>` to allow changing the body type. [#2515] * Both variants in `ErrorHandlerResponse` now use `ServiceResponse>`. [#2515] * Rename `test::{default_service => simple_service}`. Old name is deprecated. [#2518] -- Rename `test::{read_response_json => call_and_read_body_json}`. Old name is deprecated. [#2518] -- Rename `test::{read_response => call_and_read_body}`. Old name is deprecated. [#2518] -- Relax body type and error bounds on test utilities. [#2518] -- -- # Removed -- Top-level `EitherExtractError` export. [#2510] -- Conversion implementations for `either` crate. [#2516] +* Rename `test::{read_response_json => call_and_read_body_json}`. Old name is deprecated. [#2518] +* Rename `test::{read_response => call_and_read_body}`. Old name is deprecated. [#2518] +* Relax body type and error bounds on test utilities. [#2518] + +### Removed +* Top-level `EitherExtractError` export. [#2510] +* Conversion implementations for `either` crate. [#2516] * `test::load_stream` and `test::load_body`; replace usage with `body::to_bytes`. [#2518] -- 2510]: https://github.com/actix/actix-web/pull/2510 -- 2515]: https://github.com/actix/actix-web/pull/2515 -- 2516]: https://github.com/actix/actix-web/pull/2516 +[#2510]: https://github.com/actix/actix-web/pull/2510 +[#2515]: https://github.com/actix/actix-web/pull/2515 +[#2516]: https://github.com/actix/actix-web/pull/2516 [#2518]: https://github.com/actix/actix-web/pull/2518 @@ -34,31 +34,31 @@ * `AcceptEncoding` typed header. [#2482] * `Range` typed header. [#2485] * `HttpResponse::map_into_{left,right}_body` and `HttpResponse::map_into_boxed_body`. [#2468] -- `ServiceResponse::map_into_{left,right}_body` and `HttpResponse::map_into_boxed_body`. [#2468] -- Connection data set through the `HttpServer::on_connect` callback is now accessible only from the new `HttpRequest::conn_data()` and `ServiceRequest::conn_data()` methods. [#2491] -- `HttpRequest::{req_data,req_data_mut}`. [#2487] -- `ServiceResponse::into_parts`. [#2499] -- -- # Changed -- Rename `Accept::{mime_precedence => ranked}`. [#2480] -- Rename `Accept::{mime_preference => preference}`. [#2480] +* `ServiceResponse::map_into_{left,right}_body` and `HttpResponse::map_into_boxed_body`. [#2468] +* Connection data set through the `HttpServer::on_connect` callback is now accessible only from the new `HttpRequest::conn_data()` and `ServiceRequest::conn_data()` methods. [#2491] +* `HttpRequest::{req_data,req_data_mut}`. [#2487] +* `ServiceResponse::into_parts`. [#2499] + +### Changed +* Rename `Accept::{mime_precedence => ranked}`. [#2480] +* Rename `Accept::{mime_preference => preference}`. [#2480] * Un-deprecate `App::data_factory`. [#2484] * `HttpRequest::url_for` no longer constructs URLs with query or fragment components. [#2430] -- Remove `B` (body) type parameter on `App`. [#2493] -- Add `B` (body) type parameter on `Scope`. [#2492] -- Request-local data container is no longer part of a `RequestHead`. Instead it is a distinct part of a `Request`. [#2487] -- -- # Fixed -- Accept wildcard `*` items in `AcceptLanguage`. [#2480] -- Re-exports `dev::{BodySize, MessageBody, SizedStream}`. They are exposed through the `body` module. [#2468] +* Remove `B` (body) type parameter on `App`. [#2493] +* Add `B` (body) type parameter on `Scope`. [#2492] +* Request-local data container is no longer part of a `RequestHead`. Instead it is a distinct part of a `Request`. [#2487] + +### Fixed +* Accept wildcard `*` items in `AcceptLanguage`. [#2480] +* Re-exports `dev::{BodySize, MessageBody, SizedStream}`. They are exposed through the `body` module. [#2468] * Typed headers containing lists that require one or more items now enforce this minimum. [#2482] -- # Removed -- `ConnectionInfo::get`. [#2487] -- +### Removed +* `ConnectionInfo::get`. [#2487] + [#2430]: https://github.com/actix/actix-web/pull/2430 [#2468]: https://github.com/actix/actix-web/pull/2468 -- 2480]: https://github.com/actix/actix-web/pull/2480 +[#2480]: https://github.com/actix/actix-web/pull/2480 [#2482]: https://github.com/actix/actix-web/pull/2482 [#2484]: https://github.com/actix/actix-web/pull/2484 [#2485]: https://github.com/actix/actix-web/pull/2485 @@ -75,20 +75,20 @@ [#2474]: https://github.com/actix/actix-web/pull/2474 -- + ## 4.0.0-beta.12 - 2021-11-22 ### Changed * Compress middleware's response type is now `AnyBody>`. [#2448] ### Fixed * Relax `Unpin` bound on `S` (stream) parameter of `HttpResponseBuilder::streaming`. [#2448] -- + ### Removed * `dev::ResponseBody` re-export; is function is replaced by the new `dev::AnyBody` enum. [#2446] -- + [#2446]: https://github.com/actix/actix-web/pull/2446 [#2448]: https://github.com/actix/actix-web/pull/2448 -- + ## 4.0.0-beta.11 - 2021-11-15 ### Added @@ -96,11 +96,11 @@ ### Changed * `ContentType::html` now produces `text/html; charset=utf-8` instead of `text/html`. [#2423] -- Update `actix-server` to `2.0.0-beta.9`. [#2442] +* Update `actix-server` to `2.0.0-beta.9`. [#2442] [#2423]: https://github.com/actix/actix-web/pull/2423 -- 2442]: https://github.com/actix/actix-web/pull/2442 -- +[#2442]: https://github.com/actix/actix-web/pull/2442 + ## 4.0.0-beta.10 - 2021-10-20 ### Added @@ -108,18 +108,18 @@ * `#[actix_web::test]` macro for setting up tests with a runtime. [#2409] ### Changed -- Associated type `FromRequest::Config` was removed. [#2233] -- Inner field made private on `web::Payload`. [#2384] +* Associated type `FromRequest::Config` was removed. [#2233] +* Inner field made private on `web::Payload`. [#2384] * `Data::into_inner` and `Data::get_ref` no longer requires `T: Sized`. [#2403] * Updated rustls to v0.20. [#2414] -- Minimum supported Rust version (MSRV) is now 1.52. -- -- # Removed -- Useless `ServiceResponse::checked_expr` method. [#2401] -- +* Minimum supported Rust version (MSRV) is now 1.52. + +### Removed +* Useless `ServiceResponse::checked_expr` method. [#2401] + [#2233]: https://github.com/actix/actix-web/pull/2233 [#2362]: https://github.com/actix/actix-web/pull/2362 -- 2384]: https://github.com/actix/actix-web/pull/2384 +[#2384]: https://github.com/actix/actix-web/pull/2384 [#2401]: https://github.com/actix/actix-web/pull/2401 [#2403]: https://github.com/actix/actix-web/pull/2403 [#2409]: https://github.com/actix/actix-web/pull/2409 @@ -132,17 +132,17 @@ ### Changed * Compress middleware will return 406 Not Acceptable when no content encoding is acceptable to the client. [#2344] -- Move `BaseHttpResponse` to `dev::Response`. [#2379] +* Move `BaseHttpResponse` to `dev::Response`. [#2379] * Enable `TestRequest::param` to accept more than just static strings. [#2172] * Minimum supported Rust version (MSRV) is now 1.51. -- -- # Fixed -- Fix quality parse error in Accept-Encoding header. [#2344] -- Re-export correct type at `web::HttpResponse`. [#2379] + +### Fixed +* Fix quality parse error in Accept-Encoding header. [#2344] +* Re-export correct type at `web::HttpResponse`. [#2379] [#2172]: https://github.com/actix/actix-web/pull/2172 -- 2325]: https://github.com/actix/actix-web/pull/2325 -- 2344]: https://github.com/actix/actix-web/pull/2344 +[#2325]: https://github.com/actix/actix-web/pull/2325 +[#2344]: https://github.com/actix/actix-web/pull/2344 [#2379]: https://github.com/actix/actix-web/pull/2379 @@ -152,18 +152,18 @@ * Add extractors for `Uri` and `Method`. [#2263] * Add extractors for `ConnectionInfo` and `PeerAddr`. [#2263] * Add `Route::service` for using hand-written services as handlers. [#2262] -- -- # Changed -- Change compression algorithm features flags. [#2250] -- Deprecate `App::data` and `App::data_factory`. [#2271] + +### Changed +* Change compression algorithm features flags. [#2250] +* Deprecate `App::data` and `App::data_factory`. [#2271] * Smarter extraction of `ConnectionInfo` parts. [#2282] -- # Fixed -- Scope and Resource middleware can access data items set on their own layer. [#2288] -- +### Fixed +* Scope and Resource middleware can access data items set on their own layer. [#2288] + [#2177]: https://github.com/actix/actix-web/pull/2177 [#2250]: https://github.com/actix/actix-web/pull/2250 -- 2271]: https://github.com/actix/actix-web/pull/2271 +[#2271]: https://github.com/actix/actix-web/pull/2271 [#2262]: https://github.com/actix/actix-web/pull/2262 [#2263]: https://github.com/actix/actix-web/pull/2263 [#2282]: https://github.com/actix/actix-web/pull/2282 @@ -176,23 +176,23 @@ ### Changed * Adjusted default JSON payload limit to 2MB (from 32kb) and included size and limits in the `JsonPayloadError::Overflow` error variant. [#2162] -- 2162]: (https://github.com/actix/actix-web/pull/2162) +[#2162]: (https://github.com/actix/actix-web/pull/2162) * `ServiceResponse::error_response` now uses body type of `Body`. [#2201] * `ServiceResponse::checked_expr` now returns a `Result`. [#2201] -- Update `language-tags` to `0.3`. +* Update `language-tags` to `0.3`. * `ServiceResponse::take_body`. [#2201] -- `ServiceResponse::map_body` closure receives and returns `B` instead of `ResponseBody` types. [#2201] -- All error trait bounds in server service builders have changed from `Into` to `Into>`. [#2253] -- All error trait bounds in message body and stream impls changed from `Into` to `Into>`. [#2253] -- `HttpServer::{listen_rustls(), bind_rustls()}` now honor the ALPN protocols in the configuation parameter. [#2226] -- `middleware::normalize` now will not try to normalize URIs with no valid path [#2246] -- -- # Removed -- `HttpResponse::take_body` and old `HttpResponse::into_body` method that casted body type. [#2201] -- +* `ServiceResponse::map_body` closure receives and returns `B` instead of `ResponseBody` types. [#2201] +* All error trait bounds in server service builders have changed from `Into` to `Into>`. [#2253] +* All error trait bounds in message body and stream impls changed from `Into` to `Into>`. [#2253] +* `HttpServer::{listen_rustls(), bind_rustls()}` now honor the ALPN protocols in the configuation parameter. [#2226] +* `middleware::normalize` now will not try to normalize URIs with no valid path [#2246] + +### Removed +* `HttpResponse::take_body` and old `HttpResponse::into_body` method that casted body type. [#2201] + [#2200]: https://github.com/actix/actix-web/pull/2200 [#2201]: https://github.com/actix/actix-web/pull/2201 -- 2253]: https://github.com/actix/actix-web/pull/2253 +[#2253]: https://github.com/actix/actix-web/pull/2253 [#2246]: https://github.com/actix/actix-web/pull/2246 @@ -202,11 +202,11 @@ ### Changed * Most error types are now marked `#[non_exhaustive]`. [#2148] -- Methods on `ContentDisposition` that took `T: AsRef` now take `impl AsRef`. +* Methods on `ContentDisposition` that took `T: AsRef` now take `impl AsRef`. [#2065]: https://github.com/actix/actix-web/pull/2065 -- 2148]: https://github.com/actix/actix-web/pull/2148 -- +[#2148]: https://github.com/actix/actix-web/pull/2148 + ## 4.0.0-beta.5 - 2021-04-02 ### Added @@ -214,20 +214,20 @@ * Added `TestServer::client_headers` method. [#2097] ### Fixed -- Double ampersand in Logger format is escaped correctly. [#2067] -- +* Double ampersand in Logger format is escaped correctly. [#2067] + ### Changed * `CustomResponder` would return error as `HttpResponse` when `CustomResponder::with_header` failed -- instead of skipping. (Only the first error is kept when multiple error occur) [#2093] + instead of skipping. (Only the first error is kept when multiple error occur) [#2093] ### Removed -- The `client` mod was removed. Clients should now use `awc` directly. +* The `client` mod was removed. Clients should now use `awc` directly. [871ca5e4](https://github.com/actix/actix-web/commit/871ca5e4ae2bdc22d1ea02701c2992fa8d04aed7) * Integration testing was moved to new `actix-test` crate. Namely these items from the `test` module: `TestServer`, `TestServerConfig`, `start`, `start_with`, and `unused_addr`. [#2112] -- + [#2067]: https://github.com/actix/actix-web/pull/2067 -- 2093]: https://github.com/actix/actix-web/pull/2093 +[#2093]: https://github.com/actix/actix-web/pull/2093 [#2094]: https://github.com/actix/actix-web/pull/2094 [#2097]: https://github.com/actix/actix-web/pull/2097 [#2112]: https://github.com/actix/actix-web/pull/2112 @@ -239,8 +239,8 @@ * `JsonBody::new` returns a default limit of 32kB to be consistent with `JsonConfig` and the default behaviour of the `web::Json` extractor. [#2010] -- 1981]: https://github.com/actix/actix-web/pull/1981 -- 2010]: https://github.com/actix/actix-web/pull/2010 +[#1981]: https://github.com/actix/actix-web/pull/1981 +[#2010]: https://github.com/actix/actix-web/pull/2010 ## 4.0.0-beta.3 - 2021-02-10 @@ -248,36 +248,36 @@ ## 4.0.0-beta.2 - 2021-02-10 -- # Added +### Added * The method `Either, web::Form>::into_inner()` which returns the inner type for whichever variant was created. Also works for `Either, web::Json>`. [#1894] * Add `services!` macro for helping register multiple services to `App`. [#1933] * Enable registering a vec of services of the same type to `App` [#1933] -- + ### Changed -- Rework `Responder` trait to be sync and returns `Response`/`HttpResponse` directly. -- Making it simpler and more performant. [#1891] +* Rework `Responder` trait to be sync and returns `Response`/`HttpResponse` directly. + Making it simpler and more performant. [#1891] * `ServiceRequest::into_parts` and `ServiceRequest::from_parts` can no longer fail. [#1893] * `ServiceRequest::from_request` can no longer fail. [#1893] -- Our `Either` type now uses `Left`/`Right` variants (instead of `A`/`B`) [#1894] +* Our `Either` type now uses `Left`/`Right` variants (instead of `A`/`B`) [#1894] * `test::{call_service, read_response, read_response_json, send_request}` take `&Service` -- in argument [#1905] -- `App::wrap_fn`, `Resource::wrap_fn` and `Scope::wrap_fn` provide `&Service` in closure -- argument. [#1905] -- `web::block` no longer requires the output is a Result. [#1957] + in argument [#1905] +* `App::wrap_fn`, `Resource::wrap_fn` and `Scope::wrap_fn` provide `&Service` in closure + argument. [#1905] +* `web::block` no longer requires the output is a Result. [#1957] -- # Fixed +### Fixed * Multiple calls to `App::data` with the same type now keeps the latest call's data. [#1906] -- + ### Removed * Public field of `web::Path` has been made private. [#1894] -- Public field of `web::Query` has been made private. [#1894] +* Public field of `web::Query` has been made private. [#1894] * `TestRequest::with_header`; use `TestRequest::default().insert_header()`. [#1869] * `AppService::set_service_data`; for custom HTTP service factories adding application data, use the -- layered data model by calling `ServiceRequest::add_data_container` when handling -- requests instead. [#1906] -- -- 1891]: https://github.com/actix/actix-web/pull/1891 + layered data model by calling `ServiceRequest::add_data_container` when handling + requests instead. [#1906] + +[#1891]: https://github.com/actix/actix-web/pull/1891 [#1893]: https://github.com/actix/actix-web/pull/1893 [#1894]: https://github.com/actix/actix-web/pull/1894 [#1869]: https://github.com/actix/actix-web/pull/1869 @@ -293,26 +293,26 @@ `Compress` to be used in `middleware::Condition` and `Resource`, `Scope` services. [#1865] ### Changed -- Update `actix-*` dependencies to tokio `1.0` based versions. [#1813] +* Update `actix-*` dependencies to tokio `1.0` based versions. [#1813] * Bumped `rand` to `0.8`. * Update `rust-tls` to `0.19`. [#1813] * Rename `Handler` to `HandlerService` and rename `Factory` to `Handler`. [#1852] -- The default `TrailingSlash` is now `Trim`, in line with existing documentation. See migration -- guide for implications. [#1875] -- Rename `DefaultHeaders::{content_type => add_content_type}`. [#1875] -- MSRV is now 1.46.0. -- +* The default `TrailingSlash` is now `Trim`, in line with existing documentation. See migration + guide for implications. [#1875] +* Rename `DefaultHeaders::{content_type => add_content_type}`. [#1875] +* MSRV is now 1.46.0. + ### Fixed -- Added the underlying parse error to `test::read_body_json`'s panic message. [#1812] -- +* Added the underlying parse error to `test::read_body_json`'s panic message. [#1812] + ### Removed * Public modules `middleware::{normalize, err_handlers}`. All necessary middleware structs are now -- exposed directly by the `middleware` module. + exposed directly by the `middleware` module. * Remove `actix-threadpool` as dependency. `actix_threadpool::BlockingError` error type can be imported from `actix_web::error` module. [#1878] -- + [#1812]: https://github.com/actix/actix-web/pull/1812 -- 1813]: https://github.com/actix/actix-web/pull/1813 +[#1813]: https://github.com/actix/actix-web/pull/1813 [#1852]: https://github.com/actix/actix-web/pull/1852 [#1865]: https://github.com/actix/actix-web/pull/1865 [#1875]: https://github.com/actix/actix-web/pull/1875 @@ -325,16 +325,16 @@ [#2529]: https://github.com/actix/actix-web/pull/2529 -- + ## 3.3.2 - 2020-12-01 ### Fixed * Removed an occasional `unwrap` on `None` panic in `NormalizePathNormalization`. [#1762] * Fix `match_pattern()` returning `None` for scope with empty path resource. [#1798] * Increase minimum `socket2` version. [#1803] -- 1762]: https://github.com/actix/actix-web/pull/1762 -- 1798]: https://github.com/actix/actix-web/pull/1798 -- 1803]: https://github.com/actix/actix-web/pull/1803 +[#1762]: https://github.com/actix/actix-web/pull/1762 +[#1798]: https://github.com/actix/actix-web/pull/1798 +[#1803]: https://github.com/actix/actix-web/pull/1803 ## 3.3.1 - 2020-11-29 @@ -342,15 +342,15 @@ ## 3.3.0 - 2020-11-25 -- # Added +### Added * Add `Either` extractor helper. [#1788] ### Changed * Upgrade `serde_urlencoded` to `0.7`. [#1773] -- + [#1773]: https://github.com/actix/actix-web/pull/1773 [#1788]: https://github.com/actix/actix-web/pull/1788 -- + ## 3.2.0 - 2020-10-30 ### Added @@ -358,17 +358,17 @@ * Add request-local data extractor `web::ReqData`. [#1748] * Add ability to register closure for request middleware logging. [#1749] * Add `app_data` to `ServiceConfig`. [#1757] -- Expose `on_connect` for access to the connection stream before request is handled. [#1754] -- -- # Changed -- Updated actix-web-codegen dependency for access to new `#[route(...)]` multi-method macro. -- Print non-configured `Data` type when attempting extraction. [#1743] +* Expose `on_connect` for access to the connection stream before request is handled. [#1754] + +### Changed +* Updated actix-web-codegen dependency for access to new `#[route(...)]` multi-method macro. +* Print non-configured `Data` type when attempting extraction. [#1743] * Re-export bytes::Buf{Mut} in web module. [#1750] * Upgrade `pin-project` to `1.0`. -- -- 1723]: https://github.com/actix/actix-web/pull/1723 -- 1743]: https://github.com/actix/actix-web/pull/1743 -- 1748]: https://github.com/actix/actix-web/pull/1748 + +[#1723]: https://github.com/actix/actix-web/pull/1723 +[#1743]: https://github.com/actix/actix-web/pull/1743 +[#1748]: https://github.com/actix/actix-web/pull/1748 [#1750]: https://github.com/actix/actix-web/pull/1750 [#1754]: https://github.com/actix/actix-web/pull/1754 [#1749]: https://github.com/actix/actix-web/pull/1749 @@ -380,13 +380,13 @@ to retain any trailing slashes. [#1695] * Remove bound `std::marker::Sized` from `web::Data` to support storing `Arc` via `web::Data::from` [#1710] -- + ### Fixed -- `ResourceMap` debug printing is no longer infinitely recursive. [#1708] +* `ResourceMap` debug printing is no longer infinitely recursive. [#1708] [#1695]: https://github.com/actix/actix-web/pull/1695 [#1708]: https://github.com/actix/actix-web/pull/1708 -- 1710]: https://github.com/actix/actix-web/pull/1710 +[#1710]: https://github.com/actix/actix-web/pull/1710 ## 3.0.2 - 2020-09-15 @@ -395,33 +395,33 @@ [#1678]: https://github.com/actix/actix-web/pull/1678 -- + ## 3.0.1 - 2020-09-13 ### Changed * `middleware::normalize::TrailingSlash` enum is now accessible. [#1673] [#1673]: https://github.com/actix/actix-web/pull/1673 -- + ## 3.0.0 - 2020-09-11 * No significant changes from `3.0.0-beta.4`. ## 3.0.0-beta.4 - 2020-09-09 -- # Added +### Added * `middleware::NormalizePath` now has configurable behavior for either always having a trailing slash, or as the new addition, always trimming trailing slashes. [#1639] ### Changed -- Update actix-codec and actix-utils dependencies. [#1634] +* Update actix-codec and actix-utils dependencies. [#1634] * `FormConfig` and `JsonConfig` configurations are now also considered when set using `App::data`. [#1641] * `HttpServer::maxconn` is renamed to the more expressive `HttpServer::max_connections`. [#1655] -- `HttpServer::maxconnrate` is renamed to the more expressive -- `HttpServer::max_connection_rate`. [#1655] +* `HttpServer::maxconnrate` is renamed to the more expressive + `HttpServer::max_connection_rate`. [#1655] -- 1639]: https://github.com/actix/actix-web/pull/1639 -- 1641]: https://github.com/actix/actix-web/pull/1641 +[#1639]: https://github.com/actix/actix-web/pull/1639 +[#1641]: https://github.com/actix/actix-web/pull/1641 [#1634]: https://github.com/actix/actix-web/pull/1634 [#1655]: https://github.com/actix/actix-web/pull/1655 @@ -431,22 +431,22 @@ ## 3.0.0-beta.2 - 2020-08-17 -- # Changed +### Changed * `PayloadConfig` is now also considered in `Bytes` and `String` extractors when set using `App::data`. [#1610] * `web::Path` now has a public representation: `web::Path(pub T)` that enables destructuring. [#1594] -- `ServiceRequest::app_data` allows retrieval of non-Data data without splitting into parts to +* `ServiceRequest::app_data` allows retrieval of non-Data data without splitting into parts to access `HttpRequest` which already allows this. [#1618] -- Re-export all error types from `awc`. [#1621] +* Re-export all error types from `awc`. [#1621] * MSRV is now 1.42.0. -- + ### Fixed -- Memory leak of app data in pooled requests. [#1609] -- +* Memory leak of app data in pooled requests. [#1609] + [#1594]: https://github.com/actix/actix-web/pull/1594 [#1609]: https://github.com/actix/actix-web/pull/1609 -- 1610]: https://github.com/actix/actix-web/pull/1610 +[#1610]: https://github.com/actix/actix-web/pull/1610 [#1618]: https://github.com/actix/actix-web/pull/1618 [#1621]: https://github.com/actix/actix-web/pull/1621 @@ -457,29 +457,29 @@ * `HttpRequest::match_pattern` and `ServiceRequest::match_pattern` for extracting the matched resource pattern. * `HttpRequest::match_name` and `ServiceRequest::match_name` for extracting matched resource name. -- -- # Changed + +### Changed * Fix actix_http::h1::dispatcher so it returns when HW_BUFFER_SIZE is reached. Should reduce peak memory consumption during large uploads. [#1550] -- Migrate cookie handling to `cookie` crate. Actix-web no longer requires `ring` dependency. +* Migrate cookie handling to `cookie` crate. Actix-web no longer requires `ring` dependency. * MSRV is now 1.41.1 -- # Fixed -- `NormalizePath` improved consistency when path needs slashes added _and_ removed. -- +### Fixed +* `NormalizePath` improved consistency when path needs slashes added _and_ removed. + ## 3.0.0-alpha.3 - 2020-05-21 -- # Added +### Added * Add option to create `Data` from `Arc` [#1509] ### Changed * Resources and Scopes can now access non-overridden data types set on App (or containing scopes) when setting their own data. [#1486] -- Fix audit issue logging by default peer address [#1485] +* Fix audit issue logging by default peer address [#1485] * Bump minimum supported Rust version to 1.40 * Replace deprecated `net2` crate with `socket2` -- -- 1485]: https://github.com/actix/actix-web/pull/1485 -- 1509]: https://github.com/actix/actix-web/pull/1509 -- + +[#1485]: https://github.com/actix/actix-web/pull/1485 +[#1509]: https://github.com/actix/actix-web/pull/1509 + ## [3.0.0-alpha.2] - 2020-05-08 ### Changed @@ -488,10 +488,10 @@ * Implement `std::error::Error` for our custom errors [#1422] * NormalizePath middleware now appends trailing / so that routes of form /example/ respond to /example requests. [#1433] * Remove the `failure` feature and support. -- -- 1422]: https://github.com/actix/actix-web/pull/1422 -- 1433]: https://github.com/actix/actix-web/pull/1433 -- 1452]: https://github.com/actix/actix-web/pull/1452 + +[#1422]: https://github.com/actix/actix-web/pull/1422 +[#1433]: https://github.com/actix/actix-web/pull/1433 +[#1452]: https://github.com/actix/actix-web/pull/1452 [#1486]: https://github.com/actix/actix-web/pull/1486 @@ -503,16 +503,16 @@ * Add convenience functions `test::read_body_json()` and `test::TestRequest::send_request()` for testing. ### Changed -- -- Use `sha-1` crate instead of unmaintained `sha1` crate + +* Use `sha-1` crate instead of unmaintained `sha1` crate * Skip empty chunks when returning response from a `Stream` [#1308] * Update the `time` dependency to 0.2.7 * Update `actix-tls` dependency to 2.0.0-alpha.1 -- Update `rustls` dependency to 0.17 -- -- 1308]: https://github.com/actix/actix-web/pull/1308 -- -- [2.0.0] - 2019-12-25 +* Update `rustls` dependency to 0.17 + +[#1308]: https://github.com/actix/actix-web/pull/1308 + +## [2.0.0] - 2019-12-25 ### Changed @@ -520,405 +520,404 @@ * Allow to gracefully stop test server via `TestServer::stop()` -- Allow to specify multi-patterns for resources +* Allow to specify multi-patterns for resources -- [2.0.0-rc] - 2019-12-20 +## [2.0.0-rc] - 2019-12-20 -- # Changed +### Changed * Move `BodyEncoding` to `dev` module #1220 * Allow to set `peer_addr` for TestRequest #1074 -- Make web::Data deref to Arc #1214 +* Make web::Data deref to Arc #1214 -- Rename `App::register_data()` to `App::app_data()` +* Rename `App::register_data()` to `App::app_data()` -- `HttpRequest::app_data()` returns `Option<&T>` instead of `Option<&Data>` +* `HttpRequest::app_data()` returns `Option<&T>` instead of `Option<&Data>` -- # Fixed +### Fixed -- Fix `AppConfig::secure()` is always false. #1202 +* Fix `AppConfig::secure()` is always false. #1202 ## [2.0.0-alpha.6] - 2019-12-15 -- + ### Fixed * Fixed compilation with default features off ## [2.0.0-alpha.5] - 2019-12-13 -- # Added +### Added * Add test server, `test::start()` and `test::start_with()` ## [2.0.0-alpha.4] - 2019-12-08 -- # Deleted +### Deleted * Delete HttpServer::run(), it is not useful with async/await ## [2.0.0-alpha.3] - 2019-12-07 -- # Changed +### Changed * Migrate to tokio 0.2 ## [2.0.0-alpha.1] - 2019-11-22 -- + ### Changed * Migrated to `std::future` * Remove implementation of `Responder` for `()`. (#1167) -- + ## [1.0.9] - 2019-11-14 -- + ### Added * Add `Payload::into_inner` method and make stored `def::Payload` public. (#1110) ### Changed -- Support `Host` guards when the `Host` header is unset (e.g. HTTP/2 requests) (#1129) +* Support `Host` guards when the `Host` header is unset (e.g. HTTP/2 requests) (#1129) ## [1.0.8] - 2019-09-25 -- + ### Added * Add `Scope::register_data` and `Resource::register_data` methods, parallel to `App::register_data`. * Add `middleware::Condition` that conditionally enables another middleware -- + * Allow to re-construct `ServiceRequest` from `HttpRequest` and `Payload` -- Add `HttpServer::listen_uds` for ability to listen on UDS FD rather than path, +* Add `HttpServer::listen_uds` for ability to listen on UDS FD rather than path, which is useful for example with systemd. -- + ### Changed -- + * Make UrlEncodedError::Overflow more informative * Use actix-testing for testing utils -- + ## [1.0.7] - 2019-08-29 -- + ### Fixed * Request Extensions leak #1062 ## [1.0.6] - 2019-08-28 -- + ### Added * Re-implement Host predicate (#989) * Form implements Responder, returning a `application/x-www-form-urlencoded` response -- Add `into_inner` to `Data` +* Add `into_inner` to `Data` -- Add `test::TestRequest::set_form()` convenience method to automatically serialize data and set +* Add `test::TestRequest::set_form()` convenience method to automatically serialize data and set the header in test requests. -- + ### Changed -- + * `Query` payload made `pub`. Allows user to pattern-match the payload. * Enable `rust-tls` feature for client #1045 -- Update serde_urlencoded to 0.6.1 +* Update serde_urlencoded to 0.6.1 + +* Update url to 2.1 -- Update url to 2.1 -- ## [1.0.5] - 2019-07-18 -- + ### Added * Unix domain sockets (HttpServer::bind_uds) #92 * Actix now logs errors resulting in "internal server error" responses always, with the `error` logging level -- + ### Fixed -- + * Restored logging of errors through the `Logger` middleware ## [1.0.4] - 2019-07-17 -- + ### Added * Add `Responder` impl for `(T, StatusCode) where T: Responder` * Allow to access app's resource map via `ServiceRequest::resource_map()` and `HttpRequest::resource_map()` methods. -- + ### Changed -- + * Upgrade `rand` dependency version to 0.7 ## [1.0.3] - 2019-06-28 -- + ### Added * Support asynchronous data factories #850 ### Changed -- Use `encoding_rs` crate instead of unmaintained `encoding` crate +* Use `encoding_rs` crate instead of unmaintained `encoding` crate ## [1.0.2] - 2019-06-17 -- + ### Changed * Move cors middleware to `actix-cors` crate. * Move identity middleware to `actix-identity` crate. -- + ## [1.0.1] - 2019-06-17 -- + ### Added * Add support for PathConfig #903 * Add `middleware::identity::RequestIdentity` trait to `get_identity` from `HttpMessage`. -- # Changed +### Changed -- Move cors middleware to `actix-cors` crate. +* Move cors middleware to `actix-cors` crate. * Move identity middleware to `actix-identity` crate. -- Disable default feature `secure-cookies`. +* Disable default feature `secure-cookies`. -- Allow to test an app that uses async actors #897 +* Allow to test an app that uses async actors #897 -- Re-apply patch from #637 #894 +* Re-apply patch from #637 #894 -- # Fixed +### Fixed -- HttpRequest::url_for is broken with nested scopes #915 +* HttpRequest::url_for is broken with nested scopes #915 ## [1.0.0] - 2019-06-05 -- + ### Added * Add `Scope::configure()` method. * Add `ServiceRequest::set_payload()` method. -- Add `test::TestRequest::set_json()` convenience method to automatically +* Add `test::TestRequest::set_json()` convenience method to automatically serialize data and set header in test requests. -- + * Add macros for head, options, trace, connect and patch http methods -- + ### Changed -- Drop an unnecessary `Option<_>` indirection around `ServerBuilder` from `HttpServer`. #863 +* Drop an unnecessary `Option<_>` indirection around `ServerBuilder` from `HttpServer`. #863 ### Fixed -- Fix Logger request time format, and use rfc3339. #867 +* Fix Logger request time format, and use rfc3339. #867 * Clear http requests pool on app service drop #860 -- + ## [1.0.0-rc] - 2019-05-18 -- + ### Added * Add `Query::from_query()` to extract parameters from a query string. #846 * `QueryConfig`, similar to `JsonConfig` for customizing error handling of query extractors. ### Changed -- -- `JsonConfig` is now `Send + Sync`, this implies that `error_handler` must be `Send + Sync` too. + +* `JsonConfig` is now `Send + Sync`, this implies that `error_handler` must be `Send + Sync` too. ### Fixed -- Codegen with parameters in the path only resolves the first registered endpoint #841 +* Codegen with parameters in the path only resolves the first registered endpoint #841 ## [1.0.0-beta.4] - 2019-05-12 -- + ### Added * Allow to set/override app data on scope level ### Changed -- `App::configure` take an `FnOnce` instead of `Fn` +* `App::configure` take an `FnOnce` instead of `Fn` * Upgrade actix-net crates -- [1.0.0-beta.3] - 2019-05-04 -- +## [1.0.0-beta.3] - 2019-05-04 + ### Added * Add helper function for executing futures `test::block_fn()` ### Changed -- Extractor configuration could be registered with `App::data()` +* Extractor configuration could be registered with `App::data()` or with `Resource::data()` #775 * Route data is unified with app data, `Route::data()` moved to resource -- level to `Resource::data()` + level to `Resource::data()` * CORS handling without headers #702 -- + * Allow constructing `Data` instances to avoid double `Arc` for `Send + Sync` types. -- # Fixed +### Fixed -- Fix `NormalizePath` middleware impl #806 +* Fix `NormalizePath` middleware impl #806 ### Deleted -- `App::data_factory()` is deleted. +* `App::data_factory()` is deleted. ## [1.0.0-beta.2] - 2019-04-24 -- + ### Added * Add raw services support via `web::service()` * Add helper functions for reading response body `test::read_body()` -- Add support for `remainder match` (i.e "/path/{tail}*") +* Add support for `remainder match` (i.e "/path/{tail}*") -- Extend `Responder` trait, allow to override status code and headers. +* Extend `Responder` trait, allow to override status code and headers. -- Store visit and login timestamp in the identity cookie #502 +* Store visit and login timestamp in the identity cookie #502 -- # Changed +### Changed -- `.to_async()` handler can return `Responder` type #792 +* `.to_async()` handler can return `Responder` type #792 ### Fixed -- Fix async web::Data factory handling +* Fix async web::Data factory handling ## [1.0.0-beta.1] - 2019-04-20 -- + ### Added * Add helper functions for reading test response body, `test::read_response()` and test::read_response_json()` * Add `.peer_addr()` #744 -- + * Add `NormalizePath` middleware -- # Changed +### Changed -- Rename `RouterConfig` to `ServiceConfig` +* Rename `RouterConfig` to `ServiceConfig` * Rename `test::call_success` to `test::call_service` -- Removed `ServiceRequest::from_parts()` as it is unsafe to create from parts. +* Removed `ServiceRequest::from_parts()` as it is unsafe to create from parts. -- `CookieIdentityPolicy::max_age()` accepts value in seconds +* `CookieIdentityPolicy::max_age()` accepts value in seconds -- # Fixed +### Fixed -- Fixed `TestRequest::app_data()` +* Fixed `TestRequest::app_data()` ## [1.0.0-alpha.6] - 2019-04-14 -- + ### Changed * Allow using any service as default service. * Remove generic type for request payload, always use default. -- Removed `Decompress` middleware. Bytes, String, Json, Form extractors +* Removed `Decompress` middleware. Bytes, String, Json, Form extractors automatically decompress payload. -- + * Make extractor config type explicit. Add `FromRequest::Config` associated type. -- + ## [1.0.0-alpha.5] - 2019-04-12 -- + ### Added * Added async io `TestBuffer` for testing. ### Deleted -- Removed native-tls support +* Removed native-tls support ## [1.0.0-alpha.4] - 2019-04-08 -- + ### Added * `App::configure()` allow to offload app configuration to different methods * Added `URLPath` option for logger -- Added `ServiceRequest::app_data()`, returns `Data` +* Added `ServiceRequest::app_data()`, returns `Data` -- Added `ServiceFromRequest::app_data()`, returns `Data` +* Added `ServiceFromRequest::app_data()`, returns `Data` -- # Changed +### Changed -- `FromRequest` trait refactoring +* `FromRequest` trait refactoring * Move multipart support to actix-multipart crate -- # Fixed +### Fixed -- Fix body propagation in Response::from_error. #760 +* Fix body propagation in Response::from_error. #760 ## [1.0.0-alpha.3] - 2019-04-02 -- + ### Changed * Renamed `TestRequest::to_service()` to `TestRequest::to_srv_request()` * Renamed `TestRequest::to_response()` to `TestRequest::to_srv_response()` -- Removed `Deref` impls +* Removed `Deref` impls -- # Removed +### Removed -- Removed unused `actix_web::web::md()` +* Removed unused `actix_web::web::md()` ## [1.0.0-alpha.2] - 2019-03-29 -- + ### Added * Rustls support ### Changed -- Use forked cookie +* Use forked cookie * Multipart::Field renamed to MultipartField -- [1.0.0-alpha.1] - 2019-03-28 +## [1.0.0-alpha.1] - 2019-03-28 -- # Changed +### Changed * Complete architecture re-design. * Return 405 response if no matching route found within resource #538 -- - diff --git a/actix-files/CHANGES.md b/actix-files/CHANGES.md index ef8eba0fc..d6b39e28f 100644 --- a/actix-files/CHANGES.md +++ b/actix-files/CHANGES.md @@ -4,42 +4,42 @@ ## 0.6.0-beta.10 - 2021-12-11 -- No significant changes since `0.6.0-beta.9`. +* No significant changes since `0.6.0-beta.9`. ## 0.6.0-beta.9 - 2021-11-22 -- Add crate feature `experimental-io-uring`, enabling async file I/O to be utilized. This feature is only available on Linux OSes with recent kernel versions. This feature is semver-exempt. [#2408] -- Add `NamedFile::open_async`. [#2408] -- Fix 304 Not Modified responses to omit the Content-Length header, as per the spec. [#2453] -- The `Responder` impl for `NamedFile` now has a boxed future associated type. [#2408] -- The `Service` impl for `NamedFileService` now has a boxed future associated type. [#2408] -- Add `impl Clone` for `FilesService`. [#2408] +* Add crate feature `experimental-io-uring`, enabling async file I/O to be utilized. This feature is only available on Linux OSes with recent kernel versions. This feature is semver-exempt. [#2408] +* Add `NamedFile::open_async`. [#2408] +* Fix 304 Not Modified responses to omit the Content-Length header, as per the spec. [#2453] +* The `Responder` impl for `NamedFile` now has a boxed future associated type. [#2408] +* The `Service` impl for `NamedFileService` now has a boxed future associated type. [#2408] +* Add `impl Clone` for `FilesService`. [#2408] [#2408]: https://github.com/actix/actix-web/pull/2408 [#2453]: https://github.com/actix/actix-web/pull/2453 ## 0.6.0-beta.8 - 2021-10-20 -- Minimum supported Rust version (MSRV) is now 1.52. +* Minimum supported Rust version (MSRV) is now 1.52. ## 0.6.0-beta.7 - 2021-09-09 -- Minimum supported Rust version (MSRV) is now 1.51. +* Minimum supported Rust version (MSRV) is now 1.51. ## 0.6.0-beta.6 - 2021-06-26 -- Added `Files::path_filter()`. [#2274] -- `Files::show_files_listing()` can now be used with `Files::index_file()` to show files listing as a fallback when the index file is not found. [#2228] +* Added `Files::path_filter()`. [#2274] +* `Files::show_files_listing()` can now be used with `Files::index_file()` to show files listing as a fallback when the index file is not found. [#2228] [#2274]: https://github.com/actix/actix-web/pull/2274 [#2228]: https://github.com/actix/actix-web/pull/2228 ## 0.6.0-beta.5 - 2021-06-17 -- `NamedFile` now implements `ServiceFactory` and `HttpServiceFactory` making it much more useful in routing. For example, it can be used directly as a default service. [#2135] -- For symbolic links, `Content-Disposition` header no longer shows the filename of the original file. [#2156] -- `Files::redirect_to_slash_directory()` now works as expected when used with `Files::show_files_listing()`. [#2225] -- `application/{javascript, json, wasm}` mime type now have `inline` disposition by default. [#2257] +* `NamedFile` now implements `ServiceFactory` and `HttpServiceFactory` making it much more useful in routing. For example, it can be used directly as a default service. [#2135] +* For symbolic links, `Content-Disposition` header no longer shows the filename of the original file. [#2156] +* `Files::redirect_to_slash_directory()` now works as expected when used with `Files::show_files_listing()`. [#2225] +* `application/{javascript, json, wasm}` mime type now have `inline` disposition by default. [#2257] [#2135]: https://github.com/actix/actix-web/pull/2135 [#2156]: https://github.com/actix/actix-web/pull/2156 @@ -48,130 +48,130 @@ ## 0.6.0-beta.4 - 2021-04-02 -- Add support for `.guard` in `Files` to selectively filter `Files` services. [#2046] +* Add support for `.guard` in `Files` to selectively filter `Files` services. [#2046] [#2046]: https://github.com/actix/actix-web/pull/2046 ## 0.6.0-beta.3 - 2021-03-09 -- No notable changes. +* No notable changes. ## 0.6.0-beta.2 - 2021-02-10 -- Fix If-Modified-Since and If-Unmodified-Since to not compare using sub-second timestamps. [#1887] -- Replace `v_htmlescape` with `askama_escape`. [#1953] +* Fix If-Modified-Since and If-Unmodified-Since to not compare using sub-second timestamps. [#1887] +* Replace `v_htmlescape` with `askama_escape`. [#1953] [#1887]: https://github.com/actix/actix-web/pull/1887 [#1953]: https://github.com/actix/actix-web/pull/1953 ## 0.6.0-beta.1 - 2021-01-07 -- `HttpRange::parse` now has its own error type. -- Update `bytes` to `1.0`. [#1813] +* `HttpRange::parse` now has its own error type. +* Update `bytes` to `1.0`. [#1813] [#1813]: https://github.com/actix/actix-web/pull/1813 ## 0.5.0 - 2020-12-26 -- Optionally support hidden files/directories. [#1811] +* Optionally support hidden files/directories. [#1811] [#1811]: https://github.com/actix/actix-web/pull/1811 ## 0.4.1 - 2020-11-24 -- Clarify order of parameters in `Files::new` and improve docs. +* Clarify order of parameters in `Files::new` and improve docs. ## 0.4.0 - 2020-10-06 -- Add `Files::prefer_utf8` option that adds UTF-8 charset on certain response types. [#1714] +* Add `Files::prefer_utf8` option that adds UTF-8 charset on certain response types. [#1714] [#1714]: https://github.com/actix/actix-web/pull/1714 ## 0.3.0 - 2020-09-11 -- No significant changes from 0.3.0-beta.1. +* No significant changes from 0.3.0-beta.1. ## 0.3.0-beta.1 - 2020-07-15 -- Update `v_htmlescape` to 0.10 -- Update `actix-web` and `actix-http` dependencies to beta.1 +* Update `v_htmlescape` to 0.10 +* Update `actix-web` and `actix-http` dependencies to beta.1 ## 0.3.0-alpha.1 - 2020-05-23 -- Update `actix-web` and `actix-http` dependencies to alpha -- Fix some typos in the docs -- Bump minimum supported Rust version to 1.40 -- Support sending Content-Length when Content-Range is specified [#1384] +* Update `actix-web` and `actix-http` dependencies to alpha +* Fix some typos in the docs +* Bump minimum supported Rust version to 1.40 +* Support sending Content-Length when Content-Range is specified [#1384] [#1384]: https://github.com/actix/actix-web/pull/1384 ## 0.2.1 - 2019-12-22 -- Use the same format for file URLs regardless of platforms +* Use the same format for file URLs regardless of platforms ## 0.2.0 - 2019-12-20 -- Fix BodyEncoding trait import #1220 +* Fix BodyEncoding trait import #1220 ## 0.2.0-alpha.1 - 2019-12-07 -- Migrate to `std::future` +* Migrate to `std::future` ## 0.1.7 - 2019-11-06 -- Add an additional `filename*` param in the `Content-Disposition` header of +* Add an additional `filename*` param in the `Content-Disposition` header of `actix_files::NamedFile` to be more compatible. (#1151) ## 0.1.6 - 2019-10-14 -- Add option to redirect to a slash-ended path `Files` #1132 +* Add option to redirect to a slash-ended path `Files` #1132 ## 0.1.5 - 2019-10-08 -- Bump up `mime_guess` crate version to 2.0.1 -- Bump up `percent-encoding` crate version to 2.1 -- Allow user defined request guards for `Files` #1113 +* Bump up `mime_guess` crate version to 2.0.1 +* Bump up `percent-encoding` crate version to 2.1 +* Allow user defined request guards for `Files` #1113 ## 0.1.4 - 2019-07-20 -- Allow to disable `Content-Disposition` header #686 +* Allow to disable `Content-Disposition` header #686 ## 0.1.3 - 2019-06-28 -- Do not set `Content-Length` header, let actix-http set it #930 +* Do not set `Content-Length` header, let actix-http set it #930 ## 0.1.2 - 2019-06-13 -- Content-Length is 0 for NamedFile HEAD request #914 -- Fix ring dependency from actix-web default features for #741 +* Content-Length is 0 for NamedFile HEAD request #914 +* Fix ring dependency from actix-web default features for #741 ## 0.1.1 - 2019-06-01 -- Static files are incorrectly served as both chunked and with length #812 +* Static files are incorrectly served as both chunked and with length #812 ## 0.1.0 - 2019-05-25 -- NamedFile last-modified check always fails due to nano-seconds in file modified date #820 +* NamedFile last-modified check always fails due to nano-seconds in file modified date #820 ## 0.1.0-beta.4 - 2019-05-12 -- Update actix-web to beta.4 +* Update actix-web to beta.4 ## 0.1.0-beta.1 - 2019-04-20 -- Update actix-web to beta.1 +* Update actix-web to beta.1 ## 0.1.0-alpha.6 - 2019-04-14 -- Update actix-web to alpha6 +* Update actix-web to alpha6 ## 0.1.0-alpha.4 - 2019-04-08 -- Update actix-web to alpha4 +* Update actix-web to alpha4 ## 0.1.0-alpha.2 - 2019-04-02 -- Add default handler support +* Add default handler support ## 0.1.0-alpha.1 - 2019-03-28 -- Initial impl +* Initial impl diff --git a/actix-http-test/CHANGES.md b/actix-http-test/CHANGES.md index 4e86e20e8..156012168 100644 --- a/actix-http-test/CHANGES.md +++ b/actix-http-test/CHANGES.md @@ -4,125 +4,125 @@ ## 3.0.0-beta.9 - 2021-12-11 -- No significant changes since `3.0.0-beta.8`. +* No significant changes since `3.0.0-beta.8`. ## 3.0.0-beta.8 - 2021-11-30 -- Update `actix-tls` to `3.0.0-rc.1`. [#2474] +* Update `actix-tls` to `3.0.0-rc.1`. [#2474] [#2474]: https://github.com/actix/actix-web/pull/2474 ## 3.0.0-beta.7 - 2021-11-22 -- Fix compatibility with experimental `io-uring` feature of `actix-rt`. [#2408] +* Fix compatibility with experimental `io-uring` feature of `actix-rt`. [#2408] [#2408]: https://github.com/actix/actix-web/pull/2408 ## 3.0.0-beta.6 - 2021-11-15 -- `TestServer::stop` is now async and will wait for the server and system to shutdown. [#2442] -- Update `actix-server` to `2.0.0-beta.9`. [#2442] -- Minimum supported Rust version (MSRV) is now 1.52. +* `TestServer::stop` is now async and will wait for the server and system to shutdown. [#2442] +* Update `actix-server` to `2.0.0-beta.9`. [#2442] +* Minimum supported Rust version (MSRV) is now 1.52. [#2442]: https://github.com/actix/actix-web/pull/2442 ## 3.0.0-beta.5 - 2021-09-09 -- Minimum supported Rust version (MSRV) is now 1.51. +* Minimum supported Rust version (MSRV) is now 1.51. ## 3.0.0-beta.4 - 2021-04-02 -- Added `TestServer::client_headers` method. [#2097] +* Added `TestServer::client_headers` method. [#2097] [#2097]: https://github.com/actix/actix-web/pull/2097 ## 3.0.0-beta.3 - 2021-03-09 -- No notable changes. +* No notable changes. ## 3.0.0-beta.2 - 2021-02-10 -- No notable changes. +* No notable changes. ## 3.0.0-beta.1 - 2021-01-07 -- Update `bytes` to `1.0`. [#1813] +* Update `bytes` to `1.0`. [#1813] [#1813]: https://github.com/actix/actix-web/pull/1813 ## 2.1.0 - 2020-11-25 -- Add ability to set address for `TestServer`. [#1645] -- Upgrade `base64` to `0.13`. -- Upgrade `serde_urlencoded` to `0.7`. [#1773] +* Add ability to set address for `TestServer`. [#1645] +* Upgrade `base64` to `0.13`. +* Upgrade `serde_urlencoded` to `0.7`. [#1773] [#1773]: https://github.com/actix/actix-web/pull/1773 [#1645]: https://github.com/actix/actix-web/pull/1645 ## 2.0.0 - 2020-09-11 -- Update actix-codec and actix-utils dependencies. +* Update actix-codec and actix-utils dependencies. ## 2.0.0-alpha.1 - 2020-05-23 -- Update the `time` dependency to 0.2.7 -- Update `actix-connect` dependency to 2.0.0-alpha.2 -- Make `test_server` `async` fn. -- Bump minimum supported Rust version to 1.40 -- Replace deprecated `net2` crate with `socket2` -- Update `base64` dependency to 0.12 -- Update `env_logger` dependency to 0.7 +* Update the `time` dependency to 0.2.7 +* Update `actix-connect` dependency to 2.0.0-alpha.2 +* Make `test_server` `async` fn. +* Bump minimum supported Rust version to 1.40 +* Replace deprecated `net2` crate with `socket2` +* Update `base64` dependency to 0.12 +* Update `env_logger` dependency to 0.7 ## 1.0.0 - 2019-12-13 -- Replaced `TestServer::start()` with `test_server()` +* Replaced `TestServer::start()` with `test_server()` ## 1.0.0-alpha.3 - 2019-12-07 -- Migrate to `std::future` +* Migrate to `std::future` ## 0.2.5 - 2019-09-17 -- Update serde_urlencoded to "0.6.1" -- Increase TestServerRuntime timeouts from 500ms to 3000ms -- Do not override current `System` +* Update serde_urlencoded to "0.6.1" +* Increase TestServerRuntime timeouts from 500ms to 3000ms +* Do not override current `System` ## 0.2.4 - 2019-07-18 -- Update actix-server to 0.6 +* Update actix-server to 0.6 ## 0.2.3 - 2019-07-16 -- Add `delete`, `options`, `patch` methods to `TestServerRunner` +* Add `delete`, `options`, `patch` methods to `TestServerRunner` ## 0.2.2 - 2019-06-16 -- Add .put() and .sput() methods +* Add .put() and .sput() methods ## 0.2.1 - 2019-06-05 -- Add license files +* Add license files ## 0.2.0 - 2019-05-12 -- Update awc and actix-http deps +* Update awc and actix-http deps ## 0.1.1 - 2019-04-24 -- Always make new connection for http client +* Always make new connection for http client ## 0.1.0 - 2019-04-16 -- No changes +* No changes ## 0.1.0-alpha.3 - 2019-04-02 -- Request functions accept path #743 +* Request functions accept path #743 ## 0.1.0-alpha.2 - 2019-03-29 -- Added TestServerRuntime::load_body() method -- Update actix-http and awc libraries +* Added TestServerRuntime::load_body() method +* Update actix-http and awc libraries ## 0.1.0-alpha.1 - 2019-03-28 -- Initial impl +* Initial impl diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 3b45e934f..ad98d132a 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -2,22 +2,22 @@ ## Unreleased - 2021-xx-xx ### Changes -- `HeaderMap::get_all` now returns a `std::slice::Iter`. [#2527] +* `HeaderMap::get_all` now returns a `std::slice::Iter`. [#2527] [#2527]: https://github.com/actix/actix-web/pull/2527 ## 3.0.0-beta.16 - 2021-12-17 ### Added -- New method on `MessageBody` trait, `try_into_bytes`, with default implementation, for optimizations on body types that complete in exactly one poll. Replaces `is_complete_body` and `take_complete_body`. [#2522] +* New method on `MessageBody` trait, `try_into_bytes`, with default implementation, for optimizations on body types that complete in exactly one poll. Replaces `is_complete_body` and `take_complete_body`. [#2522] ### Changed -- Rename trait `IntoHeaderPair => TryIntoHeaderPair`. [#2510] -- Rename `TryIntoHeaderPair::{try_into_header_pair => try_into_pair}`. [#2510] -- Rename trait `IntoHeaderValue => TryIntoHeaderValue`. [#2510] +* Rename trait `IntoHeaderPair => TryIntoHeaderPair`. [#2510] +* Rename `TryIntoHeaderPair::{try_into_header_pair => try_into_pair}`. [#2510] +* Rename trait `IntoHeaderValue => TryIntoHeaderValue`. [#2510] ### Removed -- `MessageBody::{is_complete_body,take_complete_body}`. [#2522] +* `MessageBody::{is_complete_body,take_complete_body}`. [#2522] [#2510]: https://github.com/actix/actix-web/pull/2510 [#2522]: https://github.com/actix/actix-web/pull/2522 @@ -25,43 +25,43 @@ ## 3.0.0-beta.15 - 2021-12-11 ### Added -- Add timeout for canceling HTTP/2 server side connection handshake. Default to 5 seconds. [#2483] -- HTTP/2 handshake timeout can be configured with `ServiceConfig::client_timeout`. [#2483] -- `Response::map_into_boxed_body`. [#2468] -- `body::EitherBody` enum. [#2468] -- `body::None` struct. [#2468] -- Impl `MessageBody` for `bytestring::ByteString`. [#2468] -- `impl Clone for ws::HandshakeError`. [#2468] -- `#[must_use]` for `ws::Codec` to prevent subtle bugs. [#1920] -- `impl Default ` for `ws::Codec`. [#1920] -- `header::QualityItem::{max, min}`. [#2486] -- `header::Quality::{MAX, MIN}`. [#2486] -- `impl Display` for `header::Quality`. [#2486] -- Connection data set through the `on_connect_ext` callbacks is now accessible only from the new `Request::conn_data()` method. [#2491] -- `Request::take_conn_data()`. [#2491] -- `Request::take_req_data()`. [#2487] -- `impl Clone` for `RequestHead`. [#2487] -- New methods on `MessageBody` trait, `is_complete_body` and `take_complete_body`, both with default implementations, for optimizations on body types that are done in exactly one poll/chunk. [#2497] -- New `boxed` method on `MessageBody` trait for wrapping body type. [#2520] +* Add timeout for canceling HTTP/2 server side connection handshake. Default to 5 seconds. [#2483] +* HTTP/2 handshake timeout can be configured with `ServiceConfig::client_timeout`. [#2483] +* `Response::map_into_boxed_body`. [#2468] +* `body::EitherBody` enum. [#2468] +* `body::None` struct. [#2468] +* Impl `MessageBody` for `bytestring::ByteString`. [#2468] +* `impl Clone for ws::HandshakeError`. [#2468] +* `#[must_use]` for `ws::Codec` to prevent subtle bugs. [#1920] +* `impl Default ` for `ws::Codec`. [#1920] +* `header::QualityItem::{max, min}`. [#2486] +* `header::Quality::{MAX, MIN}`. [#2486] +* `impl Display` for `header::Quality`. [#2486] +* Connection data set through the `on_connect_ext` callbacks is now accessible only from the new `Request::conn_data()` method. [#2491] +* `Request::take_conn_data()`. [#2491] +* `Request::take_req_data()`. [#2487] +* `impl Clone` for `RequestHead`. [#2487] +* New methods on `MessageBody` trait, `is_complete_body` and `take_complete_body`, both with default implementations, for optimizations on body types that are done in exactly one poll/chunk. [#2497] +* New `boxed` method on `MessageBody` trait for wrapping body type. [#2520] ### Changed -- Rename `body::BoxBody::{from_body => new}`. [#2468] -- Body type for `Responses` returned from `Response::{new, ok, etc...}` is now `BoxBody`. [#2468] -- The `Error` associated type on `MessageBody` type now requires `impl Error` (or similar). [#2468] -- Error types using in service builders now require `Into>`. [#2468] -- `From` implementations on error types now return a `Response`. [#2468] -- `ResponseBuilder::body(B)` now returns `Response>`. [#2468] -- `ResponseBuilder::finish()` now returns `Response>`. [#2468] +* Rename `body::BoxBody::{from_body => new}`. [#2468] +* Body type for `Responses` returned from `Response::{new, ok, etc...}` is now `BoxBody`. [#2468] +* The `Error` associated type on `MessageBody` type now requires `impl Error` (or similar). [#2468] +* Error types using in service builders now require `Into>`. [#2468] +* `From` implementations on error types now return a `Response`. [#2468] +* `ResponseBuilder::body(B)` now returns `Response>`. [#2468] +* `ResponseBuilder::finish()` now returns `Response>`. [#2468] ### Removed -- `ResponseBuilder::streaming`. [#2468] -- `impl Future` for `ResponseBuilder`. [#2468] -- Remove unnecessary `MessageBody` bound on types passed to `body::AnyBody::new`. [#2468] -- Move `body::AnyBody` to `awc`. Replaced with `EitherBody` and `BoxBody`. [#2468] -- `impl Copy` for `ws::Codec`. [#1920] -- `header::qitem` helper. Replaced with `header::QualityItem::max`. [#2486] -- `impl TryFrom` for `header::Quality`. [#2486] -- `http` module. Most everything it contained is exported at the crate root. [#2488] +* `ResponseBuilder::streaming`. [#2468] +* `impl Future` for `ResponseBuilder`. [#2468] +* Remove unnecessary `MessageBody` bound on types passed to `body::AnyBody::new`. [#2468] +* Move `body::AnyBody` to `awc`. Replaced with `EitherBody` and `BoxBody`. [#2468] +* `impl Copy` for `ws::Codec`. [#1920] +* `header::qitem` helper. Replaced with `header::QualityItem::max`. [#2486] +* `impl TryFrom` for `header::Quality`. [#2486] +* `http` module. Most everything it contained is exported at the crate root. [#2488] [#2483]: https://github.com/actix/actix-web/pull/2483 [#2468]: https://github.com/actix/actix-web/pull/2468 @@ -76,10 +76,10 @@ ## 3.0.0-beta.14 - 2021-11-30 ### Changed -- Guarantee ordering of `header::GetAll` iterator to be same as insertion order. [#2467] -- Expose `header::map` module. [#2467] -- Implement `ExactSizeIterator` and `FusedIterator` for all `HeaderMap` iterators. [#2470] -- Update `actix-tls` to `3.0.0-rc.1`. [#2474] +* Guarantee ordering of `header::GetAll` iterator to be same as insertion order. [#2467] +* Expose `header::map` module. [#2467] +* Implement `ExactSizeIterator` and `FusedIterator` for all `HeaderMap` iterators. [#2470] +* Update `actix-tls` to `3.0.0-rc.1`. [#2474] [#2467]: https://github.com/actix/actix-web/pull/2467 [#2470]: https://github.com/actix/actix-web/pull/2470 @@ -88,24 +88,24 @@ ## 3.0.0-beta.13 - 2021-11-22 ### Added -- `body::AnyBody::empty` for quickly creating an empty body. [#2446] -- `body::AnyBody::none` for quickly creating a "none" body. [#2456] -- `impl Clone` for `body::AnyBody where S: Clone`. [#2448] -- `body::AnyBody::into_boxed` for quickly converting to a type-erased, boxed body type. [#2448] +* `body::AnyBody::empty` for quickly creating an empty body. [#2446] +* `body::AnyBody::none` for quickly creating a "none" body. [#2456] +* `impl Clone` for `body::AnyBody where S: Clone`. [#2448] +* `body::AnyBody::into_boxed` for quickly converting to a type-erased, boxed body type. [#2448] ### Changed -- Rename `body::AnyBody::{Message => Body}`. [#2446] -- Rename `body::AnyBody::{from_message => new_boxed}`. [#2448] -- Rename `body::AnyBody::{from_slice => copy_from_slice}`. [#2448] -- Rename `body::{BoxAnyBody => BoxBody}`. [#2448] -- Change representation of `AnyBody` to include a type parameter in `Body` variant. Defaults to `BoxBody`. [#2448] -- `Encoder::response` now returns `AnyBody>`. [#2448] +* Rename `body::AnyBody::{Message => Body}`. [#2446] +* Rename `body::AnyBody::{from_message => new_boxed}`. [#2448] +* Rename `body::AnyBody::{from_slice => copy_from_slice}`. [#2448] +* Rename `body::{BoxAnyBody => BoxBody}`. [#2448] +* Change representation of `AnyBody` to include a type parameter in `Body` variant. Defaults to `BoxBody`. [#2448] +* `Encoder::response` now returns `AnyBody>`. [#2448] ### Removed -- `body::AnyBody::Empty`; an empty body can now only be represented as a zero-length `Bytes` variant. [#2446] -- `body::BodySize::Empty`; an empty body can now only be represented as a `Sized(0)` variant. [#2446] -- `EncoderError::Boxed`; it is no longer required. [#2446] -- `body::ResponseBody`; is function is replaced by the new `body::AnyBody` enum. [#2446] +* `body::AnyBody::Empty`; an empty body can now only be represented as a zero-length `Bytes` variant. [#2446] +* `body::BodySize::Empty`; an empty body can now only be represented as a `Sized(0)` variant. [#2446] +* `EncoderError::Boxed`; it is no longer required. [#2446] +* `body::ResponseBody`; is function is replaced by the new `body::AnyBody` enum. [#2446] [#2446]: https://github.com/actix/actix-web/pull/2446 [#2448]: https://github.com/actix/actix-web/pull/2448 @@ -114,11 +114,11 @@ ## 3.0.0-beta.12 - 2021-11-15 ### Changed -- Update `actix-server` to `2.0.0-beta.9`. [#2442] +* Update `actix-server` to `2.0.0-beta.9`. [#2442] ### Removed -- `client` module. [#2425] -- `trust-dns` feature. [#2425] +* `client` module. [#2425] +* `trust-dns` feature. [#2425] [#2425]: https://github.com/actix/actix-web/pull/2425 [#2442]: https://github.com/actix/actix-web/pull/2442 @@ -126,21 +126,21 @@ ## 3.0.0-beta.11 - 2021-10-20 ### Changed -- Updated rustls to v0.20. [#2414] -- Minimum supported Rust version (MSRV) is now 1.52. +* Updated rustls to v0.20. [#2414] +* Minimum supported Rust version (MSRV) is now 1.52. [#2414]: https://github.com/actix/actix-web/pull/2414 ## 3.0.0-beta.10 - 2021-09-09 ### Changed -- `ContentEncoding` is now marked `#[non_exhaustive]`. [#2377] -- Minimum supported Rust version (MSRV) is now 1.51. +* `ContentEncoding` is now marked `#[non_exhaustive]`. [#2377] +* Minimum supported Rust version (MSRV) is now 1.51. ### Fixed -- Remove slice creation pointing to potential uninitialized data on h1 encoder. [#2364] -- Remove `Into` bound on `Encoder` body types. [#2375] -- Fix quality parse error in Accept-Encoding header. [#2344] +* Remove slice creation pointing to potential uninitialized data on h1 encoder. [#2364] +* Remove `Into` bound on `Encoder` body types. [#2375] +* Fix quality parse error in Accept-Encoding header. [#2344] [#2364]: https://github.com/actix/actix-web/pull/2364 [#2375]: https://github.com/actix/actix-web/pull/2375 @@ -150,15 +150,15 @@ ## 3.0.0-beta.9 - 2021-08-09 ### Fixed -- Potential HTTP request smuggling vulnerabilities. [RUSTSEC-2021-0081](https://github.com/rustsec/advisory-db/pull/977) +* Potential HTTP request smuggling vulnerabilities. [RUSTSEC-2021-0081](https://github.com/rustsec/advisory-db/pull/977) ## 3.0.0-beta.8 - 2021-06-26 ### Changed -- Change compression algorithm features flags. [#2250] +* Change compression algorithm features flags. [#2250] ### Removed -- `downcast` and `downcast_get_type_id` macros. [#2291] +* `downcast` and `downcast_get_type_id` macros. [#2291] [#2291]: https://github.com/actix/actix-web/pull/2291 [#2250]: https://github.com/actix/actix-web/pull/2250 @@ -166,37 +166,37 @@ ## 3.0.0-beta.7 - 2021-06-17 ### Added -- Alias `body::Body` as `body::AnyBody`. [#2215] -- `BoxAnyBody`: a boxed message body with boxed errors. [#2183] -- Re-export `http` crate's `Error` type as `error::HttpError`. [#2171] -- Re-export `StatusCode`, `Method`, `Version` and `Uri` at the crate root. [#2171] -- Re-export `ContentEncoding` and `ConnectionType` at the crate root. [#2171] -- `Response::into_body` that consumes response and returns body type. [#2201] -- `impl Default` for `Response`. [#2201] -- Add zstd support for `ContentEncoding`. [#2244] +* Alias `body::Body` as `body::AnyBody`. [#2215] +* `BoxAnyBody`: a boxed message body with boxed errors. [#2183] +* Re-export `http` crate's `Error` type as `error::HttpError`. [#2171] +* Re-export `StatusCode`, `Method`, `Version` and `Uri` at the crate root. [#2171] +* Re-export `ContentEncoding` and `ConnectionType` at the crate root. [#2171] +* `Response::into_body` that consumes response and returns body type. [#2201] +* `impl Default` for `Response`. [#2201] +* Add zstd support for `ContentEncoding`. [#2244] ### Changed -- The `MessageBody` trait now has an associated `Error` type. [#2183] -- All error trait bounds in server service builders have changed from `Into` to `Into>`. [#2253] -- All error trait bounds in message body and stream impls changed from `Into` to `Into>`. [#2253] -- Places in `Response` where `ResponseBody` was received or returned now simply use `B`. [#2201] -- `header` mod is now public. [#2171] -- `uri` mod is now public. [#2171] -- Update `language-tags` to `0.3`. -- Reduce the level from `error` to `debug` for the log line that is emitted when a `500 Internal Server Error` is built using `HttpResponse::from_error`. [#2201] -- `ResponseBuilder::message_body` now returns a `Result`. [#2201] -- Remove `Unpin` bound on `ResponseBuilder::streaming`. [#2253] -- `HttpServer::{listen_rustls(), bind_rustls()}` now honor the ALPN protocols in the configuation parameter. [#2226] +* The `MessageBody` trait now has an associated `Error` type. [#2183] +* All error trait bounds in server service builders have changed from `Into` to `Into>`. [#2253] +* All error trait bounds in message body and stream impls changed from `Into` to `Into>`. [#2253] +* Places in `Response` where `ResponseBody` was received or returned now simply use `B`. [#2201] +* `header` mod is now public. [#2171] +* `uri` mod is now public. [#2171] +* Update `language-tags` to `0.3`. +* Reduce the level from `error` to `debug` for the log line that is emitted when a `500 Internal Server Error` is built using `HttpResponse::from_error`. [#2201] +* `ResponseBuilder::message_body` now returns a `Result`. [#2201] +* Remove `Unpin` bound on `ResponseBuilder::streaming`. [#2253] +* `HttpServer::{listen_rustls(), bind_rustls()}` now honor the ALPN protocols in the configuation parameter. [#2226] ### Removed -- Stop re-exporting `http` crate's `HeaderMap` types in addition to ours. [#2171] -- Down-casting for `MessageBody` types. [#2183] -- `error::Result` alias. [#2201] -- Error field from `Response` and `Response::error`. [#2205] -- `impl Future` for `Response`. [#2201] -- `Response::take_body` and old `Response::into_body` method that casted body type. [#2201] -- `InternalError` and all the error types it constructed. [#2215] -- Conversion (`impl Into`) of `Response` and `ResponseBuilder` to `Error`. [#2215] +* Stop re-exporting `http` crate's `HeaderMap` types in addition to ours. [#2171] +* Down-casting for `MessageBody` types. [#2183] +* `error::Result` alias. [#2201] +* Error field from `Response` and `Response::error`. [#2205] +* `impl Future` for `Response`. [#2201] +* `Response::take_body` and old `Response::into_body` method that casted body type. [#2201] +* `InternalError` and all the error types it constructed. [#2215] +* Conversion (`impl Into`) of `Response` and `ResponseBuilder` to `Error`. [#2215] [#2171]: https://github.com/actix/actix-web/pull/2171 [#2183]: https://github.com/actix/actix-web/pull/2183 @@ -211,27 +211,27 @@ ## 3.0.0-beta.6 - 2021-04-17 ### Added -- `impl MessageBody for Pin>`. [#2152] -- `Response::{ok, bad_request, not_found, internal_server_error}`. [#2159] -- Helper `body::to_bytes` for async collecting message body into Bytes. [#2158] +* `impl MessageBody for Pin>`. [#2152] +* `Response::{ok, bad_request, not_found, internal_server_error}`. [#2159] +* Helper `body::to_bytes` for async collecting message body into Bytes. [#2158] ### Changes -- The type parameter of `Response` no longer has a default. [#2152] -- The `Message` variant of `body::Body` is now `Pin>`. [#2152] -- `BodyStream` and `SizedStream` are no longer restricted to Unpin types. [#2152] -- Error enum types are marked `#[non_exhaustive]`. [#2161] +* The type parameter of `Response` no longer has a default. [#2152] +* The `Message` variant of `body::Body` is now `Pin>`. [#2152] +* `BodyStream` and `SizedStream` are no longer restricted to Unpin types. [#2152] +* Error enum types are marked `#[non_exhaustive]`. [#2161] ### Removed -- `cookies` feature flag. [#2065] -- Top-level `cookies` mod (re-export). [#2065] -- `HttpMessage` trait loses the `cookies` and `cookie` methods. [#2065] -- `impl ResponseError for CookieParseError`. [#2065] -- Deprecated methods on `ResponseBuilder`: `if_true`, `if_some`. [#2148] -- `ResponseBuilder::json`. [#2148] -- `ResponseBuilder::{set_header, header}`. [#2148] -- `impl From for Body`. [#2148] -- `Response::build_from`. [#2159] -- Most of the status code builders on `Response`. [#2159] +* `cookies` feature flag. [#2065] +* Top-level `cookies` mod (re-export). [#2065] +* `HttpMessage` trait loses the `cookies` and `cookie` methods. [#2065] +* `impl ResponseError for CookieParseError`. [#2065] +* Deprecated methods on `ResponseBuilder`: `if_true`, `if_some`. [#2148] +* `ResponseBuilder::json`. [#2148] +* `ResponseBuilder::{set_header, header}`. [#2148] +* `impl From for Body`. [#2148] +* `Response::build_from`. [#2159] +* Most of the status code builders on `Response`. [#2159] [#2065]: https://github.com/actix/actix-web/pull/2065 [#2148]: https://github.com/actix/actix-web/pull/2148 @@ -243,16 +243,16 @@ ## 3.0.0-beta.5 - 2021-04-02 ### Added -- `client::Connector::handshake_timeout` method for customizing TLS connection handshake timeout. [#2081] -- `client::ConnectorService` as `client::Connector::finish` method's return type [#2081] -- `client::ConnectionIo` trait alias [#2081] +* `client::Connector::handshake_timeout` method for customizing TLS connection handshake timeout. [#2081] +* `client::ConnectorService` as `client::Connector::finish` method's return type [#2081] +* `client::ConnectionIo` trait alias [#2081] ### Changed -- `client::Connector` type now only have one generic type for `actix_service::Service`. [#2063] +* `client::Connector` type now only have one generic type for `actix_service::Service`. [#2063] ### Removed -- Common typed HTTP headers were moved to actix-web. [2094] -- `ResponseError` impl for `actix_utils::timeout::TimeoutError`. [#2127] +* Common typed HTTP headers were moved to actix-web. [2094] +* `ResponseError` impl for `actix_utils::timeout::TimeoutError`. [#2127] [#2063]: https://github.com/actix/actix-web/pull/2063 [#2081]: https://github.com/actix/actix-web/pull/2081 @@ -262,13 +262,13 @@ ## 3.0.0-beta.4 - 2021-03-08 ### Changed -- Feature `cookies` is now optional and disabled by default. [#1981] -- `ws::hash_key` now returns array. [#2035] -- `ResponseBuilder::json` now takes `impl Serialize`. [#2052] +* Feature `cookies` is now optional and disabled by default. [#1981] +* `ws::hash_key` now returns array. [#2035] +* `ResponseBuilder::json` now takes `impl Serialize`. [#2052] ### Removed -- Re-export of `futures_channel::oneshot::Canceled` is removed from `error` mod. [#1994] -- `ResponseError` impl for `futures_channel::oneshot::Canceled` is removed. [#1994] +* Re-export of `futures_channel::oneshot::Canceled` is removed from `error` mod. [#1994] +* `ResponseError` impl for `futures_channel::oneshot::Canceled` is removed. [#1994] [#1981]: https://github.com/actix/actix-web/pull/1981 [#1994]: https://github.com/actix/actix-web/pull/1994 @@ -277,48 +277,48 @@ ## 3.0.0-beta.3 - 2021-02-10 -- No notable changes. +* No notable changes. ## 3.0.0-beta.2 - 2021-02-10 ### Added -- `TryIntoHeaderPair` trait that allows using typed and untyped headers in the same methods. [#1869] -- `ResponseBuilder::insert_header` method which allows using typed headers. [#1869] -- `ResponseBuilder::append_header` method which allows using typed headers. [#1869] -- `TestRequest::insert_header` method which allows using typed headers. [#1869] -- `ContentEncoding` implements all necessary header traits. [#1912] -- `HeaderMap::len_keys` has the behavior of the old `len` method. [#1964] -- `HeaderMap::drain` as an efficient draining iterator. [#1964] -- Implement `IntoIterator` for owned `HeaderMap`. [#1964] -- `trust-dns` optional feature to enable `trust-dns-resolver` as client dns resolver. [#1969] +* `TryIntoHeaderPair` trait that allows using typed and untyped headers in the same methods. [#1869] +* `ResponseBuilder::insert_header` method which allows using typed headers. [#1869] +* `ResponseBuilder::append_header` method which allows using typed headers. [#1869] +* `TestRequest::insert_header` method which allows using typed headers. [#1869] +* `ContentEncoding` implements all necessary header traits. [#1912] +* `HeaderMap::len_keys` has the behavior of the old `len` method. [#1964] +* `HeaderMap::drain` as an efficient draining iterator. [#1964] +* Implement `IntoIterator` for owned `HeaderMap`. [#1964] +* `trust-dns` optional feature to enable `trust-dns-resolver` as client dns resolver. [#1969] ### Changed -- `ResponseBuilder::content_type` now takes an `impl TryIntoHeaderValue` to support using typed +* `ResponseBuilder::content_type` now takes an `impl TryIntoHeaderValue` to support using typed `mime` types. [#1894] -- Renamed `TryIntoHeaderValue::{try_into => try_into_value}` to avoid ambiguity with std +* Renamed `TryIntoHeaderValue::{try_into => try_into_value}` to avoid ambiguity with std `TryInto` trait. [#1894] -- `Extensions::insert` returns Option of replaced item. [#1904] -- Remove `HttpResponseBuilder::json2()`. [#1903] -- Enable `HttpResponseBuilder::json()` to receive data by value and reference. [#1903] -- `client::error::ConnectError` Resolver variant contains `Box` type. [#1905] -- `client::ConnectorConfig` default timeout changed to 5 seconds. [#1905] -- Simplify `BlockingError` type to a unit struct. It's now only triggered when blocking thread pool +* `Extensions::insert` returns Option of replaced item. [#1904] +* Remove `HttpResponseBuilder::json2()`. [#1903] +* Enable `HttpResponseBuilder::json()` to receive data by value and reference. [#1903] +* `client::error::ConnectError` Resolver variant contains `Box` type. [#1905] +* `client::ConnectorConfig` default timeout changed to 5 seconds. [#1905] +* Simplify `BlockingError` type to a unit struct. It's now only triggered when blocking thread pool is dead. [#1957] -- `HeaderMap::len` now returns number of values instead of number of keys. [#1964] -- `HeaderMap::insert` now returns iterator of removed values. [#1964] -- `HeaderMap::remove` now returns iterator of removed values. [#1964] +* `HeaderMap::len` now returns number of values instead of number of keys. [#1964] +* `HeaderMap::insert` now returns iterator of removed values. [#1964] +* `HeaderMap::remove` now returns iterator of removed values. [#1964] ### Removed -- `ResponseBuilder::set`; use `ResponseBuilder::insert_header`. [#1869] -- `ResponseBuilder::set_header`; use `ResponseBuilder::insert_header`. [#1869] -- `ResponseBuilder::header`; use `ResponseBuilder::append_header`. [#1869] -- `TestRequest::with_hdr`; use `TestRequest::default().insert_header()`. [#1869] -- `TestRequest::with_header`; use `TestRequest::default().insert_header()`. [#1869] -- `actors` optional feature. [#1969] -- `ResponseError` impl for `actix::MailboxError`. [#1969] +* `ResponseBuilder::set`; use `ResponseBuilder::insert_header`. [#1869] +* `ResponseBuilder::set_header`; use `ResponseBuilder::insert_header`. [#1869] +* `ResponseBuilder::header`; use `ResponseBuilder::append_header`. [#1869] +* `TestRequest::with_hdr`; use `TestRequest::default().insert_header()`. [#1869] +* `TestRequest::with_header`; use `TestRequest::default().insert_header()`. [#1869] +* `actors` optional feature. [#1969] +* `ResponseError` impl for `actix::MailboxError`. [#1969] ### Documentation -- Vastly improve docs and add examples for `HeaderMap`. [#1964] +* Vastly improve docs and add examples for `HeaderMap`. [#1964] [#1869]: https://github.com/actix/actix-web/pull/1869 [#1894]: https://github.com/actix/actix-web/pull/1894 @@ -333,24 +333,24 @@ ## 3.0.0-beta.1 - 2021-01-07 ### Added -- Add `Http3` to `Protocol` enum for future compatibility and also mark `#[non_exhaustive]`. +* Add `Http3` to `Protocol` enum for future compatibility and also mark `#[non_exhaustive]`. ### Changed -- Update `actix-*` dependencies to tokio `1.0` based versions. [#1813] -- Bumped `rand` to `0.8`. -- Update `bytes` to `1.0`. [#1813] -- Update `h2` to `0.3`. [#1813] -- The `ws::Message::Text` enum variant now contains a `bytestring::ByteString`. [#1864] +* Update `actix-*` dependencies to tokio `1.0` based versions. [#1813] +* Bumped `rand` to `0.8`. +* Update `bytes` to `1.0`. [#1813] +* Update `h2` to `0.3`. [#1813] +* The `ws::Message::Text` enum variant now contains a `bytestring::ByteString`. [#1864] ### Removed -- Deprecated `on_connect` methods have been removed. Prefer the new +* Deprecated `on_connect` methods have been removed. Prefer the new `on_connect_ext` technique. [#1857] -- Remove `ResponseError` impl for `actix::actors::resolver::ResolverError` +* Remove `ResponseError` impl for `actix::actors::resolver::ResolverError` due to deprecate of resolver actor. [#1813] -- Remove `ConnectError::SslHandshakeError` and re-export of `HandshakeError`. +* Remove `ConnectError::SslHandshakeError` and re-export of `HandshakeError`. due to the removal of this type from `tokio-openssl` crate. openssl handshake error would return as `ConnectError::SslError`. [#1813] -- Remove `actix-threadpool` dependency. Use `actix_rt::task::spawn_blocking`. +* Remove `actix-threadpool` dependency. Use `actix_rt::task::spawn_blocking`. Due to this change `actix_threadpool::BlockingError` type is moved into `actix_http::error` module. [#1878] @@ -362,20 +362,20 @@ ## 2.2.1 - 2021-08-09 ### Fixed -- Potential HTTP request smuggling vulnerabilities. [RUSTSEC-2021-0081](https://github.com/rustsec/advisory-db/pull/977) +* Potential HTTP request smuggling vulnerabilities. [RUSTSEC-2021-0081](https://github.com/rustsec/advisory-db/pull/977) ## 2.2.0 - 2020-11-25 ### Added -- HttpResponse builders for 1xx status codes. [#1768] -- `Accept::mime_precedence` and `Accept::mime_preference`. [#1793] -- `TryFrom` and `TryFrom` for `http::header::Quality`. [#1797] +* HttpResponse builders for 1xx status codes. [#1768] +* `Accept::mime_precedence` and `Accept::mime_preference`. [#1793] +* `TryFrom` and `TryFrom` for `http::header::Quality`. [#1797] ### Fixed -- Started dropping `transfer-encoding: chunked` and `Content-Length` for 1XX and 204 responses. [#1767] +* Started dropping `transfer-encoding: chunked` and `Content-Length` for 1XX and 204 responses. [#1767] ### Changed -- Upgrade `serde_urlencoded` to `0.7`. [#1773] +* Upgrade `serde_urlencoded` to `0.7`. [#1773] [#1773]: https://github.com/actix/actix-web/pull/1773 [#1767]: https://github.com/actix/actix-web/pull/1767 @@ -386,12 +386,12 @@ ## 2.1.0 - 2020-10-30 ### Added -- Added more flexible `on_connect_ext` methods for on-connect handling. [#1754] +* Added more flexible `on_connect_ext` methods for on-connect handling. [#1754] ### Changed -- Upgrade `base64` to `0.13`. [#1744] -- Upgrade `pin-project` to `1.0`. [#1733] -- Deprecate `ResponseBuilder::{if_some, if_true}`. [#1760] +* Upgrade `base64` to `0.13`. [#1744] +* Upgrade `pin-project` to `1.0`. [#1733] +* Deprecate `ResponseBuilder::{if_some, if_true}`. [#1760] [#1760]: https://github.com/actix/actix-web/pull/1760 [#1754]: https://github.com/actix/actix-web/pull/1754 @@ -400,28 +400,28 @@ ## 2.0.0 - 2020-09-11 -- No significant changes from `2.0.0-beta.4`. +* No significant changes from `2.0.0-beta.4`. ## 2.0.0-beta.4 - 2020-09-09 ### Changed -- Update actix-codec and actix-utils dependencies. -- Update actix-connect and actix-tls dependencies. +* Update actix-codec and actix-utils dependencies. +* Update actix-connect and actix-tls dependencies. ## 2.0.0-beta.3 - 2020-08-14 ### Fixed -- Memory leak of `client::pool::ConnectorPoolSupport`. [#1626] +* Memory leak of `client::pool::ConnectorPoolSupport`. [#1626] [#1626]: https://github.com/actix/actix-web/pull/1626 ## 2.0.0-beta.2 - 2020-07-21 ### Fixed -- Potential UB in h1 decoder using uninitialized memory. [#1614] +* Potential UB in h1 decoder using uninitialized memory. [#1614] ### Changed -- Fix illegal chunked encoding. [#1615] +* Fix illegal chunked encoding. [#1615] [#1614]: https://github.com/actix/actix-web/pull/1614 [#1615]: https://github.com/actix/actix-web/pull/1615 @@ -429,10 +429,10 @@ ## 2.0.0-beta.1 - 2020-07-11 ### Changed -- Migrate cookie handling to `cookie` crate. [#1558] -- Update `sha-1` to 0.9. [#1586] -- Fix leak in client pool. [#1580] -- MSRV is now 1.41.1. +* Migrate cookie handling to `cookie` crate. [#1558] +* Update `sha-1` to 0.9. [#1586] +* Fix leak in client pool. [#1580] +* MSRV is now 1.41.1. [#1558]: https://github.com/actix/actix-web/pull/1558 [#1586]: https://github.com/actix/actix-web/pull/1586 @@ -441,15 +441,15 @@ ## 2.0.0-alpha.4 - 2020-05-21 ### Changed -- Bump minimum supported Rust version to 1.40 -- content_length function is removed, and you can set Content-Length by calling +* Bump minimum supported Rust version to 1.40 +* content_length function is removed, and you can set Content-Length by calling no_chunking function [#1439] -- `BodySize::Sized64` variant has been removed. `BodySize::Sized` now receives a +* `BodySize::Sized64` variant has been removed. `BodySize::Sized` now receives a `u64` instead of a `usize`. -- Update `base64` dependency to 0.12 +* Update `base64` dependency to 0.12 ### Fixed -- Support parsing of `SameSite=None` [#1503] +* Support parsing of `SameSite=None` [#1503] [#1439]: https://github.com/actix/actix-web/pull/1439 [#1503]: https://github.com/actix/actix-web/pull/1503 @@ -457,13 +457,13 @@ ## 2.0.0-alpha.3 - 2020-05-08 ### Fixed -- Correct spelling of ConnectError::Unresolved [#1487] -- Fix a mistake in the encoding of websocket continuation messages wherein +* Correct spelling of ConnectError::Unresolved [#1487] +* Fix a mistake in the encoding of websocket continuation messages wherein Item::FirstText and Item::FirstBinary are each encoded as the other. ### Changed -- Implement `std::error::Error` for our custom errors [#1422] -- Remove `failure` support for `ResponseError` since that crate +* Implement `std::error::Error` for our custom errors [#1422] +* Remove `failure` support for `ResponseError` since that crate will be deprecated in the near future. [#1422]: https://github.com/actix/actix-web/pull/1422 @@ -472,12 +472,12 @@ ## 2.0.0-alpha.2 - 2020-03-07 ### Changed -- Update `actix-connect` and `actix-tls` dependency to 2.0.0-alpha.1. [#1395] -- Change default initial window size and connection window size for HTTP2 to 2MB and 1MB +* Update `actix-connect` and `actix-tls` dependency to 2.0.0-alpha.1. [#1395] +* Change default initial window size and connection window size for HTTP2 to 2MB and 1MB respectively to improve download speed for awc when downloading large objects. [#1394] -- client::Connector accepts initial_window_size and initial_connection_window_size +* client::Connector accepts initial_window_size and initial_connection_window_size HTTP2 configuration. [#1394] -- client::Connector allowing to set max_http_version to limit HTTP version to be used. [#1394] +* client::Connector allowing to set max_http_version to limit HTTP version to be used. [#1394] [#1394]: https://github.com/actix/actix-web/pull/1394 [#1395]: https://github.com/actix/actix-web/pull/1395 @@ -485,61 +485,61 @@ ## 2.0.0-alpha.1 - 2020-02-27 ### Changed -- Update the `time` dependency to 0.2.7. -- Moved actors messages support from actix crate, enabled with feature `actors`. -- Breaking change: trait MessageBody requires Unpin and accepting `Pin<&mut Self>` instead of +* Update the `time` dependency to 0.2.7. +* Moved actors messages support from actix crate, enabled with feature `actors`. +* Breaking change: trait MessageBody requires Unpin and accepting `Pin<&mut Self>` instead of `&mut self` in the poll_next(). -- MessageBody is not implemented for &'static [u8] anymore. +* MessageBody is not implemented for &'static [u8] anymore. ### Fixed -- Allow `SameSite=None` cookies to be sent in a response. +* Allow `SameSite=None` cookies to be sent in a response. ## 1.0.1 - 2019-12-20 ### Fixed -- Poll upgrade service's readiness from HTTP service handlers -- Replace brotli with brotli2 #1224 +* Poll upgrade service's readiness from HTTP service handlers +* Replace brotli with brotli2 #1224 ## 1.0.0 - 2019-12-13 ### Added -- Add websockets continuation frame support +* Add websockets continuation frame support ### Changed -- Replace `flate2-xxx` features with `compress` +* Replace `flate2-xxx` features with `compress` ## 1.0.0-alpha.5 - 2019-12-09 ### Fixed -- Check `Upgrade` service readiness before calling it -- Fix buffer remaining capacity calculation +* Check `Upgrade` service readiness before calling it +* Fix buffer remaining capacity calculation ### Changed -- Websockets: Ping and Pong should have binary data #1049 +* Websockets: Ping and Pong should have binary data #1049 ## 1.0.0-alpha.4 - 2019-12-08 ### Added -- Add impl ResponseBuilder for Error +* Add impl ResponseBuilder for Error ### Changed -- Use rust based brotli compression library +* Use rust based brotli compression library ## 1.0.0-alpha.3 - 2019-12-07 ### Changed -- Migrate to tokio 0.2 -- Migrate to `std::future` +* Migrate to tokio 0.2 +* Migrate to `std::future` ## 0.2.11 - 2019-11-06 ### Added -- Add support for serde_json::Value to be passed as argument to ResponseBuilder.body() -- Add an additional `filename*` param in the `Content-Disposition` header of +* Add support for serde_json::Value to be passed as argument to ResponseBuilder.body() +* Add an additional `filename*` param in the `Content-Disposition` header of `actix_files::NamedFile` to be more compatible. (#1151) -- Allow to use `std::convert::Infallible` as `actix_http::error::Error` +* Allow to use `std::convert::Infallible` as `actix_http::error::Error` ### Fixed -- To be compatible with non-English error responses, `ResponseError` rendered with `text/plain; +* To be compatible with non-English error responses, `ResponseError` rendered with `text/plain; charset=utf-8` header [#1118] [#1878]: https://github.com/actix/actix-web/pull/1878 @@ -547,169 +547,169 @@ ## 0.2.10 - 2019-09-11 ### Added -- Add support for sending HTTP requests with `Rc` in addition to sending HTTP requests +* Add support for sending HTTP requests with `Rc` in addition to sending HTTP requests with `RequestHead` ### Fixed -- h2 will use error response #1080 -- on_connect result isn't added to request extensions for http2 requests #1009 +* h2 will use error response #1080 +* on_connect result isn't added to request extensions for http2 requests #1009 ## 0.2.9 - 2019-08-13 ### Changed -- Dropped the `byteorder`-dependency in favor of `stdlib`-implementation -- Update percent-encoding to 2.1 -- Update serde_urlencoded to 0.6.1 +* Dropped the `byteorder`-dependency in favor of `stdlib`-implementation +* Update percent-encoding to 2.1 +* Update serde_urlencoded to 0.6.1 ### Fixed -- Fixed a panic in the HTTP2 handshake in client HTTP requests (#1031) +* Fixed a panic in the HTTP2 handshake in client HTTP requests (#1031) ## 0.2.8 - 2019-08-01 ### Added -- Add `rustls` support -- Add `Clone` impl for `HeaderMap` +* Add `rustls` support +* Add `Clone` impl for `HeaderMap` ### Fixed -- awc client panic #1016 -- Invalid response with compression middleware enabled, but compression-related features +* awc client panic #1016 +* Invalid response with compression middleware enabled, but compression-related features disabled #997 ## 0.2.7 - 2019-07-18 ### Added -- Add support for downcasting response errors #986 +* Add support for downcasting response errors #986 ## 0.2.6 - 2019-07-17 ### Changed -- Replace `ClonableService` with local copy -- Upgrade `rand` dependency version to 0.7 +* Replace `ClonableService` with local copy +* Upgrade `rand` dependency version to 0.7 ## 0.2.5 - 2019-06-28 ### Added -- Add `on-connect` callback, `HttpServiceBuilder::on_connect()` #946 +* Add `on-connect` callback, `HttpServiceBuilder::on_connect()` #946 ### Changed -- Use `encoding_rs` crate instead of unmaintained `encoding` crate -- Add `Copy` and `Clone` impls for `ws::Codec` +* Use `encoding_rs` crate instead of unmaintained `encoding` crate +* Add `Copy` and `Clone` impls for `ws::Codec` ## 0.2.4 - 2019-06-16 ### Fixed -- Do not compress NoContent (204) responses #918 +* Do not compress NoContent (204) responses #918 ## 0.2.3 - 2019-06-02 ### Added -- Debug impl for ResponseBuilder -- From SizedStream and BodyStream for Body +* Debug impl for ResponseBuilder +* From SizedStream and BodyStream for Body ### Changed -- SizedStream uses u64 +* SizedStream uses u64 ## 0.2.2 - 2019-05-29 ### Fixed -- Parse incoming stream before closing stream on disconnect #868 +* Parse incoming stream before closing stream on disconnect #868 ## 0.2.1 - 2019-05-25 ### Fixed -- Handle socket read disconnect +* Handle socket read disconnect ## 0.2.0 - 2019-05-12 ### Changed -- Update actix-service to 0.4 -- Expect and upgrade services accept `ServerConfig` config. +* Update actix-service to 0.4 +* Expect and upgrade services accept `ServerConfig` config. ### Deleted -- `OneRequest` service +* `OneRequest` service ## 0.1.5 - 2019-05-04 ### Fixed -- Clean up response extensions in response pool #817 +* Clean up response extensions in response pool #817 ## 0.1.4 - 2019-04-24 ### Added -- Allow to render h1 request headers in `Camel-Case` +* Allow to render h1 request headers in `Camel-Case` ### Fixed -- Read until eof for http/1.0 responses #771 +* Read until eof for http/1.0 responses #771 ## 0.1.3 - 2019-04-23 ### Fixed -- Fix http client pool management -- Fix http client wait queue management #794 +* Fix http client pool management +* Fix http client wait queue management #794 ## 0.1.2 - 2019-04-23 ### Fixed -- Fix BorrowMutError panic in client connector #793 +* Fix BorrowMutError panic in client connector #793 ## 0.1.1 - 2019-04-19 ### Changed -- Cookie::max_age() accepts value in seconds -- Cookie::max_age_time() accepts value in time::Duration -- Allow to specify server address for client connector +* Cookie::max_age() accepts value in seconds +* Cookie::max_age_time() accepts value in time::Duration +* Allow to specify server address for client connector ## 0.1.0 - 2019-04-16 ### Added -- Expose peer addr via `Request::peer_addr()` and `RequestHead::peer_addr` +* Expose peer addr via `Request::peer_addr()` and `RequestHead::peer_addr` ### Changed -- `actix_http::encoding` always available -- use trust-dns-resolver 0.11.0 +* `actix_http::encoding` always available +* use trust-dns-resolver 0.11.0 ## 0.1.0-alpha.5 - 2019-04-12 ### Added -- Allow to use custom service for upgrade requests -- Added `h1::SendResponse` future. +* Allow to use custom service for upgrade requests +* Added `h1::SendResponse` future. ### Changed -- MessageBody::length() renamed to MessageBody::size() for consistency -- ws handshake verification functions take RequestHead instead of Request +* MessageBody::length() renamed to MessageBody::size() for consistency +* ws handshake verification functions take RequestHead instead of Request ## 0.1.0-alpha.4 - 2019-04-08 ### Added -- Allow to use custom `Expect` handler -- Add minimal `std::error::Error` impl for `Error` +* Allow to use custom `Expect` handler +* Add minimal `std::error::Error` impl for `Error` ### Changed -- Export IntoHeaderValue -- Render error and return as response body -- Use thread pool for response body compression +* Export IntoHeaderValue +* Render error and return as response body +* Use thread pool for response body compression ### Deleted -- Removed PayloadBuffer +* Removed PayloadBuffer ## 0.1.0-alpha.3 - 2019-04-02 ### Added -- Warn when an unsealed private cookie isn't valid UTF-8 +* Warn when an unsealed private cookie isn't valid UTF-8 ### Fixed -- Rust 1.31.0 compatibility -- Preallocate read buffer for h1 codec -- Detect socket disconnection during protocol selection +* Rust 1.31.0 compatibility +* Preallocate read buffer for h1 codec +* Detect socket disconnection during protocol selection ## 0.1.0-alpha.2 - 2019-03-29 ### Added -- Added ws::Message::Nop, no-op websockets message +* Added ws::Message::Nop, no-op websockets message ### Changed -- Do not use thread pool for decompression if chunk size is smaller than 2048. +* Do not use thread pool for decompression if chunk size is smaller than 2048. ## 0.1.0-alpha.1 - 2019-03-28 -- Initial impl +* Initial impl diff --git a/actix-multipart/CHANGES.md b/actix-multipart/CHANGES.md index e58c3ee24..8d9c1640f 100644 --- a/actix-multipart/CHANGES.md +++ b/actix-multipart/CHANGES.md @@ -4,119 +4,119 @@ ## 0.4.0-beta.10 - 2021-12-11 -- No significant changes since `0.4.0-beta.9`. +* No significant changes since `0.4.0-beta.9`. ## 0.4.0-beta.9 - 2021-12-01 -- Polling `Field` after dropping `Multipart` now fails immediately instead of hanging forever. [#2463] +* Polling `Field` after dropping `Multipart` now fails immediately instead of hanging forever. [#2463] [#2463]: https://github.com/actix/actix-web/pull/2463 ## 0.4.0-beta.8 - 2021-11-22 -- Ensure a correct Content-Disposition header is included in every part of a multipart message. [#2451] -- Added `MultipartError::NoContentDisposition` variant. [#2451] -- Since Content-Disposition is now ensured, `Field::content_disposition` is now infallible. [#2451] -- Added `Field::name` method for getting the field name. [#2451] -- `MultipartError` now marks variants with inner errors as the source. [#2451] -- `MultipartError` is now marked as non-exhaustive. [#2451] +* Ensure a correct Content-Disposition header is included in every part of a multipart message. [#2451] +* Added `MultipartError::NoContentDisposition` variant. [#2451] +* Since Content-Disposition is now ensured, `Field::content_disposition` is now infallible. [#2451] +* Added `Field::name` method for getting the field name. [#2451] +* `MultipartError` now marks variants with inner errors as the source. [#2451] +* `MultipartError` is now marked as non-exhaustive. [#2451] [#2451]: https://github.com/actix/actix-web/pull/2451 ## 0.4.0-beta.7 - 2021-10-20 -- Minimum supported Rust version (MSRV) is now 1.52. +* Minimum supported Rust version (MSRV) is now 1.52. ## 0.4.0-beta.6 - 2021-09-09 -- Minimum supported Rust version (MSRV) is now 1.51. +* Minimum supported Rust version (MSRV) is now 1.51. ## 0.4.0-beta.5 - 2021-06-17 -- No notable changes. +* No notable changes. ## 0.4.0-beta.4 - 2021-04-02 -- No notable changes. +* No notable changes. ## 0.4.0-beta.3 - 2021-03-09 -- No notable changes. +* No notable changes. ## 0.4.0-beta.2 - 2021-02-10 -- No notable changes. +* No notable changes. ## 0.4.0-beta.1 - 2021-01-07 -- Fix multipart consuming payload before header checks. [#1513] -- Update `bytes` to `1.0`. [#1813] +* Fix multipart consuming payload before header checks. [#1513] +* Update `bytes` to `1.0`. [#1813] [#1813]: https://github.com/actix/actix-web/pull/1813 [#1513]: https://github.com/actix/actix-web/pull/1513 ## 0.3.0 - 2020-09-11 -- No significant changes from `0.3.0-beta.2`. +* No significant changes from `0.3.0-beta.2`. ## 0.3.0-beta.2 - 2020-09-10 -- Update `actix-*` dependencies to latest versions. +* Update `actix-*` dependencies to latest versions. ## 0.3.0-beta.1 - 2020-07-15 -- Update `actix-web` to 3.0.0-beta.1 +* Update `actix-web` to 3.0.0-beta.1 ## 0.3.0-alpha.1 - 2020-05-25 -- Update `actix-web` to 3.0.0-alpha.3 -- Bump minimum supported Rust version to 1.40 -- Minimize `futures` dependencies -- Remove the unused `time` dependency -- Fix missing `std::error::Error` implement for `MultipartError`. +* Update `actix-web` to 3.0.0-alpha.3 +* Bump minimum supported Rust version to 1.40 +* Minimize `futures` dependencies +* Remove the unused `time` dependency +* Fix missing `std::error::Error` implement for `MultipartError`. ## [0.2.0] - 2019-12-20 -- Release +* Release ## [0.2.0-alpha.4] - 2019-12-xx -- Multipart handling now handles Pending during read of boundary #1205 +* Multipart handling now handles Pending during read of boundary #1205 ## [0.2.0-alpha.2] - 2019-12-03 -- Migrate to `std::future` +* Migrate to `std::future` ## [0.1.4] - 2019-09-12 -- Multipart handling now parses requests which do not end in CRLF #1038 +* Multipart handling now parses requests which do not end in CRLF #1038 ## [0.1.3] - 2019-08-18 -- Fix ring dependency from actix-web default features for #741. +* Fix ring dependency from actix-web default features for #741. ## [0.1.2] - 2019-06-02 -- Fix boundary parsing #876 +* Fix boundary parsing #876 ## [0.1.1] - 2019-05-25 -- Fix disconnect handling #834 +* Fix disconnect handling #834 ## [0.1.0] - 2019-05-18 -- Release +* Release ## [0.1.0-beta.4] - 2019-05-12 -- Handle cancellation of uploads #736 +* Handle cancellation of uploads #736 -- Upgrade to actix-web 1.0.0-beta.4 +* Upgrade to actix-web 1.0.0-beta.4 ## [0.1.0-beta.1] - 2019-04-21 -- Do not support nested multipart +* Do not support nested multipart -- Split multipart support to separate crate +* Split multipart support to separate crate -- Optimize multipart handling #634, #769 +* Optimize multipart handling #634, #769 diff --git a/actix-router/CHANGES.md b/actix-router/CHANGES.md index 0a6a56359..d0ed55c88 100644 --- a/actix-router/CHANGES.md +++ b/actix-router/CHANGES.md @@ -4,20 +4,20 @@ ## 0.5.0-beta.3 - 2021-12-17 -- Minimum supported Rust version (MSRV) is now 1.52. +* Minimum supported Rust version (MSRV) is now 1.52. ## 0.5.0-beta.2 - 2021-09-09 -- Introduce `ResourceDef::join`. [#380] -- Disallow prefix routes with tail segments. [#379] -- Enforce path separators on dynamic prefixes. [#378] -- Improve malformed path error message. [#384] -- Prefix segments now always end with with a segment delimiter or end-of-input. [#2355] -- Prefix segments with trailing slashes define a trailing empty segment. [#2355] -- Support multi-pattern prefixes and joins. [#2356] -- `ResourceDef::pattern` now returns the first pattern in multi-pattern resources. [#2356] -- Support `build_resource_path` on multi-pattern resources. [#2356] -- Minimum supported Rust version (MSRV) is now 1.51. +* Introduce `ResourceDef::join`. [#380] +* Disallow prefix routes with tail segments. [#379] +* Enforce path separators on dynamic prefixes. [#378] +* Improve malformed path error message. [#384] +* Prefix segments now always end with with a segment delimiter or end-of-input. [#2355] +* Prefix segments with trailing slashes define a trailing empty segment. [#2355] +* Support multi-pattern prefixes and joins. [#2356] +* `ResourceDef::pattern` now returns the first pattern in multi-pattern resources. [#2356] +* Support `build_resource_path` on multi-pattern resources. [#2356] +* Minimum supported Rust version (MSRV) is now 1.51. [#378]: https://github.com/actix/actix-net/pull/378 [#379]: https://github.com/actix/actix-net/pull/379 @@ -28,23 +28,23 @@ ## 0.5.0-beta.1 - 2021-07-20 -- Fix a bug in multi-patterns where static patterns are interpreted as regex. [#366] -- Introduce `ResourceDef::pattern_iter` to get an iterator over all patterns in a multi-pattern resource. [#373] -- Fix segment interpolation leaving `Path` in unintended state after matching. [#368] -- Fix `ResourceDef` `PartialEq` implementation. [#373] -- Re-work `IntoPatterns` trait, adding a `Patterns` enum. [#372] -- Implement `IntoPatterns` for `bytestring::ByteString`. [#372] -- Rename `Path::{len => segment_count}` to be more descriptive of it's purpose. [#370] -- Rename `ResourceDef::{resource_path => resource_path_from_iter}`. [#371] -- `ResourceDef::resource_path_from_iter` now takes an `IntoIterator`. [#373] -- Rename `ResourceDef::{resource_path_named => resource_path_from_map}`. [#371] -- Rename `ResourceDef::{is_prefix_match => find_match}`. [#373] -- Rename `ResourceDef::{match_path => capture_match_info}`. [#373] -- Rename `ResourceDef::{match_path_checked => capture_match_info_fn}`. [#373] -- Remove `ResourceDef::name_mut` and introduce `ResourceDef::set_name`. [#373] -- Rename `Router::{*_checked => *_fn}`. [#373] -- Return type of `ResourceDef::name` is now `Option<&str>`. [#373] -- Return type of `ResourceDef::pattern` is now `Option<&str>`. [#373] +* Fix a bug in multi-patterns where static patterns are interpreted as regex. [#366] +* Introduce `ResourceDef::pattern_iter` to get an iterator over all patterns in a multi-pattern resource. [#373] +* Fix segment interpolation leaving `Path` in unintended state after matching. [#368] +* Fix `ResourceDef` `PartialEq` implementation. [#373] +* Re-work `IntoPatterns` trait, adding a `Patterns` enum. [#372] +* Implement `IntoPatterns` for `bytestring::ByteString`. [#372] +* Rename `Path::{len => segment_count}` to be more descriptive of it's purpose. [#370] +* Rename `ResourceDef::{resource_path => resource_path_from_iter}`. [#371] +* `ResourceDef::resource_path_from_iter` now takes an `IntoIterator`. [#373] +* Rename `ResourceDef::{resource_path_named => resource_path_from_map}`. [#371] +* Rename `ResourceDef::{is_prefix_match => find_match}`. [#373] +* Rename `ResourceDef::{match_path => capture_match_info}`. [#373] +* Rename `ResourceDef::{match_path_checked => capture_match_info_fn}`. [#373] +* Remove `ResourceDef::name_mut` and introduce `ResourceDef::set_name`. [#373] +* Rename `Router::{*_checked => *_fn}`. [#373] +* Return type of `ResourceDef::name` is now `Option<&str>`. [#373] +* Return type of `ResourceDef::pattern` is now `Option<&str>`. [#373] [#368]: https://github.com/actix/actix-net/pull/368 [#366]: https://github.com/actix/actix-net/pull/366 @@ -56,10 +56,10 @@ ## 0.4.0 - 2021-06-06 -- When matching path parameters, `%25` is now kept in the percent-encoded form; no longer decoded to `%`. [#357] -- Path tail patterns now match new lines (`\n`) in request URL. [#360] -- Fixed a safety bug where `Path` could return a malformed string after percent decoding. [#359] -- Methods `Path::{add, add_static}` now take `impl Into>`. [#345] +* When matching path parameters, `%25` is now kept in the percent-encoded form; no longer decoded to `%`. [#357] +* Path tail patterns now match new lines (`\n`) in request URL. [#360] +* Fixed a safety bug where `Path` could return a malformed string after percent decoding. [#359] +* Methods `Path::{add, add_static}` now take `impl Into>`. [#345] [#345]: https://github.com/actix/actix-net/pull/345 [#357]: https://github.com/actix/actix-net/pull/357 @@ -68,68 +68,68 @@ ## 0.3.0 - 2019-12-31 -- Version was yanked previously. See https://crates.io/crates/actix-router/0.3.0 +* Version was yanked previously. See https://crates.io/crates/actix-router/0.3.0 ## 0.2.7 - 2021-02-06 -- Add `Router::recognize_checked` [#247] +* Add `Router::recognize_checked` [#247] [#247]: https://github.com/actix/actix-net/pull/247 ## 0.2.6 - 2021-01-09 -- Use `bytestring` version range compatible with Bytes v1.0. [#246] +* Use `bytestring` version range compatible with Bytes v1.0. [#246] [#246]: https://github.com/actix/actix-net/pull/246 ## 0.2.5 - 2020-09-20 -- Fix `from_hex()` method +* Fix `from_hex()` method ## 0.2.4 - 2019-12-31 -- Add `ResourceDef::resource_path_named()` path generation method +* Add `ResourceDef::resource_path_named()` path generation method ## 0.2.3 - 2019-12-25 -- Add impl `IntoPattern` for `&String` +* Add impl `IntoPattern` for `&String` ## 0.2.2 - 2019-12-25 -- Use `IntoPattern` for `RouterBuilder::path()` +* Use `IntoPattern` for `RouterBuilder::path()` ## 0.2.1 - 2019-12-25 -- Add `IntoPattern` trait -- Add multi-pattern resources +* Add `IntoPattern` trait +* Add multi-pattern resources ## 0.2.0 - 2019-12-07 -- Update http to 0.2 -- Update regex to 1.3 -- Use bytestring instead of string +* Update http to 0.2 +* Update regex to 1.3 +* Use bytestring instead of string ## 0.1.5 - 2019-05-15 -- Remove debug prints +* Remove debug prints ## 0.1.4 - 2019-05-15 -- Fix checked resource match +* Fix checked resource match ## 0.1.3 - 2019-04-22 -- Added support for `remainder match` (i.e "/path/{tail}*") +* Added support for `remainder match` (i.e "/path/{tail}*") ## 0.1.2 - 2019-04-07 -- Export `Quoter` type -- Allow to reset `Path` instance +* Export `Quoter` type +* Allow to reset `Path` instance ## 0.1.1 - 2019-04-03 -- Get dynamic segment by name instead of iterator. +* Get dynamic segment by name instead of iterator. ## 0.1.0 - 2019-03-09 -- Initial release +* Initial release diff --git a/actix-test/CHANGES.md b/actix-test/CHANGES.md index e3deeb3f4..ef78ac54a 100644 --- a/actix-test/CHANGES.md +++ b/actix-test/CHANGES.md @@ -4,46 +4,46 @@ ## 0.1.0-beta.9 - 2021-12-17 -- Re-export `actix_http::body::to_bytes`. [#2518] -- Update `actix_web::test` re-exports. [#2518] +* Re-export `actix_http::body::to_bytes`. [#2518] +* Update `actix_web::test` re-exports. [#2518] [#2518]: https://github.com/actix/actix-web/pull/2518 ## 0.1.0-beta.8 - 2021-12-11 -- No significant changes since `0.1.0-beta.7`. +* No significant changes since `0.1.0-beta.7`. ## 0.1.0-beta.7 - 2021-11-22 -- Fix compatibility with experimental `io-uring` feature of `actix-rt`. [#2408] +* Fix compatibility with experimental `io-uring` feature of `actix-rt`. [#2408] [#2408]: https://github.com/actix/actix-web/pull/2408 ## 0.1.0-beta.6 - 2021-11-15 -- No significant changes from `0.1.0-beta.5`. +* No significant changes from `0.1.0-beta.5`. ## 0.1.0-beta.5 - 2021-10-20 -- Updated rustls to v0.20. [#2414] -- Minimum supported Rust version (MSRV) is now 1.52. +* Updated rustls to v0.20. [#2414] +* Minimum supported Rust version (MSRV) is now 1.52. [#2414]: https://github.com/actix/actix-web/pull/2414 ## 0.1.0-beta.4 - 2021-09-09 -- Minimum supported Rust version (MSRV) is now 1.51. +* Minimum supported Rust version (MSRV) is now 1.51. ## 0.1.0-beta.3 - 2021-06-20 -- No significant changes from `0.1.0-beta.2`. +* No significant changes from `0.1.0-beta.2`. ## 0.1.0-beta.2 - 2021-04-17 -- No significant changes from `0.1.0-beta.1`. +* No significant changes from `0.1.0-beta.1`. ## 0.1.0-beta.1 - 2021-04-02 -- Move integration testing structs from `actix-web`. [#2112] +* Move integration testing structs from `actix-web`. [#2112] [#2112]: https://github.com/actix/actix-web/pull/2112 diff --git a/actix-web-actors/CHANGES.md b/actix-web-actors/CHANGES.md index 6abfe2c61..d3078499c 100644 --- a/actix-web-actors/CHANGES.md +++ b/actix-web-actors/CHANGES.md @@ -4,105 +4,105 @@ ## 4.0.0-beta.8 - 2021-12-11 -- Add `ws:WsResponseBuilder` for building WebSocket session response. [#1920] -- Deprecate `ws::{start_with_addr, start_with_protocols}`. [#1920] -- Minimum supported Rust version (MSRV) is now 1.52. +* Add `ws:WsResponseBuilder` for building WebSocket session response. [#1920] +* Deprecate `ws::{start_with_addr, start_with_protocols}`. [#1920] +* Minimum supported Rust version (MSRV) is now 1.52. [#1920]: https://github.com/actix/actix-web/pull/1920 ## 4.0.0-beta.7 - 2021-09-09 -- Minimum supported Rust version (MSRV) is now 1.51. +* Minimum supported Rust version (MSRV) is now 1.51. ## 4.0.0-beta.6 - 2021-06-26 -- Update `actix` to `0.12`. [#2277] +* Update `actix` to `0.12`. [#2277] [#2277]: https://github.com/actix/actix-web/pull/2277 ## 4.0.0-beta.5 - 2021-06-17 -- No notable changes. +* No notable changes. ## 4.0.0-beta.4 - 2021-04-02 -- No notable changes. +* No notable changes. ## 4.0.0-beta.3 - 2021-03-09 -- No notable changes. +* No notable changes. ## 4.0.0-beta.2 - 2021-02-10 -- No notable changes. +* No notable changes. ## 4.0.0-beta.1 - 2021-01-07 -- Update `pin-project` to `1.0`. -- Update `bytes` to `1.0`. [#1813] -- `WebsocketContext::text` now takes an `Into`. [#1864] +* Update `pin-project` to `1.0`. +* Update `bytes` to `1.0`. [#1813] +* `WebsocketContext::text` now takes an `Into`. [#1864] [#1813]: https://github.com/actix/actix-web/pull/1813 [#1864]: https://github.com/actix/actix-web/pull/1864 ## 3.0.0 - 2020-09-11 -- No significant changes from `3.0.0-beta.2`. +* No significant changes from `3.0.0-beta.2`. ## 3.0.0-beta.2 - 2020-09-10 -- Update `actix-*` dependencies to latest versions. +* Update `actix-*` dependencies to latest versions. ## [3.0.0-beta.1] - 2020-xx-xx -- Update `actix-web` & `actix-http` dependencies to beta.1 -- Bump minimum supported Rust version to 1.40 +* Update `actix-web` & `actix-http` dependencies to beta.1 +* Bump minimum supported Rust version to 1.40 ## [3.0.0-alpha.1] - 2020-05-08 -- Update the actix-web dependency to 3.0.0-alpha.1 -- Update the actix dependency to 0.10.0-alpha.2 -- Update the actix-http dependency to 2.0.0-alpha.3 +* Update the actix-web dependency to 3.0.0-alpha.1 +* Update the actix dependency to 0.10.0-alpha.2 +* Update the actix-http dependency to 2.0.0-alpha.3 ## [2.0.0] - 2019-12-20 -- Release +* Release ## [2.0.0-alpha.1] - 2019-12-15 -- Migrate to actix-web 2.0.0 +* Migrate to actix-web 2.0.0 ## [1.0.4] - 2019-12-07 -- Allow comma-separated websocket subprotocols without spaces (#1172) +* Allow comma-separated websocket subprotocols without spaces (#1172) ## [1.0.3] - 2019-11-14 -- Update actix-web and actix-http dependencies +* Update actix-web and actix-http dependencies ## [1.0.2] - 2019-07-20 -- Add `ws::start_with_addr()`, returning the address of the created actor, along +* Add `ws::start_with_addr()`, returning the address of the created actor, along with the `HttpResponse`. -- Add support for specifying protocols on websocket handshake #835 +* Add support for specifying protocols on websocket handshake #835 ## [1.0.1] - 2019-06-28 -- Allow to use custom ws codec with `WebsocketContext` #925 +* Allow to use custom ws codec with `WebsocketContext` #925 ## [1.0.0] - 2019-05-29 -- Update actix-http and actix-web +* Update actix-http and actix-web ## [0.1.0-alpha.3] - 2019-04-02 -- Update actix-http and actix-web +* Update actix-http and actix-web ## [0.1.0-alpha.2] - 2019-03-29 -- Update actix-http and actix-web +* Update actix-http and actix-web ## [0.1.0-alpha.1] - 2019-03-28 -- Initial impl +* Initial impl diff --git a/actix-web-codegen/CHANGES.md b/actix-web-codegen/CHANGES.md index 0d881d303..309274563 100644 --- a/actix-web-codegen/CHANGES.md +++ b/actix-web-codegen/CHANGES.md @@ -4,101 +4,101 @@ ## 0.5.0-beta.6 - 2021-12-11 -- No significant changes since `0.5.0-beta.5`. +* No significant changes since `0.5.0-beta.5`. ## 0.5.0-beta.5 - 2021-10-20 -- Improve error recovery potential when macro input is invalid. [#2410] -- Add `#[actix_web::test]` macro for setting up tests with a runtime. [#2409] -- Minimum supported Rust version (MSRV) is now 1.52. +* Improve error recovery potential when macro input is invalid. [#2410] +* Add `#[actix_web::test]` macro for setting up tests with a runtime. [#2409] +* Minimum supported Rust version (MSRV) is now 1.52. [#2410]: https://github.com/actix/actix-web/pull/2410 [#2409]: https://github.com/actix/actix-web/pull/2409 ## 0.5.0-beta.4 - 2021-09-09 -- In routing macros, paths are now validated at compile time. [#2350] -- Minimum supported Rust version (MSRV) is now 1.51. +* In routing macros, paths are now validated at compile time. [#2350] +* Minimum supported Rust version (MSRV) is now 1.51. [#2350]: https://github.com/actix/actix-web/pull/2350 ## 0.5.0-beta.3 - 2021-06-17 -- No notable changes. +* No notable changes. ## 0.5.0-beta.2 - 2021-03-09 -- Preserve doc comments when using route macros. [#2022] -- Add `name` attribute to `route` macro. [#1934] +* Preserve doc comments when using route macros. [#2022] +* Add `name` attribute to `route` macro. [#1934] [#2022]: https://github.com/actix/actix-web/pull/2022 [#1934]: https://github.com/actix/actix-web/pull/1934 ## 0.5.0-beta.1 - 2021-02-10 -- Use new call signature for `System::new`. +* Use new call signature for `System::new`. ## 0.4.0 - 2020-09-20 -- Added compile success and failure testing. [#1677] -- Add `route` macro for supporting multiple HTTP methods guards. [#1674] +* Added compile success and failure testing. [#1677] +* Add `route` macro for supporting multiple HTTP methods guards. [#1674] [#1677]: https://github.com/actix/actix-web/pull/1677 [#1674]: https://github.com/actix/actix-web/pull/1674 ## 0.3.0 - 2020-09-11 -- No significant changes from `0.3.0-beta.1`. +* No significant changes from `0.3.0-beta.1`. ## 0.3.0-beta.1 - 2020-07-14 -- Add main entry-point macro that uses re-exported runtime. [#1559] +* Add main entry-point macro that uses re-exported runtime. [#1559] [#1559]: https://github.com/actix/actix-web/pull/1559 ## 0.2.2 - 2020-05-23 -- Add resource middleware on actix-web-codegen [#1467] +* Add resource middleware on actix-web-codegen [#1467] [#1467]: https://github.com/actix/actix-web/pull/1467 ## 0.2.1 - 2020-02-25 -- Add `#[allow(missing_docs)]` attribute to generated structs [#1368] -- Allow the handler function to be named as `config` [#1290] +* Add `#[allow(missing_docs)]` attribute to generated structs [#1368] +* Allow the handler function to be named as `config` [#1290] [#1368]: https://github.com/actix/actix-web/issues/1368 [#1290]: https://github.com/actix/actix-web/issues/1290 ## 0.2.0 - 2019-12-13 -- Generate code for actix-web 2.0 +* Generate code for actix-web 2.0 ## 0.1.3 - 2019-10-14 -- Bump up `syn` & `quote` to 1.0 -- Provide better error message +* Bump up `syn` & `quote` to 1.0 +* Provide better error message ## 0.1.2 - 2019-06-04 -- Add macros for head, options, trace, connect and patch http methods +* Add macros for head, options, trace, connect and patch http methods ## 0.1.1 - 2019-06-01 -- Add syn "extra-traits" feature +* Add syn "extra-traits" feature ## 0.1.0 - 2019-05-18 -- Release +* Release ## 0.1.0-beta.1 - 2019-04-20 -- Gen code for actix-web 1.0.0-beta.1 +* Gen code for actix-web 1.0.0-beta.1 ## 0.1.0-alpha.6 - 2019-04-14 -- Gen code for actix-web 1.0.0-alpha.6 +* Gen code for actix-web 1.0.0-alpha.6 ## 0.1.0-alpha.1 - 2019-03-28 -- Initial impl +* Initial impl diff --git a/awc/CHANGES.md b/awc/CHANGES.md index b5144b7a2..7b822930c 100644 --- a/awc/CHANGES.md +++ b/awc/CHANGES.md @@ -1,75 +1,75 @@ # Changes ## Unreleased - 2021-xx-xx -- Rename `Connector::{ssl => openssl}`. [#2503] -- Improve `Client` instantiation efficiency when using `openssl` by only building connectors once. [#2503] +* Rename `Connector::{ssl => openssl}`. [#2503] +* Improve `Client` instantiation efficiency when using `openssl` by only building connectors once. [#2503] [#2503]: https://github.com/actix/actix-web/pull/2503 ## 3.0.0-beta.14 - 2021-12-17 -- Add `ClientBuilder::add_default_header` and deprecate `ClientBuilder::header`. [#2510] +* Add `ClientBuilder::add_default_header` and deprecate `ClientBuilder::header`. [#2510] [#2510]: https://github.com/actix/actix-web/pull/2510 ## 3.0.0-beta.13 - 2021-12-11 -- No significant changes since `3.0.0-beta.12`. +* No significant changes since `3.0.0-beta.12`. ## 3.0.0-beta.12 - 2021-11-30 -- Update `actix-tls` to `3.0.0-rc.1`. [#2474] +* Update `actix-tls` to `3.0.0-rc.1`. [#2474] [#2474]: https://github.com/actix/actix-web/pull/2474 ## 3.0.0-beta.11 - 2021-11-22 -- No significant changes from `3.0.0-beta.10`. +* No significant changes from `3.0.0-beta.10`. ## 3.0.0-beta.10 - 2021-11-15 -- No significant changes from `3.0.0-beta.9`. +* No significant changes from `3.0.0-beta.9`. ## 3.0.0-beta.9 - 2021-10-20 -- Updated rustls to v0.20. [#2414] +* Updated rustls to v0.20. [#2414] [#2414]: https://github.com/actix/actix-web/pull/2414 ## 3.0.0-beta.8 - 2021-09-09 ### Changed -- Send headers within the redirect requests. [#2310] +* Send headers within the redirect requests. [#2310] [#2310]: https://github.com/actix/actix-web/pull/2310 ## 3.0.0-beta.7 - 2021-06-26 ### Changed -- Change compression algorithm features flags. [#2250] +* Change compression algorithm features flags. [#2250] [#2250]: https://github.com/actix/actix-web/pull/2250 ## 3.0.0-beta.6 - 2021-06-17 -- No significant changes since 3.0.0-beta.5. +* No significant changes since 3.0.0-beta.5. ## 3.0.0-beta.5 - 2021-04-17 ### Removed -- Deprecated methods on `ClientRequest`: `if_true`, `if_some`. [#2148] +* Deprecated methods on `ClientRequest`: `if_true`, `if_some`. [#2148] [#2148]: https://github.com/actix/actix-web/pull/2148 ## 3.0.0-beta.4 - 2021-04-02 ### Added -- Add `Client::headers` to get default mut reference of `HeaderMap` of client object. [#2114] +* Add `Client::headers` to get default mut reference of `HeaderMap` of client object. [#2114] ### Changed -- `ConnectorService` type is renamed to `BoxConnectorService`. [#2081] -- Fix http/https encoding when enabling `compress` feature. [#2116] -- Rename `TestResponse::header` to `append_header`, `set` to `insert_header`. `TestResponse` header +* `ConnectorService` type is renamed to `BoxConnectorService`. [#2081] +* Fix http/https encoding when enabling `compress` feature. [#2116] +* Rename `TestResponse::header` to `append_header`, `set` to `insert_header`. `TestResponse` header methods now take `TryIntoHeaderPair` tuples. [#2094] [#2081]: https://github.com/actix/actix-web/pull/2081 @@ -80,16 +80,16 @@ ## 3.0.0-beta.3 - 2021-03-08 ### Added -- `ClientResponse::timeout` for set the timeout of collecting response body. [#1931] -- `ClientBuilder::local_address` for bind to a local ip address for this client. [#2024] +* `ClientResponse::timeout` for set the timeout of collecting response body. [#1931] +* `ClientBuilder::local_address` for bind to a local ip address for this client. [#2024] ### Changed -- Feature `cookies` is now optional and enabled by default. [#1981] -- `ClientBuilder::connector` method would take `actix_http::client::Connector` type. [#2008] -- Basic auth password now takes blank passwords as an empty string instead of Option. [#2050] +* Feature `cookies` is now optional and enabled by default. [#1981] +* `ClientBuilder::connector` method would take `actix_http::client::Connector` type. [#2008] +* Basic auth password now takes blank passwords as an empty string instead of Option. [#2050] ### Removed -- `ClientBuilder::default` function [#2008] +* `ClientBuilder::default` function [#2008] [#1931]: https://github.com/actix/actix-web/pull/1931 [#1981]: https://github.com/actix/actix-web/pull/1981 @@ -100,18 +100,18 @@ ## 3.0.0-beta.2 - 2021-02-10 ### Added -- `ClientRequest::insert_header` method which allows using typed headers. [#1869] -- `ClientRequest::append_header` method which allows using typed headers. [#1869] -- `trust-dns` optional feature to enable `trust-dns-resolver` as client dns resolver. [#1969] +* `ClientRequest::insert_header` method which allows using typed headers. [#1869] +* `ClientRequest::append_header` method which allows using typed headers. [#1869] +* `trust-dns` optional feature to enable `trust-dns-resolver` as client dns resolver. [#1969] ### Changed -- Relax default timeout for `Connector` to 5 seconds(original 1 second). [#1905] +* Relax default timeout for `Connector` to 5 seconds(original 1 second). [#1905] ### Removed -- `ClientRequest::set`; use `ClientRequest::insert_header`. [#1869] -- `ClientRequest::set_header`; use `ClientRequest::insert_header`. [#1869] -- `ClientRequest::set_header_if_none`; use `ClientRequest::insert_header_if_none`. [#1869] -- `ClientRequest::header`; use `ClientRequest::append_header`. [#1869] +* `ClientRequest::set`; use `ClientRequest::insert_header`. [#1869] +* `ClientRequest::set_header`; use `ClientRequest::insert_header`. [#1869] +* `ClientRequest::set_header_if_none`; use `ClientRequest::insert_header_if_none`. [#1869] +* `ClientRequest::header`; use `ClientRequest::append_header`. [#1869] [#1869]: https://github.com/actix/actix-web/pull/1869 [#1905]: https://github.com/actix/actix-web/pull/1905 @@ -120,32 +120,32 @@ ## 3.0.0-beta.1 - 2021-01-07 ### Changed -- Update `rand` to `0.8` -- Update `bytes` to `1.0`. [#1813] -- Update `rust-tls` to `0.19`. [#1813] +* Update `rand` to `0.8` +* Update `bytes` to `1.0`. [#1813] +* Update `rust-tls` to `0.19`. [#1813] [#1813]: https://github.com/actix/actix-web/pull/1813 ## 2.0.3 - 2020-11-29 ### Fixed -- Ensure `actix-http` dependency uses same `serde_urlencoded`. +* Ensure `actix-http` dependency uses same `serde_urlencoded`. ## 2.0.2 - 2020-11-25 ### Changed -- Upgrade `serde_urlencoded` to `0.7`. [#1773] +* Upgrade `serde_urlencoded` to `0.7`. [#1773] [#1773]: https://github.com/actix/actix-web/pull/1773 ## 2.0.1 - 2020-10-30 ### Changed -- Upgrade `base64` to `0.13`. [#1744] -- Deprecate `ClientRequest::{if_some, if_true}`. [#1760] +* Upgrade `base64` to `0.13`. [#1744] +* Deprecate `ClientRequest::{if_some, if_true}`. [#1760] ### Fixed -- Use `Accept-Encoding: identity` instead of `Accept-Encoding: br` when no compression feature +* Use `Accept-Encoding: identity` instead of `Accept-Encoding: br` when no compression feature is enabled [#1737] [#1737]: https://github.com/actix/actix-web/pull/1737 @@ -155,209 +155,209 @@ ## 2.0.0 - 2020-09-11 ### Changed -- `Client::build` was renamed to `Client::builder`. +* `Client::build` was renamed to `Client::builder`. ## 2.0.0-beta.4 - 2020-09-09 ### Changed -- Update actix-codec & actix-tls dependencies. +* Update actix-codec & actix-tls dependencies. ## 2.0.0-beta.3 - 2020-08-17 ### Changed -- Update `rustls` to 0.18 +* Update `rustls` to 0.18 ## 2.0.0-beta.2 - 2020-07-21 ### Changed -- Update `actix-http` dependency to 2.0.0-beta.2 +* Update `actix-http` dependency to 2.0.0-beta.2 ## [2.0.0-beta.1] - 2020-07-14 ### Changed -- Update `actix-http` dependency to 2.0.0-beta.1 +* Update `actix-http` dependency to 2.0.0-beta.1 ## [2.0.0-alpha.2] - 2020-05-21 ### Changed -- Implement `std::error::Error` for our custom errors [#1422] -- Bump minimum supported Rust version to 1.40 -- Update `base64` dependency to 0.12 +* Implement `std::error::Error` for our custom errors [#1422] +* Bump minimum supported Rust version to 1.40 +* Update `base64` dependency to 0.12 [#1422]: https://github.com/actix/actix-web/pull/1422 ## [2.0.0-alpha.1] - 2020-03-11 -- Update `actix-http` dependency to 2.0.0-alpha.2 -- Update `rustls` dependency to 0.17 -- ClientBuilder accepts initial_window_size and initial_connection_window_size HTTP2 configuration -- ClientBuilder allowing to set max_http_version to limit HTTP version to be used +* Update `actix-http` dependency to 2.0.0-alpha.2 +* Update `rustls` dependency to 0.17 +* ClientBuilder accepts initial_window_size and initial_connection_window_size HTTP2 configuration +* ClientBuilder allowing to set max_http_version to limit HTTP version to be used ## [1.0.1] - 2019-12-15 -- Fix compilation with default features off +* Fix compilation with default features off ## [1.0.0] - 2019-12-13 -- Release +* Release ## [1.0.0-alpha.3] -- Migrate to `std::future` +* Migrate to `std::future` ## [0.2.8] - 2019-11-06 -- Add support for setting query from Serialize type for client request. +* Add support for setting query from Serialize type for client request. ## [0.2.7] - 2019-09-25 ### Added -- Remaining getter methods for `ClientRequest`'s private `head` field #1101 +* Remaining getter methods for `ClientRequest`'s private `head` field #1101 ## [0.2.6] - 2019-09-12 ### Added -- Export frozen request related types. +* Export frozen request related types. ## [0.2.5] - 2019-09-11 ### Added -- Add `FrozenClientRequest` to support retries for sending HTTP requests +* Add `FrozenClientRequest` to support retries for sending HTTP requests ### Changed -- Ensure that the `Host` header is set when initiating a WebSocket client connection. +* Ensure that the `Host` header is set when initiating a WebSocket client connection. ## [0.2.4] - 2019-08-13 ### Changed -- Update percent-encoding to "2.1" +* Update percent-encoding to "2.1" -- Update serde_urlencoded to "0.6.1" +* Update serde_urlencoded to "0.6.1" ## [0.2.3] - 2019-08-01 ### Added -- Add `rustls` support +* Add `rustls` support ## [0.2.2] - 2019-07-01 ### Changed -- Always append a colon after username in basic auth +* Always append a colon after username in basic auth -- Upgrade `rand` dependency version to 0.7 +* Upgrade `rand` dependency version to 0.7 ## [0.2.1] - 2019-06-05 ### Added -- Add license files +* Add license files ## [0.2.0] - 2019-05-12 ### Added -- Allow to send headers in `Camel-Case` form. +* Allow to send headers in `Camel-Case` form. ### Changed -- Upgrade actix-http dependency. +* Upgrade actix-http dependency. ## [0.1.1] - 2019-04-19 ### Added -- Allow to specify server address for http and ws requests. +* Allow to specify server address for http and ws requests. ### Changed -- `ClientRequest::if_true()` and `ClientRequest::if_some()` use instance instead of ref +* `ClientRequest::if_true()` and `ClientRequest::if_some()` use instance instead of ref ## [0.1.0] - 2019-04-16 -- No changes +* No changes ## [0.1.0-alpha.6] - 2019-04-14 ### Changed -- Do not set default headers for websocket request +* Do not set default headers for websocket request ## [0.1.0-alpha.5] - 2019-04-12 ### Changed -- Do not set any default headers +* Do not set any default headers ### Added -- Add Debug impl for BoxedSocket +* Add Debug impl for BoxedSocket ## [0.1.0-alpha.4] - 2019-04-08 ### Changed -- Update actix-http dependency +* Update actix-http dependency ## [0.1.0-alpha.3] - 2019-04-02 ### Added -- Export `MessageBody` type +* Export `MessageBody` type -- `ClientResponse::json()` - Loads and parse `application/json` encoded body +* `ClientResponse::json()` - Loads and parse `application/json` encoded body ### Changed -- `ClientRequest::json()` accepts reference instead of object. +* `ClientRequest::json()` accepts reference instead of object. -- `ClientResponse::body()` does not consume response object. +* `ClientResponse::body()` does not consume response object. -- Renamed `ClientRequest::close_connection()` to `ClientRequest::force_close()` +* Renamed `ClientRequest::close_connection()` to `ClientRequest::force_close()` ## [0.1.0-alpha.2] - 2019-03-29 ### Added -- Per request and session wide request timeout. +* Per request and session wide request timeout. -- Session wide headers. +* Session wide headers. -- Session wide basic and bearer auth. +* Session wide basic and bearer auth. -- Re-export `actix_http::client::Connector`. +* Re-export `actix_http::client::Connector`. ### Changed -- Allow to override request's uri +* Allow to override request's uri -- Export `ws` sub-module with websockets related types +* Export `ws` sub-module with websockets related types ## [0.1.0-alpha.1] - 2019-03-28 -- Initial impl +* Initial impl From de20d21703759d61af0836011210f6d7ffcf10c7 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Wed, 22 Dec 2021 08:21:30 +0000 Subject: [PATCH 369/428] use dash hyphenation in markdown --- .github/ISSUE_TEMPLATE/bug_report.md | 4 +- CHANGES.md | 552 +++++++++++++-------------- CODE_OF_CONDUCT.md | 20 +- MIGRATION.md | 156 ++++---- README.md | 60 +-- actix-files/CHANGES.md | 102 ++--- actix-http-test/CHANGES.md | 76 ++-- actix-http/CHANGES.md | 544 +++++++++++++------------- actix-http/README.md | 4 +- actix-multipart/CHANGES.md | 74 ++-- actix-router/CHANGES.md | 102 ++--- actix-test/CHANGES.md | 22 +- actix-web-actors/CHANGES.md | 60 +-- actix-web-codegen/CHANGES.md | 52 +-- awc/CHANGES.md | 170 ++++----- 15 files changed, 999 insertions(+), 999 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 2df863ae8..fa06a137a 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -33,5 +33,5 @@ Please search on the [Actix Web issue tracker](https://github.com/actix/actix-we ## Your Environment -* Rust Version (I.e, output of `rustc -V`): -* Actix Web Version: +- Rust Version (I.e, output of `rustc -V`): +- Actix Web Version: diff --git a/CHANGES.md b/CHANGES.md index 77ab2e218..8e030819f 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -5,22 +5,22 @@ ## 4.0.0-beta.15 - 2021-12-17 ### Added -* Method on `Responder` trait (`customize`) for customizing responders and `CustomizeResponder` struct. [#2510] -* Implement `Debug` for `DefaultHeaders`. [#2510] +- Method on `Responder` trait (`customize`) for customizing responders and `CustomizeResponder` struct. [#2510] +- Implement `Debug` for `DefaultHeaders`. [#2510] ### Changed -* Align `DefaultHeader` method terminology, deprecating previous methods. [#2510] -* Response service types in `ErrorHandlers` middleware now use `ServiceResponse>` to allow changing the body type. [#2515] -* Both variants in `ErrorHandlerResponse` now use `ServiceResponse>`. [#2515] -* Rename `test::{default_service => simple_service}`. Old name is deprecated. [#2518] -* Rename `test::{read_response_json => call_and_read_body_json}`. Old name is deprecated. [#2518] -* Rename `test::{read_response => call_and_read_body}`. Old name is deprecated. [#2518] -* Relax body type and error bounds on test utilities. [#2518] +- Align `DefaultHeader` method terminology, deprecating previous methods. [#2510] +- Response service types in `ErrorHandlers` middleware now use `ServiceResponse>` to allow changing the body type. [#2515] +- Both variants in `ErrorHandlerResponse` now use `ServiceResponse>`. [#2515] +- Rename `test::{default_service => simple_service}`. Old name is deprecated. [#2518] +- Rename `test::{read_response_json => call_and_read_body_json}`. Old name is deprecated. [#2518] +- Rename `test::{read_response => call_and_read_body}`. Old name is deprecated. [#2518] +- Relax body type and error bounds on test utilities. [#2518] ### Removed -* Top-level `EitherExtractError` export. [#2510] -* Conversion implementations for `either` crate. [#2516] -* `test::load_stream` and `test::load_body`; replace usage with `body::to_bytes`. [#2518] +- Top-level `EitherExtractError` export. [#2510] +- Conversion implementations for `either` crate. [#2516] +- `test::load_stream` and `test::load_body`; replace usage with `body::to_bytes`. [#2518] [#2510]: https://github.com/actix/actix-web/pull/2510 [#2515]: https://github.com/actix/actix-web/pull/2515 @@ -30,31 +30,31 @@ ## 4.0.0-beta.14 - 2021-12-11 ### Added -* Methods on `AcceptLanguage`: `ranked` and `preference`. [#2480] -* `AcceptEncoding` typed header. [#2482] -* `Range` typed header. [#2485] -* `HttpResponse::map_into_{left,right}_body` and `HttpResponse::map_into_boxed_body`. [#2468] -* `ServiceResponse::map_into_{left,right}_body` and `HttpResponse::map_into_boxed_body`. [#2468] -* Connection data set through the `HttpServer::on_connect` callback is now accessible only from the new `HttpRequest::conn_data()` and `ServiceRequest::conn_data()` methods. [#2491] -* `HttpRequest::{req_data,req_data_mut}`. [#2487] -* `ServiceResponse::into_parts`. [#2499] +- Methods on `AcceptLanguage`: `ranked` and `preference`. [#2480] +- `AcceptEncoding` typed header. [#2482] +- `Range` typed header. [#2485] +- `HttpResponse::map_into_{left,right}_body` and `HttpResponse::map_into_boxed_body`. [#2468] +- `ServiceResponse::map_into_{left,right}_body` and `HttpResponse::map_into_boxed_body`. [#2468] +- Connection data set through the `HttpServer::on_connect` callback is now accessible only from the new `HttpRequest::conn_data()` and `ServiceRequest::conn_data()` methods. [#2491] +- `HttpRequest::{req_data,req_data_mut}`. [#2487] +- `ServiceResponse::into_parts`. [#2499] ### Changed -* Rename `Accept::{mime_precedence => ranked}`. [#2480] -* Rename `Accept::{mime_preference => preference}`. [#2480] -* Un-deprecate `App::data_factory`. [#2484] -* `HttpRequest::url_for` no longer constructs URLs with query or fragment components. [#2430] -* Remove `B` (body) type parameter on `App`. [#2493] -* Add `B` (body) type parameter on `Scope`. [#2492] -* Request-local data container is no longer part of a `RequestHead`. Instead it is a distinct part of a `Request`. [#2487] +- Rename `Accept::{mime_precedence => ranked}`. [#2480] +- Rename `Accept::{mime_preference => preference}`. [#2480] +- Un-deprecate `App::data_factory`. [#2484] +- `HttpRequest::url_for` no longer constructs URLs with query or fragment components. [#2430] +- Remove `B` (body) type parameter on `App`. [#2493] +- Add `B` (body) type parameter on `Scope`. [#2492] +- Request-local data container is no longer part of a `RequestHead`. Instead it is a distinct part of a `Request`. [#2487] ### Fixed -* Accept wildcard `*` items in `AcceptLanguage`. [#2480] -* Re-exports `dev::{BodySize, MessageBody, SizedStream}`. They are exposed through the `body` module. [#2468] -* Typed headers containing lists that require one or more items now enforce this minimum. [#2482] +- Accept wildcard `*` items in `AcceptLanguage`. [#2480] +- Re-exports `dev::{BodySize, MessageBody, SizedStream}`. They are exposed through the `body` module. [#2468] +- Typed headers containing lists that require one or more items now enforce this minimum. [#2482] ### Removed -* `ConnectionInfo::get`. [#2487] +- `ConnectionInfo::get`. [#2487] [#2430]: https://github.com/actix/actix-web/pull/2430 [#2468]: https://github.com/actix/actix-web/pull/2468 @@ -71,20 +71,20 @@ ## 4.0.0-beta.13 - 2021-11-30 ### Changed -* Update `actix-tls` to `3.0.0-rc.1`. [#2474] +- Update `actix-tls` to `3.0.0-rc.1`. [#2474] [#2474]: https://github.com/actix/actix-web/pull/2474 ## 4.0.0-beta.12 - 2021-11-22 ### Changed -* Compress middleware's response type is now `AnyBody>`. [#2448] +- Compress middleware's response type is now `AnyBody>`. [#2448] ### Fixed -* Relax `Unpin` bound on `S` (stream) parameter of `HttpResponseBuilder::streaming`. [#2448] +- Relax `Unpin` bound on `S` (stream) parameter of `HttpResponseBuilder::streaming`. [#2448] ### Removed -* `dev::ResponseBody` re-export; is function is replaced by the new `dev::AnyBody` enum. [#2446] +- `dev::ResponseBody` re-export; is function is replaced by the new `dev::AnyBody` enum. [#2446] [#2446]: https://github.com/actix/actix-web/pull/2446 [#2448]: https://github.com/actix/actix-web/pull/2448 @@ -92,11 +92,11 @@ ## 4.0.0-beta.11 - 2021-11-15 ### Added -* Re-export `dev::ServerHandle` from `actix-server`. [#2442] +- Re-export `dev::ServerHandle` from `actix-server`. [#2442] ### Changed -* `ContentType::html` now produces `text/html; charset=utf-8` instead of `text/html`. [#2423] -* Update `actix-server` to `2.0.0-beta.9`. [#2442] +- `ContentType::html` now produces `text/html; charset=utf-8` instead of `text/html`. [#2423] +- Update `actix-server` to `2.0.0-beta.9`. [#2442] [#2423]: https://github.com/actix/actix-web/pull/2423 [#2442]: https://github.com/actix/actix-web/pull/2442 @@ -104,18 +104,18 @@ ## 4.0.0-beta.10 - 2021-10-20 ### Added -* Option to allow `Json` extractor to work without a `Content-Type` header present. [#2362] -* `#[actix_web::test]` macro for setting up tests with a runtime. [#2409] +- Option to allow `Json` extractor to work without a `Content-Type` header present. [#2362] +- `#[actix_web::test]` macro for setting up tests with a runtime. [#2409] ### Changed -* Associated type `FromRequest::Config` was removed. [#2233] -* Inner field made private on `web::Payload`. [#2384] -* `Data::into_inner` and `Data::get_ref` no longer requires `T: Sized`. [#2403] -* Updated rustls to v0.20. [#2414] -* Minimum supported Rust version (MSRV) is now 1.52. +- Associated type `FromRequest::Config` was removed. [#2233] +- Inner field made private on `web::Payload`. [#2384] +- `Data::into_inner` and `Data::get_ref` no longer requires `T: Sized`. [#2403] +- Updated rustls to v0.20. [#2414] +- Minimum supported Rust version (MSRV) is now 1.52. ### Removed -* Useless `ServiceResponse::checked_expr` method. [#2401] +- Useless `ServiceResponse::checked_expr` method. [#2401] [#2233]: https://github.com/actix/actix-web/pull/2233 [#2362]: https://github.com/actix/actix-web/pull/2362 @@ -128,17 +128,17 @@ ## 4.0.0-beta.9 - 2021-09-09 ### Added -* Re-export actix-service `ServiceFactory` in `dev` module. [#2325] +- Re-export actix-service `ServiceFactory` in `dev` module. [#2325] ### Changed -* Compress middleware will return 406 Not Acceptable when no content encoding is acceptable to the client. [#2344] -* Move `BaseHttpResponse` to `dev::Response`. [#2379] -* Enable `TestRequest::param` to accept more than just static strings. [#2172] -* Minimum supported Rust version (MSRV) is now 1.51. +- Compress middleware will return 406 Not Acceptable when no content encoding is acceptable to the client. [#2344] +- Move `BaseHttpResponse` to `dev::Response`. [#2379] +- Enable `TestRequest::param` to accept more than just static strings. [#2172] +- Minimum supported Rust version (MSRV) is now 1.51. ### Fixed -* Fix quality parse error in Accept-Encoding header. [#2344] -* Re-export correct type at `web::HttpResponse`. [#2379] +- Fix quality parse error in Accept-Encoding header. [#2344] +- Re-export correct type at `web::HttpResponse`. [#2379] [#2172]: https://github.com/actix/actix-web/pull/2172 [#2325]: https://github.com/actix/actix-web/pull/2325 @@ -148,18 +148,18 @@ ## 4.0.0-beta.8 - 2021-06-26 ### Added -* Add `ServiceRequest::parts_mut`. [#2177] -* Add extractors for `Uri` and `Method`. [#2263] -* Add extractors for `ConnectionInfo` and `PeerAddr`. [#2263] -* Add `Route::service` for using hand-written services as handlers. [#2262] +- Add `ServiceRequest::parts_mut`. [#2177] +- Add extractors for `Uri` and `Method`. [#2263] +- Add extractors for `ConnectionInfo` and `PeerAddr`. [#2263] +- Add `Route::service` for using hand-written services as handlers. [#2262] ### Changed -* Change compression algorithm features flags. [#2250] -* Deprecate `App::data` and `App::data_factory`. [#2271] -* Smarter extraction of `ConnectionInfo` parts. [#2282] +- Change compression algorithm features flags. [#2250] +- Deprecate `App::data` and `App::data_factory`. [#2271] +- Smarter extraction of `ConnectionInfo` parts. [#2282] ### Fixed -* Scope and Resource middleware can access data items set on their own layer. [#2288] +- Scope and Resource middleware can access data items set on their own layer. [#2288] [#2177]: https://github.com/actix/actix-web/pull/2177 [#2250]: https://github.com/actix/actix-web/pull/2250 @@ -172,23 +172,23 @@ ## 4.0.0-beta.7 - 2021-06-17 ### Added -* `HttpServer::worker_max_blocking_threads` for setting block thread pool. [#2200] +- `HttpServer::worker_max_blocking_threads` for setting block thread pool. [#2200] ### Changed -* Adjusted default JSON payload limit to 2MB (from 32kb) and included size and limits in the `JsonPayloadError::Overflow` error variant. [#2162] +- Adjusted default JSON payload limit to 2MB (from 32kb) and included size and limits in the `JsonPayloadError::Overflow` error variant. [#2162] [#2162]: (https://github.com/actix/actix-web/pull/2162) -* `ServiceResponse::error_response` now uses body type of `Body`. [#2201] -* `ServiceResponse::checked_expr` now returns a `Result`. [#2201] -* Update `language-tags` to `0.3`. -* `ServiceResponse::take_body`. [#2201] -* `ServiceResponse::map_body` closure receives and returns `B` instead of `ResponseBody` types. [#2201] -* All error trait bounds in server service builders have changed from `Into` to `Into>`. [#2253] -* All error trait bounds in message body and stream impls changed from `Into` to `Into>`. [#2253] -* `HttpServer::{listen_rustls(), bind_rustls()}` now honor the ALPN protocols in the configuation parameter. [#2226] -* `middleware::normalize` now will not try to normalize URIs with no valid path [#2246] +- `ServiceResponse::error_response` now uses body type of `Body`. [#2201] +- `ServiceResponse::checked_expr` now returns a `Result`. [#2201] +- Update `language-tags` to `0.3`. +- `ServiceResponse::take_body`. [#2201] +- `ServiceResponse::map_body` closure receives and returns `B` instead of `ResponseBody` types. [#2201] +- All error trait bounds in server service builders have changed from `Into` to `Into>`. [#2253] +- All error trait bounds in message body and stream impls changed from `Into` to `Into>`. [#2253] +- `HttpServer::{listen_rustls(), bind_rustls()}` now honor the ALPN protocols in the configuation parameter. [#2226] +- `middleware::normalize` now will not try to normalize URIs with no valid path [#2246] ### Removed -* `HttpResponse::take_body` and old `HttpResponse::into_body` method that casted body type. [#2201] +- `HttpResponse::take_body` and old `HttpResponse::into_body` method that casted body type. [#2201] [#2200]: https://github.com/actix/actix-web/pull/2200 [#2201]: https://github.com/actix/actix-web/pull/2201 @@ -198,11 +198,11 @@ ## 4.0.0-beta.6 - 2021-04-17 ### Added -* `HttpResponse` and `HttpResponseBuilder` structs. [#2065] +- `HttpResponse` and `HttpResponseBuilder` structs. [#2065] ### Changed -* Most error types are now marked `#[non_exhaustive]`. [#2148] -* Methods on `ContentDisposition` that took `T: AsRef` now take `impl AsRef`. +- Most error types are now marked `#[non_exhaustive]`. [#2148] +- Methods on `ContentDisposition` that took `T: AsRef` now take `impl AsRef`. [#2065]: https://github.com/actix/actix-web/pull/2065 [#2148]: https://github.com/actix/actix-web/pull/2148 @@ -210,20 +210,20 @@ ## 4.0.0-beta.5 - 2021-04-02 ### Added -* `Header` extractor for extracting common HTTP headers in handlers. [#2094] -* Added `TestServer::client_headers` method. [#2097] +- `Header` extractor for extracting common HTTP headers in handlers. [#2094] +- Added `TestServer::client_headers` method. [#2097] ### Fixed -* Double ampersand in Logger format is escaped correctly. [#2067] +- Double ampersand in Logger format is escaped correctly. [#2067] ### Changed -* `CustomResponder` would return error as `HttpResponse` when `CustomResponder::with_header` failed +- `CustomResponder` would return error as `HttpResponse` when `CustomResponder::with_header` failed instead of skipping. (Only the first error is kept when multiple error occur) [#2093] ### Removed -* The `client` mod was removed. Clients should now use `awc` directly. +- The `client` mod was removed. Clients should now use `awc` directly. [871ca5e4](https://github.com/actix/actix-web/commit/871ca5e4ae2bdc22d1ea02701c2992fa8d04aed7) -* Integration testing was moved to new `actix-test` crate. Namely these items from the `test` +- Integration testing was moved to new `actix-test` crate. Namely these items from the `test` module: `TestServer`, `TestServerConfig`, `start`, `start_with`, and `unused_addr`. [#2112] [#2067]: https://github.com/actix/actix-web/pull/2067 @@ -235,8 +235,8 @@ ## 4.0.0-beta.4 - 2021-03-09 ### Changed -* Feature `cookies` is now optional and enabled by default. [#1981] -* `JsonBody::new` returns a default limit of 32kB to be consistent with `JsonConfig` and the default +- Feature `cookies` is now optional and enabled by default. [#1981] +- `JsonBody::new` returns a default limit of 32kB to be consistent with `JsonConfig` and the default behaviour of the `web::Json` extractor. [#2010] [#1981]: https://github.com/actix/actix-web/pull/1981 @@ -244,36 +244,36 @@ ## 4.0.0-beta.3 - 2021-02-10 -* Update `actix-web-codegen` to `0.5.0-beta.1`. +- Update `actix-web-codegen` to `0.5.0-beta.1`. ## 4.0.0-beta.2 - 2021-02-10 ### Added -* The method `Either, web::Form>::into_inner()` which returns the inner type for +- The method `Either, web::Form>::into_inner()` which returns the inner type for whichever variant was created. Also works for `Either, web::Json>`. [#1894] -* Add `services!` macro for helping register multiple services to `App`. [#1933] -* Enable registering a vec of services of the same type to `App` [#1933] +- Add `services!` macro for helping register multiple services to `App`. [#1933] +- Enable registering a vec of services of the same type to `App` [#1933] ### Changed -* Rework `Responder` trait to be sync and returns `Response`/`HttpResponse` directly. +- Rework `Responder` trait to be sync and returns `Response`/`HttpResponse` directly. Making it simpler and more performant. [#1891] -* `ServiceRequest::into_parts` and `ServiceRequest::from_parts` can no longer fail. [#1893] -* `ServiceRequest::from_request` can no longer fail. [#1893] -* Our `Either` type now uses `Left`/`Right` variants (instead of `A`/`B`) [#1894] -* `test::{call_service, read_response, read_response_json, send_request}` take `&Service` +- `ServiceRequest::into_parts` and `ServiceRequest::from_parts` can no longer fail. [#1893] +- `ServiceRequest::from_request` can no longer fail. [#1893] +- Our `Either` type now uses `Left`/`Right` variants (instead of `A`/`B`) [#1894] +- `test::{call_service, read_response, read_response_json, send_request}` take `&Service` in argument [#1905] -* `App::wrap_fn`, `Resource::wrap_fn` and `Scope::wrap_fn` provide `&Service` in closure +- `App::wrap_fn`, `Resource::wrap_fn` and `Scope::wrap_fn` provide `&Service` in closure argument. [#1905] -* `web::block` no longer requires the output is a Result. [#1957] +- `web::block` no longer requires the output is a Result. [#1957] ### Fixed -* Multiple calls to `App::data` with the same type now keeps the latest call's data. [#1906] +- Multiple calls to `App::data` with the same type now keeps the latest call's data. [#1906] ### Removed -* Public field of `web::Path` has been made private. [#1894] -* Public field of `web::Query` has been made private. [#1894] -* `TestRequest::with_header`; use `TestRequest::default().insert_header()`. [#1869] -* `AppService::set_service_data`; for custom HTTP service factories adding application data, use the +- Public field of `web::Path` has been made private. [#1894] +- Public field of `web::Query` has been made private. [#1894] +- `TestRequest::with_header`; use `TestRequest::default().insert_header()`. [#1869] +- `AppService::set_service_data`; for custom HTTP service factories adding application data, use the layered data model by calling `ServiceRequest::add_data_container` when handling requests instead. [#1906] @@ -289,26 +289,26 @@ ## 4.0.0-beta.1 - 2021-01-07 ### Added -* `Compat` middleware enabling generic response body/error type of middlewares like `Logger` and +- `Compat` middleware enabling generic response body/error type of middlewares like `Logger` and `Compress` to be used in `middleware::Condition` and `Resource`, `Scope` services. [#1865] ### Changed -* Update `actix-*` dependencies to tokio `1.0` based versions. [#1813] -* Bumped `rand` to `0.8`. -* Update `rust-tls` to `0.19`. [#1813] -* Rename `Handler` to `HandlerService` and rename `Factory` to `Handler`. [#1852] -* The default `TrailingSlash` is now `Trim`, in line with existing documentation. See migration +- Update `actix-*` dependencies to tokio `1.0` based versions. [#1813] +- Bumped `rand` to `0.8`. +- Update `rust-tls` to `0.19`. [#1813] +- Rename `Handler` to `HandlerService` and rename `Factory` to `Handler`. [#1852] +- The default `TrailingSlash` is now `Trim`, in line with existing documentation. See migration guide for implications. [#1875] -* Rename `DefaultHeaders::{content_type => add_content_type}`. [#1875] -* MSRV is now 1.46.0. +- Rename `DefaultHeaders::{content_type => add_content_type}`. [#1875] +- MSRV is now 1.46.0. ### Fixed -* Added the underlying parse error to `test::read_body_json`'s panic message. [#1812] +- Added the underlying parse error to `test::read_body_json`'s panic message. [#1812] ### Removed -* Public modules `middleware::{normalize, err_handlers}`. All necessary middleware structs are now +- Public modules `middleware::{normalize, err_handlers}`. All necessary middleware structs are now exposed directly by the `middleware` module. -* Remove `actix-threadpool` as dependency. `actix_threadpool::BlockingError` error type can be imported +- Remove `actix-threadpool` as dependency. `actix_threadpool::BlockingError` error type can be imported from `actix_web::error` module. [#1878] [#1812]: https://github.com/actix/actix-web/pull/1812 @@ -321,16 +321,16 @@ ## 3.3.3 - 2021-12-18 ### Changed -* Soft-deprecate `NormalizePath::default()`, noting upcoming behavior change in v4. [#2529] +- Soft-deprecate `NormalizePath::default()`, noting upcoming behavior change in v4. [#2529] [#2529]: https://github.com/actix/actix-web/pull/2529 ## 3.3.2 - 2020-12-01 ### Fixed -* Removed an occasional `unwrap` on `None` panic in `NormalizePathNormalization`. [#1762] -* Fix `match_pattern()` returning `None` for scope with empty path resource. [#1798] -* Increase minimum `socket2` version. [#1803] +- Removed an occasional `unwrap` on `None` panic in `NormalizePathNormalization`. [#1762] +- Fix `match_pattern()` returning `None` for scope with empty path resource. [#1798] +- Increase minimum `socket2` version. [#1803] [#1762]: https://github.com/actix/actix-web/pull/1762 [#1798]: https://github.com/actix/actix-web/pull/1798 @@ -338,15 +338,15 @@ ## 3.3.1 - 2020-11-29 -* Ensure `actix-http` dependency uses same `serde_urlencoded`. +- Ensure `actix-http` dependency uses same `serde_urlencoded`. ## 3.3.0 - 2020-11-25 ### Added -* Add `Either` extractor helper. [#1788] +- Add `Either` extractor helper. [#1788] ### Changed -* Upgrade `serde_urlencoded` to `0.7`. [#1773] +- Upgrade `serde_urlencoded` to `0.7`. [#1773] [#1773]: https://github.com/actix/actix-web/pull/1773 [#1788]: https://github.com/actix/actix-web/pull/1788 @@ -354,17 +354,17 @@ ## 3.2.0 - 2020-10-30 ### Added -* Implement `exclude_regex` for Logger middleware. [#1723] -* Add request-local data extractor `web::ReqData`. [#1748] -* Add ability to register closure for request middleware logging. [#1749] -* Add `app_data` to `ServiceConfig`. [#1757] -* Expose `on_connect` for access to the connection stream before request is handled. [#1754] +- Implement `exclude_regex` for Logger middleware. [#1723] +- Add request-local data extractor `web::ReqData`. [#1748] +- Add ability to register closure for request middleware logging. [#1749] +- Add `app_data` to `ServiceConfig`. [#1757] +- Expose `on_connect` for access to the connection stream before request is handled. [#1754] ### Changed -* Updated actix-web-codegen dependency for access to new `#[route(...)]` multi-method macro. -* Print non-configured `Data` type when attempting extraction. [#1743] -* Re-export bytes::Buf{Mut} in web module. [#1750] -* Upgrade `pin-project` to `1.0`. +- Updated actix-web-codegen dependency for access to new `#[route(...)]` multi-method macro. +- Print non-configured `Data` type when attempting extraction. [#1743] +- Re-export bytes::Buf{Mut} in web module. [#1750] +- Upgrade `pin-project` to `1.0`. [#1723]: https://github.com/actix/actix-web/pull/1723 [#1743]: https://github.com/actix/actix-web/pull/1743 @@ -376,13 +376,13 @@ ## 3.1.0 - 2020-09-29 ### Changed -* Add `TrailingSlash::MergeOnly` behaviour to `NormalizePath`, which allows `NormalizePath` +- Add `TrailingSlash::MergeOnly` behaviour to `NormalizePath`, which allows `NormalizePath` to retain any trailing slashes. [#1695] -* Remove bound `std::marker::Sized` from `web::Data` to support storing `Arc` +- Remove bound `std::marker::Sized` from `web::Data` to support storing `Arc` via `web::Data::from` [#1710] ### Fixed -* `ResourceMap` debug printing is no longer infinitely recursive. [#1708] +- `ResourceMap` debug printing is no longer infinitely recursive. [#1708] [#1695]: https://github.com/actix/actix-web/pull/1695 [#1708]: https://github.com/actix/actix-web/pull/1708 @@ -391,33 +391,33 @@ ## 3.0.2 - 2020-09-15 ### Fixed -* `NormalizePath` when used with `TrailingSlash::Trim` no longer trims the root path "/". [#1678] +- `NormalizePath` when used with `TrailingSlash::Trim` no longer trims the root path "/". [#1678] [#1678]: https://github.com/actix/actix-web/pull/1678 ## 3.0.1 - 2020-09-13 ### Changed -* `middleware::normalize::TrailingSlash` enum is now accessible. [#1673] +- `middleware::normalize::TrailingSlash` enum is now accessible. [#1673] [#1673]: https://github.com/actix/actix-web/pull/1673 ## 3.0.0 - 2020-09-11 -* No significant changes from `3.0.0-beta.4`. +- No significant changes from `3.0.0-beta.4`. ## 3.0.0-beta.4 - 2020-09-09 ### Added -* `middleware::NormalizePath` now has configurable behavior for either always having a trailing +- `middleware::NormalizePath` now has configurable behavior for either always having a trailing slash, or as the new addition, always trimming trailing slashes. [#1639] ### Changed -* Update actix-codec and actix-utils dependencies. [#1634] -* `FormConfig` and `JsonConfig` configurations are now also considered when set +- Update actix-codec and actix-utils dependencies. [#1634] +- `FormConfig` and `JsonConfig` configurations are now also considered when set using `App::data`. [#1641] -* `HttpServer::maxconn` is renamed to the more expressive `HttpServer::max_connections`. [#1655] -* `HttpServer::maxconnrate` is renamed to the more expressive +- `HttpServer::maxconn` is renamed to the more expressive `HttpServer::max_connections`. [#1655] +- `HttpServer::maxconnrate` is renamed to the more expressive `HttpServer::max_connection_rate`. [#1655] [#1639]: https://github.com/actix/actix-web/pull/1639 @@ -427,22 +427,22 @@ ## 3.0.0-beta.3 - 2020-08-17 ### Changed -* Update `rustls` to 0.18 +- Update `rustls` to 0.18 ## 3.0.0-beta.2 - 2020-08-17 ### Changed -* `PayloadConfig` is now also considered in `Bytes` and `String` extractors when set +- `PayloadConfig` is now also considered in `Bytes` and `String` extractors when set using `App::data`. [#1610] -* `web::Path` now has a public representation: `web::Path(pub T)` that enables +- `web::Path` now has a public representation: `web::Path(pub T)` that enables destructuring. [#1594] -* `ServiceRequest::app_data` allows retrieval of non-Data data without splitting into parts to +- `ServiceRequest::app_data` allows retrieval of non-Data data without splitting into parts to access `HttpRequest` which already allows this. [#1618] -* Re-export all error types from `awc`. [#1621] -* MSRV is now 1.42.0. +- Re-export all error types from `awc`. [#1621] +- MSRV is now 1.42.0. ### Fixed -* Memory leak of app data in pooled requests. [#1609] +- Memory leak of app data in pooled requests. [#1609] [#1594]: https://github.com/actix/actix-web/pull/1594 [#1609]: https://github.com/actix/actix-web/pull/1609 @@ -453,29 +453,29 @@ ## 3.0.0-beta.1 - 2020-07-13 ### Added -* Re-export `actix_rt::main` as `actix_web::main`. -* `HttpRequest::match_pattern` and `ServiceRequest::match_pattern` for extracting the matched +- Re-export `actix_rt::main` as `actix_web::main`. +- `HttpRequest::match_pattern` and `ServiceRequest::match_pattern` for extracting the matched resource pattern. -* `HttpRequest::match_name` and `ServiceRequest::match_name` for extracting matched resource name. +- `HttpRequest::match_name` and `ServiceRequest::match_name` for extracting matched resource name. ### Changed -* Fix actix_http::h1::dispatcher so it returns when HW_BUFFER_SIZE is reached. Should reduce peak memory consumption during large uploads. [#1550] -* Migrate cookie handling to `cookie` crate. Actix-web no longer requires `ring` dependency. -* MSRV is now 1.41.1 +- Fix actix_http::h1::dispatcher so it returns when HW_BUFFER_SIZE is reached. Should reduce peak memory consumption during large uploads. [#1550] +- Migrate cookie handling to `cookie` crate. Actix-web no longer requires `ring` dependency. +- MSRV is now 1.41.1 ### Fixed -* `NormalizePath` improved consistency when path needs slashes added _and_ removed. +- `NormalizePath` improved consistency when path needs slashes added _and_ removed. ## 3.0.0-alpha.3 - 2020-05-21 ### Added -* Add option to create `Data` from `Arc` [#1509] +- Add option to create `Data` from `Arc` [#1509] ### Changed -* Resources and Scopes can now access non-overridden data types set on App (or containing scopes) when setting their own data. [#1486] -* Fix audit issue logging by default peer address [#1485] -* Bump minimum supported Rust version to 1.40 -* Replace deprecated `net2` crate with `socket2` +- Resources and Scopes can now access non-overridden data types set on App (or containing scopes) when setting their own data. [#1486] +- Fix audit issue logging by default peer address [#1485] +- Bump minimum supported Rust version to 1.40 +- Replace deprecated `net2` crate with `socket2` [#1485]: https://github.com/actix/actix-web/pull/1485 [#1509]: https://github.com/actix/actix-web/pull/1509 @@ -484,10 +484,10 @@ ### Changed -* `{Resource,Scope}::default_service(f)` handlers now support app data extraction. [#1452] -* Implement `std::error::Error` for our custom errors [#1422] -* NormalizePath middleware now appends trailing / so that routes of form /example/ respond to /example requests. [#1433] -* Remove the `failure` feature and support. +- `{Resource,Scope}::default_service(f)` handlers now support app data extraction. [#1452] +- Implement `std::error::Error` for our custom errors [#1422] +- NormalizePath middleware now appends trailing / so that routes of form /example/ respond to /example requests. [#1433] +- Remove the `failure` feature and support. [#1422]: https://github.com/actix/actix-web/pull/1422 [#1433]: https://github.com/actix/actix-web/pull/1433 @@ -499,16 +499,16 @@ ### Added -* Add helper function for creating routes with `TRACE` method guard `web::trace()` -* Add convenience functions `test::read_body_json()` and `test::TestRequest::send_request()` for testing. +- Add helper function for creating routes with `TRACE` method guard `web::trace()` +- Add convenience functions `test::read_body_json()` and `test::TestRequest::send_request()` for testing. ### Changed -* Use `sha-1` crate instead of unmaintained `sha1` crate -* Skip empty chunks when returning response from a `Stream` [#1308] -* Update the `time` dependency to 0.2.7 -* Update `actix-tls` dependency to 2.0.0-alpha.1 -* Update `rustls` dependency to 0.17 +- Use `sha-1` crate instead of unmaintained `sha1` crate +- Skip empty chunks when returning response from a `Stream` [#1308] +- Update the `time` dependency to 0.2.7 +- Update `actix-tls` dependency to 2.0.0-alpha.1 +- Update `rustls` dependency to 0.17 [#1308]: https://github.com/actix/actix-web/pull/1308 @@ -516,408 +516,408 @@ ### Changed -* Rename `HttpServer::start()` to `HttpServer::run()` +- Rename `HttpServer::start()` to `HttpServer::run()` -* Allow to gracefully stop test server via `TestServer::stop()` +- Allow to gracefully stop test server via `TestServer::stop()` -* Allow to specify multi-patterns for resources +- Allow to specify multi-patterns for resources ## [2.0.0-rc] - 2019-12-20 ### Changed -* Move `BodyEncoding` to `dev` module #1220 +- Move `BodyEncoding` to `dev` module #1220 -* Allow to set `peer_addr` for TestRequest #1074 +- Allow to set `peer_addr` for TestRequest #1074 -* Make web::Data deref to Arc #1214 +- Make web::Data deref to Arc #1214 -* Rename `App::register_data()` to `App::app_data()` +- Rename `App::register_data()` to `App::app_data()` -* `HttpRequest::app_data()` returns `Option<&T>` instead of `Option<&Data>` +- `HttpRequest::app_data()` returns `Option<&T>` instead of `Option<&Data>` ### Fixed -* Fix `AppConfig::secure()` is always false. #1202 +- Fix `AppConfig::secure()` is always false. #1202 ## [2.0.0-alpha.6] - 2019-12-15 ### Fixed -* Fixed compilation with default features off +- Fixed compilation with default features off ## [2.0.0-alpha.5] - 2019-12-13 ### Added -* Add test server, `test::start()` and `test::start_with()` +- Add test server, `test::start()` and `test::start_with()` ## [2.0.0-alpha.4] - 2019-12-08 ### Deleted -* Delete HttpServer::run(), it is not useful with async/await +- Delete HttpServer::run(), it is not useful with async/await ## [2.0.0-alpha.3] - 2019-12-07 ### Changed -* Migrate to tokio 0.2 +- Migrate to tokio 0.2 ## [2.0.0-alpha.1] - 2019-11-22 ### Changed -* Migrated to `std::future` +- Migrated to `std::future` -* Remove implementation of `Responder` for `()`. (#1167) +- Remove implementation of `Responder` for `()`. (#1167) ## [1.0.9] - 2019-11-14 ### Added -* Add `Payload::into_inner` method and make stored `def::Payload` public. (#1110) +- Add `Payload::into_inner` method and make stored `def::Payload` public. (#1110) ### Changed -* Support `Host` guards when the `Host` header is unset (e.g. HTTP/2 requests) (#1129) +- Support `Host` guards when the `Host` header is unset (e.g. HTTP/2 requests) (#1129) ## [1.0.8] - 2019-09-25 ### Added -* Add `Scope::register_data` and `Resource::register_data` methods, parallel to +- Add `Scope::register_data` and `Resource::register_data` methods, parallel to `App::register_data`. -* Add `middleware::Condition` that conditionally enables another middleware +- Add `middleware::Condition` that conditionally enables another middleware -* Allow to re-construct `ServiceRequest` from `HttpRequest` and `Payload` +- Allow to re-construct `ServiceRequest` from `HttpRequest` and `Payload` -* Add `HttpServer::listen_uds` for ability to listen on UDS FD rather than path, +- Add `HttpServer::listen_uds` for ability to listen on UDS FD rather than path, which is useful for example with systemd. ### Changed -* Make UrlEncodedError::Overflow more informative +- Make UrlEncodedError::Overflow more informative -* Use actix-testing for testing utils +- Use actix-testing for testing utils ## [1.0.7] - 2019-08-29 ### Fixed -* Request Extensions leak #1062 +- Request Extensions leak #1062 ## [1.0.6] - 2019-08-28 ### Added -* Re-implement Host predicate (#989) +- Re-implement Host predicate (#989) -* Form implements Responder, returning a `application/x-www-form-urlencoded` response +- Form implements Responder, returning a `application/x-www-form-urlencoded` response -* Add `into_inner` to `Data` +- Add `into_inner` to `Data` -* Add `test::TestRequest::set_form()` convenience method to automatically serialize data and set +- Add `test::TestRequest::set_form()` convenience method to automatically serialize data and set the header in test requests. ### Changed -* `Query` payload made `pub`. Allows user to pattern-match the payload. +- `Query` payload made `pub`. Allows user to pattern-match the payload. -* Enable `rust-tls` feature for client #1045 +- Enable `rust-tls` feature for client #1045 -* Update serde_urlencoded to 0.6.1 +- Update serde_urlencoded to 0.6.1 -* Update url to 2.1 +- Update url to 2.1 ## [1.0.5] - 2019-07-18 ### Added -* Unix domain sockets (HttpServer::bind_uds) #92 +- Unix domain sockets (HttpServer::bind_uds) #92 -* Actix now logs errors resulting in "internal server error" responses always, with the `error` +- Actix now logs errors resulting in "internal server error" responses always, with the `error` logging level ### Fixed -* Restored logging of errors through the `Logger` middleware +- Restored logging of errors through the `Logger` middleware ## [1.0.4] - 2019-07-17 ### Added -* Add `Responder` impl for `(T, StatusCode) where T: Responder` +- Add `Responder` impl for `(T, StatusCode) where T: Responder` -* Allow to access app's resource map via +- Allow to access app's resource map via `ServiceRequest::resource_map()` and `HttpRequest::resource_map()` methods. ### Changed -* Upgrade `rand` dependency version to 0.7 +- Upgrade `rand` dependency version to 0.7 ## [1.0.3] - 2019-06-28 ### Added -* Support asynchronous data factories #850 +- Support asynchronous data factories #850 ### Changed -* Use `encoding_rs` crate instead of unmaintained `encoding` crate +- Use `encoding_rs` crate instead of unmaintained `encoding` crate ## [1.0.2] - 2019-06-17 ### Changed -* Move cors middleware to `actix-cors` crate. +- Move cors middleware to `actix-cors` crate. -* Move identity middleware to `actix-identity` crate. +- Move identity middleware to `actix-identity` crate. ## [1.0.1] - 2019-06-17 ### Added -* Add support for PathConfig #903 +- Add support for PathConfig #903 -* Add `middleware::identity::RequestIdentity` trait to `get_identity` from `HttpMessage`. +- Add `middleware::identity::RequestIdentity` trait to `get_identity` from `HttpMessage`. ### Changed -* Move cors middleware to `actix-cors` crate. +- Move cors middleware to `actix-cors` crate. -* Move identity middleware to `actix-identity` crate. +- Move identity middleware to `actix-identity` crate. -* Disable default feature `secure-cookies`. +- Disable default feature `secure-cookies`. -* Allow to test an app that uses async actors #897 +- Allow to test an app that uses async actors #897 -* Re-apply patch from #637 #894 +- Re-apply patch from #637 #894 ### Fixed -* HttpRequest::url_for is broken with nested scopes #915 +- HttpRequest::url_for is broken with nested scopes #915 ## [1.0.0] - 2019-06-05 ### Added -* Add `Scope::configure()` method. +- Add `Scope::configure()` method. -* Add `ServiceRequest::set_payload()` method. +- Add `ServiceRequest::set_payload()` method. -* Add `test::TestRequest::set_json()` convenience method to automatically +- Add `test::TestRequest::set_json()` convenience method to automatically serialize data and set header in test requests. -* Add macros for head, options, trace, connect and patch http methods +- Add macros for head, options, trace, connect and patch http methods ### Changed -* Drop an unnecessary `Option<_>` indirection around `ServerBuilder` from `HttpServer`. #863 +- Drop an unnecessary `Option<_>` indirection around `ServerBuilder` from `HttpServer`. #863 ### Fixed -* Fix Logger request time format, and use rfc3339. #867 +- Fix Logger request time format, and use rfc3339. #867 -* Clear http requests pool on app service drop #860 +- Clear http requests pool on app service drop #860 ## [1.0.0-rc] - 2019-05-18 ### Added -* Add `Query::from_query()` to extract parameters from a query string. #846 -* `QueryConfig`, similar to `JsonConfig` for customizing error handling of query extractors. +- Add `Query::from_query()` to extract parameters from a query string. #846 +- `QueryConfig`, similar to `JsonConfig` for customizing error handling of query extractors. ### Changed -* `JsonConfig` is now `Send + Sync`, this implies that `error_handler` must be `Send + Sync` too. +- `JsonConfig` is now `Send + Sync`, this implies that `error_handler` must be `Send + Sync` too. ### Fixed -* Codegen with parameters in the path only resolves the first registered endpoint #841 +- Codegen with parameters in the path only resolves the first registered endpoint #841 ## [1.0.0-beta.4] - 2019-05-12 ### Added -* Allow to set/override app data on scope level +- Allow to set/override app data on scope level ### Changed -* `App::configure` take an `FnOnce` instead of `Fn` -* Upgrade actix-net crates +- `App::configure` take an `FnOnce` instead of `Fn` +- Upgrade actix-net crates ## [1.0.0-beta.3] - 2019-05-04 ### Added -* Add helper function for executing futures `test::block_fn()` +- Add helper function for executing futures `test::block_fn()` ### Changed -* Extractor configuration could be registered with `App::data()` +- Extractor configuration could be registered with `App::data()` or with `Resource::data()` #775 -* Route data is unified with app data, `Route::data()` moved to resource +- Route data is unified with app data, `Route::data()` moved to resource level to `Resource::data()` -* CORS handling without headers #702 +- CORS handling without headers #702 -* Allow constructing `Data` instances to avoid double `Arc` for `Send + Sync` types. +- Allow constructing `Data` instances to avoid double `Arc` for `Send + Sync` types. ### Fixed -* Fix `NormalizePath` middleware impl #806 +- Fix `NormalizePath` middleware impl #806 ### Deleted -* `App::data_factory()` is deleted. +- `App::data_factory()` is deleted. ## [1.0.0-beta.2] - 2019-04-24 ### Added -* Add raw services support via `web::service()` +- Add raw services support via `web::service()` -* Add helper functions for reading response body `test::read_body()` +- Add helper functions for reading response body `test::read_body()` -* Add support for `remainder match` (i.e "/path/{tail}*") +- Add support for `remainder match` (i.e "/path/{tail}*") -* Extend `Responder` trait, allow to override status code and headers. +- Extend `Responder` trait, allow to override status code and headers. -* Store visit and login timestamp in the identity cookie #502 +- Store visit and login timestamp in the identity cookie #502 ### Changed -* `.to_async()` handler can return `Responder` type #792 +- `.to_async()` handler can return `Responder` type #792 ### Fixed -* Fix async web::Data factory handling +- Fix async web::Data factory handling ## [1.0.0-beta.1] - 2019-04-20 ### Added -* Add helper functions for reading test response body, +- Add helper functions for reading test response body, `test::read_response()` and test::read_response_json()` -* Add `.peer_addr()` #744 +- Add `.peer_addr()` #744 -* Add `NormalizePath` middleware +- Add `NormalizePath` middleware ### Changed -* Rename `RouterConfig` to `ServiceConfig` +- Rename `RouterConfig` to `ServiceConfig` -* Rename `test::call_success` to `test::call_service` +- Rename `test::call_success` to `test::call_service` -* Removed `ServiceRequest::from_parts()` as it is unsafe to create from parts. +- Removed `ServiceRequest::from_parts()` as it is unsafe to create from parts. -* `CookieIdentityPolicy::max_age()` accepts value in seconds +- `CookieIdentityPolicy::max_age()` accepts value in seconds ### Fixed -* Fixed `TestRequest::app_data()` +- Fixed `TestRequest::app_data()` ## [1.0.0-alpha.6] - 2019-04-14 ### Changed -* Allow using any service as default service. +- Allow using any service as default service. -* Remove generic type for request payload, always use default. +- Remove generic type for request payload, always use default. -* Removed `Decompress` middleware. Bytes, String, Json, Form extractors +- Removed `Decompress` middleware. Bytes, String, Json, Form extractors automatically decompress payload. -* Make extractor config type explicit. Add `FromRequest::Config` associated type. +- Make extractor config type explicit. Add `FromRequest::Config` associated type. ## [1.0.0-alpha.5] - 2019-04-12 ### Added -* Added async io `TestBuffer` for testing. +- Added async io `TestBuffer` for testing. ### Deleted -* Removed native-tls support +- Removed native-tls support ## [1.0.0-alpha.4] - 2019-04-08 ### Added -* `App::configure()` allow to offload app configuration to different methods +- `App::configure()` allow to offload app configuration to different methods -* Added `URLPath` option for logger +- Added `URLPath` option for logger -* Added `ServiceRequest::app_data()`, returns `Data` +- Added `ServiceRequest::app_data()`, returns `Data` -* Added `ServiceFromRequest::app_data()`, returns `Data` +- Added `ServiceFromRequest::app_data()`, returns `Data` ### Changed -* `FromRequest` trait refactoring +- `FromRequest` trait refactoring -* Move multipart support to actix-multipart crate +- Move multipart support to actix-multipart crate ### Fixed -* Fix body propagation in Response::from_error. #760 +- Fix body propagation in Response::from_error. #760 ## [1.0.0-alpha.3] - 2019-04-02 ### Changed -* Renamed `TestRequest::to_service()` to `TestRequest::to_srv_request()` +- Renamed `TestRequest::to_service()` to `TestRequest::to_srv_request()` -* Renamed `TestRequest::to_response()` to `TestRequest::to_srv_response()` +- Renamed `TestRequest::to_response()` to `TestRequest::to_srv_response()` -* Removed `Deref` impls +- Removed `Deref` impls ### Removed -* Removed unused `actix_web::web::md()` +- Removed unused `actix_web::web::md()` ## [1.0.0-alpha.2] - 2019-03-29 ### Added -* Rustls support +- Rustls support ### Changed -* Use forked cookie +- Use forked cookie -* Multipart::Field renamed to MultipartField +- Multipart::Field renamed to MultipartField ## [1.0.0-alpha.1] - 2019-03-28 ### Changed -* Complete architecture re-design. +- Complete architecture re-design. -* Return 405 response if no matching route found within resource #538 +- Return 405 response if no matching route found within resource #538 diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index ae97b3240..dbd092095 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -8,19 +8,19 @@ In the interest of fostering an open and welcoming environment, we as contributo Examples of behavior that contributes to creating a positive environment include: -* Using welcoming and inclusive language -* Being respectful of differing viewpoints and experiences -* Gracefully accepting constructive criticism -* Focusing on what is best for the community -* Showing empathy towards other community members +- Using welcoming and inclusive language +- Being respectful of differing viewpoints and experiences +- Gracefully accepting constructive criticism +- Focusing on what is best for the community +- Showing empathy towards other community members Examples of unacceptable behavior by participants include: -* The use of sexualized language or imagery and unwelcome sexual attention or advances -* Trolling, insulting/derogatory comments, and personal or political attacks -* Public or private harassment -* Publishing others' private information, such as a physical or electronic address, without explicit permission -* Other conduct which could reasonably be considered inappropriate in a professional setting +- The use of sexualized language or imagery and unwelcome sexual attention or advances +- Trolling, insulting/derogatory comments, and personal or political attacks +- Public or private harassment +- Publishing others' private information, such as a physical or electronic address, without explicit permission +- Other conduct which could reasonably be considered inappropriate in a professional setting ## Our Responsibilities diff --git a/MIGRATION.md b/MIGRATION.md index d53bd7bf8..338a04389 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -1,6 +1,6 @@ ## Unreleased -* The default `NormalizePath` behavior now strips trailing slashes by default. This was +- The default `NormalizePath` behavior now strips trailing slashes by default. This was previously documented to be the case in v3 but the behavior now matches. The effect is that routes defined with trailing slashes will become inaccessible when using `NormalizePath::default()`. As such, calling `NormalizePath::default()` will log a warning. @@ -11,9 +11,9 @@ Alternatively, explicitly require trailing slashes: `NormalizePath::new(TrailingSlash::Always)`. -* The `type Config` of `FromRequest` was removed. +- The `type Config` of `FromRequest` was removed. -* Feature flag `compress` has been split into its supported algorithm (brotli, gzip, zstd). +- Feature flag `compress` has been split into its supported algorithm (brotli, gzip, zstd). By default all compression algorithms are enabled. To select algorithm you want to include with `middleware::Compress` use following flags: - `compress-brotli` @@ -28,30 +28,30 @@ ## 3.0.0 -* The return type for `ServiceRequest::app_data::()` was changed from returning a `Data` to +- The return type for `ServiceRequest::app_data::()` was changed from returning a `Data` to simply a `T`. To access a `Data` use `ServiceRequest::app_data::>()`. -* Cookie handling has been offloaded to the `cookie` crate: +- Cookie handling has been offloaded to the `cookie` crate: * `USERINFO_ENCODE_SET` is no longer exposed. Percent-encoding is still supported; check docs. * Some types now require lifetime parameters. -* The time crate was updated to `v0.2`, a major breaking change to the time crate, which affects +- The time crate was updated to `v0.2`, a major breaking change to the time crate, which affects any `actix-web` method previously expecting a time v0.1 input. -* Setting a cookie's SameSite property, explicitly, to `SameSite::None` will now +- Setting a cookie's SameSite property, explicitly, to `SameSite::None` will now result in `SameSite=None` being sent with the response Set-Cookie header. To create a cookie without a SameSite attribute, remove any calls setting same_site. -* actix-http support for Actors messages was moved to actix-http crate and is enabled +- actix-http support for Actors messages was moved to actix-http crate and is enabled with feature `actors` -* content_length function is removed from actix-http. +- content_length function is removed from actix-http. You can set Content-Length by normally setting the response body or calling no_chunking function. -* `BodySize::Sized64` variant has been removed. `BodySize::Sized` now receives a +- `BodySize::Sized64` variant has been removed. `BodySize::Sized` now receives a `u64` instead of a `usize`. -* Code that was using `path.` to access a `web::Path<(A, B, C)>`s elements now needs to use +- Code that was using `path.` to access a `web::Path<(A, B, C)>`s elements now needs to use destructuring or `.into_inner()`. For example: ```rust @@ -71,35 +71,35 @@ } ``` -* `middleware::NormalizePath` can now also be configured to trim trailing slashes instead of always keeping one. +- `middleware::NormalizePath` can now also be configured to trim trailing slashes instead of always keeping one. It will need `middleware::normalize::TrailingSlash` when being constructed with `NormalizePath::new(...)`, or for an easier migration you can replace `wrap(middleware::NormalizePath)` with `wrap(middleware::NormalizePath::new(TrailingSlash::MergeOnly))`. -* `HttpServer::maxconn` is renamed to the more expressive `HttpServer::max_connections`. +- `HttpServer::maxconn` is renamed to the more expressive `HttpServer::max_connections`. -* `HttpServer::maxconnrate` is renamed to the more expressive `HttpServer::max_connection_rate`. +- `HttpServer::maxconnrate` is renamed to the more expressive `HttpServer::max_connection_rate`. ## 2.0.0 -* `HttpServer::start()` renamed to `HttpServer::run()`. It also possible to +- `HttpServer::start()` renamed to `HttpServer::run()`. It also possible to `.await` on `run` method result, in that case it awaits server exit. -* `App::register_data()` renamed to `App::app_data()` and accepts any type `T: 'static`. +- `App::register_data()` renamed to `App::app_data()` and accepts any type `T: 'static`. Stored data is available via `HttpRequest::app_data()` method at runtime. -* Extractor configuration must be registered with `App::app_data()` instead of `App::data()` +- Extractor configuration must be registered with `App::app_data()` instead of `App::data()` -* Sync handlers has been removed. `.to_async()` method has been renamed to `.to()` +- Sync handlers has been removed. `.to_async()` method has been renamed to `.to()` replace `fn` with `async fn` to convert sync handler to async -* `actix_http_test::TestServer` moved to `actix_web::test` module. To start +- `actix_http_test::TestServer` moved to `actix_web::test` module. To start test server use `test::start()` or `test_start_with_config()` methods -* `ResponseError` trait has been reafctored. `ResponseError::error_response()` renders +- `ResponseError` trait has been reafctored. `ResponseError::error_response()` renders http response. -* Feature `rust-tls` renamed to `rustls` +- Feature `rust-tls` renamed to `rustls` instead of @@ -113,7 +113,7 @@ actix-web = { version = "2.0.0", features = ["rustls"] } ``` -* Feature `ssl` renamed to `openssl` +- Feature `ssl` renamed to `openssl` instead of @@ -126,11 +126,11 @@ ```rust actix-web = { version = "2.0.0", features = ["openssl"] } ``` -* `Cors` builder now requires that you call `.finish()` to construct the middleware +- `Cors` builder now requires that you call `.finish()` to construct the middleware ## 1.0.1 -* Cors middleware has been moved to `actix-cors` crate +- Cors middleware has been moved to `actix-cors` crate instead of @@ -144,7 +144,7 @@ use actix_cors::Cors; ``` -* Identity middleware has been moved to `actix-identity` crate +- Identity middleware has been moved to `actix-identity` crate instead of @@ -161,7 +161,7 @@ ## 1.0.0 -* Extractor configuration. In version 1.0 this is handled with the new `Data` mechanism for both setting and retrieving the configuration +- Extractor configuration. In version 1.0 this is handled with the new `Data` mechanism for both setting and retrieving the configuration instead of @@ -219,7 +219,7 @@ ) ``` -* Resource registration. 1.0 version uses generalized resource +- Resource registration. 1.0 version uses generalized resource registration via `.service()` method. instead of @@ -239,7 +239,7 @@ .route(web::post().to(post_handler)) ``` -* Scope registration. +- Scope registration. instead of @@ -263,7 +263,7 @@ ); ``` -* `.with()`, `.with_async()` registration methods have been renamed to `.to()` and `.to_async()`. +- `.with()`, `.with_async()` registration methods have been renamed to `.to()` and `.to_async()`. instead of @@ -277,7 +277,7 @@ App.new().service(web::resource("/welcome").to(welcome)) ``` -* Passing arguments to handler with extractors, multiple arguments are allowed +- Passing arguments to handler with extractors, multiple arguments are allowed instead of @@ -295,7 +295,7 @@ } ``` -* `.f()`, `.a()` and `.h()` handler registration methods have been removed. +- `.f()`, `.a()` and `.h()` handler registration methods have been removed. Use `.to()` for handlers and `.to_async()` for async handlers. Handler function must use extractors. @@ -311,7 +311,7 @@ App.new().service(web::resource("/welcome").to(welcome)) ``` -* `HttpRequest` does not provide access to request's payload stream. +- `HttpRequest` does not provide access to request's payload stream. instead of @@ -341,7 +341,7 @@ } ``` -* `State` is now `Data`. You register Data during the App initialization process +- `State` is now `Data`. You register Data during the App initialization process and then access it from handlers either using a Data extractor or using HttpRequest's api. @@ -377,7 +377,7 @@ ``` -* AsyncResponder is removed, use `.to_async()` registration method and `impl Future<>` as result type. +- AsyncResponder is removed, use `.to_async()` registration method and `impl Future<>` as result type. instead of @@ -393,7 +393,7 @@ .. simply omit AsyncResponder and the corresponding responder() finish method -* Middleware +- Middleware instead of @@ -410,7 +410,7 @@ .route("/index.html", web::get().to(index)); ``` -* `HttpRequest::body()`, `HttpRequest::urlencoded()`, `HttpRequest::json()`, `HttpRequest::multipart()` +- `HttpRequest::body()`, `HttpRequest::urlencoded()`, `HttpRequest::json()`, `HttpRequest::multipart()` method have been removed. Use `Bytes`, `String`, `Form`, `Json`, `Multipart` extractors instead. instead of @@ -432,9 +432,9 @@ } ``` -* `actix_web::server` module has been removed. To start http server use `actix_web::HttpServer` type +- `actix_web::server` module has been removed. To start http server use `actix_web::HttpServer` type -* StaticFiles and NamedFile have been moved to a separate crate. +- StaticFiles and NamedFile have been moved to a separate crate. instead of `use actix_web::fs::StaticFile` @@ -444,20 +444,20 @@ use `use actix_files::NamedFile` -* Multipart has been moved to a separate crate. +- Multipart has been moved to a separate crate. instead of `use actix_web::multipart::Multipart` use `use actix_multipart::Multipart` -* Response compression is not enabled by default. +- Response compression is not enabled by default. To enable, use `Compress` middleware, `App::new().wrap(Compress::default())`. -* Session middleware moved to actix-session crate +- Session middleware moved to actix-session crate -* Actors support have been moved to `actix-web-actors` crate +- Actors support have been moved to `actix-web-actors` crate -* Custom Error +- Custom Error Instead of error_response method alone, ResponseError now provides two methods: error_response and render_response respectively. Where, error_response creates the error response and render_response returns the error response to the caller. @@ -471,7 +471,7 @@ ## 0.7.15 -* The `' '` character is not percent decoded anymore before matching routes. If you need to use it in +- The `' '` character is not percent decoded anymore before matching routes. If you need to use it in your routes, you should use `%20`. instead of @@ -496,18 +496,18 @@ } ``` -* If you used `AsyncResult::async` you need to replace it with `AsyncResult::future` +- If you used `AsyncResult::async` you need to replace it with `AsyncResult::future` ## 0.7.4 -* `Route::with_config()`/`Route::with_async_config()` always passes configuration objects as tuple +- `Route::with_config()`/`Route::with_async_config()` always passes configuration objects as tuple even for handler with one parameter. ## 0.7 -* `HttpRequest` does not implement `Stream` anymore. If you need to read request payload +- `HttpRequest` does not implement `Stream` anymore. If you need to read request payload use `HttpMessage::payload()` method. instead of @@ -533,10 +533,10 @@ } ``` -* [Middleware](https://actix.rs/actix-web/actix_web/middleware/trait.Middleware.html) +- [Middleware](https://actix.rs/actix-web/actix_web/middleware/trait.Middleware.html) trait uses `&HttpRequest` instead of `&mut HttpRequest`. -* Removed `Route::with2()` and `Route::with3()` use tuple of extractors instead. +- Removed `Route::with2()` and `Route::with3()` use tuple of extractors instead. instead of @@ -550,17 +550,17 @@ fn index((query, json): (Query<..>, Json impl Responder {} ``` -* `Handler::handle()` uses `&self` instead of `&mut self` +- `Handler::handle()` uses `&self` instead of `&mut self` -* `Handler::handle()` accepts reference to `HttpRequest<_>` instead of value +- `Handler::handle()` accepts reference to `HttpRequest<_>` instead of value -* Removed deprecated `HttpServer::threads()`, use +- Removed deprecated `HttpServer::threads()`, use [HttpServer::workers()](https://actix.rs/actix-web/actix_web/server/struct.HttpServer.html#method.workers) instead. -* Renamed `client::ClientConnectorError::Connector` to +- Renamed `client::ClientConnectorError::Connector` to `client::ClientConnectorError::Resolver` -* `Route::with()` does not return `ExtractorConfig`, to configure +- `Route::with()` does not return `ExtractorConfig`, to configure extractor use `Route::with_config()` instead of @@ -589,26 +589,26 @@ } ``` -* `Route::with_async()` does not return `ExtractorConfig`, to configure +- `Route::with_async()` does not return `ExtractorConfig`, to configure extractor use `Route::with_async_config()` ## 0.6 -* `Path` extractor return `ErrorNotFound` on failure instead of `ErrorBadRequest` +- `Path` extractor return `ErrorNotFound` on failure instead of `ErrorBadRequest` -* `ws::Message::Close` now includes optional close reason. +- `ws::Message::Close` now includes optional close reason. `ws::CloseCode::Status` and `ws::CloseCode::Empty` have been removed. -* `HttpServer::threads()` renamed to `HttpServer::workers()`. +- `HttpServer::threads()` renamed to `HttpServer::workers()`. -* `HttpServer::start_ssl()` and `HttpServer::start_tls()` deprecated. +- `HttpServer::start_ssl()` and `HttpServer::start_tls()` deprecated. Use `HttpServer::bind_ssl()` and `HttpServer::bind_tls()` instead. -* `HttpRequest::extensions()` returns read only reference to the request's Extension +- `HttpRequest::extensions()` returns read only reference to the request's Extension `HttpRequest::extensions_mut()` returns mutable reference. -* Instead of +- Instead of `use actix_web::middleware::{ CookieSessionBackend, CookieSessionError, RequestSession, @@ -619,15 +619,15 @@ `use actix_web::middleware::session{CookieSessionBackend, CookieSessionError, RequestSession, Session, SessionBackend, SessionImpl, SessionStorage};` -* `FromRequest::from_request()` accepts mutable reference to a request +- `FromRequest::from_request()` accepts mutable reference to a request -* `FromRequest::Result` has to implement `Into>` +- `FromRequest::Result` has to implement `Into>` -* [`Responder::respond_to()`]( +- [`Responder::respond_to()`]( https://actix.rs/actix-web/actix_web/trait.Responder.html#tymethod.respond_to) is generic over `S` -* Use `Query` extractor instead of HttpRequest::query()`. +- Use `Query` extractor instead of HttpRequest::query()`. ```rust fn index(q: Query>) -> Result<..> { @@ -641,37 +641,37 @@ let q = Query::>::extract(req); ``` -* Websocket operations are implemented as `WsWriter` trait. +- Websocket operations are implemented as `WsWriter` trait. you need to use `use actix_web::ws::WsWriter` ## 0.5 -* `HttpResponseBuilder::body()`, `.finish()`, `.json()` +- `HttpResponseBuilder::body()`, `.finish()`, `.json()` methods return `HttpResponse` instead of `Result` -* `actix_web::Method`, `actix_web::StatusCode`, `actix_web::Version` +- `actix_web::Method`, `actix_web::StatusCode`, `actix_web::Version` moved to `actix_web::http` module -* `actix_web::header` moved to `actix_web::http::header` +- `actix_web::header` moved to `actix_web::http::header` -* `NormalizePath` moved to `actix_web::http` module +- `NormalizePath` moved to `actix_web::http` module -* `HttpServer` moved to `actix_web::server`, added new `actix_web::server::new()` function, +- `HttpServer` moved to `actix_web::server`, added new `actix_web::server::new()` function, shortcut for `actix_web::server::HttpServer::new()` -* `DefaultHeaders` middleware does not use separate builder, all builder methods moved to type itself +- `DefaultHeaders` middleware does not use separate builder, all builder methods moved to type itself -* `StaticFiles::new()`'s show_index parameter removed, use `show_files_listing()` method instead. +- `StaticFiles::new()`'s show_index parameter removed, use `show_files_listing()` method instead. -* `CookieSessionBackendBuilder` removed, all methods moved to `CookieSessionBackend` type +- `CookieSessionBackendBuilder` removed, all methods moved to `CookieSessionBackend` type -* `actix_web::httpcodes` module is deprecated, `HttpResponse::Ok()`, `HttpResponse::Found()` and other `HttpResponse::XXX()` +- `actix_web::httpcodes` module is deprecated, `HttpResponse::Ok()`, `HttpResponse::Found()` and other `HttpResponse::XXX()` functions should be used instead -* `ClientRequestBuilder::body()` returns `Result<_, actix_web::Error>` +- `ClientRequestBuilder::body()` returns `Result<_, actix_web::Error>` instead of `Result<_, http::Error>` -* `Application` renamed to a `App` +- `Application` renamed to a `App` -* `actix_web::Reply`, `actix_web::Resource` moved to `actix_web::dev` +- `actix_web::Reply`, `actix_web::Resource` moved to `actix_web::dev` diff --git a/README.md b/README.md index 5cce9f3b9..a9afbf386 100644 --- a/README.md +++ b/README.md @@ -21,25 +21,25 @@ ## Features -* Supports *HTTP/1.x* and *HTTP/2* -* Streaming and pipelining -* Keep-alive and slow requests handling -* Client/server [WebSockets](https://actix.rs/docs/websockets/) support -* Transparent content compression/decompression (br, gzip, deflate, zstd) -* Powerful [request routing](https://actix.rs/docs/url-dispatch/) -* Multipart streams -* Static assets -* SSL support using OpenSSL or Rustls -* Middlewares ([Logger, Session, CORS, etc](https://actix.rs/docs/middleware/)) -* Includes an async [HTTP client](https://docs.rs/awc/) -* Runs on stable Rust 1.52+ +- Supports *HTTP/1.x* and *HTTP/2* +- Streaming and pipelining +- Keep-alive and slow requests handling +- Client/server [WebSockets](https://actix.rs/docs/websockets/) support +- Transparent content compression/decompression (br, gzip, deflate, zstd) +- Powerful [request routing](https://actix.rs/docs/url-dispatch/) +- Multipart streams +- Static assets +- SSL support using OpenSSL or Rustls +- Middlewares ([Logger, Session, CORS, etc](https://actix.rs/docs/middleware/)) +- Includes an async [HTTP client](https://docs.rs/awc/) +- Runs on stable Rust 1.52+ ## Documentation -* [Website & User Guide](https://actix.rs) -* [Examples Repository](https://github.com/actix/examples) -* [API Documentation](https://docs.rs/actix-web) -* [API Documentation (master branch)](https://actix.rs/actix-web/actix_web) +- [Website & User Guide](https://actix.rs) +- [Examples Repository](https://github.com/actix/examples) +- [API Documentation](https://docs.rs/actix-web) +- [API Documentation (master branch)](https://actix.rs/actix-web/actix_web) ## Example @@ -71,18 +71,18 @@ async fn main() -> std::io::Result<()> { ### More examples -* [Basic Setup](https://github.com/actix/examples/tree/master/basics/basics/) -* [Application State](https://github.com/actix/examples/tree/master/basics/state/) -* [JSON Handling](https://github.com/actix/examples/tree/master/json/json/) -* [Multipart Streams](https://github.com/actix/examples/tree/master/forms/multipart/) -* [Diesel Integration](https://github.com/actix/examples/tree/master/database_interactions/diesel/) -* [r2d2 Integration](https://github.com/actix/examples/tree/master/database_interactions/r2d2/) -* [Simple WebSocket](https://github.com/actix/examples/tree/master/websockets/websocket/) -* [Tera Templates](https://github.com/actix/examples/tree/master/template_engines/tera/) -* [Askama Templates](https://github.com/actix/examples/tree/master/template_engines/askama/) -* [HTTPS using Rustls](https://github.com/actix/examples/tree/master/security/rustls/) -* [HTTPS using OpenSSL](https://github.com/actix/examples/tree/master/security/openssl/) -* [WebSocket Chat](https://github.com/actix/examples/tree/master/websockets/chat/) +- [Basic Setup](https://github.com/actix/examples/tree/master/basics/basics/) +- [Application State](https://github.com/actix/examples/tree/master/basics/state/) +- [JSON Handling](https://github.com/actix/examples/tree/master/json/json/) +- [Multipart Streams](https://github.com/actix/examples/tree/master/forms/multipart/) +- [Diesel Integration](https://github.com/actix/examples/tree/master/database_interactions/diesel/) +- [r2d2 Integration](https://github.com/actix/examples/tree/master/database_interactions/r2d2/) +- [Simple WebSocket](https://github.com/actix/examples/tree/master/websockets/websocket/) +- [Tera Templates](https://github.com/actix/examples/tree/master/template_engines/tera/) +- [Askama Templates](https://github.com/actix/examples/tree/master/template_engines/askama/) +- [HTTPS using Rustls](https://github.com/actix/examples/tree/master/security/rustls/) +- [HTTPS using OpenSSL](https://github.com/actix/examples/tree/master/security/openssl/) +- [WebSocket Chat](https://github.com/actix/examples/tree/master/websockets/chat/) You may consider checking out [this directory](https://github.com/actix/examples/tree/master/) for more examples. @@ -96,9 +96,9 @@ One of the fastest web frameworks available according to the This project is licensed under either of -* Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or +- Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or [http://www.apache.org/licenses/LICENSE-2.0]) -* MIT license ([LICENSE-MIT](LICENSE-MIT) or +- MIT license ([LICENSE-MIT](LICENSE-MIT) or [http://opensource.org/licenses/MIT]) at your option. diff --git a/actix-files/CHANGES.md b/actix-files/CHANGES.md index d6b39e28f..ef8eba0fc 100644 --- a/actix-files/CHANGES.md +++ b/actix-files/CHANGES.md @@ -4,42 +4,42 @@ ## 0.6.0-beta.10 - 2021-12-11 -* No significant changes since `0.6.0-beta.9`. +- No significant changes since `0.6.0-beta.9`. ## 0.6.0-beta.9 - 2021-11-22 -* Add crate feature `experimental-io-uring`, enabling async file I/O to be utilized. This feature is only available on Linux OSes with recent kernel versions. This feature is semver-exempt. [#2408] -* Add `NamedFile::open_async`. [#2408] -* Fix 304 Not Modified responses to omit the Content-Length header, as per the spec. [#2453] -* The `Responder` impl for `NamedFile` now has a boxed future associated type. [#2408] -* The `Service` impl for `NamedFileService` now has a boxed future associated type. [#2408] -* Add `impl Clone` for `FilesService`. [#2408] +- Add crate feature `experimental-io-uring`, enabling async file I/O to be utilized. This feature is only available on Linux OSes with recent kernel versions. This feature is semver-exempt. [#2408] +- Add `NamedFile::open_async`. [#2408] +- Fix 304 Not Modified responses to omit the Content-Length header, as per the spec. [#2453] +- The `Responder` impl for `NamedFile` now has a boxed future associated type. [#2408] +- The `Service` impl for `NamedFileService` now has a boxed future associated type. [#2408] +- Add `impl Clone` for `FilesService`. [#2408] [#2408]: https://github.com/actix/actix-web/pull/2408 [#2453]: https://github.com/actix/actix-web/pull/2453 ## 0.6.0-beta.8 - 2021-10-20 -* Minimum supported Rust version (MSRV) is now 1.52. +- Minimum supported Rust version (MSRV) is now 1.52. ## 0.6.0-beta.7 - 2021-09-09 -* Minimum supported Rust version (MSRV) is now 1.51. +- Minimum supported Rust version (MSRV) is now 1.51. ## 0.6.0-beta.6 - 2021-06-26 -* Added `Files::path_filter()`. [#2274] -* `Files::show_files_listing()` can now be used with `Files::index_file()` to show files listing as a fallback when the index file is not found. [#2228] +- Added `Files::path_filter()`. [#2274] +- `Files::show_files_listing()` can now be used with `Files::index_file()` to show files listing as a fallback when the index file is not found. [#2228] [#2274]: https://github.com/actix/actix-web/pull/2274 [#2228]: https://github.com/actix/actix-web/pull/2228 ## 0.6.0-beta.5 - 2021-06-17 -* `NamedFile` now implements `ServiceFactory` and `HttpServiceFactory` making it much more useful in routing. For example, it can be used directly as a default service. [#2135] -* For symbolic links, `Content-Disposition` header no longer shows the filename of the original file. [#2156] -* `Files::redirect_to_slash_directory()` now works as expected when used with `Files::show_files_listing()`. [#2225] -* `application/{javascript, json, wasm}` mime type now have `inline` disposition by default. [#2257] +- `NamedFile` now implements `ServiceFactory` and `HttpServiceFactory` making it much more useful in routing. For example, it can be used directly as a default service. [#2135] +- For symbolic links, `Content-Disposition` header no longer shows the filename of the original file. [#2156] +- `Files::redirect_to_slash_directory()` now works as expected when used with `Files::show_files_listing()`. [#2225] +- `application/{javascript, json, wasm}` mime type now have `inline` disposition by default. [#2257] [#2135]: https://github.com/actix/actix-web/pull/2135 [#2156]: https://github.com/actix/actix-web/pull/2156 @@ -48,130 +48,130 @@ ## 0.6.0-beta.4 - 2021-04-02 -* Add support for `.guard` in `Files` to selectively filter `Files` services. [#2046] +- Add support for `.guard` in `Files` to selectively filter `Files` services. [#2046] [#2046]: https://github.com/actix/actix-web/pull/2046 ## 0.6.0-beta.3 - 2021-03-09 -* No notable changes. +- No notable changes. ## 0.6.0-beta.2 - 2021-02-10 -* Fix If-Modified-Since and If-Unmodified-Since to not compare using sub-second timestamps. [#1887] -* Replace `v_htmlescape` with `askama_escape`. [#1953] +- Fix If-Modified-Since and If-Unmodified-Since to not compare using sub-second timestamps. [#1887] +- Replace `v_htmlescape` with `askama_escape`. [#1953] [#1887]: https://github.com/actix/actix-web/pull/1887 [#1953]: https://github.com/actix/actix-web/pull/1953 ## 0.6.0-beta.1 - 2021-01-07 -* `HttpRange::parse` now has its own error type. -* Update `bytes` to `1.0`. [#1813] +- `HttpRange::parse` now has its own error type. +- Update `bytes` to `1.0`. [#1813] [#1813]: https://github.com/actix/actix-web/pull/1813 ## 0.5.0 - 2020-12-26 -* Optionally support hidden files/directories. [#1811] +- Optionally support hidden files/directories. [#1811] [#1811]: https://github.com/actix/actix-web/pull/1811 ## 0.4.1 - 2020-11-24 -* Clarify order of parameters in `Files::new` and improve docs. +- Clarify order of parameters in `Files::new` and improve docs. ## 0.4.0 - 2020-10-06 -* Add `Files::prefer_utf8` option that adds UTF-8 charset on certain response types. [#1714] +- Add `Files::prefer_utf8` option that adds UTF-8 charset on certain response types. [#1714] [#1714]: https://github.com/actix/actix-web/pull/1714 ## 0.3.0 - 2020-09-11 -* No significant changes from 0.3.0-beta.1. +- No significant changes from 0.3.0-beta.1. ## 0.3.0-beta.1 - 2020-07-15 -* Update `v_htmlescape` to 0.10 -* Update `actix-web` and `actix-http` dependencies to beta.1 +- Update `v_htmlescape` to 0.10 +- Update `actix-web` and `actix-http` dependencies to beta.1 ## 0.3.0-alpha.1 - 2020-05-23 -* Update `actix-web` and `actix-http` dependencies to alpha -* Fix some typos in the docs -* Bump minimum supported Rust version to 1.40 -* Support sending Content-Length when Content-Range is specified [#1384] +- Update `actix-web` and `actix-http` dependencies to alpha +- Fix some typos in the docs +- Bump minimum supported Rust version to 1.40 +- Support sending Content-Length when Content-Range is specified [#1384] [#1384]: https://github.com/actix/actix-web/pull/1384 ## 0.2.1 - 2019-12-22 -* Use the same format for file URLs regardless of platforms +- Use the same format for file URLs regardless of platforms ## 0.2.0 - 2019-12-20 -* Fix BodyEncoding trait import #1220 +- Fix BodyEncoding trait import #1220 ## 0.2.0-alpha.1 - 2019-12-07 -* Migrate to `std::future` +- Migrate to `std::future` ## 0.1.7 - 2019-11-06 -* Add an additional `filename*` param in the `Content-Disposition` header of +- Add an additional `filename*` param in the `Content-Disposition` header of `actix_files::NamedFile` to be more compatible. (#1151) ## 0.1.6 - 2019-10-14 -* Add option to redirect to a slash-ended path `Files` #1132 +- Add option to redirect to a slash-ended path `Files` #1132 ## 0.1.5 - 2019-10-08 -* Bump up `mime_guess` crate version to 2.0.1 -* Bump up `percent-encoding` crate version to 2.1 -* Allow user defined request guards for `Files` #1113 +- Bump up `mime_guess` crate version to 2.0.1 +- Bump up `percent-encoding` crate version to 2.1 +- Allow user defined request guards for `Files` #1113 ## 0.1.4 - 2019-07-20 -* Allow to disable `Content-Disposition` header #686 +- Allow to disable `Content-Disposition` header #686 ## 0.1.3 - 2019-06-28 -* Do not set `Content-Length` header, let actix-http set it #930 +- Do not set `Content-Length` header, let actix-http set it #930 ## 0.1.2 - 2019-06-13 -* Content-Length is 0 for NamedFile HEAD request #914 -* Fix ring dependency from actix-web default features for #741 +- Content-Length is 0 for NamedFile HEAD request #914 +- Fix ring dependency from actix-web default features for #741 ## 0.1.1 - 2019-06-01 -* Static files are incorrectly served as both chunked and with length #812 +- Static files are incorrectly served as both chunked and with length #812 ## 0.1.0 - 2019-05-25 -* NamedFile last-modified check always fails due to nano-seconds in file modified date #820 +- NamedFile last-modified check always fails due to nano-seconds in file modified date #820 ## 0.1.0-beta.4 - 2019-05-12 -* Update actix-web to beta.4 +- Update actix-web to beta.4 ## 0.1.0-beta.1 - 2019-04-20 -* Update actix-web to beta.1 +- Update actix-web to beta.1 ## 0.1.0-alpha.6 - 2019-04-14 -* Update actix-web to alpha6 +- Update actix-web to alpha6 ## 0.1.0-alpha.4 - 2019-04-08 -* Update actix-web to alpha4 +- Update actix-web to alpha4 ## 0.1.0-alpha.2 - 2019-04-02 -* Add default handler support +- Add default handler support ## 0.1.0-alpha.1 - 2019-03-28 -* Initial impl +- Initial impl diff --git a/actix-http-test/CHANGES.md b/actix-http-test/CHANGES.md index 156012168..4e86e20e8 100644 --- a/actix-http-test/CHANGES.md +++ b/actix-http-test/CHANGES.md @@ -4,125 +4,125 @@ ## 3.0.0-beta.9 - 2021-12-11 -* No significant changes since `3.0.0-beta.8`. +- No significant changes since `3.0.0-beta.8`. ## 3.0.0-beta.8 - 2021-11-30 -* Update `actix-tls` to `3.0.0-rc.1`. [#2474] +- Update `actix-tls` to `3.0.0-rc.1`. [#2474] [#2474]: https://github.com/actix/actix-web/pull/2474 ## 3.0.0-beta.7 - 2021-11-22 -* Fix compatibility with experimental `io-uring` feature of `actix-rt`. [#2408] +- Fix compatibility with experimental `io-uring` feature of `actix-rt`. [#2408] [#2408]: https://github.com/actix/actix-web/pull/2408 ## 3.0.0-beta.6 - 2021-11-15 -* `TestServer::stop` is now async and will wait for the server and system to shutdown. [#2442] -* Update `actix-server` to `2.0.0-beta.9`. [#2442] -* Minimum supported Rust version (MSRV) is now 1.52. +- `TestServer::stop` is now async and will wait for the server and system to shutdown. [#2442] +- Update `actix-server` to `2.0.0-beta.9`. [#2442] +- Minimum supported Rust version (MSRV) is now 1.52. [#2442]: https://github.com/actix/actix-web/pull/2442 ## 3.0.0-beta.5 - 2021-09-09 -* Minimum supported Rust version (MSRV) is now 1.51. +- Minimum supported Rust version (MSRV) is now 1.51. ## 3.0.0-beta.4 - 2021-04-02 -* Added `TestServer::client_headers` method. [#2097] +- Added `TestServer::client_headers` method. [#2097] [#2097]: https://github.com/actix/actix-web/pull/2097 ## 3.0.0-beta.3 - 2021-03-09 -* No notable changes. +- No notable changes. ## 3.0.0-beta.2 - 2021-02-10 -* No notable changes. +- No notable changes. ## 3.0.0-beta.1 - 2021-01-07 -* Update `bytes` to `1.0`. [#1813] +- Update `bytes` to `1.0`. [#1813] [#1813]: https://github.com/actix/actix-web/pull/1813 ## 2.1.0 - 2020-11-25 -* Add ability to set address for `TestServer`. [#1645] -* Upgrade `base64` to `0.13`. -* Upgrade `serde_urlencoded` to `0.7`. [#1773] +- Add ability to set address for `TestServer`. [#1645] +- Upgrade `base64` to `0.13`. +- Upgrade `serde_urlencoded` to `0.7`. [#1773] [#1773]: https://github.com/actix/actix-web/pull/1773 [#1645]: https://github.com/actix/actix-web/pull/1645 ## 2.0.0 - 2020-09-11 -* Update actix-codec and actix-utils dependencies. +- Update actix-codec and actix-utils dependencies. ## 2.0.0-alpha.1 - 2020-05-23 -* Update the `time` dependency to 0.2.7 -* Update `actix-connect` dependency to 2.0.0-alpha.2 -* Make `test_server` `async` fn. -* Bump minimum supported Rust version to 1.40 -* Replace deprecated `net2` crate with `socket2` -* Update `base64` dependency to 0.12 -* Update `env_logger` dependency to 0.7 +- Update the `time` dependency to 0.2.7 +- Update `actix-connect` dependency to 2.0.0-alpha.2 +- Make `test_server` `async` fn. +- Bump minimum supported Rust version to 1.40 +- Replace deprecated `net2` crate with `socket2` +- Update `base64` dependency to 0.12 +- Update `env_logger` dependency to 0.7 ## 1.0.0 - 2019-12-13 -* Replaced `TestServer::start()` with `test_server()` +- Replaced `TestServer::start()` with `test_server()` ## 1.0.0-alpha.3 - 2019-12-07 -* Migrate to `std::future` +- Migrate to `std::future` ## 0.2.5 - 2019-09-17 -* Update serde_urlencoded to "0.6.1" -* Increase TestServerRuntime timeouts from 500ms to 3000ms -* Do not override current `System` +- Update serde_urlencoded to "0.6.1" +- Increase TestServerRuntime timeouts from 500ms to 3000ms +- Do not override current `System` ## 0.2.4 - 2019-07-18 -* Update actix-server to 0.6 +- Update actix-server to 0.6 ## 0.2.3 - 2019-07-16 -* Add `delete`, `options`, `patch` methods to `TestServerRunner` +- Add `delete`, `options`, `patch` methods to `TestServerRunner` ## 0.2.2 - 2019-06-16 -* Add .put() and .sput() methods +- Add .put() and .sput() methods ## 0.2.1 - 2019-06-05 -* Add license files +- Add license files ## 0.2.0 - 2019-05-12 -* Update awc and actix-http deps +- Update awc and actix-http deps ## 0.1.1 - 2019-04-24 -* Always make new connection for http client +- Always make new connection for http client ## 0.1.0 - 2019-04-16 -* No changes +- No changes ## 0.1.0-alpha.3 - 2019-04-02 -* Request functions accept path #743 +- Request functions accept path #743 ## 0.1.0-alpha.2 - 2019-03-29 -* Added TestServerRuntime::load_body() method -* Update actix-http and awc libraries +- Added TestServerRuntime::load_body() method +- Update actix-http and awc libraries ## 0.1.0-alpha.1 - 2019-03-28 -* Initial impl +- Initial impl diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index ad98d132a..3b45e934f 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -2,22 +2,22 @@ ## Unreleased - 2021-xx-xx ### Changes -* `HeaderMap::get_all` now returns a `std::slice::Iter`. [#2527] +- `HeaderMap::get_all` now returns a `std::slice::Iter`. [#2527] [#2527]: https://github.com/actix/actix-web/pull/2527 ## 3.0.0-beta.16 - 2021-12-17 ### Added -* New method on `MessageBody` trait, `try_into_bytes`, with default implementation, for optimizations on body types that complete in exactly one poll. Replaces `is_complete_body` and `take_complete_body`. [#2522] +- New method on `MessageBody` trait, `try_into_bytes`, with default implementation, for optimizations on body types that complete in exactly one poll. Replaces `is_complete_body` and `take_complete_body`. [#2522] ### Changed -* Rename trait `IntoHeaderPair => TryIntoHeaderPair`. [#2510] -* Rename `TryIntoHeaderPair::{try_into_header_pair => try_into_pair}`. [#2510] -* Rename trait `IntoHeaderValue => TryIntoHeaderValue`. [#2510] +- Rename trait `IntoHeaderPair => TryIntoHeaderPair`. [#2510] +- Rename `TryIntoHeaderPair::{try_into_header_pair => try_into_pair}`. [#2510] +- Rename trait `IntoHeaderValue => TryIntoHeaderValue`. [#2510] ### Removed -* `MessageBody::{is_complete_body,take_complete_body}`. [#2522] +- `MessageBody::{is_complete_body,take_complete_body}`. [#2522] [#2510]: https://github.com/actix/actix-web/pull/2510 [#2522]: https://github.com/actix/actix-web/pull/2522 @@ -25,43 +25,43 @@ ## 3.0.0-beta.15 - 2021-12-11 ### Added -* Add timeout for canceling HTTP/2 server side connection handshake. Default to 5 seconds. [#2483] -* HTTP/2 handshake timeout can be configured with `ServiceConfig::client_timeout`. [#2483] -* `Response::map_into_boxed_body`. [#2468] -* `body::EitherBody` enum. [#2468] -* `body::None` struct. [#2468] -* Impl `MessageBody` for `bytestring::ByteString`. [#2468] -* `impl Clone for ws::HandshakeError`. [#2468] -* `#[must_use]` for `ws::Codec` to prevent subtle bugs. [#1920] -* `impl Default ` for `ws::Codec`. [#1920] -* `header::QualityItem::{max, min}`. [#2486] -* `header::Quality::{MAX, MIN}`. [#2486] -* `impl Display` for `header::Quality`. [#2486] -* Connection data set through the `on_connect_ext` callbacks is now accessible only from the new `Request::conn_data()` method. [#2491] -* `Request::take_conn_data()`. [#2491] -* `Request::take_req_data()`. [#2487] -* `impl Clone` for `RequestHead`. [#2487] -* New methods on `MessageBody` trait, `is_complete_body` and `take_complete_body`, both with default implementations, for optimizations on body types that are done in exactly one poll/chunk. [#2497] -* New `boxed` method on `MessageBody` trait for wrapping body type. [#2520] +- Add timeout for canceling HTTP/2 server side connection handshake. Default to 5 seconds. [#2483] +- HTTP/2 handshake timeout can be configured with `ServiceConfig::client_timeout`. [#2483] +- `Response::map_into_boxed_body`. [#2468] +- `body::EitherBody` enum. [#2468] +- `body::None` struct. [#2468] +- Impl `MessageBody` for `bytestring::ByteString`. [#2468] +- `impl Clone for ws::HandshakeError`. [#2468] +- `#[must_use]` for `ws::Codec` to prevent subtle bugs. [#1920] +- `impl Default ` for `ws::Codec`. [#1920] +- `header::QualityItem::{max, min}`. [#2486] +- `header::Quality::{MAX, MIN}`. [#2486] +- `impl Display` for `header::Quality`. [#2486] +- Connection data set through the `on_connect_ext` callbacks is now accessible only from the new `Request::conn_data()` method. [#2491] +- `Request::take_conn_data()`. [#2491] +- `Request::take_req_data()`. [#2487] +- `impl Clone` for `RequestHead`. [#2487] +- New methods on `MessageBody` trait, `is_complete_body` and `take_complete_body`, both with default implementations, for optimizations on body types that are done in exactly one poll/chunk. [#2497] +- New `boxed` method on `MessageBody` trait for wrapping body type. [#2520] ### Changed -* Rename `body::BoxBody::{from_body => new}`. [#2468] -* Body type for `Responses` returned from `Response::{new, ok, etc...}` is now `BoxBody`. [#2468] -* The `Error` associated type on `MessageBody` type now requires `impl Error` (or similar). [#2468] -* Error types using in service builders now require `Into>`. [#2468] -* `From` implementations on error types now return a `Response`. [#2468] -* `ResponseBuilder::body(B)` now returns `Response>`. [#2468] -* `ResponseBuilder::finish()` now returns `Response>`. [#2468] +- Rename `body::BoxBody::{from_body => new}`. [#2468] +- Body type for `Responses` returned from `Response::{new, ok, etc...}` is now `BoxBody`. [#2468] +- The `Error` associated type on `MessageBody` type now requires `impl Error` (or similar). [#2468] +- Error types using in service builders now require `Into>`. [#2468] +- `From` implementations on error types now return a `Response`. [#2468] +- `ResponseBuilder::body(B)` now returns `Response>`. [#2468] +- `ResponseBuilder::finish()` now returns `Response>`. [#2468] ### Removed -* `ResponseBuilder::streaming`. [#2468] -* `impl Future` for `ResponseBuilder`. [#2468] -* Remove unnecessary `MessageBody` bound on types passed to `body::AnyBody::new`. [#2468] -* Move `body::AnyBody` to `awc`. Replaced with `EitherBody` and `BoxBody`. [#2468] -* `impl Copy` for `ws::Codec`. [#1920] -* `header::qitem` helper. Replaced with `header::QualityItem::max`. [#2486] -* `impl TryFrom` for `header::Quality`. [#2486] -* `http` module. Most everything it contained is exported at the crate root. [#2488] +- `ResponseBuilder::streaming`. [#2468] +- `impl Future` for `ResponseBuilder`. [#2468] +- Remove unnecessary `MessageBody` bound on types passed to `body::AnyBody::new`. [#2468] +- Move `body::AnyBody` to `awc`. Replaced with `EitherBody` and `BoxBody`. [#2468] +- `impl Copy` for `ws::Codec`. [#1920] +- `header::qitem` helper. Replaced with `header::QualityItem::max`. [#2486] +- `impl TryFrom` for `header::Quality`. [#2486] +- `http` module. Most everything it contained is exported at the crate root. [#2488] [#2483]: https://github.com/actix/actix-web/pull/2483 [#2468]: https://github.com/actix/actix-web/pull/2468 @@ -76,10 +76,10 @@ ## 3.0.0-beta.14 - 2021-11-30 ### Changed -* Guarantee ordering of `header::GetAll` iterator to be same as insertion order. [#2467] -* Expose `header::map` module. [#2467] -* Implement `ExactSizeIterator` and `FusedIterator` for all `HeaderMap` iterators. [#2470] -* Update `actix-tls` to `3.0.0-rc.1`. [#2474] +- Guarantee ordering of `header::GetAll` iterator to be same as insertion order. [#2467] +- Expose `header::map` module. [#2467] +- Implement `ExactSizeIterator` and `FusedIterator` for all `HeaderMap` iterators. [#2470] +- Update `actix-tls` to `3.0.0-rc.1`. [#2474] [#2467]: https://github.com/actix/actix-web/pull/2467 [#2470]: https://github.com/actix/actix-web/pull/2470 @@ -88,24 +88,24 @@ ## 3.0.0-beta.13 - 2021-11-22 ### Added -* `body::AnyBody::empty` for quickly creating an empty body. [#2446] -* `body::AnyBody::none` for quickly creating a "none" body. [#2456] -* `impl Clone` for `body::AnyBody where S: Clone`. [#2448] -* `body::AnyBody::into_boxed` for quickly converting to a type-erased, boxed body type. [#2448] +- `body::AnyBody::empty` for quickly creating an empty body. [#2446] +- `body::AnyBody::none` for quickly creating a "none" body. [#2456] +- `impl Clone` for `body::AnyBody where S: Clone`. [#2448] +- `body::AnyBody::into_boxed` for quickly converting to a type-erased, boxed body type. [#2448] ### Changed -* Rename `body::AnyBody::{Message => Body}`. [#2446] -* Rename `body::AnyBody::{from_message => new_boxed}`. [#2448] -* Rename `body::AnyBody::{from_slice => copy_from_slice}`. [#2448] -* Rename `body::{BoxAnyBody => BoxBody}`. [#2448] -* Change representation of `AnyBody` to include a type parameter in `Body` variant. Defaults to `BoxBody`. [#2448] -* `Encoder::response` now returns `AnyBody>`. [#2448] +- Rename `body::AnyBody::{Message => Body}`. [#2446] +- Rename `body::AnyBody::{from_message => new_boxed}`. [#2448] +- Rename `body::AnyBody::{from_slice => copy_from_slice}`. [#2448] +- Rename `body::{BoxAnyBody => BoxBody}`. [#2448] +- Change representation of `AnyBody` to include a type parameter in `Body` variant. Defaults to `BoxBody`. [#2448] +- `Encoder::response` now returns `AnyBody>`. [#2448] ### Removed -* `body::AnyBody::Empty`; an empty body can now only be represented as a zero-length `Bytes` variant. [#2446] -* `body::BodySize::Empty`; an empty body can now only be represented as a `Sized(0)` variant. [#2446] -* `EncoderError::Boxed`; it is no longer required. [#2446] -* `body::ResponseBody`; is function is replaced by the new `body::AnyBody` enum. [#2446] +- `body::AnyBody::Empty`; an empty body can now only be represented as a zero-length `Bytes` variant. [#2446] +- `body::BodySize::Empty`; an empty body can now only be represented as a `Sized(0)` variant. [#2446] +- `EncoderError::Boxed`; it is no longer required. [#2446] +- `body::ResponseBody`; is function is replaced by the new `body::AnyBody` enum. [#2446] [#2446]: https://github.com/actix/actix-web/pull/2446 [#2448]: https://github.com/actix/actix-web/pull/2448 @@ -114,11 +114,11 @@ ## 3.0.0-beta.12 - 2021-11-15 ### Changed -* Update `actix-server` to `2.0.0-beta.9`. [#2442] +- Update `actix-server` to `2.0.0-beta.9`. [#2442] ### Removed -* `client` module. [#2425] -* `trust-dns` feature. [#2425] +- `client` module. [#2425] +- `trust-dns` feature. [#2425] [#2425]: https://github.com/actix/actix-web/pull/2425 [#2442]: https://github.com/actix/actix-web/pull/2442 @@ -126,21 +126,21 @@ ## 3.0.0-beta.11 - 2021-10-20 ### Changed -* Updated rustls to v0.20. [#2414] -* Minimum supported Rust version (MSRV) is now 1.52. +- Updated rustls to v0.20. [#2414] +- Minimum supported Rust version (MSRV) is now 1.52. [#2414]: https://github.com/actix/actix-web/pull/2414 ## 3.0.0-beta.10 - 2021-09-09 ### Changed -* `ContentEncoding` is now marked `#[non_exhaustive]`. [#2377] -* Minimum supported Rust version (MSRV) is now 1.51. +- `ContentEncoding` is now marked `#[non_exhaustive]`. [#2377] +- Minimum supported Rust version (MSRV) is now 1.51. ### Fixed -* Remove slice creation pointing to potential uninitialized data on h1 encoder. [#2364] -* Remove `Into` bound on `Encoder` body types. [#2375] -* Fix quality parse error in Accept-Encoding header. [#2344] +- Remove slice creation pointing to potential uninitialized data on h1 encoder. [#2364] +- Remove `Into` bound on `Encoder` body types. [#2375] +- Fix quality parse error in Accept-Encoding header. [#2344] [#2364]: https://github.com/actix/actix-web/pull/2364 [#2375]: https://github.com/actix/actix-web/pull/2375 @@ -150,15 +150,15 @@ ## 3.0.0-beta.9 - 2021-08-09 ### Fixed -* Potential HTTP request smuggling vulnerabilities. [RUSTSEC-2021-0081](https://github.com/rustsec/advisory-db/pull/977) +- Potential HTTP request smuggling vulnerabilities. [RUSTSEC-2021-0081](https://github.com/rustsec/advisory-db/pull/977) ## 3.0.0-beta.8 - 2021-06-26 ### Changed -* Change compression algorithm features flags. [#2250] +- Change compression algorithm features flags. [#2250] ### Removed -* `downcast` and `downcast_get_type_id` macros. [#2291] +- `downcast` and `downcast_get_type_id` macros. [#2291] [#2291]: https://github.com/actix/actix-web/pull/2291 [#2250]: https://github.com/actix/actix-web/pull/2250 @@ -166,37 +166,37 @@ ## 3.0.0-beta.7 - 2021-06-17 ### Added -* Alias `body::Body` as `body::AnyBody`. [#2215] -* `BoxAnyBody`: a boxed message body with boxed errors. [#2183] -* Re-export `http` crate's `Error` type as `error::HttpError`. [#2171] -* Re-export `StatusCode`, `Method`, `Version` and `Uri` at the crate root. [#2171] -* Re-export `ContentEncoding` and `ConnectionType` at the crate root. [#2171] -* `Response::into_body` that consumes response and returns body type. [#2201] -* `impl Default` for `Response`. [#2201] -* Add zstd support for `ContentEncoding`. [#2244] +- Alias `body::Body` as `body::AnyBody`. [#2215] +- `BoxAnyBody`: a boxed message body with boxed errors. [#2183] +- Re-export `http` crate's `Error` type as `error::HttpError`. [#2171] +- Re-export `StatusCode`, `Method`, `Version` and `Uri` at the crate root. [#2171] +- Re-export `ContentEncoding` and `ConnectionType` at the crate root. [#2171] +- `Response::into_body` that consumes response and returns body type. [#2201] +- `impl Default` for `Response`. [#2201] +- Add zstd support for `ContentEncoding`. [#2244] ### Changed -* The `MessageBody` trait now has an associated `Error` type. [#2183] -* All error trait bounds in server service builders have changed from `Into` to `Into>`. [#2253] -* All error trait bounds in message body and stream impls changed from `Into` to `Into>`. [#2253] -* Places in `Response` where `ResponseBody` was received or returned now simply use `B`. [#2201] -* `header` mod is now public. [#2171] -* `uri` mod is now public. [#2171] -* Update `language-tags` to `0.3`. -* Reduce the level from `error` to `debug` for the log line that is emitted when a `500 Internal Server Error` is built using `HttpResponse::from_error`. [#2201] -* `ResponseBuilder::message_body` now returns a `Result`. [#2201] -* Remove `Unpin` bound on `ResponseBuilder::streaming`. [#2253] -* `HttpServer::{listen_rustls(), bind_rustls()}` now honor the ALPN protocols in the configuation parameter. [#2226] +- The `MessageBody` trait now has an associated `Error` type. [#2183] +- All error trait bounds in server service builders have changed from `Into` to `Into>`. [#2253] +- All error trait bounds in message body and stream impls changed from `Into` to `Into>`. [#2253] +- Places in `Response` where `ResponseBody` was received or returned now simply use `B`. [#2201] +- `header` mod is now public. [#2171] +- `uri` mod is now public. [#2171] +- Update `language-tags` to `0.3`. +- Reduce the level from `error` to `debug` for the log line that is emitted when a `500 Internal Server Error` is built using `HttpResponse::from_error`. [#2201] +- `ResponseBuilder::message_body` now returns a `Result`. [#2201] +- Remove `Unpin` bound on `ResponseBuilder::streaming`. [#2253] +- `HttpServer::{listen_rustls(), bind_rustls()}` now honor the ALPN protocols in the configuation parameter. [#2226] ### Removed -* Stop re-exporting `http` crate's `HeaderMap` types in addition to ours. [#2171] -* Down-casting for `MessageBody` types. [#2183] -* `error::Result` alias. [#2201] -* Error field from `Response` and `Response::error`. [#2205] -* `impl Future` for `Response`. [#2201] -* `Response::take_body` and old `Response::into_body` method that casted body type. [#2201] -* `InternalError` and all the error types it constructed. [#2215] -* Conversion (`impl Into`) of `Response` and `ResponseBuilder` to `Error`. [#2215] +- Stop re-exporting `http` crate's `HeaderMap` types in addition to ours. [#2171] +- Down-casting for `MessageBody` types. [#2183] +- `error::Result` alias. [#2201] +- Error field from `Response` and `Response::error`. [#2205] +- `impl Future` for `Response`. [#2201] +- `Response::take_body` and old `Response::into_body` method that casted body type. [#2201] +- `InternalError` and all the error types it constructed. [#2215] +- Conversion (`impl Into`) of `Response` and `ResponseBuilder` to `Error`. [#2215] [#2171]: https://github.com/actix/actix-web/pull/2171 [#2183]: https://github.com/actix/actix-web/pull/2183 @@ -211,27 +211,27 @@ ## 3.0.0-beta.6 - 2021-04-17 ### Added -* `impl MessageBody for Pin>`. [#2152] -* `Response::{ok, bad_request, not_found, internal_server_error}`. [#2159] -* Helper `body::to_bytes` for async collecting message body into Bytes. [#2158] +- `impl MessageBody for Pin>`. [#2152] +- `Response::{ok, bad_request, not_found, internal_server_error}`. [#2159] +- Helper `body::to_bytes` for async collecting message body into Bytes. [#2158] ### Changes -* The type parameter of `Response` no longer has a default. [#2152] -* The `Message` variant of `body::Body` is now `Pin>`. [#2152] -* `BodyStream` and `SizedStream` are no longer restricted to Unpin types. [#2152] -* Error enum types are marked `#[non_exhaustive]`. [#2161] +- The type parameter of `Response` no longer has a default. [#2152] +- The `Message` variant of `body::Body` is now `Pin>`. [#2152] +- `BodyStream` and `SizedStream` are no longer restricted to Unpin types. [#2152] +- Error enum types are marked `#[non_exhaustive]`. [#2161] ### Removed -* `cookies` feature flag. [#2065] -* Top-level `cookies` mod (re-export). [#2065] -* `HttpMessage` trait loses the `cookies` and `cookie` methods. [#2065] -* `impl ResponseError for CookieParseError`. [#2065] -* Deprecated methods on `ResponseBuilder`: `if_true`, `if_some`. [#2148] -* `ResponseBuilder::json`. [#2148] -* `ResponseBuilder::{set_header, header}`. [#2148] -* `impl From for Body`. [#2148] -* `Response::build_from`. [#2159] -* Most of the status code builders on `Response`. [#2159] +- `cookies` feature flag. [#2065] +- Top-level `cookies` mod (re-export). [#2065] +- `HttpMessage` trait loses the `cookies` and `cookie` methods. [#2065] +- `impl ResponseError for CookieParseError`. [#2065] +- Deprecated methods on `ResponseBuilder`: `if_true`, `if_some`. [#2148] +- `ResponseBuilder::json`. [#2148] +- `ResponseBuilder::{set_header, header}`. [#2148] +- `impl From for Body`. [#2148] +- `Response::build_from`. [#2159] +- Most of the status code builders on `Response`. [#2159] [#2065]: https://github.com/actix/actix-web/pull/2065 [#2148]: https://github.com/actix/actix-web/pull/2148 @@ -243,16 +243,16 @@ ## 3.0.0-beta.5 - 2021-04-02 ### Added -* `client::Connector::handshake_timeout` method for customizing TLS connection handshake timeout. [#2081] -* `client::ConnectorService` as `client::Connector::finish` method's return type [#2081] -* `client::ConnectionIo` trait alias [#2081] +- `client::Connector::handshake_timeout` method for customizing TLS connection handshake timeout. [#2081] +- `client::ConnectorService` as `client::Connector::finish` method's return type [#2081] +- `client::ConnectionIo` trait alias [#2081] ### Changed -* `client::Connector` type now only have one generic type for `actix_service::Service`. [#2063] +- `client::Connector` type now only have one generic type for `actix_service::Service`. [#2063] ### Removed -* Common typed HTTP headers were moved to actix-web. [2094] -* `ResponseError` impl for `actix_utils::timeout::TimeoutError`. [#2127] +- Common typed HTTP headers were moved to actix-web. [2094] +- `ResponseError` impl for `actix_utils::timeout::TimeoutError`. [#2127] [#2063]: https://github.com/actix/actix-web/pull/2063 [#2081]: https://github.com/actix/actix-web/pull/2081 @@ -262,13 +262,13 @@ ## 3.0.0-beta.4 - 2021-03-08 ### Changed -* Feature `cookies` is now optional and disabled by default. [#1981] -* `ws::hash_key` now returns array. [#2035] -* `ResponseBuilder::json` now takes `impl Serialize`. [#2052] +- Feature `cookies` is now optional and disabled by default. [#1981] +- `ws::hash_key` now returns array. [#2035] +- `ResponseBuilder::json` now takes `impl Serialize`. [#2052] ### Removed -* Re-export of `futures_channel::oneshot::Canceled` is removed from `error` mod. [#1994] -* `ResponseError` impl for `futures_channel::oneshot::Canceled` is removed. [#1994] +- Re-export of `futures_channel::oneshot::Canceled` is removed from `error` mod. [#1994] +- `ResponseError` impl for `futures_channel::oneshot::Canceled` is removed. [#1994] [#1981]: https://github.com/actix/actix-web/pull/1981 [#1994]: https://github.com/actix/actix-web/pull/1994 @@ -277,48 +277,48 @@ ## 3.0.0-beta.3 - 2021-02-10 -* No notable changes. +- No notable changes. ## 3.0.0-beta.2 - 2021-02-10 ### Added -* `TryIntoHeaderPair` trait that allows using typed and untyped headers in the same methods. [#1869] -* `ResponseBuilder::insert_header` method which allows using typed headers. [#1869] -* `ResponseBuilder::append_header` method which allows using typed headers. [#1869] -* `TestRequest::insert_header` method which allows using typed headers. [#1869] -* `ContentEncoding` implements all necessary header traits. [#1912] -* `HeaderMap::len_keys` has the behavior of the old `len` method. [#1964] -* `HeaderMap::drain` as an efficient draining iterator. [#1964] -* Implement `IntoIterator` for owned `HeaderMap`. [#1964] -* `trust-dns` optional feature to enable `trust-dns-resolver` as client dns resolver. [#1969] +- `TryIntoHeaderPair` trait that allows using typed and untyped headers in the same methods. [#1869] +- `ResponseBuilder::insert_header` method which allows using typed headers. [#1869] +- `ResponseBuilder::append_header` method which allows using typed headers. [#1869] +- `TestRequest::insert_header` method which allows using typed headers. [#1869] +- `ContentEncoding` implements all necessary header traits. [#1912] +- `HeaderMap::len_keys` has the behavior of the old `len` method. [#1964] +- `HeaderMap::drain` as an efficient draining iterator. [#1964] +- Implement `IntoIterator` for owned `HeaderMap`. [#1964] +- `trust-dns` optional feature to enable `trust-dns-resolver` as client dns resolver. [#1969] ### Changed -* `ResponseBuilder::content_type` now takes an `impl TryIntoHeaderValue` to support using typed +- `ResponseBuilder::content_type` now takes an `impl TryIntoHeaderValue` to support using typed `mime` types. [#1894] -* Renamed `TryIntoHeaderValue::{try_into => try_into_value}` to avoid ambiguity with std +- Renamed `TryIntoHeaderValue::{try_into => try_into_value}` to avoid ambiguity with std `TryInto` trait. [#1894] -* `Extensions::insert` returns Option of replaced item. [#1904] -* Remove `HttpResponseBuilder::json2()`. [#1903] -* Enable `HttpResponseBuilder::json()` to receive data by value and reference. [#1903] -* `client::error::ConnectError` Resolver variant contains `Box` type. [#1905] -* `client::ConnectorConfig` default timeout changed to 5 seconds. [#1905] -* Simplify `BlockingError` type to a unit struct. It's now only triggered when blocking thread pool +- `Extensions::insert` returns Option of replaced item. [#1904] +- Remove `HttpResponseBuilder::json2()`. [#1903] +- Enable `HttpResponseBuilder::json()` to receive data by value and reference. [#1903] +- `client::error::ConnectError` Resolver variant contains `Box` type. [#1905] +- `client::ConnectorConfig` default timeout changed to 5 seconds. [#1905] +- Simplify `BlockingError` type to a unit struct. It's now only triggered when blocking thread pool is dead. [#1957] -* `HeaderMap::len` now returns number of values instead of number of keys. [#1964] -* `HeaderMap::insert` now returns iterator of removed values. [#1964] -* `HeaderMap::remove` now returns iterator of removed values. [#1964] +- `HeaderMap::len` now returns number of values instead of number of keys. [#1964] +- `HeaderMap::insert` now returns iterator of removed values. [#1964] +- `HeaderMap::remove` now returns iterator of removed values. [#1964] ### Removed -* `ResponseBuilder::set`; use `ResponseBuilder::insert_header`. [#1869] -* `ResponseBuilder::set_header`; use `ResponseBuilder::insert_header`. [#1869] -* `ResponseBuilder::header`; use `ResponseBuilder::append_header`. [#1869] -* `TestRequest::with_hdr`; use `TestRequest::default().insert_header()`. [#1869] -* `TestRequest::with_header`; use `TestRequest::default().insert_header()`. [#1869] -* `actors` optional feature. [#1969] -* `ResponseError` impl for `actix::MailboxError`. [#1969] +- `ResponseBuilder::set`; use `ResponseBuilder::insert_header`. [#1869] +- `ResponseBuilder::set_header`; use `ResponseBuilder::insert_header`. [#1869] +- `ResponseBuilder::header`; use `ResponseBuilder::append_header`. [#1869] +- `TestRequest::with_hdr`; use `TestRequest::default().insert_header()`. [#1869] +- `TestRequest::with_header`; use `TestRequest::default().insert_header()`. [#1869] +- `actors` optional feature. [#1969] +- `ResponseError` impl for `actix::MailboxError`. [#1969] ### Documentation -* Vastly improve docs and add examples for `HeaderMap`. [#1964] +- Vastly improve docs and add examples for `HeaderMap`. [#1964] [#1869]: https://github.com/actix/actix-web/pull/1869 [#1894]: https://github.com/actix/actix-web/pull/1894 @@ -333,24 +333,24 @@ ## 3.0.0-beta.1 - 2021-01-07 ### Added -* Add `Http3` to `Protocol` enum for future compatibility and also mark `#[non_exhaustive]`. +- Add `Http3` to `Protocol` enum for future compatibility and also mark `#[non_exhaustive]`. ### Changed -* Update `actix-*` dependencies to tokio `1.0` based versions. [#1813] -* Bumped `rand` to `0.8`. -* Update `bytes` to `1.0`. [#1813] -* Update `h2` to `0.3`. [#1813] -* The `ws::Message::Text` enum variant now contains a `bytestring::ByteString`. [#1864] +- Update `actix-*` dependencies to tokio `1.0` based versions. [#1813] +- Bumped `rand` to `0.8`. +- Update `bytes` to `1.0`. [#1813] +- Update `h2` to `0.3`. [#1813] +- The `ws::Message::Text` enum variant now contains a `bytestring::ByteString`. [#1864] ### Removed -* Deprecated `on_connect` methods have been removed. Prefer the new +- Deprecated `on_connect` methods have been removed. Prefer the new `on_connect_ext` technique. [#1857] -* Remove `ResponseError` impl for `actix::actors::resolver::ResolverError` +- Remove `ResponseError` impl for `actix::actors::resolver::ResolverError` due to deprecate of resolver actor. [#1813] -* Remove `ConnectError::SslHandshakeError` and re-export of `HandshakeError`. +- Remove `ConnectError::SslHandshakeError` and re-export of `HandshakeError`. due to the removal of this type from `tokio-openssl` crate. openssl handshake error would return as `ConnectError::SslError`. [#1813] -* Remove `actix-threadpool` dependency. Use `actix_rt::task::spawn_blocking`. +- Remove `actix-threadpool` dependency. Use `actix_rt::task::spawn_blocking`. Due to this change `actix_threadpool::BlockingError` type is moved into `actix_http::error` module. [#1878] @@ -362,20 +362,20 @@ ## 2.2.1 - 2021-08-09 ### Fixed -* Potential HTTP request smuggling vulnerabilities. [RUSTSEC-2021-0081](https://github.com/rustsec/advisory-db/pull/977) +- Potential HTTP request smuggling vulnerabilities. [RUSTSEC-2021-0081](https://github.com/rustsec/advisory-db/pull/977) ## 2.2.0 - 2020-11-25 ### Added -* HttpResponse builders for 1xx status codes. [#1768] -* `Accept::mime_precedence` and `Accept::mime_preference`. [#1793] -* `TryFrom` and `TryFrom` for `http::header::Quality`. [#1797] +- HttpResponse builders for 1xx status codes. [#1768] +- `Accept::mime_precedence` and `Accept::mime_preference`. [#1793] +- `TryFrom` and `TryFrom` for `http::header::Quality`. [#1797] ### Fixed -* Started dropping `transfer-encoding: chunked` and `Content-Length` for 1XX and 204 responses. [#1767] +- Started dropping `transfer-encoding: chunked` and `Content-Length` for 1XX and 204 responses. [#1767] ### Changed -* Upgrade `serde_urlencoded` to `0.7`. [#1773] +- Upgrade `serde_urlencoded` to `0.7`. [#1773] [#1773]: https://github.com/actix/actix-web/pull/1773 [#1767]: https://github.com/actix/actix-web/pull/1767 @@ -386,12 +386,12 @@ ## 2.1.0 - 2020-10-30 ### Added -* Added more flexible `on_connect_ext` methods for on-connect handling. [#1754] +- Added more flexible `on_connect_ext` methods for on-connect handling. [#1754] ### Changed -* Upgrade `base64` to `0.13`. [#1744] -* Upgrade `pin-project` to `1.0`. [#1733] -* Deprecate `ResponseBuilder::{if_some, if_true}`. [#1760] +- Upgrade `base64` to `0.13`. [#1744] +- Upgrade `pin-project` to `1.0`. [#1733] +- Deprecate `ResponseBuilder::{if_some, if_true}`. [#1760] [#1760]: https://github.com/actix/actix-web/pull/1760 [#1754]: https://github.com/actix/actix-web/pull/1754 @@ -400,28 +400,28 @@ ## 2.0.0 - 2020-09-11 -* No significant changes from `2.0.0-beta.4`. +- No significant changes from `2.0.0-beta.4`. ## 2.0.0-beta.4 - 2020-09-09 ### Changed -* Update actix-codec and actix-utils dependencies. -* Update actix-connect and actix-tls dependencies. +- Update actix-codec and actix-utils dependencies. +- Update actix-connect and actix-tls dependencies. ## 2.0.0-beta.3 - 2020-08-14 ### Fixed -* Memory leak of `client::pool::ConnectorPoolSupport`. [#1626] +- Memory leak of `client::pool::ConnectorPoolSupport`. [#1626] [#1626]: https://github.com/actix/actix-web/pull/1626 ## 2.0.0-beta.2 - 2020-07-21 ### Fixed -* Potential UB in h1 decoder using uninitialized memory. [#1614] +- Potential UB in h1 decoder using uninitialized memory. [#1614] ### Changed -* Fix illegal chunked encoding. [#1615] +- Fix illegal chunked encoding. [#1615] [#1614]: https://github.com/actix/actix-web/pull/1614 [#1615]: https://github.com/actix/actix-web/pull/1615 @@ -429,10 +429,10 @@ ## 2.0.0-beta.1 - 2020-07-11 ### Changed -* Migrate cookie handling to `cookie` crate. [#1558] -* Update `sha-1` to 0.9. [#1586] -* Fix leak in client pool. [#1580] -* MSRV is now 1.41.1. +- Migrate cookie handling to `cookie` crate. [#1558] +- Update `sha-1` to 0.9. [#1586] +- Fix leak in client pool. [#1580] +- MSRV is now 1.41.1. [#1558]: https://github.com/actix/actix-web/pull/1558 [#1586]: https://github.com/actix/actix-web/pull/1586 @@ -441,15 +441,15 @@ ## 2.0.0-alpha.4 - 2020-05-21 ### Changed -* Bump minimum supported Rust version to 1.40 -* content_length function is removed, and you can set Content-Length by calling +- Bump minimum supported Rust version to 1.40 +- content_length function is removed, and you can set Content-Length by calling no_chunking function [#1439] -* `BodySize::Sized64` variant has been removed. `BodySize::Sized` now receives a +- `BodySize::Sized64` variant has been removed. `BodySize::Sized` now receives a `u64` instead of a `usize`. -* Update `base64` dependency to 0.12 +- Update `base64` dependency to 0.12 ### Fixed -* Support parsing of `SameSite=None` [#1503] +- Support parsing of `SameSite=None` [#1503] [#1439]: https://github.com/actix/actix-web/pull/1439 [#1503]: https://github.com/actix/actix-web/pull/1503 @@ -457,13 +457,13 @@ ## 2.0.0-alpha.3 - 2020-05-08 ### Fixed -* Correct spelling of ConnectError::Unresolved [#1487] -* Fix a mistake in the encoding of websocket continuation messages wherein +- Correct spelling of ConnectError::Unresolved [#1487] +- Fix a mistake in the encoding of websocket continuation messages wherein Item::FirstText and Item::FirstBinary are each encoded as the other. ### Changed -* Implement `std::error::Error` for our custom errors [#1422] -* Remove `failure` support for `ResponseError` since that crate +- Implement `std::error::Error` for our custom errors [#1422] +- Remove `failure` support for `ResponseError` since that crate will be deprecated in the near future. [#1422]: https://github.com/actix/actix-web/pull/1422 @@ -472,12 +472,12 @@ ## 2.0.0-alpha.2 - 2020-03-07 ### Changed -* Update `actix-connect` and `actix-tls` dependency to 2.0.0-alpha.1. [#1395] -* Change default initial window size and connection window size for HTTP2 to 2MB and 1MB +- Update `actix-connect` and `actix-tls` dependency to 2.0.0-alpha.1. [#1395] +- Change default initial window size and connection window size for HTTP2 to 2MB and 1MB respectively to improve download speed for awc when downloading large objects. [#1394] -* client::Connector accepts initial_window_size and initial_connection_window_size +- client::Connector accepts initial_window_size and initial_connection_window_size HTTP2 configuration. [#1394] -* client::Connector allowing to set max_http_version to limit HTTP version to be used. [#1394] +- client::Connector allowing to set max_http_version to limit HTTP version to be used. [#1394] [#1394]: https://github.com/actix/actix-web/pull/1394 [#1395]: https://github.com/actix/actix-web/pull/1395 @@ -485,61 +485,61 @@ ## 2.0.0-alpha.1 - 2020-02-27 ### Changed -* Update the `time` dependency to 0.2.7. -* Moved actors messages support from actix crate, enabled with feature `actors`. -* Breaking change: trait MessageBody requires Unpin and accepting `Pin<&mut Self>` instead of +- Update the `time` dependency to 0.2.7. +- Moved actors messages support from actix crate, enabled with feature `actors`. +- Breaking change: trait MessageBody requires Unpin and accepting `Pin<&mut Self>` instead of `&mut self` in the poll_next(). -* MessageBody is not implemented for &'static [u8] anymore. +- MessageBody is not implemented for &'static [u8] anymore. ### Fixed -* Allow `SameSite=None` cookies to be sent in a response. +- Allow `SameSite=None` cookies to be sent in a response. ## 1.0.1 - 2019-12-20 ### Fixed -* Poll upgrade service's readiness from HTTP service handlers -* Replace brotli with brotli2 #1224 +- Poll upgrade service's readiness from HTTP service handlers +- Replace brotli with brotli2 #1224 ## 1.0.0 - 2019-12-13 ### Added -* Add websockets continuation frame support +- Add websockets continuation frame support ### Changed -* Replace `flate2-xxx` features with `compress` +- Replace `flate2-xxx` features with `compress` ## 1.0.0-alpha.5 - 2019-12-09 ### Fixed -* Check `Upgrade` service readiness before calling it -* Fix buffer remaining capacity calculation +- Check `Upgrade` service readiness before calling it +- Fix buffer remaining capacity calculation ### Changed -* Websockets: Ping and Pong should have binary data #1049 +- Websockets: Ping and Pong should have binary data #1049 ## 1.0.0-alpha.4 - 2019-12-08 ### Added -* Add impl ResponseBuilder for Error +- Add impl ResponseBuilder for Error ### Changed -* Use rust based brotli compression library +- Use rust based brotli compression library ## 1.0.0-alpha.3 - 2019-12-07 ### Changed -* Migrate to tokio 0.2 -* Migrate to `std::future` +- Migrate to tokio 0.2 +- Migrate to `std::future` ## 0.2.11 - 2019-11-06 ### Added -* Add support for serde_json::Value to be passed as argument to ResponseBuilder.body() -* Add an additional `filename*` param in the `Content-Disposition` header of +- Add support for serde_json::Value to be passed as argument to ResponseBuilder.body() +- Add an additional `filename*` param in the `Content-Disposition` header of `actix_files::NamedFile` to be more compatible. (#1151) -* Allow to use `std::convert::Infallible` as `actix_http::error::Error` +- Allow to use `std::convert::Infallible` as `actix_http::error::Error` ### Fixed -* To be compatible with non-English error responses, `ResponseError` rendered with `text/plain; +- To be compatible with non-English error responses, `ResponseError` rendered with `text/plain; charset=utf-8` header [#1118] [#1878]: https://github.com/actix/actix-web/pull/1878 @@ -547,169 +547,169 @@ ## 0.2.10 - 2019-09-11 ### Added -* Add support for sending HTTP requests with `Rc` in addition to sending HTTP requests +- Add support for sending HTTP requests with `Rc` in addition to sending HTTP requests with `RequestHead` ### Fixed -* h2 will use error response #1080 -* on_connect result isn't added to request extensions for http2 requests #1009 +- h2 will use error response #1080 +- on_connect result isn't added to request extensions for http2 requests #1009 ## 0.2.9 - 2019-08-13 ### Changed -* Dropped the `byteorder`-dependency in favor of `stdlib`-implementation -* Update percent-encoding to 2.1 -* Update serde_urlencoded to 0.6.1 +- Dropped the `byteorder`-dependency in favor of `stdlib`-implementation +- Update percent-encoding to 2.1 +- Update serde_urlencoded to 0.6.1 ### Fixed -* Fixed a panic in the HTTP2 handshake in client HTTP requests (#1031) +- Fixed a panic in the HTTP2 handshake in client HTTP requests (#1031) ## 0.2.8 - 2019-08-01 ### Added -* Add `rustls` support -* Add `Clone` impl for `HeaderMap` +- Add `rustls` support +- Add `Clone` impl for `HeaderMap` ### Fixed -* awc client panic #1016 -* Invalid response with compression middleware enabled, but compression-related features +- awc client panic #1016 +- Invalid response with compression middleware enabled, but compression-related features disabled #997 ## 0.2.7 - 2019-07-18 ### Added -* Add support for downcasting response errors #986 +- Add support for downcasting response errors #986 ## 0.2.6 - 2019-07-17 ### Changed -* Replace `ClonableService` with local copy -* Upgrade `rand` dependency version to 0.7 +- Replace `ClonableService` with local copy +- Upgrade `rand` dependency version to 0.7 ## 0.2.5 - 2019-06-28 ### Added -* Add `on-connect` callback, `HttpServiceBuilder::on_connect()` #946 +- Add `on-connect` callback, `HttpServiceBuilder::on_connect()` #946 ### Changed -* Use `encoding_rs` crate instead of unmaintained `encoding` crate -* Add `Copy` and `Clone` impls for `ws::Codec` +- Use `encoding_rs` crate instead of unmaintained `encoding` crate +- Add `Copy` and `Clone` impls for `ws::Codec` ## 0.2.4 - 2019-06-16 ### Fixed -* Do not compress NoContent (204) responses #918 +- Do not compress NoContent (204) responses #918 ## 0.2.3 - 2019-06-02 ### Added -* Debug impl for ResponseBuilder -* From SizedStream and BodyStream for Body +- Debug impl for ResponseBuilder +- From SizedStream and BodyStream for Body ### Changed -* SizedStream uses u64 +- SizedStream uses u64 ## 0.2.2 - 2019-05-29 ### Fixed -* Parse incoming stream before closing stream on disconnect #868 +- Parse incoming stream before closing stream on disconnect #868 ## 0.2.1 - 2019-05-25 ### Fixed -* Handle socket read disconnect +- Handle socket read disconnect ## 0.2.0 - 2019-05-12 ### Changed -* Update actix-service to 0.4 -* Expect and upgrade services accept `ServerConfig` config. +- Update actix-service to 0.4 +- Expect and upgrade services accept `ServerConfig` config. ### Deleted -* `OneRequest` service +- `OneRequest` service ## 0.1.5 - 2019-05-04 ### Fixed -* Clean up response extensions in response pool #817 +- Clean up response extensions in response pool #817 ## 0.1.4 - 2019-04-24 ### Added -* Allow to render h1 request headers in `Camel-Case` +- Allow to render h1 request headers in `Camel-Case` ### Fixed -* Read until eof for http/1.0 responses #771 +- Read until eof for http/1.0 responses #771 ## 0.1.3 - 2019-04-23 ### Fixed -* Fix http client pool management -* Fix http client wait queue management #794 +- Fix http client pool management +- Fix http client wait queue management #794 ## 0.1.2 - 2019-04-23 ### Fixed -* Fix BorrowMutError panic in client connector #793 +- Fix BorrowMutError panic in client connector #793 ## 0.1.1 - 2019-04-19 ### Changed -* Cookie::max_age() accepts value in seconds -* Cookie::max_age_time() accepts value in time::Duration -* Allow to specify server address for client connector +- Cookie::max_age() accepts value in seconds +- Cookie::max_age_time() accepts value in time::Duration +- Allow to specify server address for client connector ## 0.1.0 - 2019-04-16 ### Added -* Expose peer addr via `Request::peer_addr()` and `RequestHead::peer_addr` +- Expose peer addr via `Request::peer_addr()` and `RequestHead::peer_addr` ### Changed -* `actix_http::encoding` always available -* use trust-dns-resolver 0.11.0 +- `actix_http::encoding` always available +- use trust-dns-resolver 0.11.0 ## 0.1.0-alpha.5 - 2019-04-12 ### Added -* Allow to use custom service for upgrade requests -* Added `h1::SendResponse` future. +- Allow to use custom service for upgrade requests +- Added `h1::SendResponse` future. ### Changed -* MessageBody::length() renamed to MessageBody::size() for consistency -* ws handshake verification functions take RequestHead instead of Request +- MessageBody::length() renamed to MessageBody::size() for consistency +- ws handshake verification functions take RequestHead instead of Request ## 0.1.0-alpha.4 - 2019-04-08 ### Added -* Allow to use custom `Expect` handler -* Add minimal `std::error::Error` impl for `Error` +- Allow to use custom `Expect` handler +- Add minimal `std::error::Error` impl for `Error` ### Changed -* Export IntoHeaderValue -* Render error and return as response body -* Use thread pool for response body compression +- Export IntoHeaderValue +- Render error and return as response body +- Use thread pool for response body compression ### Deleted -* Removed PayloadBuffer +- Removed PayloadBuffer ## 0.1.0-alpha.3 - 2019-04-02 ### Added -* Warn when an unsealed private cookie isn't valid UTF-8 +- Warn when an unsealed private cookie isn't valid UTF-8 ### Fixed -* Rust 1.31.0 compatibility -* Preallocate read buffer for h1 codec -* Detect socket disconnection during protocol selection +- Rust 1.31.0 compatibility +- Preallocate read buffer for h1 codec +- Detect socket disconnection during protocol selection ## 0.1.0-alpha.2 - 2019-03-29 ### Added -* Added ws::Message::Nop, no-op websockets message +- Added ws::Message::Nop, no-op websockets message ### Changed -* Do not use thread pool for decompression if chunk size is smaller than 2048. +- Do not use thread pool for decompression if chunk size is smaller than 2048. ## 0.1.0-alpha.1 - 2019-03-28 -* Initial impl +- Initial impl diff --git a/actix-http/README.md b/actix-http/README.md index 731d7a48e..05edffd2c 100644 --- a/actix-http/README.md +++ b/actix-http/README.md @@ -54,8 +54,8 @@ async fn main() -> io::Result<()> { This project is licensed under either of -* Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0)) -* MIT license ([LICENSE-MIT](LICENSE-MIT) or [http://opensource.org/licenses/MIT](http://opensource.org/licenses/MIT)) +- Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0)) +- MIT license ([LICENSE-MIT](LICENSE-MIT) or [http://opensource.org/licenses/MIT](http://opensource.org/licenses/MIT)) at your option. diff --git a/actix-multipart/CHANGES.md b/actix-multipart/CHANGES.md index 8d9c1640f..e58c3ee24 100644 --- a/actix-multipart/CHANGES.md +++ b/actix-multipart/CHANGES.md @@ -4,119 +4,119 @@ ## 0.4.0-beta.10 - 2021-12-11 -* No significant changes since `0.4.0-beta.9`. +- No significant changes since `0.4.0-beta.9`. ## 0.4.0-beta.9 - 2021-12-01 -* Polling `Field` after dropping `Multipart` now fails immediately instead of hanging forever. [#2463] +- Polling `Field` after dropping `Multipart` now fails immediately instead of hanging forever. [#2463] [#2463]: https://github.com/actix/actix-web/pull/2463 ## 0.4.0-beta.8 - 2021-11-22 -* Ensure a correct Content-Disposition header is included in every part of a multipart message. [#2451] -* Added `MultipartError::NoContentDisposition` variant. [#2451] -* Since Content-Disposition is now ensured, `Field::content_disposition` is now infallible. [#2451] -* Added `Field::name` method for getting the field name. [#2451] -* `MultipartError` now marks variants with inner errors as the source. [#2451] -* `MultipartError` is now marked as non-exhaustive. [#2451] +- Ensure a correct Content-Disposition header is included in every part of a multipart message. [#2451] +- Added `MultipartError::NoContentDisposition` variant. [#2451] +- Since Content-Disposition is now ensured, `Field::content_disposition` is now infallible. [#2451] +- Added `Field::name` method for getting the field name. [#2451] +- `MultipartError` now marks variants with inner errors as the source. [#2451] +- `MultipartError` is now marked as non-exhaustive. [#2451] [#2451]: https://github.com/actix/actix-web/pull/2451 ## 0.4.0-beta.7 - 2021-10-20 -* Minimum supported Rust version (MSRV) is now 1.52. +- Minimum supported Rust version (MSRV) is now 1.52. ## 0.4.0-beta.6 - 2021-09-09 -* Minimum supported Rust version (MSRV) is now 1.51. +- Minimum supported Rust version (MSRV) is now 1.51. ## 0.4.0-beta.5 - 2021-06-17 -* No notable changes. +- No notable changes. ## 0.4.0-beta.4 - 2021-04-02 -* No notable changes. +- No notable changes. ## 0.4.0-beta.3 - 2021-03-09 -* No notable changes. +- No notable changes. ## 0.4.0-beta.2 - 2021-02-10 -* No notable changes. +- No notable changes. ## 0.4.0-beta.1 - 2021-01-07 -* Fix multipart consuming payload before header checks. [#1513] -* Update `bytes` to `1.0`. [#1813] +- Fix multipart consuming payload before header checks. [#1513] +- Update `bytes` to `1.0`. [#1813] [#1813]: https://github.com/actix/actix-web/pull/1813 [#1513]: https://github.com/actix/actix-web/pull/1513 ## 0.3.0 - 2020-09-11 -* No significant changes from `0.3.0-beta.2`. +- No significant changes from `0.3.0-beta.2`. ## 0.3.0-beta.2 - 2020-09-10 -* Update `actix-*` dependencies to latest versions. +- Update `actix-*` dependencies to latest versions. ## 0.3.0-beta.1 - 2020-07-15 -* Update `actix-web` to 3.0.0-beta.1 +- Update `actix-web` to 3.0.0-beta.1 ## 0.3.0-alpha.1 - 2020-05-25 -* Update `actix-web` to 3.0.0-alpha.3 -* Bump minimum supported Rust version to 1.40 -* Minimize `futures` dependencies -* Remove the unused `time` dependency -* Fix missing `std::error::Error` implement for `MultipartError`. +- Update `actix-web` to 3.0.0-alpha.3 +- Bump minimum supported Rust version to 1.40 +- Minimize `futures` dependencies +- Remove the unused `time` dependency +- Fix missing `std::error::Error` implement for `MultipartError`. ## [0.2.0] - 2019-12-20 -* Release +- Release ## [0.2.0-alpha.4] - 2019-12-xx -* Multipart handling now handles Pending during read of boundary #1205 +- Multipart handling now handles Pending during read of boundary #1205 ## [0.2.0-alpha.2] - 2019-12-03 -* Migrate to `std::future` +- Migrate to `std::future` ## [0.1.4] - 2019-09-12 -* Multipart handling now parses requests which do not end in CRLF #1038 +- Multipart handling now parses requests which do not end in CRLF #1038 ## [0.1.3] - 2019-08-18 -* Fix ring dependency from actix-web default features for #741. +- Fix ring dependency from actix-web default features for #741. ## [0.1.2] - 2019-06-02 -* Fix boundary parsing #876 +- Fix boundary parsing #876 ## [0.1.1] - 2019-05-25 -* Fix disconnect handling #834 +- Fix disconnect handling #834 ## [0.1.0] - 2019-05-18 -* Release +- Release ## [0.1.0-beta.4] - 2019-05-12 -* Handle cancellation of uploads #736 +- Handle cancellation of uploads #736 -* Upgrade to actix-web 1.0.0-beta.4 +- Upgrade to actix-web 1.0.0-beta.4 ## [0.1.0-beta.1] - 2019-04-21 -* Do not support nested multipart +- Do not support nested multipart -* Split multipart support to separate crate +- Split multipart support to separate crate -* Optimize multipart handling #634, #769 +- Optimize multipart handling #634, #769 diff --git a/actix-router/CHANGES.md b/actix-router/CHANGES.md index d0ed55c88..0a6a56359 100644 --- a/actix-router/CHANGES.md +++ b/actix-router/CHANGES.md @@ -4,20 +4,20 @@ ## 0.5.0-beta.3 - 2021-12-17 -* Minimum supported Rust version (MSRV) is now 1.52. +- Minimum supported Rust version (MSRV) is now 1.52. ## 0.5.0-beta.2 - 2021-09-09 -* Introduce `ResourceDef::join`. [#380] -* Disallow prefix routes with tail segments. [#379] -* Enforce path separators on dynamic prefixes. [#378] -* Improve malformed path error message. [#384] -* Prefix segments now always end with with a segment delimiter or end-of-input. [#2355] -* Prefix segments with trailing slashes define a trailing empty segment. [#2355] -* Support multi-pattern prefixes and joins. [#2356] -* `ResourceDef::pattern` now returns the first pattern in multi-pattern resources. [#2356] -* Support `build_resource_path` on multi-pattern resources. [#2356] -* Minimum supported Rust version (MSRV) is now 1.51. +- Introduce `ResourceDef::join`. [#380] +- Disallow prefix routes with tail segments. [#379] +- Enforce path separators on dynamic prefixes. [#378] +- Improve malformed path error message. [#384] +- Prefix segments now always end with with a segment delimiter or end-of-input. [#2355] +- Prefix segments with trailing slashes define a trailing empty segment. [#2355] +- Support multi-pattern prefixes and joins. [#2356] +- `ResourceDef::pattern` now returns the first pattern in multi-pattern resources. [#2356] +- Support `build_resource_path` on multi-pattern resources. [#2356] +- Minimum supported Rust version (MSRV) is now 1.51. [#378]: https://github.com/actix/actix-net/pull/378 [#379]: https://github.com/actix/actix-net/pull/379 @@ -28,23 +28,23 @@ ## 0.5.0-beta.1 - 2021-07-20 -* Fix a bug in multi-patterns where static patterns are interpreted as regex. [#366] -* Introduce `ResourceDef::pattern_iter` to get an iterator over all patterns in a multi-pattern resource. [#373] -* Fix segment interpolation leaving `Path` in unintended state after matching. [#368] -* Fix `ResourceDef` `PartialEq` implementation. [#373] -* Re-work `IntoPatterns` trait, adding a `Patterns` enum. [#372] -* Implement `IntoPatterns` for `bytestring::ByteString`. [#372] -* Rename `Path::{len => segment_count}` to be more descriptive of it's purpose. [#370] -* Rename `ResourceDef::{resource_path => resource_path_from_iter}`. [#371] -* `ResourceDef::resource_path_from_iter` now takes an `IntoIterator`. [#373] -* Rename `ResourceDef::{resource_path_named => resource_path_from_map}`. [#371] -* Rename `ResourceDef::{is_prefix_match => find_match}`. [#373] -* Rename `ResourceDef::{match_path => capture_match_info}`. [#373] -* Rename `ResourceDef::{match_path_checked => capture_match_info_fn}`. [#373] -* Remove `ResourceDef::name_mut` and introduce `ResourceDef::set_name`. [#373] -* Rename `Router::{*_checked => *_fn}`. [#373] -* Return type of `ResourceDef::name` is now `Option<&str>`. [#373] -* Return type of `ResourceDef::pattern` is now `Option<&str>`. [#373] +- Fix a bug in multi-patterns where static patterns are interpreted as regex. [#366] +- Introduce `ResourceDef::pattern_iter` to get an iterator over all patterns in a multi-pattern resource. [#373] +- Fix segment interpolation leaving `Path` in unintended state after matching. [#368] +- Fix `ResourceDef` `PartialEq` implementation. [#373] +- Re-work `IntoPatterns` trait, adding a `Patterns` enum. [#372] +- Implement `IntoPatterns` for `bytestring::ByteString`. [#372] +- Rename `Path::{len => segment_count}` to be more descriptive of it's purpose. [#370] +- Rename `ResourceDef::{resource_path => resource_path_from_iter}`. [#371] +- `ResourceDef::resource_path_from_iter` now takes an `IntoIterator`. [#373] +- Rename `ResourceDef::{resource_path_named => resource_path_from_map}`. [#371] +- Rename `ResourceDef::{is_prefix_match => find_match}`. [#373] +- Rename `ResourceDef::{match_path => capture_match_info}`. [#373] +- Rename `ResourceDef::{match_path_checked => capture_match_info_fn}`. [#373] +- Remove `ResourceDef::name_mut` and introduce `ResourceDef::set_name`. [#373] +- Rename `Router::{*_checked => *_fn}`. [#373] +- Return type of `ResourceDef::name` is now `Option<&str>`. [#373] +- Return type of `ResourceDef::pattern` is now `Option<&str>`. [#373] [#368]: https://github.com/actix/actix-net/pull/368 [#366]: https://github.com/actix/actix-net/pull/366 @@ -56,10 +56,10 @@ ## 0.4.0 - 2021-06-06 -* When matching path parameters, `%25` is now kept in the percent-encoded form; no longer decoded to `%`. [#357] -* Path tail patterns now match new lines (`\n`) in request URL. [#360] -* Fixed a safety bug where `Path` could return a malformed string after percent decoding. [#359] -* Methods `Path::{add, add_static}` now take `impl Into>`. [#345] +- When matching path parameters, `%25` is now kept in the percent-encoded form; no longer decoded to `%`. [#357] +- Path tail patterns now match new lines (`\n`) in request URL. [#360] +- Fixed a safety bug where `Path` could return a malformed string after percent decoding. [#359] +- Methods `Path::{add, add_static}` now take `impl Into>`. [#345] [#345]: https://github.com/actix/actix-net/pull/345 [#357]: https://github.com/actix/actix-net/pull/357 @@ -68,68 +68,68 @@ ## 0.3.0 - 2019-12-31 -* Version was yanked previously. See https://crates.io/crates/actix-router/0.3.0 +- Version was yanked previously. See https://crates.io/crates/actix-router/0.3.0 ## 0.2.7 - 2021-02-06 -* Add `Router::recognize_checked` [#247] +- Add `Router::recognize_checked` [#247] [#247]: https://github.com/actix/actix-net/pull/247 ## 0.2.6 - 2021-01-09 -* Use `bytestring` version range compatible with Bytes v1.0. [#246] +- Use `bytestring` version range compatible with Bytes v1.0. [#246] [#246]: https://github.com/actix/actix-net/pull/246 ## 0.2.5 - 2020-09-20 -* Fix `from_hex()` method +- Fix `from_hex()` method ## 0.2.4 - 2019-12-31 -* Add `ResourceDef::resource_path_named()` path generation method +- Add `ResourceDef::resource_path_named()` path generation method ## 0.2.3 - 2019-12-25 -* Add impl `IntoPattern` for `&String` +- Add impl `IntoPattern` for `&String` ## 0.2.2 - 2019-12-25 -* Use `IntoPattern` for `RouterBuilder::path()` +- Use `IntoPattern` for `RouterBuilder::path()` ## 0.2.1 - 2019-12-25 -* Add `IntoPattern` trait -* Add multi-pattern resources +- Add `IntoPattern` trait +- Add multi-pattern resources ## 0.2.0 - 2019-12-07 -* Update http to 0.2 -* Update regex to 1.3 -* Use bytestring instead of string +- Update http to 0.2 +- Update regex to 1.3 +- Use bytestring instead of string ## 0.1.5 - 2019-05-15 -* Remove debug prints +- Remove debug prints ## 0.1.4 - 2019-05-15 -* Fix checked resource match +- Fix checked resource match ## 0.1.3 - 2019-04-22 -* Added support for `remainder match` (i.e "/path/{tail}*") +- Added support for `remainder match` (i.e "/path/{tail}*") ## 0.1.2 - 2019-04-07 -* Export `Quoter` type -* Allow to reset `Path` instance +- Export `Quoter` type +- Allow to reset `Path` instance ## 0.1.1 - 2019-04-03 -* Get dynamic segment by name instead of iterator. +- Get dynamic segment by name instead of iterator. ## 0.1.0 - 2019-03-09 -* Initial release +- Initial release diff --git a/actix-test/CHANGES.md b/actix-test/CHANGES.md index ef78ac54a..e3deeb3f4 100644 --- a/actix-test/CHANGES.md +++ b/actix-test/CHANGES.md @@ -4,46 +4,46 @@ ## 0.1.0-beta.9 - 2021-12-17 -* Re-export `actix_http::body::to_bytes`. [#2518] -* Update `actix_web::test` re-exports. [#2518] +- Re-export `actix_http::body::to_bytes`. [#2518] +- Update `actix_web::test` re-exports. [#2518] [#2518]: https://github.com/actix/actix-web/pull/2518 ## 0.1.0-beta.8 - 2021-12-11 -* No significant changes since `0.1.0-beta.7`. +- No significant changes since `0.1.0-beta.7`. ## 0.1.0-beta.7 - 2021-11-22 -* Fix compatibility with experimental `io-uring` feature of `actix-rt`. [#2408] +- Fix compatibility with experimental `io-uring` feature of `actix-rt`. [#2408] [#2408]: https://github.com/actix/actix-web/pull/2408 ## 0.1.0-beta.6 - 2021-11-15 -* No significant changes from `0.1.0-beta.5`. +- No significant changes from `0.1.0-beta.5`. ## 0.1.0-beta.5 - 2021-10-20 -* Updated rustls to v0.20. [#2414] -* Minimum supported Rust version (MSRV) is now 1.52. +- Updated rustls to v0.20. [#2414] +- Minimum supported Rust version (MSRV) is now 1.52. [#2414]: https://github.com/actix/actix-web/pull/2414 ## 0.1.0-beta.4 - 2021-09-09 -* Minimum supported Rust version (MSRV) is now 1.51. +- Minimum supported Rust version (MSRV) is now 1.51. ## 0.1.0-beta.3 - 2021-06-20 -* No significant changes from `0.1.0-beta.2`. +- No significant changes from `0.1.0-beta.2`. ## 0.1.0-beta.2 - 2021-04-17 -* No significant changes from `0.1.0-beta.1`. +- No significant changes from `0.1.0-beta.1`. ## 0.1.0-beta.1 - 2021-04-02 -* Move integration testing structs from `actix-web`. [#2112] +- Move integration testing structs from `actix-web`. [#2112] [#2112]: https://github.com/actix/actix-web/pull/2112 diff --git a/actix-web-actors/CHANGES.md b/actix-web-actors/CHANGES.md index d3078499c..6abfe2c61 100644 --- a/actix-web-actors/CHANGES.md +++ b/actix-web-actors/CHANGES.md @@ -4,105 +4,105 @@ ## 4.0.0-beta.8 - 2021-12-11 -* Add `ws:WsResponseBuilder` for building WebSocket session response. [#1920] -* Deprecate `ws::{start_with_addr, start_with_protocols}`. [#1920] -* Minimum supported Rust version (MSRV) is now 1.52. +- Add `ws:WsResponseBuilder` for building WebSocket session response. [#1920] +- Deprecate `ws::{start_with_addr, start_with_protocols}`. [#1920] +- Minimum supported Rust version (MSRV) is now 1.52. [#1920]: https://github.com/actix/actix-web/pull/1920 ## 4.0.0-beta.7 - 2021-09-09 -* Minimum supported Rust version (MSRV) is now 1.51. +- Minimum supported Rust version (MSRV) is now 1.51. ## 4.0.0-beta.6 - 2021-06-26 -* Update `actix` to `0.12`. [#2277] +- Update `actix` to `0.12`. [#2277] [#2277]: https://github.com/actix/actix-web/pull/2277 ## 4.0.0-beta.5 - 2021-06-17 -* No notable changes. +- No notable changes. ## 4.0.0-beta.4 - 2021-04-02 -* No notable changes. +- No notable changes. ## 4.0.0-beta.3 - 2021-03-09 -* No notable changes. +- No notable changes. ## 4.0.0-beta.2 - 2021-02-10 -* No notable changes. +- No notable changes. ## 4.0.0-beta.1 - 2021-01-07 -* Update `pin-project` to `1.0`. -* Update `bytes` to `1.0`. [#1813] -* `WebsocketContext::text` now takes an `Into`. [#1864] +- Update `pin-project` to `1.0`. +- Update `bytes` to `1.0`. [#1813] +- `WebsocketContext::text` now takes an `Into`. [#1864] [#1813]: https://github.com/actix/actix-web/pull/1813 [#1864]: https://github.com/actix/actix-web/pull/1864 ## 3.0.0 - 2020-09-11 -* No significant changes from `3.0.0-beta.2`. +- No significant changes from `3.0.0-beta.2`. ## 3.0.0-beta.2 - 2020-09-10 -* Update `actix-*` dependencies to latest versions. +- Update `actix-*` dependencies to latest versions. ## [3.0.0-beta.1] - 2020-xx-xx -* Update `actix-web` & `actix-http` dependencies to beta.1 -* Bump minimum supported Rust version to 1.40 +- Update `actix-web` & `actix-http` dependencies to beta.1 +- Bump minimum supported Rust version to 1.40 ## [3.0.0-alpha.1] - 2020-05-08 -* Update the actix-web dependency to 3.0.0-alpha.1 -* Update the actix dependency to 0.10.0-alpha.2 -* Update the actix-http dependency to 2.0.0-alpha.3 +- Update the actix-web dependency to 3.0.0-alpha.1 +- Update the actix dependency to 0.10.0-alpha.2 +- Update the actix-http dependency to 2.0.0-alpha.3 ## [2.0.0] - 2019-12-20 -* Release +- Release ## [2.0.0-alpha.1] - 2019-12-15 -* Migrate to actix-web 2.0.0 +- Migrate to actix-web 2.0.0 ## [1.0.4] - 2019-12-07 -* Allow comma-separated websocket subprotocols without spaces (#1172) +- Allow comma-separated websocket subprotocols without spaces (#1172) ## [1.0.3] - 2019-11-14 -* Update actix-web and actix-http dependencies +- Update actix-web and actix-http dependencies ## [1.0.2] - 2019-07-20 -* Add `ws::start_with_addr()`, returning the address of the created actor, along +- Add `ws::start_with_addr()`, returning the address of the created actor, along with the `HttpResponse`. -* Add support for specifying protocols on websocket handshake #835 +- Add support for specifying protocols on websocket handshake #835 ## [1.0.1] - 2019-06-28 -* Allow to use custom ws codec with `WebsocketContext` #925 +- Allow to use custom ws codec with `WebsocketContext` #925 ## [1.0.0] - 2019-05-29 -* Update actix-http and actix-web +- Update actix-http and actix-web ## [0.1.0-alpha.3] - 2019-04-02 -* Update actix-http and actix-web +- Update actix-http and actix-web ## [0.1.0-alpha.2] - 2019-03-29 -* Update actix-http and actix-web +- Update actix-http and actix-web ## [0.1.0-alpha.1] - 2019-03-28 -* Initial impl +- Initial impl diff --git a/actix-web-codegen/CHANGES.md b/actix-web-codegen/CHANGES.md index 309274563..0d881d303 100644 --- a/actix-web-codegen/CHANGES.md +++ b/actix-web-codegen/CHANGES.md @@ -4,101 +4,101 @@ ## 0.5.0-beta.6 - 2021-12-11 -* No significant changes since `0.5.0-beta.5`. +- No significant changes since `0.5.0-beta.5`. ## 0.5.0-beta.5 - 2021-10-20 -* Improve error recovery potential when macro input is invalid. [#2410] -* Add `#[actix_web::test]` macro for setting up tests with a runtime. [#2409] -* Minimum supported Rust version (MSRV) is now 1.52. +- Improve error recovery potential when macro input is invalid. [#2410] +- Add `#[actix_web::test]` macro for setting up tests with a runtime. [#2409] +- Minimum supported Rust version (MSRV) is now 1.52. [#2410]: https://github.com/actix/actix-web/pull/2410 [#2409]: https://github.com/actix/actix-web/pull/2409 ## 0.5.0-beta.4 - 2021-09-09 -* In routing macros, paths are now validated at compile time. [#2350] -* Minimum supported Rust version (MSRV) is now 1.51. +- In routing macros, paths are now validated at compile time. [#2350] +- Minimum supported Rust version (MSRV) is now 1.51. [#2350]: https://github.com/actix/actix-web/pull/2350 ## 0.5.0-beta.3 - 2021-06-17 -* No notable changes. +- No notable changes. ## 0.5.0-beta.2 - 2021-03-09 -* Preserve doc comments when using route macros. [#2022] -* Add `name` attribute to `route` macro. [#1934] +- Preserve doc comments when using route macros. [#2022] +- Add `name` attribute to `route` macro. [#1934] [#2022]: https://github.com/actix/actix-web/pull/2022 [#1934]: https://github.com/actix/actix-web/pull/1934 ## 0.5.0-beta.1 - 2021-02-10 -* Use new call signature for `System::new`. +- Use new call signature for `System::new`. ## 0.4.0 - 2020-09-20 -* Added compile success and failure testing. [#1677] -* Add `route` macro for supporting multiple HTTP methods guards. [#1674] +- Added compile success and failure testing. [#1677] +- Add `route` macro for supporting multiple HTTP methods guards. [#1674] [#1677]: https://github.com/actix/actix-web/pull/1677 [#1674]: https://github.com/actix/actix-web/pull/1674 ## 0.3.0 - 2020-09-11 -* No significant changes from `0.3.0-beta.1`. +- No significant changes from `0.3.0-beta.1`. ## 0.3.0-beta.1 - 2020-07-14 -* Add main entry-point macro that uses re-exported runtime. [#1559] +- Add main entry-point macro that uses re-exported runtime. [#1559] [#1559]: https://github.com/actix/actix-web/pull/1559 ## 0.2.2 - 2020-05-23 -* Add resource middleware on actix-web-codegen [#1467] +- Add resource middleware on actix-web-codegen [#1467] [#1467]: https://github.com/actix/actix-web/pull/1467 ## 0.2.1 - 2020-02-25 -* Add `#[allow(missing_docs)]` attribute to generated structs [#1368] -* Allow the handler function to be named as `config` [#1290] +- Add `#[allow(missing_docs)]` attribute to generated structs [#1368] +- Allow the handler function to be named as `config` [#1290] [#1368]: https://github.com/actix/actix-web/issues/1368 [#1290]: https://github.com/actix/actix-web/issues/1290 ## 0.2.0 - 2019-12-13 -* Generate code for actix-web 2.0 +- Generate code for actix-web 2.0 ## 0.1.3 - 2019-10-14 -* Bump up `syn` & `quote` to 1.0 -* Provide better error message +- Bump up `syn` & `quote` to 1.0 +- Provide better error message ## 0.1.2 - 2019-06-04 -* Add macros for head, options, trace, connect and patch http methods +- Add macros for head, options, trace, connect and patch http methods ## 0.1.1 - 2019-06-01 -* Add syn "extra-traits" feature +- Add syn "extra-traits" feature ## 0.1.0 - 2019-05-18 -* Release +- Release ## 0.1.0-beta.1 - 2019-04-20 -* Gen code for actix-web 1.0.0-beta.1 +- Gen code for actix-web 1.0.0-beta.1 ## 0.1.0-alpha.6 - 2019-04-14 -* Gen code for actix-web 1.0.0-alpha.6 +- Gen code for actix-web 1.0.0-alpha.6 ## 0.1.0-alpha.1 - 2019-03-28 -* Initial impl +- Initial impl diff --git a/awc/CHANGES.md b/awc/CHANGES.md index 7b822930c..b5144b7a2 100644 --- a/awc/CHANGES.md +++ b/awc/CHANGES.md @@ -1,75 +1,75 @@ # Changes ## Unreleased - 2021-xx-xx -* Rename `Connector::{ssl => openssl}`. [#2503] -* Improve `Client` instantiation efficiency when using `openssl` by only building connectors once. [#2503] +- Rename `Connector::{ssl => openssl}`. [#2503] +- Improve `Client` instantiation efficiency when using `openssl` by only building connectors once. [#2503] [#2503]: https://github.com/actix/actix-web/pull/2503 ## 3.0.0-beta.14 - 2021-12-17 -* Add `ClientBuilder::add_default_header` and deprecate `ClientBuilder::header`. [#2510] +- Add `ClientBuilder::add_default_header` and deprecate `ClientBuilder::header`. [#2510] [#2510]: https://github.com/actix/actix-web/pull/2510 ## 3.0.0-beta.13 - 2021-12-11 -* No significant changes since `3.0.0-beta.12`. +- No significant changes since `3.0.0-beta.12`. ## 3.0.0-beta.12 - 2021-11-30 -* Update `actix-tls` to `3.0.0-rc.1`. [#2474] +- Update `actix-tls` to `3.0.0-rc.1`. [#2474] [#2474]: https://github.com/actix/actix-web/pull/2474 ## 3.0.0-beta.11 - 2021-11-22 -* No significant changes from `3.0.0-beta.10`. +- No significant changes from `3.0.0-beta.10`. ## 3.0.0-beta.10 - 2021-11-15 -* No significant changes from `3.0.0-beta.9`. +- No significant changes from `3.0.0-beta.9`. ## 3.0.0-beta.9 - 2021-10-20 -* Updated rustls to v0.20. [#2414] +- Updated rustls to v0.20. [#2414] [#2414]: https://github.com/actix/actix-web/pull/2414 ## 3.0.0-beta.8 - 2021-09-09 ### Changed -* Send headers within the redirect requests. [#2310] +- Send headers within the redirect requests. [#2310] [#2310]: https://github.com/actix/actix-web/pull/2310 ## 3.0.0-beta.7 - 2021-06-26 ### Changed -* Change compression algorithm features flags. [#2250] +- Change compression algorithm features flags. [#2250] [#2250]: https://github.com/actix/actix-web/pull/2250 ## 3.0.0-beta.6 - 2021-06-17 -* No significant changes since 3.0.0-beta.5. +- No significant changes since 3.0.0-beta.5. ## 3.0.0-beta.5 - 2021-04-17 ### Removed -* Deprecated methods on `ClientRequest`: `if_true`, `if_some`. [#2148] +- Deprecated methods on `ClientRequest`: `if_true`, `if_some`. [#2148] [#2148]: https://github.com/actix/actix-web/pull/2148 ## 3.0.0-beta.4 - 2021-04-02 ### Added -* Add `Client::headers` to get default mut reference of `HeaderMap` of client object. [#2114] +- Add `Client::headers` to get default mut reference of `HeaderMap` of client object. [#2114] ### Changed -* `ConnectorService` type is renamed to `BoxConnectorService`. [#2081] -* Fix http/https encoding when enabling `compress` feature. [#2116] -* Rename `TestResponse::header` to `append_header`, `set` to `insert_header`. `TestResponse` header +- `ConnectorService` type is renamed to `BoxConnectorService`. [#2081] +- Fix http/https encoding when enabling `compress` feature. [#2116] +- Rename `TestResponse::header` to `append_header`, `set` to `insert_header`. `TestResponse` header methods now take `TryIntoHeaderPair` tuples. [#2094] [#2081]: https://github.com/actix/actix-web/pull/2081 @@ -80,16 +80,16 @@ ## 3.0.0-beta.3 - 2021-03-08 ### Added -* `ClientResponse::timeout` for set the timeout of collecting response body. [#1931] -* `ClientBuilder::local_address` for bind to a local ip address for this client. [#2024] +- `ClientResponse::timeout` for set the timeout of collecting response body. [#1931] +- `ClientBuilder::local_address` for bind to a local ip address for this client. [#2024] ### Changed -* Feature `cookies` is now optional and enabled by default. [#1981] -* `ClientBuilder::connector` method would take `actix_http::client::Connector` type. [#2008] -* Basic auth password now takes blank passwords as an empty string instead of Option. [#2050] +- Feature `cookies` is now optional and enabled by default. [#1981] +- `ClientBuilder::connector` method would take `actix_http::client::Connector` type. [#2008] +- Basic auth password now takes blank passwords as an empty string instead of Option. [#2050] ### Removed -* `ClientBuilder::default` function [#2008] +- `ClientBuilder::default` function [#2008] [#1931]: https://github.com/actix/actix-web/pull/1931 [#1981]: https://github.com/actix/actix-web/pull/1981 @@ -100,18 +100,18 @@ ## 3.0.0-beta.2 - 2021-02-10 ### Added -* `ClientRequest::insert_header` method which allows using typed headers. [#1869] -* `ClientRequest::append_header` method which allows using typed headers. [#1869] -* `trust-dns` optional feature to enable `trust-dns-resolver` as client dns resolver. [#1969] +- `ClientRequest::insert_header` method which allows using typed headers. [#1869] +- `ClientRequest::append_header` method which allows using typed headers. [#1869] +- `trust-dns` optional feature to enable `trust-dns-resolver` as client dns resolver. [#1969] ### Changed -* Relax default timeout for `Connector` to 5 seconds(original 1 second). [#1905] +- Relax default timeout for `Connector` to 5 seconds(original 1 second). [#1905] ### Removed -* `ClientRequest::set`; use `ClientRequest::insert_header`. [#1869] -* `ClientRequest::set_header`; use `ClientRequest::insert_header`. [#1869] -* `ClientRequest::set_header_if_none`; use `ClientRequest::insert_header_if_none`. [#1869] -* `ClientRequest::header`; use `ClientRequest::append_header`. [#1869] +- `ClientRequest::set`; use `ClientRequest::insert_header`. [#1869] +- `ClientRequest::set_header`; use `ClientRequest::insert_header`. [#1869] +- `ClientRequest::set_header_if_none`; use `ClientRequest::insert_header_if_none`. [#1869] +- `ClientRequest::header`; use `ClientRequest::append_header`. [#1869] [#1869]: https://github.com/actix/actix-web/pull/1869 [#1905]: https://github.com/actix/actix-web/pull/1905 @@ -120,32 +120,32 @@ ## 3.0.0-beta.1 - 2021-01-07 ### Changed -* Update `rand` to `0.8` -* Update `bytes` to `1.0`. [#1813] -* Update `rust-tls` to `0.19`. [#1813] +- Update `rand` to `0.8` +- Update `bytes` to `1.0`. [#1813] +- Update `rust-tls` to `0.19`. [#1813] [#1813]: https://github.com/actix/actix-web/pull/1813 ## 2.0.3 - 2020-11-29 ### Fixed -* Ensure `actix-http` dependency uses same `serde_urlencoded`. +- Ensure `actix-http` dependency uses same `serde_urlencoded`. ## 2.0.2 - 2020-11-25 ### Changed -* Upgrade `serde_urlencoded` to `0.7`. [#1773] +- Upgrade `serde_urlencoded` to `0.7`. [#1773] [#1773]: https://github.com/actix/actix-web/pull/1773 ## 2.0.1 - 2020-10-30 ### Changed -* Upgrade `base64` to `0.13`. [#1744] -* Deprecate `ClientRequest::{if_some, if_true}`. [#1760] +- Upgrade `base64` to `0.13`. [#1744] +- Deprecate `ClientRequest::{if_some, if_true}`. [#1760] ### Fixed -* Use `Accept-Encoding: identity` instead of `Accept-Encoding: br` when no compression feature +- Use `Accept-Encoding: identity` instead of `Accept-Encoding: br` when no compression feature is enabled [#1737] [#1737]: https://github.com/actix/actix-web/pull/1737 @@ -155,209 +155,209 @@ ## 2.0.0 - 2020-09-11 ### Changed -* `Client::build` was renamed to `Client::builder`. +- `Client::build` was renamed to `Client::builder`. ## 2.0.0-beta.4 - 2020-09-09 ### Changed -* Update actix-codec & actix-tls dependencies. +- Update actix-codec & actix-tls dependencies. ## 2.0.0-beta.3 - 2020-08-17 ### Changed -* Update `rustls` to 0.18 +- Update `rustls` to 0.18 ## 2.0.0-beta.2 - 2020-07-21 ### Changed -* Update `actix-http` dependency to 2.0.0-beta.2 +- Update `actix-http` dependency to 2.0.0-beta.2 ## [2.0.0-beta.1] - 2020-07-14 ### Changed -* Update `actix-http` dependency to 2.0.0-beta.1 +- Update `actix-http` dependency to 2.0.0-beta.1 ## [2.0.0-alpha.2] - 2020-05-21 ### Changed -* Implement `std::error::Error` for our custom errors [#1422] -* Bump minimum supported Rust version to 1.40 -* Update `base64` dependency to 0.12 +- Implement `std::error::Error` for our custom errors [#1422] +- Bump minimum supported Rust version to 1.40 +- Update `base64` dependency to 0.12 [#1422]: https://github.com/actix/actix-web/pull/1422 ## [2.0.0-alpha.1] - 2020-03-11 -* Update `actix-http` dependency to 2.0.0-alpha.2 -* Update `rustls` dependency to 0.17 -* ClientBuilder accepts initial_window_size and initial_connection_window_size HTTP2 configuration -* ClientBuilder allowing to set max_http_version to limit HTTP version to be used +- Update `actix-http` dependency to 2.0.0-alpha.2 +- Update `rustls` dependency to 0.17 +- ClientBuilder accepts initial_window_size and initial_connection_window_size HTTP2 configuration +- ClientBuilder allowing to set max_http_version to limit HTTP version to be used ## [1.0.1] - 2019-12-15 -* Fix compilation with default features off +- Fix compilation with default features off ## [1.0.0] - 2019-12-13 -* Release +- Release ## [1.0.0-alpha.3] -* Migrate to `std::future` +- Migrate to `std::future` ## [0.2.8] - 2019-11-06 -* Add support for setting query from Serialize type for client request. +- Add support for setting query from Serialize type for client request. ## [0.2.7] - 2019-09-25 ### Added -* Remaining getter methods for `ClientRequest`'s private `head` field #1101 +- Remaining getter methods for `ClientRequest`'s private `head` field #1101 ## [0.2.6] - 2019-09-12 ### Added -* Export frozen request related types. +- Export frozen request related types. ## [0.2.5] - 2019-09-11 ### Added -* Add `FrozenClientRequest` to support retries for sending HTTP requests +- Add `FrozenClientRequest` to support retries for sending HTTP requests ### Changed -* Ensure that the `Host` header is set when initiating a WebSocket client connection. +- Ensure that the `Host` header is set when initiating a WebSocket client connection. ## [0.2.4] - 2019-08-13 ### Changed -* Update percent-encoding to "2.1" +- Update percent-encoding to "2.1" -* Update serde_urlencoded to "0.6.1" +- Update serde_urlencoded to "0.6.1" ## [0.2.3] - 2019-08-01 ### Added -* Add `rustls` support +- Add `rustls` support ## [0.2.2] - 2019-07-01 ### Changed -* Always append a colon after username in basic auth +- Always append a colon after username in basic auth -* Upgrade `rand` dependency version to 0.7 +- Upgrade `rand` dependency version to 0.7 ## [0.2.1] - 2019-06-05 ### Added -* Add license files +- Add license files ## [0.2.0] - 2019-05-12 ### Added -* Allow to send headers in `Camel-Case` form. +- Allow to send headers in `Camel-Case` form. ### Changed -* Upgrade actix-http dependency. +- Upgrade actix-http dependency. ## [0.1.1] - 2019-04-19 ### Added -* Allow to specify server address for http and ws requests. +- Allow to specify server address for http and ws requests. ### Changed -* `ClientRequest::if_true()` and `ClientRequest::if_some()` use instance instead of ref +- `ClientRequest::if_true()` and `ClientRequest::if_some()` use instance instead of ref ## [0.1.0] - 2019-04-16 -* No changes +- No changes ## [0.1.0-alpha.6] - 2019-04-14 ### Changed -* Do not set default headers for websocket request +- Do not set default headers for websocket request ## [0.1.0-alpha.5] - 2019-04-12 ### Changed -* Do not set any default headers +- Do not set any default headers ### Added -* Add Debug impl for BoxedSocket +- Add Debug impl for BoxedSocket ## [0.1.0-alpha.4] - 2019-04-08 ### Changed -* Update actix-http dependency +- Update actix-http dependency ## [0.1.0-alpha.3] - 2019-04-02 ### Added -* Export `MessageBody` type +- Export `MessageBody` type -* `ClientResponse::json()` - Loads and parse `application/json` encoded body +- `ClientResponse::json()` - Loads and parse `application/json` encoded body ### Changed -* `ClientRequest::json()` accepts reference instead of object. +- `ClientRequest::json()` accepts reference instead of object. -* `ClientResponse::body()` does not consume response object. +- `ClientResponse::body()` does not consume response object. -* Renamed `ClientRequest::close_connection()` to `ClientRequest::force_close()` +- Renamed `ClientRequest::close_connection()` to `ClientRequest::force_close()` ## [0.1.0-alpha.2] - 2019-03-29 ### Added -* Per request and session wide request timeout. +- Per request and session wide request timeout. -* Session wide headers. +- Session wide headers. -* Session wide basic and bearer auth. +- Session wide basic and bearer auth. -* Re-export `actix_http::client::Connector`. +- Re-export `actix_http::client::Connector`. ### Changed -* Allow to override request's uri +- Allow to override request's uri -* Export `ws` sub-module with websockets related types +- Export `ws` sub-module with websockets related types ## [0.1.0-alpha.1] - 2019-03-28 -* Initial impl +- Initial impl From b3ac918d7001a351ecec84317b053e862b6c561c Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Wed, 22 Dec 2021 08:34:48 +0000 Subject: [PATCH 370/428] update itoa to v1 --- Cargo.toml | 2 +- actix-http/Cargo.toml | 2 +- actix-http/src/header/shared/quality.rs | 18 ++++++++++++------ awc/Cargo.toml | 2 +- 4 files changed, 15 insertions(+), 9 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 02bef3af6..d15f26172 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -89,7 +89,7 @@ derive_more = "0.99.5" encoding_rs = "0.8" futures-core = { version = "0.3.7", default-features = false } futures-util = { version = "0.3.7", default-features = false } -itoa = "0.4" +itoa = "1" language-tags = "0.3" once_cell = "1.5" log = "0.4" diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 9f93bf6d2..e4eadd37c 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -60,7 +60,7 @@ h2 = "0.3.9" http = "0.2.5" httparse = "1.5.1" httpdate = "1.0.1" -itoa = "0.4" +itoa = "1" language-tags = "0.3" local-channel = "0.1" log = "0.4" diff --git a/actix-http/src/header/shared/quality.rs b/actix-http/src/header/shared/quality.rs index 5321c754d..c2f08edc2 100644 --- a/actix-http/src/header/shared/quality.rs +++ b/actix-http/src/header/shared/quality.rs @@ -87,7 +87,7 @@ impl fmt::Display for Quality { // 0 is already handled so it's not possible to have a trailing 0 in this range // we can just write the integer - itoa::fmt(f, x) + itoa_fmt(f, x) } else if x < 100 { // x in is range 10โ€“99 @@ -95,21 +95,21 @@ impl fmt::Display for Quality { if x % 10 == 0 { // trailing 0, divide by 10 and write - itoa::fmt(f, x / 10) + itoa_fmt(f, x / 10) } else { - itoa::fmt(f, x) + itoa_fmt(f, x) } } else { // x is in range 100โ€“999 if x % 100 == 0 { // two trailing 0s, divide by 100 and write - itoa::fmt(f, x / 100) + itoa_fmt(f, x / 100) } else if x % 10 == 0 { // one trailing 0, divide by 10 and write - itoa::fmt(f, x / 10) + itoa_fmt(f, x / 10) } else { - itoa::fmt(f, x) + itoa_fmt(f, x) } } } @@ -117,6 +117,12 @@ impl fmt::Display for Quality { } } +/// Write integer to a `fmt::Write`. +pub fn itoa_fmt(mut wr: W, value: V) -> fmt::Result { + let mut buf = itoa::Buffer::new(); + wr.write_str(buf.format(value)) +} + #[derive(Debug, Clone, Display, Error)] #[display(fmt = "quality out of bounds")] #[non_exhaustive] diff --git a/awc/Cargo.toml b/awc/Cargo.toml index f9a541c7e..cf12f2383 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -74,7 +74,7 @@ futures-core = { version = "0.3.7", default-features = false, features = ["alloc futures-util = { version = "0.3.7", default-features = false, features = ["alloc", "sink"] } h2 = "0.3.9" http = "0.2.5" -itoa = "0.4" +itoa = "1" log =" 0.4" mime = "0.3" percent-encoding = "2.1" From 324eba7e0b5a0451025a61828da89dbb20f17966 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Wed, 22 Dec 2021 08:41:44 +0000 Subject: [PATCH 371/428] tighten tokio version range to prevent RUSTSEC-2021-0124 --- actix-http-test/Cargo.toml | 2 +- actix-http/Cargo.toml | 2 +- actix-multipart/Cargo.toml | 2 +- actix-test/Cargo.toml | 2 +- actix-web-actors/Cargo.toml | 2 +- awc/Cargo.toml | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/actix-http-test/Cargo.toml b/actix-http-test/Cargo.toml index 0c205fc2a..e1c875a1f 100644 --- a/actix-http-test/Cargo.toml +++ b/actix-http-test/Cargo.toml @@ -48,7 +48,7 @@ serde_json = "1.0" slab = "0.4" serde_urlencoded = "0.7" tls-openssl = { version = "0.10.9", package = "openssl", optional = true } -tokio = { version = "1.2", features = ["sync"] } +tokio = { version = "1.8", features = ["sync"] } [dev-dependencies] actix-web = { version = "4.0.0-beta.15", default-features = false, features = ["cookies"] } diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index e4eadd37c..3ad3d786e 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -97,7 +97,7 @@ serde_json = "1.0" static_assertions = "1" tls-openssl = { package = "openssl", version = "0.10.9" } tls-rustls = { package = "rustls", version = "0.20.0" } -tokio = { version = "1.2", features = ["net", "rt", "macros"] } +tokio = { version = "1.8", features = ["net", "rt", "macros"] } [[example]] name = "ws" diff --git a/actix-multipart/Cargo.toml b/actix-multipart/Cargo.toml index c7145e542..595c14d7e 100644 --- a/actix-multipart/Cargo.toml +++ b/actix-multipart/Cargo.toml @@ -30,5 +30,5 @@ twoway = "0.2" actix-rt = "2.2" actix-http = "3.0.0-beta.16" futures-util = { version = "0.3.7", default-features = false, features = ["alloc"] } -tokio = { version = "1", features = ["sync"] } +tokio = { version = "1.8", features = ["sync"] } tokio-stream = "0.1" diff --git a/actix-test/Cargo.toml b/actix-test/Cargo.toml index 7957b3a9c..4a4615820 100644 --- a/actix-test/Cargo.toml +++ b/actix-test/Cargo.toml @@ -45,4 +45,4 @@ serde_json = "1" serde_urlencoded = "0.7" tls-openssl = { package = "openssl", version = "0.10.9", optional = true } tls-rustls = { package = "rustls", version = "0.20.0", optional = true } -tokio = { version = "1.2", features = ["sync"] } +tokio = { version = "1.8", features = ["sync"] } diff --git a/actix-web-actors/Cargo.toml b/actix-web-actors/Cargo.toml index 3f213f378..d57f139f6 100644 --- a/actix-web-actors/Cargo.toml +++ b/actix-web-actors/Cargo.toml @@ -23,7 +23,7 @@ bytes = "1" bytestring = "1" futures-core = { version = "0.3.7", default-features = false } pin-project-lite = "0.2" -tokio = { version = "1", features = ["sync"] } +tokio = { version = "1.8", features = ["sync"] } [dev-dependencies] actix-rt = "2.2" diff --git a/awc/Cargo.toml b/awc/Cargo.toml index cf12f2383..4b29aac16 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -83,7 +83,7 @@ rand = "0.8" serde = "1.0" serde_json = "1.0" serde_urlencoded = "0.7" -tokio = { version = "1", features = ["sync"] } +tokio = { version = "1.8", features = ["sync"] } cookie = { version = "0.15", features = ["percent-encode"], optional = true } From 1769812d0b19cd9df59c3c60b4c35f495bee5d1b Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Wed, 22 Dec 2021 08:43:38 +0000 Subject: [PATCH 372/428] bump outdated deps --- actix-http/Cargo.toml | 2 +- actix-router/Cargo.toml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 3ad3d786e..2958a1c77 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -68,7 +68,7 @@ mime = "0.3" percent-encoding = "2.1" pin-project-lite = "0.2" rand = "0.8" -sha-1 = "0.9" +sha-1 = "0.10" smallvec = "1.6.1" # tls diff --git a/actix-router/Cargo.toml b/actix-router/Cargo.toml index afd39dfd3..c63448bc7 100644 --- a/actix-router/Cargo.toml +++ b/actix-router/Cargo.toml @@ -21,7 +21,7 @@ default = ["http"] [dependencies] bytestring = ">=0.1.5, <2" -firestorm = "0.4" +firestorm = "0.5" http = { version = "0.2.3", optional = true } log = "0.4" regex = "1.5" @@ -29,7 +29,7 @@ serde = "1" [dev-dependencies] criterion = { version = "0.3", features = ["html_reports"] } -firestorm = { version = "0.4", features = ["enable_system_time"] } +firestorm = { version = "0.5", features = ["enable_system_time"] } http = "0.2.5" serde = { version = "1", features = ["derive"] } From cd025f5c0ba7774263cbae38fd6b4c652b4d54a3 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Wed, 22 Dec 2021 15:00:32 +0000 Subject: [PATCH 373/428] allow any body type in Resource (#2526) --- CHANGES.md | 4 +++ actix-http/Cargo.toml | 1 - src/middleware/compat.rs | 9 +++++ src/middleware/mod.rs | 4 +++ src/middleware/noop.rs | 37 +++++++++++++++++++++ src/resource.rs | 72 +++++++++++++++++++++++++++++----------- src/scope.rs | 10 +++--- 7 files changed, 112 insertions(+), 25 deletions(-) create mode 100644 src/middleware/noop.rs diff --git a/CHANGES.md b/CHANGES.md index 8e030819f..07c247554 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,10 @@ # Changes ## Unreleased - 2021-xx-xx +### Changed +- No longer require `Resource` service body type to be boxed. [#2526] + +[#2526]: https://github.com/actix/actix-web/pull/2526 ## 4.0.0-beta.15 - 2021-12-17 diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 2958a1c77..c15f5ee28 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -55,7 +55,6 @@ bytestring = "1" derive_more = "0.99.5" encoding_rs = "0.8" futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] } -futures-task = { version = "0.3.7", default-features = false, features = ["alloc"] } h2 = "0.3.9" http = "0.2.5" httparse = "1.5.1" diff --git a/src/middleware/compat.rs b/src/middleware/compat.rs index d49c461c4..3386240b7 100644 --- a/src/middleware/compat.rs +++ b/src/middleware/compat.rs @@ -38,6 +38,15 @@ pub struct Compat { transform: T, } +#[cfg(test)] +impl Compat { + pub(crate) fn noop() -> Self { + Self { + transform: super::Noop, + } + } +} + impl Compat { /// Wrap a middleware to give it broader compatibility. pub fn new(middleware: T) -> Self { diff --git a/src/middleware/mod.rs b/src/middleware/mod.rs index 0da9b9b2e..a781052a6 100644 --- a/src/middleware/mod.rs +++ b/src/middleware/mod.rs @@ -5,6 +5,8 @@ mod condition; mod default_headers; mod err_handlers; mod logger; +#[cfg(test)] +mod noop; mod normalize; pub use self::compat::Compat; @@ -12,6 +14,8 @@ pub use self::condition::Condition; pub use self::default_headers::DefaultHeaders; pub use self::err_handlers::{ErrorHandlerResponse, ErrorHandlers}; pub use self::logger::Logger; +#[cfg(test)] +pub(crate) use self::noop::Noop; pub use self::normalize::{NormalizePath, TrailingSlash}; #[cfg(feature = "__compress")] diff --git a/src/middleware/noop.rs b/src/middleware/noop.rs new file mode 100644 index 000000000..ae7da1d81 --- /dev/null +++ b/src/middleware/noop.rs @@ -0,0 +1,37 @@ +//! A no-op middleware. See [Noop] for docs. + +use actix_utils::future::{ready, Ready}; + +use crate::dev::{Service, Transform}; + +/// A no-op middleware that passes through request and response untouched. +pub(crate) struct Noop; + +impl, Req> Transform for Noop { + type Response = S::Response; + type Error = S::Error; + type Transform = NoopService; + type InitError = (); + type Future = Ready>; + + fn new_transform(&self, service: S) -> Self::Future { + ready(Ok(NoopService { service })) + } +} + +#[doc(hidden)] +pub(crate) struct NoopService { + service: S, +} + +impl, Req> Service for NoopService { + type Response = S::Response; + type Error = S::Error; + type Future = S::Future; + + crate::dev::forward_ready!(service); + + fn call(&self, req: Req) -> Self::Future { + self.service.call(req) + } +} diff --git a/src/resource.rs b/src/resource.rs index c13544063..d94d2a464 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -1,6 +1,6 @@ -use std::{cell::RefCell, fmt, future::Future, rc::Rc}; +use std::{cell::RefCell, fmt, future::Future, marker::PhantomData, rc::Rc}; -use actix_http::Extensions; +use actix_http::{body::BoxBody, Extensions}; use actix_router::{IntoPatterns, Patterns}; use actix_service::{ apply, apply_fn_factory, boxed, fn_service, IntoServiceFactory, Service, ServiceFactory, @@ -45,7 +45,7 @@ use crate::{ /// /// If no matching route could be found, *405* response code get returned. /// Default behavior could be overridden with `default_resource()` method. -pub struct Resource { +pub struct Resource { endpoint: T, rdef: Patterns, name: Option, @@ -54,6 +54,7 @@ pub struct Resource { guards: Vec>, default: BoxedHttpServiceFactory, factory_ref: Rc>>, + _phantom: PhantomData, } impl Resource { @@ -71,19 +72,21 @@ impl Resource { default: boxed::factory(fn_service(|req: ServiceRequest| async { Ok(req.into_response(HttpResponse::MethodNotAllowed())) })), + _phantom: PhantomData, } } } -impl Resource +impl Resource where T: ServiceFactory< ServiceRequest, Config = (), - Response = ServiceResponse, + Response = ServiceResponse, Error = Error, InitError = (), >, + B: MessageBody, { /// Set resource name. /// @@ -252,26 +255,28 @@ where /// type (i.e modify response's body). /// /// **Note**: middlewares get called in opposite order of middlewares registration. - pub fn wrap( + pub fn wrap( self, mw: M, ) -> Resource< impl ServiceFactory< ServiceRequest, Config = (), - Response = ServiceResponse, + Response = ServiceResponse, Error = Error, InitError = (), >, + B1, > where M: Transform< T::Service, ServiceRequest, - Response = ServiceResponse, + Response = ServiceResponse, Error = Error, InitError = (), >, + B1: MessageBody, { Resource { endpoint: apply(mw, self.endpoint), @@ -282,6 +287,7 @@ where default: self.default, app_data: self.app_data, factory_ref: self.factory_ref, + _phantom: PhantomData, } } @@ -319,21 +325,23 @@ where /// .route(web::get().to(index))); /// } /// ``` - pub fn wrap_fn( + pub fn wrap_fn( self, mw: F, ) -> Resource< impl ServiceFactory< ServiceRequest, Config = (), - Response = ServiceResponse, + Response = ServiceResponse, Error = Error, InitError = (), >, + B1, > where F: Fn(ServiceRequest, &T::Service) -> R + Clone, - R: Future>, + R: Future, Error>>, + B1: MessageBody, { Resource { endpoint: apply_fn_factory(self.endpoint, mw), @@ -344,6 +352,7 @@ where default: self.default, app_data: self.app_data, factory_ref: self.factory_ref, + _phantom: PhantomData, } } @@ -371,15 +380,16 @@ where } } -impl HttpServiceFactory for Resource +impl HttpServiceFactory for Resource where T: ServiceFactory< ServiceRequest, Config = (), - Response = ServiceResponse, + Response = ServiceResponse, Error = Error, InitError = (), > + 'static, + B: MessageBody + 'static, { fn register(mut self, config: &mut AppService) { let guards = if self.guards.is_empty() { @@ -411,7 +421,9 @@ where req.add_data_container(Rc::clone(data)); } - srv.call(req) + let fut = srv.call(req); + + async { Ok(fut.await?.map_into_boxed_body()) } }); config.register_service(rdef, guards, endpoint, None) @@ -534,11 +546,11 @@ mod tests { >, > { web::resource("/test-compat") - // .wrap_fn(|req, srv| { - // let fut = srv.call(req); - // async { Ok(fut.await?.map_into_right_body::<()>()) } - // }) - .wrap(Compat::new(DefaultHeaders::new())) + .wrap_fn(|req, srv| { + let fut = srv.call(req); + async { Ok(fut.await?.map_into_right_body::<()>()) } + }) + .wrap(Compat::noop()) .route(web::get().to(|| async { "hello" })) } @@ -801,4 +813,26 @@ mod tests { let resp = call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::BAD_REQUEST); } + + #[actix_rt::test] + async fn test_middleware_body_type() { + let srv = init_service( + App::new().service( + web::resource("/test") + .wrap_fn(|req, srv| { + let fut = srv.call(req); + async { Ok(fut.await?.map_into_right_body::<()>()) } + }) + .route(web::get().to(|| async { "hello" })), + ), + ) + .await; + + // test if `try_into_bytes()` is preserved across scope layer + use actix_http::body::MessageBody as _; + let req = TestRequest::with_uri("/test").to_request(); + let resp = call_service(&srv, req).await; + let body = resp.into_body(); + assert_eq!(body.try_into_bytes().unwrap(), b"hello".as_ref()); + } } diff --git a/src/scope.rs b/src/scope.rs index 35bbb50ba..7f9a94875 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -616,11 +616,11 @@ mod tests { >, > { web::scope("/test-compat") - // .wrap_fn(|req, srv| { - // let fut = srv.call(req); - // async { Ok(fut.await?.map_into_right_body::<()>()) } - // }) - .wrap(Compat::new(DefaultHeaders::new())) + .wrap_fn(|req, srv| { + let fut = srv.call(req); + async { Ok(fut.await?.map_into_right_body::<()>()) } + }) + .wrap(Compat::noop()) .service(web::resource("").route(web::get().to(|| async { "hello" }))) } From 7b1512d863ed63cb7a1fab51ff358b24b33c5f19 Mon Sep 17 00:00:00 2001 From: Ali MJ Al-Nasrawy Date: Wed, 22 Dec 2021 18:48:59 +0300 Subject: [PATCH 374/428] allow any body type in Scope (#2523) --- CHANGES.md | 2 ++ src/middleware/compat.rs | 2 +- src/scope.rs | 37 +++++++++++++++++++++++++++++++++---- 3 files changed, 36 insertions(+), 5 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 07c247554..a43b3ee41 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -2,8 +2,10 @@ ## Unreleased - 2021-xx-xx ### Changed +- No longer require `Scope` service body type to be boxed. [#2523] - No longer require `Resource` service body type to be boxed. [#2526] +[#2523]: https://github.com/actix/actix-web/pull/2523 [#2526]: https://github.com/actix/actix-web/pull/2526 diff --git a/src/middleware/compat.rs b/src/middleware/compat.rs index 3386240b7..18c9ff6a7 100644 --- a/src/middleware/compat.rs +++ b/src/middleware/compat.rs @@ -17,7 +17,7 @@ use crate::{ }; /// Middleware for enabling any middleware to be used in [`Resource::wrap`](crate::Resource::wrap), -/// [`Scope::wrap`](crate::Scope::wrap) and [`Condition`](super::Condition). +/// and [`Condition`](super::Condition). /// /// # Examples /// ``` diff --git a/src/scope.rs b/src/scope.rs index 7f9a94875..1fd282f61 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -1,6 +1,9 @@ use std::{cell::RefCell, fmt, future::Future, marker::PhantomData, mem, rc::Rc}; -use actix_http::{body::BoxBody, Extensions}; +use actix_http::{ + body::{BoxBody, MessageBody}, + Extensions, +}; use actix_router::{ResourceDef, Router}; use actix_service::{ apply, apply_fn_factory, boxed, IntoServiceFactory, Service, ServiceFactory, @@ -399,15 +402,16 @@ where } } -impl HttpServiceFactory for Scope +impl HttpServiceFactory for Scope where T: ServiceFactory< ServiceRequest, Config = (), - Response = ServiceResponse, + Response = ServiceResponse, Error = Error, InitError = (), > + 'static, + B: MessageBody + 'static, { fn register(mut self, config: &mut AppService) { // update default resource if needed @@ -457,7 +461,9 @@ where req.add_data_container(Rc::clone(data)); } - srv.call(req) + let fut = srv.call(req); + + async { Ok(fut.await?.map_into_boxed_body()) } }); // register final service @@ -980,6 +986,29 @@ mod tests { ); } + #[actix_rt::test] + async fn test_middleware_body_type() { + // Compile test that Scope accepts any body type; test for `EitherBody` + let srv = init_service( + App::new().service( + web::scope("app") + .wrap_fn(|req, srv| { + let fut = srv.call(req); + async { Ok(fut.await?.map_into_right_body::<()>()) } + }) + .service(web::resource("/test").route(web::get().to(|| async { "hello" }))), + ), + ) + .await; + + // test if `MessageBody::try_into_bytes()` is preserved across scope layer + use actix_http::body::MessageBody as _; + let req = TestRequest::with_uri("/app/test").to_request(); + let resp = call_service(&srv, req).await; + let body = resp.into_body(); + assert_eq!(body.try_into_bytes().unwrap(), b"hello".as_ref()); + } + #[actix_rt::test] async fn test_middleware_fn() { let srv = init_service( From 1296e07c4830f0ab2e2864a6fc9faa93972e5935 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Fri, 24 Dec 2021 17:47:47 +0000 Subject: [PATCH 375/428] relax unpin bounds on payload types (#2545) --- actix-http/CHANGES.md | 9 ++++ actix-http/src/encoding/decoder.rs | 39 ++++++++------- actix-http/src/h1/dispatcher.rs | 7 +-- actix-http/src/h1/payload.rs | 76 +++++++++++++++++------------- actix-http/src/h1/utils.rs | 4 +- actix-http/src/h2/dispatcher.rs | 4 +- actix-http/src/h2/mod.rs | 11 +++++ actix-http/src/lib.rs | 3 +- actix-http/src/payload.rs | 73 +++++++++++++++++----------- actix-http/src/requests/request.rs | 11 +++-- actix-http/src/test.rs | 2 +- actix-http/tests/test_rustls.rs | 33 +++++++++---- actix-multipart/src/server.rs | 2 +- awc/src/client/connection.rs | 4 +- awc/src/client/h1proto.rs | 16 +++++-- awc/src/response.rs | 6 +-- awc/src/sender.rs | 15 +++--- awc/src/test.rs | 5 +- src/dev.rs | 2 +- src/response/builder.rs | 36 ++++++-------- src/service.rs | 4 +- src/test/test_request.rs | 21 +++++---- 22 files changed, 229 insertions(+), 154 deletions(-) diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 3b45e934f..adc4c35c7 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -3,8 +3,17 @@ ## Unreleased - 2021-xx-xx ### Changes - `HeaderMap::get_all` now returns a `std::slice::Iter`. [#2527] +- `Payload` inner fields are now named. [#2545] +- `impl Stream` for `Payload` no longer requires the `Stream` variant be `Unpin`. [#2545] +- `impl Future` for `h1::SendResponse` no longer requires the body type be `Unpin`. [#2545] +- `impl Stream` for `encoding::Decoder` no longer requires the stream type be `Unpin`. [#2545] +- Rename `PayloadStream` to `BoxedPayloadStream`. [#2545] + +### Removed +- `h1::Payload::readany`. [#2545] [#2527]: https://github.com/actix/actix-web/pull/2527 +[#2545]: https://github.com/actix/actix-web/pull/2545 ## 3.0.0-beta.16 - 2021-12-17 diff --git a/actix-http/src/encoding/decoder.rs b/actix-http/src/encoding/decoder.rs index a46e330c9..0f519637a 100644 --- a/actix-http/src/encoding/decoder.rs +++ b/actix-http/src/encoding/decoder.rs @@ -28,11 +28,14 @@ use crate::{ const MAX_CHUNK_SIZE_DECODE_IN_PLACE: usize = 2049; -pub struct Decoder { - decoder: Option, - stream: S, - eof: bool, - fut: Option, ContentDecoder), io::Error>>>, +pin_project_lite::pin_project! { + pub struct Decoder { + decoder: Option, + #[pin] + stream: S, + eof: bool, + fut: Option, ContentDecoder), io::Error>>>, + } } impl Decoder @@ -89,42 +92,44 @@ where impl Stream for Decoder where - S: Stream> + Unpin, + S: Stream>, { type Item = Result; - fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + let mut this = self.project(); + loop { - if let Some(ref mut fut) = self.fut { + if let Some(ref mut fut) = this.fut { let (chunk, decoder) = ready!(Pin::new(fut).poll(cx)).map_err(|_| BlockingError)??; - self.decoder = Some(decoder); - self.fut.take(); + *this.decoder = Some(decoder); + this.fut.take(); if let Some(chunk) = chunk { return Poll::Ready(Some(Ok(chunk))); } } - if self.eof { + if *this.eof { return Poll::Ready(None); } - match ready!(Pin::new(&mut self.stream).poll_next(cx)) { + match ready!(this.stream.as_mut().poll_next(cx)) { Some(Err(err)) => return Poll::Ready(Some(Err(err))), Some(Ok(chunk)) => { - if let Some(mut decoder) = self.decoder.take() { + if let Some(mut decoder) = this.decoder.take() { if chunk.len() < MAX_CHUNK_SIZE_DECODE_IN_PLACE { let chunk = decoder.feed_data(chunk)?; - self.decoder = Some(decoder); + *this.decoder = Some(decoder); if let Some(chunk) = chunk { return Poll::Ready(Some(Ok(chunk))); } } else { - self.fut = Some(spawn_blocking(move || { + *this.fut = Some(spawn_blocking(move || { let chunk = decoder.feed_data(chunk)?; Ok((chunk, decoder)) })); @@ -137,9 +142,9 @@ where } None => { - self.eof = true; + *this.eof = true; - return if let Some(mut decoder) = self.decoder.take() { + return if let Some(mut decoder) = this.decoder.take() { match decoder.feed_eof() { Ok(Some(res)) => Poll::Ready(Some(Ok(res))), Ok(None) => Poll::Ready(None), diff --git a/actix-http/src/h1/dispatcher.rs b/actix-http/src/h1/dispatcher.rs index 5c0cb64af..13055f08a 100644 --- a/actix-http/src/h1/dispatcher.rs +++ b/actix-http/src/h1/dispatcher.rs @@ -646,10 +646,11 @@ where Payload is attached to Request and passed to Service::call where the state can be collected and consumed. */ - let (ps, pl) = Payload::create(false); - let (req1, _) = req.replace_payload(crate::Payload::H1(pl)); + let (sender, payload) = Payload::create(false); + let (req1, _) = + req.replace_payload(crate::Payload::H1 { payload }); req = req1; - *this.payload = Some(ps); + *this.payload = Some(sender); } // Request has no payload. diff --git a/actix-http/src/h1/payload.rs b/actix-http/src/h1/payload.rs index f912e0ba3..4d031c15a 100644 --- a/actix-http/src/h1/payload.rs +++ b/actix-http/src/h1/payload.rs @@ -1,9 +1,12 @@ //! Payload stream -use std::cell::RefCell; -use std::collections::VecDeque; -use std::pin::Pin; -use std::rc::{Rc, Weak}; -use std::task::{Context, Poll, Waker}; + +use std::{ + cell::RefCell, + collections::VecDeque, + pin::Pin, + rc::{Rc, Weak}, + task::{Context, Poll, Waker}, +}; use bytes::Bytes; use futures_core::Stream; @@ -22,39 +25,32 @@ pub enum PayloadStatus { /// Buffered stream of bytes chunks /// -/// Payload stores chunks in a vector. First chunk can be received with -/// `.readany()` method. Payload stream is not thread safe. Payload does not -/// notify current task when new data is available. +/// Payload stores chunks in a vector. First chunk can be received with `poll_next`. Payload does +/// not notify current task when new data is available. /// -/// Payload stream can be used as `Response` body stream. +/// Payload can be used as `Response` body stream. #[derive(Debug)] pub struct Payload { inner: Rc>, } impl Payload { - /// Create payload stream. + /// Creates a payload stream. /// - /// This method construct two objects responsible for bytes stream - /// generation. - /// - /// * `PayloadSender` - *Sender* side of the stream - /// - /// * `Payload` - *Receiver* side of the stream + /// This method construct two objects responsible for bytes stream generation: + /// - `PayloadSender` - *Sender* side of the stream + /// - `Payload` - *Receiver* side of the stream pub fn create(eof: bool) -> (PayloadSender, Payload) { let shared = Rc::new(RefCell::new(Inner::new(eof))); ( - PayloadSender { - inner: Rc::downgrade(&shared), - }, + PayloadSender::new(Rc::downgrade(&shared)), Payload { inner: shared }, ) } - /// Create empty payload - #[doc(hidden)] - pub fn empty() -> Payload { + /// Creates an empty payload. + pub(crate) fn empty() -> Payload { Payload { inner: Rc::new(RefCell::new(Inner::new(true))), } @@ -77,14 +73,6 @@ impl Payload { pub fn unread_data(&mut self, data: Bytes) { self.inner.borrow_mut().unread_data(data); } - - #[inline] - pub fn readany( - &mut self, - cx: &mut Context<'_>, - ) -> Poll>> { - self.inner.borrow_mut().readany(cx) - } } impl Stream for Payload { @@ -94,7 +82,7 @@ impl Stream for Payload { self: Pin<&mut Self>, cx: &mut Context<'_>, ) -> Poll>> { - self.inner.borrow_mut().readany(cx) + Pin::new(&mut *self.inner.borrow_mut()).poll_next(cx) } } @@ -104,6 +92,10 @@ pub struct PayloadSender { } impl PayloadSender { + fn new(inner: Weak>) -> Self { + Self { inner } + } + #[inline] pub fn set_error(&mut self, err: PayloadError) { if let Some(shared) = self.inner.upgrade() { @@ -227,7 +219,10 @@ impl Inner { self.len } - fn readany(&mut self, cx: &mut Context<'_>) -> Poll>> { + fn poll_next( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + ) -> Poll>> { if let Some(data) = self.items.pop_front() { self.len -= data.len(); self.need_read = self.len < MAX_BUFFER_SIZE; @@ -257,8 +252,18 @@ impl Inner { #[cfg(test)] mod tests { - use super::*; + use std::panic::{RefUnwindSafe, UnwindSafe}; + use actix_utils::future::poll_fn; + use static_assertions::{assert_impl_all, assert_not_impl_any}; + + use super::*; + + assert_impl_all!(Payload: Unpin); + assert_not_impl_any!(Payload: Send, Sync, UnwindSafe, RefUnwindSafe); + + assert_impl_all!(Inner: Unpin, Send, Sync); + assert_not_impl_any!(Inner: UnwindSafe, RefUnwindSafe); #[actix_rt::test] async fn test_unread_data() { @@ -270,7 +275,10 @@ mod tests { assert_eq!( Bytes::from("data"), - poll_fn(|cx| payload.readany(cx)).await.unwrap().unwrap() + poll_fn(|cx| Pin::new(&mut payload).poll_next(cx)) + .await + .unwrap() + .unwrap() ); } } diff --git a/actix-http/src/h1/utils.rs b/actix-http/src/h1/utils.rs index 131c7f1ed..5c11b1dab 100644 --- a/actix-http/src/h1/utils.rs +++ b/actix-http/src/h1/utils.rs @@ -45,7 +45,7 @@ where impl Future for SendResponse where T: AsyncRead + AsyncWrite + Unpin, - B: MessageBody + Unpin, + B: MessageBody, B::Error: Into, { type Output = Result, Error>; @@ -81,7 +81,7 @@ where // body is done when item is None body_done = item.is_none(); if body_done { - let _ = this.body.take(); + this.body.set(None); } let framed = this.framed.as_mut().as_pin_mut().unwrap(); framed diff --git a/actix-http/src/h2/dispatcher.rs b/actix-http/src/h2/dispatcher.rs index 8fbefe6de..7f0f15ee6 100644 --- a/actix-http/src/h2/dispatcher.rs +++ b/actix-http/src/h2/dispatcher.rs @@ -108,8 +108,8 @@ where match Pin::new(&mut this.connection).poll_accept(cx)? { Poll::Ready(Some((req, tx))) => { let (parts, body) = req.into_parts(); - let pl = crate::h2::Payload::new(body); - let pl = Payload::H2(pl); + let payload = crate::h2::Payload::new(body); + let pl = Payload::H2 { payload }; let mut req = Request::with_payload(pl); let head = req.head_mut(); diff --git a/actix-http/src/h2/mod.rs b/actix-http/src/h2/mod.rs index cbcb6d0fc..47d51b420 100644 --- a/actix-http/src/h2/mod.rs +++ b/actix-http/src/h2/mod.rs @@ -98,3 +98,14 @@ where } } } + +#[cfg(test)] +mod tests { + use std::panic::{RefUnwindSafe, UnwindSafe}; + + use static_assertions::assert_impl_all; + + use super::*; + + assert_impl_all!(Payload: Unpin, Send, Sync, UnwindSafe, RefUnwindSafe); +} diff --git a/actix-http/src/lib.rs b/actix-http/src/lib.rs index 2b7bc730b..f2b415790 100644 --- a/actix-http/src/lib.rs +++ b/actix-http/src/lib.rs @@ -58,7 +58,8 @@ pub use self::header::ContentEncoding; pub use self::http_message::HttpMessage; pub use self::message::ConnectionType; pub use self::message::Message; -pub use self::payload::{Payload, PayloadStream}; +#[allow(deprecated)] +pub use self::payload::{BoxedPayloadStream, Payload, PayloadStream}; pub use self::requests::{Request, RequestHead, RequestHeadType}; pub use self::responses::{Response, ResponseBuilder, ResponseHead}; pub use self::service::HttpService; diff --git a/actix-http/src/payload.rs b/actix-http/src/payload.rs index 69840e7c1..c9f338c7d 100644 --- a/actix-http/src/payload.rs +++ b/actix-http/src/payload.rs @@ -1,70 +1,89 @@ use std::{ + mem, pin::Pin, task::{Context, Poll}, }; use bytes::Bytes; use futures_core::Stream; -use h2::RecvStream; use crate::error::PayloadError; -// TODO: rename to boxed payload -/// A boxed payload. -pub type PayloadStream = Pin>>>; +/// A boxed payload stream. +pub type BoxedPayloadStream = Pin>>>; -/// A streaming payload. -pub enum Payload { - None, - H1(crate::h1::Payload), - H2(crate::h2::Payload), - Stream(S), +#[deprecated(since = "4.0.0", note = "Renamed to `BoxedPayloadStream`.")] +pub type PayloadStream = BoxedPayloadStream; + +pin_project_lite::pin_project! { + /// A streaming payload. + #[project = PayloadProj] + pub enum Payload { + None, + H1 { payload: crate::h1::Payload }, + H2 { payload: crate::h2::Payload }, + Stream { #[pin] payload: S }, + } } impl From for Payload { - fn from(v: crate::h1::Payload) -> Self { - Payload::H1(v) + fn from(payload: crate::h1::Payload) -> Self { + Payload::H1 { payload } } } impl From for Payload { - fn from(v: crate::h2::Payload) -> Self { - Payload::H2(v) + fn from(payload: crate::h2::Payload) -> Self { + Payload::H2 { payload } } } -impl From for Payload { - fn from(v: RecvStream) -> Self { - Payload::H2(crate::h2::Payload::new(v)) +impl From for Payload { + fn from(stream: h2::RecvStream) -> Self { + Payload::H2 { + payload: crate::h2::Payload::new(stream), + } } } -impl From for Payload { - fn from(pl: PayloadStream) -> Self { - Payload::Stream(pl) +impl From for Payload { + fn from(payload: BoxedPayloadStream) -> Self { + Payload::Stream { payload } } } impl Payload { /// Takes current payload and replaces it with `None` value pub fn take(&mut self) -> Payload { - std::mem::replace(self, Payload::None) + mem::replace(self, Payload::None) } } impl Stream for Payload where - S: Stream> + Unpin, + S: Stream>, { type Item = Result; #[inline] fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - match self.get_mut() { - Payload::None => Poll::Ready(None), - Payload::H1(ref mut pl) => pl.readany(cx), - Payload::H2(ref mut pl) => Pin::new(pl).poll_next(cx), - Payload::Stream(ref mut pl) => Pin::new(pl).poll_next(cx), + match self.project() { + PayloadProj::None => Poll::Ready(None), + PayloadProj::H1 { payload } => Pin::new(payload).poll_next(cx), + PayloadProj::H2 { payload } => Pin::new(payload).poll_next(cx), + PayloadProj::Stream { payload } => payload.poll_next(cx), } } } + +#[cfg(test)] +mod tests { + use std::panic::{RefUnwindSafe, UnwindSafe}; + + use static_assertions::{assert_impl_all, assert_not_impl_any}; + + use super::*; + + assert_impl_all!(Payload: Unpin); + assert_not_impl_any!(Payload: Send, Sync, UnwindSafe, RefUnwindSafe); +} diff --git a/actix-http/src/requests/request.rs b/actix-http/src/requests/request.rs index 0254a8f11..4eaaba8e1 100644 --- a/actix-http/src/requests/request.rs +++ b/actix-http/src/requests/request.rs @@ -10,11 +10,12 @@ use std::{ use http::{header, Method, Uri, Version}; use crate::{ - header::HeaderMap, Extensions, HttpMessage, Message, Payload, PayloadStream, RequestHead, + header::HeaderMap, BoxedPayloadStream, Extensions, HttpMessage, Message, Payload, + RequestHead, }; /// An HTTP request. -pub struct Request

{ +pub struct Request

{ pub(crate) payload: Payload

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

HttpMessage for Request

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

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

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

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

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

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

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

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

, - P: ResourcePath, + R: Resource, { profile_method!(recognize_mut_checked); diff --git a/src/service.rs b/src/service.rs index 975556197..f15cbfc9f 100644 --- a/src/service.rs +++ b/src/service.rs @@ -307,9 +307,11 @@ impl ServiceRequest { } } -impl Resource for ServiceRequest { +impl Resource for ServiceRequest { + type Path = Url; + #[inline] - fn resource_path(&mut self) -> &mut Path { + fn resource_path(&mut self) -> &mut Path { self.match_info_mut() } } From 2462b6dd5d2162f8bbe26c3cbccb351e6497d31e Mon Sep 17 00:00:00 2001 From: Ali MJ Al-Nasrawy Date: Wed, 5 Jan 2022 07:42:52 +0300 Subject: [PATCH 426/428] generalize impl Responder for HttpResponse (#2567) Co-authored-by: Rob Ede --- CHANGES.md | 4 ++++ Cargo.toml | 1 + src/response/builder.rs | 11 ++++++++++- src/response/customize_responder.rs | 8 ++------ src/response/responder.rs | 22 +--------------------- src/response/response.rs | 21 ++++++++++++++++++++- 6 files changed, 38 insertions(+), 29 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index e39eaef61..922e0c7c8 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,10 @@ # Changes ## Unreleased - 2021-xx-xx +### Changed +- `HttpResponse` can now be used as a `Responder` with any body type. + +[#2501]: https://github.com/actix/actix-web/pull/2501 ## 4.0.0-beta.19 - 2022-01-04 diff --git a/Cargo.toml b/Cargo.toml index 130307827..a2b8fb885 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -118,6 +118,7 @@ futures-util = { version = "0.3.7", default-features = false, features = ["std"] rand = "0.8" rcgen = "0.8" rustls-pemfile = "0.2" +static_assertions = "1" tls-openssl = { package = "openssl", version = "0.10.9" } tls-rustls = { package = "rustls", version = "0.20.0" } zstd = "0.9" diff --git a/src/response/builder.rs b/src/response/builder.rs index 93d8ab567..bdb0aaa12 100644 --- a/src/response/builder.rs +++ b/src/response/builder.rs @@ -23,7 +23,7 @@ use cookie::{Cookie, CookieJar}; use crate::{ error::{Error, JsonPayloadError}, - BoxError, HttpResponse, + BoxError, HttpRequest, HttpResponse, Responder, }; /// An HTTP response builder. @@ -424,6 +424,15 @@ impl Future for HttpResponseBuilder { } } +impl Responder for HttpResponseBuilder { + type Body = BoxBody; + + #[inline] + fn respond_to(mut self, _: &HttpRequest) -> HttpResponse { + self.finish() + } +} + #[cfg(test)] mod tests { use actix_http::body; diff --git a/src/response/customize_responder.rs b/src/response/customize_responder.rs index 11f6b2916..8cb146dda 100644 --- a/src/response/customize_responder.rs +++ b/src/response/customize_responder.rs @@ -1,12 +1,9 @@ use actix_http::{ - body::{EitherBody, MessageBody}, - error::HttpError, - header::HeaderMap, - header::TryIntoHeaderPair, + body::EitherBody, error::HttpError, header::HeaderMap, header::TryIntoHeaderPair, StatusCode, }; -use crate::{BoxError, HttpRequest, HttpResponse, Responder}; +use crate::{HttpRequest, HttpResponse, Responder}; /// Allows overriding status code and headers for a [`Responder`]. /// @@ -143,7 +140,6 @@ impl CustomizeResponder { impl Responder for CustomizeResponder where T: Responder, - ::Error: Into, { type Body = EitherBody; diff --git a/src/response/responder.rs b/src/response/responder.rs index 319b824f1..d1b9e49e0 100644 --- a/src/response/responder.rs +++ b/src/response/responder.rs @@ -7,7 +7,7 @@ use actix_http::{ }; use bytes::{Bytes, BytesMut}; -use crate::{BoxError, Error, HttpRequest, HttpResponse, HttpResponseBuilder}; +use crate::{Error, HttpRequest, HttpResponse}; use super::CustomizeResponder; @@ -57,15 +57,6 @@ pub trait Responder { } } -impl Responder for HttpResponse { - type Body = BoxBody; - - #[inline] - fn respond_to(self, _: &HttpRequest) -> HttpResponse { - self - } -} - impl Responder for actix_http::Response { type Body = BoxBody; @@ -75,15 +66,6 @@ impl Responder for actix_http::Response { } } -impl Responder for HttpResponseBuilder { - type Body = BoxBody; - - #[inline] - fn respond_to(mut self, _: &HttpRequest) -> HttpResponse { - self.finish() - } -} - impl Responder for actix_http::ResponseBuilder { type Body = BoxBody; @@ -96,7 +78,6 @@ impl Responder for actix_http::ResponseBuilder { impl Responder for Option where T: Responder, - ::Error: Into, { type Body = EitherBody; @@ -111,7 +92,6 @@ where impl Responder for Result where T: Responder, - ::Error: Into, E: Into, { type Body = EitherBody; diff --git a/src/response/response.rs b/src/response/response.rs index 6fa2082e7..f24a75b19 100644 --- a/src/response/response.rs +++ b/src/response/response.rs @@ -22,7 +22,7 @@ use { cookie::Cookie, }; -use crate::{error::Error, HttpResponseBuilder}; +use crate::{error::Error, HttpRequest, HttpResponseBuilder, Responder}; /// An outgoing response. pub struct HttpResponse { @@ -311,6 +311,18 @@ impl Future for HttpResponse { } } +impl Responder for HttpResponse +where + B: MessageBody + 'static, +{ + type Body = B; + + #[inline] + fn respond_to(self, _: &HttpRequest) -> HttpResponse { + self + } +} + #[cfg(feature = "cookies")] pub struct CookieIter<'a> { iter: std::slice::Iter<'a, HeaderValue>, @@ -333,9 +345,16 @@ impl<'a> Iterator for CookieIter<'a> { #[cfg(test)] mod tests { + use static_assertions::assert_impl_all; + use super::*; use crate::http::header::{HeaderValue, COOKIE}; + assert_impl_all!(HttpResponse: Responder); + assert_impl_all!(HttpResponse: Responder); + assert_impl_all!(HttpResponse<&'static str>: Responder); + assert_impl_all!(HttpResponse: Responder); + #[test] fn test_debug() { let resp = HttpResponse::Ok() From fe0bbfb3da03a29ee7b715334531f4b770496e2d Mon Sep 17 00:00:00 2001 From: Ali MJ Al-Nasrawy Date: Wed, 5 Jan 2022 13:48:20 +0300 Subject: [PATCH 427/428] optimize PathDeserializer (#2570) --- actix-router/src/de.rs | 122 +++++++++++++++++++++++++++-------------- 1 file changed, 82 insertions(+), 40 deletions(-) diff --git a/actix-router/src/de.rs b/actix-router/src/de.rs index ec7b1066a..27aa49ef2 100644 --- a/actix-router/src/de.rs +++ b/actix-router/src/de.rs @@ -1,3 +1,5 @@ +use std::borrow::Cow; + use serde::de::{self, Deserializer, Error as DeError, Visitor}; use serde::forward_to_deserialize_any; @@ -20,7 +22,7 @@ macro_rules! unsupported_type { } macro_rules! parse_single_value { - ($trait_fn:ident, $visit_fn:ident, $tp:expr) => { + ($trait_fn:ident) => { fn $trait_fn(self, visitor: V) -> Result where V: Visitor<'de>, @@ -34,15 +36,10 @@ macro_rules! parse_single_value { .as_str(), )) } else { - let decoded = FULL_QUOTER - .with(|q| q.requote(self.path[0].as_bytes())) - .unwrap_or_else(|| self.path[0].to_owned()); - - let v = decoded.parse().map_err(|_| { - de::Error::custom(format!("can not parse {:?} to a {}", &self.path[0], $tp)) - })?; - - visitor.$visit_fn(v) + Value { + value: &self.path[0], + } + .$trait_fn(visitor) } } }; @@ -56,7 +53,8 @@ macro_rules! parse_value { { let decoded = FULL_QUOTER .with(|q| q.requote(self.value.as_bytes())) - .unwrap_or_else(|| self.value.to_owned()); + .map(Cow::Owned) + .unwrap_or(Cow::Borrowed(self.value)); let v = decoded.parse().map_err(|_| { de::value::Error::custom(format!("can not parse {:?} to a {}", self.value, $tp)) @@ -204,26 +202,26 @@ impl<'de, T: ResourcePath + 'de> Deserializer<'de> for PathDeserializer<'de, T> } unsupported_type!(deserialize_any, "'any'"); - unsupported_type!(deserialize_bytes, "bytes"); unsupported_type!(deserialize_option, "Option"); unsupported_type!(deserialize_identifier, "identifier"); unsupported_type!(deserialize_ignored_any, "ignored_any"); - parse_single_value!(deserialize_bool, visit_bool, "bool"); - parse_single_value!(deserialize_i8, visit_i8, "i8"); - parse_single_value!(deserialize_i16, visit_i16, "i16"); - parse_single_value!(deserialize_i32, visit_i32, "i32"); - parse_single_value!(deserialize_i64, visit_i64, "i64"); - parse_single_value!(deserialize_u8, visit_u8, "u8"); - parse_single_value!(deserialize_u16, visit_u16, "u16"); - parse_single_value!(deserialize_u32, visit_u32, "u32"); - parse_single_value!(deserialize_u64, visit_u64, "u64"); - parse_single_value!(deserialize_f32, visit_f32, "f32"); - parse_single_value!(deserialize_f64, visit_f64, "f64"); - parse_single_value!(deserialize_str, visit_string, "String"); - parse_single_value!(deserialize_string, visit_string, "String"); - parse_single_value!(deserialize_byte_buf, visit_string, "String"); - parse_single_value!(deserialize_char, visit_char, "char"); + parse_single_value!(deserialize_bool); + parse_single_value!(deserialize_i8); + parse_single_value!(deserialize_i16); + parse_single_value!(deserialize_i32); + parse_single_value!(deserialize_i64); + parse_single_value!(deserialize_u8); + parse_single_value!(deserialize_u16); + parse_single_value!(deserialize_u32); + parse_single_value!(deserialize_u64); + parse_single_value!(deserialize_f32); + parse_single_value!(deserialize_f64); + parse_single_value!(deserialize_str); + parse_single_value!(deserialize_string); + parse_single_value!(deserialize_bytes); + parse_single_value!(deserialize_byte_buf); + parse_single_value!(deserialize_char); } struct ParamsDeserializer<'de, T: ResourcePath> { @@ -303,8 +301,6 @@ impl<'de> Deserializer<'de> for Value<'de> { parse_value!(deserialize_u64, visit_u64, "u64"); parse_value!(deserialize_f32, visit_f32, "f32"); parse_value!(deserialize_f64, visit_f64, "f64"); - parse_value!(deserialize_string, visit_string, "String"); - parse_value!(deserialize_byte_buf, visit_string, "String"); parse_value!(deserialize_char, visit_char, "char"); fn deserialize_ignored_any(self, visitor: V) -> Result @@ -332,18 +328,38 @@ impl<'de> Deserializer<'de> for Value<'de> { visitor.visit_unit() } - fn deserialize_bytes(self, visitor: V) -> Result - where - V: Visitor<'de>, - { - visitor.visit_borrowed_bytes(self.value.as_bytes()) - } - fn deserialize_str(self, visitor: V) -> Result where V: Visitor<'de>, { - visitor.visit_borrowed_str(self.value) + match FULL_QUOTER.with(|q| q.requote(self.value.as_bytes())) { + Some(s) => visitor.visit_string(s), + None => visitor.visit_borrowed_str(self.value), + } + } + + fn deserialize_bytes(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + match FULL_QUOTER.with(|q| q.requote(self.value.as_bytes())) { + Some(s) => visitor.visit_byte_buf(s.into()), + None => visitor.visit_borrowed_bytes(self.value.as_bytes()), + } + } + + fn deserialize_byte_buf(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + self.deserialize_bytes(visitor) + } + + fn deserialize_string(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + self.deserialize_str(visitor) } fn deserialize_option(self, visitor: V) -> Result @@ -671,12 +687,12 @@ mod tests { fn deserialize_path_decode_seq() { let rdef = ResourceDef::new("/{key}/{value}"); - let mut path = Path::new("/%25/%2F"); + let mut path = Path::new("/%30%25/%30%2F"); rdef.capture_match_info(&mut path); let de = PathDeserializer::new(&path); let segment: (String, String) = serde::Deserialize::deserialize(de).unwrap(); - assert_eq!(segment.0, "%"); - assert_eq!(segment.1, "/"); + assert_eq!(segment.0, "0%"); + assert_eq!(segment.1, "0/"); } #[test] @@ -697,6 +713,32 @@ mod tests { assert_eq!(vals.value, "/"); } + #[test] + fn deserialize_borrowed() { + #[derive(Debug, Deserialize)] + struct Params<'a> { + val: &'a str, + } + + let rdef = ResourceDef::new("/{val}"); + + let mut path = Path::new("/X"); + rdef.capture_match_info(&mut path); + let de = PathDeserializer::new(&path); + let params: Params<'_> = serde::Deserialize::deserialize(de).unwrap(); + assert_eq!(params.val, "X"); + let de = PathDeserializer::new(&path); + let params: &str = serde::Deserialize::deserialize(de).unwrap(); + assert_eq!(params, "X"); + + let mut path = Path::new("/%2F"); + rdef.capture_match_info(&mut path); + let de = PathDeserializer::new(&path); + assert!( as serde::Deserialize>::deserialize(de).is_err()); + let de = PathDeserializer::new(&path); + assert!(<&str as serde::Deserialize>::deserialize(de).is_err()); + } + // #[test] // fn test_extract_path_decode() { // let mut router = Router::<()>::default(); From 4ebf16890d2e3e45dd9f8352d5ddd712eafa6c47 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Wed, 5 Jan 2022 11:47:14 +0000 Subject: [PATCH 428/428] add `GuardContext::header` (#2569) --- CHANGES.md | 10 +++++++--- src/guard.rs | 22 +++++++++++++++++++++- src/http/header/accept.rs | 4 ++-- 3 files changed, 30 insertions(+), 6 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 922e0c7c8..ec09de2ac 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,10 +1,14 @@ # Changes ## Unreleased - 2021-xx-xx -### Changed -- `HttpResponse` can now be used as a `Responder` with any body type. +### Added +- `GuardContext::header` [#2569] -[#2501]: https://github.com/actix/actix-web/pull/2501 +### Changed +- `HttpResponse` can now be used as a `Responder` with any body type. [#2567] + +[#2567]: https://github.com/actix/actix-web/pull/2567 +[#2569]: https://github.com/actix/actix-web/pull/2569 ## 4.0.0-beta.19 - 2022-01-04 diff --git a/src/guard.rs b/src/guard.rs index 7a015d2da..f4200a382 100644 --- a/src/guard.rs +++ b/src/guard.rs @@ -54,7 +54,7 @@ use std::{ use actix_http::{header, uri::Uri, Extensions, Method as HttpMethod, RequestHead}; -use crate::service::ServiceRequest; +use crate::{http::header::Header, service::ServiceRequest}; /// Provides access to request parts that are useful during routing. #[derive(Debug)] @@ -80,6 +80,26 @@ impl<'a> GuardContext<'a> { pub fn req_data_mut(&self) -> RefMut<'a, Extensions> { self.req.req_data_mut() } + + /// Extracts a typed header from the request. + /// + /// Returns `None` if parsing `H` fails. + /// + /// # Examples + /// ``` + /// use actix_web::{guard::fn_guard, http::header}; + /// + /// let image_accept_guard = fn_guard(|ctx| { + /// match ctx.header::() { + /// Some(hdr) => hdr.preference() == "image/*", + /// None => false, + /// } + /// }); + /// ``` + #[inline] + pub fn header(&self) -> Option { + H::parse(self.req).ok() + } } /// Interface for routing guards. diff --git a/src/http/header/accept.rs b/src/http/header/accept.rs index 368a05bb2..744c9b6e8 100644 --- a/src/http/header/accept.rs +++ b/src/http/header/accept.rs @@ -2,10 +2,10 @@ use std::cmp::Ordering; use mime::Mime; -use super::QualityItem; +use super::{common_header, QualityItem}; use crate::http::header; -crate::http::header::common_header! { +common_header! { /// `Accept` header, defined /// in [RFC 7231 ยง5.3.2](https://datatracker.ietf.org/doc/html/rfc7231#section-5.3.2) ///

[![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 053/428] 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 054/428] 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 055/428] 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 056/428] 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 057/428] 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 058/428] 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 059/428] 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 060/428] 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 061/428] 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 062/428] 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 063/428] 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 064/428] 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 065/428] 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 066/428] 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 067/428] 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 068/428] 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 069/428] 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 070/428] 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 071/428] 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 072/428] 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 073/428] 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 074/428] 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 075/428] 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 076/428] 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 077/428] 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 078/428] 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 079/428] 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 080/428] 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 081/428] 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 082/428] 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 083/428] 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 084/428] 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 085/428] 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 086/428] 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 087/428] 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 088/428] 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 089/428] 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 090/428] 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 091/428] 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 092/428] 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 093/428] 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 094/428] 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 095/428] 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 096/428] 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 @@