From 1bd2076b35f9c9bcc6ef9fe96f157c4de0cbd7cc Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Tue, 25 Jan 2022 16:44:05 +0000 Subject: [PATCH 001/170] prevent drive traversal in windows --- actix-files/src/path_buf.rs | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/actix-files/src/path_buf.rs b/actix-files/src/path_buf.rs index f7f7cdab6..9ee1338c6 100644 --- a/actix-files/src/path_buf.rs +++ b/actix-files/src/path_buf.rs @@ -59,6 +59,8 @@ impl PathBufWrap { continue; } else if cfg!(windows) && segment.contains('\\') { return Err(UriSegmentError::BadChar('\\')); + } else if cfg!(windows) && segment.contains(':') { + return Err(UriSegmentError::BadChar(':')); } else { buf.push(segment) } @@ -66,7 +68,11 @@ impl PathBufWrap { // make sure we agree with stdlib parser for (i, component) in buf.components().enumerate() { - assert!(matches!(component, Component::Normal(_))); + assert!( + matches!(component, Component::Normal(_)), + "component `{:?}` is not normal", + component + ); assert!(i < segment_count); } @@ -159,4 +165,26 @@ mod tests { PathBuf::from_iter(vec!["etc/passwd"]) ); } + + #[test] + #[cfg_attr(windows, should_panic)] + fn windows_drive_traversal() { + // detect issues in windows that could lead to path traversal + // see Date: Thu, 27 Jan 2022 06:06:55 +0000 Subject: [PATCH 002/170] move dispatcher tests to own file --- actix-http/src/h1/dispatcher.rs | 406 +---------------------- actix-http/src/h1/dispatcher_tests.rs | 460 ++++++++++++++++++++++++++ actix-http/src/h1/mod.rs | 2 + actix-http/src/test.rs | 55 ++- 4 files changed, 511 insertions(+), 412 deletions(-) create mode 100644 actix-http/src/h1/dispatcher_tests.rs diff --git a/actix-http/src/h1/dispatcher.rs b/actix-http/src/h1/dispatcher.rs index 13055f08a..5b790469f 100644 --- a/actix-http/src/h1/dispatcher.rs +++ b/actix-http/src/h1/dispatcher.rs @@ -89,16 +89,16 @@ pin_project! { U::Error: fmt::Display, { #[pin] - inner: DispatcherState, + pub(super) inner: DispatcherState, // used in tests - poll_count: u64, + pub(super) poll_count: u64, } } pin_project! { #[project = DispatcherStateProj] - enum DispatcherState + pub(super) enum DispatcherState where S: Service, S::Error: Into>, @@ -118,7 +118,7 @@ pin_project! { pin_project! { #[project = InnerDispatcherProj] - struct InnerDispatcher + pub(super) struct InnerDispatcher where S: Service, S::Error: Into>, @@ -132,7 +132,7 @@ pin_project! { U::Error: fmt::Display, { flow: Rc>, - flags: Flags, + pub(super) flags: Flags, peer_addr: Option, conn_data: Option>, error: Option, @@ -146,7 +146,7 @@ pin_project! { #[pin] ka_timer: Option, - io: Option, + pub(super) io: Option, read_buf: BytesMut, write_buf: BytesMut, codec: Codec, @@ -1039,397 +1039,3 @@ where } } } - -#[cfg(test)] -mod tests { - use std::str; - - use actix_service::fn_service; - use actix_utils::future::{ready, Ready}; - use bytes::Bytes; - use futures_util::future::lazy; - - use super::*; - use crate::{ - error::Error, - h1::{ExpectHandler, UpgradeHandler}, - test::{TestBuffer, TestSeqBuffer}, - HttpMessage, KeepAlive, Method, - }; - - fn find_slice(haystack: &[u8], needle: &[u8], from: usize) -> Option { - haystack[from..] - .windows(needle.len()) - .position(|window| window == needle) - } - - fn stabilize_date_header(payload: &mut [u8]) { - let mut from = 0; - - while let Some(pos) = find_slice(payload, b"date", from) { - payload[(from + pos)..(from + pos + 35)] - .copy_from_slice(b"date: Thu, 01 Jan 1970 12:34:56 UTC"); - from += 35; - } - } - - fn ok_service( - ) -> impl Service, Error = Error> { - fn_service(|_req: Request| ready(Ok::<_, Error>(Response::ok()))) - } - - fn echo_path_service( - ) -> impl Service, Error = Error> { - fn_service(|req: Request| { - let path = req.path().as_bytes(); - ready(Ok::<_, Error>( - Response::ok().set_body(Bytes::copy_from_slice(path)), - )) - }) - } - - fn echo_payload_service() -> impl Service, Error = Error> - { - fn_service(|mut req: Request| { - Box::pin(async move { - use futures_util::stream::StreamExt as _; - - let mut pl = req.take_payload(); - let mut body = BytesMut::new(); - while let Some(chunk) = pl.next().await { - body.extend_from_slice(chunk.unwrap().chunk()) - } - - Ok::<_, Error>(Response::ok().set_body(body.freeze())) - }) - }) - } - - #[actix_rt::test] - async fn test_req_parse_err() { - lazy(|cx| { - let buf = TestBuffer::new("GET /test HTTP/1\r\n\r\n"); - - let services = HttpFlow::new(ok_service(), ExpectHandler, None); - - let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new( - buf, - services, - ServiceConfig::default(), - None, - OnConnectData::default(), - ); - - actix_rt::pin!(h1); - - match h1.as_mut().poll(cx) { - Poll::Pending => panic!(), - Poll::Ready(res) => assert!(res.is_err()), - } - - if let DispatcherStateProj::Normal { inner } = h1.project().inner.project() { - assert!(inner.flags.contains(Flags::READ_DISCONNECT)); - assert_eq!( - &inner.project().io.take().unwrap().write_buf[..26], - b"HTTP/1.1 400 Bad Request\r\n" - ); - } - }) - .await; - } - - #[actix_rt::test] - async fn test_pipelining() { - lazy(|cx| { - let buf = TestBuffer::new( - "\ - GET /abcd HTTP/1.1\r\n\r\n\ - GET /def HTTP/1.1\r\n\r\n\ - ", - ); - - let cfg = ServiceConfig::new(KeepAlive::Disabled, 1, 1, false, None); - - let services = HttpFlow::new(echo_path_service(), ExpectHandler, None); - - let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new( - buf, - services, - cfg, - None, - OnConnectData::default(), - ); - - actix_rt::pin!(h1); - - assert!(matches!(&h1.inner, DispatcherState::Normal { .. })); - - match h1.as_mut().poll(cx) { - Poll::Pending => panic!("first poll should not be pending"), - Poll::Ready(res) => assert!(res.is_ok()), - } - - // polls: initial => shutdown - assert_eq!(h1.poll_count, 2); - - if let DispatcherStateProj::Normal { inner } = h1.project().inner.project() { - let res = &mut inner.project().io.take().unwrap().write_buf[..]; - stabilize_date_header(res); - - let exp = b"\ - HTTP/1.1 200 OK\r\n\ - content-length: 5\r\n\ - connection: close\r\n\ - date: Thu, 01 Jan 1970 12:34:56 UTC\r\n\r\n\ - /abcd\ - HTTP/1.1 200 OK\r\n\ - content-length: 4\r\n\ - connection: close\r\n\ - date: Thu, 01 Jan 1970 12:34:56 UTC\r\n\r\n\ - /def\ - "; - - assert_eq!(res.to_vec(), exp.to_vec()); - } - }) - .await; - - lazy(|cx| { - let buf = TestBuffer::new( - "\ - GET /abcd HTTP/1.1\r\n\r\n\ - GET /def HTTP/1\r\n\r\n\ - ", - ); - - let cfg = ServiceConfig::new(KeepAlive::Disabled, 1, 1, false, None); - - let services = HttpFlow::new(echo_path_service(), ExpectHandler, None); - - let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new( - buf, - services, - cfg, - None, - OnConnectData::default(), - ); - - actix_rt::pin!(h1); - - assert!(matches!(&h1.inner, DispatcherState::Normal { .. })); - - match h1.as_mut().poll(cx) { - Poll::Pending => panic!("first poll should not be pending"), - Poll::Ready(res) => assert!(res.is_err()), - } - - // polls: initial => shutdown - assert_eq!(h1.poll_count, 1); - - if let DispatcherStateProj::Normal { inner } = h1.project().inner.project() { - let res = &mut inner.project().io.take().unwrap().write_buf[..]; - stabilize_date_header(res); - - let exp = b"\ - HTTP/1.1 200 OK\r\n\ - content-length: 5\r\n\ - connection: close\r\n\ - date: Thu, 01 Jan 1970 12:34:56 UTC\r\n\r\n\ - /abcd\ - HTTP/1.1 400 Bad Request\r\n\ - content-length: 0\r\n\ - connection: close\r\n\ - date: Thu, 01 Jan 1970 12:34:56 UTC\r\n\r\n\ - "; - - assert_eq!(res.to_vec(), exp.to_vec()); - } - }) - .await; - } - - #[actix_rt::test] - async fn test_expect() { - lazy(|cx| { - let mut buf = TestSeqBuffer::empty(); - let cfg = ServiceConfig::new(KeepAlive::Disabled, 0, 0, false, None); - - let services = HttpFlow::new(echo_payload_service(), ExpectHandler, None); - - let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new( - buf.clone(), - services, - cfg, - None, - OnConnectData::default(), - ); - - buf.extend_read_buf( - "\ - POST /upload HTTP/1.1\r\n\ - Content-Length: 5\r\n\ - Expect: 100-continue\r\n\ - \r\n\ - ", - ); - - actix_rt::pin!(h1); - - assert!(h1.as_mut().poll(cx).is_pending()); - assert!(matches!(&h1.inner, DispatcherState::Normal { .. })); - - // polls: manual - assert_eq!(h1.poll_count, 1); - eprintln!("poll count: {}", h1.poll_count); - - if let DispatcherState::Normal { ref inner } = h1.inner { - let io = inner.io.as_ref().unwrap(); - let res = &io.write_buf()[..]; - assert_eq!( - str::from_utf8(res).unwrap(), - "HTTP/1.1 100 Continue\r\n\r\n" - ); - } - - buf.extend_read_buf("12345"); - assert!(h1.as_mut().poll(cx).is_ready()); - - // polls: manual manual shutdown - assert_eq!(h1.poll_count, 3); - - if let DispatcherState::Normal { ref inner } = h1.inner { - let io = inner.io.as_ref().unwrap(); - let mut res = (&io.write_buf()[..]).to_owned(); - stabilize_date_header(&mut res); - - assert_eq!( - str::from_utf8(&res).unwrap(), - "\ - HTTP/1.1 100 Continue\r\n\ - \r\n\ - HTTP/1.1 200 OK\r\n\ - content-length: 5\r\n\ - connection: close\r\n\ - date: Thu, 01 Jan 1970 12:34:56 UTC\r\n\ - \r\n\ - 12345\ - " - ); - } - }) - .await; - } - - #[actix_rt::test] - async fn test_eager_expect() { - lazy(|cx| { - let mut buf = TestSeqBuffer::empty(); - let cfg = ServiceConfig::new(KeepAlive::Disabled, 0, 0, false, None); - - let services = HttpFlow::new(echo_path_service(), ExpectHandler, None); - - let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new( - buf.clone(), - services, - cfg, - None, - OnConnectData::default(), - ); - - buf.extend_read_buf( - "\ - POST /upload HTTP/1.1\r\n\ - Content-Length: 5\r\n\ - Expect: 100-continue\r\n\ - \r\n\ - ", - ); - - actix_rt::pin!(h1); - - assert!(h1.as_mut().poll(cx).is_ready()); - assert!(matches!(&h1.inner, DispatcherState::Normal { .. })); - - // polls: manual shutdown - assert_eq!(h1.poll_count, 2); - - if let DispatcherState::Normal { ref inner } = h1.inner { - let io = inner.io.as_ref().unwrap(); - let mut res = (&io.write_buf()[..]).to_owned(); - stabilize_date_header(&mut res); - - // Despite the content-length header and even though the request payload has not - // been sent, this test expects a complete service response since the payload - // is not used at all. The service passed to dispatcher is path echo and doesn't - // consume payload bytes. - assert_eq!( - str::from_utf8(&res).unwrap(), - "\ - HTTP/1.1 100 Continue\r\n\ - \r\n\ - HTTP/1.1 200 OK\r\n\ - content-length: 7\r\n\ - connection: close\r\n\ - date: Thu, 01 Jan 1970 12:34:56 UTC\r\n\ - \r\n\ - /upload\ - " - ); - } - }) - .await; - } - - #[actix_rt::test] - async fn test_upgrade() { - struct TestUpgrade; - - impl Service<(Request, Framed)> for TestUpgrade { - type Response = (); - type Error = Error; - type Future = Ready>; - - actix_service::always_ready!(); - - fn call(&self, (req, _framed): (Request, Framed)) -> Self::Future { - assert_eq!(req.method(), Method::GET); - assert!(req.upgrade()); - assert_eq!(req.headers().get("upgrade").unwrap(), "websocket"); - ready(Ok(())) - } - } - - lazy(|cx| { - let mut buf = TestSeqBuffer::empty(); - let cfg = ServiceConfig::new(KeepAlive::Disabled, 0, 0, false, None); - - let services = HttpFlow::new(ok_service(), ExpectHandler, Some(TestUpgrade)); - - let h1 = Dispatcher::<_, _, _, _, TestUpgrade>::new( - buf.clone(), - services, - cfg, - None, - OnConnectData::default(), - ); - - buf.extend_read_buf( - "\ - GET /ws HTTP/1.1\r\n\ - Connection: Upgrade\r\n\ - Upgrade: websocket\r\n\ - \r\n\ - ", - ); - - actix_rt::pin!(h1); - - assert!(h1.as_mut().poll(cx).is_ready()); - assert!(matches!(&h1.inner, DispatcherState::Upgrade { .. })); - - // polls: manual shutdown - assert_eq!(h1.poll_count, 2); - }) - .await; - } -} diff --git a/actix-http/src/h1/dispatcher_tests.rs b/actix-http/src/h1/dispatcher_tests.rs new file mode 100644 index 000000000..21da2422f --- /dev/null +++ b/actix-http/src/h1/dispatcher_tests.rs @@ -0,0 +1,460 @@ +use std::{future::Future, str, task::Poll}; + +use actix_service::fn_service; +use actix_utils::future::{ready, Ready}; +use bytes::Bytes; +use futures_util::future::lazy; + +use actix_codec::Framed; +use actix_service::Service; +use bytes::{Buf, BytesMut}; + +use super::dispatcher::{Dispatcher, DispatcherState, DispatcherStateProj, Flags}; +use crate::{ + body::MessageBody, + config::ServiceConfig, + h1::{Codec, ExpectHandler, UpgradeHandler}, + service::HttpFlow, + test::{TestBuffer, TestSeqBuffer}, + Error, HttpMessage, KeepAlive, Method, OnConnectData, Request, Response, +}; + +fn find_slice(haystack: &[u8], needle: &[u8], from: usize) -> Option { + memchr::memmem::find(&haystack[from..], needle) +} + +fn stabilize_date_header(payload: &mut [u8]) { + let mut from = 0; + while let Some(pos) = find_slice(payload, b"date", from) { + payload[(from + pos)..(from + pos + 35)] + .copy_from_slice(b"date: Thu, 01 Jan 1970 12:34:56 UTC"); + from += 35; + } +} + +fn ok_service() -> impl Service, Error = Error> { + fn_service(|_req: Request| ready(Ok::<_, Error>(Response::ok()))) +} + +fn echo_path_service( +) -> impl Service, Error = Error> { + fn_service(|req: Request| { + let path = req.path().as_bytes(); + ready(Ok::<_, Error>( + Response::ok().set_body(Bytes::copy_from_slice(path)), + )) + }) +} + +fn echo_payload_service() -> impl Service, Error = Error> { + fn_service(|mut req: Request| { + Box::pin(async move { + use futures_util::stream::StreamExt as _; + + let mut pl = req.take_payload(); + let mut body = BytesMut::new(); + while let Some(chunk) = pl.next().await { + body.extend_from_slice(chunk.unwrap().chunk()) + } + + Ok::<_, Error>(Response::ok().set_body(body.freeze())) + }) + }) +} + +#[actix_rt::test] +#[ignore] +async fn test_keep_alive() { + lazy(|cx| { + let buf = TestBuffer::new("GET /abcd HTTP/1.1\r\n\r\n"); + + let cfg = ServiceConfig::new(KeepAlive::Timeout(1), 100, 0, false, None); + let services = HttpFlow::new(echo_path_service(), ExpectHandler, None); + + let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new( + buf.clone(), + services, + cfg, + None, + OnConnectData::default(), + ); + actix_rt::pin!(h1); + + assert!(matches!(&h1.inner, DispatcherState::Normal { .. })); + + assert!( + h1.as_mut().poll(cx).is_pending(), + "keep-alive should prevent poll from resolving" + ); + + // polls: initial + assert_eq!(h1.poll_count, 1); + + let mut res = buf.write_buf_slice_mut(); + stabilize_date_header(&mut res); + let res = &res[..]; + + let exp = b"\ + HTTP/1.1 200 OK\r\n\ + content-length: 5\r\n\ + date: Thu, 01 Jan 1970 12:34:56 UTC\r\n\r\n\ + /abcd\ + "; + + assert_eq!( + res, + exp, + "\nexpected response not in write buffer:\n\ + response: {:?}\n\ + expected: {:?}", + String::from_utf8_lossy(res), + String::from_utf8_lossy(exp) + ); + }) + .await; +} + +#[actix_rt::test] +async fn test_req_parse_err() { + lazy(|cx| { + let buf = TestBuffer::new("GET /test HTTP/1\r\n\r\n"); + + let services = HttpFlow::new(ok_service(), ExpectHandler, None); + + let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new( + buf.clone(), + services, + ServiceConfig::default(), + None, + OnConnectData::default(), + ); + + actix_rt::pin!(h1); + + match h1.as_mut().poll(cx) { + Poll::Pending => panic!(), + Poll::Ready(res) => assert!(res.is_err()), + } + + if let DispatcherStateProj::Normal { inner } = h1.project().inner.project() { + assert!(inner.flags.contains(Flags::READ_DISCONNECT)); + assert_eq!( + &buf.write_buf_slice()[..26], + b"HTTP/1.1 400 Bad Request\r\n" + ); + } + }) + .await; +} + +#[actix_rt::test] +async fn pipelining_ok_then_ok() { + lazy(|cx| { + let buf = TestBuffer::new( + "\ + GET /abcd HTTP/1.1\r\n\r\n\ + GET /def HTTP/1.1\r\n\r\n\ + ", + ); + + let cfg = ServiceConfig::new(KeepAlive::Disabled, 1, 1, false, None); + + let services = HttpFlow::new(echo_path_service(), ExpectHandler, None); + + let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new( + buf.clone(), + services, + cfg, + None, + OnConnectData::default(), + ); + + actix_rt::pin!(h1); + + assert!(matches!(&h1.inner, DispatcherState::Normal { .. })); + + match h1.as_mut().poll(cx) { + Poll::Pending => panic!("first poll should not be pending"), + Poll::Ready(res) => assert!(res.is_ok()), + } + + // polls: initial => shutdown + assert_eq!(h1.poll_count, 2); + + let mut res = buf.write_buf_slice_mut(); + stabilize_date_header(&mut res); + let res = &res[..]; + + let exp = b"\ + HTTP/1.1 200 OK\r\n\ + content-length: 5\r\n\ + connection: close\r\n\ + date: Thu, 01 Jan 1970 12:34:56 UTC\r\n\r\n\ + /abcd\ + HTTP/1.1 200 OK\r\n\ + content-length: 4\r\n\ + connection: close\r\n\ + date: Thu, 01 Jan 1970 12:34:56 UTC\r\n\r\n\ + /def\ + "; + + assert_eq!( + res, + exp, + "\nexpected response not in write buffer:\n\ + response: {:?}\n\ + expected: {:?}", + String::from_utf8_lossy(res), + String::from_utf8_lossy(exp) + ); + }) + .await; +} + +#[actix_rt::test] +async fn pipelining_ok_then_bad() { + lazy(|cx| { + let buf = TestBuffer::new( + "\ + GET /abcd HTTP/1.1\r\n\r\n\ + GET /def HTTP/1\r\n\r\n\ + ", + ); + + let cfg = ServiceConfig::new(KeepAlive::Disabled, 1, 1, false, None); + + let services = HttpFlow::new(echo_path_service(), ExpectHandler, None); + + let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new( + buf.clone(), + services, + cfg, + None, + OnConnectData::default(), + ); + + actix_rt::pin!(h1); + + assert!(matches!(&h1.inner, DispatcherState::Normal { .. })); + + match h1.as_mut().poll(cx) { + Poll::Pending => panic!("first poll should not be pending"), + Poll::Ready(res) => assert!(res.is_err()), + } + + // polls: initial => shutdown + assert_eq!(h1.poll_count, 1); + + let mut res = buf.write_buf_slice_mut(); + stabilize_date_header(&mut res); + let res = &res[..]; + + let exp = b"\ + HTTP/1.1 200 OK\r\n\ + content-length: 5\r\n\ + connection: close\r\n\ + date: Thu, 01 Jan 1970 12:34:56 UTC\r\n\r\n\ + /abcd\ + HTTP/1.1 400 Bad Request\r\n\ + content-length: 0\r\n\ + connection: close\r\n\ + date: Thu, 01 Jan 1970 12:34:56 UTC\r\n\r\n\ + "; + + assert_eq!( + res, + exp, + "\nexpected response not in write buffer:\n\ + response: {:?}\n\ + expected: {:?}", + String::from_utf8_lossy(res), + String::from_utf8_lossy(exp) + ); + }) + .await; +} + +#[actix_rt::test] +async fn test_expect() { + lazy(|cx| { + let mut buf = TestSeqBuffer::empty(); + let cfg = ServiceConfig::new(KeepAlive::Disabled, 0, 0, false, None); + + let services = HttpFlow::new(echo_payload_service(), ExpectHandler, None); + + let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new( + buf.clone(), + services, + cfg, + None, + OnConnectData::default(), + ); + + buf.extend_read_buf( + "\ + POST /upload HTTP/1.1\r\n\ + Content-Length: 5\r\n\ + Expect: 100-continue\r\n\ + \r\n\ + ", + ); + + actix_rt::pin!(h1); + + assert!(h1.as_mut().poll(cx).is_pending()); + assert!(matches!(&h1.inner, DispatcherState::Normal { .. })); + + // polls: manual + assert_eq!(h1.poll_count, 1); + eprintln!("poll count: {}", h1.poll_count); + + if let DispatcherState::Normal { ref inner } = h1.inner { + let io = inner.io.as_ref().unwrap(); + let res = &io.write_buf()[..]; + assert_eq!( + str::from_utf8(res).unwrap(), + "HTTP/1.1 100 Continue\r\n\r\n" + ); + } + + buf.extend_read_buf("12345"); + assert!(h1.as_mut().poll(cx).is_ready()); + + // polls: manual manual shutdown + assert_eq!(h1.poll_count, 3); + + if let DispatcherState::Normal { ref inner } = h1.inner { + let io = inner.io.as_ref().unwrap(); + let mut res = (&io.write_buf()[..]).to_owned(); + stabilize_date_header(&mut res); + + assert_eq!( + str::from_utf8(&res).unwrap(), + "\ + HTTP/1.1 100 Continue\r\n\ + \r\n\ + HTTP/1.1 200 OK\r\n\ + content-length: 5\r\n\ + connection: close\r\n\ + date: Thu, 01 Jan 1970 12:34:56 UTC\r\n\ + \r\n\ + 12345\ + " + ); + } + }) + .await; +} + +#[actix_rt::test] +async fn test_eager_expect() { + lazy(|cx| { + let mut buf = TestSeqBuffer::empty(); + let cfg = ServiceConfig::new(KeepAlive::Disabled, 0, 0, false, None); + + let services = HttpFlow::new(echo_path_service(), ExpectHandler, None); + + let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new( + buf.clone(), + services, + cfg, + None, + OnConnectData::default(), + ); + + buf.extend_read_buf( + "\ + POST /upload HTTP/1.1\r\n\ + Content-Length: 5\r\n\ + Expect: 100-continue\r\n\ + \r\n\ + ", + ); + + actix_rt::pin!(h1); + + assert!(h1.as_mut().poll(cx).is_ready()); + assert!(matches!(&h1.inner, DispatcherState::Normal { .. })); + + // polls: manual shutdown + assert_eq!(h1.poll_count, 2); + + if let DispatcherState::Normal { ref inner } = h1.inner { + let io = inner.io.as_ref().unwrap(); + let mut res = (&io.write_buf()[..]).to_owned(); + stabilize_date_header(&mut res); + + // Despite the content-length header and even though the request payload has not + // been sent, this test expects a complete service response since the payload + // is not used at all. The service passed to dispatcher is path echo and doesn't + // consume payload bytes. + assert_eq!( + str::from_utf8(&res).unwrap(), + "\ + HTTP/1.1 100 Continue\r\n\ + \r\n\ + HTTP/1.1 200 OK\r\n\ + content-length: 7\r\n\ + connection: close\r\n\ + date: Thu, 01 Jan 1970 12:34:56 UTC\r\n\ + \r\n\ + /upload\ + " + ); + } + }) + .await; +} + +#[actix_rt::test] +async fn test_upgrade() { + struct TestUpgrade; + + impl Service<(Request, Framed)> for TestUpgrade { + type Response = (); + type Error = Error; + type Future = Ready>; + + actix_service::always_ready!(); + + fn call(&self, (req, _framed): (Request, Framed)) -> Self::Future { + assert_eq!(req.method(), Method::GET); + assert!(req.upgrade()); + assert_eq!(req.headers().get("upgrade").unwrap(), "websocket"); + ready(Ok(())) + } + } + + lazy(|cx| { + let mut buf = TestSeqBuffer::empty(); + let cfg = ServiceConfig::new(KeepAlive::Disabled, 0, 0, false, None); + + let services = HttpFlow::new(ok_service(), ExpectHandler, Some(TestUpgrade)); + + let h1 = Dispatcher::<_, _, _, _, TestUpgrade>::new( + buf.clone(), + services, + cfg, + None, + OnConnectData::default(), + ); + + buf.extend_read_buf( + "\ + GET /ws HTTP/1.1\r\n\ + Connection: Upgrade\r\n\ + Upgrade: websocket\r\n\ + \r\n\ + ", + ); + + actix_rt::pin!(h1); + + assert!(h1.as_mut().poll(cx).is_ready()); + assert!(matches!(&h1.inner, DispatcherState::Upgrade { .. })); + + // polls: manual shutdown + assert_eq!(h1.poll_count, 2); + }) + .await; +} diff --git a/actix-http/src/h1/mod.rs b/actix-http/src/h1/mod.rs index 64586a2dc..8c569165d 100644 --- a/actix-http/src/h1/mod.rs +++ b/actix-http/src/h1/mod.rs @@ -7,6 +7,8 @@ mod client; mod codec; mod decoder; mod dispatcher; +#[cfg(test)] +mod dispatcher_tests; mod encoder; mod expect; mod payload; diff --git a/actix-http/src/test.rs b/actix-http/src/test.rs index 1f76498ef..529197736 100644 --- a/actix-http/src/test.rs +++ b/actix-http/src/test.rs @@ -1,7 +1,7 @@ //! Various testing helpers for use in internal and app tests. use std::{ - cell::{Ref, RefCell}, + cell::{Ref, RefCell, RefMut}, io::{self, Read, Write}, pin::Pin, rc::Rc, @@ -157,10 +157,11 @@ fn parts(parts: &mut Option) -> &mut Inner { } /// Async I/O test buffer. +#[derive(Debug)] pub struct TestBuffer { - pub read_buf: BytesMut, - pub write_buf: BytesMut, - pub err: Option, + pub read_buf: Rc>, + pub write_buf: Rc>, + pub err: Option>, } impl TestBuffer { @@ -170,34 +171,64 @@ impl TestBuffer { T: Into, { Self { - read_buf: data.into(), - write_buf: BytesMut::new(), + read_buf: Rc::new(RefCell::new(data.into())), + write_buf: Rc::new(RefCell::new(BytesMut::new())), err: None, } } + // intentionally not using Clone trait + #[allow(dead_code)] + pub(crate) fn clone(&self) -> Self { + Self { + read_buf: self.read_buf.clone(), + write_buf: self.write_buf.clone(), + err: self.err.clone(), + } + } + /// Create new empty `TestBuffer` instance. pub fn empty() -> Self { Self::new("") } + #[allow(dead_code)] + pub(crate) fn read_buf_slice(&self) -> Ref<'_, [u8]> { + Ref::map(self.read_buf.borrow(), |b| b.as_ref()) + } + + #[allow(dead_code)] + pub(crate) fn read_buf_slice_mut(&self) -> RefMut<'_, [u8]> { + RefMut::map(self.read_buf.borrow_mut(), |b| b.as_mut()) + } + + #[allow(dead_code)] + pub(crate) fn write_buf_slice(&self) -> Ref<'_, [u8]> { + Ref::map(self.write_buf.borrow(), |b| b.as_ref()) + } + + #[allow(dead_code)] + pub(crate) fn write_buf_slice_mut(&self) -> RefMut<'_, [u8]> { + RefMut::map(self.write_buf.borrow_mut(), |b| b.as_mut()) + } + /// Add data to read buffer. pub fn extend_read_buf>(&mut self, data: T) { - self.read_buf.extend_from_slice(data.as_ref()) + self.read_buf.borrow_mut().extend_from_slice(data.as_ref()) } } impl io::Read for TestBuffer { fn read(&mut self, dst: &mut [u8]) -> Result { - if self.read_buf.is_empty() { + if self.read_buf.borrow().is_empty() { if self.err.is_some() { - Err(self.err.take().unwrap()) + Err(Rc::try_unwrap(self.err.take().unwrap()).unwrap()) } else { Err(io::Error::new(io::ErrorKind::WouldBlock, "")) } } else { - let size = std::cmp::min(self.read_buf.len(), dst.len()); - let b = self.read_buf.split_to(size); + let size = std::cmp::min(self.read_buf.borrow().len(), dst.len()); + let b = self.read_buf.borrow_mut().split_to(size); dst[..size].copy_from_slice(&b); Ok(size) } @@ -206,7 +237,7 @@ impl io::Read for TestBuffer { impl io::Write for TestBuffer { fn write(&mut self, buf: &[u8]) -> io::Result { - self.write_buf.extend(buf); + RefCell::borrow_mut(&self.write_buf).extend(buf); Ok(buf.len()) } From 3ae4f0a62915120172dc4d11e508ce247abde78a Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Thu, 27 Jan 2022 06:29:46 +0000 Subject: [PATCH 003/170] add keep-alive dispatcher tests --- actix-http/src/h1/dispatcher_tests.rs | 181 +++++++++++++++++++++++--- actix-http/src/test.rs | 5 + 2 files changed, 166 insertions(+), 20 deletions(-) diff --git a/actix-http/src/h1/dispatcher_tests.rs b/actix-http/src/h1/dispatcher_tests.rs index 21da2422f..cc86cbdfd 100644 --- a/actix-http/src/h1/dispatcher_tests.rs +++ b/actix-http/src/h1/dispatcher_tests.rs @@ -1,5 +1,6 @@ -use std::{future::Future, str, task::Poll}; +use std::{future::Future, str, task::Poll, time::Duration}; +use actix_rt::time::sleep; use actix_service::fn_service; use actix_utils::future::{ready, Ready}; use bytes::Bytes; @@ -63,23 +64,22 @@ fn echo_payload_service() -> impl Service, E } #[actix_rt::test] -#[ignore] -async fn test_keep_alive() { +async fn test_keep_alive_timeout() { + let buf = TestBuffer::new("GET /abcd HTTP/1.1\r\n\r\n"); + + let cfg = ServiceConfig::new(KeepAlive::Timeout(1), 100, 0, false, None); + let services = HttpFlow::new(echo_path_service(), ExpectHandler, None); + + let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new( + buf.clone(), + services, + cfg, + None, + OnConnectData::default(), + ); + actix_rt::pin!(h1); + lazy(|cx| { - let buf = TestBuffer::new("GET /abcd HTTP/1.1\r\n\r\n"); - - let cfg = ServiceConfig::new(KeepAlive::Timeout(1), 100, 0, false, None); - let services = HttpFlow::new(echo_path_service(), ExpectHandler, None); - - let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new( - buf.clone(), - services, - cfg, - None, - OnConnectData::default(), - ); - actix_rt::pin!(h1); - assert!(matches!(&h1.inner, DispatcherState::Normal { .. })); assert!( @@ -90,7 +90,7 @@ async fn test_keep_alive() { // polls: initial assert_eq!(h1.poll_count, 1); - let mut res = buf.write_buf_slice_mut(); + let mut res = buf.take_write_buf().to_vec(); stabilize_date_header(&mut res); let res = &res[..]; @@ -105,8 +105,149 @@ async fn test_keep_alive() { res, exp, "\nexpected response not in write buffer:\n\ - response: {:?}\n\ - expected: {:?}", + response: {:?}\n\ + expected: {:?}", + String::from_utf8_lossy(res), + String::from_utf8_lossy(exp) + ); + }) + .await; + + // sleep slightly longer than keep-alive timeout + sleep(Duration::from_millis(1100)).await; + + lazy(|cx| { + assert!( + h1.as_mut().poll(cx).is_ready(), + "keep-alive should have resolved", + ); + + // polls: initial => keep-alive wake-up shutdown + assert_eq!(h1.poll_count, 2); + + if let DispatcherStateProj::Normal { inner } = h1.project().inner.project() { + // connection closed + assert!(inner.flags.contains(Flags::SHUTDOWN)); + assert!(inner.flags.contains(Flags::WRITE_DISCONNECT)); + // and nothing added to write buffer + assert!(buf.write_buf_slice().is_empty()); + } + }) + .await; +} + +#[actix_rt::test] +async fn test_keep_alive_follow_up_req() { + let mut buf = TestBuffer::new("GET /abcd HTTP/1.1\r\n\r\n"); + + let cfg = ServiceConfig::new(KeepAlive::Timeout(2), 100, 0, false, None); + let services = HttpFlow::new(echo_path_service(), ExpectHandler, None); + + let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new( + buf.clone(), + services, + cfg, + None, + OnConnectData::default(), + ); + actix_rt::pin!(h1); + + lazy(|cx| { + assert!(matches!(&h1.inner, DispatcherState::Normal { .. })); + + assert!( + h1.as_mut().poll(cx).is_pending(), + "keep-alive should prevent poll from resolving" + ); + + // polls: initial + assert_eq!(h1.poll_count, 1); + + let mut res = buf.take_write_buf().to_vec(); + stabilize_date_header(&mut res); + let res = &res[..]; + + let exp = b"\ + HTTP/1.1 200 OK\r\n\ + content-length: 5\r\n\ + date: Thu, 01 Jan 1970 12:34:56 UTC\r\n\r\n\ + /abcd\ + "; + + assert_eq!( + res, + exp, + "\nexpected response not in write buffer:\n\ + response: {:?}\n\ + expected: {:?}", + String::from_utf8_lossy(res), + String::from_utf8_lossy(exp) + ); + }) + .await; + + // sleep for less than KA timeout + sleep(Duration::from_millis(200)).await; + + lazy(|cx| { + assert!( + h1.as_mut().poll(cx).is_pending(), + "keep-alive should not have resolved dispatcher yet", + ); + + // polls: initial => manual + assert_eq!(h1.poll_count, 2); + + if let DispatcherStateProj::Normal { inner } = h1.as_mut().project().inner.project() { + // connection not closed + assert!(!inner.flags.contains(Flags::SHUTDOWN)); + assert!(!inner.flags.contains(Flags::WRITE_DISCONNECT)); + // and nothing added to write buffer + assert!(buf.write_buf_slice().is_empty()); + } + }) + .await; + + lazy(|cx| { + buf.extend_read_buf( + "\ + GET /efg HTTP/1.1\r\n\ + Connection: close\r\n\ + \r\n\r\n", + ); + + assert!( + h1.as_mut().poll(cx).is_ready(), + "connection close header should override keep-alive setting", + ); + + // polls: initial => manual => follow-up req => shutdown + assert_eq!(h1.poll_count, 4); + + if let DispatcherStateProj::Normal { inner } = h1.as_mut().project().inner.project() { + // connection closed + assert!(inner.flags.contains(Flags::SHUTDOWN)); + assert!(!inner.flags.contains(Flags::WRITE_DISCONNECT)); + } + + let mut res = buf.take_write_buf().to_vec(); + stabilize_date_header(&mut res); + let res = &res[..]; + + let exp = b"\ + HTTP/1.1 200 OK\r\n\ + content-length: 4\r\n\ + connection: close\r\n\ + date: Thu, 01 Jan 1970 12:34:56 UTC\r\n\r\n\ + /efg\ + "; + + assert_eq!( + res, + exp, + "\nexpected response not in write buffer:\n\ + response: {:?}\n\ + expected: {:?}", String::from_utf8_lossy(res), String::from_utf8_lossy(exp) ); diff --git a/actix-http/src/test.rs b/actix-http/src/test.rs index 529197736..0d4d342ec 100644 --- a/actix-http/src/test.rs +++ b/actix-http/src/test.rs @@ -212,6 +212,11 @@ impl TestBuffer { RefMut::map(self.write_buf.borrow_mut(), |b| b.as_mut()) } + #[allow(dead_code)] + pub(crate) fn take_write_buf(&self) -> Bytes { + self.write_buf.borrow_mut().split().freeze() + } + /// Add data to read buffer. pub fn extend_read_buf>(&mut self, data: T) { self.read_buf.borrow_mut().extend_from_slice(data.as_ref()) From 0d93a8c273ac30cac9e7af5700bd8090af6a60dd Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Thu, 27 Jan 2022 06:32:28 +0000 Subject: [PATCH 004/170] add examples readme --- examples/README.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 examples/README.md diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 000000000..163f67b46 --- /dev/null +++ b/examples/README.md @@ -0,0 +1,3 @@ +# Actix Web Examples + +This folder contain just a few standalone code samples. There is a much larger registry of example projects [in the examples repo](https://github.com/actix/examples). From 37799df978818d180e5aec5f9261042a13587d6d Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Thu, 27 Jan 2022 06:42:54 +0000 Subject: [PATCH 005/170] add basic dispatcher test --- actix-http/src/h1/dispatcher_tests.rs | 52 +++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/actix-http/src/h1/dispatcher_tests.rs b/actix-http/src/h1/dispatcher_tests.rs index cc86cbdfd..057ef1583 100644 --- a/actix-http/src/h1/dispatcher_tests.rs +++ b/actix-http/src/h1/dispatcher_tests.rs @@ -63,6 +63,58 @@ fn echo_payload_service() -> impl Service, E }) } +#[actix_rt::test] +async fn test_basic() { + let buf = TestBuffer::new("GET /abcd HTTP/1.1\r\n\r\n"); + + let cfg = ServiceConfig::new(KeepAlive::Disabled, 100, 0, false, None); + let services = HttpFlow::new(echo_path_service(), ExpectHandler, None); + + let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new( + buf.clone(), + services, + cfg, + None, + OnConnectData::default(), + ); + actix_rt::pin!(h1); + + lazy(|cx| { + assert!(matches!(&h1.inner, DispatcherState::Normal { .. })); + + match h1.as_mut().poll(cx) { + Poll::Pending => panic!("first poll should not be pending"), + Poll::Ready(res) => assert!(res.is_ok()), + } + + // polls: initial => shutdown + assert_eq!(h1.poll_count, 2); + + let mut res = buf.take_write_buf().to_vec(); + stabilize_date_header(&mut res); + let res = &res[..]; + + let exp = b"\ + HTTP/1.1 200 OK\r\n\ + content-length: 5\r\n\ + connection: close\r\n\ + date: Thu, 01 Jan 1970 12:34:56 UTC\r\n\r\n\ + /abcd\ + "; + + assert_eq!( + res, + exp, + "\nexpected response not in write buffer:\n\ + response: {:?}\n\ + expected: {:?}", + String::from_utf8_lossy(res), + String::from_utf8_lossy(exp) + ); + }) + .await; +} + #[actix_rt::test] async fn test_keep_alive_timeout() { let buf = TestBuffer::new("GET /abcd HTTP/1.1\r\n\r\n"); From cc9ba162f78253ac3fbf77d02c82814b132b64ff Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Thu, 27 Jan 2022 17:00:07 +0000 Subject: [PATCH 006/170] add late request dispatcher test --- actix-http/src/h1/dispatcher_tests.rs | 63 +++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/actix-http/src/h1/dispatcher_tests.rs b/actix-http/src/h1/dispatcher_tests.rs index 057ef1583..379019c6f 100644 --- a/actix-http/src/h1/dispatcher_tests.rs +++ b/actix-http/src/h1/dispatcher_tests.rs @@ -63,6 +63,69 @@ fn echo_payload_service() -> impl Service, E }) } +#[actix_rt::test] +async fn late_request() { + let _ = env_logger::try_init(); + + let mut buf = TestBuffer::empty(); + + let cfg = ServiceConfig::new(KeepAlive::Disabled, 100, 0, false, None); + let services = HttpFlow::new(ok_service(), ExpectHandler, None); + + let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new( + buf.clone(), + services, + cfg, + None, + OnConnectData::default(), + ); + actix_rt::pin!(h1); + + lazy(|cx| { + assert!(matches!(&h1.inner, DispatcherState::Normal { .. })); + + match h1.as_mut().poll(cx) { + Poll::Ready(_) => panic!("first poll should not be ready"), + Poll::Pending => {} + } + + // polls: initial + assert_eq!(h1.poll_count, 1); + + buf.extend_read_buf("GET /abcd HTTP/1.1\r\nConnection: close\r\n\r\n"); + + match h1.as_mut().poll(cx) { + Poll::Pending => panic!("second poll should not be pending"), + Poll::Ready(res) => assert!(res.is_ok()), + } + + // polls: initial pending => handle req => shutdown + assert_eq!(h1.poll_count, 3); + + let mut res = buf.take_write_buf().to_vec(); + stabilize_date_header(&mut res); + let res = &res[..]; + + let exp = b"\ + HTTP/1.1 200 OK\r\n\ + content-length: 0\r\n\ + connection: close\r\n\ + date: Thu, 01 Jan 1970 12:34:56 UTC\r\n\r\n\ + "; + + assert_eq!( + res, + exp, + "\nexpected response not in write buffer:\n\ + response: {:?}\n\ + expected: {:?}", + String::from_utf8_lossy(res), + String::from_utf8_lossy(exp) + ); + }) + .await; +} + #[actix_rt::test] async fn test_basic() { let buf = TestBuffer::new("GET /abcd HTTP/1.1\r\n\r\n"); From a9f497d05f259e91370b7c8ac5161adf958f5d13 Mon Sep 17 00:00:00 2001 From: Luca Palmieri Date: Fri, 28 Jan 2022 17:28:16 +0000 Subject: [PATCH 007/170] Guard against broken intra-doc links in CI (#2616) --- .github/workflows/clippy-fmt.yml | 18 ++++++++++++++++++ actix-files/src/named.rs | 3 ++- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/.github/workflows/clippy-fmt.yml b/.github/workflows/clippy-fmt.yml index 9fcb0a561..9f5a0570a 100644 --- a/.github/workflows/clippy-fmt.yml +++ b/.github/workflows/clippy-fmt.yml @@ -5,6 +5,24 @@ on: types: [opened, synchronize, reopened] jobs: + lint-docs: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Install Rust + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + profile: minimal + components: rust-docs + - name: Check for broken intra-doc links + uses: actions-rs/cargo@v1 + env: + RUSTDOCFLAGS: "-D warnings" + with: + command: doc + args: --no-deps --all-features --workspace + fmt: runs-on: ubuntu-latest steps: diff --git a/actix-files/src/named.rs b/actix-files/src/named.rs index 14495e660..cb6875065 100644 --- a/actix-files/src/named.rs +++ b/actix-files/src/named.rs @@ -209,6 +209,7 @@ impl NamedFile { Self::from_file(file, path) } + #[allow(rustdoc::broken_intra_doc_links)] /// Attempts to open a file asynchronously in read-only mode. /// /// When the `experimental-io-uring` crate feature is enabled, this will be async. @@ -300,7 +301,7 @@ impl NamedFile { /// Set content encoding for serving this file /// - /// Must be used with [`actix_web::middleware::Compress`] to take effect. + /// Must be used with `actix_web::middleware::Compress` to take effect. #[inline] pub fn set_content_encoding(mut self, enc: ContentEncoding) -> Self { self.encoding = Some(enc); From 21a08ca7969e9a08035a4b9e78d8419f3cce3c64 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Fri, 28 Jan 2022 20:27:16 +0000 Subject: [PATCH 008/170] tweak set_content_encoding docs --- .github/workflows/clippy-fmt.yml | 36 ++++++++++++++++---------------- actix-files/src/named.rs | 6 ++++-- 2 files changed, 22 insertions(+), 20 deletions(-) diff --git a/.github/workflows/clippy-fmt.yml b/.github/workflows/clippy-fmt.yml index 9f5a0570a..bc2cec145 100644 --- a/.github/workflows/clippy-fmt.yml +++ b/.github/workflows/clippy-fmt.yml @@ -5,24 +5,6 @@ on: types: [opened, synchronize, reopened] jobs: - lint-docs: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - name: Install Rust - uses: actions-rs/toolchain@v1 - with: - toolchain: stable - profile: minimal - components: rust-docs - - name: Check for broken intra-doc links - uses: actions-rs/cargo@v1 - env: - RUSTDOCFLAGS: "-D warnings" - with: - command: doc - args: --no-deps --all-features --workspace - fmt: runs-on: ubuntu-latest steps: @@ -64,3 +46,21 @@ jobs: with: token: ${{ secrets.GITHUB_TOKEN }} args: --workspace --tests --examples --all-features + + lint-docs: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Install Rust + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + profile: minimal + components: rust-docs + - name: Check for broken intra-doc links + uses: actions-rs/cargo@v1 + env: + RUSTDOCFLAGS: "-D warnings" + with: + command: doc + args: --no-deps --all-features --workspace diff --git a/actix-files/src/named.rs b/actix-files/src/named.rs index cb6875065..baf9b5531 100644 --- a/actix-files/src/named.rs +++ b/actix-files/src/named.rs @@ -299,9 +299,11 @@ impl NamedFile { self } - /// Set content encoding for serving this file + /// Sets content encoding for this file. /// - /// Must be used with `actix_web::middleware::Compress` to take effect. + /// This prevents the `Compress` middleware from modifying the file contents and signals to + /// browsers/clients how to decode it. For example, if serving a compressed HTML file (e.g., + /// `index.html.gz`) then use `.set_content_encoding(ContentEncoding::Gzip)`. #[inline] pub fn set_content_encoding(mut self, enc: ContentEncoding) -> Self { self.encoding = Some(enc); From a3416112a5010b378dcfaebad3b9c65f8838941e Mon Sep 17 00:00:00 2001 From: Luca Palmieri Date: Fri, 28 Jan 2022 20:31:54 +0000 Subject: [PATCH 009/170] Improve the documentation for `default_service` (#2614) --- src/app.rs | 17 ++--------------- src/resource.rs | 8 +++++--- src/scope.rs | 3 ++- 3 files changed, 9 insertions(+), 19 deletions(-) diff --git a/src/app.rs b/src/app.rs index da33ebc4b..bbf752595 100644 --- a/src/app.rs +++ b/src/app.rs @@ -236,9 +236,9 @@ where self } - /// Default service to be used if no matching resource could be found. + /// Default service that is invoked when no matching resource could be found. /// - /// It is possible to use services like `Resource`, `Route`. + /// You must use a [`Route`] as default service: /// /// ``` /// use actix_web::{web, App, HttpResponse}; @@ -253,19 +253,6 @@ where /// .default_service( /// web::route().to(|| HttpResponse::NotFound())); /// ``` - /// - /// It is also possible to use static files as default service. - /// - /// ``` - /// use actix_web::{web, App, HttpResponse}; - /// - /// let app = App::new() - /// .service( - /// web::resource("/index.html").to(|| HttpResponse::Ok())) - /// .default_service( - /// web::to(|| HttpResponse::NotFound()) - /// ); - /// ``` pub fn default_service(mut self, svc: F) -> Self where F: IntoServiceFactory, diff --git a/src/resource.rs b/src/resource.rs index a0fc19faf..3451eff45 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -312,9 +312,11 @@ where } } - /// Default service to be used if no matching route could be found. - /// By default *405* response get returned. Resource does not use - /// default handler from `App` or `Scope`. + /// Default service to be used if no matching route could be found. + /// You can pass a [`Route`] as default_service. + /// + /// If no default service is specified, a `405 Method Not Allowed` response will be returned to the caller. + /// [`Resource`] does **not** inherit the default handler specified on the parent [`App`](crate::App) or [`Scope`](crate::Scope). pub fn default_service(mut self, f: F) -> Self where F: IntoServiceFactory, diff --git a/src/scope.rs b/src/scope.rs index c05ce054d..0bad5c581 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -264,7 +264,8 @@ where /// Default service to be used if no matching route could be found. /// - /// If default resource is not registered, app's default resource is being used. + /// If a default service is not registered, it will fall back to the default service of + /// the parent [`App`](crate::App) (see [`App::default_service`](crate::App::default_service). pub fn default_service(mut self, f: F) -> Self where F: IntoServiceFactory, From b3e84b5c4bf32fd8aeac8c938f9906ce730a7458 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Fri, 28 Jan 2022 20:53:51 +0000 Subject: [PATCH 010/170] tweak default_service docs --- src/app.rs | 12 +++++++----- src/app_service.rs | 2 +- src/resource.rs | 10 ++++++---- src/scope.rs | 4 ++-- 4 files changed, 16 insertions(+), 12 deletions(-) diff --git a/src/app.rs b/src/app.rs index bbf752595..a63cf5d50 100644 --- a/src/app.rs +++ b/src/app.rs @@ -238,8 +238,12 @@ where /// Default service that is invoked when no matching resource could be found. /// - /// You must use a [`Route`] as default service: + /// You can use a [`Route`] as default service. /// + /// If a default service is not registered, an empty `404 Not Found` response will be sent to + /// the client instead. + /// + /// # Examples /// ``` /// use actix_web::{web, App, HttpResponse}; /// @@ -248,10 +252,8 @@ where /// } /// /// let app = App::new() - /// .service( - /// web::resource("/index.html").route(web::get().to(index))) - /// .default_service( - /// web::route().to(|| HttpResponse::NotFound())); + /// .service(web::resource("/index.html").route(web::get().to(index))) + /// .default_service(web::to(|| HttpResponse::NotFound())); /// ``` pub fn default_service(mut self, svc: F) -> Self where diff --git a/src/app_service.rs b/src/app_service.rs index edfb3e4a2..dbd718330 100644 --- a/src/app_service.rs +++ b/src/app_service.rs @@ -72,7 +72,7 @@ where }))) }); - // App config + // create App config to pass to child services let mut config = AppService::new(config, default.clone()); // register services diff --git a/src/resource.rs b/src/resource.rs index 3451eff45..6a01a0496 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -312,11 +312,13 @@ where } } - /// Default service to be used if no matching route could be found. - /// You can pass a [`Route`] as default_service. + /// Default service to be used if no matching route could be found. /// - /// If no default service is specified, a `405 Method Not Allowed` response will be returned to the caller. - /// [`Resource`] does **not** inherit the default handler specified on the parent [`App`](crate::App) or [`Scope`](crate::Scope). + /// You can use a [`Route`] as default service. + /// + /// If a default service is not registered, an empty `405 Method Not Allowed` response will be + /// sent to the client instead. Unlike [`Scope`](crate::Scope)s, a [`Resource`] does **not** + /// inherit its parent's default service. pub fn default_service(mut self, f: F) -> Self where F: IntoServiceFactory, diff --git a/src/scope.rs b/src/scope.rs index 0bad5c581..dad727430 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -262,10 +262,10 @@ where ) } - /// Default service to be used if no matching route could be found. + /// Default service to be used if no matching resource could be found. /// /// If a default service is not registered, it will fall back to the default service of - /// the parent [`App`](crate::App) (see [`App::default_service`](crate::App::default_service). + /// the parent [`App`](crate::App) (see [`App::default_service`](crate::App::default_service)). pub fn default_service(mut self, f: F) -> Self where F: IntoServiceFactory, From 3200de3f34b21f65bf84d7b04ba118f03d808f02 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Mon, 31 Jan 2022 17:30:34 +0000 Subject: [PATCH 011/170] fix request head timeout (#2611) --- CHANGES.md | 5 + actix-http/CHANGES.md | 26 + actix-http/Cargo.toml | 1 + actix-http/examples/bench.rs | 27 + actix-http/examples/echo.rs | 6 +- actix-http/examples/h2spec.rs | 25 + actix-http/examples/hello-world.rs | 6 +- actix-http/src/builder.rs | 93 +-- actix-http/src/config.rs | 317 ++------- actix-http/src/date.rs | 92 +++ actix-http/src/h1/client.rs | 49 +- actix-http/src/h1/codec.rs | 24 +- actix-http/src/h1/dispatcher.rs | 752 ++++++++++++++-------- actix-http/src/h1/dispatcher_tests.rs | 103 ++- actix-http/src/h1/encoder.rs | 7 +- actix-http/src/h1/mod.rs | 6 +- actix-http/src/h1/timer.rs | 80 +++ actix-http/src/h2/dispatcher.rs | 16 +- actix-http/src/h2/mod.rs | 18 +- actix-http/src/header/map.rs | 2 +- actix-http/src/header/shared/http_date.rs | 3 +- actix-http/src/keep_alive.rs | 83 +++ actix-http/src/lib.rs | 7 +- actix-http/src/notify_on_drop.rs | 49 ++ actix-http/src/requests/head.rs | 2 +- actix-http/src/responses/head.rs | 24 +- actix-http/src/service.rs | 28 +- actix-http/src/test.rs | 2 +- actix-http/tests/test_client.rs | 8 +- actix-http/tests/test_h2_timer.rs | 8 +- actix-http/tests/test_server.rs | 95 +-- actix-http/tests/test_ws.rs | 2 +- actix-test/CHANGES.md | 3 + actix-test/src/lib.rs | 30 +- awc/src/client/h1proto.rs | 6 +- src/server.rs | 51 +- tests/test_httpserver.rs | 6 +- tests/test_server.rs | 8 +- 38 files changed, 1303 insertions(+), 767 deletions(-) create mode 100644 actix-http/examples/bench.rs create mode 100644 actix-http/examples/h2spec.rs create mode 100644 actix-http/src/date.rs create mode 100644 actix-http/src/h1/timer.rs create mode 100644 actix-http/src/keep_alive.rs create mode 100644 actix-http/src/notify_on_drop.rs diff --git a/CHANGES.md b/CHANGES.md index 8c3997663..c00bc7198 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,10 +1,15 @@ # Changes ## Unreleased - 2021-xx-xx +### Changed +- Rename `HttpServer::{client_timeout => client_request_timeout}`. [#2611] +- Rename `HttpServer::{client_shutdown => client_disconnect_timeout}`. [#2611] + ### Removed - `impl Future for HttpResponse`. [#2601] [#2601]: https://github.com/actix/actix-web/pull/2601 +[#2611]: https://github.com/actix/actix-web/pull/2611 ## 4.0.0-beta.21 - 2022-01-21 diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 6047a6bc5..a748bc43f 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -1,6 +1,32 @@ # Changes ## Unreleased - 2021-xx-xx +### Added +- Implement `Default` for `KeepAlive`. [#2611] +- Implement `From` for `KeepAlive`. [#2611] +- Implement `From>` for `KeepAlive`. [#2611] +- Implement `Default` for `HttpServiceBuilder`. [#2611] + +### Changed +- Rename `ServiceConfig::{client_timer_expire => client_request_deadline}`. [#2611] +- Rename `ServiceConfig::{client_disconnect_timer => client_disconnect_deadline}`. [#2611] +- Deadline methods in `ServiceConfig` now return `std::time::Instant`s instead of Tokio's wrapper type. [#2611] +- Rename `h1::Codec::{keepalive => keep_alive}`. [#2611] +- Rename `h1::Codec::{keepalive_enabled => keep_alive_enabled}`. [#2611] +- Rename `h1::ClientCodec::{keepalive => keep_alive}`. [#2611] +- Rename `h1::ClientPayloadCodec::{keepalive => keep_alive}`. [#2611] +- `ServiceConfig::keep_alive` now returns a `KeepAlive`. [#2611] + +### Fixed +- HTTP/1.1 dispatcher correctly uses client request timeout. [#2611] + +### Removed +- `ServiceConfig::{client_timer, keep_alive_timer}`. [#2611] +- `impl From for KeepAlive`; use `Duration`s instead. [#2611] +- `impl From> for KeepAlive`; use `Duration`s instead. [#2611] +- `HttpServiceBuilder::new`; use `default` instead. [#2611] + +[#2611]: https://github.com/actix/actix-web/pull/2611 ## 3.0.0-beta.19 - 2022-01-21 diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index e93d1b7af..11bfa7a1a 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -92,6 +92,7 @@ criterion = { version = "0.3", features = ["html_reports"] } env_logger = "0.9" futures-util = { version = "0.3.7", default-features = false, features = ["alloc"] } memchr = "2.4" +once_cell = "1.9" rcgen = "0.8" regex = "1.3" rustls-pemfile = "0.2" diff --git a/actix-http/examples/bench.rs b/actix-http/examples/bench.rs new file mode 100644 index 000000000..e41c0bb4f --- /dev/null +++ b/actix-http/examples/bench.rs @@ -0,0 +1,27 @@ +use std::{convert::Infallible, io, time::Duration}; + +use actix_http::{HttpService, Request, Response, StatusCode}; +use actix_server::Server; +use once_cell::sync::Lazy; + +static STR: Lazy = Lazy::new(|| "HELLO WORLD ".repeat(20)); + +#[actix_rt::main] +async fn main() -> io::Result<()> { + env_logger::init_from_env(env_logger::Env::new().default_filter_or("info")); + + Server::build() + .bind("dispatcher-benchmark", ("127.0.0.1", 8080), || { + HttpService::build() + .client_request_timeout(Duration::from_secs(1)) + .finish(|_: Request| async move { + let mut res = Response::build(StatusCode::OK); + Ok::<_, Infallible>(res.body(&**STR)) + }) + .tcp() + })? + // limiting number of workers so that bench client is not sharing as many resources + .workers(4) + .run() + .await +} diff --git a/actix-http/examples/echo.rs b/actix-http/examples/echo.rs index f9188ed9f..58de64530 100644 --- a/actix-http/examples/echo.rs +++ b/actix-http/examples/echo.rs @@ -1,4 +1,4 @@ -use std::io; +use std::{io, time::Duration}; use actix_http::{Error, HttpService, Request, Response, StatusCode}; use actix_server::Server; @@ -13,8 +13,8 @@ async fn main() -> io::Result<()> { Server::build() .bind("echo", ("127.0.0.1", 8080), || { HttpService::build() - .client_timeout(1000) - .client_disconnect(1000) + .client_request_timeout(Duration::from_secs(1)) + .client_disconnect_timeout(Duration::from_secs(1)) // handles HTTP/1.1 and HTTP/2 .finish(|mut req: Request| async move { let mut body = BytesMut::new(); diff --git a/actix-http/examples/h2spec.rs b/actix-http/examples/h2spec.rs new file mode 100644 index 000000000..4ab426c6c --- /dev/null +++ b/actix-http/examples/h2spec.rs @@ -0,0 +1,25 @@ +use std::{convert::Infallible, io}; + +use actix_http::{HttpService, Request, Response, StatusCode}; +use actix_server::Server; +use once_cell::sync::Lazy; + +static STR: Lazy = Lazy::new(|| "HELLO WORLD ".repeat(100)); + +#[actix_rt::main] +async fn main() -> io::Result<()> { + env_logger::init_from_env(env_logger::Env::new().default_filter_or("info")); + + Server::build() + .bind("h2spec", ("127.0.0.1", 8080), || { + HttpService::build() + .h2(|_: Request| async move { + let mut res = Response::build(StatusCode::OK); + Ok::<_, Infallible>(res.body(&**STR)) + }) + .tcp() + })? + .workers(4) + .run() + .await +} diff --git a/actix-http/examples/hello-world.rs b/actix-http/examples/hello-world.rs index a29903cc4..1a83d4d9c 100644 --- a/actix-http/examples/hello-world.rs +++ b/actix-http/examples/hello-world.rs @@ -1,4 +1,4 @@ -use std::{convert::Infallible, io}; +use std::{convert::Infallible, io, time::Duration}; use actix_http::{ header::HeaderValue, HttpMessage, HttpService, Request, Response, StatusCode, @@ -12,8 +12,8 @@ async fn main() -> io::Result<()> { Server::build() .bind("hello-world", ("127.0.0.1", 8080), || { HttpService::build() - .client_timeout(1000) - .client_disconnect(1000) + .client_request_timeout(Duration::from_secs(1)) + .client_disconnect_timeout(Duration::from_secs(1)) .on_connect_ext(|_, ext| { ext.insert(42u32); }) diff --git a/actix-http/src/builder.rs b/actix-http/src/builder.rs index 408ee7924..9dd145ce1 100644 --- a/actix-http/src/builder.rs +++ b/actix-http/src/builder.rs @@ -1,25 +1,23 @@ -use std::{fmt, marker::PhantomData, net, rc::Rc}; +use std::{fmt, marker::PhantomData, net, rc::Rc, time::Duration}; use actix_codec::Framed; use actix_service::{IntoServiceFactory, Service, ServiceFactory}; use crate::{ body::{BoxBody, MessageBody}, - config::{KeepAlive, ServiceConfig}, h1::{self, ExpectHandler, H1Service, UpgradeHandler}, h2::H2Service, service::HttpService, - ConnectCallback, Extensions, Request, Response, + ConnectCallback, Extensions, KeepAlive, Request, Response, ServiceConfig, }; -/// A HTTP service builder +/// An HTTP service builder. /// -/// This type can be used to construct an instance of [`HttpService`] through a -/// builder-like pattern. +/// This type can construct an instance of [`HttpService`] through a builder-like pattern. pub struct HttpServiceBuilder { keep_alive: KeepAlive, - client_timeout: u64, - client_disconnect: u64, + client_request_timeout: Duration, + client_disconnect_timeout: Duration, secure: bool, local_addr: Option, expect: X, @@ -28,22 +26,23 @@ pub struct HttpServiceBuilder { _phantom: PhantomData, } -impl HttpServiceBuilder +impl Default for HttpServiceBuilder where S: ServiceFactory, S::Error: Into> + 'static, S::InitError: fmt::Debug, >::Future: 'static, { - /// Create instance of `ServiceConfigBuilder` - #[allow(clippy::new_without_default)] - pub fn new() -> Self { + fn default() -> Self { HttpServiceBuilder { - keep_alive: KeepAlive::Timeout(5), - client_timeout: 5000, - client_disconnect: 0, + // ServiceConfig parts (make sure defaults match) + keep_alive: KeepAlive::default(), + client_request_timeout: Duration::from_secs(5), + client_disconnect_timeout: Duration::ZERO, secure: false, local_addr: None, + + // dispatcher parts expect: ExpectHandler, upgrade: None, on_connect_ext: None, @@ -65,9 +64,11 @@ where U::Error: fmt::Display, U::InitError: fmt::Debug, { - /// Set server keep-alive setting. + /// Set connection keep-alive setting. /// - /// By default keep alive is set to a 5 seconds. + /// Applies to HTTP/1.1 keep-alive and HTTP/2 ping-pong. + /// + /// By default keep-alive is 5 seconds. pub fn keep_alive>(mut self, val: W) -> Self { self.keep_alive = val.into(); self @@ -85,33 +86,45 @@ where self } - /// Set server client timeout in milliseconds for first request. + /// Set client request timeout (for first request). /// - /// Defines a timeout for reading client request header. If a client does not transmit - /// the entire set headers within this time, the request is terminated with - /// the 408 (Request Time-out) error. + /// Defines a timeout for reading client request header. If the client does not transmit the + /// request head within this duration, the connection is terminated with a `408 Request Timeout` + /// response error. /// - /// To disable timeout set value to 0. + /// A duration of zero disables the timeout. /// - /// By default client timeout is set to 5000 milliseconds. - pub fn client_timeout(mut self, val: u64) -> Self { - self.client_timeout = val; + /// By default, the client timeout is 5 seconds. + pub fn client_request_timeout(mut self, dur: Duration) -> Self { + self.client_request_timeout = dur; self } - /// Set server connection disconnect timeout in milliseconds. + #[doc(hidden)] + #[deprecated(since = "3.0.0", note = "Renamed to `client_request_timeout`.")] + pub fn client_timeout(self, dur: Duration) -> Self { + self.client_request_timeout(dur) + } + + /// Set client connection disconnect timeout. /// /// Defines a timeout for disconnect connection. If a disconnect procedure does not complete /// within this time, the request get dropped. This timeout affects secure connections. /// - /// To disable timeout set value to 0. + /// A duration of zero disables the timeout. /// - /// By default disconnect timeout is set to 0. - pub fn client_disconnect(mut self, val: u64) -> Self { - self.client_disconnect = val; + /// By default, the disconnect timeout is disabled. + pub fn client_disconnect_timeout(mut self, dur: Duration) -> Self { + self.client_disconnect_timeout = dur; self } + #[doc(hidden)] + #[deprecated(since = "3.0.0", note = "Renamed to `client_disconnect_timeout`.")] + pub fn client_disconnect(self, dur: Duration) -> Self { + self.client_disconnect_timeout(dur) + } + /// Provide service for `EXPECT: 100-Continue` support. /// /// Service get called with request that contains `EXPECT` header. @@ -126,8 +139,8 @@ where { HttpServiceBuilder { keep_alive: self.keep_alive, - client_timeout: self.client_timeout, - client_disconnect: self.client_disconnect, + client_request_timeout: self.client_request_timeout, + client_disconnect_timeout: self.client_disconnect_timeout, secure: self.secure, local_addr: self.local_addr, expect: expect.into_factory(), @@ -150,8 +163,8 @@ where { HttpServiceBuilder { keep_alive: self.keep_alive, - client_timeout: self.client_timeout, - client_disconnect: self.client_disconnect, + client_request_timeout: self.client_request_timeout, + client_disconnect_timeout: self.client_disconnect_timeout, secure: self.secure, local_addr: self.local_addr, expect: self.expect, @@ -185,8 +198,8 @@ where { let cfg = ServiceConfig::new( self.keep_alive, - self.client_timeout, - self.client_disconnect, + self.client_request_timeout, + self.client_disconnect_timeout, self.secure, self.local_addr, ); @@ -209,8 +222,8 @@ where { let cfg = ServiceConfig::new( self.keep_alive, - self.client_timeout, - self.client_disconnect, + self.client_request_timeout, + self.client_disconnect_timeout, self.secure, self.local_addr, ); @@ -230,8 +243,8 @@ where { let cfg = ServiceConfig::new( self.keep_alive, - self.client_timeout, - self.client_disconnect, + self.client_request_timeout, + self.client_disconnect_timeout, self.secure, self.local_addr, ); diff --git a/actix-http/src/config.rs b/actix-http/src/config.rs index b6d5a7d51..aa05d6aba 100644 --- a/actix-http/src/config.rs +++ b/actix-http/src/config.rs @@ -1,71 +1,36 @@ use std::{ - cell::Cell, - fmt::{self, Write}, net, rc::Rc, - time::{Duration, SystemTime}, + time::{Duration, Instant}, }; -use actix_rt::{ - task::JoinHandle, - time::{interval, sleep_until, Instant, Sleep}, -}; use bytes::BytesMut; -/// "Sun, 06 Nov 1994 08:49:37 GMT".len() -pub(crate) const DATE_VALUE_LENGTH: usize = 29; +use crate::{date::DateService, KeepAlive}; -#[derive(Debug, PartialEq, Clone, Copy)] -/// Server keep-alive setting -pub enum KeepAlive { - /// Keep alive in seconds - Timeout(usize), - - /// Rely on OS to shutdown tcp connection - Os, - - /// Disabled - Disabled, -} - -impl From for KeepAlive { - fn from(keepalive: usize) -> Self { - KeepAlive::Timeout(keepalive) - } -} - -impl From> for KeepAlive { - fn from(keepalive: Option) -> Self { - if let Some(keepalive) = keepalive { - KeepAlive::Timeout(keepalive) - } else { - KeepAlive::Disabled - } - } -} - -/// Http service configuration +/// HTTP service configuration. +#[derive(Debug, Clone)] pub struct ServiceConfig(Rc); +#[derive(Debug)] struct Inner { - keep_alive: Option, - client_timeout: u64, - client_disconnect: u64, - ka_enabled: bool, + keep_alive: KeepAlive, + client_request_timeout: Duration, + client_disconnect_timeout: Duration, secure: bool, local_addr: Option, date_service: DateService, } -impl Clone for ServiceConfig { - fn clone(&self) -> Self { - ServiceConfig(self.0.clone()) - } -} - impl Default for ServiceConfig { fn default() -> Self { - Self::new(KeepAlive::Timeout(5), 0, 0, false, None) + Self::new( + KeepAlive::default(), + Duration::from_secs(5), + Duration::ZERO, + false, + None, + ) } } @@ -73,34 +38,22 @@ impl ServiceConfig { /// Create instance of `ServiceConfig` pub fn new( keep_alive: KeepAlive, - client_timeout: u64, - client_disconnect: u64, + client_request_timeout: Duration, + client_disconnect_timeout: Duration, secure: bool, local_addr: Option, ) -> ServiceConfig { - let (keep_alive, ka_enabled) = match keep_alive { - KeepAlive::Timeout(val) => (val as u64, true), - KeepAlive::Os => (0, true), - KeepAlive::Disabled => (0, false), - }; - let keep_alive = if ka_enabled && keep_alive > 0 { - Some(Duration::from_secs(keep_alive)) - } else { - None - }; - ServiceConfig(Rc::new(Inner { - keep_alive, - ka_enabled, - client_timeout, - client_disconnect, + keep_alive: keep_alive.normalize(), + client_request_timeout, + client_disconnect_timeout, secure, local_addr, date_service: DateService::new(), })) } - /// Returns true if connection is secure (HTTPS) + /// Returns `true` if connection is secure (i.e., using TLS / HTTPS). #[inline] pub fn secure(&self) -> bool { self.0.secure @@ -114,239 +67,91 @@ impl ServiceConfig { self.0.local_addr } - /// Keep alive duration if configured. + /// Connection keep-alive setting. #[inline] - pub fn keep_alive(&self) -> Option { + pub fn keep_alive(&self) -> KeepAlive { self.0.keep_alive } - /// Return state of connection keep-alive functionality - #[inline] - pub fn keep_alive_enabled(&self) -> bool { - self.0.ka_enabled - } - - /// Client timeout for first request. - #[inline] - pub fn client_timer(&self) -> Option { - let delay_time = self.0.client_timeout; - if delay_time != 0 { - Some(sleep_until(self.now() + Duration::from_millis(delay_time))) - } else { - None + /// Creates a time object representing the deadline for this connection's keep-alive period, if + /// enabled. + /// + /// When [`KeepAlive::Os`] or [`KeepAlive::Disabled`] is set, this will return `None`. + pub fn keep_alive_deadline(&self) -> Option { + match self.keep_alive() { + KeepAlive::Timeout(dur) => Some(self.now() + dur), + KeepAlive::Os => None, + KeepAlive::Disabled => None, } } - /// Client timeout for first request. - pub fn client_timer_expire(&self) -> Option { - let delay = self.0.client_timeout; - if delay != 0 { - Some(self.now() + Duration::from_millis(delay)) - } else { - None - } + /// Creates a time object representing the deadline for the client to finish sending the head of + /// its first request. + /// + /// Returns `None` if this `ServiceConfig was` constructed with `client_request_timeout: 0`. + pub fn client_request_deadline(&self) -> Option { + let timeout = self.0.client_request_timeout; + (timeout != Duration::ZERO).then(|| self.now() + timeout) } - /// Client disconnect timer - pub fn client_disconnect_timer(&self) -> Option { - let delay = self.0.client_disconnect; - if delay != 0 { - Some(self.now() + Duration::from_millis(delay)) - } else { - None - } + /// Creates a time object representing the deadline for the client to disconnect. + pub fn client_disconnect_deadline(&self) -> Option { + let timeout = self.0.client_disconnect_timeout; + (timeout != Duration::ZERO).then(|| self.now() + timeout) } - /// Return keep-alive timer delay is configured. - #[inline] - pub fn keep_alive_timer(&self) -> Option { - self.keep_alive().map(|ka| sleep_until(self.now() + ka)) - } - - /// Keep-alive expire time - pub fn keep_alive_expire(&self) -> Option { - self.keep_alive().map(|ka| self.now() + ka) - } - - #[inline] pub(crate) fn now(&self) -> Instant { self.0.date_service.now() } - #[doc(hidden)] - pub fn set_date(&self, dst: &mut BytesMut, camel_case: bool) { + pub(crate) fn write_date_header(&self, dst: &mut BytesMut, camel_case: bool) { let mut buf: [u8; 39] = [0; 39]; buf[..6].copy_from_slice(if camel_case { b"Date: " } else { b"date: " }); self.0 .date_service - .set_date(|date| buf[6..35].copy_from_slice(&date.bytes)); + .with_date(|date| buf[6..35].copy_from_slice(&date.bytes)); buf[35..].copy_from_slice(b"\r\n\r\n"); dst.extend_from_slice(&buf); } - pub(crate) fn set_date_header(&self, dst: &mut BytesMut) { + pub(crate) fn write_date_header_value(&self, dst: &mut BytesMut) { self.0 .date_service - .set_date(|date| dst.extend_from_slice(&date.bytes)); - } -} - -#[derive(Copy, Clone)] -struct Date { - bytes: [u8; DATE_VALUE_LENGTH], - pos: usize, -} - -impl Date { - fn new() -> Date { - let mut date = Date { - bytes: [0; DATE_VALUE_LENGTH], - pos: 0, - }; - date.update(); - date - } - - fn update(&mut self) { - self.pos = 0; - write!(self, "{}", httpdate::fmt_http_date(SystemTime::now())).unwrap(); - } -} - -impl fmt::Write for Date { - fn write_str(&mut self, s: &str) -> fmt::Result { - let len = s.len(); - self.bytes[self.pos..self.pos + len].copy_from_slice(s.as_bytes()); - self.pos += len; - Ok(()) - } -} - -/// Service for update Date and Instant periodically at 500 millis interval. -struct DateService { - current: Rc>, - handle: JoinHandle<()>, -} - -impl Drop for DateService { - fn drop(&mut self) { - // stop the timer update async task on drop. - self.handle.abort(); - } -} - -impl DateService { - fn new() -> Self { - // shared date and timer for DateService and update async task. - let current = Rc::new(Cell::new((Date::new(), Instant::now()))); - let current_clone = Rc::clone(¤t); - // spawn an async task sleep for 500 milli and update current date/timer in a loop. - // handle is used to stop the task on DateService drop. - let handle = actix_rt::spawn(async move { - #[cfg(test)] - let _notify = notify_on_drop::NotifyOnDrop::new(); - - let mut interval = interval(Duration::from_millis(500)); - loop { - let now = interval.tick().await; - let date = Date::new(); - current_clone.set((date, now)); - } - }); - - DateService { current, handle } - } - - fn now(&self) -> Instant { - self.current.get().1 - } - - fn set_date(&self, mut f: F) { - f(&self.current.get().0); - } -} - -// TODO: move to a util module for testing all spawn handle drop style tasks. -/// Test Module for checking the drop state of certain async tasks that are spawned -/// with `actix_rt::spawn` -/// -/// The target task must explicitly generate `NotifyOnDrop` when spawn the task -#[cfg(test)] -mod notify_on_drop { - use std::cell::RefCell; - - thread_local! { - static NOTIFY_DROPPED: RefCell> = RefCell::new(None); - } - - /// Check if the spawned task is dropped. - /// - /// # Panics - /// Panics when there was no `NotifyOnDrop` instance on current thread. - pub(crate) fn is_dropped() -> bool { - NOTIFY_DROPPED.with(|bool| { - bool.borrow() - .expect("No NotifyOnDrop existed on current thread") - }) - } - - pub(crate) struct NotifyOnDrop; - - impl NotifyOnDrop { - /// # Panic: - /// - /// When construct multiple instances on any given thread. - pub(crate) fn new() -> Self { - NOTIFY_DROPPED.with(|bool| { - let mut bool = bool.borrow_mut(); - if bool.is_some() { - panic!("NotifyOnDrop existed on current thread"); - } else { - *bool = Some(false); - } - }); - - NotifyOnDrop - } - } - - impl Drop for NotifyOnDrop { - fn drop(&mut self) { - NOTIFY_DROPPED.with(|bool| { - if let Some(b) = bool.borrow_mut().as_mut() { - *b = true; - } - }); - } + .with_date(|date| dst.extend_from_slice(&date.bytes)); } } #[cfg(test)] mod tests { use super::*; + use crate::{date::DATE_VALUE_LENGTH, notify_on_drop}; - use actix_rt::{task::yield_now, time::sleep}; + use actix_rt::{ + task::yield_now, + time::{sleep, sleep_until}, + }; use memchr::memmem; #[actix_rt::test] async fn test_date_service_update() { - let settings = ServiceConfig::new(KeepAlive::Os, 0, 0, false, None); + let settings = + ServiceConfig::new(KeepAlive::Os, Duration::ZERO, Duration::ZERO, false, None); yield_now().await; let mut buf1 = BytesMut::with_capacity(DATE_VALUE_LENGTH + 10); - settings.set_date(&mut buf1, false); + settings.write_date_header(&mut buf1, false); let now1 = settings.now(); - sleep_until(Instant::now() + Duration::from_secs(2)).await; + sleep_until((Instant::now() + Duration::from_secs(2)).into()).await; yield_now().await; let now2 = settings.now(); let mut buf2 = BytesMut::with_capacity(DATE_VALUE_LENGTH + 10); - settings.set_date(&mut buf2, false); + settings.write_date_header(&mut buf2, false); assert_ne!(now1, now2); @@ -402,10 +207,10 @@ mod tests { let settings = ServiceConfig::default(); let mut buf1 = BytesMut::with_capacity(DATE_VALUE_LENGTH + 10); - settings.set_date(&mut buf1, false); + settings.write_date_header(&mut buf1, false); let mut buf2 = BytesMut::with_capacity(DATE_VALUE_LENGTH + 10); - settings.set_date(&mut buf2, false); + settings.write_date_header(&mut buf2, false); assert_eq!(buf1, buf2); } @@ -415,11 +220,11 @@ mod tests { let settings = ServiceConfig::default(); let mut buf = BytesMut::with_capacity(DATE_VALUE_LENGTH + 10); - settings.set_date(&mut buf, false); + settings.write_date_header(&mut buf, false); assert!(memmem::find(&buf, b"date:").is_some()); let mut buf = BytesMut::with_capacity(DATE_VALUE_LENGTH + 10); - settings.set_date(&mut buf, true); + settings.write_date_header(&mut buf, true); assert!(memmem::find(&buf, b"Date:").is_some()); } } diff --git a/actix-http/src/date.rs b/actix-http/src/date.rs new file mode 100644 index 000000000..1358bbd8c --- /dev/null +++ b/actix-http/src/date.rs @@ -0,0 +1,92 @@ +use std::{ + cell::Cell, + fmt::{self, Write}, + rc::Rc, + time::{Duration, Instant, SystemTime}, +}; + +use actix_rt::{task::JoinHandle, time::interval}; + +/// "Thu, 01 Jan 1970 00:00:00 GMT".len() +pub(crate) const DATE_VALUE_LENGTH: usize = 29; + +#[derive(Clone, Copy)] +pub(crate) struct Date { + pub(crate) bytes: [u8; DATE_VALUE_LENGTH], + pos: usize, +} + +impl Date { + fn new() -> Date { + let mut date = Date { + bytes: [0; DATE_VALUE_LENGTH], + pos: 0, + }; + date.update(); + date + } + + fn update(&mut self) { + self.pos = 0; + write!(self, "{}", httpdate::fmt_http_date(SystemTime::now())).unwrap(); + } +} + +impl fmt::Write for Date { + fn write_str(&mut self, s: &str) -> fmt::Result { + let len = s.len(); + self.bytes[self.pos..self.pos + len].copy_from_slice(s.as_bytes()); + self.pos += len; + Ok(()) + } +} + +/// Service for update Date and Instant periodically at 500 millis interval. +pub(crate) struct DateService { + current: Rc>, + handle: JoinHandle<()>, +} + +impl DateService { + pub(crate) fn new() -> Self { + // shared date and timer for DateService and update async task. + let current = Rc::new(Cell::new((Date::new(), Instant::now()))); + let current_clone = Rc::clone(¤t); + // spawn an async task sleep for 500 millis and update current date/timer in a loop. + // handle is used to stop the task on DateService drop. + let handle = actix_rt::spawn(async move { + #[cfg(test)] + let _notify = crate::notify_on_drop::NotifyOnDrop::new(); + + let mut interval = interval(Duration::from_millis(500)); + loop { + let now = interval.tick().await; + let date = Date::new(); + current_clone.set((date, now.into_std())); + } + }); + + DateService { current, handle } + } + + pub(crate) fn now(&self) -> Instant { + self.current.get().1 + } + + pub(crate) fn with_date(&self, mut f: F) { + f(&self.current.get().0); + } +} + +impl fmt::Debug for DateService { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("DateService").finish_non_exhaustive() + } +} + +impl Drop for DateService { + fn drop(&mut self) { + // stop the timer update async task on drop. + self.handle.abort(); + } +} diff --git a/actix-http/src/h1/client.rs b/actix-http/src/h1/client.rs index 9bd896ae0..4e0ae8f48 100644 --- a/actix-http/src/h1/client.rs +++ b/actix-http/src/h1/client.rs @@ -1,4 +1,4 @@ -use std::io; +use std::{fmt, io}; use actix_codec::{Decoder, Encoder}; use bitflags::bitflags; @@ -17,9 +17,9 @@ use crate::{ bitflags! { struct Flags: u8 { - const HEAD = 0b0000_0001; - const KEEPALIVE_ENABLED = 0b0000_1000; - const STREAM = 0b0001_0000; + const HEAD = 0b0000_0001; + const KEEP_ALIVE_ENABLED = 0b0000_1000; + const STREAM = 0b0001_0000; } } @@ -38,7 +38,7 @@ struct ClientCodecInner { decoder: decoder::MessageDecoder, payload: Option, version: Version, - ctype: ConnectionType, + conn_type: ConnectionType, // encoder part flags: Flags, @@ -51,23 +51,32 @@ impl Default for ClientCodec { } } +impl fmt::Debug for ClientCodec { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("h1::ClientCodec") + .field("flags", &self.inner.flags) + .finish_non_exhaustive() + } +} + impl ClientCodec { /// Create HTTP/1 codec. /// /// `keepalive_enabled` how response `connection` header get generated. pub fn new(config: ServiceConfig) -> Self { - let flags = if config.keep_alive_enabled() { - Flags::KEEPALIVE_ENABLED + let flags = if config.keep_alive().enabled() { + Flags::KEEP_ALIVE_ENABLED } else { Flags::empty() }; + ClientCodec { inner: ClientCodecInner { config, decoder: decoder::MessageDecoder::default(), payload: None, version: Version::HTTP_11, - ctype: ConnectionType::Close, + conn_type: ConnectionType::Close, flags, encoder: encoder::MessageEncoder::default(), @@ -77,12 +86,12 @@ impl ClientCodec { /// Check if request is upgrade pub fn upgrade(&self) -> bool { - self.inner.ctype == ConnectionType::Upgrade + self.inner.conn_type == ConnectionType::Upgrade } /// Check if last response is keep-alive - pub fn keepalive(&self) -> bool { - self.inner.ctype == ConnectionType::KeepAlive + pub fn keep_alive(&self) -> bool { + self.inner.conn_type == ConnectionType::KeepAlive } /// Check last request's message type @@ -104,8 +113,8 @@ impl ClientCodec { impl ClientPayloadCodec { /// Check if last response is keep-alive - pub fn keepalive(&self) -> bool { - self.inner.ctype == ConnectionType::KeepAlive + pub fn keep_alive(&self) -> bool { + self.inner.conn_type == ConnectionType::KeepAlive } /// Transform payload codec to a message codec @@ -122,12 +131,12 @@ impl Decoder for ClientCodec { debug_assert!(!self.inner.payload.is_some(), "Payload decoder is set"); if let Some((req, payload)) = self.inner.decoder.decode(src)? { - if let Some(ctype) = req.conn_type() { + if let Some(conn_type) = req.conn_type() { // do not use peer's keep-alive - self.inner.ctype = if ctype == ConnectionType::KeepAlive { - self.inner.ctype + self.inner.conn_type = if conn_type == ConnectionType::KeepAlive { + self.inner.conn_type } else { - ctype + conn_type }; } @@ -192,9 +201,9 @@ impl Encoder> for ClientCodec { .set(Flags::HEAD, head.as_ref().method == Method::HEAD); // connection status - inner.ctype = match head.as_ref().connection_type() { + inner.conn_type = match head.as_ref().connection_type() { ConnectionType::KeepAlive => { - if inner.flags.contains(Flags::KEEPALIVE_ENABLED) { + if inner.flags.contains(Flags::KEEP_ALIVE_ENABLED) { ConnectionType::KeepAlive } else { ConnectionType::Close @@ -211,7 +220,7 @@ impl Encoder> for ClientCodec { false, inner.version, length, - inner.ctype, + inner.conn_type, &inner.config, )?; } diff --git a/actix-http/src/h1/codec.rs b/actix-http/src/h1/codec.rs index 9a8907579..df74bcc42 100644 --- a/actix-http/src/h1/codec.rs +++ b/actix-http/src/h1/codec.rs @@ -15,9 +15,9 @@ use crate::{ bitflags! { struct Flags: u8 { - const HEAD = 0b0000_0001; - const KEEPALIVE_ENABLED = 0b0000_0010; - const STREAM = 0b0000_0100; + const HEAD = 0b0000_0001; + const KEEP_ALIVE_ENABLED = 0b0000_0010; + const STREAM = 0b0000_0100; } } @@ -42,7 +42,9 @@ impl Default for Codec { impl fmt::Debug for Codec { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "h1::Codec({:?})", self.flags) + f.debug_struct("h1::Codec") + .field("flags", &self.flags) + .finish_non_exhaustive() } } @@ -51,8 +53,8 @@ impl Codec { /// /// `keepalive_enabled` how response `connection` header get generated. pub fn new(config: ServiceConfig) -> Self { - let flags = if config.keep_alive_enabled() { - Flags::KEEPALIVE_ENABLED + let flags = if config.keep_alive().enabled() { + Flags::KEEP_ALIVE_ENABLED } else { Flags::empty() }; @@ -76,14 +78,14 @@ impl Codec { /// Check if last response is keep-alive. #[inline] - pub fn keepalive(&self) -> bool { + pub fn keep_alive(&self) -> bool { self.conn_type == ConnectionType::KeepAlive } /// Check if keep-alive enabled on server level. #[inline] - pub fn keepalive_enabled(&self) -> bool { - self.flags.contains(Flags::KEEPALIVE_ENABLED) + pub fn keep_alive_enabled(&self) -> bool { + self.flags.contains(Flags::KEEP_ALIVE_ENABLED) } /// Check last request's message type. @@ -124,7 +126,7 @@ impl Decoder for Codec { self.version = head.version; self.conn_type = head.connection_type(); if self.conn_type == ConnectionType::KeepAlive - && !self.flags.contains(Flags::KEEPALIVE_ENABLED) + && !self.flags.contains(Flags::KEEP_ALIVE_ENABLED) { self.conn_type = ConnectionType::Close } @@ -179,9 +181,11 @@ impl Encoder, BodySize)>> for Codec { &self.config, )?; } + Message::Chunk(Some(bytes)) => { self.encoder.encode_chunk(bytes.as_ref(), dst)?; } + Message::Chunk(None) => { self.encoder.encode_eof(dst)?; } diff --git a/actix-http/src/h1/dispatcher.rs b/actix-http/src/h1/dispatcher.rs index 5b790469f..3f327171d 100644 --- a/actix-http/src/h1/dispatcher.rs +++ b/actix-http/src/h1/dispatcher.rs @@ -8,13 +8,12 @@ use std::{ task::{Context, Poll}, }; -use actix_codec::{AsyncRead, AsyncWrite, Decoder, Encoder, Framed, FramedParts}; -use actix_rt::time::{sleep_until, Instant, Sleep}; +use actix_codec::{AsyncRead, AsyncWrite, Decoder as _, Encoder as _, Framed, FramedParts}; +use actix_rt::time::sleep_until; use actix_service::Service; use bitflags::bitflags; use bytes::{Buf, BytesMut}; use futures_core::ready; -use log::{error, trace}; use pin_project_lite::pin_project; use crate::{ @@ -29,6 +28,7 @@ use super::{ codec::Codec, decoder::MAX_BUFFER_SIZE, payload::{Payload, PayloadSender, PayloadStatus}, + timer::TimerState, Message, MessageType, }; @@ -38,11 +38,23 @@ const MAX_PIPELINED_MESSAGES: usize = 16; bitflags! { pub struct Flags: u8 { - const STARTED = 0b0000_0001; - const KEEPALIVE = 0b0000_0010; - const SHUTDOWN = 0b0000_0100; - const READ_DISCONNECT = 0b0000_1000; - const WRITE_DISCONNECT = 0b0001_0000; + /// Set when stream is read for first time. + const STARTED = 0b0000_0001; + + /// Set when full request-response cycle has occurred. + const FINISHED = 0b0000_0010; + + /// Set if connection is in keep-alive (inactive) state. + const KEEP_ALIVE = 0b0000_0100; + + /// Set if in shutdown procedure. + const SHUTDOWN = 0b0000_1000; + + /// Set if read-half is disconnected. + const READ_DISCONNECT = 0b0001_0000; + + /// Set if write-half is disconnected. + const WRITE_DISCONNECT = 0b0010_0000; } } @@ -135,6 +147,7 @@ pin_project! { pub(super) flags: Flags, peer_addr: Option, conn_data: Option>, + config: ServiceConfig, error: Option, #[pin] @@ -142,9 +155,9 @@ pin_project! { payload: Option, messages: VecDeque, - ka_expire: Instant, - #[pin] - ka_timer: Option, + head_timer: TimerState, + ka_timer: TimerState, + shutdown_timer: TimerState, pub(super) io: Option, read_buf: BytesMut, @@ -165,7 +178,6 @@ pin_project! { where S: Service, X: Service, - B: MessageBody, { None, @@ -179,16 +191,40 @@ pin_project! { impl State where S: Service, - X: Service, - B: MessageBody, { - fn is_empty(&self) -> bool { + fn is_none(&self) -> bool { matches!(self, State::None) } } +impl fmt::Debug for State +where + S: Service, + X: Service, + B: MessageBody, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::None => write!(f, "State::None"), + Self::ExpectCall { .. } => { + f.debug_struct("State::ExpectCall").finish_non_exhaustive() + } + Self::ServiceCall { .. } => { + f.debug_struct("State::ServiceCall").finish_non_exhaustive() + } + Self::SendPayload { .. } => { + f.debug_struct("State::SendPayload").finish_non_exhaustive() + } + Self::SendErrorPayload { .. } => f + .debug_struct("State::SendErrorPayload") + .finish_non_exhaustive(), + } + } +} + +#[derive(Debug)] enum PollResponse { Upgrade(Request), DoNothing, @@ -219,33 +255,25 @@ where peer_addr: Option, conn_data: OnConnectData, ) -> Self { - let flags = if config.keep_alive_enabled() { - Flags::KEEPALIVE - } else { - Flags::empty() - }; - - // keep-alive timer - let (ka_expire, ka_timer) = match config.keep_alive_timer() { - Some(delay) => (delay.deadline(), Some(delay)), - None => (config.now(), None), - }; - Dispatcher { inner: DispatcherState::Normal { inner: InnerDispatcher { flow, - flags, + flags: Flags::empty(), peer_addr, conn_data: conn_data.0.map(Rc::new), + config: config.clone(), error: None, state: State::None, payload: None, messages: VecDeque::new(), - ka_expire, - ka_timer, + head_timer: TimerState::new(config.client_request_deadline().is_some()), + ka_timer: TimerState::new(config.keep_alive().enabled()), + shutdown_timer: TimerState::new( + config.client_disconnect_deadline().is_some(), + ), io: Some(io), read_buf: BytesMut::with_capacity(HW_BUFFER_SIZE), @@ -286,11 +314,12 @@ where } } - // if checked is set to true, delay disconnect until all tasks have finished. fn client_disconnected(self: Pin<&mut Self>) { let this = self.project(); + this.flags .insert(Flags::READ_DISCONNECT | Flags::WRITE_DISCONNECT); + if let Some(mut payload) = this.payload.take() { payload.set_error(PayloadError::Incomplete(None)); } @@ -306,9 +335,12 @@ where while written < len { match io.as_mut().poll_write(cx, &write_buf[written..])? { Poll::Ready(0) => { - return Poll::Ready(Err(io::Error::new(io::ErrorKind::WriteZero, ""))) + log::error!("write zero; closing"); + return Poll::Ready(Err(io::Error::new(io::ErrorKind::WriteZero, ""))); } + Poll::Ready(n) => written += n, + Poll::Pending => { write_buf.advance(written); return Poll::Pending; @@ -316,59 +348,70 @@ where } } - // everything has written to io. clear buffer. + // everything has written to I/O; clear buffer write_buf.clear(); - // flush the io and check if get blocked. + // flush the I/O and check if get blocked io.poll_flush(cx) } fn send_response_inner( self: Pin<&mut Self>, - message: Response<()>, + res: Response<()>, body: &impl MessageBody, ) -> Result { - let size = body.size(); let this = self.project(); + + let size = body.size(); + this.codec - .encode(Message::Item((message, size)), this.write_buf) + .encode(Message::Item((res, size)), this.write_buf) .map_err(|err| { if let Some(mut payload) = this.payload.take() { payload.set_error(PayloadError::Incomplete(None)); } + DispatchError::Io(err) })?; - this.flags.set(Flags::KEEPALIVE, this.codec.keepalive()); + this.flags.set(Flags::KEEP_ALIVE, this.codec.keep_alive()); Ok(size) } fn send_response( mut self: Pin<&mut Self>, - message: Response<()>, + res: Response<()>, body: B, ) -> Result<(), DispatchError> { - let size = self.as_mut().send_response_inner(message, &body)?; - let state = match size { - BodySize::None | BodySize::Sized(0) => State::None, + let size = self.as_mut().send_response_inner(res, &body)?; + let mut this = self.project(); + this.state.set(match size { + BodySize::None | BodySize::Sized(0) => { + this.flags.insert(Flags::FINISHED); + State::None + } _ => State::SendPayload { body }, - }; - self.project().state.set(state); + }); + Ok(()) } fn send_error_response( mut self: Pin<&mut Self>, - message: Response<()>, + res: Response<()>, body: BoxBody, ) -> Result<(), DispatchError> { - let size = self.as_mut().send_response_inner(message, &body)?; - let state = match size { - BodySize::None | BodySize::Sized(0) => State::None, + let size = self.as_mut().send_response_inner(res, &body)?; + let mut this = self.project(); + this.state.set(match size { + BodySize::None | BodySize::Sized(0) => { + this.flags.insert(Flags::FINISHED); + State::None + } _ => State::SendErrorPayload { body }, - }; - self.project().state.set(state); + }); + Ok(()) } @@ -385,63 +428,66 @@ where 'res: loop { let mut this = self.as_mut().project(); match this.state.as_mut().project() { - // no future is in InnerDispatcher state. pop next message. + // no future is in InnerDispatcher state; pop next message StateProj::None => match this.messages.pop_front() { - // handle request message. + // handle request message Some(DispatcherMessage::Item(req)) => { // Handle `EXPECT: 100-Continue` header if req.head().expect() { - // set InnerDispatcher state and continue loop to poll it. + // set InnerDispatcher state and continue loop to poll it let fut = this.flow.expect.call(req); this.state.set(State::ExpectCall { fut }); } else { - // the same as expect call. + // set InnerDispatcher state and continue loop to poll it let fut = this.flow.service.call(req); this.state.set(State::ServiceCall { fut }); }; } - // handle error message. + // handle error message Some(DispatcherMessage::Error(res)) => { - // send_response would update InnerDispatcher state to SendPayload or - // None(If response body is empty). - // continue loop to poll it. + // send_response would update InnerDispatcher state to SendPayload or None + // (If response body is empty) + // continue loop to poll it self.as_mut().send_error_response(res, BoxBody::new(()))?; } - // return with upgrade request and poll it exclusively. + // return with upgrade request and poll it exclusively Some(DispatcherMessage::Upgrade(req)) => { - return Ok(PollResponse::Upgrade(req)); + return Ok(PollResponse::Upgrade(req)) } - // all messages are dealt with. + // all messages are dealt with None => return Ok(PollResponse::DoNothing), }, - StateProj::ServiceCall { fut } => match fut.poll(cx) { - // service call resolved. send response. - Poll::Ready(Ok(res)) => { - let (res, body) = res.into().replace_body(()); - self.as_mut().send_response(res, body)?; - } - // send service call error as response - Poll::Ready(Err(err)) => { - let res: Response = err.into(); - let (res, body) = res.replace_body(()); - self.as_mut().send_error_response(res, body)?; - } - - // service call pending and could be waiting for more chunk messages. - // (pipeline message limit and/or payload can_read limit) - Poll::Pending => { - // no new message is decoded and no new payload is feed. - // nothing to do except waiting for new incoming data from client. - if !self.as_mut().poll_request(cx)? { - return Ok(PollResponse::DoNothing); + StateProj::ServiceCall { fut } => { + match fut.poll(cx) { + // service call resolved. send response. + Poll::Ready(Ok(res)) => { + let (res, body) = res.into().replace_body(()); + self.as_mut().send_response(res, body)?; + } + + // send service call error as response + Poll::Ready(Err(err)) => { + let res: Response = err.into(); + let (res, body) = res.replace_body(()); + self.as_mut().send_error_response(res, body)?; + } + + // service call pending and could be waiting for more chunk messages + // (pipeline message limit and/or payload can_read limit) + Poll::Pending => { + // no new message is decoded and no new payload is fed + // nothing to do except waiting for new incoming data from client + if !self.as_mut().poll_request(cx)? { + return Ok(PollResponse::DoNothing); + } + // else loop } - // otherwise keep loop. } - }, + } StateProj::SendPayload { mut body } => { // keep populate writer buffer until buffer size limit hit, @@ -455,21 +501,26 @@ where Poll::Ready(None) => { this.codec.encode(Message::Chunk(None), this.write_buf)?; + // payload stream finished. // set state to None and handle next message this.state.set(State::None); + this.flags.insert(Flags::FINISHED); + continue 'res; } Poll::Ready(Some(Err(err))) => { - return Err(DispatchError::Body(err.into())) + this.flags.insert(Flags::FINISHED); + return Err(DispatchError::Body(err.into())); } Poll::Pending => return Ok(PollResponse::DoNothing), } } - // buffer is beyond max size. - // return and try to write the whole buffer to io stream. + + // buffer is beyond max size + // return and try to write the whole buffer to I/O stream. return Ok(PollResponse::DrainWriteBuf); } @@ -487,46 +538,55 @@ where Poll::Ready(None) => { this.codec.encode(Message::Chunk(None), this.write_buf)?; - // payload stream finished. + + // payload stream finished // set state to None and handle next message this.state.set(State::None); + this.flags.insert(Flags::FINISHED); + continue 'res; } Poll::Ready(Some(Err(err))) => { + this.flags.insert(Flags::FINISHED); return Err(DispatchError::Body( Error::new_body().with_cause(err).into(), - )) + )); } Poll::Pending => return Ok(PollResponse::DoNothing), } } - // buffer is beyond max size. - // return and try to write the whole buffer to io stream. + + // buffer is beyond max size + // return and try to write the whole buffer to stream return Ok(PollResponse::DrainWriteBuf); } - StateProj::ExpectCall { fut } => match fut.poll(cx) { - // expect resolved. write continue to buffer and set InnerDispatcher state - // to service call. - Poll::Ready(Ok(req)) => { - this.write_buf - .extend_from_slice(b"HTTP/1.1 100 Continue\r\n\r\n"); - let fut = this.flow.service.call(req); - this.state.set(State::ServiceCall { fut }); - } + StateProj::ExpectCall { fut } => { + log::trace!(" calling expect service"); - // send expect error as response - Poll::Ready(Err(err)) => { - let res: Response = err.into(); - let (res, body) = res.replace_body(()); - self.as_mut().send_error_response(res, body)?; - } + match fut.poll(cx) { + // expect resolved. write continue to buffer and set InnerDispatcher state + // to service call. + Poll::Ready(Ok(req)) => { + this.write_buf + .extend_from_slice(b"HTTP/1.1 100 Continue\r\n\r\n"); + let fut = this.flow.service.call(req); + this.state.set(State::ServiceCall { fut }); + } - // expect must be solved before progress can be made. - Poll::Pending => return Ok(PollResponse::DoNothing), - }, + // send expect error as response + Poll::Ready(Err(err)) => { + let res: Response = err.into(); + let (res, body) = res.replace_body(()); + self.as_mut().send_error_response(res, body)?; + } + + // expect must be solved before progress can be made. + Poll::Pending => return Ok(PollResponse::DoNothing), + } + } } } } @@ -536,64 +596,76 @@ where req: Request, cx: &mut Context<'_>, ) -> Result<(), DispatchError> { - // Handle `EXPECT: 100-Continue` header - let mut this = self.as_mut().project(); - if req.head().expect() { - // set dispatcher state so the future is pinned. - let fut = this.flow.expect.call(req); - this.state.set(State::ExpectCall { fut }); - } else { - // the same as above. - let fut = this.flow.service.call(req); - this.state.set(State::ServiceCall { fut }); + // initialize dispatcher state + { + let mut this = self.as_mut().project(); + + // Handle `EXPECT: 100-Continue` header + if req.head().expect() { + // set dispatcher state to call expect handler + let fut = this.flow.expect.call(req); + this.state.set(State::ExpectCall { fut }); + } else { + // set dispatcher state to call service handler + let fut = this.flow.service.call(req); + this.state.set(State::ServiceCall { fut }); + }; }; - // eagerly poll the future for once(or twice if expect is resolved immediately). + // eagerly poll the future once (or twice if expect is resolved immediately). loop { match self.as_mut().project().state.project() { StateProj::ExpectCall { fut } => { match fut.poll(cx) { - // expect is resolved. continue loop and poll the service call branch. + // expect is resolved; continue loop and poll the service call branch. Poll::Ready(Ok(req)) => { self.as_mut().send_continue(); + let mut this = self.as_mut().project(); let fut = this.flow.service.call(req); this.state.set(State::ServiceCall { fut }); + continue; } - // future is pending. return Ok(()) to notify that a new state is - // set and the outer loop should be continue. - Poll::Pending => return Ok(()), - // future is error. send response and return a result. On success - // to notify the dispatcher a new state is set and the outer loop - // should be continue. + + // future is error; send response and return a result + // on success to notify the dispatcher a new state is set and the outer loop + // should be continued Poll::Ready(Err(err)) => { let res: Response = err.into(); let (res, body) = res.replace_body(()); return self.send_error_response(res, body); } + + // future is pending; return Ok(()) to notify that a new state is + // set and the outer loop should be continue. + Poll::Pending => return Ok(()), } } + StateProj::ServiceCall { fut } => { // return no matter the service call future's result. return match fut.poll(cx) { - // future is resolved. send response and return a result. On success + // Future is resolved. Send response and return a result. On success // to notify the dispatcher a new state is set and the outer loop // should be continue. Poll::Ready(Ok(res)) => { let (res, body) = res.into().replace_body(()); - self.send_response(res, body) + self.as_mut().send_response(res, body) } - // see the comment on ExpectCall state branch's Pending. + + // see the comment on ExpectCall state branch's Pending Poll::Pending => Ok(()), - // see the comment on ExpectCall state branch's Ready(Err(err)). + + // see the comment on ExpectCall state branch's Ready(Err(_)) Poll::Ready(Err(err)) => { let res: Response = err.into(); let (res, body) = res.replace_body(()); - self.send_error_response(res, body) + self.as_mut().send_error_response(res, body) } }; } + _ => { unreachable!( "State must be set to ServiceCall or ExceptCall in handle_request" @@ -604,72 +676,77 @@ where } /// Process one incoming request. + /// + /// Returns true if any meaningful work was done. fn poll_request( mut self: Pin<&mut Self>, cx: &mut Context<'_>, ) -> Result { + let pipeline_queue_full = self.messages.len() >= MAX_PIPELINED_MESSAGES; + let can_not_read = !self.can_read(cx); + // limit amount of non-processed requests - if self.messages.len() >= MAX_PIPELINED_MESSAGES || !self.can_read(cx) { + if pipeline_queue_full || can_not_read { return Ok(false); } - let mut updated = false; let mut this = self.as_mut().project(); + + let mut updated = false; + loop { match this.codec.decode(this.read_buf) { Ok(Some(msg)) => { updated = true; - this.flags.insert(Flags::STARTED); match msg { Message::Item(mut req) => { + // head timer only applies to first request on connection + this.head_timer.clear(line!()); + req.head_mut().peer_addr = *this.peer_addr; req.conn_data = this.conn_data.as_ref().map(Rc::clone); match this.codec.message_type() { - // Request is upgradable. add upgrade message and break. - // everything remain in read buffer would be handed to + // request has no payload + MessageType::None => {} + + // Request is upgradable. Add upgrade message and break. + // Everything remaining in read buffer will be handed to // upgraded Request. MessageType::Stream if this.flow.upgrade.is_some() => { this.messages.push_back(DispatcherMessage::Upgrade(req)); break; } - // Request is not upgradable. + // request is not upgradable MessageType::Payload | MessageType::Stream => { - /* - PayloadSender and Payload are smart pointers share the - same state. - PayloadSender is attached to dispatcher and used to sink - new chunked request data to state. - Payload is attached to Request and passed to Service::call - where the state can be collected and consumed. - */ + // PayloadSender and Payload are smart pointers share the + // same state. PayloadSender is attached to dispatcher and used + // to sink new chunked request data to state. Payload is + // attached to Request and passed to Service::call where the + // state can be collected and consumed. let (sender, payload) = Payload::create(false); - let (req1, _) = - req.replace_payload(crate::Payload::H1 { payload }); - req = req1; + *req.payload() = crate::Payload::H1 { payload }; *this.payload = Some(sender); } - - // Request has no payload. - MessageType::None => {} } // handle request early when no future in InnerDispatcher state. - if this.state.is_empty() { + if this.state.is_none() { self.as_mut().handle_request(req, cx)?; this = self.as_mut().project(); } else { this.messages.push_back(DispatcherMessage::Item(req)); } } + Message::Chunk(Some(chunk)) => { if let Some(ref mut payload) = this.payload { payload.feed_data(chunk); } else { - error!("Internal server error: unexpected payload chunk"); + log::error!("Internal server error: unexpected payload chunk"); this.flags.insert(Flags::READ_DISCONNECT); this.messages.push_back(DispatcherMessage::Error( Response::internal_server_error().drop_body(), @@ -678,11 +755,12 @@ where break; } } + Message::Chunk(None) => { if let Some(mut payload) = this.payload.take() { payload.feed_eof(); } else { - error!("Internal server error: unexpected eof"); + log::error!("Internal server error: unexpected eof"); this.flags.insert(Flags::READ_DISCONNECT); this.messages.push_back(DispatcherMessage::Error( Response::internal_server_error().drop_body(), @@ -693,38 +771,51 @@ where } } } - // decode is partial and buffer is not full yet. - // break and wait for more read. + + // decode is partial and buffer is not full yet + // break and wait for more read Ok(None) => break, + Err(ParseError::Io(err)) => { + log::trace!("I/O error: {}", &err); self.as_mut().client_disconnected(); this = self.as_mut().project(); *this.error = Some(DispatchError::Io(err)); break; } + Err(ParseError::TooLarge) => { + log::trace!("request head was too big; returning 431 response"); + if let Some(mut payload) = this.payload.take() { payload.set_error(PayloadError::Overflow); } - // Requests overflow buffer size should be responded with 431 + + // request heads that overflow buffer size return a 431 error this.messages .push_back(DispatcherMessage::Error(Response::with_body( StatusCode::REQUEST_HEADER_FIELDS_TOO_LARGE, (), ))); + this.flags.insert(Flags::READ_DISCONNECT); *this.error = Some(ParseError::TooLarge.into()); + break; } + Err(err) => { + log::trace!("parse error {}", &err); + if let Some(mut payload) = this.payload.take() { payload.set_error(PayloadError::EncodingCorrupted); } - // Malformed requests should be responded with 400 + // malformed requests should be responded with 400 this.messages.push_back(DispatcherMessage::Error( Response::bad_request().drop_body(), )); + this.flags.insert(Flags::READ_DISCONNECT); *this.error = Some(err.into()); break; @@ -732,92 +823,115 @@ where } } - if updated && this.ka_timer.is_some() { - if let Some(expire) = this.codec.config().keep_alive_expire() { - *this.ka_expire = expire; - } - } Ok(updated) } - /// keep-alive timer - fn poll_keepalive( + fn poll_head_timer( mut self: Pin<&mut Self>, cx: &mut Context<'_>, ) -> Result<(), DispatchError> { - let mut this = self.as_mut().project(); + let this = self.as_mut().project(); - // when a branch is not explicit return early it's meant to fall through - // and return as Ok(()) - match this.ka_timer.as_mut().as_pin_mut() { - None => { - // conditionally go into shutdown timeout - if this.flags.contains(Flags::SHUTDOWN) { - if let Some(deadline) = this.codec.config().client_disconnect_timer() { - // write client disconnect time out and poll again to - // go into Some> branch - this.ka_timer.set(Some(sleep_until(deadline))); - return self.poll_keepalive(cx); - } - } - } - Some(mut timer) => { - // only operate when keep-alive timer is resolved. - if timer.as_mut().poll(cx).is_ready() { - // got timeout during shutdown, drop connection - if this.flags.contains(Flags::SHUTDOWN) { - return Err(DispatchError::DisconnectTimeout); - // exceed deadline. check for any outstanding tasks - } else if timer.deadline() >= *this.ka_expire { - // have no task at hand. - if this.state.is_empty() && this.write_buf.is_empty() { - if this.flags.contains(Flags::STARTED) { - trace!("Keep-alive timeout, close connection"); - this.flags.insert(Flags::SHUTDOWN); + if let TimerState::Active { timer } = this.head_timer { + if timer.as_mut().poll(cx).is_ready() { + // timeout on first request (slow request) return 408 - // start shutdown timeout - if let Some(deadline) = - this.codec.config().client_disconnect_timer() - { - timer.as_mut().reset(deadline); - let _ = timer.poll(cx); - } else { - // no shutdown timeout, drop socket - this.flags.insert(Flags::WRITE_DISCONNECT); - } - } else { - // timeout on first request (slow request) return 408 - trace!("Slow request timeout"); - let _ = self.as_mut().send_error_response( - Response::with_body(StatusCode::REQUEST_TIMEOUT, ()), - BoxBody::new(()), - ); - this = self.project(); - this.flags.insert(Flags::STARTED | Flags::SHUTDOWN); - } - // still have unfinished task. try to reset and register keep-alive. - } else if let Some(deadline) = this.codec.config().keep_alive_expire() { - timer.as_mut().reset(deadline); - let _ = timer.poll(cx); - } - // timer resolved but still have not met the keep-alive expire deadline. - // reset and register for later wakeup. - } else { - timer.as_mut().reset(*this.ka_expire); - let _ = timer.poll(cx); - } - } + log::trace!( + "timed out on slow request; \ + replying with 408 and closing connection" + ); + + let _ = self.as_mut().send_error_response( + Response::with_body(StatusCode::REQUEST_TIMEOUT, ()), + BoxBody::new(()), + ); + + self.project().flags.insert(Flags::SHUTDOWN); } - } + }; + Ok(()) } - /// Returns true when io stream can be disconnected after write to it. + fn poll_ka_timer( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + ) -> Result<(), DispatchError> { + let this = self.as_mut().project(); + if let TimerState::Active { timer } = this.ka_timer { + debug_assert!( + this.flags.contains(Flags::KEEP_ALIVE), + "keep-alive flag should be set when timer is active", + ); + debug_assert!( + this.state.is_none(), + "dispatcher should not be in keep-alive phase if state is not none: {:?}", + this.state, + ); + debug_assert!( + this.write_buf.is_empty(), + "dispatcher should not be in keep-alive phase if write_buf is not empty", + ); + + // keep-alive timer has timed out + if timer.as_mut().poll(cx).is_ready() { + // no tasks at hand + log::trace!("timer timed out; closing connection"); + this.flags.insert(Flags::SHUTDOWN); + + if let Some(deadline) = this.config.client_disconnect_deadline() { + // start shutdown timeout if enabled + this.shutdown_timer + .set_and_init(cx, sleep_until(deadline.into()), line!()); + } else { + // no shutdown timeout, drop socket + this.flags.insert(Flags::WRITE_DISCONNECT); + } + } + } + + Ok(()) + } + + fn poll_shutdown_timer( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + ) -> Result<(), DispatchError> { + let this = self.as_mut().project(); + if let TimerState::Active { timer } = this.shutdown_timer { + debug_assert!( + this.flags.contains(Flags::SHUTDOWN), + "shutdown flag should be set when timer is active", + ); + + // timed-out during shutdown; drop connection + if timer.as_mut().poll(cx).is_ready() { + log::trace!("timed-out during shutdown"); + return Err(DispatchError::DisconnectTimeout); + } + } + + Ok(()) + } + + /// Poll head, keep-alive, and disconnect timer. + fn poll_timers( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + ) -> Result<(), DispatchError> { + self.as_mut().poll_head_timer(cx)?; + self.as_mut().poll_ka_timer(cx)?; + self.as_mut().poll_shutdown_timer(cx)?; + + Ok(()) + } + + /// Returns true when I/O stream can be disconnected after write to it. /// /// It covers these conditions: - /// - `std::io::ErrorKind::ConnectionReset` after partial read. + /// - `std::io::ErrorKind::ConnectionReset` after partial read; /// - all data read done. - #[inline(always)] + #[inline(always)] // TODO: bench this inline fn read_available( self: Pin<&mut Self>, cx: &mut Context<'_>, @@ -846,13 +960,12 @@ where // When read_buf is beyond max buffer size the early return could be successfully // be parsed as a new Request. This case would not generate ParseError::TooLarge and // at this point IO stream is not fully read to Pending and would result in - // dispatcher stuck until timeout (KA) + // dispatcher stuck until timeout (keep-alive). // // Note: // This is a perf choice to reduce branch on ::decode. // - // A Request head too large to parse is only checked on - // `httparse::Status::Partial` condition. + // A Request head too large to parse is only checked on `httparse::Status::Partial`. if this.payload.is_none() { // When dispatcher has a payload the responsibility of wake up it would be shift @@ -881,18 +994,29 @@ where match actix_codec::poll_read_buf(io.as_mut(), cx, this.read_buf) { Poll::Ready(Ok(n)) => { + this.flags.remove(Flags::FINISHED); + if n == 0 { return Ok(true); } + read_some = true; } - Poll::Pending => return Ok(false), + + Poll::Pending => { + return Ok(false); + } + Poll::Ready(Err(err)) => { return match err.kind() { + // convert WouldBlock error to the same as Pending return io::ErrorKind::WouldBlock => Ok(false), + + // connection reset after partial read io::ErrorKind::ConnectionReset if read_some => Ok(true), + _ => Err(DispatchError::Io(err)), - } + }; } } } @@ -940,27 +1064,60 @@ where } match this.inner.project() { - DispatcherStateProj::Normal { mut inner } => { - inner.as_mut().poll_keepalive(cx)?; + DispatcherStateProj::Upgrade { fut: upgrade } => upgrade.poll(cx).map_err(|err| { + log::error!("Upgrade handler error: {}", err); + DispatchError::Upgrade + }), - if inner.flags.contains(Flags::SHUTDOWN) { + DispatcherStateProj::Normal { mut inner } => { + log::trace!("start flags: {:?}", &inner.flags); + + trace_timer_states( + "start", + &inner.head_timer, + &inner.ka_timer, + &inner.shutdown_timer, + ); + + inner.as_mut().poll_timers(cx)?; + + let poll = if inner.flags.contains(Flags::SHUTDOWN) { if inner.flags.contains(Flags::WRITE_DISCONNECT) { Poll::Ready(Ok(())) } else { - // flush buffer and wait on blocked. + // flush buffer and wait on blocked ready!(inner.as_mut().poll_flush(cx))?; - Pin::new(inner.project().io.as_mut().unwrap()) + Pin::new(inner.as_mut().project().io.as_mut().unwrap()) .poll_shutdown(cx) .map_err(DispatchError::from) } } else { - // read from io stream and fill read buffer. + // read from I/O stream and fill read buffer let should_disconnect = inner.as_mut().read_available(cx)?; + // after reading something from stream, clear keep-alive timer + if !inner.read_buf.is_empty() && inner.flags.contains(Flags::KEEP_ALIVE) { + let inner = inner.as_mut().project(); + inner.flags.remove(Flags::KEEP_ALIVE); + inner.ka_timer.clear(line!()); + } + + if !inner.flags.contains(Flags::STARTED) { + inner.as_mut().project().flags.insert(Flags::STARTED); + + if let Some(deadline) = inner.config.client_request_deadline() { + inner.as_mut().project().head_timer.set_and_init( + cx, + sleep_until(deadline.into()), + line!(), + ); + } + } + inner.as_mut().poll_request(cx)?; - // io stream should to be closed. if should_disconnect { + // I/O stream should to be closed let inner = inner.as_mut().project(); inner.flags.insert(Flags::READ_DISCONNECT); if let Some(mut payload) = inner.payload.take() { @@ -969,11 +1126,27 @@ where }; loop { - // poll_response and populate write buffer. - // drain indicate if write buffer should be emptied before next run. + // poll response to populate write buffer + // drain indicates whether write buffer should be emptied before next run let drain = match inner.as_mut().poll_response(cx)? { PollResponse::DrainWriteBuf => true, - PollResponse::DoNothing => false, + + PollResponse::DoNothing => { + // KEEP_ALIVE is set in send_response_inner if client allows it + // FINISHED is set after writing last chunk of response + if inner.flags.contains(Flags::KEEP_ALIVE | Flags::FINISHED) { + if let Some(timer) = inner.config.keep_alive_deadline() { + inner.as_mut().project().ka_timer.set_and_init( + cx, + sleep_until(timer.into()), + line!(), + ); + } + } + + false + } + // upgrade request and goes Upgrade variant of DispatcherState. PollResponse::Upgrade(req) => { let upgrade = inner.upgrade(req); @@ -985,57 +1158,96 @@ where } }; - // we didn't get WouldBlock from write operation, - // so data get written to kernel completely (macOS) - // and we have to write again otherwise response can get stuck + // we didn't get WouldBlock from write operation, so data get written to + // kernel completely (macOS) and we have to write again otherwise response + // can get stuck // - // TODO: what? is WouldBlock good or bad? - // want to find a reference for this macOS behavior - if inner.as_mut().poll_flush(cx)?.is_pending() || !drain { + // TODO: want to find a reference for this behavior + // see introduced commit: 3872d3ba + let flush_was_ready = inner.as_mut().poll_flush(cx)?.is_ready(); + + // this assert seems to always be true but not willing to commit to it until + // we understand what Nikolay meant when writing the above comment + // debug_assert!(flush_was_ready); + + if !flush_was_ready || !drain { break; } } // client is gone if inner.flags.contains(Flags::WRITE_DISCONNECT) { + log::trace!("client is gone; disconnecting"); return Poll::Ready(Ok(())); } - let is_empty = inner.state.is_empty(); - let inner_p = inner.as_mut().project(); - // read half is closed and we do not processing any responses - if inner_p.flags.contains(Flags::READ_DISCONNECT) && is_empty { + let state_is_none = inner_p.state.is_none(); + + // read half is closed; we do not process any responses + if inner_p.flags.contains(Flags::READ_DISCONNECT) && state_is_none { + log::trace!("read half closed; start shutdown"); inner_p.flags.insert(Flags::SHUTDOWN); } // keep-alive and stream errors - if is_empty && inner_p.write_buf.is_empty() { + if state_is_none && inner_p.write_buf.is_empty() { if let Some(err) = inner_p.error.take() { - Poll::Ready(Err(err)) + log::error!("stream error: {}", &err); + return Poll::Ready(Err(err)); } + // disconnect if keep-alive is not enabled - else if inner_p.flags.contains(Flags::STARTED) - && !inner_p.flags.intersects(Flags::KEEPALIVE) + if inner_p.flags.contains(Flags::FINISHED) + && !inner_p.flags.contains(Flags::KEEP_ALIVE) { + inner_p.flags.remove(Flags::FINISHED); inner_p.flags.insert(Flags::SHUTDOWN); - self.poll(cx) + return self.poll(cx); } + // disconnect if shutdown - else if inner_p.flags.contains(Flags::SHUTDOWN) { - self.poll(cx) - } else { - Poll::Pending + if inner_p.flags.contains(Flags::SHUTDOWN) { + return self.poll(cx); } - } else { - Poll::Pending } - } + + trace_timer_states( + "end", + inner_p.head_timer, + inner_p.ka_timer, + inner_p.shutdown_timer, + ); + + Poll::Pending + }; + + log::trace!("end flags: {:?}", &inner.flags); + + poll } - DispatcherStateProj::Upgrade { fut: upgrade } => upgrade.poll(cx).map_err(|err| { - error!("Upgrade handler error: {}", err); - DispatchError::Upgrade - }), } } } + +#[allow(dead_code)] +fn trace_timer_states( + label: &str, + head_timer: &TimerState, + ka_timer: &TimerState, + shutdown_timer: &TimerState, +) { + log::trace!("{} timers:", label); + + if head_timer.is_enabled() { + log::trace!(" head {}", &head_timer); + } + + if ka_timer.is_enabled() { + log::trace!(" keep-alive {}", &ka_timer); + } + + if shutdown_timer.is_enabled() { + log::trace!(" shutdown {}", &shutdown_timer); + } +} diff --git a/actix-http/src/h1/dispatcher_tests.rs b/actix-http/src/h1/dispatcher_tests.rs index 379019c6f..891cce69c 100644 --- a/actix-http/src/h1/dispatcher_tests.rs +++ b/actix-http/src/h1/dispatcher_tests.rs @@ -17,7 +17,7 @@ use crate::{ h1::{Codec, ExpectHandler, UpgradeHandler}, service::HttpFlow, test::{TestBuffer, TestSeqBuffer}, - Error, HttpMessage, KeepAlive, Method, OnConnectData, Request, Response, + Error, HttpMessage, KeepAlive, Method, OnConnectData, Request, Response, StatusCode, }; fn find_slice(haystack: &[u8], needle: &[u8], from: usize) -> Option { @@ -34,7 +34,13 @@ fn stabilize_date_header(payload: &mut [u8]) { } fn ok_service() -> impl Service, Error = Error> { - fn_service(|_req: Request| ready(Ok::<_, Error>(Response::ok()))) + status_service(StatusCode::OK) +} + +fn status_service( + status: StatusCode, +) -> impl Service, Error = Error> { + fn_service(move |_req: Request| ready(Ok::<_, Error>(Response::new(status)))) } fn echo_path_service( @@ -65,11 +71,15 @@ fn echo_payload_service() -> impl Service, E #[actix_rt::test] async fn late_request() { - let _ = env_logger::try_init(); - let mut buf = TestBuffer::empty(); - let cfg = ServiceConfig::new(KeepAlive::Disabled, 100, 0, false, None); + let cfg = ServiceConfig::new( + KeepAlive::Disabled, + Duration::from_millis(100), + Duration::ZERO, + false, + None, + ); let services = HttpFlow::new(ok_service(), ExpectHandler, None); let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new( @@ -127,10 +137,16 @@ async fn late_request() { } #[actix_rt::test] -async fn test_basic() { +async fn oneshot_connection() { let buf = TestBuffer::new("GET /abcd HTTP/1.1\r\n\r\n"); - let cfg = ServiceConfig::new(KeepAlive::Disabled, 100, 0, false, None); + let cfg = ServiceConfig::new( + KeepAlive::Disabled, + Duration::from_millis(100), + Duration::ZERO, + false, + None, + ); let services = HttpFlow::new(echo_path_service(), ExpectHandler, None); let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new( @@ -179,10 +195,16 @@ async fn test_basic() { } #[actix_rt::test] -async fn test_keep_alive_timeout() { +async fn keep_alive_timeout() { let buf = TestBuffer::new("GET /abcd HTTP/1.1\r\n\r\n"); - let cfg = ServiceConfig::new(KeepAlive::Timeout(1), 100, 0, false, None); + let cfg = ServiceConfig::new( + KeepAlive::Timeout(Duration::from_millis(200)), + Duration::from_millis(100), + Duration::ZERO, + false, + None, + ); let services = HttpFlow::new(echo_path_service(), ExpectHandler, None); let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new( @@ -229,7 +251,7 @@ async fn test_keep_alive_timeout() { .await; // sleep slightly longer than keep-alive timeout - sleep(Duration::from_millis(1100)).await; + sleep(Duration::from_millis(250)).await; lazy(|cx| { assert!( @@ -252,10 +274,16 @@ async fn test_keep_alive_timeout() { } #[actix_rt::test] -async fn test_keep_alive_follow_up_req() { +async fn keep_alive_follow_up_req() { let mut buf = TestBuffer::new("GET /abcd HTTP/1.1\r\n\r\n"); - let cfg = ServiceConfig::new(KeepAlive::Timeout(2), 100, 0, false, None); + let cfg = ServiceConfig::new( + KeepAlive::Timeout(Duration::from_millis(500)), + Duration::from_millis(100), + Duration::ZERO, + false, + None, + ); let services = HttpFlow::new(echo_path_service(), ExpectHandler, None); let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new( @@ -302,7 +330,7 @@ async fn test_keep_alive_follow_up_req() { .await; // sleep for less than KA timeout - sleep(Duration::from_millis(200)).await; + sleep(Duration::from_millis(100)).await; lazy(|cx| { assert!( @@ -371,7 +399,7 @@ async fn test_keep_alive_follow_up_req() { } #[actix_rt::test] -async fn test_req_parse_err() { +async fn req_parse_err() { lazy(|cx| { let buf = TestBuffer::new("GET /test HTTP/1\r\n\r\n"); @@ -413,7 +441,13 @@ async fn pipelining_ok_then_ok() { ", ); - let cfg = ServiceConfig::new(KeepAlive::Disabled, 1, 1, false, None); + let cfg = ServiceConfig::new( + KeepAlive::Disabled, + Duration::from_millis(1), + Duration::from_millis(1), + false, + None, + ); let services = HttpFlow::new(echo_path_service(), ExpectHandler, None); @@ -477,7 +511,13 @@ async fn pipelining_ok_then_bad() { ", ); - let cfg = ServiceConfig::new(KeepAlive::Disabled, 1, 1, false, None); + let cfg = ServiceConfig::new( + KeepAlive::Disabled, + Duration::from_millis(1), + Duration::from_millis(1), + false, + None, + ); let services = HttpFlow::new(echo_path_service(), ExpectHandler, None); @@ -531,10 +571,16 @@ async fn pipelining_ok_then_bad() { } #[actix_rt::test] -async fn test_expect() { +async fn expect_handling() { lazy(|cx| { let mut buf = TestSeqBuffer::empty(); - let cfg = ServiceConfig::new(KeepAlive::Disabled, 0, 0, false, None); + let cfg = ServiceConfig::new( + KeepAlive::Disabled, + Duration::ZERO, + Duration::ZERO, + false, + None, + ); let services = HttpFlow::new(echo_payload_service(), ExpectHandler, None); @@ -562,7 +608,6 @@ async fn test_expect() { // polls: manual assert_eq!(h1.poll_count, 1); - eprintln!("poll count: {}", h1.poll_count); if let DispatcherState::Normal { ref inner } = h1.inner { let io = inner.io.as_ref().unwrap(); @@ -603,10 +648,16 @@ async fn test_expect() { } #[actix_rt::test] -async fn test_eager_expect() { +async fn expect_eager() { lazy(|cx| { let mut buf = TestSeqBuffer::empty(); - let cfg = ServiceConfig::new(KeepAlive::Disabled, 0, 0, false, None); + let cfg = ServiceConfig::new( + KeepAlive::Disabled, + Duration::ZERO, + Duration::ZERO, + false, + None, + ); let services = HttpFlow::new(echo_path_service(), ExpectHandler, None); @@ -663,7 +714,7 @@ async fn test_eager_expect() { } #[actix_rt::test] -async fn test_upgrade() { +async fn upgrade_handling() { struct TestUpgrade; impl Service<(Request, Framed)> for TestUpgrade { @@ -683,7 +734,13 @@ async fn test_upgrade() { lazy(|cx| { let mut buf = TestSeqBuffer::empty(); - let cfg = ServiceConfig::new(KeepAlive::Disabled, 0, 0, false, None); + let cfg = ServiceConfig::new( + KeepAlive::Disabled, + Duration::ZERO, + Duration::ZERO, + false, + None, + ); let services = HttpFlow::new(ok_service(), ExpectHandler, Some(TestUpgrade)); diff --git a/actix-http/src/h1/encoder.rs b/actix-http/src/h1/encoder.rs index 5fcb2f688..a24ba5911 100644 --- a/actix-http/src/h1/encoder.rs +++ b/actix-http/src/h1/encoder.rs @@ -212,7 +212,7 @@ pub(crate) trait MessageType: Sized { // optimized date header, set_date writes \r\n if !has_date { - config.set_date(dst, camel_case); + config.write_date_header(dst, camel_case); } else { // msg eof dst.extend_from_slice(b"\r\n"); @@ -318,16 +318,17 @@ impl MessageType for RequestHeadType { } impl MessageEncoder { - /// Encode message + /// Encode chunk. pub fn encode_chunk(&mut self, msg: &[u8], buf: &mut BytesMut) -> io::Result { self.te.encode(msg, buf) } - /// Encode eof + /// Encode EOF. pub fn encode_eof(&mut self, buf: &mut BytesMut) -> io::Result<()> { self.te.encode_eof(buf) } + /// Encode message. pub fn encode( &mut self, dst: &mut BytesMut, diff --git a/actix-http/src/h1/mod.rs b/actix-http/src/h1/mod.rs index 8c569165d..858cf542a 100644 --- a/actix-http/src/h1/mod.rs +++ b/actix-http/src/h1/mod.rs @@ -13,6 +13,7 @@ mod encoder; mod expect; mod payload; mod service; +mod timer; mod upgrade; mod utils; @@ -28,9 +29,10 @@ pub use self::utils::SendResponse; #[derive(Debug)] /// Codec message pub enum Message { - /// Http message + /// HTTP message. Item(T), - /// Payload chunk + + /// Payload chunk. Chunk(Option), } diff --git a/actix-http/src/h1/timer.rs b/actix-http/src/h1/timer.rs new file mode 100644 index 000000000..bb69fdb80 --- /dev/null +++ b/actix-http/src/h1/timer.rs @@ -0,0 +1,80 @@ +use std::{fmt, future::Future, pin::Pin, task::Context}; + +use actix_rt::time::{Instant, Sleep}; + +#[derive(Debug)] +pub(super) enum TimerState { + Disabled, + Inactive, + Active { timer: Pin> }, +} + +impl TimerState { + pub(super) fn new(enabled: bool) -> Self { + if enabled { + Self::Inactive + } else { + Self::Disabled + } + } + + pub(super) fn is_enabled(&self) -> bool { + matches!(self, Self::Active { .. } | Self::Inactive) + } + + pub(super) fn set(&mut self, timer: Sleep, line: u32) { + if matches!(self, Self::Disabled) { + log::trace!("setting disabled timer from line {}", line); + } + + *self = Self::Active { + timer: Box::pin(timer), + }; + } + + pub(super) fn set_and_init(&mut self, cx: &mut Context<'_>, timer: Sleep, line: u32) { + self.set(timer, line); + self.init(cx); + } + + pub(super) fn clear(&mut self, line: u32) { + if matches!(self, Self::Disabled) { + log::trace!("trying to clear a disabled timer from line {}", line); + } + + if matches!(self, Self::Inactive) { + log::trace!("trying to clear an inactive timer from line {}", line); + } + + *self = Self::Inactive; + } + + pub(super) fn init(&mut self, cx: &mut Context<'_>) { + if let TimerState::Active { timer } = self { + let _ = timer.as_mut().poll(cx); + } + } +} + +impl fmt::Display for TimerState { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + TimerState::Disabled => f.write_str("timer is disabled"), + TimerState::Inactive => f.write_str("timer is inactive"), + TimerState::Active { timer } => { + let deadline = timer.deadline(); + let now = Instant::now(); + + if deadline < now { + f.write_str("timer is active and has reached deadline") + } else { + write!( + f, + "timer is active and due to expire in {} milliseconds", + ((deadline - now).as_secs_f32() * 1000.0) + ) + } + } + } + } +} diff --git a/actix-http/src/h2/dispatcher.rs b/actix-http/src/h2/dispatcher.rs index a90eb3466..7a11f9b33 100644 --- a/actix-http/src/h2/dispatcher.rs +++ b/actix-http/src/h2/dispatcher.rs @@ -57,11 +57,11 @@ where conn_data: OnConnectData, timer: Option>>, ) -> Self { - let ping_pong = config.keep_alive().map(|dur| H2PingPong { + let ping_pong = config.keep_alive().duration().map(|dur| H2PingPong { timer: timer .map(|mut timer| { - // reset timer if it's received from new function. - timer.as_mut().reset(config.now() + dur); + // reuse timer slot if it was initialized for handshake + timer.as_mut().reset((config.now() + dur).into()); timer }) .unwrap_or_else(|| Box::pin(sleep(dur))), @@ -160,8 +160,8 @@ where Poll::Ready(_) => { ping_pong.on_flight = false; - let dead_line = this.config.keep_alive_expire().unwrap(); - ping_pong.timer.as_mut().reset(dead_line); + let dead_line = this.config.keep_alive_deadline().unwrap(); + ping_pong.timer.as_mut().reset(dead_line.into()); } Poll::Pending => { return ping_pong.timer.as_mut().poll(cx).map(|_| Ok(())) @@ -174,8 +174,8 @@ where ping_pong.ping_pong.send_ping(Ping::opaque())?; - let dead_line = this.config.keep_alive_expire().unwrap(); - ping_pong.timer.as_mut().reset(dead_line); + let dead_line = this.config.keep_alive_deadline().unwrap(); + ping_pong.timer.as_mut().reset(dead_line.into()); ping_pong.on_flight = true; } @@ -322,7 +322,7 @@ fn prepare_response( // set date header if !has_date { let mut bytes = BytesMut::with_capacity(29); - config.set_date_header(&mut bytes); + config.write_date_header_value(&mut bytes); res.headers_mut().insert( DATE, // SAFETY: serialized date-times are known ASCII strings diff --git a/actix-http/src/h2/mod.rs b/actix-http/src/h2/mod.rs index 47d51b420..c8aaaaa5f 100644 --- a/actix-http/src/h2/mod.rs +++ b/actix-http/src/h2/mod.rs @@ -7,7 +7,7 @@ use std::{ }; use actix_codec::{AsyncRead, AsyncWrite}; -use actix_rt::time::Sleep; +use actix_rt::time::{sleep_until, Sleep}; use bytes::Bytes; use futures_core::{ready, Stream}; use h2::{ @@ -15,17 +15,17 @@ use h2::{ RecvStream, }; +use crate::{ + config::ServiceConfig, + error::{DispatchError, PayloadError}, +}; + mod dispatcher; mod service; pub use self::dispatcher::Dispatcher; pub use self::service::H2Service; -use crate::{ - config::ServiceConfig, - error::{DispatchError, PayloadError}, -}; - /// HTTP/2 peer stream. pub struct Payload { stream: RecvStream, @@ -67,7 +67,9 @@ where { HandshakeWithTimeout { handshake: handshake(io), - timer: config.client_timer().map(Box::pin), + timer: config + .client_request_deadline() + .map(|deadline| Box::pin(sleep_until(deadline.into()))), } } @@ -86,7 +88,7 @@ where let this = self.get_mut(); match Pin::new(&mut this.handshake).poll(cx)? { - // return the timer on success handshake. It can be re-used for h2 ping-pong. + // return the timer on success handshake; its slot can be re-used for h2 ping-pong Poll::Ready(conn) => Poll::Ready(Ok((conn, this.timer.take()))), Poll::Pending => match this.timer.as_mut() { Some(timer) => { diff --git a/actix-http/src/header/map.rs b/actix-http/src/header/map.rs index 33fb262c4..8f6d1cead 100644 --- a/actix-http/src/header/map.rs +++ b/actix-http/src/header/map.rs @@ -630,7 +630,7 @@ impl Removed { /// Returns true if iterator contains no elements, without consuming it. /// /// If called immediately after [`HeaderMap::insert`] or [`HeaderMap::remove`], it will indicate - /// wether any items were actually replaced or removed, respectively. + /// whether any items were actually replaced or removed, respectively. pub fn is_empty(&self) -> bool { match self.inner { // size hint lower bound of smallvec is the correct length diff --git a/actix-http/src/header/shared/http_date.rs b/actix-http/src/header/shared/http_date.rs index 473d6cad0..21ed49f0c 100644 --- a/actix-http/src/header/shared/http_date.rs +++ b/actix-http/src/header/shared/http_date.rs @@ -4,8 +4,7 @@ use bytes::BytesMut; use http::header::{HeaderValue, InvalidHeaderValue}; use crate::{ - config::DATE_VALUE_LENGTH, error::ParseError, header::TryIntoHeaderValue, - helpers::MutWriter, + date::DATE_VALUE_LENGTH, error::ParseError, header::TryIntoHeaderValue, helpers::MutWriter, }; /// A timestamp with HTTP-style formatting and parsing. diff --git a/actix-http/src/keep_alive.rs b/actix-http/src/keep_alive.rs new file mode 100644 index 000000000..27161614d --- /dev/null +++ b/actix-http/src/keep_alive.rs @@ -0,0 +1,83 @@ +use std::time::Duration; + +/// Connection keep-alive config. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum KeepAlive { + /// Keep-alive duration. + /// + /// `KeepAlive::Timeout(Duration::ZERO)` is mapped to `KeepAlive::Disabled`. + Timeout(Duration), + + /// Rely on OS to shutdown TCP connection. + /// + /// Some defaults can be very long, check your OS documentation. + Os, + + /// Keep-alive is disabled. + /// + /// Connections will be closed immediately. + Disabled, +} + +impl KeepAlive { + pub(crate) fn enabled(&self) -> bool { + !matches!(self, Self::Disabled) + } + + pub(crate) fn duration(&self) -> Option { + match self { + KeepAlive::Timeout(dur) => Some(*dur), + _ => None, + } + } + + /// Map zero duration to disabled. + pub(crate) fn normalize(self) -> KeepAlive { + match self { + KeepAlive::Timeout(Duration::ZERO) => KeepAlive::Disabled, + ka => ka, + } + } +} + +impl Default for KeepAlive { + fn default() -> Self { + Self::Timeout(Duration::from_secs(5)) + } +} + +impl From for KeepAlive { + fn from(dur: Duration) -> Self { + KeepAlive::Timeout(dur).normalize() + } +} + +impl From> for KeepAlive { + fn from(ka_dur: Option) -> Self { + match ka_dur { + Some(dur) => KeepAlive::from(dur), + None => KeepAlive::Disabled, + } + .normalize() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn from_impls() { + let test: KeepAlive = Duration::from_secs(1).into(); + assert_eq!(test, KeepAlive::Timeout(Duration::from_secs(1))); + + let test: KeepAlive = Duration::from_secs(0).into(); + assert_eq!(test, KeepAlive::Disabled); + + let test: KeepAlive = Some(Duration::from_secs(0)).into(); + assert_eq!(test, KeepAlive::Disabled); + + let test: KeepAlive = None.into(); + assert_eq!(test, KeepAlive::Disabled); + } +} diff --git a/actix-http/src/lib.rs b/actix-http/src/lib.rs index f2b415790..c8c7d55c9 100644 --- a/actix-http/src/lib.rs +++ b/actix-http/src/lib.rs @@ -33,6 +33,7 @@ pub use ::http::{Method, StatusCode, Version}; pub mod body; mod builder; mod config; +mod date; #[cfg(feature = "__compress")] pub mod encoding; pub mod error; @@ -42,7 +43,10 @@ pub mod h2; pub mod header; mod helpers; mod http_message; +mod keep_alive; mod message; +#[cfg(test)] +mod notify_on_drop; mod payload; mod requests; mod responses; @@ -51,11 +55,12 @@ pub mod test; pub mod ws; pub use self::builder::HttpServiceBuilder; -pub use self::config::{KeepAlive, ServiceConfig}; +pub use self::config::ServiceConfig; pub use self::error::Error; pub use self::extensions::Extensions; pub use self::header::ContentEncoding; pub use self::http_message::HttpMessage; +pub use self::keep_alive::KeepAlive; pub use self::message::ConnectionType; pub use self::message::Message; #[allow(deprecated)] diff --git a/actix-http/src/notify_on_drop.rs b/actix-http/src/notify_on_drop.rs new file mode 100644 index 000000000..98544bb5d --- /dev/null +++ b/actix-http/src/notify_on_drop.rs @@ -0,0 +1,49 @@ +/// Test Module for checking the drop state of certain async tasks that are spawned +/// with `actix_rt::spawn` +/// +/// The target task must explicitly generate `NotifyOnDrop` when spawn the task +use std::cell::RefCell; + +thread_local! { + static NOTIFY_DROPPED: RefCell> = RefCell::new(None); +} + +/// Check if the spawned task is dropped. +/// +/// # Panics +/// Panics when there was no `NotifyOnDrop` instance on current thread. +pub(crate) fn is_dropped() -> bool { + NOTIFY_DROPPED.with(|bool| { + bool.borrow() + .expect("No NotifyOnDrop existed on current thread") + }) +} + +pub(crate) struct NotifyOnDrop; + +impl NotifyOnDrop { + /// # Panics + /// Panics hen construct multiple instances on any given thread. + pub(crate) fn new() -> Self { + NOTIFY_DROPPED.with(|bool| { + let mut bool = bool.borrow_mut(); + if bool.is_some() { + panic!("NotifyOnDrop existed on current thread"); + } else { + *bool = Some(false); + } + }); + + NotifyOnDrop + } +} + +impl Drop for NotifyOnDrop { + fn drop(&mut self) { + NOTIFY_DROPPED.with(|bool| { + if let Some(b) = bool.borrow_mut().as_mut() { + *b = true; + } + }); + } +} diff --git a/actix-http/src/requests/head.rs b/actix-http/src/requests/head.rs index 06fd0429e..4558801f3 100644 --- a/actix-http/src/requests/head.rs +++ b/actix-http/src/requests/head.rs @@ -130,8 +130,8 @@ impl RequestHead { } } + /// Request contains `EXPECT` header. #[inline] - /// Request contains `EXPECT` header pub fn expect(&self) -> bool { self.flags.contains(Flags::EXPECT) } diff --git a/actix-http/src/responses/head.rs b/actix-http/src/responses/head.rs index 870073ab3..cb47c4b7a 100644 --- a/actix-http/src/responses/head.rs +++ b/actix-http/src/responses/head.rs @@ -42,7 +42,7 @@ impl ResponseHead { &mut self.headers } - /// Sets the flag that controls wether to send headers formatted as Camel-Case. + /// Sets the flag that controls whether to send headers formatted as Camel-Case. /// /// Only applicable to HTTP/1.x responses; HTTP/2 header names are always lowercase. #[inline] @@ -210,14 +210,15 @@ mod tests { use memchr::memmem; use crate::{ + h1::H1Service, header::{HeaderName, HeaderValue}, - Error, HttpService, Request, Response, + Error, Request, Response, ServiceConfig, }; #[actix_rt::test] async fn camel_case_headers() { let mut srv = actix_http_test::test_server(|| { - HttpService::new(|req: Request| async move { + H1Service::with_config(ServiceConfig::default(), |req: Request| async move { let mut res = Response::ok(); if req.path().contains("camel") { @@ -228,6 +229,7 @@ mod tests { HeaderName::from_static("foo-bar"), HeaderValue::from_static("baz"), ); + Ok::<_, Error>(res) }) .tcp() @@ -235,9 +237,11 @@ mod tests { .await; let mut stream = net::TcpStream::connect(srv.addr()).unwrap(); - let _ = stream.write_all(b"GET /camel HTTP/1.1\r\nConnection: Close\r\n\r\n"); - let mut data = vec![0; 1024]; - let _ = stream.read(&mut data); + let _ = stream + .write_all(b"GET /camel HTTP/1.1\r\nConnection: Close\r\n\r\n") + .unwrap(); + let mut data = vec![]; + let _ = stream.read_to_end(&mut data).unwrap(); assert_eq!(&data[..17], b"HTTP/1.1 200 OK\r\n"); assert!(memmem::find(&data, b"Foo-Bar").is_some()); assert!(memmem::find(&data, b"foo-bar").is_none()); @@ -247,9 +251,11 @@ mod tests { assert!(memmem::find(&data, b"content-length").is_none()); let mut stream = net::TcpStream::connect(srv.addr()).unwrap(); - let _ = stream.write_all(b"GET /lower HTTP/1.1\r\nConnection: Close\r\n\r\n"); - let mut data = vec![0; 1024]; - let _ = stream.read(&mut data); + let _ = stream + .write_all(b"GET /lower HTTP/1.1\r\nConnection: Close\r\n\r\n") + .unwrap(); + let mut data = vec![]; + let _ = stream.read_to_end(&mut data).unwrap(); assert_eq!(&data[..17], b"HTTP/1.1 200 OK\r\n"); assert!(memmem::find(&data, b"Foo-Bar").is_none()); assert!(memmem::find(&data, b"foo-bar").is_some()); diff --git a/actix-http/src/service.rs b/actix-http/src/service.rs index cd2efe678..4fe573aa5 100644 --- a/actix-http/src/service.rs +++ b/actix-http/src/service.rs @@ -19,9 +19,8 @@ use pin_project_lite::pin_project; use crate::{ body::{BoxBody, MessageBody}, builder::HttpServiceBuilder, - config::{KeepAlive, ServiceConfig}, error::DispatchError, - h1, h2, ConnectCallback, OnConnectData, Protocol, Request, Response, + h1, h2, ConnectCallback, OnConnectData, Protocol, Request, Response, ServiceConfig, }; /// A `ServiceFactory` for HTTP/1.1 or HTTP/2 protocol. @@ -43,9 +42,9 @@ where >::Future: 'static, B: MessageBody + 'static, { - /// Create builder for `HttpService` instance. + /// Constructs builder for `HttpService` instance. pub fn build() -> HttpServiceBuilder { - HttpServiceBuilder::new() + HttpServiceBuilder::default() } } @@ -58,12 +57,10 @@ where >::Future: 'static, B: MessageBody + 'static, { - /// Create new `HttpService` instance. + /// Constructs new `HttpService` instance from service with default config. pub fn new>(service: F) -> Self { - let cfg = ServiceConfig::new(KeepAlive::Timeout(5), 5000, 0, false, None); - HttpService { - cfg, + cfg: ServiceConfig::default(), srv: service.into_factory(), expect: h1::ExpectHandler, upgrade: None, @@ -72,7 +69,7 @@ where } } - /// Create new `HttpService` instance with config. + /// Constructs new `HttpService` instance from config and service. pub(crate) fn with_config>( cfg: ServiceConfig, service: F, @@ -97,11 +94,10 @@ where >::Future: 'static, B: MessageBody, { - /// Provide service for `EXPECT: 100-Continue` support. + /// Sets service for `Expect: 100-Continue` handling. /// - /// Service get called with request that contains `EXPECT` header. - /// Service must return request in case of success, in that case - /// request will be forwarded to main service. + /// An expect service is called with requests that contain an `Expect` header. A successful + /// response type is also a request which will be forwarded to the main service. pub fn expect(self, expect: X1) -> HttpService where X1: ServiceFactory, @@ -118,10 +114,10 @@ where } } - /// Provide service for custom `Connection: UPGRADE` support. + /// Sets service for custom `Connection: Upgrade` handling. /// - /// If service is provided then normal requests handling get halted - /// and this service get called with original request and framed object. + /// If service is provided then normal requests handling get halted and this service get called + /// with original request and framed object. pub fn upgrade(self, upgrade: Option) -> HttpService where U1: ServiceFactory<(Request, Framed), Config = (), Response = ()>, diff --git a/actix-http/src/test.rs b/actix-http/src/test.rs index 0d4d342ec..6212c19d1 100644 --- a/actix-http/src/test.rs +++ b/actix-http/src/test.rs @@ -242,7 +242,7 @@ impl io::Read for TestBuffer { impl io::Write for TestBuffer { fn write(&mut self, buf: &[u8]) -> io::Result { - RefCell::borrow_mut(&self.write_buf).extend(buf); + self.write_buf.borrow_mut().extend(buf); Ok(buf.len()) } diff --git a/actix-http/tests/test_client.rs b/actix-http/tests/test_client.rs index a3adcdfd6..5888527f1 100644 --- a/actix-http/tests/test_client.rs +++ b/actix-http/tests/test_client.rs @@ -31,7 +31,7 @@ const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ Hello World Hello World Hello World Hello World Hello World"; #[actix_rt::test] -async fn test_h1_v2() { +async fn h1_v2() { let srv = test_server(move || { HttpService::build() .finish(|_| future::ok::<_, Infallible>(Response::ok().set_body(STR))) @@ -59,7 +59,7 @@ async fn test_h1_v2() { } #[actix_rt::test] -async fn test_connection_close() { +async fn connection_close() { let srv = test_server(move || { HttpService::build() .finish(|_| future::ok::<_, Infallible>(Response::ok().set_body(STR))) @@ -73,7 +73,7 @@ async fn test_connection_close() { } #[actix_rt::test] -async fn test_with_query_parameter() { +async fn with_query_parameter() { let srv = test_server(move || { HttpService::build() .finish(|req: Request| async move { @@ -104,7 +104,7 @@ impl From for Response { } #[actix_rt::test] -async fn test_h1_expect() { +async fn h1_expect() { let srv = test_server(move || { HttpService::build() .expect(|req: Request| async { diff --git a/actix-http/tests/test_h2_timer.rs b/actix-http/tests/test_h2_timer.rs index 2b9c26e4a..2e1480297 100644 --- a/actix-http/tests/test_h2_timer.rs +++ b/actix-http/tests/test_h2_timer.rs @@ -1,4 +1,4 @@ -use std::io; +use std::{io, time::Duration}; use actix_http::{error::Error, HttpService, Response}; use actix_server::Server; @@ -19,7 +19,7 @@ async fn h2_ping_pong() -> io::Result<()> { .workers(1) .listen("h2_ping_pong", lst, || { HttpService::build() - .keep_alive(3) + .keep_alive(Duration::from_secs(3)) .h2(|_| async { Ok::<_, Error>(Response::ok()) }) .tcp() })? @@ -92,10 +92,10 @@ async fn h2_handshake_timeout() -> io::Result<()> { .workers(1) .listen("h2_ping_pong", lst, || { HttpService::build() - .keep_alive(30) + .keep_alive(Duration::from_secs(30)) // set first request timeout to 5 seconds. // this is the timeout used for http2 handshake. - .client_timeout(5000) + .client_request_timeout(Duration::from_secs(5)) .h2(|_| async { Ok::<_, Error>(Response::ok()) }) .tcp() })? diff --git a/actix-http/tests/test_server.rs b/actix-http/tests/test_server.rs index 1bb574fd6..1b5de3425 100644 --- a/actix-http/tests/test_server.rs +++ b/actix-http/tests/test_server.rs @@ -2,7 +2,7 @@ use std::{ convert::Infallible, io::{Read, Write}, net, thread, - time::Duration, + time::{Duration, Instant}, }; use actix_http::{ @@ -22,12 +22,12 @@ use futures_util::{ use regex::Regex; #[actix_rt::test] -async fn test_h1() { +async fn h1_basic() { let mut srv = test_server(|| { HttpService::build() .keep_alive(KeepAlive::Disabled) - .client_timeout(1000) - .client_disconnect(1000) + .client_request_timeout(Duration::from_secs(1)) + .client_disconnect_timeout(Duration::from_secs(1)) .h1(|req: Request| { assert!(req.peer_addr().is_some()); ok::<_, Infallible>(Response::ok()) @@ -43,12 +43,12 @@ async fn test_h1() { } #[actix_rt::test] -async fn test_h1_2() { +async fn h1_2() { let mut srv = test_server(|| { HttpService::build() .keep_alive(KeepAlive::Disabled) - .client_timeout(1000) - .client_disconnect(1000) + .client_request_timeout(Duration::from_secs(1)) + .client_disconnect_timeout(Duration::from_secs(1)) .finish(|req: Request| { assert!(req.peer_addr().is_some()); assert_eq!(req.version(), http::Version::HTTP_11); @@ -75,7 +75,7 @@ impl From for Response { } #[actix_rt::test] -async fn test_expect_continue() { +async fn expect_continue() { let mut srv = test_server(|| { HttpService::build() .expect(fn_service(|req: Request| { @@ -106,7 +106,7 @@ async fn test_expect_continue() { } #[actix_rt::test] -async fn test_expect_continue_h1() { +async fn expect_continue_h1() { let mut srv = test_server(|| { HttpService::build() .expect(fn_service(|req: Request| { @@ -139,7 +139,7 @@ async fn test_expect_continue_h1() { } #[actix_rt::test] -async fn test_chunked_payload() { +async fn chunked_payload() { let chunk_sizes = vec![32768, 32, 32768]; let total_size: usize = chunk_sizes.iter().sum(); @@ -197,26 +197,43 @@ async fn test_chunked_payload() { } #[actix_rt::test] -async fn test_slow_request() { +async fn slow_request_408() { let mut srv = test_server(|| { HttpService::build() - .client_timeout(100) + .client_request_timeout(Duration::from_millis(200)) + .keep_alive(Duration::from_secs(2)) .finish(|_| ok::<_, Infallible>(Response::ok())) .tcp() }) .await; + let start = Instant::now(); + let mut stream = net::TcpStream::connect(srv.addr()).unwrap(); - let _ = stream.write_all(b"GET /test/tests/test HTTP/1.1\r\n"); + let _ = stream.write_all(b"GET /test HTTP/1.1\r\n"); let mut data = String::new(); let _ = stream.read_to_string(&mut data); - assert!(data.starts_with("HTTP/1.1 408 Request Timeout")); + assert!( + data.starts_with("HTTP/1.1 408 Request Timeout"), + "response was not 408: {}", + data + ); + + let diff = start.elapsed(); + + if diff < Duration::from_secs(1) { + // test success + } else if diff < Duration::from_secs(3) { + panic!("request seems to have wrongly timed-out according to keep-alive"); + } else { + panic!("request took way too long to time out"); + } srv.stop().await; } #[actix_rt::test] -async fn test_http1_malformed_request() { +async fn http1_malformed_request() { let mut srv = test_server(|| { HttpService::build() .h1(|_| ok::<_, Infallible>(Response::ok())) @@ -234,7 +251,7 @@ async fn test_http1_malformed_request() { } #[actix_rt::test] -async fn test_http1_keepalive() { +async fn http1_keepalive() { let mut srv = test_server(|| { HttpService::build() .h1(|_| ok::<_, Infallible>(Response::ok())) @@ -257,23 +274,25 @@ async fn test_http1_keepalive() { } #[actix_rt::test] -async fn test_http1_keepalive_timeout() { +async fn http1_keepalive_timeout() { let mut srv = test_server(|| { HttpService::build() - .keep_alive(1) + .keep_alive(Duration::from_secs(1)) .h1(|_| ok::<_, Infallible>(Response::ok())) .tcp() }) .await; let mut stream = net::TcpStream::connect(srv.addr()).unwrap(); - let _ = stream.write_all(b"GET /test/tests/test HTTP/1.1\r\n\r\n"); - let mut data = vec![0; 1024]; + + let _ = stream.write_all(b"GET /test HTTP/1.1\r\n\r\n"); + let mut data = vec![0; 256]; let _ = stream.read(&mut data); assert_eq!(&data[..17], b"HTTP/1.1 200 OK\r\n"); + thread::sleep(Duration::from_millis(1100)); - let mut data = vec![0; 1024]; + let mut data = vec![0; 256]; let res = stream.read(&mut data).unwrap(); assert_eq!(res, 0); @@ -281,7 +300,7 @@ async fn test_http1_keepalive_timeout() { } #[actix_rt::test] -async fn test_http1_keepalive_close() { +async fn http1_keepalive_close() { let mut srv = test_server(|| { HttpService::build() .h1(|_| ok::<_, Infallible>(Response::ok())) @@ -303,7 +322,7 @@ async fn test_http1_keepalive_close() { } #[actix_rt::test] -async fn test_http10_keepalive_default_close() { +async fn http10_keepalive_default_close() { let mut srv = test_server(|| { HttpService::build() .h1(|_| ok::<_, Infallible>(Response::ok())) @@ -325,7 +344,7 @@ async fn test_http10_keepalive_default_close() { } #[actix_rt::test] -async fn test_http10_keepalive() { +async fn http10_keepalive() { let mut srv = test_server(|| { HttpService::build() .h1(|_| ok::<_, Infallible>(Response::ok())) @@ -354,7 +373,7 @@ async fn test_http10_keepalive() { } #[actix_rt::test] -async fn test_http1_keepalive_disabled() { +async fn http1_keepalive_disabled() { let mut srv = test_server(|| { HttpService::build() .keep_alive(KeepAlive::Disabled) @@ -377,7 +396,7 @@ async fn test_http1_keepalive_disabled() { } #[actix_rt::test] -async fn test_content_length() { +async fn content_length() { use actix_http::{ header::{HeaderName, HeaderValue}, StatusCode, @@ -426,7 +445,7 @@ async fn test_content_length() { } #[actix_rt::test] -async fn test_h1_headers() { +async fn h1_headers() { let data = STR.repeat(10); let data2 = data.clone(); @@ -492,7 +511,7 @@ const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ Hello World Hello World Hello World Hello World Hello World"; #[actix_rt::test] -async fn test_h1_body() { +async fn h1_body() { let mut srv = test_server(|| { HttpService::build() .h1(|_| ok::<_, Infallible>(Response::ok().set_body(STR))) @@ -511,7 +530,7 @@ async fn test_h1_body() { } #[actix_rt::test] -async fn test_h1_head_empty() { +async fn h1_head_empty() { let mut srv = test_server(|| { HttpService::build() .h1(|_| ok::<_, Infallible>(Response::ok().set_body(STR))) @@ -538,7 +557,7 @@ async fn test_h1_head_empty() { } #[actix_rt::test] -async fn test_h1_head_binary() { +async fn h1_head_binary() { let mut srv = test_server(|| { HttpService::build() .h1(|_| ok::<_, Infallible>(Response::ok().set_body(STR))) @@ -565,7 +584,7 @@ async fn test_h1_head_binary() { } #[actix_rt::test] -async fn test_h1_head_binary2() { +async fn h1_head_binary2() { let mut srv = test_server(|| { HttpService::build() .h1(|_| ok::<_, Infallible>(Response::ok().set_body(STR))) @@ -588,7 +607,7 @@ async fn test_h1_head_binary2() { } #[actix_rt::test] -async fn test_h1_body_length() { +async fn h1_body_length() { let mut srv = test_server(|| { HttpService::build() .h1(|_| { @@ -612,7 +631,7 @@ async fn test_h1_body_length() { } #[actix_rt::test] -async fn test_h1_body_chunked_explicit() { +async fn h1_body_chunked_explicit() { let mut srv = test_server(|| { HttpService::build() .h1(|_| { @@ -649,7 +668,7 @@ async fn test_h1_body_chunked_explicit() { } #[actix_rt::test] -async fn test_h1_body_chunked_implicit() { +async fn h1_body_chunked_implicit() { let mut srv = test_server(|| { HttpService::build() .h1(|_| { @@ -680,7 +699,7 @@ async fn test_h1_body_chunked_implicit() { } #[actix_rt::test] -async fn test_h1_response_http_error_handling() { +async fn h1_response_http_error_handling() { let mut srv = test_server(|| { HttpService::build() .h1(fn_service(|_| { @@ -719,7 +738,7 @@ impl From for Response { } #[actix_rt::test] -async fn test_h1_service_error() { +async fn h1_service_error() { let mut srv = test_server(|| { HttpService::build() .h1(|_| err::, _>(BadRequest)) @@ -738,7 +757,7 @@ async fn test_h1_service_error() { } #[actix_rt::test] -async fn test_h1_on_connect() { +async fn h1_on_connect() { let mut srv = test_server(|| { HttpService::build() .on_connect_ext(|_, data| { @@ -761,7 +780,7 @@ async fn test_h1_on_connect() { /// Tests compliance with 304 Not Modified spec in RFC 7232 ยง4.1. /// https://datatracker.ietf.org/doc/html/rfc7232#section-4.1 #[actix_rt::test] -async fn test_not_modified_spec_h1() { +async fn not_modified_spec_h1() { // TODO: this test needing a few seconds to complete reveals some weirdness with either the // dispatcher or the client, though similar hangs occur on other tests in this file, only // succeeding, it seems, because of the keepalive timer diff --git a/actix-http/tests/test_ws.rs b/actix-http/tests/test_ws.rs index ed8c61fd6..8b3ab8e1b 100644 --- a/actix-http/tests/test_ws.rs +++ b/actix-http/tests/test_ws.rs @@ -109,7 +109,7 @@ async fn service(msg: Frame) -> Result { } #[actix_rt::test] -async fn test_simple() { +async fn simple() { let mut srv = test_server(|| { HttpService::build() .upgrade(fn_factory(|| async { diff --git a/actix-test/CHANGES.md b/actix-test/CHANGES.md index 32ab2344f..3877f4fbf 100644 --- a/actix-test/CHANGES.md +++ b/actix-test/CHANGES.md @@ -1,6 +1,9 @@ # Changes ## Unreleased - 2021-xx-xx +- Rename `TestServerConfig::{client_timeout => client_request_timeout}`. [#2611] + +[#2611]: https://github.com/actix/actix-web/pull/2611 ## 0.1.0-beta.11 - 2022-01-04 diff --git a/actix-test/src/lib.rs b/actix-test/src/lib.rs index f86120f2f..d44bc7a45 100644 --- a/actix-test/src/lib.rs +++ b/actix-test/src/lib.rs @@ -149,7 +149,7 @@ where let local_addr = tcp.local_addr().unwrap(); let factory = factory.clone(); let srv_cfg = cfg.clone(); - let timeout = cfg.client_timeout; + let timeout = cfg.client_request_timeout; let builder = Server::build().workers(1).disable_signals().system_exit(); @@ -167,7 +167,7 @@ where .map_err(|err| err.into().error_response()); HttpService::build() - .client_timeout(timeout) + .client_request_timeout(timeout) .h1(map_config(fac, move |_| app_cfg.clone())) .tcp() }), @@ -183,7 +183,7 @@ where .map_err(|err| err.into().error_response()); HttpService::build() - .client_timeout(timeout) + .client_request_timeout(timeout) .h2(map_config(fac, move |_| app_cfg.clone())) .tcp() }), @@ -199,7 +199,7 @@ where .map_err(|err| err.into().error_response()); HttpService::build() - .client_timeout(timeout) + .client_request_timeout(timeout) .finish(map_config(fac, move |_| app_cfg.clone())) .tcp() }), @@ -218,7 +218,7 @@ where .map_err(|err| err.into().error_response()); HttpService::build() - .client_timeout(timeout) + .client_request_timeout(timeout) .h1(map_config(fac, move |_| app_cfg.clone())) .openssl(acceptor.clone()) }), @@ -234,7 +234,7 @@ where .map_err(|err| err.into().error_response()); HttpService::build() - .client_timeout(timeout) + .client_request_timeout(timeout) .h2(map_config(fac, move |_| app_cfg.clone())) .openssl(acceptor.clone()) }), @@ -250,7 +250,7 @@ where .map_err(|err| err.into().error_response()); HttpService::build() - .client_timeout(timeout) + .client_request_timeout(timeout) .finish(map_config(fac, move |_| app_cfg.clone())) .openssl(acceptor.clone()) }), @@ -269,7 +269,7 @@ where .map_err(|err| err.into().error_response()); HttpService::build() - .client_timeout(timeout) + .client_request_timeout(timeout) .h1(map_config(fac, move |_| app_cfg.clone())) .rustls(config.clone()) }), @@ -285,7 +285,7 @@ where .map_err(|err| err.into().error_response()); HttpService::build() - .client_timeout(timeout) + .client_request_timeout(timeout) .h2(map_config(fac, move |_| app_cfg.clone())) .rustls(config.clone()) }), @@ -301,7 +301,7 @@ where .map_err(|err| err.into().error_response()); HttpService::build() - .client_timeout(timeout) + .client_request_timeout(timeout) .finish(map_config(fac, move |_| app_cfg.clone())) .rustls(config.clone()) }), @@ -388,7 +388,7 @@ pub fn config() -> TestServerConfig { pub struct TestServerConfig { tp: HttpVer, stream: StreamType, - client_timeout: u64, + client_request_timeout: Duration, } impl Default for TestServerConfig { @@ -403,7 +403,7 @@ impl TestServerConfig { TestServerConfig { tp: HttpVer::Both, stream: StreamType::Tcp, - client_timeout: 5000, + client_request_timeout: Duration::from_secs(5), } } @@ -433,9 +433,9 @@ impl TestServerConfig { self } - /// Set client timeout in milliseconds for first request. - pub fn client_timeout(mut self, val: u64) -> Self { - self.client_timeout = val; + /// Set client timeout for first request. + pub fn client_request_timeout(mut self, dur: Duration) -> Self { + self.client_request_timeout = dur; self } } diff --git a/awc/src/client/h1proto.rs b/awc/src/client/h1proto.rs index cf716db72..4f6a87ac5 100644 --- a/awc/src/client/h1proto.rs +++ b/awc/src/client/h1proto.rs @@ -70,7 +70,7 @@ where let is_expect = if head.as_ref().headers.contains_key(EXPECT) { match body.size() { BodySize::None | BodySize::Sized(0) => { - let keep_alive = framed.codec_ref().keepalive(); + let keep_alive = framed.codec_ref().keep_alive(); framed.io_mut().on_release(keep_alive); // TODO: use a new variant or a new type better describing error violate @@ -119,7 +119,7 @@ where match pin_framed.codec_ref().message_type() { h1::MessageType::None => { - let keep_alive = pin_framed.codec_ref().keepalive(); + let keep_alive = pin_framed.codec_ref().keep_alive(); pin_framed.io_mut().on_release(keep_alive); Ok((head, Payload::None)) @@ -223,7 +223,7 @@ impl Stream for PlStream { match ready!(this.framed.as_mut().next_item(cx)?) { Some(Some(chunk)) => Poll::Ready(Some(Ok(chunk))), Some(None) => { - let keep_alive = this.framed.codec_ref().keepalive(); + let keep_alive = this.framed.codec_ref().keep_alive(); this.framed.io_mut().on_release(keep_alive); Poll::Ready(None) } diff --git a/src/server.rs b/src/server.rs index ed0c965b3..83e025fb0 100644 --- a/src/server.rs +++ b/src/server.rs @@ -4,6 +4,7 @@ use std::{ marker::PhantomData, net, sync::{Arc, Mutex}, + time::Duration, }; use actix_http::{body::MessageBody, Extensions, HttpService, KeepAlive, Request, Response}; @@ -27,8 +28,8 @@ struct Socket { struct Config { host: Option, keep_alive: KeepAlive, - client_timeout: u64, - client_shutdown: u64, + client_request_timeout: Duration, + client_disconnect_timeout: Duration, } /// An HTTP Server. @@ -88,9 +89,9 @@ where factory, config: Arc::new(Mutex::new(Config { host: None, - keep_alive: KeepAlive::Timeout(5), - client_timeout: 5000, - client_shutdown: 5000, + keep_alive: KeepAlive::default(), + client_request_timeout: Duration::from_secs(5), + client_disconnect_timeout: Duration::from_secs(1), })), backlog: 1024, sockets: Vec::new(), @@ -200,11 +201,17 @@ where /// To disable timeout set value to 0. /// /// By default client timeout is set to 5000 milliseconds. - pub fn client_timeout(self, val: u64) -> Self { - self.config.lock().unwrap().client_timeout = val; + pub fn client_request_timeout(self, dur: Duration) -> Self { + self.config.lock().unwrap().client_request_timeout = dur; self } + #[doc(hidden)] + #[deprecated(since = "4.0.0", note = "Renamed to `client_request_timeout`.")] + pub fn client_timeout(self, dur: Duration) -> Self { + self.client_request_timeout(dur) + } + /// Set server connection shutdown timeout in milliseconds. /// /// Defines a timeout for shutdown connection. If a shutdown procedure does not complete @@ -213,11 +220,17 @@ where /// To disable timeout set value to 0. /// /// By default client timeout is set to 5000 milliseconds. - pub fn client_shutdown(self, val: u64) -> Self { - self.config.lock().unwrap().client_shutdown = val; + pub fn client_disconnect_timeout(self, dur: Duration) -> Self { + self.config.lock().unwrap().client_disconnect_timeout = dur; self } + #[doc(hidden)] + #[deprecated(since = "4.0.0", note = "Renamed to `client_request_timeout`.")] + pub fn client_shutdown(self, dur: u64) -> Self { + self.client_disconnect_timeout(Duration::from_millis(dur)) + } + /// Set server host name. /// /// Host name is used by application router as a hostname for url generation. @@ -291,8 +304,8 @@ where let mut svc = HttpService::build() .keep_alive(c.keep_alive) - .client_timeout(c.client_timeout) - .client_disconnect(c.client_shutdown) + .client_request_timeout(c.client_request_timeout) + .client_disconnect_timeout(c.client_disconnect_timeout) .local_addr(addr); if let Some(handler) = on_connect_fn.clone() { @@ -349,8 +362,8 @@ where let svc = HttpService::build() .keep_alive(c.keep_alive) - .client_timeout(c.client_timeout) - .client_disconnect(c.client_shutdown) + .client_request_timeout(c.client_request_timeout) + .client_disconnect_timeout(c.client_disconnect_timeout) .local_addr(addr); let svc = if let Some(handler) = on_connect_fn.clone() { @@ -410,8 +423,8 @@ where let svc = HttpService::build() .keep_alive(c.keep_alive) - .client_timeout(c.client_timeout) - .client_disconnect(c.client_shutdown); + .client_request_timeout(c.client_request_timeout) + .client_disconnect_timeout(c.client_disconnect_timeout); let svc = if let Some(handler) = on_connect_fn.clone() { svc.on_connect_ext(move |io: &_, ext: _| (handler)(io as &dyn Any, ext)) @@ -537,8 +550,8 @@ where fn_service(|io: UnixStream| async { Ok((io, Protocol::Http1, None)) }).and_then({ let mut svc = HttpService::build() .keep_alive(c.keep_alive) - .client_timeout(c.client_timeout) - .client_disconnect(c.client_shutdown); + .client_request_timeout(c.client_request_timeout) + .client_disconnect_timeout(c.client_disconnect_timeout); if let Some(handler) = on_connect_fn.clone() { svc = svc @@ -593,8 +606,8 @@ where fn_service(|io: UnixStream| async { Ok((io, Protocol::Http1, None)) }).and_then( HttpService::build() .keep_alive(c.keep_alive) - .client_timeout(c.client_timeout) - .client_disconnect(c.client_shutdown) + .client_request_timeout(c.client_request_timeout) + .client_disconnect_timeout(c.client_disconnect_timeout) .finish(map_config(fac, move |_| config.clone())), ) }, diff --git a/tests/test_httpserver.rs b/tests/test_httpserver.rs index 6ea8e520c..86e0575f3 100644 --- a/tests/test_httpserver.rs +++ b/tests/test_httpserver.rs @@ -26,9 +26,9 @@ async fn test_start() { .backlog(1) .max_connections(10) .max_connection_rate(10) - .keep_alive(10) - .client_timeout(5000) - .client_shutdown(0) + .keep_alive(Duration::from_secs(10)) + .client_request_timeout(Duration::from_secs(5)) + .client_disconnect_timeout(Duration::ZERO) .server_hostname("localhost") .system_exit() .disable_signals() diff --git a/tests/test_server.rs b/tests/test_server.rs index b8193a004..bd8934061 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -8,6 +8,7 @@ use std::{ io::{Read, Write}, pin::Pin, task::{Context, Poll}, + time::Duration, }; use actix_web::{ @@ -835,9 +836,10 @@ async fn test_server_cookies() { async fn test_slow_request() { use std::net; - let srv = actix_test::start_with(actix_test::config().client_timeout(200), || { - App::new().service(web::resource("/").route(web::to(HttpResponse::Ok))) - }); + let srv = actix_test::start_with( + actix_test::config().client_request_timeout(Duration::from_millis(200)), + || App::new().service(web::resource("/").route(web::to(HttpResponse::Ok))), + ); let mut stream = net::TcpStream::connect(srv.addr()).unwrap(); let mut data = String::new(); From cd511affd5e7f7c9c2883a82757c5e4e1791e64b Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Mon, 31 Jan 2022 21:22:23 +0000 Subject: [PATCH 012/170] add ws and http2 feature flags (#2618) --- Cargo.toml | 2 +- actix-http/CHANGES.md | 3 ++ actix-http/Cargo.toml | 47 ++++++++++++++++++---------- actix-http/src/builder.rs | 7 +++-- actix-http/src/config.rs | 1 + actix-http/src/encoding/encoder.rs | 8 ++--- actix-http/src/error.rs | 26 +++++++++++----- actix-http/src/h2/dispatcher.rs | 2 +- actix-http/src/h2/service.rs | 2 +- actix-http/src/http_message.rs | 2 +- actix-http/src/keep_alive.rs | 1 + actix-http/src/lib.rs | 5 ++- actix-http/src/payload.rs | 21 +++++++++++-- actix-http/src/service.rs | 50 ++++++++++++++++++++++++++---- actix-http/src/ws/codec.rs | 10 +++--- actix-http/src/ws/dispatcher.rs | 8 +++-- actix-http/src/ws/frame.rs | 8 +++-- awc/Cargo.toml | 2 +- 18 files changed, 148 insertions(+), 57 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 99ff85e8d..38c8512bc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -80,7 +80,7 @@ actix-service = "2.0.0" actix-utils = "3.0.0" actix-tls = { version = "3.0.0", default-features = false, optional = true } -actix-http = "3.0.0-beta.19" +actix-http = { version = "3.0.0-beta.19", features = ["http2", "ws"] } actix-router = "0.5.0-rc.2" actix-web-codegen = "0.5.0-rc.1" diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index a748bc43f..38bec78ba 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -6,6 +6,8 @@ - Implement `From` for `KeepAlive`. [#2611] - Implement `From>` for `KeepAlive`. [#2611] - Implement `Default` for `HttpServiceBuilder`. [#2611] +- Crate `ws` feature flag, disabled by default. [#2618] +- Crate `http2` feature flag, disabled by default. [#2618] ### Changed - Rename `ServiceConfig::{client_timer_expire => client_request_deadline}`. [#2611] @@ -27,6 +29,7 @@ - `HttpServiceBuilder::new`; use `default` instead. [#2611] [#2611]: https://github.com/actix/actix-web/pull/2611 +[#2618]: https://github.com/actix/actix-web/pull/2618 ## 3.0.0-beta.19 - 2022-01-21 diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 11bfa7a1a..f68eda074 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -29,54 +29,69 @@ path = "src/lib.rs" [features] default = [] -# openssl +# HTTP/2 protocol support +http2 = ["h2"] + +# WebSocket protocol implementation +ws = [ + "local-channel", + "base64", + "rand", + "sha-1", +] + +# TLS via OpenSSL openssl = ["actix-tls/accept", "actix-tls/openssl"] -# rustls support +# TLS via Rustls rustls = ["actix-tls/accept", "actix-tls/rustls"] -# enable compression support -compress-brotli = ["brotli", "__compress"] -compress-gzip = ["flate2", "__compress"] -compress-zstd = ["zstd", "__compress"] +# Compression codecs +compress-brotli = ["__compress", "brotli"] +compress-gzip = ["__compress", "flate2"] +compress-zstd = ["__compress", "zstd"] # Internal (PRIVATE!) features used to aid testing and cheking feature status. -# Don't rely on these whatsoever. They may disappear at anytime. +# Don't rely on these whatsoever. They are semver-exempt and may disappear at anytime. __compress = [] [dependencies] -actix-service = "2.0.0" +actix-service = "2" actix-codec = "0.4.1" -actix-utils = "3.0.0" +actix-utils = "3" actix-rt = { version = "2.2", default-features = false } ahash = "0.7" -base64 = "0.13" bitflags = "1.2" bytes = "1" bytestring = "1" derive_more = "0.99.5" encoding_rs = "0.8" futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] } -h2 = "0.3.9" http = "0.2.5" httparse = "1.5.1" httpdate = "1.0.1" itoa = "1" language-tags = "0.3" -local-channel = "0.1" log = "0.4" mime = "0.3" percent-encoding = "2.1" pin-project-lite = "0.2" -rand = "0.8" -sha-1 = "0.10" smallvec = "1.6.1" -# tls +# http2 +h2 = { version = "0.3.9", optional = true } + +# websockets +local-channel = { version = "0.1", optional = true } +base64 = { version = "0.13", optional = true } +rand = { version = "0.8", optional = true } +sha-1 = { version = "0.10", optional = true } + +# openssl/rustls actix-tls = { version = "3.0.0", default-features = false, optional = true } -# compression +# compress-* brotli = { version = "3.3.3", optional = true } flate2 = { version = "1.0.13", optional = true } zstd = { version = "0.9", optional = true } diff --git a/actix-http/src/builder.rs b/actix-http/src/builder.rs index 9dd145ce1..526a23d53 100644 --- a/actix-http/src/builder.rs +++ b/actix-http/src/builder.rs @@ -6,7 +6,6 @@ use actix_service::{IntoServiceFactory, Service, ServiceFactory}; use crate::{ body::{BoxBody, MessageBody}, h1::{self, ExpectHandler, H1Service, UpgradeHandler}, - h2::H2Service, service::HttpService, ConnectCallback, Extensions, KeepAlive, Request, Response, ServiceConfig, }; @@ -211,7 +210,8 @@ where } /// Finish service configuration and create a HTTP service for HTTP/2 protocol. - pub fn h2(self, service: F) -> H2Service + #[cfg(feature = "http2")] + pub fn h2(self, service: F) -> crate::h2::H2Service where F: IntoServiceFactory, S::Error: Into> + 'static, @@ -228,7 +228,8 @@ where self.local_addr, ); - H2Service::with_config(cfg, service.into_factory()).on_connect_ext(self.on_connect_ext) + crate::h2::H2Service::with_config(cfg, service.into_factory()) + .on_connect_ext(self.on_connect_ext) } /// Finish service configuration and create `HttpService` instance. diff --git a/actix-http/src/config.rs b/actix-http/src/config.rs index aa05d6aba..8045910be 100644 --- a/actix-http/src/config.rs +++ b/actix-http/src/config.rs @@ -117,6 +117,7 @@ impl ServiceConfig { dst.extend_from_slice(&buf); } + #[allow(unused)] // used with `http2` feature flag pub(crate) fn write_date_header_value(&self, dst: &mut BytesMut) { self.0 .date_service diff --git a/actix-http/src/encoding/encoder.rs b/actix-http/src/encoding/encoder.rs index 116fe76ab..2f104ee8f 100644 --- a/actix-http/src/encoding/encoder.rs +++ b/actix-http/src/encoding/encoder.rs @@ -352,7 +352,7 @@ impl ContentEncoder { ContentEncoder::Brotli(ref mut encoder) => match encoder.write_all(data) { Ok(_) => Ok(()), Err(err) => { - trace!("Error decoding br encoding: {}", err); + log::trace!("Error decoding br encoding: {}", err); Err(err) } }, @@ -361,7 +361,7 @@ impl ContentEncoder { ContentEncoder::Gzip(ref mut encoder) => match encoder.write_all(data) { Ok(_) => Ok(()), Err(err) => { - trace!("Error decoding gzip encoding: {}", err); + log::trace!("Error decoding gzip encoding: {}", err); Err(err) } }, @@ -370,7 +370,7 @@ impl ContentEncoder { ContentEncoder::Deflate(ref mut encoder) => match encoder.write_all(data) { Ok(_) => Ok(()), Err(err) => { - trace!("Error decoding deflate encoding: {}", err); + log::trace!("Error decoding deflate encoding: {}", err); Err(err) } }, @@ -379,7 +379,7 @@ impl ContentEncoder { ContentEncoder::Zstd(ref mut encoder) => match encoder.write_all(data) { Ok(_) => Ok(()), Err(err) => { - trace!("Error decoding ztsd encoding: {}", err); + log::trace!("Error decoding ztsd encoding: {}", err); Err(err) } }, diff --git a/actix-http/src/error.rs b/actix-http/src/error.rs index df6d3813a..841322861 100644 --- a/actix-http/src/error.rs +++ b/actix-http/src/error.rs @@ -5,7 +5,7 @@ use std::{error::Error as StdError, fmt, io, str::Utf8Error, string::FromUtf8Err use derive_more::{Display, Error, From}; use http::{uri::InvalidUri, StatusCode}; -use crate::{body::BoxBody, ws, Response}; +use crate::{body::BoxBody, Response}; pub use http::Error as HttpError; @@ -61,6 +61,7 @@ impl Error { Self::new(Kind::Encoder) } + #[allow(unused)] // used with `ws` feature flag pub(crate) fn new_ws() -> Self { Self::new(Kind::Ws) } @@ -139,14 +140,16 @@ impl From for Error { } } -impl From for Error { - fn from(err: ws::HandshakeError) -> Self { +#[cfg(feature = "ws")] +impl From for Error { + fn from(err: crate::ws::HandshakeError) -> Self { Self::new_ws().with_cause(err) } } -impl From for Error { - fn from(err: ws::ProtocolError) -> Self { +#[cfg(feature = "ws")] +impl From for Error { + fn from(err: crate::ws::ProtocolError) -> Self { Self::new_ws().with_cause(err) } } @@ -277,8 +280,9 @@ pub enum PayloadError { UnknownLength, /// HTTP/2 payload error. + #[cfg(feature = "http2")] #[display(fmt = "{}", _0)] - Http2Payload(h2::Error), + Http2Payload(::h2::Error), /// Generic I/O error. #[display(fmt = "{}", _0)] @@ -293,14 +297,16 @@ impl std::error::Error for PayloadError { PayloadError::EncodingCorrupted => None, PayloadError::Overflow => None, PayloadError::UnknownLength => None, + #[cfg(feature = "http2")] PayloadError::Http2Payload(err) => Some(err as &dyn std::error::Error), PayloadError::Io(err) => Some(err as &dyn std::error::Error), } } } -impl From for PayloadError { - fn from(err: h2::Error) -> Self { +#[cfg(feature = "http2")] +impl From<::h2::Error> for PayloadError { + fn from(err: ::h2::Error) -> Self { PayloadError::Http2Payload(err) } } @@ -356,6 +362,7 @@ pub enum DispatchError { /// HTTP/2 error. #[display(fmt = "{}", _0)] + #[cfg(feature = "http2")] H2(h2::Error), /// The first request did not complete within the specified timeout. @@ -379,7 +386,10 @@ impl StdError for DispatchError { DispatchError::Body(err) => Some(&**err), DispatchError::Io(err) => Some(err), DispatchError::Parse(err) => Some(err), + + #[cfg(feature = "http2")] DispatchError::H2(err) => Some(err), + _ => None, } } diff --git a/actix-http/src/h2/dispatcher.rs b/actix-http/src/h2/dispatcher.rs index 7a11f9b33..d528bec96 100644 --- a/actix-http/src/h2/dispatcher.rs +++ b/actix-http/src/h2/dispatcher.rs @@ -141,7 +141,7 @@ where DispatchError::SendResponse(err) => { trace!("Error sending HTTP/2 response: {:?}", err) } - DispatchError::SendData(err) => warn!("{:?}", err), + DispatchError::SendData(err) => log::warn!("{:?}", err), DispatchError::ResponseBody(err) => { error!("Response payload stream error: {:?}", err) } diff --git a/actix-http/src/h2/service.rs b/actix-http/src/h2/service.rs index 469648054..653982d37 100644 --- a/actix-http/src/h2/service.rs +++ b/actix-http/src/h2/service.rs @@ -355,7 +355,7 @@ where } Err(err) => { - trace!("H2 handshake error: {}", err); + log::trace!("H2 handshake error: {}", err); Poll::Ready(Err(err)) } }, diff --git a/actix-http/src/http_message.rs b/actix-http/src/http_message.rs index 068e23b96..198254e02 100644 --- a/actix-http/src/http_message.rs +++ b/actix-http/src/http_message.rs @@ -55,7 +55,7 @@ pub trait HttpMessage: Sized { "" } - /// Get content type encoding + /// Get content type encoding. /// /// UTF-8 is used by default, If request charset is not set. fn encoding(&self) -> Result<&'static Encoding, ContentTypeError> { diff --git a/actix-http/src/keep_alive.rs b/actix-http/src/keep_alive.rs index 27161614d..feb7ff5df 100644 --- a/actix-http/src/keep_alive.rs +++ b/actix-http/src/keep_alive.rs @@ -24,6 +24,7 @@ impl KeepAlive { !matches!(self, Self::Disabled) } + #[allow(unused)] // used with `http2` feature flag pub(crate) fn duration(&self) -> Option { match self { KeepAlive::Timeout(dur) => Some(*dur), diff --git a/actix-http/src/lib.rs b/actix-http/src/lib.rs index c8c7d55c9..dbff89612 100644 --- a/actix-http/src/lib.rs +++ b/actix-http/src/lib.rs @@ -24,9 +24,6 @@ #![doc(html_logo_url = "https://actix.rs/img/logo.png")] #![doc(html_favicon_url = "https://actix.rs/favicon.ico")] -#[macro_use] -extern crate log; - pub use ::http::{uri, uri::Uri}; pub use ::http::{Method, StatusCode, Version}; @@ -39,6 +36,7 @@ pub mod encoding; pub mod error; mod extensions; pub mod h1; +#[cfg(feature = "http2")] pub mod h2; pub mod header; mod helpers; @@ -52,6 +50,7 @@ mod requests; mod responses; mod service; pub mod test; +#[cfg(feature = "ws")] pub mod ws; pub use self::builder::HttpServiceBuilder; diff --git a/actix-http/src/payload.rs b/actix-http/src/payload.rs index aed24e963..33d9ec6f5 100644 --- a/actix-http/src/payload.rs +++ b/actix-http/src/payload.rs @@ -16,6 +16,18 @@ pub type BoxedPayloadStream = Pin { + None, + H1 { payload: crate::h1::Payload }, + Stream { #[pin] payload: S }, + } +} + +#[cfg(feature = "http2")] pin_project! { /// A streaming payload. #[project = PayloadProj] @@ -33,14 +45,16 @@ impl From for Payload { } } +#[cfg(feature = "http2")] impl From for Payload { fn from(payload: crate::h2::Payload) -> Self { Payload::H2 { payload } } } -impl From for Payload { - fn from(stream: h2::RecvStream) -> Self { +#[cfg(feature = "http2")] +impl From<::h2::RecvStream> for Payload { + fn from(stream: ::h2::RecvStream) -> Self { Payload::H2 { payload: crate::h2::Payload::new(stream), } @@ -71,7 +85,10 @@ where match self.project() { PayloadProj::None => Poll::Ready(None), PayloadProj::H1 { payload } => Pin::new(payload).poll_next(cx), + + #[cfg(feature = "http2")] PayloadProj::H2 { payload } => Pin::new(payload).poll_next(cx), + PayloadProj::Stream { payload } => payload.poll_next(cx), } } diff --git a/actix-http/src/service.rs b/actix-http/src/service.rs index 4fe573aa5..b220e55a4 100644 --- a/actix-http/src/service.rs +++ b/actix-http/src/service.rs @@ -20,7 +20,7 @@ use crate::{ body::{BoxBody, MessageBody}, builder::HttpServiceBuilder, error::DispatchError, - h1, h2, ConnectCallback, OnConnectData, Protocol, Request, Response, ServiceConfig, + h1, ConnectCallback, OnConnectData, Protocol, Request, Response, ServiceConfig, }; /// A `ServiceFactory` for HTTP/1.1 or HTTP/2 protocol. @@ -502,10 +502,11 @@ where let conn_data = OnConnectData::from_io(&io, self.on_connect_ext.as_deref()); match proto { + #[cfg(feature = "http2")] Protocol::Http2 => HttpServiceHandlerResponse { state: State::H2Handshake { handshake: Some(( - h2::handshake_with_timeout(io, &self.cfg), + crate::h2::handshake_with_timeout(io, &self.cfg), self.cfg.clone(), self.flow.clone(), conn_data, @@ -514,6 +515,11 @@ where }, }, + #[cfg(not(feature = "http2"))] + Protocol::Http2 => { + panic!("HTTP/2 support is disabled (enable with the `http2` feature flag)") + } + Protocol::Http1 => HttpServiceHandlerResponse { state: State::H1 { dispatcher: h1::Dispatcher::new( @@ -531,6 +537,7 @@ where } } +#[cfg(not(feature = "http2"))] pin_project! { #[project = StateProj] enum State @@ -552,10 +559,37 @@ pin_project! { U::Error: fmt::Display, { H1 { #[pin] dispatcher: h1::Dispatcher }, - H2 { #[pin] dispatcher: h2::Dispatcher }, + } +} + +#[cfg(feature = "http2")] +pin_project! { + #[project = StateProj] + enum State + where + T: AsyncRead, + T: AsyncWrite, + T: Unpin, + + S: Service, + S::Future: 'static, + S::Error: Into>, + + B: MessageBody, + + X: Service, + X::Error: Into>, + + U: Service<(Request, Framed), Response = ()>, + U::Error: fmt::Display, + { + H1 { #[pin] dispatcher: h1::Dispatcher }, + + H2 { #[pin] dispatcher: crate::h2::Dispatcher }, + H2Handshake { handshake: Option<( - h2::HandshakeWithTimeout, + crate::h2::HandshakeWithTimeout, ServiceConfig, Rc>, OnConnectData, @@ -614,21 +648,25 @@ where fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { match self.as_mut().project().state.project() { StateProj::H1 { dispatcher } => dispatcher.poll(cx), + + #[cfg(feature = "http2")] StateProj::H2 { dispatcher } => dispatcher.poll(cx), + + #[cfg(feature = "http2")] StateProj::H2Handshake { handshake: data } => { match ready!(Pin::new(&mut data.as_mut().unwrap().0).poll(cx)) { Ok((conn, timer)) => { let (_, config, flow, conn_data, peer_addr) = data.take().unwrap(); self.as_mut().project().state.set(State::H2 { - dispatcher: h2::Dispatcher::new( + dispatcher: crate::h2::Dispatcher::new( conn, flow, config, peer_addr, conn_data, timer, ), }); self.poll(cx) } Err(err) => { - trace!("H2 handshake error: {}", err); + log::trace!("H2 handshake error: {}", err); Poll::Ready(Err(err)) } } diff --git a/actix-http/src/ws/codec.rs b/actix-http/src/ws/codec.rs index f5b755eec..6e7aa7c11 100644 --- a/actix-http/src/ws/codec.rs +++ b/actix-http/src/ws/codec.rs @@ -3,9 +3,11 @@ use bitflags::bitflags; use bytes::{Bytes, BytesMut}; use bytestring::ByteString; -use super::frame::Parser; -use super::proto::{CloseReason, OpCode}; -use super::ProtocolError; +use super::{ + frame::Parser, + proto::{CloseReason, OpCode}, + ProtocolError, +}; /// A WebSocket message. #[derive(Debug, PartialEq)] @@ -251,7 +253,7 @@ impl Decoder for Codec { } } _ => { - error!("Unfinished fragment {:?}", opcode); + log::error!("Unfinished fragment {:?}", opcode); Err(ProtocolError::ContinuationFragment(opcode)) } }; diff --git a/actix-http/src/ws/dispatcher.rs b/actix-http/src/ws/dispatcher.rs index f12ae1b1a..4c7470d37 100644 --- a/actix-http/src/ws/dispatcher.rs +++ b/actix-http/src/ws/dispatcher.rs @@ -1,6 +1,8 @@ -use std::future::Future; -use std::pin::Pin; -use std::task::{Context, Poll}; +use std::{ + future::Future, + pin::Pin, + task::{Context, Poll}, +}; use actix_codec::{AsyncRead, AsyncWrite, Framed}; use actix_service::{IntoService, Service}; diff --git a/actix-http/src/ws/frame.rs b/actix-http/src/ws/frame.rs index b58ef7362..78cef1046 100644 --- a/actix-http/src/ws/frame.rs +++ b/actix-http/src/ws/frame.rs @@ -3,9 +3,11 @@ use std::convert::TryFrom; use bytes::{Buf, BufMut, BytesMut}; use log::debug; -use crate::ws::mask::apply_mask; -use crate::ws::proto::{CloseCode, CloseReason, OpCode}; -use crate::ws::ProtocolError; +use super::{ + mask::apply_mask, + proto::{CloseCode, CloseReason, OpCode}, + ProtocolError, +}; /// A struct representing a WebSocket frame. #[derive(Debug)] diff --git a/awc/Cargo.toml b/awc/Cargo.toml index 222765991..b3afdec10 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -60,7 +60,7 @@ dangerous-h2c = [] [dependencies] actix-codec = "0.4.1" actix-service = "2.0.0" -actix-http = "3.0.0-beta.19" +actix-http = { version = "3.0.0-beta.19", features = ["http2", "ws"] } actix-rt = { version = "2.1", default-features = false } actix-tls = { version = "3.0.0", features = ["connect", "uri"] } actix-utils = "3.0.0" From fd412a8223168f86d0cd3aaf1ca81a9f67c8a15d Mon Sep 17 00:00:00 2001 From: Ali MJ Al-Nasrawy Date: Tue, 1 Feb 2022 00:26:34 +0300 Subject: [PATCH 013/170] `Quoter::requote` returns `Vec` (#2613) --- actix-router/CHANGES.md | 3 +++ actix-router/src/de.rs | 6 +++--- actix-router/src/quoter.rs | 43 ++++++++++++++++++++++++++++++-------- actix-router/src/url.rs | 8 +++---- 4 files changed, 44 insertions(+), 16 deletions(-) diff --git a/actix-router/CHANGES.md b/actix-router/CHANGES.md index 6253b522a..1f22e5764 100644 --- a/actix-router/CHANGES.md +++ b/actix-router/CHANGES.md @@ -1,6 +1,9 @@ # Changes ## Unreleased - 2021-xx-xx +- `Quoter::requote` now returns `Option>`. [#2613] + +[#2613]: https://github.com/actix/actix-web/pull/2613 ## 0.5.0-rc.2 - 2022-01-21 diff --git a/actix-router/src/de.rs b/actix-router/src/de.rs index 27aa49ef2..efafd08db 100644 --- a/actix-router/src/de.rs +++ b/actix-router/src/de.rs @@ -52,7 +52,7 @@ macro_rules! parse_value { V: Visitor<'de>, { let decoded = FULL_QUOTER - .with(|q| q.requote(self.value.as_bytes())) + .with(|q| q.requote_str_lossy(self.value)) .map(Cow::Owned) .unwrap_or(Cow::Borrowed(self.value)); @@ -332,7 +332,7 @@ impl<'de> Deserializer<'de> for Value<'de> { where V: Visitor<'de>, { - match FULL_QUOTER.with(|q| q.requote(self.value.as_bytes())) { + match FULL_QUOTER.with(|q| q.requote_str_lossy(self.value)) { Some(s) => visitor.visit_string(s), None => visitor.visit_borrowed_str(self.value), } @@ -342,7 +342,7 @@ impl<'de> Deserializer<'de> for Value<'de> { where V: Visitor<'de>, { - match FULL_QUOTER.with(|q| q.requote(self.value.as_bytes())) { + match FULL_QUOTER.with(|q| q.requote_str_lossy(self.value)) { Some(s) => visitor.visit_byte_buf(s.into()), None => visitor.visit_borrowed_bytes(self.value.as_bytes()), } diff --git a/actix-router/src/quoter.rs b/actix-router/src/quoter.rs index 26ecc92cd..73b1e72dd 100644 --- a/actix-router/src/quoter.rs +++ b/actix-router/src/quoter.rs @@ -66,8 +66,13 @@ impl Quoter { /// Re-quotes... ? /// - /// Returns `None` when no modification to the original string was required. - pub fn requote(&self, val: &[u8]) -> Option { + /// Returns `None` when no modification to the original byte string was required. + /// + /// Non-ASCII bytes are accepted as valid input. + /// + /// Behavior for invalid/incomplete percent-encoding sequences is unspecified and may include removing + /// the invalid sequence from the output or passing it as it is. + pub fn requote(&self, val: &[u8]) -> Option> { let mut has_pct = 0; let mut pct = [b'%', 0, 0]; let mut idx = 0; @@ -121,7 +126,12 @@ impl Quoter { idx += 1; } - cloned.map(|data| String::from_utf8_lossy(&data).into_owned()) + cloned + } + + pub(crate) fn requote_str_lossy(&self, val: &str) -> Option { + self.requote(val.as_bytes()) + .map(|data| String::from_utf8_lossy(&data).into_owned()) } } @@ -201,14 +211,29 @@ mod tests { #[test] fn custom_quoter() { let q = Quoter::new(b"", b"+"); - assert_eq!(q.requote(b"/a%25c").unwrap(), "/a%c"); - assert_eq!(q.requote(b"/a%2Bc").unwrap(), "/a%2Bc"); + assert_eq!(q.requote(b"/a%25c").unwrap(), b"/a%c"); + assert_eq!(q.requote(b"/a%2Bc").unwrap(), b"/a%2Bc"); let q = Quoter::new(b"%+", b"/"); - assert_eq!(q.requote(b"/a%25b%2Bc").unwrap(), "/a%b+c"); - assert_eq!(q.requote(b"/a%2fb").unwrap(), "/a%2fb"); - assert_eq!(q.requote(b"/a%2Fb").unwrap(), "/a%2Fb"); - assert_eq!(q.requote(b"/a%0Ab").unwrap(), "/a\nb"); + assert_eq!(q.requote(b"/a%25b%2Bc").unwrap(), b"/a%b+c"); + assert_eq!(q.requote(b"/a%2fb").unwrap(), b"/a%2fb"); + assert_eq!(q.requote(b"/a%2Fb").unwrap(), b"/a%2Fb"); + assert_eq!(q.requote(b"/a%0Ab").unwrap(), b"/a\nb"); + assert_eq!(q.requote(b"/a%FE\xffb").unwrap(), b"/a\xfe\xffb"); + assert_eq!(q.requote(b"/a\xfe\xffb"), None); + } + + #[test] + fn non_ascii() { + let q = Quoter::new(b"%+", b"/"); + assert_eq!(q.requote(b"/a%FE\xffb").unwrap(), b"/a\xfe\xffb"); + assert_eq!(q.requote(b"/a\xfe\xffb"), None); + } + + #[test] + fn invalid_sequences() { + let q = Quoter::new(b"%+", b"/"); + assert_eq!(q.requote(b"/a%2x%2X%%").unwrap(), b"/a%2x%2X"); } #[test] diff --git a/actix-router/src/url.rs b/actix-router/src/url.rs index f8d94ae4a..e7dda3fca 100644 --- a/actix-router/src/url.rs +++ b/actix-router/src/url.rs @@ -15,14 +15,14 @@ pub struct Url { impl Url { #[inline] pub fn new(uri: http::Uri) -> Url { - let path = DEFAULT_QUOTER.with(|q| q.requote(uri.path().as_bytes())); + let path = DEFAULT_QUOTER.with(|q| q.requote_str_lossy(uri.path())); Url { uri, path } } #[inline] pub fn new_with_quoter(uri: http::Uri, quoter: &Quoter) -> Url { Url { - path: quoter.requote(uri.path().as_bytes()), + path: quoter.requote_str_lossy(uri.path()), uri, } } @@ -45,13 +45,13 @@ impl Url { #[inline] pub fn update(&mut self, uri: &http::Uri) { self.uri = uri.clone(); - self.path = DEFAULT_QUOTER.with(|q| q.requote(uri.path().as_bytes())); + self.path = DEFAULT_QUOTER.with(|q| q.requote_str_lossy(uri.path())); } #[inline] pub fn update_with_quoter(&mut self, uri: &http::Uri, quoter: &Quoter) { self.uri = uri.clone(); - self.path = quoter.requote(uri.path().as_bytes()); + self.path = quoter.requote_str_lossy(uri.path()); } } From 9fde5b30dbf7b861cc328ba1353bc6d6cbfc2f30 Mon Sep 17 00:00:00 2001 From: Ali MJ Al-Nasrawy Date: Tue, 1 Feb 2022 01:12:48 +0300 Subject: [PATCH 014/170] tweak and document router (#2612) Co-authored-by: Rob Ede --- actix-router/CHANGES.md | 6 ++ actix-router/src/lib.rs | 2 +- actix-router/src/quoter.rs | 6 +- actix-router/src/resource.rs | 30 +++---- actix-router/src/router.rs | 152 +++++++++++++++++++---------------- src/app_service.rs | 23 ++---- src/scope.rs | 17 +--- 7 files changed, 115 insertions(+), 121 deletions(-) diff --git a/actix-router/CHANGES.md b/actix-router/CHANGES.md index 1f22e5764..368138603 100644 --- a/actix-router/CHANGES.md +++ b/actix-router/CHANGES.md @@ -1,8 +1,14 @@ # Changes ## Unreleased - 2021-xx-xx +- Remove unused `ResourceInfo`. [#2612] +- Add `RouterBuilder::push`. [#2612] +- Change signature of `ResourceDef::capture_match_info_fn` to remove `user_data` parameter. [#2612] +- Replace `Option` with `U` in `Router` API. [#2612] +- Relax bounds on `Router::recognize*` and `ResourceDef::capture_match_info`. [#2612] - `Quoter::requote` now returns `Option>`. [#2613] +[#2612]: https://github.com/actix/actix-web/pull/2612 [#2613]: https://github.com/actix/actix-web/pull/2613 diff --git a/actix-router/src/lib.rs b/actix-router/src/lib.rs index 22f294b9d..0febcf1ac 100644 --- a/actix-router/src/lib.rs +++ b/actix-router/src/lib.rs @@ -22,7 +22,7 @@ pub use self::pattern::{IntoPatterns, Patterns}; pub use self::quoter::Quoter; pub use self::resource::ResourceDef; pub use self::resource_path::{Resource, ResourcePath}; -pub use self::router::{ResourceInfo, Router, RouterBuilder}; +pub use self::router::{ResourceId, Router, RouterBuilder}; #[cfg(feature = "http")] pub use self::url::Url; diff --git a/actix-router/src/quoter.rs b/actix-router/src/quoter.rs index 73b1e72dd..8a1e99e1d 100644 --- a/actix-router/src/quoter.rs +++ b/actix-router/src/quoter.rs @@ -64,14 +64,14 @@ impl Quoter { quoter } - /// Re-quotes... ? + /// Decodes safe percent-encoded sequences from `val`. /// /// Returns `None` when no modification to the original byte string was required. /// /// Non-ASCII bytes are accepted as valid input. /// - /// Behavior for invalid/incomplete percent-encoding sequences is unspecified and may include removing - /// the invalid sequence from the output or passing it as it is. + /// Behavior for invalid/incomplete percent-encoding sequences is unspecified and may include + /// removing the invalid sequence from the output or passing it as-is. pub fn requote(&self, val: &[u8]) -> Option> { let mut has_pct = 0; let mut pct = [b'%', 0, 0]; diff --git a/actix-router/src/resource.rs b/actix-router/src/resource.rs index c0b5522af..f3eaa9f42 100644 --- a/actix-router/src/resource.rs +++ b/actix-router/src/resource.rs @@ -8,10 +8,7 @@ use std::{ use firestorm::{profile_fn, profile_method, profile_section}; use regex::{escape, Regex, RegexSet}; -use crate::{ - path::{Path, PathItem}, - IntoPatterns, Patterns, Resource, ResourcePath, -}; +use crate::{path::PathItem, IntoPatterns, Patterns, Resource, ResourcePath}; const MAX_DYNAMIC_SEGMENTS: usize = 16; @@ -615,7 +612,7 @@ impl ResourceDef { } } - /// Collects dynamic segment values into `path`. + /// Collects dynamic segment values into `resource`. /// /// Returns `true` if `path` matches this resource. /// @@ -635,9 +632,9 @@ impl ResourceDef { /// assert_eq!(path.get("path").unwrap(), "HEAD/Cargo.toml"); /// assert_eq!(path.unprocessed(), ""); /// ``` - pub fn capture_match_info(&self, path: &mut Path) -> bool { + pub fn capture_match_info(&self, resource: &mut R) -> bool { profile_method!(capture_match_info); - self.capture_match_info_fn(path, |_, _| true, ()) + self.capture_match_info_fn(resource, |_| true) } /// Collects dynamic segment values into `resource` after matching paths and executing @@ -655,13 +652,12 @@ impl ResourceDef { /// use actix_router::{Path, ResourceDef}; /// /// fn try_match(resource: &ResourceDef, path: &mut Path<&str>) -> bool { - /// let admin_allowed = std::env::var("ADMIN_ALLOWED").ok(); + /// let admin_allowed = std::env::var("ADMIN_ALLOWED").is_ok(); /// /// resource.capture_match_info_fn( /// path, /// // when env var is not set, reject when path contains "admin" - /// |res, admin_allowed| !res.path().contains("admin"), - /// &admin_allowed + /// |res| !(!admin_allowed && res.path().contains("admin")), /// ) /// } /// @@ -678,15 +674,10 @@ impl ResourceDef { /// assert!(!try_match(&resource, &mut path)); /// assert_eq!(path.unprocessed(), "/user/admin/stars"); /// ``` - pub fn capture_match_info_fn( - &self, - resource: &mut R, - check_fn: F, - user_data: U, - ) -> bool + pub fn capture_match_info_fn(&self, resource: &mut R, check_fn: F) -> bool where R: Resource, - F: FnOnce(&R, U) -> bool, + F: FnOnce(&R) -> bool, { profile_method!(capture_match_info_fn); @@ -762,7 +753,7 @@ impl ResourceDef { } }; - if !check_fn(resource, user_data) { + if !check_fn(resource) { return false; } @@ -857,7 +848,7 @@ impl ResourceDef { S: BuildHasher, { profile_method!(resource_path_from_map); - self.build_resource_path(path, |name| values.get(name).map(AsRef::::as_ref)) + self.build_resource_path(path, |name| values.get(name)) } /// Returns true if `prefix` acts as a proper prefix (i.e., separated by a slash) in `path`. @@ -1157,6 +1148,7 @@ pub(crate) fn insert_slash(path: &str) -> Cow<'_, str> { #[cfg(test)] mod tests { use super::*; + use crate::Path; #[test] fn equivalence() { diff --git a/actix-router/src/router.rs b/actix-router/src/router.rs index 4652ef678..f0e598683 100644 --- a/actix-router/src/router.rs +++ b/actix-router/src/router.rs @@ -5,87 +5,83 @@ use crate::{IntoPatterns, Resource, ResourceDef}; #[derive(Debug, Copy, Clone, PartialEq)] pub struct ResourceId(pub u16); -/// Information about current resource -#[derive(Debug, Clone)] -pub struct ResourceInfo { - #[allow(dead_code)] - resource: ResourceId, -} - /// Resource router. -// T is the resource itself -// U is any other data needed for routing like method guards +/// +/// It matches a [routing resource](Resource) to an ordered list of _routes_. Each is defined by a +/// single [`ResourceDef`] and contains two types of custom data: +/// 1. The route _value_, of the generic type `T`. +/// 1. Some _context_ data, of the generic type `U`, which is only provided to the check function in +/// [`recognize_fn`](Self::recognize_fn). This parameter defaults to `()` and can be omitted if +/// not required. pub struct Router { - routes: Vec<(ResourceDef, T, Option)>, + routes: Vec<(ResourceDef, T, U)>, } impl Router { + /// Constructs new `RouterBuilder` with empty route list. pub fn build() -> RouterBuilder { - RouterBuilder { - resources: Vec::new(), - } + RouterBuilder { routes: Vec::new() } } + /// Finds the value in the router that matches a given [routing resource](Resource). + /// + /// The match result, including the captured dynamic segments, in the `resource`. pub fn recognize(&self, resource: &mut R) -> Option<(&T, ResourceId)> where R: Resource, { profile_method!(recognize); - - for item in self.routes.iter() { - if item.0.capture_match_info(resource.resource_path()) { - return Some((&item.1, ResourceId(item.0.id()))); - } - } - - None + self.recognize_fn(resource, |_, _| true) } + /// Same as [`recognize`](Self::recognize) but returns a mutable reference to the matched value. pub fn recognize_mut(&mut self, resource: &mut R) -> Option<(&mut T, ResourceId)> where R: Resource, { profile_method!(recognize_mut); - - for item in self.routes.iter_mut() { - if item.0.capture_match_info(resource.resource_path()) { - return Some((&mut item.1, ResourceId(item.0.id()))); - } - } - - None + self.recognize_mut_fn(resource, |_, _| true) } - pub fn recognize_fn(&self, resource: &mut R, check: F) -> Option<(&T, ResourceId)> + /// Finds the value in the router that matches a given [routing resource](Resource) and passes + /// an additional predicate check using context data. + /// + /// Similar to [`recognize`](Self::recognize). However, before accepting the route as matched, + /// the `check` closure is executed, passing the resource and each route's context data. If the + /// closure returns true then the match result is stored into `resource` and a reference to + /// the matched _value_ is returned. + pub fn recognize_fn(&self, resource: &mut R, mut check: F) -> Option<(&T, ResourceId)> where - F: Fn(&R, &Option) -> bool, R: Resource, + F: FnMut(&R, &U) -> bool, { profile_method!(recognize_checked); - for item in self.routes.iter() { - if item.0.capture_match_info_fn(resource, &check, &item.2) { - return Some((&item.1, ResourceId(item.0.id()))); + for (rdef, val, ctx) in self.routes.iter() { + if rdef.capture_match_info_fn(resource, |res| check(res, ctx)) { + return Some((val, ResourceId(rdef.id()))); } } None } + /// Same as [`recognize_fn`](Self::recognize_fn) but returns a mutable reference to the matched + /// value. pub fn recognize_mut_fn( &mut self, resource: &mut R, - check: F, + mut check: F, ) -> Option<(&mut T, ResourceId)> where - F: Fn(&R, &Option) -> bool, R: Resource, + F: FnMut(&R, &U) -> bool, { profile_method!(recognize_mut_checked); - for item in self.routes.iter_mut() { - if item.0.capture_match_info_fn(resource, &check, &item.2) { - return Some((&mut item.1, ResourceId(item.0.id()))); + for (rdef, val, ctx) in self.routes.iter_mut() { + if rdef.capture_match_info_fn(resource, |res| check(res, ctx)) { + return Some((val, ResourceId(rdef.id()))); } } @@ -93,49 +89,69 @@ impl Router { } } +/// Builder for an ordered [routing](Router) list. pub struct RouterBuilder { - resources: Vec<(ResourceDef, T, Option)>, + routes: Vec<(ResourceDef, T, U)>, } impl RouterBuilder { - /// Register resource for specified path. - pub fn path( + /// Adds a new route to the end of the routing list. + /// + /// Returns mutable references to elements of the new route. + pub fn push( &mut self, - path: P, - resource: T, - ) -> &mut (ResourceDef, T, Option) { - profile_method!(path); - - self.resources - .push((ResourceDef::new(path), resource, None)); - self.resources.last_mut().unwrap() - } - - /// Register resource for specified path prefix. - pub fn prefix(&mut self, prefix: &str, resource: T) -> &mut (ResourceDef, T, Option) { - profile_method!(prefix); - - self.resources - .push((ResourceDef::prefix(prefix), resource, None)); - self.resources.last_mut().unwrap() - } - - /// Register resource for ResourceDef - pub fn rdef(&mut self, rdef: ResourceDef, resource: T) -> &mut (ResourceDef, T, Option) { - profile_method!(rdef); - - self.resources.push((rdef, resource, None)); - self.resources.last_mut().unwrap() + rdef: ResourceDef, + val: T, + ctx: U, + ) -> (&mut ResourceDef, &mut T, &mut U) { + profile_method!(push); + self.routes.push((rdef, val, ctx)); + self.routes + .last_mut() + .map(|(rdef, val, ctx)| (rdef, val, ctx)) + .unwrap() } /// Finish configuration and create router instance. pub fn finish(self) -> Router { Router { - routes: self.resources, + routes: self.routes, } } } +/// Convenience methods provided when context data impls [`Default`] +impl RouterBuilder +where + U: Default, +{ + /// Registers resource for specified path. + pub fn path( + &mut self, + path: impl IntoPatterns, + val: T, + ) -> (&mut ResourceDef, &mut T, &mut U) { + profile_method!(path); + self.push(ResourceDef::new(path), val, U::default()) + } + + /// Registers resource for specified path prefix. + pub fn prefix( + &mut self, + prefix: impl IntoPatterns, + val: T, + ) -> (&mut ResourceDef, &mut T, &mut U) { + profile_method!(prefix); + self.push(ResourceDef::prefix(prefix), val, U::default()) + } + + /// Registers resource for [`ResourceDef`]. + pub fn rdef(&mut self, rdef: ResourceDef, val: T) -> (&mut ResourceDef, &mut T, &mut U) { + profile_method!(rdef); + self.push(rdef, val, U::default()) + } +} + #[cfg(test)] mod tests { use crate::path::Path; diff --git a/src/app_service.rs b/src/app_service.rs index dbd718330..3ef31ac75 100644 --- a/src/app_service.rs +++ b/src/app_service.rs @@ -21,8 +21,6 @@ use crate::{ Error, HttpResponse, }; -type Guards = Vec>; - /// Service factory to convert `Request` to a `ServiceRequest`. /// /// It also executes data factories. @@ -244,7 +242,7 @@ pub struct AppRoutingFactory { [( ResourceDef, BoxedHttpServiceFactory, - RefCell>, + RefCell>>>, )], >, default: Rc, @@ -262,7 +260,7 @@ impl ServiceFactory for AppRoutingFactory { // construct all services factory future with it's resource def and guards. let factory_fut = join_all(self.services.iter().map(|(path, factory, guards)| { let path = path.clone(); - let guards = guards.borrow_mut().take(); + let guards = guards.borrow_mut().take().unwrap_or_default(); let factory_fut = factory.new_service(()); async move { let service = factory_fut.await?; @@ -283,7 +281,7 @@ impl ServiceFactory for AppRoutingFactory { .collect::, _>>()? .drain(..) .fold(Router::build(), |mut router, (path, guards, service)| { - router.rdef(path, service).2 = guards; + router.push(path, service, guards); router }) .finish(); @@ -295,7 +293,7 @@ impl ServiceFactory for AppRoutingFactory { /// The Actix Web router default entry point. pub struct AppRouting { - router: Router, + router: Router>>, default: BoxedHttpService, } @@ -308,17 +306,8 @@ impl Service for AppRouting { fn call(&self, mut req: ServiceRequest) -> Self::Future { let res = self.router.recognize_fn(&mut req, |req, guards| { - if let Some(ref guards) = guards { - let guard_ctx = req.guard_ctx(); - - for guard in guards { - if !guard.check(&guard_ctx) { - return false; - } - } - } - - true + let guard_ctx = req.guard_ctx(); + guards.iter().all(|guard| guard.check(&guard_ctx)) }); if let Some((srv, _info)) = res { diff --git a/src/scope.rs b/src/scope.rs index dad727430..0fcc83d70 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -467,7 +467,7 @@ impl ServiceFactory for ScopeFactory { // construct all services factory future with it's resource def and guards. let factory_fut = join_all(self.services.iter().map(|(path, factory, guards)| { let path = path.clone(); - let guards = guards.borrow_mut().take(); + let guards = guards.borrow_mut().take().unwrap_or_default(); let factory_fut = factory.new_service(()); async move { let service = factory_fut.await?; @@ -485,7 +485,7 @@ impl ServiceFactory for ScopeFactory { .collect::, _>>()? .drain(..) .fold(Router::build(), |mut router, (path, guards, service)| { - router.rdef(path, service).2 = guards; + router.push(path, service, guards); router }) .finish(); @@ -509,17 +509,8 @@ impl Service for ScopeService { fn call(&self, mut req: ServiceRequest) -> Self::Future { let res = self.router.recognize_fn(&mut req, |req, guards| { - if let Some(ref guards) = guards { - let guard_ctx = req.guard_ctx(); - - for guard in guards { - if !guard.check(&guard_ctx) { - return false; - } - } - } - - true + let guard_ctx = req.guard_ctx(); + guards.iter().all(|guard| guard.check(&guard_ctx)) }); if let Some((srv, _info)) = res { From 9777653dc0abe039b72b3a1582b009232c136d77 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Mon, 31 Jan 2022 22:20:53 +0000 Subject: [PATCH 015/170] prep readme for rc release --- README.md | 30 +++++++++++++----------------- scripts/ci-test | 30 ++++++++++++++++++++---------- 2 files changed, 33 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index c3ea70f2c..cc761a98e 100644 --- a/README.md +++ b/README.md @@ -21,12 +21,13 @@ ## Features -- Supports *HTTP/1.x* and *HTTP/2* +- Supports _HTTP/1.x_ and _HTTP/2_ - Streaming and pipelining +- Powerful [request routing](https://actix.rs/docs/url-dispatch/) with optional macros +- Full [Tokio](https://tokio.rs) compatibility - Keep-alive and slow requests handling - Client/server [WebSockets](https://actix.rs/docs/websockets/) support - Transparent content compression/decompression (br, gzip, deflate, zstd) -- Powerful [request routing](https://actix.rs/docs/url-dispatch/) - Multipart streams - Static assets - SSL support using OpenSSL or Rustls @@ -47,7 +48,7 @@ Dependencies: ```toml [dependencies] -actix-web = "3" +actix-web = "4.0.0-rc.1" ``` Code: @@ -56,14 +57,15 @@ Code: use actix_web::{get, web, App, HttpServer, Responder}; #[get("/{id}/{name}/index.html")] -async fn index(web::Path((id, name)): web::Path<(u32, String)>) -> impl Responder { +async fn index(params: web::Path<(u32, String)>) -> impl Responder { + let (id, name) = params.into_inner(); format!("Hello {}! id:{}", name, id) } -#[actix_web::main] +#[actix_web::main] // or #[tokio::main] async fn main() -> std::io::Result<()> { HttpServer::new(|| App::new().service(index)) - .bind("127.0.0.1:8080")? + .bind(("127.0.0.1", 8080))? .run() .await } @@ -84,24 +86,18 @@ async fn main() -> std::io::Result<()> { - [HTTPS using OpenSSL](https://github.com/actix/examples/tree/master/security/openssl/) - [WebSocket Chat](https://github.com/actix/examples/tree/master/websockets/chat/) -You may consider checking out -[this directory](https://github.com/actix/examples/tree/master/) for more examples. +You may consider checking out [this directory](https://github.com/actix/examples/tree/master/) for more examples. ## Benchmarks -One of the fastest web frameworks available according to the -[TechEmpower Framework Benchmark](https://www.techempower.com/benchmarks/#section=data-r20&test=composite). +One of the fastest web frameworks available according to the [TechEmpower Framework Benchmark](https://www.techempower.com/benchmarks/#section=data-r20&test=composite). ## License -This project is licensed under either of +This project is licensed under either of the following licenses, at your option: -- Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or - [http://www.apache.org/licenses/LICENSE-2.0]) -- MIT license ([LICENSE-MIT](LICENSE-MIT) or - [http://opensource.org/licenses/MIT]) - -at your option. +- Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or [http://www.apache.org/licenses/LICENSE-2.0]) +- MIT license ([LICENSE-MIT](LICENSE-MIT) or [http://opensource.org/licenses/MIT]) ## Code of Conduct diff --git a/scripts/ci-test b/scripts/ci-test index 567012d33..8b7e3d12d 100755 --- a/scripts/ci-test +++ b/scripts/ci-test @@ -13,16 +13,26 @@ save_exit_code() { } save_exit_code cargo test --lib --tests -p=actix-router --all-features -- --nocapture -save_exit_code cargo test --lib --tests -p=actix-http --all-features -- --nocapture -save_exit_code cargo test --lib --tests -p=actix-web --features=rustls,openssl -- --nocapture --skip=test_reading_deflate_encoding_large_random_rustls -save_exit_code cargo test --lib --tests -p=actix-web-codegen --all-features -- --nocapture -save_exit_code cargo test --lib --tests -p=awc --all-features -- --nocapture -save_exit_code cargo test --lib --tests -p=actix-http-test --all-features -- --nocapture -save_exit_code cargo test --lib --tests -p=actix-test --all-features -- --nocapture -save_exit_code cargo test --lib --tests -p=actix-files -- --nocapture -save_exit_code cargo test --lib --tests -p=actix-multipart --all-features -- --nocapture -save_exit_code cargo test --lib --tests -p=actix-web-actors --all-features -- --nocapture +# save_exit_code cargo test --lib --tests -p=actix-http --all-features -- --nocapture +# save_exit_code cargo test --lib --tests -p=actix-web --features=rustls,openssl -- --nocapture --skip=test_reading_deflate_encoding_large_random_rustls +# save_exit_code cargo test --lib --tests -p=actix-web-codegen --all-features -- --nocapture +# save_exit_code cargo test --lib --tests -p=awc --all-features -- --nocapture +# save_exit_code cargo test --lib --tests -p=actix-http-test --all-features -- --nocapture +# save_exit_code cargo test --lib --tests -p=actix-test --all-features -- --nocapture +# save_exit_code cargo test --lib --tests -p=actix-files -- --nocapture +# save_exit_code cargo test --lib --tests -p=actix-multipart --all-features -- --nocapture +# save_exit_code cargo test --lib --tests -p=actix-web-actors --all-features -- --nocapture -save_exit_code cargo test --workspace --doc +# save_exit_code cargo test --workspace --doc + +if [ "$EXIT" = "0" ]; then + PASSED="All tests passed!" + + if [ "$(command -v figlet)" ]; then + figlet "$PASSED" + else + echo "$PASSED" + fi +fi exit $EXIT From 47f5faf26e08671e0c7fce53945a33ed9a258493 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Mon, 31 Jan 2022 22:21:30 +0000 Subject: [PATCH 016/170] prepare actix-router release 0.5.0-rc.3 --- Cargo.toml | 2 +- actix-router/CHANGES.md | 3 +++ actix-router/Cargo.toml | 2 +- actix-web-codegen/Cargo.toml | 2 +- 4 files changed, 6 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 38c8512bc..0560ec190 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -81,7 +81,7 @@ actix-utils = "3.0.0" actix-tls = { version = "3.0.0", default-features = false, optional = true } actix-http = { version = "3.0.0-beta.19", features = ["http2", "ws"] } -actix-router = "0.5.0-rc.2" +actix-router = "0.5.0-rc.3" actix-web-codegen = "0.5.0-rc.1" ahash = "0.7" diff --git a/actix-router/CHANGES.md b/actix-router/CHANGES.md index 368138603..ff9d8b4ab 100644 --- a/actix-router/CHANGES.md +++ b/actix-router/CHANGES.md @@ -1,6 +1,9 @@ # Changes ## Unreleased - 2021-xx-xx + + +## 0.5.0-rc.3 - 2022-01-31 - Remove unused `ResourceInfo`. [#2612] - Add `RouterBuilder::push`. [#2612] - Change signature of `ResourceDef::capture_match_info_fn` to remove `user_data` parameter. [#2612] diff --git a/actix-router/Cargo.toml b/actix-router/Cargo.toml index 0d4e4f897..647a34479 100644 --- a/actix-router/Cargo.toml +++ b/actix-router/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-router" -version = "0.5.0-rc.2" +version = "0.5.0-rc.3" authors = [ "Nikolay Kim ", "Ali MJ Al-Nasrawy ", diff --git a/actix-web-codegen/Cargo.toml b/actix-web-codegen/Cargo.toml index 3ee2fbd02..eb2e5536d 100644 --- a/actix-web-codegen/Cargo.toml +++ b/actix-web-codegen/Cargo.toml @@ -15,7 +15,7 @@ edition = "2018" proc-macro = true [dependencies] -actix-router = "0.5.0-beta.4" +actix-router = "0.5.0-rc.3" proc-macro2 = "1" quote = "1" syn = { version = "1", features = ["full", "parsing"] } From 21f57caf4ac1e2287e211d0e2f1f245820e49b70 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Mon, 31 Jan 2022 22:22:40 +0000 Subject: [PATCH 017/170] prepare actix-http release 3.0.0-rc.1 --- Cargo.toml | 2 +- actix-files/Cargo.toml | 2 +- actix-http-test/Cargo.toml | 2 +- actix-http/CHANGES.md | 3 +++ actix-http/Cargo.toml | 2 +- actix-http/README.md | 4 ++-- actix-multipart/Cargo.toml | 2 +- actix-test/Cargo.toml | 2 +- actix-web-actors/Cargo.toml | 2 +- awc/Cargo.toml | 4 ++-- 10 files changed, 14 insertions(+), 11 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 0560ec190..3799febe7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -80,7 +80,7 @@ actix-service = "2.0.0" actix-utils = "3.0.0" actix-tls = { version = "3.0.0", default-features = false, optional = true } -actix-http = { version = "3.0.0-beta.19", features = ["http2", "ws"] } +actix-http = { version = "3.0.0-rc.1", features = ["http2", "ws"] } actix-router = "0.5.0-rc.3" actix-web-codegen = "0.5.0-rc.1" diff --git a/actix-files/Cargo.toml b/actix-files/Cargo.toml index 68a3399a5..5faa0ebad 100644 --- a/actix-files/Cargo.toml +++ b/actix-files/Cargo.toml @@ -22,7 +22,7 @@ path = "src/lib.rs" experimental-io-uring = ["actix-web/experimental-io-uring", "tokio-uring"] [dependencies] -actix-http = "3.0.0-beta.19" +actix-http = "3.0.0-rc.1" actix-service = "2" actix-utils = "3" actix-web = { version = "4.0.0-beta.21", default-features = false } diff --git a/actix-http-test/Cargo.toml b/actix-http-test/Cargo.toml index 9bd3e9f9b..15fd298c1 100644 --- a/actix-http-test/Cargo.toml +++ b/actix-http-test/Cargo.toml @@ -52,4 +52,4 @@ tokio = { version = "1.8.4", features = ["sync"] } [dev-dependencies] actix-web = { version = "4.0.0-beta.21", default-features = false, features = ["cookies"] } -actix-http = "3.0.0-beta.19" +actix-http = "3.0.0-rc.1" diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 38bec78ba..7f7af23a8 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -1,6 +1,9 @@ # Changes ## Unreleased - 2021-xx-xx + + +## 3.0.0-rc.1 - 2022-01-31 ### Added - Implement `Default` for `KeepAlive`. [#2611] - Implement `From` for `KeepAlive`. [#2611] diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index f68eda074..959eefd8d 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-http" -version = "3.0.0-beta.19" +version = "3.0.0-rc.1" authors = [ "Nikolay Kim ", "Rob Ede ", diff --git a/actix-http/README.md b/actix-http/README.md index 9f275f597..0087fabe3 100644 --- a/actix-http/README.md +++ b/actix-http/README.md @@ -3,11 +3,11 @@ > HTTP primitives for the Actix ecosystem. [![crates.io](https://img.shields.io/crates/v/actix-http?label=latest)](https://crates.io/crates/actix-http) -[![Documentation](https://docs.rs/actix-http/badge.svg?version=3.0.0-beta.19)](https://docs.rs/actix-http/3.0.0-beta.19) +[![Documentation](https://docs.rs/actix-http/badge.svg?version=3.0.0-rc.1)](https://docs.rs/actix-http/3.0.0-rc.1) [![Version](https://img.shields.io/badge/rustc-1.54+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.54.0.html) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-http.svg)
-[![dependency status](https://deps.rs/crate/actix-http/3.0.0-beta.19/status.svg)](https://deps.rs/crate/actix-http/3.0.0-beta.19) +[![dependency status](https://deps.rs/crate/actix-http/3.0.0-rc.1/status.svg)](https://deps.rs/crate/actix-http/3.0.0-rc.1) [![Download](https://img.shields.io/crates/d/actix-http.svg)](https://crates.io/crates/actix-http) [![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) diff --git a/actix-multipart/Cargo.toml b/actix-multipart/Cargo.toml index 327252dbf..08c15da9c 100644 --- a/actix-multipart/Cargo.toml +++ b/actix-multipart/Cargo.toml @@ -28,7 +28,7 @@ twoway = "0.2" [dev-dependencies] actix-rt = "2.2" -actix-http = "3.0.0-beta.19" +actix-http = "3.0.0-rc.1" futures-util = { version = "0.3.7", default-features = false, features = ["alloc"] } tokio = { version = "1.8.4", features = ["sync"] } tokio-stream = "0.1" diff --git a/actix-test/Cargo.toml b/actix-test/Cargo.toml index e2c75b01e..6d70db0b9 100644 --- a/actix-test/Cargo.toml +++ b/actix-test/Cargo.toml @@ -29,7 +29,7 @@ openssl = ["tls-openssl", "actix-http/openssl", "awc/openssl"] [dependencies] actix-codec = "0.4.1" -actix-http = "3.0.0-beta.19" +actix-http = "3.0.0-rc.1" actix-http-test = "3.0.0-beta.11" actix-rt = "2.1" actix-service = "2.0.0" diff --git a/actix-web-actors/Cargo.toml b/actix-web-actors/Cargo.toml index 3c28c0a15..51673a177 100644 --- a/actix-web-actors/Cargo.toml +++ b/actix-web-actors/Cargo.toml @@ -16,7 +16,7 @@ path = "src/lib.rs" [dependencies] actix = { version = "0.12.0", default-features = false } actix-codec = "0.4.1" -actix-http = "3.0.0-beta.19" +actix-http = "3.0.0-rc.1" actix-web = { version = "4.0.0-beta.21", default-features = false } bytes = "1" diff --git a/awc/Cargo.toml b/awc/Cargo.toml index b3afdec10..6d6e1c134 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -60,7 +60,7 @@ dangerous-h2c = [] [dependencies] actix-codec = "0.4.1" actix-service = "2.0.0" -actix-http = { version = "3.0.0-beta.19", features = ["http2", "ws"] } +actix-http = { version = "3.0.0-rc.1", features = ["http2", "ws"] } actix-rt = { version = "2.1", default-features = false } actix-tls = { version = "3.0.0", features = ["connect", "uri"] } actix-utils = "3.0.0" @@ -93,7 +93,7 @@ tls-rustls = { package = "rustls", version = "0.20.0", optional = true, features trust-dns-resolver = { version = "0.20.0", optional = true } [dev-dependencies] -actix-http = { version = "3.0.0-beta.19", features = ["openssl"] } +actix-http = { version = "3.0.0-rc.1", features = ["openssl"] } actix-http-test = { version = "3.0.0-beta.11", features = ["openssl"] } actix-server = "2" actix-test = { version = "0.1.0-beta.11", features = ["openssl", "rustls"] } From c3c7eb8df9b52b33fc8826fda08d7758075ffd19 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Mon, 31 Jan 2022 22:23:33 +0000 Subject: [PATCH 018/170] prepare actix-web release 4.0.0-rc.1 --- CHANGES.md | 3 +++ Cargo.toml | 2 +- README.md | 4 ++-- actix-files/Cargo.toml | 4 ++-- actix-http-test/Cargo.toml | 2 +- actix-http/Cargo.toml | 2 +- actix-multipart/Cargo.toml | 2 +- actix-test/Cargo.toml | 2 +- actix-web-actors/Cargo.toml | 2 +- actix-web-codegen/Cargo.toml | 2 +- awc/Cargo.toml | 2 +- 11 files changed, 15 insertions(+), 12 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index c00bc7198..a7e0a0309 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,9 @@ # Changes ## Unreleased - 2021-xx-xx + + +## 4.0.0-rc.1 - 2022-01-31 ### Changed - Rename `HttpServer::{client_timeout => client_request_timeout}`. [#2611] - Rename `HttpServer::{client_shutdown => client_disconnect_timeout}`. [#2611] diff --git a/Cargo.toml b/Cargo.toml index 3799febe7..0f5b87810 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-web" -version = "4.0.0-beta.21" +version = "4.0.0-rc.1" authors = [ "Nikolay Kim ", "Rob Ede ", diff --git a/README.md b/README.md index cc761a98e..f99a7be23 100644 --- a/README.md +++ b/README.md @@ -6,10 +6,10 @@

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

-

Actix Web

-

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

-

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

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

Actix Web

+

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

+

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

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

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

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

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

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

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

+3.0.0 Pre-Releases + ## 3.0.0-beta.21 - 2022-02-16 - No significant changes since `3.0.0-beta.20`. @@ -170,6 +267,7 @@ [#1813]: https://github.com/actix/actix-web/pull/1813 +
## 2.0.3 - 2020-11-29 ### Fixed diff --git a/awc/Cargo.toml b/awc/Cargo.toml index f86aa5543..9dd29e4b7 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "awc" -version = "3.0.0-beta.21" +version = "3.0.0" authors = [ "Nikolay Kim ", "fakeshadow <24548779@qq.com>", @@ -59,11 +59,11 @@ dangerous-h2c = [] [dependencies] actix-codec = "0.5" -actix-service = "2.0.0" -actix-http = { version = "3.0.0", features = ["http2", "ws"] } +actix-service = "2" +actix-http = { version = "3", features = ["http2", "ws"] } actix-rt = { version = "2.1", default-features = false } actix-tls = { version = "3", features = ["connect", "uri"] } -actix-utils = "3.0.0" +actix-utils = "3" ahash = "0.7" base64 = "0.13" @@ -93,13 +93,13 @@ tls-rustls = { package = "rustls", version = "0.20.0", optional = true, features trust-dns-resolver = { version = "0.20.0", optional = true } [dev-dependencies] -actix-http = { version = "3.0.0", features = ["openssl"] } +actix-http = { version = "3", features = ["openssl"] } actix-http-test = { version = "3.0.0-beta.13", features = ["openssl"] } actix-server = "2" actix-test = { version = "0.1.0-beta.13", features = ["openssl", "rustls"] } actix-tls = { version = "3", features = ["openssl", "rustls"] } -actix-utils = "3.0.0" -actix-web = { version = "4.0.0", features = ["openssl"] } +actix-utils = "3" +actix-web = { version = "4", features = ["openssl"] } brotli = "3.3.3" const-str = "0.3" diff --git a/awc/README.md b/awc/README.md index 417647e62..db70f7332 100644 --- a/awc/README.md +++ b/awc/README.md @@ -3,9 +3,9 @@ > Async HTTP and WebSocket client library. [![crates.io](https://img.shields.io/crates/v/awc?label=latest)](https://crates.io/crates/awc) -[![Documentation](https://docs.rs/awc/badge.svg?version=3.0.0-beta.21)](https://docs.rs/awc/3.0.0-beta.21) +[![Documentation](https://docs.rs/awc/badge.svg?version=3.0.0)](https://docs.rs/awc/3.0.0) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/awc) -[![Dependency Status](https://deps.rs/crate/awc/3.0.0-beta.21/status.svg)](https://deps.rs/crate/awc/3.0.0-beta.21) +[![Dependency Status](https://deps.rs/crate/awc/3.0.0/status.svg)](https://deps.rs/crate/awc/3.0.0) [![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) ## Documentation & Resources diff --git a/awc/src/client/connector.rs b/awc/src/client/connector.rs index 26c62b924..51d6e180b 100644 --- a/awc/src/client/connector.rs +++ b/awc/src/client/connector.rs @@ -246,7 +246,12 @@ where /// /// The default limit size is 100. pub fn limit(mut self, limit: usize) -> Self { - self.config.limit = limit; + if limit == 0 { + self.config.limit = u32::MAX as usize; + } else { + self.config.limit = limit; + } + self } diff --git a/awc/src/client/h1proto.rs b/awc/src/client/h1proto.rs index 4f6a87ac5..8738c2f7f 100644 --- a/awc/src/client/h1proto.rs +++ b/awc/src/client/h1proto.rs @@ -83,12 +83,12 @@ where false }; - framed.send((head, body.size()).into()).await?; - let mut pin_framed = Pin::new(&mut framed); // special handle for EXPECT request. let (do_send, mut res_head) = if is_expect { + pin_framed.send((head, body.size()).into()).await?; + let head = poll_fn(|cx| pin_framed.as_mut().poll_next(cx)) .await .ok_or(ConnectError::Disconnected)??; @@ -97,13 +97,17 @@ where // and current head would be used as final response head. (head.status == StatusCode::CONTINUE, Some(head)) } else { + pin_framed.feed((head, body.size()).into()).await?; + (true, None) }; if do_send { // send request body match body.size() { - BodySize::None | BodySize::Sized(0) => {} + BodySize::None | BodySize::Sized(0) => { + poll_fn(|cx| pin_framed.as_mut().flush(cx)).await?; + } _ => send_body(body, pin_framed.as_mut()).await?, }; diff --git a/awc/src/connect.rs b/awc/src/connect.rs index f93014a67..be1ea0fee 100644 --- a/awc/src/connect.rs +++ b/awc/src/connect.rs @@ -30,17 +30,35 @@ pub type BoxConnectorService = Rc< pub type BoxedSocket = Box; +/// Combined HTTP and WebSocket request type received by connection service. pub enum ConnectRequest { + /// Standard HTTP request. + /// + /// Contains the request head, body type, and optional pre-resolved socket address. Client(RequestHeadType, AnyBody, Option), + + /// Tunnel used by WebSocket connection requests. + /// + /// Contains the request head and optional pre-resolved socket address. Tunnel(RequestHead, Option), } +/// Combined HTTP response & WebSocket tunnel type returned from connection service. pub enum ConnectResponse { + /// Standard HTTP response. Client(ClientResponse), + + /// Tunnel used for WebSocket communication. + /// + /// Contains response head and framed HTTP/1.1 codec. Tunnel(ResponseHead, Framed), } impl ConnectResponse { + /// Unwraps type into HTTP response. + /// + /// # Panics + /// Panics if enum variant is not `Client`. pub fn into_client_response(self) -> ClientResponse { match self { ConnectResponse::Client(res) => res, @@ -50,6 +68,10 @@ impl ConnectResponse { } } + /// Unwraps type into WebSocket tunnel response. + /// + /// # Panics + /// Panics if enum variant is not `Tunnel`. pub fn into_tunnel_response(self) -> (ResponseHead, Framed) { match self { ConnectResponse::Tunnel(head, framed) => (head, framed), @@ -136,30 +158,37 @@ where ConnectRequestProj::Connection { fut, req } => { let connection = ready!(fut.poll(cx))?; let req = req.take().unwrap(); + match req { ConnectRequest::Client(head, body, ..) => { // send request let fut = ConnectRequestFuture::Client { fut: connection.send_request(head, body), }; + self.set(fut); } + ConnectRequest::Tunnel(head, ..) => { // send request let fut = ConnectRequestFuture::Tunnel { fut: connection.open_tunnel(RequestHeadType::from(head)), }; + self.set(fut); } } + self.poll(cx) } + ConnectRequestProj::Client { fut } => { let (head, payload) = ready!(fut.as_mut().poll(cx))?; Poll::Ready(Ok(ConnectResponse::Client(ClientResponse::new( head, payload, )))) } + ConnectRequestProj::Tunnel { fut } => { let (head, framed) = ready!(fut.as_mut().poll(cx))?; let framed = framed.into_map_io(|io| Box::new(io) as _); diff --git a/awc/src/lib.rs b/awc/src/lib.rs index 3f5e25330..8d6ea759a 100644 --- a/awc/src/lib.rs +++ b/awc/src/lib.rs @@ -1,22 +1,25 @@ //! `awc` is an asynchronous HTTP and WebSocket client library. //! -//! # Making a GET request +//! # `GET` Requests //! ```no_run //! # #[actix_rt::main] //! # async fn main() -> Result<(), awc::error::SendRequestError> { +//! // create client //! let mut client = awc::Client::default(); -//! let response = client.get("http://www.rust-lang.org") // <- Create request builder -//! .insert_header(("User-Agent", "Actix-web")) -//! .send() // <- Send http request -//! .await?; //! -//! println!("Response: {:?}", response); +//! // construct request +//! let req = client.get("http://www.rust-lang.org") +//! .insert_header(("User-Agent", "awc/3.0")); +//! +//! // send request and await response +//! let res = req.send().await?; +//! println!("Response: {:?}", res); //! # Ok(()) //! # } //! ``` //! -//! # Making POST requests -//! ## Raw body contents +//! # `POST` Requests +//! ## Raw Body //! ```no_run //! # #[actix_rt::main] //! # async fn main() -> Result<(), awc::error::SendRequestError> { @@ -28,20 +31,6 @@ //! # } //! ``` //! -//! ## Forms -//! ```no_run -//! # #[actix_rt::main] -//! # async fn main() -> Result<(), awc::error::SendRequestError> { -//! let params = [("foo", "bar"), ("baz", "quux")]; -//! -//! let mut client = awc::Client::default(); -//! let response = client.post("http://httpbin.org/post") -//! .send_form(¶ms) -//! .await?; -//! # Ok(()) -//! # } -//! ``` -//! //! ## JSON //! ```no_run //! # #[actix_rt::main] @@ -59,6 +48,20 @@ //! # } //! ``` //! +//! ## URL Encoded Form +//! ```no_run +//! # #[actix_rt::main] +//! # async fn main() -> Result<(), awc::error::SendRequestError> { +//! let params = [("foo", "bar"), ("baz", "quux")]; +//! +//! let mut client = awc::Client::default(); +//! let response = client.post("http://httpbin.org/post") +//! .send_form(¶ms) +//! .await?; +//! # Ok(()) +//! # } +//! ``` +//! //! # Response Compression //! All [official][iana-encodings] and common content encoding codecs are supported, optionally. //! @@ -76,11 +79,12 @@ //! //! [iana-encodings]: https://www.iana.org/assignments/http-parameters/http-parameters.xhtml#content-coding //! -//! # WebSocket support +//! # WebSockets //! ```no_run //! # #[actix_rt::main] //! # async fn main() -> Result<(), Box> { -//! use futures_util::{sink::SinkExt, stream::StreamExt}; +//! use futures_util::{sink::SinkExt as _, stream::StreamExt as _}; +//! //! let (_resp, mut connection) = awc::Client::new() //! .ws("ws://echo.websocket.org") //! .connect() @@ -89,8 +93,9 @@ //! connection //! .send(awc::ws::Message::Text("Echo".into())) //! .await?; +//! //! let response = connection.next().await.unwrap()?; -//! # assert_eq!(response, awc::ws::Frame::Text("Echo".as_bytes().into())); +//! assert_eq!(response, awc::ws::Frame::Text("Echo".into())); //! # Ok(()) //! # } //! ``` diff --git a/awc/src/middleware/redirect.rs b/awc/src/middleware/redirect.rs index ac6690471..d48822168 100644 --- a/awc/src/middleware/redirect.rs +++ b/awc/src/middleware/redirect.rs @@ -161,7 +161,8 @@ where | StatusCode::SEE_OTHER | StatusCode::TEMPORARY_REDIRECT | StatusCode::PERMANENT_REDIRECT - if *max_redirect_times > 0 => + if *max_redirect_times > 0 + && res.headers().contains_key(header::LOCATION) => { let reuse_body = res.head().status == StatusCode::TEMPORARY_REDIRECT || res.head().status == StatusCode::PERMANENT_REDIRECT; @@ -245,26 +246,32 @@ where } fn build_next_uri(res: &ClientResponse, prev_uri: &Uri) -> Result { - let uri = res - .headers() - .get(header::LOCATION) - .map(|value| { - // try to parse the location to a full uri - let uri = Uri::try_from(value.as_bytes()) - .map_err(|e| SendRequestError::Url(InvalidUrl::HttpError(e.into())))?; - if uri.scheme().is_none() || uri.authority().is_none() { - let uri = Uri::builder() - .scheme(prev_uri.scheme().cloned().unwrap()) - .authority(prev_uri.authority().cloned().unwrap()) - .path_and_query(value.as_bytes()) - .build()?; - Ok::<_, SendRequestError>(uri) - } else { - Ok(uri) - } - }) - // TODO: this error type is wrong. - .ok_or(SendRequestError::Url(InvalidUrl::MissingScheme))??; + // responses without this header are not processed + let location = res.headers().get(header::LOCATION).unwrap(); + + // try to parse the location and resolve to a full URI but fall back to default if it fails + let uri = Uri::try_from(location.as_bytes()).unwrap_or_else(|_| Uri::default()); + + let uri = if uri.scheme().is_none() || uri.authority().is_none() { + let builder = Uri::builder() + .scheme(prev_uri.scheme().cloned().unwrap()) + .authority(prev_uri.authority().cloned().unwrap()); + + // when scheme or authority is missing treat the location value as path and query + // recover error where location does not have leading slash + let path = if location.as_bytes().starts_with(b"/") { + location.as_bytes().to_owned() + } else { + [b"/", location.as_bytes()].concat() + }; + + builder + .path_and_query(path) + .build() + .map_err(|err| SendRequestError::Url(InvalidUrl::HttpError(err)))? + } else { + uri + }; Ok(uri) } @@ -287,10 +294,13 @@ mod tests { use actix_web::{web, App, Error, HttpRequest, HttpResponse}; use super::*; - use crate::{http::header::HeaderValue, ClientBuilder}; + use crate::{ + http::{header::HeaderValue, StatusCode}, + ClientBuilder, + }; #[actix_rt::test] - async fn test_basic_redirect() { + async fn basic_redirect() { let client = ClientBuilder::new() .disable_redirects() .wrap(Redirect::new().max_redirect_times(10)) @@ -315,6 +325,44 @@ mod tests { assert_eq!(res.status().as_u16(), 400); } + #[actix_rt::test] + async fn redirect_relative_without_leading_slash() { + let client = ClientBuilder::new().finish(); + + let srv = actix_test::start(|| { + App::new() + .service(web::resource("/").route(web::to(|| async { + HttpResponse::Found() + .insert_header(("location", "abc/")) + .finish() + }))) + .service( + web::resource("/abc/") + .route(web::to(|| async { HttpResponse::Accepted().finish() })), + ) + }); + + let res = client.get(srv.url("/")).send().await.unwrap(); + assert_eq!(res.status(), StatusCode::ACCEPTED); + } + + #[actix_rt::test] + async fn redirect_without_location() { + let client = ClientBuilder::new() + .disable_redirects() + .wrap(Redirect::new().max_redirect_times(10)) + .finish(); + + let srv = actix_test::start(|| { + App::new().service(web::resource("/").route(web::to(|| async { + Ok::<_, Error>(HttpResponse::Found().finish()) + }))) + }); + + let res = client.get(srv.url("/")).send().await.unwrap(); + assert_eq!(res.status(), StatusCode::FOUND); + } + #[actix_rt::test] async fn test_redirect_limit() { let client = ClientBuilder::new() @@ -328,14 +376,14 @@ mod tests { .service(web::resource("/").route(web::to(|| async { Ok::<_, Error>( HttpResponse::Found() - .append_header(("location", "/test")) + .insert_header(("location", "/test")) .finish(), ) }))) .service(web::resource("/test").route(web::to(|| async { Ok::<_, Error>( HttpResponse::Found() - .append_header(("location", "/test2")) + .insert_header(("location", "/test2")) .finish(), ) }))) @@ -345,8 +393,15 @@ mod tests { }); let res = client.get(srv.url("/")).send().await.unwrap(); - - assert_eq!(res.status().as_u16(), 302); + assert_eq!(res.status(), StatusCode::FOUND); + assert_eq!( + res.headers() + .get(header::LOCATION) + .unwrap() + .to_str() + .unwrap(), + "/test2" + ); } #[actix_rt::test] diff --git a/awc/src/request.rs b/awc/src/request.rs index 8bcf1ee01..102db3c16 100644 --- a/awc/src/request.rs +++ b/awc/src/request.rs @@ -505,7 +505,7 @@ impl fmt::Debug for ClientRequest { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { writeln!( f, - "\nClientRequest {:?} {}:{}", + "\nClientRequest {:?} {} {}", self.head.version, self.head.method, self.head.uri )?; writeln!(f, " headers:")?; From be986d96b387f9a040904a6385e9500a4eb5bb8f Mon Sep 17 00:00:00 2001 From: Dylan DPC <99973273+Dylan-DPC@users.noreply.github.com> Date: Tue, 8 Mar 2022 18:42:42 +0100 Subject: [PATCH 108/170] bump `regex` requirement to `1.5.5` due to security advisory (#2687) --- actix-web/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actix-web/Cargo.toml b/actix-web/Cargo.toml index 7bbeec64d..093c000b4 100644 --- a/actix-web/Cargo.toml +++ b/actix-web/Cargo.toml @@ -90,7 +90,7 @@ once_cell = "1.5" log = "0.4" mime = "0.3" pin-project-lite = "0.2.7" -regex = "1.4" +regex = "1.5.5" serde = "1.0" serde_json = "1.0" serde_urlencoded = "0.7" From dce943851802ea792a3fd233110f011b7b7a1d6a Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Wed, 9 Mar 2022 18:11:06 +0000 Subject: [PATCH 109/170] document with ws feature --- actix-http/Cargo.toml | 2 +- actix-http/src/payload.rs | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 7006d92d7..8ac3465a2 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -20,7 +20,7 @@ edition = "2018" [package.metadata.docs.rs] # features that docs.rs will build with -features = ["http2", "openssl", "rustls", "compress-brotli", "compress-gzip", "compress-zstd"] +features = ["http2", "ws", "openssl", "rustls", "compress-brotli", "compress-gzip", "compress-zstd"] [lib] name = "actix_http" diff --git a/actix-http/src/payload.rs b/actix-http/src/payload.rs index 33d9ec6f5..ee0128af4 100644 --- a/actix-http/src/payload.rs +++ b/actix-http/src/payload.rs @@ -13,7 +13,8 @@ use crate::error::PayloadError; /// A boxed payload stream. pub type BoxedPayloadStream = Pin>>>; -#[deprecated(since = "4.0.0", note = "Renamed to `BoxedPayloadStream`.")] +#[doc(hidden)] +#[deprecated(since = "3.0.0", note = "Renamed to `BoxedPayloadStream`.")] pub type PayloadStream = BoxedPayloadStream; #[cfg(not(feature = "http2"))] From 5611b98c0d46537bcb330774c74f814cde6bad31 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Wed, 9 Mar 2022 18:13:39 +0000 Subject: [PATCH 110/170] prepare actix-http release 3.0.4 --- actix-http/CHANGES.md | 5 +++++ actix-http/Cargo.toml | 2 +- actix-http/README.md | 4 ++-- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index ab7f1e332..71132c6b2 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -3,6 +3,11 @@ ## Unreleased - 2021-xx-xx +## 3.0.4 - 2022-03-09 +### Fixed +- Document on docs.rs with `ws` feature enabled. + + ## 3.0.3 - 2022-03-08 ### Fixed - Allow spaces between header name and colon when parsing responses. [#2684] diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 8ac3465a2..7c9284836 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-http" -version = "3.0.3" +version = "3.0.4" authors = [ "Nikolay Kim ", "Rob Ede ", diff --git a/actix-http/README.md b/actix-http/README.md index afe445ebd..bf0b7c824 100644 --- a/actix-http/README.md +++ b/actix-http/README.md @@ -3,11 +3,11 @@ > HTTP primitives for the Actix ecosystem. [![crates.io](https://img.shields.io/crates/v/actix-http?label=latest)](https://crates.io/crates/actix-http) -[![Documentation](https://docs.rs/actix-http/badge.svg?version=3.0.3)](https://docs.rs/actix-http/3.0.3) +[![Documentation](https://docs.rs/actix-http/badge.svg?version=3.0.4)](https://docs.rs/actix-http/3.0.4) [![Version](https://img.shields.io/badge/rustc-1.54+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.54.0.html) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-http.svg)
-[![dependency status](https://deps.rs/crate/actix-http/3.0.3/status.svg)](https://deps.rs/crate/actix-http/3.0.3) +[![dependency status](https://deps.rs/crate/actix-http/3.0.4/status.svg)](https://deps.rs/crate/actix-http/3.0.4) [![Download](https://img.shields.io/crates/d/actix-http.svg)](https://crates.io/crates/actix-http) [![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) From a35804b89f5b08b11304d4fa3e4ca37c9a4f6627 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Thu, 10 Mar 2022 01:05:03 +0000 Subject: [PATCH 111/170] update files tokio-uring to 0.3 --- actix-files/CHANGES.md | 3 ++- actix-files/Cargo.toml | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/actix-files/CHANGES.md b/actix-files/CHANGES.md index 4d4c790e8..2fdc7ba34 100644 --- a/actix-files/CHANGES.md +++ b/actix-files/CHANGES.md @@ -1,7 +1,8 @@ # Changes ## Unreleased - 2021-xx-xx -- Add support for streaming audio files by setting the `content-disposition` header `inline` instead of `attachement`. [#2645] +- Update `tokio-uring` dependency to `0.3`. +- Audio files now use `Content-Disposition: inline` instead of `attachment`. [#2645] [#2645]: https://github.com/actix/actix-web/pull/2645 diff --git a/actix-files/Cargo.toml b/actix-files/Cargo.toml index e7e6aea23..8f856c109 100644 --- a/actix-files/Cargo.toml +++ b/actix-files/Cargo.toml @@ -39,10 +39,12 @@ mime_guess = "2.0.1" percent-encoding = "2.1" pin-project-lite = "0.2.7" +# experimental-io-uring tokio-uring = { version = "0.2", optional = true, features = ["bytes"] } +actix-server = "2.1" # ensure matching tokio-uring versions [dev-dependencies] -actix-rt = "2.2" +actix-rt = "2.7" actix-test = "0.1.0-beta.13" actix-web = "4.0.0" tempfile = "3.2" From 1fd90f0b1098bf6b6bf7219b74e7b8adb29c851f Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Thu, 10 Mar 2022 01:29:26 +0000 Subject: [PATCH 112/170] Implement getters for named file fields (#2689) Co-authored-by: Janis Goldschmidt --- actix-files/CHANGES.md | 2 ++ actix-files/src/named.rs | 55 +++++++++++++++++++++++++++++++++++----- 2 files changed, 51 insertions(+), 6 deletions(-) diff --git a/actix-files/CHANGES.md b/actix-files/CHANGES.md index 2fdc7ba34..7e99c2ae1 100644 --- a/actix-files/CHANGES.md +++ b/actix-files/CHANGES.md @@ -1,9 +1,11 @@ # Changes ## Unreleased - 2021-xx-xx +- Add `NamedFile::{modified, metadata, content_type, content_disposition, encoding}()` getters. [#2021] - Update `tokio-uring` dependency to `0.3`. - Audio files now use `Content-Disposition: inline` instead of `attachment`. [#2645] +[#2021]: https://github.com/actix/actix-web/pull/2021 [#2645]: https://github.com/actix/actix-web/pull/2645 diff --git a/actix-files/src/named.rs b/actix-files/src/named.rs index 6f3c6e1c8..7ab29e5c8 100644 --- a/actix-files/src/named.rs +++ b/actix-files/src/named.rs @@ -23,6 +23,7 @@ use actix_web::{ use bitflags::bitflags; use derive_more::{Deref, DerefMut}; use futures_core::future::LocalBoxFuture; +use mime::Mime; use mime_guess::from_path; use crate::{encoding::equiv_utf8_text, range::HttpRange}; @@ -76,8 +77,8 @@ pub struct NamedFile { pub(crate) md: Metadata, pub(crate) flags: Flags, pub(crate) status_code: StatusCode, - pub(crate) content_type: mime::Mime, - pub(crate) content_disposition: header::ContentDisposition, + pub(crate) content_type: Mime, + pub(crate) content_disposition: ContentDisposition, pub(crate) encoding: Option, } @@ -238,13 +239,13 @@ impl NamedFile { Self::from_file(file, path) } - /// Returns reference to the underlying `File` object. + /// Returns reference to the underlying file object. #[inline] pub fn file(&self) -> &File { &self.file } - /// Retrieve the path of this file. + /// Returns the filesystem path to this file. /// /// # Examples /// ``` @@ -262,6 +263,48 @@ impl NamedFile { self.path.as_path() } + /// Returns the time the file was last modified. + /// + /// Returns `None` only on unsupported platforms; see [`std::fs::Metadata::modified()`]. + /// Therefore, it is usually safe to unwrap this. + #[inline] + pub fn modified(&self) -> Option { + self.modified + } + + /// Returns the filesystem metadata associated with this file. + #[inline] + pub fn metadata(&self) -> &Metadata { + &self.md + } + + /// Returns the `Content-Type` header that will be used when serving this file. + #[inline] + pub fn content_type(&self) -> &Mime { + &self.content_type + } + + /// Returns the `Content-Disposition` that will be used when serving this file. + #[inline] + pub fn content_disposition(&self) -> &ContentDisposition { + &self.content_disposition + } + + /// Returns the `Content-Encoding` that will be used when serving this file. + /// + /// A return value of `None` indicates that the content is not already using a compressed + /// representation and may be subject to compression downstream. + #[inline] + pub fn content_encoding(&self) -> Option { + self.encoding + } + + /// Returns the status code for serving this file. + #[inline] + pub fn status_code(&self) -> &StatusCode { + &self.status_code + } + /// Set response **Status Code** pub fn set_status_code(mut self, status: StatusCode) -> Self { self.status_code = status; @@ -271,7 +314,7 @@ impl NamedFile { /// Set the MIME Content-Type for serving this file. By default the Content-Type is inferred /// from the filename extension. #[inline] - pub fn set_content_type(mut self, mime_type: mime::Mime) -> Self { + pub fn set_content_type(mut self, mime_type: Mime) -> Self { self.content_type = mime_type; self } @@ -284,7 +327,7 @@ impl NamedFile { /// filename is taken from the path provided in the `open` method after converting it to UTF-8 /// (using `to_string_lossy`). #[inline] - pub fn set_content_disposition(mut self, cd: header::ContentDisposition) -> Self { + pub fn set_content_disposition(mut self, cd: ContentDisposition) -> Self { self.content_disposition = cd; self.flags.insert(Flags::CONTENT_DISPOSITION); self From 745e738955e7b1572f970fb25dfb4de9bb61b985 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Thu, 10 Mar 2022 02:36:57 +0000 Subject: [PATCH 113/170] fix negative impl assertion on 1.60+ see https://github.com/rust-lang/rust/issues/94791 --- actix-files/src/named.rs | 5 ++--- actix-http/Cargo.toml | 1 + actix-http/src/h1/payload.rs | 3 +++ 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/actix-files/src/named.rs b/actix-files/src/named.rs index 7ab29e5c8..459670c3a 100644 --- a/actix-files/src/named.rs +++ b/actix-files/src/named.rs @@ -210,11 +210,10 @@ impl NamedFile { Self::from_file(file, path) } - #[allow(rustdoc::broken_intra_doc_links)] /// Attempts to open a file asynchronously in read-only mode. /// - /// When the `experimental-io-uring` crate feature is enabled, this will be async. - /// Otherwise, it will be just like [`open`][Self::open]. + /// When the `experimental-io-uring` crate feature is enabled, this will be async. Otherwise, it + /// will behave just like `open`. /// /// # Examples /// ``` diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 7c9284836..a063bd1b9 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -110,6 +110,7 @@ memchr = "2.4" once_cell = "1.9" rcgen = "0.8" regex = "1.3" +rustversion = "1" rustls-pemfile = "0.2" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" diff --git a/actix-http/src/h1/payload.rs b/actix-http/src/h1/payload.rs index 4d031c15a..5a93e9051 100644 --- a/actix-http/src/h1/payload.rs +++ b/actix-http/src/h1/payload.rs @@ -263,7 +263,10 @@ mod tests { assert_not_impl_any!(Payload: Send, Sync, UnwindSafe, RefUnwindSafe); assert_impl_all!(Inner: Unpin, Send, Sync); + #[rustversion::before(1.60)] assert_not_impl_any!(Inner: UnwindSafe, RefUnwindSafe); + #[rustversion::since(1.60)] + assert_impl_all!(Inner: UnwindSafe, RefUnwindSafe); #[actix_rt::test] async fn test_unread_data() { From a03a2a0076d16dd80467b6c91697baabfeb66ddf Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Thu, 10 Mar 2022 02:54:06 +0000 Subject: [PATCH 114/170] deprecate `NamedFile::set_status_code` --- actix-files/src/lib.rs | 27 +++++++++++++++++++++++++-- actix-files/src/named.rs | 17 ++++++----------- 2 files changed, 31 insertions(+), 13 deletions(-) diff --git a/actix-files/src/lib.rs b/actix-files/src/lib.rs index 41113f2ab..40327e5e8 100644 --- a/actix-files/src/lib.rs +++ b/actix-files/src/lib.rs @@ -364,20 +364,43 @@ mod tests { ); } + #[allow(deprecated)] #[actix_rt::test] - async fn test_named_file_status_code_text() { - let mut file = NamedFile::open_async("Cargo.toml") + async fn status_code_customize_same_output() { + let file1 = NamedFile::open_async("Cargo.toml") .await .unwrap() .set_status_code(StatusCode::NOT_FOUND); + + let file2 = NamedFile::open_async("Cargo.toml") + .await + .unwrap() + .customize() + .with_status(StatusCode::NOT_FOUND); + + let req = TestRequest::default().to_http_request(); + let res1 = file1.respond_to(&req); + let res2 = file2.respond_to(&req); + + assert_eq!(res1.status(), StatusCode::NOT_FOUND); + assert_eq!(res2.status(), StatusCode::NOT_FOUND); + } + + #[actix_rt::test] + async fn test_named_file_status_code_text() { + let mut file = NamedFile::open_async("Cargo.toml").await.unwrap(); + { file.file(); let _f: &File = &file; } + { let _f: &mut File = &mut file; } + let file = file.customize().with_status(StatusCode::NOT_FOUND); + let req = TestRequest::default().to_http_request(); let resp = file.respond_to(&req); assert_eq!( diff --git a/actix-files/src/named.rs b/actix-files/src/named.rs index 459670c3a..5580e6f7e 100644 --- a/actix-files/src/named.rs +++ b/actix-files/src/named.rs @@ -298,20 +298,15 @@ impl NamedFile { self.encoding } - /// Returns the status code for serving this file. - #[inline] - pub fn status_code(&self) -> &StatusCode { - &self.status_code - } - - /// Set response **Status Code** + /// Set response status code. + #[deprecated(since = "0.7.0", note = "Prefer `Responder::customize()`.")] pub fn set_status_code(mut self, status: StatusCode) -> Self { self.status_code = status; self } - /// Set the MIME Content-Type for serving this file. By default the Content-Type is inferred - /// from the filename extension. + /// Sets the `Content-Type` header that will be used when serving this file. By default the + /// `Content-Type` is inferred from the filename extension. #[inline] pub fn set_content_type(mut self, mime_type: Mime) -> Self { self.content_type = mime_type; @@ -332,9 +327,9 @@ impl NamedFile { self } - /// Disable `Content-Disposition` header. + /// Disables `Content-Disposition` header. /// - /// By default Content-Disposition` header is enabled. + /// By default, the `Content-Disposition` header is sent. #[inline] pub fn disable_content_disposition(mut self) -> Self { self.flags.remove(Flags::CONTENT_DISPOSITION); From 80d222aa78717893d98e6f6982b16a7a8d7ee95c Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Thu, 10 Mar 2022 03:12:29 +0000 Subject: [PATCH 115/170] use tracing in actix-http --- actix-http/Cargo.toml | 4 +-- actix-http/README.md | 2 +- actix-http/examples/echo.rs | 3 +- actix-http/examples/hello-world.rs | 9 +++-- actix-http/examples/streaming-error.rs | 3 +- actix-http/examples/ws.rs | 9 ++--- actix-http/src/encoding/encoder.rs | 9 ++--- actix-http/src/h1/chunked.rs | 5 +-- actix-http/src/h1/decoder.rs | 2 +- actix-http/src/h1/dispatcher.rs | 49 +++++++++++++------------- actix-http/src/h1/service.rs | 9 ++--- actix-http/src/h1/timer.rs | 7 ++-- actix-http/src/h2/dispatcher.rs | 4 +-- actix-http/src/h2/service.rs | 4 +-- actix-http/src/service.rs | 11 +++--- actix-http/src/ws/codec.rs | 3 +- actix-http/src/ws/dispatcher.rs | 2 +- actix-http/src/ws/frame.rs | 2 +- actix-http/src/ws/proto.rs | 4 ++- actix-http/tests/test_rustls.rs | 2 ++ actix-web/src/server.rs | 6 ++-- 21 files changed, 80 insertions(+), 69 deletions(-) diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index a063bd1b9..6d410e46f 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -73,11 +73,11 @@ httparse = "1.5.1" httpdate = "1.0.1" itoa = "1" language-tags = "0.3" -log = "0.4" mime = "0.3" percent-encoding = "2.1" pin-project-lite = "0.2" smallvec = "1.6.1" +tracing = { version = "0.1.30", default-features = false, features = ["log"] } # http2 h2 = { version = "0.3.9", optional = true } @@ -121,7 +121,7 @@ tokio = { version = "1.8.4", features = ["net", "rt", "macros"] } [[example]] name = "ws" -required-features = ["rustls"] +required-features = ["ws", "rustls"] [[bench]] name = "write-camel-case" diff --git a/actix-http/README.md b/actix-http/README.md index bf0b7c824..14a7013db 100644 --- a/actix-http/README.md +++ b/actix-http/README.md @@ -25,7 +25,7 @@ use actix_http::{HttpService, Response}; use actix_server::Server; use futures_util::future; use http::header::HeaderValue; -use log::info; +use tracing::info; #[actix_rt::main] async fn main() -> io::Result<()> { diff --git a/actix-http/examples/echo.rs b/actix-http/examples/echo.rs index 58de64530..ae6f00cce 100644 --- a/actix-http/examples/echo.rs +++ b/actix-http/examples/echo.rs @@ -5,6 +5,7 @@ use actix_server::Server; use bytes::BytesMut; use futures_util::StreamExt as _; use http::header::HeaderValue; +use tracing::info; #[actix_rt::main] async fn main() -> io::Result<()> { @@ -22,7 +23,7 @@ async fn main() -> io::Result<()> { body.extend_from_slice(&item?); } - log::info!("request body: {:?}", body); + info!("request body: {:?}", body); let res = Response::build(StatusCode::OK) .insert_header(("x-head", HeaderValue::from_static("dummy value!"))) diff --git a/actix-http/examples/hello-world.rs b/actix-http/examples/hello-world.rs index 1a83d4d9c..c749cdd00 100644 --- a/actix-http/examples/hello-world.rs +++ b/actix-http/examples/hello-world.rs @@ -1,9 +1,8 @@ use std::{convert::Infallible, io, time::Duration}; -use actix_http::{ - header::HeaderValue, HttpMessage, HttpService, Request, Response, StatusCode, -}; +use actix_http::{header::HeaderValue, HttpService, Request, Response, StatusCode}; use actix_server::Server; +use tracing::info; #[actix_rt::main] async fn main() -> io::Result<()> { @@ -18,12 +17,12 @@ async fn main() -> io::Result<()> { ext.insert(42u32); }) .finish(|req: Request| async move { - log::info!("{:?}", req); + info!("{:?}", req); let mut res = Response::build(StatusCode::OK); res.insert_header(("x-head", HeaderValue::from_static("dummy value!"))); - let forty_two = req.extensions().get::().unwrap().to_string(); + let forty_two = req.conn_data::().unwrap().to_string(); res.insert_header(( "x-forty-two", HeaderValue::from_str(&forty_two).unwrap(), diff --git a/actix-http/examples/streaming-error.rs b/actix-http/examples/streaming-error.rs index 3988cbac2..8c8a249cb 100644 --- a/actix-http/examples/streaming-error.rs +++ b/actix-http/examples/streaming-error.rs @@ -12,6 +12,7 @@ use actix_http::{body::BodyStream, HttpService, Response}; use actix_server::Server; use async_stream::stream; use bytes::Bytes; +use tracing::info; #[actix_rt::main] async fn main() -> io::Result<()> { @@ -21,7 +22,7 @@ async fn main() -> io::Result<()> { .bind("streaming-error", ("127.0.0.1", 8080), || { HttpService::build() .finish(|req| async move { - log::info!("{:?}", req); + info!("{:?}", req); let res = Response::ok(); Ok::<_, Infallible>(res.set_body(BodyStream::new(stream! { diff --git a/actix-http/examples/ws.rs b/actix-http/examples/ws.rs index d70e43314..c4f0503cd 100644 --- a/actix-http/examples/ws.rs +++ b/actix-http/examples/ws.rs @@ -17,6 +17,7 @@ use actix_server::Server; use bytes::{Bytes, BytesMut}; use bytestring::ByteString; use futures_core::{ready, Stream}; +use tracing::{info, trace}; #[actix_rt::main] async fn main() -> io::Result<()> { @@ -34,13 +35,13 @@ async fn main() -> io::Result<()> { } async fn handler(req: Request) -> Result>, Error> { - log::info!("handshaking"); + info!("handshaking"); let mut res = ws::handshake(req.head())?; // handshake will always fail under HTTP/2 - log::info!("responding"); - Ok(res.message_body(BodyStream::new(Heartbeat::new(ws::Codec::new())))?) + info!("responding"); + res.message_body(BodyStream::new(Heartbeat::new(ws::Codec::new()))) } struct Heartbeat { @@ -61,7 +62,7 @@ impl Stream for Heartbeat { type Item = Result; fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - log::trace!("poll"); + trace!("poll"); ready!(self.as_mut().interval.poll_tick(cx)); diff --git a/actix-http/src/encoding/encoder.rs b/actix-http/src/encoding/encoder.rs index 0c81ffe1b..0bbb1c106 100644 --- a/actix-http/src/encoding/encoder.rs +++ b/actix-http/src/encoding/encoder.rs @@ -17,6 +17,7 @@ use pin_project_lite::pin_project; #[cfg(feature = "compress-gzip")] use flate2::write::{GzEncoder, ZlibEncoder}; +use tracing::trace; #[cfg(feature = "compress-zstd")] use zstd::stream::write::Encoder as ZstdEncoder; @@ -356,7 +357,7 @@ impl ContentEncoder { ContentEncoder::Brotli(ref mut encoder) => match encoder.write_all(data) { Ok(_) => Ok(()), Err(err) => { - log::trace!("Error decoding br encoding: {}", err); + trace!("Error decoding br encoding: {}", err); Err(err) } }, @@ -365,7 +366,7 @@ impl ContentEncoder { ContentEncoder::Gzip(ref mut encoder) => match encoder.write_all(data) { Ok(_) => Ok(()), Err(err) => { - log::trace!("Error decoding gzip encoding: {}", err); + trace!("Error decoding gzip encoding: {}", err); Err(err) } }, @@ -374,7 +375,7 @@ impl ContentEncoder { ContentEncoder::Deflate(ref mut encoder) => match encoder.write_all(data) { Ok(_) => Ok(()), Err(err) => { - log::trace!("Error decoding deflate encoding: {}", err); + trace!("Error decoding deflate encoding: {}", err); Err(err) } }, @@ -383,7 +384,7 @@ impl ContentEncoder { ContentEncoder::Zstd(ref mut encoder) => match encoder.write_all(data) { Ok(_) => Ok(()), Err(err) => { - log::trace!("Error decoding ztsd encoding: {}", err); + trace!("Error decoding ztsd encoding: {}", err); Err(err) } }, diff --git a/actix-http/src/h1/chunked.rs b/actix-http/src/h1/chunked.rs index 7d0532fcd..c1dd4b283 100644 --- a/actix-http/src/h1/chunked.rs +++ b/actix-http/src/h1/chunked.rs @@ -1,6 +1,7 @@ use std::{io, task::Poll}; use bytes::{Buf as _, Bytes, BytesMut}; +use tracing::{debug, trace}; macro_rules! byte ( ($rdr:ident) => ({ @@ -76,7 +77,7 @@ impl ChunkedState { Poll::Ready(Ok(ChunkedState::Size)) } None => { - log::debug!("chunk size would overflow u64"); + debug!("chunk size would overflow u64"); Poll::Ready(Err(io::Error::new( io::ErrorKind::InvalidInput, "Invalid chunk size line: Size is too big", @@ -124,7 +125,7 @@ impl ChunkedState { rem: &mut u64, buf: &mut Option, ) -> Poll> { - log::trace!("Chunked read, remaining={:?}", rem); + trace!("Chunked read, remaining={:?}", rem); let len = rdr.len() as u64; if len == 0 { diff --git a/actix-http/src/h1/decoder.rs b/actix-http/src/h1/decoder.rs index 0e444756e..a9443997e 100644 --- a/actix-http/src/h1/decoder.rs +++ b/actix-http/src/h1/decoder.rs @@ -6,7 +6,7 @@ use http::{ header::{self, HeaderName, HeaderValue}, Method, StatusCode, Uri, Version, }; -use log::{debug, error, trace}; +use tracing::{debug, error, trace}; use super::chunked::ChunkedState; use crate::{error::ParseError, header::HeaderMap, ConnectionType, Request, ResponseHead}; diff --git a/actix-http/src/h1/dispatcher.rs b/actix-http/src/h1/dispatcher.rs index 648cf14d7..dea8a4beb 100644 --- a/actix-http/src/h1/dispatcher.rs +++ b/actix-http/src/h1/dispatcher.rs @@ -15,6 +15,7 @@ use bitflags::bitflags; use bytes::{Buf, BytesMut}; use futures_core::ready; use pin_project_lite::pin_project; +use tracing::{debug, error, trace}; use crate::{ body::{BodySize, BoxBody, MessageBody}, @@ -336,7 +337,7 @@ where while written < len { match io.as_mut().poll_write(cx, &write_buf[written..])? { Poll::Ready(0) => { - log::error!("write zero; closing"); + error!("write zero; closing"); return Poll::Ready(Err(io::Error::new(io::ErrorKind::WriteZero, ""))); } @@ -568,7 +569,7 @@ where } StateProj::ExpectCall { fut } => { - log::trace!(" calling expect service"); + trace!(" calling expect service"); match fut.poll(cx) { // expect resolved. write continue to buffer and set InnerDispatcher state @@ -697,12 +698,12 @@ where let mut this = self.as_mut().project(); if can_not_read { - log::debug!("cannot read request payload"); + debug!("cannot read request payload"); if let Some(sender) = &this.payload { // ...maybe handler does not want to read any more payload... if let PayloadStatus::Dropped = sender.need_read(cx) { - log::debug!("handler dropped payload early; attempt to clean connection"); + debug!("handler dropped payload early; attempt to clean connection"); // ...in which case poll request payload a few times loop { match this.codec.decode(this.read_buf)? { @@ -716,7 +717,7 @@ where // connection is in clean state for next request Message::Chunk(None) => { - log::debug!("connection successfully cleaned"); + debug!("connection successfully cleaned"); // reset dispatcher state let _ = this.payload.take(); @@ -737,7 +738,7 @@ where // not enough info to decide if connection is going to be clean or not None => { - log::error!( + error!( "handler did not read whole payload and dispatcher could not \ drain read buf; return 500 and close connection" ); @@ -813,7 +814,7 @@ where if let Some(ref mut payload) = this.payload { payload.feed_data(chunk); } else { - log::error!("Internal server error: unexpected payload chunk"); + error!("Internal server error: unexpected payload chunk"); this.flags.insert(Flags::READ_DISCONNECT); this.messages.push_back(DispatcherMessage::Error( Response::internal_server_error().drop_body(), @@ -827,7 +828,7 @@ where if let Some(mut payload) = this.payload.take() { payload.feed_eof(); } else { - log::error!("Internal server error: unexpected eof"); + error!("Internal server error: unexpected eof"); this.flags.insert(Flags::READ_DISCONNECT); this.messages.push_back(DispatcherMessage::Error( Response::internal_server_error().drop_body(), @@ -844,7 +845,7 @@ where Ok(None) => break, Err(ParseError::Io(err)) => { - log::trace!("I/O error: {}", &err); + trace!("I/O error: {}", &err); self.as_mut().client_disconnected(); this = self.as_mut().project(); *this.error = Some(DispatchError::Io(err)); @@ -852,7 +853,7 @@ where } Err(ParseError::TooLarge) => { - log::trace!("request head was too big; returning 431 response"); + trace!("request head was too big; returning 431 response"); if let Some(mut payload) = this.payload.take() { payload.set_error(PayloadError::Overflow); @@ -872,7 +873,7 @@ where } Err(err) => { - log::trace!("parse error {}", &err); + trace!("parse error {}", &err); if let Some(mut payload) = this.payload.take() { payload.set_error(PayloadError::EncodingCorrupted); @@ -903,7 +904,7 @@ where if timer.as_mut().poll(cx).is_ready() { // timeout on first request (slow request) return 408 - log::trace!( + trace!( "timed out on slow request; \ replying with 408 and closing connection" ); @@ -949,7 +950,7 @@ where // keep-alive timer has timed out if timer.as_mut().poll(cx).is_ready() { // no tasks at hand - log::trace!("timer timed out; closing connection"); + trace!("timer timed out; closing connection"); this.flags.insert(Flags::SHUTDOWN); if let Some(deadline) = this.config.client_disconnect_deadline() { @@ -979,7 +980,7 @@ where // timed-out during shutdown; drop connection if timer.as_mut().poll(cx).is_ready() { - log::trace!("timed-out during shutdown"); + trace!("timed-out during shutdown"); return Err(DispatchError::DisconnectTimeout); } } @@ -1138,12 +1139,12 @@ where match this.inner.project() { DispatcherStateProj::Upgrade { fut: upgrade } => upgrade.poll(cx).map_err(|err| { - log::error!("Upgrade handler error: {}", err); + error!("Upgrade handler error: {}", err); DispatchError::Upgrade }), DispatcherStateProj::Normal { mut inner } => { - log::trace!("start flags: {:?}", &inner.flags); + trace!("start flags: {:?}", &inner.flags); trace_timer_states( "start", @@ -1250,7 +1251,7 @@ where // client is gone if inner.flags.contains(Flags::WRITE_DISCONNECT) { - log::trace!("client is gone; disconnecting"); + trace!("client is gone; disconnecting"); return Poll::Ready(Ok(())); } @@ -1259,14 +1260,14 @@ where // read half is closed; we do not process any responses if inner_p.flags.contains(Flags::READ_DISCONNECT) && state_is_none { - log::trace!("read half closed; start shutdown"); + trace!("read half closed; start shutdown"); inner_p.flags.insert(Flags::SHUTDOWN); } // keep-alive and stream errors if state_is_none && inner_p.write_buf.is_empty() { if let Some(err) = inner_p.error.take() { - log::error!("stream error: {}", &err); + error!("stream error: {}", &err); return Poll::Ready(Err(err)); } @@ -1295,7 +1296,7 @@ where Poll::Pending }; - log::trace!("end flags: {:?}", &inner.flags); + trace!("end flags: {:?}", &inner.flags); poll } @@ -1310,17 +1311,17 @@ fn trace_timer_states( ka_timer: &TimerState, shutdown_timer: &TimerState, ) { - log::trace!("{} timers:", label); + trace!("{} timers:", label); if head_timer.is_enabled() { - log::trace!(" head {}", &head_timer); + trace!(" head {}", &head_timer); } if ka_timer.is_enabled() { - log::trace!(" keep-alive {}", &ka_timer); + trace!(" keep-alive {}", &ka_timer); } if shutdown_timer.is_enabled() { - log::trace!(" shutdown {}", &shutdown_timer); + trace!(" shutdown {}", &shutdown_timer); } } diff --git a/actix-http/src/h1/service.rs b/actix-http/src/h1/service.rs index 43b7919a7..a791ea8c3 100644 --- a/actix-http/src/h1/service.rs +++ b/actix-http/src/h1/service.rs @@ -13,6 +13,7 @@ use actix_service::{ }; use actix_utils::future::ready; use futures_core::future::LocalBoxFuture; +use tracing::error; use crate::{ body::{BoxBody, MessageBody}, @@ -305,13 +306,13 @@ where Box::pin(async move { let expect = expect .await - .map_err(|e| log::error!("Init http expect service error: {:?}", e))?; + .map_err(|e| error!("Init http expect service error: {:?}", e))?; let upgrade = match upgrade { Some(upgrade) => { let upgrade = upgrade .await - .map_err(|e| log::error!("Init http upgrade service error: {:?}", e))?; + .map_err(|e| error!("Init http upgrade service error: {:?}", e))?; Some(upgrade) } None => None, @@ -319,7 +320,7 @@ where let service = service .await - .map_err(|e| log::error!("Init http service error: {:?}", e))?; + .map_err(|e| error!("Init http service error: {:?}", e))?; Ok(H1ServiceHandler::new( cfg, @@ -357,7 +358,7 @@ where fn poll_ready(&self, cx: &mut Context<'_>) -> Poll> { self._poll_ready(cx).map_err(|err| { - log::error!("HTTP/1 service readiness error: {:?}", err); + error!("HTTP/1 service readiness error: {:?}", err); DispatchError::Service(err) }) } diff --git a/actix-http/src/h1/timer.rs b/actix-http/src/h1/timer.rs index bb69fdb80..0ea70a319 100644 --- a/actix-http/src/h1/timer.rs +++ b/actix-http/src/h1/timer.rs @@ -1,6 +1,7 @@ use std::{fmt, future::Future, pin::Pin, task::Context}; use actix_rt::time::{Instant, Sleep}; +use tracing::trace; #[derive(Debug)] pub(super) enum TimerState { @@ -24,7 +25,7 @@ impl TimerState { pub(super) fn set(&mut self, timer: Sleep, line: u32) { if matches!(self, Self::Disabled) { - log::trace!("setting disabled timer from line {}", line); + trace!("setting disabled timer from line {}", line); } *self = Self::Active { @@ -39,11 +40,11 @@ impl TimerState { pub(super) fn clear(&mut self, line: u32) { if matches!(self, Self::Disabled) { - log::trace!("trying to clear a disabled timer from line {}", line); + trace!("trying to clear a disabled timer from line {}", line); } if matches!(self, Self::Inactive) { - log::trace!("trying to clear an inactive timer from line {}", line); + trace!("trying to clear an inactive timer from line {}", line); } *self = Self::Inactive; diff --git a/actix-http/src/h2/dispatcher.rs b/actix-http/src/h2/dispatcher.rs index ce1be537f..85516cccc 100644 --- a/actix-http/src/h2/dispatcher.rs +++ b/actix-http/src/h2/dispatcher.rs @@ -19,8 +19,8 @@ use h2::{ server::{Connection, SendResponse}, Ping, PingPong, }; -use log::{error, trace}; use pin_project_lite::pin_project; +use tracing::{error, trace, warn}; use crate::{ body::{BodySize, BoxBody, MessageBody}, @@ -143,7 +143,7 @@ where DispatchError::SendResponse(err) => { trace!("Error sending HTTP/2 response: {:?}", err) } - DispatchError::SendData(err) => log::warn!("{:?}", err), + DispatchError::SendData(err) => warn!("{:?}", err), DispatchError::ResponseBody(err) => { error!("Response payload stream error: {:?}", err) } diff --git a/actix-http/src/h2/service.rs b/actix-http/src/h2/service.rs index 653982d37..e526918c7 100644 --- a/actix-http/src/h2/service.rs +++ b/actix-http/src/h2/service.rs @@ -14,7 +14,7 @@ use actix_service::{ }; use actix_utils::future::ready; use futures_core::{future::LocalBoxFuture, ready}; -use log::error; +use tracing::{error, trace}; use crate::{ body::{BoxBody, MessageBody}, @@ -355,7 +355,7 @@ where } Err(err) => { - log::trace!("H2 handshake error: {}", err); + trace!("H2 handshake error: {}", err); Poll::Ready(Err(err)) } }, diff --git a/actix-http/src/service.rs b/actix-http/src/service.rs index b220e55a4..f4fe625a3 100644 --- a/actix-http/src/service.rs +++ b/actix-http/src/service.rs @@ -15,6 +15,7 @@ use actix_service::{ }; use futures_core::{future::LocalBoxFuture, ready}; use pin_project_lite::pin_project; +use tracing::error; use crate::{ body::{BoxBody, MessageBody}, @@ -369,13 +370,13 @@ where Box::pin(async move { let expect = expect .await - .map_err(|e| log::error!("Init http expect service error: {:?}", e))?; + .map_err(|e| error!("Init http expect service error: {:?}", e))?; let upgrade = match upgrade { Some(upgrade) => { let upgrade = upgrade .await - .map_err(|e| log::error!("Init http upgrade service error: {:?}", e))?; + .map_err(|e| error!("Init http upgrade service error: {:?}", e))?; Some(upgrade) } None => None, @@ -383,7 +384,7 @@ where let service = service .await - .map_err(|e| log::error!("Init http service error: {:?}", e))?; + .map_err(|e| error!("Init http service error: {:?}", e))?; Ok(HttpServiceHandler::new( cfg, @@ -490,7 +491,7 @@ where fn poll_ready(&self, cx: &mut Context<'_>) -> Poll> { self._poll_ready(cx).map_err(|err| { - log::error!("HTTP service readiness error: {:?}", err); + error!("HTTP service readiness error: {:?}", err); DispatchError::Service(err) }) } @@ -666,7 +667,7 @@ where self.poll(cx) } Err(err) => { - log::trace!("H2 handshake error: {}", err); + tracing::trace!("H2 handshake error: {}", err); Poll::Ready(Err(err)) } } diff --git a/actix-http/src/ws/codec.rs b/actix-http/src/ws/codec.rs index 6e7aa7c11..3aa325d6a 100644 --- a/actix-http/src/ws/codec.rs +++ b/actix-http/src/ws/codec.rs @@ -2,6 +2,7 @@ use actix_codec::{Decoder, Encoder}; use bitflags::bitflags; use bytes::{Bytes, BytesMut}; use bytestring::ByteString; +use tracing::error; use super::{ frame::Parser, @@ -253,7 +254,7 @@ impl Decoder for Codec { } } _ => { - log::error!("Unfinished fragment {:?}", opcode); + error!("Unfinished fragment {:?}", opcode); Err(ProtocolError::ContinuationFragment(opcode)) } }; diff --git a/actix-http/src/ws/dispatcher.rs b/actix-http/src/ws/dispatcher.rs index 4c7470d37..2f6b2363b 100644 --- a/actix-http/src/ws/dispatcher.rs +++ b/actix-http/src/ws/dispatcher.rs @@ -73,8 +73,8 @@ mod inner { use actix_service::{IntoService, Service}; use futures_core::stream::Stream; use local_channel::mpsc; - use log::debug; use pin_project_lite::pin_project; + use tracing::debug; use actix_codec::{AsyncRead, AsyncWrite, Decoder, Encoder, Framed}; diff --git a/actix-http/src/ws/frame.rs b/actix-http/src/ws/frame.rs index 78cef1046..17e34e2ba 100644 --- a/actix-http/src/ws/frame.rs +++ b/actix-http/src/ws/frame.rs @@ -1,7 +1,7 @@ use std::convert::TryFrom; use bytes::{Buf, BufMut, BytesMut}; -use log::debug; +use tracing::debug; use super::{ mask::apply_mask, diff --git a/actix-http/src/ws/proto.rs b/actix-http/src/ws/proto.rs index 4227f221d..01fe9dd3c 100644 --- a/actix-http/src/ws/proto.rs +++ b/actix-http/src/ws/proto.rs @@ -3,6 +3,8 @@ use std::{ fmt, }; +use tracing::error; + /// Operation codes defined in [RFC 6455 ยง11.8]. /// /// [RFC 6455]: https://datatracker.ietf.org/doc/html/rfc6455#section-11.8 @@ -58,7 +60,7 @@ impl From for u8 { Ping => 9, Pong => 10, Bad => { - log::error!("Attempted to convert invalid opcode to u8. This is a bug."); + error!("Attempted to convert invalid opcode to u8. This is a bug."); 8 // if this somehow happens, a close frame will help us tear down quickly } } diff --git a/actix-http/tests/test_rustls.rs b/actix-http/tests/test_rustls.rs index 8e59ec65d..550375296 100644 --- a/actix-http/tests/test_rustls.rs +++ b/actix-http/tests/test_rustls.rs @@ -212,6 +212,7 @@ async fn h2_content_length() { let value = HeaderValue::from_static("0"); { + #[allow(clippy::single_element_loop)] for &i in &[0] { let req = srv .request(Method::HEAD, srv.surl(&format!("/{}", i))) @@ -226,6 +227,7 @@ async fn h2_content_length() { // assert_eq!(response.headers().get(&header), None); } + #[allow(clippy::single_element_loop)] for &i in &[1] { let req = srv .request(Method::GET, srv.surl(&format!("/{}", i))) diff --git a/actix-web/src/server.rs b/actix-web/src/server.rs index bdcfbf48a..99812600c 100644 --- a/actix-web/src/server.rs +++ b/actix-web/src/server.rs @@ -367,9 +367,7 @@ where .local_addr(addr); let svc = if let Some(handler) = on_connect_fn.clone() { - svc.on_connect_ext(move |io: &_, ext: _| { - (&*handler)(io as &dyn Any, ext) - }) + svc.on_connect_ext(move |io: &_, ext: _| (handler)(io as &dyn Any, ext)) } else { svc }; @@ -555,7 +553,7 @@ where if let Some(handler) = on_connect_fn.clone() { svc = svc - .on_connect_ext(move |io: &_, ext: _| (&*handler)(io as &dyn Any, ext)); + .on_connect_ext(move |io: &_, ext: _| (handler)(io as &dyn Any, ext)); } let fac = factory() From fe5279c77ae8c39792e5865a1478343ebf39b7ec Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Thu, 10 Mar 2022 03:14:14 +0000 Subject: [PATCH 116/170] use tracing in actix-router --- actix-router/Cargo.toml | 2 +- actix-router/src/resource.rs | 12 +++++------- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/actix-router/Cargo.toml b/actix-router/Cargo.toml index 502109114..6fcef125d 100644 --- a/actix-router/Cargo.toml +++ b/actix-router/Cargo.toml @@ -23,9 +23,9 @@ default = ["http"] bytestring = ">=0.1.5, <2" firestorm = "0.5" http = { version = "0.2.3", optional = true } -log = "0.4" regex = "1.5" serde = "1" +tracing = { version = "0.1.30", default-features = false, features = ["log"] } [dev-dependencies] criterion = { version = "0.3", features = ["html_reports"] } diff --git a/actix-router/src/resource.rs b/actix-router/src/resource.rs index c616b467a..3d121f369 100644 --- a/actix-router/src/resource.rs +++ b/actix-router/src/resource.rs @@ -7,6 +7,7 @@ use std::{ use firestorm::{profile_fn, profile_method, profile_section}; use regex::{escape, Regex, RegexSet}; +use tracing::error; use crate::{path::PathItem, IntoPatterns, Patterns, Resource, ResourcePath}; @@ -714,10 +715,7 @@ impl ResourceDef { if let Some(m) = captures.name(name) { segments[no] = PathItem::Segment(m.start() as u16, m.end() as u16); } else { - log::error!( - "Dynamic path match but not all segments found: {}", - name - ); + error!("Dynamic path match but not all segments found: {}", name); return false; } } @@ -744,7 +742,7 @@ impl ResourceDef { if let Some(m) = captures.name(name) { segments[no] = PathItem::Segment(m.start() as u16, m.end() as u16); } else { - log::error!("Dynamic path match but not all segments found: {}", name); + error!("Dynamic path match but not all segments found: {}", name); return false; } } @@ -1038,7 +1036,7 @@ impl ResourceDef { // tail segments in prefixes have no defined semantics #[cfg(not(test))] - log::warn!( + tracing::warn!( "Prefix resources should not have tail segments. \ Use `ResourceDef::new` constructor. \ This may become a panic in the future." @@ -1053,7 +1051,7 @@ impl ResourceDef { // unnamed tail segment #[cfg(not(test))] - log::warn!( + tracing::warn!( "Tail segments must have names. \ Consider `.../{{tail}}*`. \ This may become a panic in the future." From 592b40f914c0fc6fba6c7011edd9111b0e1258dd Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Thu, 10 Mar 2022 15:03:55 +0000 Subject: [PATCH 117/170] move io-uring tests to own job --- .github/workflows/ci.yml | 32 +++++++++++++++++++++++++------- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f41aa972f..7bb911f79 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -81,19 +81,37 @@ jobs: cargo test --lib --tests -p=actix-multipart --all-features cargo test --lib --tests -p=actix-web-actors --all-features + - name: Clear the cargo caches + run: | + cargo install cargo-cache --version 0.6.3 --no-default-features --features ci-autoclean + cargo-cache + + io-uring: + name: io-uring tests + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + + - name: Install Rust + uses: actions-rs/toolchain@v1 + with: + toolchain: stable-x86_64-unknown-linux-gnu + profile: minimal + override: true + + - name: Generate Cargo.lock + uses: actions-rs/cargo@v1 + with: { command: generate-lockfile } + - name: Cache Dependencies + uses: Swatinem/rust-cache@v1.3.0 + - name: tests (io-uring) - if: matrix.target.os == 'ubuntu-latest' timeout-minutes: 60 run: > sudo bash -c "ulimit -Sl 512 && ulimit -Hl 512 && PATH=$PATH:/usr/share/rust/.cargo/bin - && RUSTUP_TOOLCHAIN=${{ matrix.version }} cargo test --lib --tests -p=actix-files --all-features" - - - name: Clear the cargo caches - run: | - cargo install cargo-cache --version 0.6.3 --no-default-features --features ci-autoclean - cargo-cache + && RUSTUP_TOOLCHAIN=stable cargo test --lib --tests -p=actix-files --all-features" rustdoc: name: doc tests From 478b33b8a30d261a8fffb3fa9e1f5963a0595571 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Thu, 10 Mar 2022 16:00:15 +0000 Subject: [PATCH 118/170] remove nightly io-uring job --- .github/workflows/ci-post-merge.yml | 55 ++++++++++++----------------- 1 file changed, 23 insertions(+), 32 deletions(-) diff --git a/.github/workflows/ci-post-merge.yml b/.github/workflows/ci-post-merge.yml index d37b2c107..2857eb51e 100644 --- a/.github/workflows/ci-post-merge.yml +++ b/.github/workflows/ci-post-merge.yml @@ -78,15 +78,6 @@ jobs: cargo test --lib --tests -p=actix-multipart --all-features cargo test --lib --tests -p=actix-web-actors --all-features - - name: tests (io-uring) - if: matrix.target.os == 'ubuntu-latest' - timeout-minutes: 60 - run: > - sudo bash -c "ulimit -Sl 512 - && ulimit -Hl 512 - && PATH=$PATH:/usr/share/rust/.cargo/bin - && RUSTUP_TOOLCHAIN=${{ matrix.version }} cargo test --lib --tests -p=actix-files --all-features" - - name: Clear the cargo caches run: | cargo install cargo-cache --version 0.6.3 --no-default-features --features ci-autoclean @@ -126,32 +117,32 @@ jobs: with: { command: ci-check-all-feature-powerset-linux } # job currently (1st Feb 2022) segfaults - # coverage: - # name: coverage - # runs-on: ubuntu-latest - # steps: - # - uses: actions/checkout@v2 + coverage: + name: coverage + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 - # - name: Install stable - # uses: actions-rs/toolchain@v1 - # with: - # toolchain: stable-x86_64-unknown-linux-gnu - # profile: minimal - # override: true + - name: Install stable + uses: actions-rs/toolchain@v1 + with: + toolchain: stable-x86_64-unknown-linux-gnu + profile: minimal + override: true - # - name: Generate Cargo.lock - # uses: actions-rs/cargo@v1 - # with: { command: generate-lockfile } - # - name: Cache Dependencies - # uses: Swatinem/rust-cache@v1.2.0 + - name: Generate Cargo.lock + uses: actions-rs/cargo@v1 + with: { command: generate-lockfile } + - name: Cache Dependencies + uses: Swatinem/rust-cache@v1.2.0 - # - name: Generate coverage file - # run: | - # cargo install cargo-tarpaulin --vers "^0.13" - # cargo tarpaulin --workspace --features=rustls,openssl --out Xml --verbose - # - name: Upload to Codecov - # uses: codecov/codecov-action@v1 - # with: { file: cobertura.xml } + - name: Generate coverage file + run: | + cargo install cargo-tarpaulin --vers "^0.13" + cargo tarpaulin --workspace --features=rustls,openssl --out Xml --verbose + - name: Upload to Codecov + uses: codecov/codecov-action@v1 + with: { file: cobertura.xml } nextest: name: nextest From 7b27493e4c660f41dcf3cac8a9b0580acf8df4e2 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Thu, 10 Mar 2022 16:17:49 +0000 Subject: [PATCH 119/170] move coverage to own workflow --- .github/workflows/ci-post-merge.yml | 28 ---------------------- .github/workflows/coverage.yml | 36 +++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 28 deletions(-) create mode 100644 .github/workflows/coverage.yml diff --git a/.github/workflows/ci-post-merge.yml b/.github/workflows/ci-post-merge.yml index 2857eb51e..9fce98f4c 100644 --- a/.github/workflows/ci-post-merge.yml +++ b/.github/workflows/ci-post-merge.yml @@ -116,34 +116,6 @@ jobs: uses: actions-rs/cargo@v1 with: { command: ci-check-all-feature-powerset-linux } - # job currently (1st Feb 2022) segfaults - coverage: - name: coverage - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - - name: Install stable - uses: actions-rs/toolchain@v1 - with: - toolchain: stable-x86_64-unknown-linux-gnu - profile: minimal - override: true - - - name: Generate Cargo.lock - uses: actions-rs/cargo@v1 - with: { command: generate-lockfile } - - name: Cache Dependencies - uses: Swatinem/rust-cache@v1.2.0 - - - name: Generate coverage file - run: | - cargo install cargo-tarpaulin --vers "^0.13" - cargo tarpaulin --workspace --features=rustls,openssl --out Xml --verbose - - name: Upload to Codecov - uses: codecov/codecov-action@v1 - with: { file: cobertura.xml } - nextest: name: nextest runs-on: ubuntu-latest diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml new file mode 100644 index 000000000..137a413d0 --- /dev/null +++ b/.github/workflows/coverage.yml @@ -0,0 +1,36 @@ +# disabled because `cargo tarpaulin` currently segfaults + +name: Coverage + +on: + push: + branches: [master] + +jobs: + # job currently (1st Feb 2022) segfaults + coverage: + name: coverage + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + + - name: Install stable + uses: actions-rs/toolchain@v1 + with: + toolchain: stable-x86_64-unknown-linux-gnu + profile: minimal + override: true + + - name: Generate Cargo.lock + uses: actions-rs/cargo@v1 + with: { command: generate-lockfile } + - name: Cache Dependencies + uses: Swatinem/rust-cache@v1.2.0 + + - name: Generate coverage file + run: | + cargo install cargo-tarpaulin --vers "^0.13" + cargo tarpaulin --workspace --features=rustls,openssl --out Xml --verbose + - name: Upload to Codecov + uses: codecov/codecov-action@v1 + with: { file: cobertura.xml } From c58f287044c844b55a8767757c4f38d77b489a15 Mon Sep 17 00:00:00 2001 From: nikstur <61635709+nikstur@users.noreply.github.com> Date: Sun, 20 Mar 2022 22:36:19 +0100 Subject: [PATCH 120/170] Removed random superfluous whitespace (#2705) --- actix-web/src/app.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actix-web/src/app.rs b/actix-web/src/app.rs index d2df72714..18749d346 100644 --- a/actix-web/src/app.rs +++ b/actix-web/src/app.rs @@ -308,7 +308,7 @@ where /// Registers an app-wide middleware. /// - /// Registers middleware, in the form of a middleware compo nen t (type), that runs during + /// Registers middleware, in the form of a middleware component (type), that runs during /// inbound and/or outbound processing in the request life-cycle (request -> response), /// modifying request/response as necessary, across all requests managed by the `App`. /// From 09cffc093cd755d09a40a14f073f49015aa6e7ad Mon Sep 17 00:00:00 2001 From: mellowagain Date: Tue, 22 Mar 2022 16:30:06 +0100 Subject: [PATCH 121/170] Bump zstd to 0.11 (#2694) Co-authored-by: Rob Ede --- actix-http/Cargo.toml | 2 +- actix-web/Cargo.toml | 2 +- awc/Cargo.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 6d410e46f..cd5d3f379 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -94,7 +94,7 @@ actix-tls = { version = "3", default-features = false, optional = true } # compress-* brotli = { version = "3.3.3", optional = true } flate2 = { version = "1.0.13", optional = true } -zstd = { version = "0.10", optional = true } +zstd = { version = "0.11", optional = true } [dev-dependencies] actix-http-test = { version = "3.0.0-beta.13", features = ["openssl"] } diff --git a/actix-web/Cargo.toml b/actix-web/Cargo.toml index 093c000b4..7793fd8be 100644 --- a/actix-web/Cargo.toml +++ b/actix-web/Cargo.toml @@ -118,7 +118,7 @@ static_assertions = "1" tls-openssl = { package = "openssl", version = "0.10.9" } tls-rustls = { package = "rustls", version = "0.20.0" } tokio = { version = "1.13.1", features = ["rt-multi-thread", "macros"] } -zstd = "0.10" +zstd = "0.11" [[test]] name = "test_server" diff --git a/awc/Cargo.toml b/awc/Cargo.toml index 9dd29e4b7..ba0fc14e3 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -110,7 +110,7 @@ static_assertions = "1.1" rcgen = "0.8" rustls-pemfile = "0.2" tokio = { version = "1.13.1", features = ["rt-multi-thread", "macros"] } -zstd = "0.10" +zstd = "0.11" [[example]] name = "client" From e942d3e3b101cde08ccaa30f21020144be5522f8 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sat, 26 Mar 2022 13:26:08 +0000 Subject: [PATCH 122/170] update migration guide --- actix-web/MIGRATION-4.0.md | 22 ++++++++++++++++++++++ actix-web/src/data.rs | 7 ++----- actix-web/src/middleware/authors-guide.md | 3 +++ 3 files changed, 27 insertions(+), 5 deletions(-) diff --git a/actix-web/MIGRATION-4.0.md b/actix-web/MIGRATION-4.0.md index 7192d0bc6..fbeae0680 100644 --- a/actix-web/MIGRATION-4.0.md +++ b/actix-web/MIGRATION-4.0.md @@ -31,6 +31,7 @@ Headings marked with :warning: are **breaking behavioral changes**. They will pr - [Returning `HttpResponse` synchronously](#returning-httpresponse-synchronously) - [`#[actix_web::main]` and `#[tokio::main]`](#actix_webmain-and-tokiomain) - [`web::block`](#webblock) +- ## MSRV @@ -483,3 +484,24 @@ The `web::block` helper has changed return type from roughly `async fn(fn() -> R - let n: u32 = web::block(|| Ok(123)).await?; + let n: u32 = web::block(|| Ok(123)).await??; ``` + +## `HttpResponse` as a `ResponseError` + +The implementation of `ResponseError` for `HttpResponse` has been removed. + +It was common in v3 to use `HttpResponse` as an error type in fallible handlers. The problem is that `HttpResponse` contains no knowledge or reference to the source error. Being able to guarantee that an "error" response actually contains an error reference makes middleware and other parts of Actix Web more effective. + +The error response builders in the `error` module were available in v3 but are now the best method for simple error responses without requiring you to implement the trait on your own custom error types. These builders can receive simple strings and third party errors that can not implement the `ResponseError` trait. + +A few common patterns are affected by this change: + +```diff +- Err(HttpResponse::InternalServerError().finish()) ++ Err(error::ErrorInternalServerError("reason")) + +- Err(HttpResponse::InternalServerError().body(third_party_error.to_string())) ++ Err(error::ErrorInternalServerError(err)) + +- .map_err(|err| HttpResponse::InternalServerError().finish())? ++ .map_err(error::ErrorInternalServerError)? +``` diff --git a/actix-web/src/data.rs b/actix-web/src/data.rs index ce7b1fee6..a689d13e0 100644 --- a/actix-web/src/data.rs +++ b/actix-web/src/data.rs @@ -5,10 +5,7 @@ use actix_utils::future::{err, ok, Ready}; use futures_core::future::LocalBoxFuture; use serde::Serialize; -use crate::{ - dev::Payload, error::ErrorInternalServerError, extract::FromRequest, request::HttpRequest, - Error, -}; +use crate::{dev::Payload, error, Error, FromRequest, HttpRequest}; /// Data factory. pub(crate) trait DataFactory { @@ -160,7 +157,7 @@ impl FromRequest for Data { req.match_name().unwrap_or_else(|| req.path()) ); - err(ErrorInternalServerError( + err(error::ErrorInternalServerError( "Requested application data is not configured correctly. \ View/enable debug logs for more details.", )) diff --git a/actix-web/src/middleware/authors-guide.md b/actix-web/src/middleware/authors-guide.md index 344523a1a..a8d1edea4 100644 --- a/actix-web/src/middleware/authors-guide.md +++ b/actix-web/src/middleware/authors-guide.md @@ -11,3 +11,6 @@ ## Error Propagation ## When To (Not) Use Middleware + +## Author's References +- `EitherBody` + when is middleware appropriate: https://discord.com/channels/771444961383153695/952016890723729428 From 40048a581158053ee89ced4c7cc21804b828b271 Mon Sep 17 00:00:00 2001 From: Ali MJ Al-Nasrawy Date: Mon, 28 Mar 2022 23:58:35 +0300 Subject: [PATCH 123/170] rework actix_router::Quoter (#2709) Co-authored-by: Rob Ede --- actix-router/Cargo.toml | 5 + actix-router/benches/quoter.rs | 52 +++++++ actix-router/src/de.rs | 2 +- actix-router/src/quoter.rs | 268 ++++++++++++--------------------- actix-router/src/url.rs | 2 +- 5 files changed, 157 insertions(+), 172 deletions(-) create mode 100644 actix-router/benches/quoter.rs diff --git a/actix-router/Cargo.toml b/actix-router/Cargo.toml index 6fcef125d..76f39f631 100644 --- a/actix-router/Cargo.toml +++ b/actix-router/Cargo.toml @@ -32,7 +32,12 @@ criterion = { version = "0.3", features = ["html_reports"] } firestorm = { version = "0.5", features = ["enable_system_time"] } http = "0.2.5" serde = { version = "1", features = ["derive"] } +percent-encoding = "2.1" [[bench]] name = "router" harness = false + +[[bench]] +name = "quoter" +harness = false diff --git a/actix-router/benches/quoter.rs b/actix-router/benches/quoter.rs new file mode 100644 index 000000000..c18f1620e --- /dev/null +++ b/actix-router/benches/quoter.rs @@ -0,0 +1,52 @@ +use criterion::{black_box, criterion_group, criterion_main, Criterion}; + +use std::borrow::Cow; + +fn compare_quoters(c: &mut Criterion) { + let mut group = c.benchmark_group("Compare Quoters"); + + let quoter = actix_router::Quoter::new(b"", b""); + let path_quoted = (0..=0x7f) + .map(|c| format!("%{:02X}", c)) + .collect::(); + let path_unquoted = ('\u{00}'..='\u{7f}').collect::(); + + group.bench_function("quoter_unquoted", |b| { + b.iter(|| { + for _ in 0..10 { + black_box(quoter.requote(path_unquoted.as_bytes())); + } + }); + }); + + group.bench_function("percent_encode_unquoted", |b| { + b.iter(|| { + for _ in 0..10 { + let decode = percent_encoding::percent_decode(path_unquoted.as_bytes()); + black_box(Into::>::into(decode)); + } + }); + }); + + group.bench_function("quoter_quoted", |b| { + b.iter(|| { + for _ in 0..10 { + black_box(quoter.requote(path_quoted.as_bytes())); + } + }); + }); + + group.bench_function("percent_encode_quoted", |b| { + b.iter(|| { + for _ in 0..10 { + let decode = percent_encoding::percent_decode(path_quoted.as_bytes()); + black_box(Into::>::into(decode)); + } + }); + }); + + group.finish(); +} + +criterion_group!(benches, compare_quoters); +criterion_main!(benches); diff --git a/actix-router/src/de.rs b/actix-router/src/de.rs index efafd08db..55fcdc912 100644 --- a/actix-router/src/de.rs +++ b/actix-router/src/de.rs @@ -7,7 +7,7 @@ use crate::path::{Path, PathIter}; use crate::{Quoter, ResourcePath}; thread_local! { - static FULL_QUOTER: Quoter = Quoter::new(b"+/%", b""); + static FULL_QUOTER: Quoter = Quoter::new(b"", b""); } macro_rules! unsupported_type { diff --git a/actix-router/src/quoter.rs b/actix-router/src/quoter.rs index 8a1e99e1d..6c929d3ac 100644 --- a/actix-router/src/quoter.rs +++ b/actix-router/src/quoter.rs @@ -1,132 +1,89 @@ -#[allow(dead_code)] -const GEN_DELIMS: &[u8] = b":/?#[]@"; - -#[allow(dead_code)] -const SUB_DELIMS_WITHOUT_QS: &[u8] = b"!$'()*,"; - -#[allow(dead_code)] -const SUB_DELIMS: &[u8] = b"!$'()*,+?=;"; - -#[allow(dead_code)] -const RESERVED: &[u8] = b":/?#[]@!$'()*,+?=;"; - -#[allow(dead_code)] -const UNRESERVED: &[u8] = b"abcdefghijklmnopqrstuvwxyz - ABCDEFGHIJKLMNOPQRSTUVWXYZ - 1234567890 - -._~"; - -const ALLOWED: &[u8] = b"abcdefghijklmnopqrstuvwxyz - ABCDEFGHIJKLMNOPQRSTUVWXYZ - 1234567890 - -._~ - !$'()*,"; - -const QS: &[u8] = b"+&=;b"; - -/// A quoter +/// Partial percent-decoding. +/// +/// Performs percent-decoding on a slice but can selectively skip decoding certain sequences. +/// +/// # Examples +/// ``` +/// # use actix_router::Quoter; +/// // + is set as a protected character and will not be decoded... +/// let q = Quoter::new(&[], b"+"); +/// +/// // ...but the other encoded characters (like the hyphen below) will. +/// assert_eq!(q.requote(b"/a%2Db%2Bc").unwrap(), b"/a-b%2Bc"); +/// ``` pub struct Quoter { - /// Simple bit-map of safe values in the 0-127 ASCII range. - safe_table: [u8; 16], - /// Simple bit-map of protected values in the 0-127 ASCII range. - protected_table: [u8; 16], + protected_table: AsciiBitmap, } impl Quoter { - pub fn new(safe: &[u8], protected: &[u8]) -> Quoter { - let mut quoter = Quoter { - safe_table: [0; 16], - protected_table: [0; 16], - }; - - // prepare safe table - for ch in 0..128 { - if ALLOWED.contains(&ch) { - set_bit(&mut quoter.safe_table, ch); - } - - if QS.contains(&ch) { - set_bit(&mut quoter.safe_table, ch); - } - } - - for &ch in safe { - set_bit(&mut quoter.safe_table, ch) - } + /// Constructs a new `Quoter` instance given a set of protected ASCII bytes. + /// + /// The first argument is ignored but is kept for backward compatibility. + /// + /// # Panics + /// Panics if any of the `protected` bytes are not in the 0-127 ASCII range. + pub fn new(_: &[u8], protected: &[u8]) -> Quoter { + let mut protected_table = AsciiBitmap::default(); // prepare protected table for &ch in protected { - set_bit(&mut quoter.safe_table, ch); - set_bit(&mut quoter.protected_table, ch); + protected_table.set_bit(ch); } - quoter + Quoter { protected_table } } - /// Decodes safe percent-encoded sequences from `val`. - /// - /// Returns `None` when no modification to the original byte string was required. - /// - /// Non-ASCII bytes are accepted as valid input. - /// - /// Behavior for invalid/incomplete percent-encoding sequences is unspecified and may include - /// removing the invalid sequence from the output or passing it as-is. - pub fn requote(&self, val: &[u8]) -> Option> { - let mut has_pct = 0; - let mut pct = [b'%', 0, 0]; - let mut idx = 0; - let mut cloned: Option> = None; - - let len = val.len(); - - while idx < len { - let ch = val[idx]; - - if has_pct != 0 { - pct[has_pct] = val[idx]; - has_pct += 1; - - if has_pct == 3 { - has_pct = 0; - let buf = cloned.as_mut().unwrap(); - - if let Some(ch) = hex_pair_to_char(pct[1], pct[2]) { - if ch < 128 { - if bit_at(&self.protected_table, ch) { - buf.extend_from_slice(&pct); - idx += 1; - continue; - } - - if bit_at(&self.safe_table, ch) { - buf.push(ch); - idx += 1; - continue; - } - } - - buf.push(ch); - } else { - buf.extend_from_slice(&pct[..]); - } + /// Decodes the next escape sequence, if any, and advances `val`. + #[inline(always)] + fn decode_next<'a>(&self, val: &mut &'a [u8]) -> Option<(&'a [u8], u8)> { + for i in 0..val.len() { + if let (prev, [b'%', p1, p2, rem @ ..]) = val.split_at(i) { + if let Some(ch) = hex_pair_to_char(*p1, *p2) + // ignore protected ascii bytes + .filter(|&ch| !(ch < 128 && self.protected_table.bit_at(ch))) + { + *val = rem; + return Some((prev, ch)); } - } else if ch == b'%' { - has_pct = 1; - - if cloned.is_none() { - let mut c = Vec::with_capacity(len); - c.extend_from_slice(&val[..idx]); - cloned = Some(c); - } - } else if let Some(ref mut cloned) = cloned { - cloned.push(ch) } - - idx += 1; } - cloned + None + } + + /// Partially percent-decodes the given bytes. + /// + /// Escape sequences of the protected set are *not* decoded. + /// + /// Returns `None` when no modification to the original bytes was required. + /// + /// Invalid/incomplete percent-encoding sequences are passed unmodified. + pub fn requote(&self, val: &[u8]) -> Option> { + let mut remaining = val; + + // early return indicates that no percent-encoded sequences exist and we can skip allocation + let (pre, decoded_char) = self.decode_next(&mut remaining)?; + + // decoded output will always be shorter than the input + let mut decoded = Vec::::with_capacity(val.len()); + + // push first segment and decoded char + decoded.extend_from_slice(pre); + decoded.push(decoded_char); + + // decode and push rest of segments and decoded chars + while let Some((prev, ch)) = self.decode_next(&mut remaining) { + // this ugly conditional achieves +50% perf in cases where this is a tight loop. + if !prev.is_empty() { + decoded.extend_from_slice(prev); + } + decoded.push(ch); + } + + decoded.extend_from_slice(remaining); + + Some(decoded) } pub(crate) fn requote_str_lossy(&self, val: &str) -> Option { @@ -135,24 +92,6 @@ impl Quoter { } } -/// Converts an ASCII character in the hex-encoded set (`0-9`, `A-F`, `a-f`) to its integer -/// representation from `0x0`โ€“`0xF`. -/// -/// - `0x30 ('0') => 0x0` -/// - `0x39 ('9') => 0x9` -/// - `0x41 ('a') => 0xA` -/// - `0x61 ('A') => 0xA` -/// - `0x46 ('f') => 0xF` -/// - `0x66 ('F') => 0xF` -fn from_ascii_hex(v: u8) -> Option { - match v { - b'0'..=b'9' => Some(v - 0x30), // ord('0') == 0x30 - b'A'..=b'F' => Some(v - 0x41 + 10), // ord('A') == 0x41 - b'a'..=b'f' => Some(v - 0x61 + 10), // ord('a') == 0x61 - _ => None, - } -} - /// Decode a ASCII hex-encoded pair to an integer. /// /// Returns `None` if either portion of the decoded pair does not evaluate to a valid hex value. @@ -160,64 +99,52 @@ fn from_ascii_hex(v: u8) -> Option { /// - `0x33 ('3'), 0x30 ('0') => 0x30 ('0')` /// - `0x34 ('4'), 0x31 ('1') => 0x41 ('A')` /// - `0x36 ('6'), 0x31 ('1') => 0x61 ('a')` +#[inline(always)] fn hex_pair_to_char(d1: u8, d2: u8) -> Option { - let (d_high, d_low) = (from_ascii_hex(d1)?, from_ascii_hex(d2)?); + let d_high = char::from(d1).to_digit(16)?; + let d_low = char::from(d2).to_digit(16)?; // left shift high nibble by 4 bits - Some(d_high << 4 | d_low) + Some((d_high as u8) << 4 | (d_low as u8)) } -/// Sets bit in given bit-map to 1=true. -/// -/// # Panics -/// Panics if `ch` index is out of bounds. -fn set_bit(array: &mut [u8], ch: u8) { - array[(ch >> 3) as usize] |= 0b1 << (ch & 0b111) +#[derive(Debug, Default, Clone)] +struct AsciiBitmap { + array: [u8; 16], } -/// Returns true if bit to true in given bit-map. -/// -/// # Panics -/// Panics if `ch` index is out of bounds. -fn bit_at(array: &[u8], ch: u8) -> bool { - array[(ch >> 3) as usize] & (0b1 << (ch & 0b111)) != 0 +impl AsciiBitmap { + /// Sets bit in given bit-map to 1=true. + /// + /// # Panics + /// Panics if `ch` index is out of bounds. + fn set_bit(&mut self, ch: u8) { + self.array[(ch >> 3) as usize] |= 0b1 << (ch & 0b111) + } + + /// Returns true if bit to true in given bit-map. + /// + /// # Panics + /// Panics if `ch` index is out of bounds. + fn bit_at(&self, ch: u8) -> bool { + self.array[(ch >> 3) as usize] & (0b1 << (ch & 0b111)) != 0 + } } #[cfg(test)] mod tests { use super::*; - #[test] - fn hex_encoding() { - let hex = b"0123456789abcdefABCDEF"; - - for i in 0..256 { - let c = i as u8; - if hex.contains(&c) { - assert!(from_ascii_hex(c).is_some()) - } else { - assert!(from_ascii_hex(c).is_none()) - } - } - - let expected = [ - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 10, 11, 12, 13, 14, 15, - ]; - for i in 0..hex.len() { - assert_eq!(from_ascii_hex(hex[i]).unwrap(), expected[i]); - } - } - #[test] fn custom_quoter() { let q = Quoter::new(b"", b"+"); assert_eq!(q.requote(b"/a%25c").unwrap(), b"/a%c"); - assert_eq!(q.requote(b"/a%2Bc").unwrap(), b"/a%2Bc"); + assert_eq!(q.requote(b"/a%2Bc"), None); let q = Quoter::new(b"%+", b"/"); assert_eq!(q.requote(b"/a%25b%2Bc").unwrap(), b"/a%b+c"); - assert_eq!(q.requote(b"/a%2fb").unwrap(), b"/a%2fb"); - assert_eq!(q.requote(b"/a%2Fb").unwrap(), b"/a%2Fb"); + assert_eq!(q.requote(b"/a%2fb"), None); + assert_eq!(q.requote(b"/a%2Fb"), None); assert_eq!(q.requote(b"/a%0Ab").unwrap(), b"/a\nb"); assert_eq!(q.requote(b"/a%FE\xffb").unwrap(), b"/a\xfe\xffb"); assert_eq!(q.requote(b"/a\xfe\xffb"), None); @@ -233,7 +160,8 @@ mod tests { #[test] fn invalid_sequences() { let q = Quoter::new(b"%+", b"/"); - assert_eq!(q.requote(b"/a%2x%2X%%").unwrap(), b"/a%2x%2X"); + assert_eq!(q.requote(b"/a%2x%2X%%"), None); + assert_eq!(q.requote(b"/a%20%2X%%").unwrap(), b"/a %2X%%"); } #[test] diff --git a/actix-router/src/url.rs b/actix-router/src/url.rs index e7dda3fca..8ac033861 100644 --- a/actix-router/src/url.rs +++ b/actix-router/src/url.rs @@ -3,7 +3,7 @@ use crate::ResourcePath; use crate::Quoter; thread_local! { - static DEFAULT_QUOTER: Quoter = Quoter::new(b"@:", b"%/+"); + static DEFAULT_QUOTER: Quoter = Quoter::new(b"", b"%/+"); } #[derive(Debug, Clone, Default)] From 2fed9785972f964ecc1a27afca8705c3087aa17c Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Mon, 28 Mar 2022 22:44:32 +0100 Subject: [PATCH 124/170] remove -http TestRequest doc test --- actix-http/src/test.rs | 24 +----------------------- actix-web/src/test/test_request.rs | 7 ++++--- 2 files changed, 5 insertions(+), 26 deletions(-) diff --git a/actix-http/src/test.rs b/actix-http/src/test.rs index 6212c19d1..3815e64c6 100644 --- a/actix-http/src/test.rs +++ b/actix-http/src/test.rs @@ -19,29 +19,7 @@ use crate::{ Request, }; -/// Test `Request` builder -/// -/// ```ignore -/// # use http::{header, StatusCode}; -/// # use actix_web::*; -/// use actix_web::test::TestRequest; -/// -/// fn index(req: &HttpRequest) -> Response { -/// if let Some(hdr) = req.headers().get(header::CONTENT_TYPE) { -/// Response::Ok().into() -/// } else { -/// Response::BadRequest().into() -/// } -/// } -/// -/// let resp = TestRequest::default().insert_header("content-type", "text/plain") -/// .run(&index) -/// .unwrap(); -/// assert_eq!(resp.status(), StatusCode::OK); -/// -/// let resp = TestRequest::default().run(&index).unwrap(); -/// assert_eq!(resp.status(), StatusCode::BAD_REQUEST); -/// ``` +/// Test `Request` builder. pub struct TestRequest(Option); struct Inner { diff --git a/actix-web/src/test/test_request.rs b/actix-web/src/test/test_request.rs index a368d873f..2b60fca71 100644 --- a/actix-web/src/test/test_request.rs +++ b/actix-web/src/test/test_request.rs @@ -33,7 +33,7 @@ use crate::cookie::{Cookie, CookieJar}; /// use actix_web::{test, HttpRequest, HttpResponse, HttpMessage}; /// use actix_web::http::{header, StatusCode}; /// -/// async fn index(req: HttpRequest) -> HttpResponse { +/// async fn handler(req: HttpRequest) -> HttpResponse { /// if let Some(hdr) = req.headers().get(header::CONTENT_TYPE) { /// HttpResponse::Ok().into() /// } else { @@ -45,10 +45,11 @@ use crate::cookie::{Cookie, CookieJar}; /// # // force rustdoc to display the correct thing and also compile check the test /// # async fn _test() {} /// async fn test_index() { -/// let req = test::TestRequest::default().insert_header(header::ContentType::plaintext()) +/// let req = test::TestRequest::default() +/// .insert_header(header::ContentType::plaintext()) /// .to_http_request(); /// -/// let resp = index(req).await; +/// let resp = handler(req).await; /// assert_eq!(resp.status(), StatusCode::OK); /// /// let req = test::TestRequest::default().to_http_request(); From de9e41484af51ad47f8a42ff3191f93e565e6875 Mon Sep 17 00:00:00 2001 From: Luca Palmieri Date: Sat, 2 Apr 2022 19:46:26 +0100 Subject: [PATCH 125/170] Add `ServiceRequest::extract` (#2647) Co-authored-by: Rob Ede --- actix-web/CHANGES.md | 3 +++ actix-web/src/extract.rs | 4 +++- actix-web/src/service.rs | 27 ++++++++++++++++++++++++++- 3 files changed, 32 insertions(+), 2 deletions(-) diff --git a/actix-web/CHANGES.md b/actix-web/CHANGES.md index 2461cb3a1..ce1837c78 100644 --- a/actix-web/CHANGES.md +++ b/actix-web/CHANGES.md @@ -1,6 +1,9 @@ # Changelog ## Unreleased - 2021-xx-xx +- Add `ServiceRequest::extract` to make it easier to use extractors when writing middlewares. [#2647] + +[#2647]: https://github.com/actix/actix-web/pull/2647 ## 4.0.1 - 2022-02-25 diff --git a/actix-web/src/extract.rs b/actix-web/src/extract.rs index a8b3d4565..1b2f0bd19 100644 --- a/actix-web/src/extract.rs +++ b/actix-web/src/extract.rs @@ -18,9 +18,11 @@ use crate::{dev::Payload, Error, HttpRequest}; /// A type that implements [`FromRequest`] is called an **extractor** and can extract data from /// the request. Some types that implement this trait are: [`Json`], [`Header`], and [`Path`]. /// +/// Check out [`ServiceRequest::extract`](crate::dev::ServiceRequest::extract) if you want to +/// leverage extractors when implementing middlewares. +/// /// # Configuration /// An extractor can be customized by injecting the corresponding configuration with one of: -/// /// - [`App::app_data()`][crate::App::app_data] /// - [`Scope::app_data()`][crate::Scope::app_data] /// - [`Resource::app_data()`][crate::Resource::app_data] diff --git a/actix-web/src/service.rs b/actix-web/src/service.rs index 426e9d62b..a9e809bf9 100644 --- a/actix-web/src/service.rs +++ b/actix-web/src/service.rs @@ -24,7 +24,7 @@ use crate::{ guard::{Guard, GuardContext}, info::ConnectionInfo, rmap::ResourceMap, - Error, HttpRequest, HttpResponse, + Error, FromRequest, HttpRequest, HttpResponse, }; pub(crate) type BoxedHttpService = BoxService, Error>; @@ -95,6 +95,31 @@ impl ServiceRequest { (&mut self.req, &mut self.payload) } + /// Derives a type from this request using an [extractor](crate::FromRequest). + /// + /// Returns the `T` extractor's `Future` type which can be `await`ed. This is particularly handy + /// when you want to use an extractor in a middleware implementation. + /// + /// # Examples + /// ``` + /// use actix_web::{ + /// dev::{ServiceRequest, ServiceResponse}, + /// web::Path, Error + /// }; + /// + /// async fn my_helper(mut srv_req: ServiceRequest) -> Result { + /// let path = srv_req.extract::>().await?; + /// // [...] + /// # todo!() + /// } + /// ``` + pub fn extract(&mut self) -> ::Future + where + T: FromRequest, + { + T::from_request(&self.req, &mut self.payload) + } + /// Construct request from parts. pub fn from_parts(req: HttpRequest, payload: Payload) -> Self { #[cfg(debug_assertions)] From 56b9c0d08eec2faec847b6dd5abe5436352cd7b0 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sat, 23 Apr 2022 12:31:32 +0100 Subject: [PATCH 126/170] remove payload unwindsafe impl assert --- actix-http/src/h1/payload.rs | 6 ++---- actix-web/src/types/readlines.rs | 26 +++++++++++++------------- 2 files changed, 15 insertions(+), 17 deletions(-) diff --git a/actix-http/src/h1/payload.rs b/actix-http/src/h1/payload.rs index 5a93e9051..a8c632396 100644 --- a/actix-http/src/h1/payload.rs +++ b/actix-http/src/h1/payload.rs @@ -263,10 +263,8 @@ mod tests { assert_not_impl_any!(Payload: Send, Sync, UnwindSafe, RefUnwindSafe); assert_impl_all!(Inner: Unpin, Send, Sync); - #[rustversion::before(1.60)] - assert_not_impl_any!(Inner: UnwindSafe, RefUnwindSafe); - #[rustversion::since(1.60)] - assert_impl_all!(Inner: UnwindSafe, RefUnwindSafe); + // assertion not stable wrt rustc versions yet + // assert_impl_all!(Inner: UnwindSafe, RefUnwindSafe); #[actix_rt::test] async fn test_unread_data() { diff --git a/actix-web/src/types/readlines.rs b/actix-web/src/types/readlines.rs index 6c456e21c..8a775a073 100644 --- a/actix-web/src/types/readlines.rs +++ b/actix-web/src/types/readlines.rs @@ -20,7 +20,7 @@ use crate::{ /// Stream that reads request line by line. pub struct Readlines { stream: Payload, - buff: BytesMut, + buf: BytesMut, limit: usize, checked_buff: bool, encoding: &'static Encoding, @@ -41,7 +41,7 @@ where Readlines { stream: req.take_payload(), - buff: BytesMut::with_capacity(262_144), + buf: BytesMut::with_capacity(262_144), limit: 262_144, checked_buff: true, err: None, @@ -58,7 +58,7 @@ where fn err(err: ReadlinesError) -> Self { Readlines { stream: Payload::None, - buff: BytesMut::new(), + buf: BytesMut::new(), limit: 262_144, checked_buff: true, encoding: UTF_8, @@ -84,7 +84,7 @@ where // check if there is a newline in the buffer if !this.checked_buff { let mut found: Option = None; - for (ind, b) in this.buff.iter().enumerate() { + for (ind, b) in this.buf.iter().enumerate() { if *b == b'\n' { found = Some(ind); break; @@ -96,13 +96,13 @@ where return Poll::Ready(Some(Err(ReadlinesError::LimitOverflow))); } let line = if this.encoding == UTF_8 { - str::from_utf8(&this.buff.split_to(ind + 1)) + str::from_utf8(&this.buf.split_to(ind + 1)) .map_err(|_| ReadlinesError::EncodingError)? .to_owned() } else { this.encoding .decode_without_bom_handling_and_without_replacement( - &this.buff.split_to(ind + 1), + &this.buf.split_to(ind + 1), ) .map(Cow::into_owned) .ok_or(ReadlinesError::EncodingError)? @@ -141,32 +141,32 @@ where .ok_or(ReadlinesError::EncodingError)? }; // extend buffer with rest of the bytes; - this.buff.extend_from_slice(&bytes); + this.buf.extend_from_slice(&bytes); this.checked_buff = false; return Poll::Ready(Some(Ok(line))); } - this.buff.extend_from_slice(&bytes); + this.buf.extend_from_slice(&bytes); Poll::Pending } None => { - if this.buff.is_empty() { + if this.buf.is_empty() { return Poll::Ready(None); } - if this.buff.len() > this.limit { + if this.buf.len() > this.limit { return Poll::Ready(Some(Err(ReadlinesError::LimitOverflow))); } let line = if this.encoding == UTF_8 { - str::from_utf8(&this.buff) + str::from_utf8(&this.buf) .map_err(|_| ReadlinesError::EncodingError)? .to_owned() } else { this.encoding - .decode_without_bom_handling_and_without_replacement(&this.buff) + .decode_without_bom_handling_and_without_replacement(&this.buf) .map(Cow::into_owned) .ok_or(ReadlinesError::EncodingError)? }; - this.buff.clear(); + this.buf.clear(); Poll::Ready(Some(Ok(line))) } From f2cacc4c9d3634da5c024bc75315314c97262607 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sat, 23 Apr 2022 13:35:41 +0100 Subject: [PATCH 127/170] clear conn_data on HttpRequest drop (#2742) * clear conn_data on HttpRequest drop fixes #2740 * update changelog * fix doc test --- actix-http/benches/uninit-headers.rs | 2 +- actix-web/CHANGES.md | 5 +++++ actix-web/src/request.rs | 12 +++++++----- actix-web/src/test/test_request.rs | 2 +- 4 files changed, 14 insertions(+), 7 deletions(-) diff --git a/actix-http/benches/uninit-headers.rs b/actix-http/benches/uninit-headers.rs index 5dfd3bc11..3eda96be8 100644 --- a/actix-http/benches/uninit-headers.rs +++ b/actix-http/benches/uninit-headers.rs @@ -114,7 +114,7 @@ mod _original { use std::mem::MaybeUninit; pub fn parse_headers(src: &mut BytesMut) -> usize { - #![allow(clippy::uninit_assumed_init)] + #![allow(invalid_value, clippy::uninit_assumed_init)] let mut headers: [HeaderIndex; MAX_HEADERS] = unsafe { MaybeUninit::uninit().assume_init() }; diff --git a/actix-web/CHANGES.md b/actix-web/CHANGES.md index ce1837c78..eac40e38d 100644 --- a/actix-web/CHANGES.md +++ b/actix-web/CHANGES.md @@ -1,9 +1,14 @@ # Changelog ## Unreleased - 2021-xx-xx +### Added - Add `ServiceRequest::extract` to make it easier to use extractors when writing middlewares. [#2647] +### Fixed +- Clear connection-level data on `HttpRequest` drop. [#2742] + [#2647]: https://github.com/actix/actix-web/pull/2647 +[#2742]: https://github.com/actix/actix-web/pull/2742 ## 4.0.1 - 2022-02-25 diff --git a/actix-web/src/request.rs b/actix-web/src/request.rs index 5545cf982..d26488e2b 100644 --- a/actix-web/src/request.rs +++ b/actix-web/src/request.rs @@ -381,12 +381,16 @@ impl Drop for HttpRequest { inner.app_data.truncate(1); // Inner is borrowed mut here and; get req data mutably to reduce borrow check. Also - // we know the req_data Rc will not have any cloned at this point to unwrap is okay. + // we know the req_data Rc will not have any clones at this point to unwrap is okay. Rc::get_mut(&mut inner.extensions) .unwrap() .get_mut() .clear(); + // We can't use the same trick as req data because the conn_data is held by the + // dispatcher, too. + inner.conn_data = None; + // a re-borrow of pool is necessary here. let req = Rc::clone(&self.inner); self.app_state().pool().push(req); @@ -761,10 +765,8 @@ mod tests { assert_eq!(body, Bytes::from_static(b"1")); } - // allow deprecated App::data - #[allow(deprecated)] #[actix_rt::test] - async fn test_extensions_dropped() { + async fn test_app_data_dropped() { struct Tracker { pub dropped: bool, } @@ -780,7 +782,7 @@ mod tests { let tracker = Rc::new(RefCell::new(Tracker { dropped: false })); { let tracker2 = Rc::clone(&tracker); - let srv = init_service(App::new().data(10u32).service(web::resource("/").to( + let srv = init_service(App::new().service(web::resource("/").to( move |req: HttpRequest| { req.extensions_mut().insert(Foo { tracker: Rc::clone(&tracker2), diff --git a/actix-web/src/test/test_request.rs b/actix-web/src/test/test_request.rs index 2b60fca71..e81561d17 100644 --- a/actix-web/src/test/test_request.rs +++ b/actix-web/src/test/test_request.rs @@ -53,7 +53,7 @@ use crate::cookie::{Cookie, CookieJar}; /// assert_eq!(resp.status(), StatusCode::OK); /// /// let req = test::TestRequest::default().to_http_request(); -/// let resp = index(req).await; +/// let resp = handler(req).await; /// assert_eq!(resp.status(), StatusCode::BAD_REQUEST); /// } /// ``` From 8abcb9451296f44ef74f111bc29ed099a2bea70d Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sat, 23 Apr 2022 14:37:03 +0100 Subject: [PATCH 128/170] fix tokio-uring version --- actix-files/Cargo.toml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/actix-files/Cargo.toml b/actix-files/Cargo.toml index 8f856c109..3eb1edf29 100644 --- a/actix-files/Cargo.toml +++ b/actix-files/Cargo.toml @@ -40,8 +40,9 @@ percent-encoding = "2.1" pin-project-lite = "0.2.7" # experimental-io-uring -tokio-uring = { version = "0.2", optional = true, features = ["bytes"] } -actix-server = "2.1" # ensure matching tokio-uring versions +[target.'cfg(target_os = "linux")'.dependencies] +tokio-uring = { version = "0.3", optional = true, features = ["bytes"] } +actix-server = { version = "2.1", optional = true } # ensure matching tokio-uring versions [dev-dependencies] actix-rt = "2.7" From 45592b37b60420ee827c964959449d006228290c Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sat, 23 Apr 2022 21:01:55 +0100 Subject: [PATCH 129/170] add `Route::wrap` (#2725) * add `Route::wrap` * add tests * fix clippy * fix doctests --- actix-http/benches/uninit-headers.rs | 1 + actix-web/CHANGES.md | 4 +- actix-web/src/route.rs | 83 +++++++++++++++++++++++++--- actix-web/tests/test_server.rs | 54 +++++++++--------- 4 files changed, 108 insertions(+), 34 deletions(-) diff --git a/actix-http/benches/uninit-headers.rs b/actix-http/benches/uninit-headers.rs index 3eda96be8..688c64d6e 100644 --- a/actix-http/benches/uninit-headers.rs +++ b/actix-http/benches/uninit-headers.rs @@ -119,6 +119,7 @@ mod _original { let mut headers: [HeaderIndex; MAX_HEADERS] = unsafe { MaybeUninit::uninit().assume_init() }; + #[allow(invalid_value)] let mut parsed: [httparse::Header<'_>; MAX_HEADERS] = unsafe { MaybeUninit::uninit().assume_init() }; diff --git a/actix-web/CHANGES.md b/actix-web/CHANGES.md index eac40e38d..f89189402 100644 --- a/actix-web/CHANGES.md +++ b/actix-web/CHANGES.md @@ -2,12 +2,14 @@ ## Unreleased - 2021-xx-xx ### Added -- Add `ServiceRequest::extract` to make it easier to use extractors when writing middlewares. [#2647] +- Add `ServiceRequest::extract()` to make it easier to use extractors when writing middlewares. [#2647] +- Add `Route::wrap()` to allow individual routes to use middleware. [#2725] ### Fixed - Clear connection-level data on `HttpRequest` drop. [#2742] [#2647]: https://github.com/actix/actix-web/pull/2647 +[#2725]: https://github.com/actix/actix-web/pull/2725 [#2742]: https://github.com/actix/actix-web/pull/2742 diff --git a/actix-web/src/route.rs b/actix-web/src/route.rs index 0410b99dd..b37128f2c 100644 --- a/actix-web/src/route.rs +++ b/actix-web/src/route.rs @@ -1,15 +1,17 @@ use std::{mem, rc::Rc}; -use actix_http::Method; +use actix_http::{body::MessageBody, Method}; use actix_service::{ + apply, boxed::{self, BoxService}, - fn_service, Service, ServiceFactory, ServiceFactoryExt, + fn_service, Service, ServiceFactory, ServiceFactoryExt, Transform, }; use futures_core::future::LocalBoxFuture; use crate::{ guard::{self, Guard}, handler::{handler_service, Handler}, + middleware::Compat, service::{BoxedHttpServiceFactory, ServiceRequest, ServiceResponse}, Error, FromRequest, HttpResponse, Responder, }; @@ -35,6 +37,31 @@ impl Route { } } + /// Registers a route middleware. + /// + /// `mw` is a middleware component (type), that can modify the requests and responses handled by + /// this `Route`. + /// + /// See [`App::wrap`](crate::App::wrap) for more details. + #[doc(alias = "middleware")] + #[doc(alias = "use")] // nodejs terminology + pub fn wrap(self, mw: M) -> Route + where + M: Transform< + BoxService, + ServiceRequest, + Response = ServiceResponse, + Error = Error, + InitError = (), + > + 'static, + B: MessageBody + 'static, + { + Route { + service: boxed::factory(apply(Compat::new(mw), self.service)), + guards: self.guards, + } + } + pub(crate) fn take_guards(&mut self) -> Vec> { mem::take(Rc::get_mut(&mut self.guards).unwrap()) } @@ -246,11 +273,15 @@ mod tests { use futures_core::future::LocalBoxFuture; use serde::Serialize; - use crate::dev::{always_ready, fn_factory, fn_service, Service}; - use crate::http::{header, Method, StatusCode}; - use crate::service::{ServiceRequest, ServiceResponse}; - use crate::test::{call_service, init_service, read_body, TestRequest}; - use crate::{error, web, App, HttpResponse}; + use crate::{ + dev::{always_ready, fn_factory, fn_service, Service}, + error, + http::{header, Method, StatusCode}, + middleware::{DefaultHeaders, Logger}, + service::{ServiceRequest, ServiceResponse}, + test::{call_service, init_service, read_body, TestRequest}, + web, App, HttpResponse, + }; #[derive(Serialize, PartialEq, Debug)] struct MyObject { @@ -323,6 +354,44 @@ mod tests { assert_eq!(body, Bytes::from_static(b"{\"name\":\"test\"}")); } + #[actix_rt::test] + async fn route_middleware() { + let srv = init_service( + App::new() + .route("/", web::get().to(HttpResponse::Ok).wrap(Logger::default())) + .service( + web::resource("/test") + .route(web::get().to(HttpResponse::Ok)) + .route( + web::post() + .to(HttpResponse::Created) + .wrap(DefaultHeaders::new().add(("x-test", "x-posted"))), + ) + .route( + web::delete() + .to(HttpResponse::Accepted) + // logger changes body type, proving Compat is not needed + .wrap(Logger::default()), + ), + ), + ) + .await; + + let req = TestRequest::get().uri("/test").to_request(); + let res = call_service(&srv, req).await; + assert_eq!(res.status(), StatusCode::OK); + assert!(!res.headers().contains_key("x-test")); + + let req = TestRequest::post().uri("/test").to_request(); + let res = call_service(&srv, req).await; + assert_eq!(res.status(), StatusCode::CREATED); + assert_eq!(res.headers().get("x-test").unwrap(), "x-posted"); + + let req = TestRequest::delete().uri("/test").to_request(); + let res = call_service(&srv, req).await; + assert_eq!(res.status(), StatusCode::ACCEPTED); + } + #[actix_rt::test] async fn test_service_handler() { struct HelloWorld; diff --git a/actix-web/tests/test_server.rs b/actix-web/tests/test_server.rs index bd8934061..270223d69 100644 --- a/actix-web/tests/test_server.rs +++ b/actix-web/tests/test_server.rs @@ -799,34 +799,36 @@ async fn test_server_cookies() { let res = req.send().await.unwrap(); assert!(res.status().is_success()); - let first_cookie = Cookie::build("first", "first_value") - .http_only(true) - .finish(); - let second_cookie = Cookie::new("second", "first_value"); + { + let first_cookie = Cookie::build("first", "first_value") + .http_only(true) + .finish(); + let second_cookie = Cookie::new("second", "first_value"); - let cookies = res.cookies().expect("To have cookies"); - assert_eq!(cookies.len(), 3); - if cookies[0] == first_cookie { - assert_eq!(cookies[1], second_cookie); - } else { - assert_eq!(cookies[0], second_cookie); - assert_eq!(cookies[1], first_cookie); - } + let cookies = res.cookies().expect("To have cookies"); + assert_eq!(cookies.len(), 3); + if cookies[0] == first_cookie { + assert_eq!(cookies[1], second_cookie); + } else { + assert_eq!(cookies[0], second_cookie); + assert_eq!(cookies[1], first_cookie); + } - let first_cookie = first_cookie.to_string(); - let second_cookie = second_cookie.to_string(); - // Check that we have exactly two instances of raw cookie headers - let cookies = res - .headers() - .get_all(http::header::SET_COOKIE) - .map(|header| header.to_str().expect("To str").to_string()) - .collect::>(); - assert_eq!(cookies.len(), 3); - if cookies[0] == first_cookie { - assert_eq!(cookies[1], second_cookie); - } else { - assert_eq!(cookies[0], second_cookie); - assert_eq!(cookies[1], first_cookie); + let first_cookie = first_cookie.to_string(); + let second_cookie = second_cookie.to_string(); + // Check that we have exactly two instances of raw cookie headers + let cookies = res + .headers() + .get_all(http::header::SET_COOKIE) + .map(|header| header.to_str().expect("To str").to_string()) + .collect::>(); + assert_eq!(cookies.len(), 3); + if cookies[0] == first_cookie { + assert_eq!(cookies[1], second_cookie); + } else { + assert_eq!(cookies[0], second_cookie); + assert_eq!(cookies[1], first_cookie); + } } srv.stop().await; From 017e40f73369d79e9beb3191d6fa8f7ac76c4b21 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sat, 23 Apr 2022 21:02:24 +0100 Subject: [PATCH 130/170] update optional extractor impl docs --- actix-web/src/extract.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/actix-web/src/extract.rs b/actix-web/src/extract.rs index 1b2f0bd19..7d187c90a 100644 --- a/actix-web/src/extract.rs +++ b/actix-web/src/extract.rs @@ -80,9 +80,9 @@ pub trait FromRequest: Sized { } } -/// Optionally extract a field from the request +/// Optionally extract from the request. /// -/// If the FromRequest for T fails, return None rather than returning an error response +/// If the inner `T::from_request` returns an error, handler will receive `None` instead. /// /// # Examples /// ``` @@ -167,9 +167,10 @@ where } } -/// Optionally extract a field from the request or extract the Error if unsuccessful +/// Extract from the request, passing error type through to handler. /// -/// If the `FromRequest` for T fails, inject Err into handler rather than returning an error response +/// If the inner `T::from_request` returns an error, allow handler to receive the error rather than +/// immediately returning an error response. /// /// # Examples /// ``` From 9aab9116006b694fc0ac93df26ca2b0dfe6262bc Mon Sep 17 00:00:00 2001 From: Matt Fellenz Date: Sat, 23 Apr 2022 13:57:11 -0700 Subject: [PATCH 131/170] Improve documentation for FromRequest::Future (#2734) Co-authored-by: Rob Ede --- actix-web/src/extract.rs | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/actix-web/src/extract.rs b/actix-web/src/extract.rs index 7d187c90a..d4f5cc91f 100644 --- a/actix-web/src/extract.rs +++ b/actix-web/src/extract.rs @@ -66,13 +66,29 @@ pub trait FromRequest: Sized { /// The associated error which can be returned. type Error: Into; - /// Future that resolves to a Self. + /// Future that resolves to a `Self`. + /// + /// To use an async function or block, the futures must be boxed. The following snippet will be + /// common when creating async/await extractors (that do not consume the body). + /// + /// ```ignore + /// type Future = Pin>>>; + /// // or + /// type Future = futures_util::future::LocalBoxFuture<'static, Result>; + /// + /// fn from_request(req: HttpRequest, ...) -> Self::Future { + /// let req = req.clone(); + /// Box::pin(async move { + /// ... + /// }) + /// } + /// ``` type Future: Future>; - /// Create a Self from request parts asynchronously. + /// Create a `Self` from request parts asynchronously. fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future; - /// Create a Self from request head asynchronously. + /// Create a `Self` from request head asynchronously. /// /// This method is short for `T::from_request(req, &mut Payload::None)`. fn extract(req: &HttpRequest) -> Self::Future { From b1c85ba85be91b5ea34f31264853b411fadce1ef Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sat, 23 Apr 2022 22:11:45 +0100 Subject: [PATCH 132/170] Add `ServiceConfig::default_service` (#2743) * Add `ServiceConfig::default_service` based on https://github.com/actix/actix-web/pull/2338 * update changelog --- actix-web/CHANGES.md | 7 ++- actix-web/src/app.rs | 8 +++- actix-web/src/config.rs | 96 ++++++++++++++++++++++++++++++++--------- actix-web/src/scope.rs | 4 ++ 4 files changed, 92 insertions(+), 23 deletions(-) diff --git a/actix-web/CHANGES.md b/actix-web/CHANGES.md index f89189402..4a16073a6 100644 --- a/actix-web/CHANGES.md +++ b/actix-web/CHANGES.md @@ -4,13 +4,16 @@ ### Added - Add `ServiceRequest::extract()` to make it easier to use extractors when writing middlewares. [#2647] - Add `Route::wrap()` to allow individual routes to use middleware. [#2725] +- Add `ServiceConfig::default_service()`. [#2338] [#2743] ### Fixed - Clear connection-level data on `HttpRequest` drop. [#2742] +[#2338]: https://github.com/actix/actix-web/pull/2338 [#2647]: https://github.com/actix/actix-web/pull/2647 [#2725]: https://github.com/actix/actix-web/pull/2725 [#2742]: https://github.com/actix/actix-web/pull/2742 +[#2743]: https://github.com/actix/actix-web/pull/2743 ## 4.0.1 - 2022-02-25 @@ -726,7 +729,7 @@ ### Removed - Public modules `middleware::{normalize, err_handlers}`. All necessary middleware types are now exposed directly by the `middleware` module. -- Remove `actix-threadpool` as dependency. `actix_threadpool::BlockingError` error type can be imported +- Remove `actix-threadpool` as dependency. `actix_threadpool::BlockingError` error type can be imported from `actix_web::error` module. [#1878] [#1812]: https://github.com/actix/actix-web/pull/1812 @@ -828,7 +831,7 @@ ## 3.0.0-beta.4 - 2020-09-09 ### Added -- `middleware::NormalizePath` now has configurable behavior for either always having a trailing +- `middleware::NormalizePath` now has configurable behavior for either always having a trailing slash, or as the new addition, always trimming trailing slashes. [#1639] ### Changed diff --git a/actix-web/src/app.rs b/actix-web/src/app.rs index 18749d346..119980a03 100644 --- a/actix-web/src/app.rs +++ b/actix-web/src/app.rs @@ -185,10 +185,17 @@ where F: FnOnce(&mut ServiceConfig), { let mut cfg = ServiceConfig::new(); + f(&mut cfg); + self.services.extend(cfg.services); self.external.extend(cfg.external); self.extensions.extend(cfg.app_data); + + if let Some(default) = cfg.default { + self.default = Some(default); + } + self } @@ -267,7 +274,6 @@ where { let svc = svc .into_factory() - .map(|res| res.map_into_boxed_body()) .map_init_err(|e| log::error!("Can not construct default service: {:?}", e)); self.default = Some(Rc::new(boxed::factory(svc))); diff --git a/actix-web/src/config.rs b/actix-web/src/config.rs index 77fba18ed..dab309175 100644 --- a/actix-web/src/config.rs +++ b/actix-web/src/config.rs @@ -1,33 +1,32 @@ -use std::net::SocketAddr; -use std::rc::Rc; +use std::{net::SocketAddr, rc::Rc}; -use actix_http::Extensions; -use actix_router::ResourceDef; -use actix_service::{boxed, IntoServiceFactory, ServiceFactory}; +use actix_service::{boxed, IntoServiceFactory, ServiceFactory, ServiceFactoryExt as _}; -use crate::data::Data; -use crate::error::Error; -use crate::guard::Guard; -use crate::resource::Resource; -use crate::rmap::ResourceMap; -use crate::route::Route; -use crate::service::{ - AppServiceFactory, HttpServiceFactory, ServiceFactoryWrapper, ServiceRequest, - ServiceResponse, +use crate::{ + data::Data, + dev::{Extensions, ResourceDef}, + error::Error, + guard::Guard, + resource::Resource, + rmap::ResourceMap, + route::Route, + service::{ + AppServiceFactory, BoxedHttpServiceFactory, HttpServiceFactory, ServiceFactoryWrapper, + ServiceRequest, ServiceResponse, + }, }; type Guards = Vec>; -type HttpNewService = boxed::BoxServiceFactory<(), ServiceRequest, ServiceResponse, Error, ()>; /// Application configuration pub struct AppService { config: AppConfig, root: bool, - default: Rc, + default: Rc, #[allow(clippy::type_complexity)] services: Vec<( ResourceDef, - HttpNewService, + BoxedHttpServiceFactory, Option, Option>, )>, @@ -35,7 +34,7 @@ pub struct AppService { impl AppService { /// Crate server settings instance. - pub(crate) fn new(config: AppConfig, default: Rc) -> Self { + pub(crate) fn new(config: AppConfig, default: Rc) -> Self { AppService { config, default, @@ -56,7 +55,7 @@ impl AppService { AppConfig, Vec<( ResourceDef, - HttpNewService, + BoxedHttpServiceFactory, Option, Option>, )>, @@ -81,7 +80,7 @@ impl AppService { } /// Returns default handler factory. - pub fn default_service(&self) -> Rc { + pub fn default_service(&self) -> Rc { self.default.clone() } @@ -187,6 +186,7 @@ pub struct ServiceConfig { pub(crate) services: Vec>, pub(crate) external: Vec, pub(crate) app_data: Extensions, + pub(crate) default: Option>, } impl ServiceConfig { @@ -195,6 +195,7 @@ impl ServiceConfig { services: Vec::new(), external: Vec::new(), app_data: Extensions::new(), + default: None, } } @@ -215,6 +216,29 @@ impl ServiceConfig { self } + /// Default service to be used if no matching resource could be found. + /// + /// Counterpart to [`App::default_service()`](crate::App::default_service). + pub fn default_service(&mut self, f: F) -> &mut Self + where + F: IntoServiceFactory, + U: ServiceFactory< + ServiceRequest, + Config = (), + Response = ServiceResponse, + Error = Error, + > + 'static, + U::InitError: std::fmt::Debug, + { + let svc = f + .into_factory() + .map_init_err(|err| log::error!("Can not construct default service: {:?}", err)); + + self.default = Some(Rc::new(boxed::factory(svc))); + + self + } + /// Run external configuration as part of the application building process /// /// Counterpart to [`App::configure()`](crate::App::configure) that allows for easy nesting. @@ -322,6 +346,38 @@ mod tests { assert_eq!(body, Bytes::from_static(b"https://youtube.com/watch/12345")); } + #[actix_rt::test] + async fn registers_default_service() { + let srv = init_service( + App::new() + .configure(|cfg| { + cfg.default_service( + web::get().to(|| HttpResponse::NotFound().body("four oh four")), + ); + }) + .service(web::scope("/scoped").configure(|cfg| { + cfg.default_service( + web::get().to(|| HttpResponse::NotFound().body("scoped four oh four")), + ); + })), + ) + .await; + + // app registers default service + let req = TestRequest::with_uri("/path/i/did/not-configure").to_request(); + let resp = call_service(&srv, req).await; + assert_eq!(resp.status(), StatusCode::NOT_FOUND); + let body = read_body(resp).await; + assert_eq!(body, Bytes::from_static(b"four oh four")); + + // scope registers default service + let req = TestRequest::with_uri("/scoped/path/i/did/not-configure").to_request(); + let resp = call_service(&srv, req).await; + assert_eq!(resp.status(), StatusCode::NOT_FOUND); + let body = read_body(resp).await; + assert_eq!(body, Bytes::from_static(b"scoped four oh four")); + } + #[actix_rt::test] async fn test_service() { let srv = init_service(App::new().configure(|cfg| { diff --git a/actix-web/src/scope.rs b/actix-web/src/scope.rs index 0fcc83d70..f8c042a5f 100644 --- a/actix-web/src/scope.rs +++ b/actix-web/src/scope.rs @@ -198,6 +198,10 @@ where .get_or_insert_with(Extensions::new) .extend(cfg.app_data); + if let Some(default) = cfg.default { + self.default = Some(default); + } + self } From 6a5b37020676fdfed4b8c7466d8542904bca825c Mon Sep 17 00:00:00 2001 From: cui fliter Date: Mon, 25 Apr 2022 06:01:20 +0800 Subject: [PATCH 133/170] fix some typos (#2744) Co-authored-by: Rob Ede --- actix-web/Cargo.toml | 2 +- actix-web/src/types/either.rs | 2 +- awc/src/builder.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/actix-web/Cargo.toml b/actix-web/Cargo.toml index 7793fd8be..702fd6743 100644 --- a/actix-web/Cargo.toml +++ b/actix-web/Cargo.toml @@ -59,7 +59,7 @@ rustls = ["actix-http/rustls", "actix-tls/accept", "actix-tls/rustls"] # Don't rely on these whatsoever. They may disappear at anytime. __compress = [] -# io-uring feature only avaiable for Linux OSes. +# io-uring feature only available for Linux OSes. experimental-io-uring = ["actix-server/io-uring"] [dependencies] diff --git a/actix-web/src/types/either.rs b/actix-web/src/types/either.rs index 0eafb9e43..c0faf04b1 100644 --- a/actix-web/src/types/either.rs +++ b/actix-web/src/types/either.rs @@ -49,7 +49,7 @@ use crate::{ /// ``` /// /// # Responder -/// It may be desireable to use a concrete type for a response with multiple branches. As long as +/// It may be desirable to use a concrete type for a response with multiple branches. As long as /// both types implement `Responder`, so will the `Either` type, enabling it to be used as a /// handler's return type. /// diff --git a/awc/src/builder.rs b/awc/src/builder.rs index 16a4e9cb5..c101d18f0 100644 --- a/awc/src/builder.rs +++ b/awc/src/builder.rs @@ -165,7 +165,7 @@ where /// Add default header. /// - /// Headers added by this method get added to every request unless overriden by . + /// Headers added by this method get added to every request unless overridden by other methods. /// /// # Panics /// Panics if header name or value is invalid. From dce57a79c96a2c7d5946491df317d9da0d1e1869 Mon Sep 17 00:00:00 2001 From: Sabrina Jewson Date: Mon, 30 May 2022 20:52:48 +0100 Subject: [PATCH 134/170] Implement `ResponseError` for `Infallible` (#2769) --- actix-web/CHANGES.md | 1 + actix-web/src/error/error.rs | 6 ------ actix-web/src/error/response_error.rs | 10 ++++++++++ 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/actix-web/CHANGES.md b/actix-web/CHANGES.md index 4a16073a6..cb82ef653 100644 --- a/actix-web/CHANGES.md +++ b/actix-web/CHANGES.md @@ -5,6 +5,7 @@ - Add `ServiceRequest::extract()` to make it easier to use extractors when writing middlewares. [#2647] - Add `Route::wrap()` to allow individual routes to use middleware. [#2725] - Add `ServiceConfig::default_service()`. [#2338] [#2743] +- Implement `ResponseError` for `std::convert::Infallible` ### Fixed - Clear connection-level data on `HttpRequest` drop. [#2742] diff --git a/actix-web/src/error/error.rs b/actix-web/src/error/error.rs index 3d3978dde..3a5a128f6 100644 --- a/actix-web/src/error/error.rs +++ b/actix-web/src/error/error.rs @@ -51,12 +51,6 @@ impl StdError for Error { } } -impl From for Error { - fn from(val: std::convert::Infallible) -> Self { - match val {} - } -} - /// `Error` for any error that implements `ResponseError` impl From for Error { fn from(err: T) -> Error { diff --git a/actix-web/src/error/response_error.rs b/actix-web/src/error/response_error.rs index 0b8a82ce8..7d2c06154 100644 --- a/actix-web/src/error/response_error.rs +++ b/actix-web/src/error/response_error.rs @@ -1,6 +1,7 @@ //! `ResponseError` trait and foreign impls. use std::{ + convert::Infallible, error::Error as StdError, fmt, io::{self, Write as _}, @@ -54,6 +55,15 @@ downcast_dyn!(ResponseError); impl ResponseError for Box {} +impl ResponseError for Infallible { + fn status_code(&self) -> StatusCode { + match *self {} + } + fn error_response(&self) -> HttpResponse { + match *self {} + } +} + #[cfg(feature = "openssl")] impl ResponseError for actix_tls::accept::openssl::reexports::Error {} From 8e76a1c77588c4a8214838c7248e7167b13508b1 Mon Sep 17 00:00:00 2001 From: JY Choi Date: Tue, 7 Jun 2022 02:53:23 +0900 Subject: [PATCH 135/170] Allow a path as a guard in route handler macro (#2771) * Allow a path as a guard in route handler macro * Update CHANGES.md Co-authored-by: Rob Ede --- actix-web-codegen/CHANGES.md | 3 +++ actix-web-codegen/src/route.rs | 6 +++--- actix-web-codegen/tests/test_macro.rs | 22 ++++++++++++++++++++++ 3 files changed, 28 insertions(+), 3 deletions(-) diff --git a/actix-web-codegen/CHANGES.md b/actix-web-codegen/CHANGES.md index 8ee787c0a..04bbb4de1 100644 --- a/actix-web-codegen/CHANGES.md +++ b/actix-web-codegen/CHANGES.md @@ -1,6 +1,9 @@ # Changes ## Unreleased - 2021-xx-xx +- Fix support for guard paths in route handler macros. [#2771] + +[#2771] https://github.com/actix/actix-web/pull/2771 ## 4.0.0 - 2022-02-24 diff --git a/actix-web-codegen/src/route.rs b/actix-web-codegen/src/route.rs index cb1ba1ef6..cae3cbd55 100644 --- a/actix-web-codegen/src/route.rs +++ b/actix-web-codegen/src/route.rs @@ -4,7 +4,7 @@ use actix_router::ResourceDef; use proc_macro::TokenStream; use proc_macro2::{Span, TokenStream as TokenStream2}; use quote::{format_ident, quote, ToTokens, TokenStreamExt}; -use syn::{parse_macro_input, AttributeArgs, Ident, LitStr, NestedMeta}; +use syn::{parse_macro_input, AttributeArgs, Ident, LitStr, NestedMeta, Path}; enum ResourceType { Async, @@ -77,7 +77,7 @@ impl TryFrom<&syn::LitStr> for MethodType { struct Args { path: syn::LitStr, resource_name: Option, - guards: Vec, + guards: Vec, wrappers: Vec, methods: HashSet, } @@ -121,7 +121,7 @@ impl Args { } } else if nv.path.is_ident("guard") { if let syn::Lit::Str(lit) = nv.lit { - guards.push(Ident::new(&lit.value(), Span::call_site())); + guards.push(lit.parse::()?); } else { return Err(syn::Error::new_spanned( nv.lit, diff --git a/actix-web-codegen/tests/test_macro.rs b/actix-web-codegen/tests/test_macro.rs index 769cf2bc3..55c2417b2 100644 --- a/actix-web-codegen/tests/test_macro.rs +++ b/actix-web-codegen/tests/test_macro.rs @@ -96,6 +96,21 @@ async fn custom_resource_name_test<'a>(req: actix_web::HttpRequest) -> impl Resp HttpResponse::Ok() } +mod guard_module { + use actix_web::{guard::GuardContext, http::header}; + + pub fn guard(ctx: &GuardContext) -> bool { + ctx.header::() + .map(|h| h.preference() == "image/*") + .unwrap_or(false) + } +} + +#[get("/test/guard", guard = "guard_module::guard")] +async fn guard_test() -> impl Responder { + HttpResponse::Ok() +} + pub struct ChangeStatusCode; impl Transform for ChangeStatusCode @@ -187,6 +202,7 @@ async fn test_body() { .service(test_handler) .service(route_test) .service(custom_resource_name_test) + .service(guard_test) }); let request = srv.request(http::Method::GET, srv.url("/test")); let response = request.send().await.unwrap(); @@ -245,6 +261,12 @@ async fn test_body() { let request = srv.request(http::Method::GET, srv.url("/custom_resource_name")); let response = request.send().await.unwrap(); assert!(response.status().is_success()); + + let request = srv + .request(http::Method::GET, srv.url("/test/guard")) + .insert_header(("Accept", "image/*")); + let response = request.send().await.unwrap(); + assert!(response.status().is_success()); } #[actix_rt::test] From 2253eae2bbedfbe3bb5d1d036451a37a6e57e0a6 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sat, 11 Jun 2022 04:03:26 +0100 Subject: [PATCH 136/170] update msrv to 1.56 (#2777) * update msrv to 1.56 * remove transitive dashmap dependency closes #2747 --- .github/workflows/ci.yml | 2 +- actix-files/CHANGES.md | 1 + actix-files/README.md | 2 +- actix-http-test/CHANGES.md | 1 + actix-http-test/README.md | 2 +- actix-http/CHANGES.md | 2 + actix-http/README.md | 2 +- actix-multipart/CHANGES.md | 1 + actix-multipart/README.md | 2 +- actix-router/CHANGES.md | 1 + actix-router/Cargo.toml | 2 - actix-router/examples/flamegraph.rs | 169 ---------------------------- actix-router/src/path.rs | 19 ---- actix-router/src/resource.rs | 65 +++-------- actix-router/src/router.rs | 12 -- actix-test/CHANGES.md | 1 + actix-web-actors/CHANGES.md | 1 + actix-web-actors/README.md | 2 +- actix-web-codegen/CHANGES.md | 1 + actix-web-codegen/README.md | 2 +- actix-web-codegen/tests/trybuild.rs | 2 +- actix-web/CHANGES.md | 3 + actix-web/README.md | 4 +- awc/CHANGES.md | 2 + clippy.toml | 2 +- 25 files changed, 39 insertions(+), 264 deletions(-) delete mode 100644 actix-router/examples/flamegraph.rs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7bb911f79..95dc6ba99 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,7 +16,7 @@ jobs: - { name: macOS, os: macos-latest, triple: x86_64-apple-darwin } - { name: Windows, os: windows-2022, triple: x86_64-pc-windows-msvc } version: - - 1.54.0 # MSRV + - 1.56.0 # MSRV - stable name: ${{ matrix.target.name }} / ${{ matrix.version }} diff --git a/actix-files/CHANGES.md b/actix-files/CHANGES.md index 7e99c2ae1..7a21e0aba 100644 --- a/actix-files/CHANGES.md +++ b/actix-files/CHANGES.md @@ -4,6 +4,7 @@ - Add `NamedFile::{modified, metadata, content_type, content_disposition, encoding}()` getters. [#2021] - Update `tokio-uring` dependency to `0.3`. - Audio files now use `Content-Disposition: inline` instead of `attachment`. [#2645] +- Minimum supported Rust version (MSRV) is now 1.56 due to transitive `hashbrown` dependency. [#2021]: https://github.com/actix/actix-web/pull/2021 [#2645]: https://github.com/actix/actix-web/pull/2645 diff --git a/actix-files/README.md b/actix-files/README.md index 3c4d4443c..5035cad9e 100644 --- a/actix-files/README.md +++ b/actix-files/README.md @@ -4,7 +4,7 @@ [![crates.io](https://img.shields.io/crates/v/actix-files?label=latest)](https://crates.io/crates/actix-files) [![Documentation](https://docs.rs/actix-files/badge.svg?version=0.6.0)](https://docs.rs/actix-files/0.6.0) -[![Version](https://img.shields.io/badge/rustc-1.54+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.54.0.html) +![Version](https://img.shields.io/badge/rustc-1.56+-ab6000.svg) ![License](https://img.shields.io/crates/l/actix-files.svg)
[![dependency status](https://deps.rs/crate/actix-files/0.6.0/status.svg)](https://deps.rs/crate/actix-files/0.6.0) diff --git a/actix-http-test/CHANGES.md b/actix-http-test/CHANGES.md index 3b98e0972..3f0be5356 100644 --- a/actix-http-test/CHANGES.md +++ b/actix-http-test/CHANGES.md @@ -1,6 +1,7 @@ # Changes ## Unreleased - 2021-xx-xx +- Minimum supported Rust version (MSRV) is now 1.56 due to transitive `hashbrown` dependency. ## 3.0.0-beta.13 - 2022-02-16 diff --git a/actix-http-test/README.md b/actix-http-test/README.md index d11ae69b2..8b8cacc2e 100644 --- a/actix-http-test/README.md +++ b/actix-http-test/README.md @@ -4,7 +4,7 @@ [![crates.io](https://img.shields.io/crates/v/actix-http-test?label=latest)](https://crates.io/crates/actix-http-test) [![Documentation](https://docs.rs/actix-http-test/badge.svg?version=3.0.0-beta.13)](https://docs.rs/actix-http-test/3.0.0-beta.13) -[![Version](https://img.shields.io/badge/rustc-1.54+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.54.0.html) +![Version](https://img.shields.io/badge/rustc-1.56+-ab6000.svg) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-http-test)
[![Dependency Status](https://deps.rs/crate/actix-http-test/3.0.0-beta.13/status.svg)](https://deps.rs/crate/actix-http-test/3.0.0-beta.13) diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 71132c6b2..75c131512 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -1,6 +1,8 @@ # Changes ## Unreleased - 2021-xx-xx +### Changed +- Minimum supported Rust version (MSRV) is now 1.56 due to transitive `hashbrown` dependency. ## 3.0.4 - 2022-03-09 diff --git a/actix-http/README.md b/actix-http/README.md index 14a7013db..136582352 100644 --- a/actix-http/README.md +++ b/actix-http/README.md @@ -4,7 +4,7 @@ [![crates.io](https://img.shields.io/crates/v/actix-http?label=latest)](https://crates.io/crates/actix-http) [![Documentation](https://docs.rs/actix-http/badge.svg?version=3.0.4)](https://docs.rs/actix-http/3.0.4) -[![Version](https://img.shields.io/badge/rustc-1.54+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.54.0.html) +![Version](https://img.shields.io/badge/rustc-1.56+-ab6000.svg) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-http.svg)
[![dependency status](https://deps.rs/crate/actix-http/3.0.4/status.svg)](https://deps.rs/crate/actix-http/3.0.4) diff --git a/actix-multipart/CHANGES.md b/actix-multipart/CHANGES.md index 11ec8a64f..53fbf9393 100644 --- a/actix-multipart/CHANGES.md +++ b/actix-multipart/CHANGES.md @@ -1,6 +1,7 @@ # Changes ## Unreleased - 2021-xx-xx +- Minimum supported Rust version (MSRV) is now 1.56 due to transitive `hashbrown` dependency. ## 0.4.0 - 2022-02-25 diff --git a/actix-multipart/README.md b/actix-multipart/README.md index 59b9651f1..0b375bf8d 100644 --- a/actix-multipart/README.md +++ b/actix-multipart/README.md @@ -4,7 +4,7 @@ [![crates.io](https://img.shields.io/crates/v/actix-multipart?label=latest)](https://crates.io/crates/actix-multipart) [![Documentation](https://docs.rs/actix-multipart/badge.svg?version=0.4.0)](https://docs.rs/actix-multipart/0.4.0) -[![Version](https://img.shields.io/badge/rustc-1.54+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.54.0.html) +![Version](https://img.shields.io/badge/rustc-1.56+-ab6000.svg) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-multipart.svg)
[![dependency status](https://deps.rs/crate/actix-multipart/0.4.0/status.svg)](https://deps.rs/crate/actix-multipart/0.4.0) diff --git a/actix-router/CHANGES.md b/actix-router/CHANGES.md index 8e0e4f41e..39ff98c39 100644 --- a/actix-router/CHANGES.md +++ b/actix-router/CHANGES.md @@ -1,6 +1,7 @@ # Changes ## Unreleased - 2021-xx-xx +- Minimum supported Rust version (MSRV) is now 1.56 due to transitive `hashbrown` dependency. ## 0.5.0 - 2022-02-22 diff --git a/actix-router/Cargo.toml b/actix-router/Cargo.toml index 76f39f631..ceb5b14dc 100644 --- a/actix-router/Cargo.toml +++ b/actix-router/Cargo.toml @@ -21,7 +21,6 @@ default = ["http"] [dependencies] bytestring = ">=0.1.5, <2" -firestorm = "0.5" http = { version = "0.2.3", optional = true } regex = "1.5" serde = "1" @@ -29,7 +28,6 @@ tracing = { version = "0.1.30", default-features = false, features = ["log"] } [dev-dependencies] criterion = { version = "0.3", features = ["html_reports"] } -firestorm = { version = "0.5", features = ["enable_system_time"] } http = "0.2.5" serde = { version = "1", features = ["derive"] } percent-encoding = "2.1" diff --git a/actix-router/examples/flamegraph.rs b/actix-router/examples/flamegraph.rs deleted file mode 100644 index 798cc22d9..000000000 --- a/actix-router/examples/flamegraph.rs +++ /dev/null @@ -1,169 +0,0 @@ -macro_rules! register { - (brackets) => {{ - register!(finish => "{p1}", "{p2}", "{p3}", "{p4}") - }}; - (finish => $p1:literal, $p2:literal, $p3:literal, $p4:literal) => {{ - let arr = [ - concat!("/authorizations"), - concat!("/authorizations/", $p1), - concat!("/applications/", $p1, "/tokens/", $p2), - concat!("/events"), - concat!("/repos/", $p1, "/", $p2, "/events"), - concat!("/networks/", $p1, "/", $p2, "/events"), - concat!("/orgs/", $p1, "/events"), - concat!("/users/", $p1, "/received_events"), - concat!("/users/", $p1, "/received_events/public"), - concat!("/users/", $p1, "/events"), - concat!("/users/", $p1, "/events/public"), - concat!("/users/", $p1, "/events/orgs/", $p2), - concat!("/feeds"), - concat!("/notifications"), - concat!("/repos/", $p1, "/", $p2, "/notifications"), - concat!("/notifications/threads/", $p1), - concat!("/notifications/threads/", $p1, "/subscription"), - concat!("/repos/", $p1, "/", $p2, "/stargazers"), - concat!("/users/", $p1, "/starred"), - concat!("/user/starred"), - concat!("/user/starred/", $p1, "/", $p2), - concat!("/repos/", $p1, "/", $p2, "/subscribers"), - concat!("/users/", $p1, "/subscriptions"), - concat!("/user/subscriptions"), - concat!("/repos/", $p1, "/", $p2, "/subscription"), - concat!("/user/subscriptions/", $p1, "/", $p2), - concat!("/users/", $p1, "/gists"), - concat!("/gists"), - concat!("/gists/", $p1), - concat!("/gists/", $p1, "/star"), - concat!("/repos/", $p1, "/", $p2, "/git/blobs/", $p3), - concat!("/repos/", $p1, "/", $p2, "/git/commits/", $p3), - concat!("/repos/", $p1, "/", $p2, "/git/refs"), - concat!("/repos/", $p1, "/", $p2, "/git/tags/", $p3), - concat!("/repos/", $p1, "/", $p2, "/git/trees/", $p3), - concat!("/issues"), - concat!("/user/issues"), - concat!("/orgs/", $p1, "/issues"), - concat!("/repos/", $p1, "/", $p2, "/issues"), - concat!("/repos/", $p1, "/", $p2, "/issues/", $p3), - concat!("/repos/", $p1, "/", $p2, "/assignees"), - concat!("/repos/", $p1, "/", $p2, "/assignees/", $p3), - concat!("/repos/", $p1, "/", $p2, "/issues/", $p3, "/comments"), - concat!("/repos/", $p1, "/", $p2, "/issues/", $p3, "/events"), - concat!("/repos/", $p1, "/", $p2, "/labels"), - concat!("/repos/", $p1, "/", $p2, "/labels/", $p3), - concat!("/repos/", $p1, "/", $p2, "/issues/", $p3, "/labels"), - concat!("/repos/", $p1, "/", $p2, "/milestones/", $p3, "/labels"), - concat!("/repos/", $p1, "/", $p2, "/milestones/"), - concat!("/repos/", $p1, "/", $p2, "/milestones/", $p3), - concat!("/emojis"), - concat!("/gitignore/templates"), - concat!("/gitignore/templates/", $p1), - concat!("/meta"), - concat!("/rate_limit"), - concat!("/users/", $p1, "/orgs"), - concat!("/user/orgs"), - concat!("/orgs/", $p1), - concat!("/orgs/", $p1, "/members"), - concat!("/orgs/", $p1, "/members", $p2), - concat!("/orgs/", $p1, "/public_members"), - concat!("/orgs/", $p1, "/public_members/", $p2), - concat!("/orgs/", $p1, "/teams"), - concat!("/teams/", $p1), - concat!("/teams/", $p1, "/members"), - concat!("/teams/", $p1, "/members", $p2), - concat!("/teams/", $p1, "/repos"), - concat!("/teams/", $p1, "/repos/", $p2, "/", $p3), - concat!("/user/teams"), - concat!("/repos/", $p1, "/", $p2, "/pulls"), - concat!("/repos/", $p1, "/", $p2, "/pulls/", $p3), - concat!("/repos/", $p1, "/", $p2, "/pulls/", $p3, "/commits"), - concat!("/repos/", $p1, "/", $p2, "/pulls/", $p3, "/files"), - concat!("/repos/", $p1, "/", $p2, "/pulls/", $p3, "/merge"), - concat!("/repos/", $p1, "/", $p2, "/pulls/", $p3, "/comments"), - concat!("/user/repos"), - concat!("/users/", $p1, "/repos"), - concat!("/orgs/", $p1, "/repos"), - concat!("/repositories"), - concat!("/repos/", $p1, "/", $p2), - concat!("/repos/", $p1, "/", $p2, "/contributors"), - concat!("/repos/", $p1, "/", $p2, "/languages"), - concat!("/repos/", $p1, "/", $p2, "/teams"), - concat!("/repos/", $p1, "/", $p2, "/tags"), - concat!("/repos/", $p1, "/", $p2, "/branches"), - concat!("/repos/", $p1, "/", $p2, "/branches/", $p3), - concat!("/repos/", $p1, "/", $p2, "/collaborators"), - concat!("/repos/", $p1, "/", $p2, "/collaborators/", $p3), - concat!("/repos/", $p1, "/", $p2, "/comments"), - concat!("/repos/", $p1, "/", $p2, "/commits/", $p3, "/comments"), - concat!("/repos/", $p1, "/", $p2, "/commits"), - concat!("/repos/", $p1, "/", $p2, "/commits/", $p3), - concat!("/repos/", $p1, "/", $p2, "/readme"), - concat!("/repos/", $p1, "/", $p2, "/keys"), - concat!("/repos/", $p1, "/", $p2, "/keys", $p3), - concat!("/repos/", $p1, "/", $p2, "/downloads"), - concat!("/repos/", $p1, "/", $p2, "/downloads", $p3), - concat!("/repos/", $p1, "/", $p2, "/forks"), - concat!("/repos/", $p1, "/", $p2, "/hooks"), - concat!("/repos/", $p1, "/", $p2, "/hooks", $p3), - concat!("/repos/", $p1, "/", $p2, "/releases"), - concat!("/repos/", $p1, "/", $p2, "/releases/", $p3), - concat!("/repos/", $p1, "/", $p2, "/releases/", $p3, "/assets"), - concat!("/repos/", $p1, "/", $p2, "/stats/contributors"), - concat!("/repos/", $p1, "/", $p2, "/stats/commit_activity"), - concat!("/repos/", $p1, "/", $p2, "/stats/code_frequency"), - concat!("/repos/", $p1, "/", $p2, "/stats/participation"), - concat!("/repos/", $p1, "/", $p2, "/stats/punch_card"), - concat!("/repos/", $p1, "/", $p2, "/statuses/", $p3), - concat!("/search/repositories"), - concat!("/search/code"), - concat!("/search/issues"), - concat!("/search/users"), - concat!("/legacy/issues/search/", $p1, "/", $p2, "/", $p3, "/", $p4), - concat!("/legacy/repos/search/", $p1), - concat!("/legacy/user/search/", $p1), - concat!("/legacy/user/email/", $p1), - concat!("/users/", $p1), - concat!("/user"), - concat!("/users"), - concat!("/user/emails"), - concat!("/users/", $p1, "/followers"), - concat!("/user/followers"), - concat!("/users/", $p1, "/following"), - concat!("/user/following"), - concat!("/user/following/", $p1), - concat!("/users/", $p1, "/following", $p2), - concat!("/users/", $p1, "/keys"), - concat!("/user/keys"), - concat!("/user/keys/", $p1), - ]; - - arr.to_vec() - }}; -} - -static PATHS: [&str; 5] = [ - "/authorizations", - "/user/repos", - "/repos/rust-lang/rust/stargazers", - "/orgs/rust-lang/public_members/nikomatsakis", - "/repos/rust-lang/rust/releases/1.51.0", -]; - -fn main() { - let mut router = actix_router::Router::::build(); - - for route in register!(brackets) { - router.path(route, true); - } - - let actix = router.finish(); - - if firestorm::enabled() { - firestorm::bench("target", || { - for &route in &PATHS { - let mut path = actix_router::Path::new(route); - actix.recognize(&mut path).unwrap(); - } - }) - .unwrap(); - } -} diff --git a/actix-router/src/path.rs b/actix-router/src/path.rs index dfb645d72..5eef1c1e7 100644 --- a/actix-router/src/path.rs +++ b/actix-router/src/path.rs @@ -1,7 +1,6 @@ use std::borrow::Cow; use std::ops::{DerefMut, Index}; -use firestorm::profile_method; use serde::de; use crate::{de::PathDeserializer, Resource, ResourcePath}; @@ -52,7 +51,6 @@ impl Path { /// Returns full path as a string. #[inline] pub fn as_str(&self) -> &str { - profile_method!(as_str); self.path.path() } @@ -61,7 +59,6 @@ impl Path { /// Returns empty string if no more is to be processed. #[inline] pub fn unprocessed(&self) -> &str { - profile_method!(unprocessed); // clamp skip to path length let skip = (self.skip as usize).min(self.as_str().len()); &self.path.path()[skip..] @@ -72,8 +69,6 @@ impl Path { #[deprecated(since = "0.6.0", note = "Use `.as_str()` or `.unprocessed()`.")] #[inline] pub fn path(&self) -> &str { - profile_method!(path); - let skip = self.skip as usize; let path = self.path.path(); if skip <= path.len() { @@ -86,8 +81,6 @@ impl Path { /// Set new path. #[inline] pub fn set(&mut self, path: T) { - profile_method!(set); - self.skip = 0; self.path = path; self.segments.clear(); @@ -96,8 +89,6 @@ impl Path { /// Reset state. #[inline] pub fn reset(&mut self) { - profile_method!(reset); - self.skip = 0; self.segments.clear(); } @@ -105,13 +96,10 @@ impl Path { /// Skip first `n` chars in path. #[inline] pub fn skip(&mut self, n: u16) { - profile_method!(skip); self.skip += n; } pub(crate) fn add(&mut self, name: impl Into>, value: PathItem) { - profile_method!(add); - match value { PathItem::Static(s) => self.segments.push((name.into(), PathItem::Static(s))), PathItem::Segment(begin, end) => self.segments.push(( @@ -127,8 +115,6 @@ impl Path { name: impl Into>, value: impl Into>, ) { - profile_method!(add_static); - self.segments .push((name.into(), PathItem::Static(value.into()))); } @@ -147,8 +133,6 @@ impl Path { /// Get matched parameter by name without type conversion pub fn get(&self, name: &str) -> Option<&str> { - profile_method!(get); - for (seg_name, val) in self.segments.iter() { if name == seg_name { return match val { @@ -167,8 +151,6 @@ impl Path { /// /// If keyed parameter is not available empty string is used as default value. pub fn query(&self, key: &str) -> &str { - profile_method!(query); - if let Some(s) = self.get(key) { s } else { @@ -186,7 +168,6 @@ impl Path { /// Try to deserialize matching parameters to a specified type `U` pub fn load<'de, U: serde::Deserialize<'de>>(&'de self) -> Result { - profile_method!(load); de::Deserialize::deserialize(PathDeserializer::new(self)) } } diff --git a/actix-router/src/resource.rs b/actix-router/src/resource.rs index 3d121f369..bc082273c 100644 --- a/actix-router/src/resource.rs +++ b/actix-router/src/resource.rs @@ -5,7 +5,6 @@ use std::{ mem, }; -use firestorm::{profile_fn, profile_method, profile_section}; use regex::{escape, Regex, RegexSet}; use tracing::error; @@ -272,7 +271,6 @@ impl ResourceDef { /// assert!(!resource.is_match("/foo")); /// ``` pub fn new(paths: T) -> Self { - profile_method!(new); Self::construct(paths, false) } @@ -300,7 +298,6 @@ impl ResourceDef { /// assert!(!resource.is_match("/foo")); /// ``` pub fn prefix(paths: T) -> Self { - profile_method!(prefix); ResourceDef::construct(paths, true) } @@ -325,7 +322,6 @@ impl ResourceDef { /// assert!(!resource.is_match("user/123")); /// ``` pub fn root_prefix(path: &str) -> Self { - profile_method!(root_prefix); ResourceDef::prefix(insert_slash(path).into_owned()) } @@ -549,8 +545,6 @@ impl ResourceDef { /// ``` #[inline] pub fn is_match(&self, path: &str) -> bool { - profile_method!(is_match); - // this function could be expressed as: // `self.find_match(path).is_some()` // but this skips some checks and uses potentially faster regex methods @@ -598,8 +592,6 @@ impl ResourceDef { /// assert_eq!(resource.find_match("/profile/1234"), Some(13)); /// ``` pub fn find_match(&self, path: &str) -> Option { - profile_method!(find_match); - match &self.pat_type { PatternType::Static(pattern) => self.static_match(pattern, path), @@ -634,7 +626,6 @@ impl ResourceDef { /// assert_eq!(path.unprocessed(), ""); /// ``` pub fn capture_match_info(&self, resource: &mut R) -> bool { - profile_method!(capture_match_info); self.capture_match_info_fn(resource, |_| true) } @@ -680,53 +671,35 @@ impl ResourceDef { R: Resource, F: FnOnce(&R) -> bool, { - profile_method!(capture_match_info_fn); - let mut segments = <[PathItem; MAX_DYNAMIC_SEGMENTS]>::default(); let path = resource.resource_path(); let path_str = path.unprocessed(); let (matched_len, matched_vars) = match &self.pat_type { - PatternType::Static(pattern) => { - profile_section!(pattern_static_or_prefix); - - match self.static_match(pattern, path_str) { - Some(len) => (len, None), - None => return false, - } - } + PatternType::Static(pattern) => match self.static_match(pattern, path_str) { + Some(len) => (len, None), + None => return false, + }, PatternType::Dynamic(re, names) => { - profile_section!(pattern_dynamic); - - let captures = { - profile_section!(pattern_dynamic_regex_exec); - - match re.captures(path.unprocessed()) { - Some(captures) => captures, - _ => return false, - } + let captures = match re.captures(path.unprocessed()) { + Some(captures) => captures, + _ => return false, }; - { - profile_section!(pattern_dynamic_extract_captures); - - for (no, name) in names.iter().enumerate() { - if let Some(m) = captures.name(name) { - segments[no] = PathItem::Segment(m.start() as u16, m.end() as u16); - } else { - error!("Dynamic path match but not all segments found: {}", name); - return false; - } + for (no, name) in names.iter().enumerate() { + if let Some(m) = captures.name(name) { + segments[no] = PathItem::Segment(m.start() as u16, m.end() as u16); + } else { + error!("Dynamic path match but not all segments found: {}", name); + return false; } - }; + } (captures[1].len(), Some(names)) } PatternType::DynamicSet(re, params) => { - profile_section!(pattern_dynamic_set); - let path = path.unprocessed(); let (pattern, names) = match re.matches(path).into_iter().next() { Some(idx) => ¶ms[idx], @@ -809,7 +782,6 @@ impl ResourceDef { I: IntoIterator, I::Item: AsRef, { - profile_method!(resource_path_from_iter); let mut iter = values.into_iter(); self.build_resource_path(path, |_| iter.next()) } @@ -845,7 +817,6 @@ impl ResourceDef { V: AsRef, S: BuildHasher, { - profile_method!(resource_path_from_map); self.build_resource_path(path, |name| values.get(name)) } @@ -866,8 +837,6 @@ impl ResourceDef { } fn construct(paths: T, is_prefix: bool) -> Self { - profile_method!(construct); - let patterns = paths.patterns(); let (pat_type, segments) = match &patterns { Patterns::Single(pattern) => ResourceDef::parse(pattern, is_prefix, false), @@ -926,8 +895,6 @@ impl ResourceDef { /// # Panics /// Panics if given patterns does not contain a dynamic segment. fn parse_param(pattern: &str) -> (PatternSegment, String, &str, bool) { - profile_method!(parse_param); - const DEFAULT_PATTERN: &str = "[^/]+"; const DEFAULT_PATTERN_TAIL: &str = ".*"; @@ -997,8 +964,6 @@ impl ResourceDef { is_prefix: bool, force_dynamic: bool, ) -> (PatternType, Vec) { - profile_method!(parse); - if !force_dynamic && pattern.find('{').is_none() && !pattern.ends_with('*') { // pattern is static return ( @@ -1131,8 +1096,6 @@ impl From for ResourceDef { } pub(crate) fn insert_slash(path: &str) -> Cow<'_, str> { - profile_fn!(insert_slash); - if !path.is_empty() && !path.starts_with('/') { let mut new_path = String::with_capacity(path.len() + 1); new_path.push('/'); diff --git a/actix-router/src/router.rs b/actix-router/src/router.rs index f0e598683..8ed966b59 100644 --- a/actix-router/src/router.rs +++ b/actix-router/src/router.rs @@ -1,5 +1,3 @@ -use firestorm::profile_method; - use crate::{IntoPatterns, Resource, ResourceDef}; #[derive(Debug, Copy, Clone, PartialEq)] @@ -30,7 +28,6 @@ impl Router { where R: Resource, { - profile_method!(recognize); self.recognize_fn(resource, |_, _| true) } @@ -39,7 +36,6 @@ impl Router { where R: Resource, { - profile_method!(recognize_mut); self.recognize_mut_fn(resource, |_, _| true) } @@ -55,8 +51,6 @@ impl Router { R: Resource, F: FnMut(&R, &U) -> bool, { - profile_method!(recognize_checked); - for (rdef, val, ctx) in self.routes.iter() { if rdef.capture_match_info_fn(resource, |res| check(res, ctx)) { return Some((val, ResourceId(rdef.id()))); @@ -77,8 +71,6 @@ impl Router { R: Resource, F: FnMut(&R, &U) -> bool, { - profile_method!(recognize_mut_checked); - for (rdef, val, ctx) in self.routes.iter_mut() { if rdef.capture_match_info_fn(resource, |res| check(res, ctx)) { return Some((val, ResourceId(rdef.id()))); @@ -104,7 +96,6 @@ impl RouterBuilder { val: T, ctx: U, ) -> (&mut ResourceDef, &mut T, &mut U) { - profile_method!(push); self.routes.push((rdef, val, ctx)); self.routes .last_mut() @@ -131,7 +122,6 @@ where path: impl IntoPatterns, val: T, ) -> (&mut ResourceDef, &mut T, &mut U) { - profile_method!(path); self.push(ResourceDef::new(path), val, U::default()) } @@ -141,13 +131,11 @@ where prefix: impl IntoPatterns, val: T, ) -> (&mut ResourceDef, &mut T, &mut U) { - profile_method!(prefix); self.push(ResourceDef::prefix(prefix), val, U::default()) } /// Registers resource for [`ResourceDef`]. pub fn rdef(&mut self, rdef: ResourceDef, val: T) -> (&mut ResourceDef, &mut T, &mut U) { - profile_method!(rdef); self.push(rdef, val, U::default()) } } diff --git a/actix-test/CHANGES.md b/actix-test/CHANGES.md index 13e75c01a..9b84f04b0 100644 --- a/actix-test/CHANGES.md +++ b/actix-test/CHANGES.md @@ -1,6 +1,7 @@ # Changes ## Unreleased - 2021-xx-xx +- Minimum supported Rust version (MSRV) is now 1.56 due to transitive `hashbrown` dependency. ## 0.1.0-beta.13 - 2022-02-16 diff --git a/actix-web-actors/CHANGES.md b/actix-web-actors/CHANGES.md index b4844bfa6..f143be29c 100644 --- a/actix-web-actors/CHANGES.md +++ b/actix-web-actors/CHANGES.md @@ -1,6 +1,7 @@ # Changes ## Unreleased - 2021-xx-xx +- Minimum supported Rust version (MSRV) is now 1.56 due to transitive `hashbrown` dependency. ## 4.1.0 - 2022-03-02 diff --git a/actix-web-actors/README.md b/actix-web-actors/README.md index 357154a86..39a10a4e2 100644 --- a/actix-web-actors/README.md +++ b/actix-web-actors/README.md @@ -4,7 +4,7 @@ [![crates.io](https://img.shields.io/crates/v/actix-web-actors?label=latest)](https://crates.io/crates/actix-web-actors) [![Documentation](https://docs.rs/actix-web-actors/badge.svg?version=4.1.0)](https://docs.rs/actix-web-actors/4.1.0) -[![Version](https://img.shields.io/badge/rustc-1.54+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.54.0.html) +![Version](https://img.shields.io/badge/rustc-1.56+-ab6000.svg) ![License](https://img.shields.io/crates/l/actix-web-actors.svg)
[![dependency status](https://deps.rs/crate/actix-web-actors/4.1.0/status.svg)](https://deps.rs/crate/actix-web-actors/4.1.0) diff --git a/actix-web-codegen/CHANGES.md b/actix-web-codegen/CHANGES.md index 04bbb4de1..14b368064 100644 --- a/actix-web-codegen/CHANGES.md +++ b/actix-web-codegen/CHANGES.md @@ -2,6 +2,7 @@ ## Unreleased - 2021-xx-xx - Fix support for guard paths in route handler macros. [#2771] +- Minimum supported Rust version (MSRV) is now 1.56 due to transitive `hashbrown` dependency. [#2771] https://github.com/actix/actix-web/pull/2771 diff --git a/actix-web-codegen/README.md b/actix-web-codegen/README.md index 439beadb4..178bb8c67 100644 --- a/actix-web-codegen/README.md +++ b/actix-web-codegen/README.md @@ -4,7 +4,7 @@ [![crates.io](https://img.shields.io/crates/v/actix-web-codegen?label=latest)](https://crates.io/crates/actix-web-codegen) [![Documentation](https://docs.rs/actix-web-codegen/badge.svg?version=4.0.0)](https://docs.rs/actix-web-codegen/4.0.0) -[![Version](https://img.shields.io/badge/rustc-1.54+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.54.0.html) +![Version](https://img.shields.io/badge/rustc-1.56+-ab6000.svg) ![License](https://img.shields.io/crates/l/actix-web-codegen.svg)
[![dependency status](https://deps.rs/crate/actix-web-codegen/4.0.0/status.svg)](https://deps.rs/crate/actix-web-codegen/4.0.0) diff --git a/actix-web-codegen/tests/trybuild.rs b/actix-web-codegen/tests/trybuild.rs index b2d9ce186..13eb84559 100644 --- a/actix-web-codegen/tests/trybuild.rs +++ b/actix-web-codegen/tests/trybuild.rs @@ -1,4 +1,4 @@ -#[rustversion::stable(1.54)] // MSRV +#[rustversion::stable(1.56)] // MSRV #[test] fn compile_macros() { let t = trybuild::TestCases::new(); diff --git a/actix-web/CHANGES.md b/actix-web/CHANGES.md index cb82ef653..86ded5729 100644 --- a/actix-web/CHANGES.md +++ b/actix-web/CHANGES.md @@ -7,6 +7,9 @@ - Add `ServiceConfig::default_service()`. [#2338] [#2743] - Implement `ResponseError` for `std::convert::Infallible` +### Changed +- Minimum supported Rust version (MSRV) is now 1.56 due to transitive `hashbrown` dependency. + ### Fixed - Clear connection-level data on `HttpRequest` drop. [#2742] diff --git a/actix-web/README.md b/actix-web/README.md index 957fb47b8..1eaaa2049 100644 --- a/actix-web/README.md +++ b/actix-web/README.md @@ -7,7 +7,7 @@ [![crates.io](https://img.shields.io/crates/v/actix-web?label=latest)](https://crates.io/crates/actix-web) [![Documentation](https://docs.rs/actix-web/badge.svg?version=4.0.1)](https://docs.rs/actix-web/4.0.1) -![MSRV](https://img.shields.io/badge/rustc-1.54+-ab6000.svg) +![MSRV](https://img.shields.io/badge/rustc-1.56+-ab6000.svg) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-web.svg) [![Dependency Status](https://deps.rs/crate/actix-web/4.0.1/status.svg)](https://deps.rs/crate/actix-web/4.0.1)
@@ -33,7 +33,7 @@ - SSL support using OpenSSL or Rustls - Middlewares ([Logger, Session, CORS, etc](https://actix.rs/docs/middleware/)) - Integrates with the [`awc` HTTP client](https://docs.rs/awc/) -- Runs on stable Rust 1.54+ +- Runs on stable Rust 1.56+ ## Documentation diff --git a/awc/CHANGES.md b/awc/CHANGES.md index ebc0dbe61..622388286 100644 --- a/awc/CHANGES.md +++ b/awc/CHANGES.md @@ -1,6 +1,8 @@ # Changes ## Unreleased - 2021-xx-xx +### Changed +- Minimum supported Rust version (MSRV) is now 1.56 due to transitive `hashbrown` dependency. ## 3.0.0 - 2022-03-07 diff --git a/clippy.toml b/clippy.toml index ece14b8d2..62ca74234 100644 --- a/clippy.toml +++ b/clippy.toml @@ -1 +1 @@ -msrv = "1.54" +msrv = "1.56" From 498fb954b37f4439e1e88ccc5606ec8e95fc974b Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sat, 11 Jun 2022 04:53:58 +0100 Subject: [PATCH 137/170] migrate from deprecated sha-1 to sha1 (#2780) closes #2778 --- actix-http/Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index cd5d3f379..8bdd72799 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -37,7 +37,7 @@ ws = [ "local-channel", "base64", "rand", - "sha-1", + "sha1", ] # TLS via OpenSSL @@ -86,7 +86,7 @@ h2 = { version = "0.3.9", optional = true } local-channel = { version = "0.1", optional = true } base64 = { version = "0.13", optional = true } rand = { version = "0.8", optional = true } -sha-1 = { version = "0.10", optional = true } +sha1 = { version = "0.10", optional = true } # openssl/rustls actix-tls = { version = "3", default-features = false, optional = true } From 264a703d947d4eaa3b39f8e90d02bb7800ddea54 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sat, 11 Jun 2022 13:43:13 +0100 Subject: [PATCH 138/170] revert broken fix in #2624 (#2779) * revert broken fix in #2624 * update changelog --- actix-http/CHANGES.md | 5 ++ actix-http/src/h1/dispatcher.rs | 73 ++------------------------- actix-http/src/h1/dispatcher_tests.rs | 3 ++ 3 files changed, 12 insertions(+), 69 deletions(-) diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 75c131512..d431ec5f3 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -4,6 +4,11 @@ ### Changed - Minimum supported Rust version (MSRV) is now 1.56 due to transitive `hashbrown` dependency. +### Fixed +- Revert broken fix in [#2624] that caused erroneous 500 error responses. Temporarily re-introduces [#2357] bug. [#2779] + +[#2779]: https://github.com/actix/actix-web/issues/2779 + ## 3.0.4 - 2022-03-09 ### Fixed diff --git a/actix-http/src/h1/dispatcher.rs b/actix-http/src/h1/dispatcher.rs index dea8a4beb..c2ddc06ba 100644 --- a/actix-http/src/h1/dispatcher.rs +++ b/actix-http/src/h1/dispatcher.rs @@ -15,14 +15,14 @@ use bitflags::bitflags; use bytes::{Buf, BytesMut}; use futures_core::ready; use pin_project_lite::pin_project; -use tracing::{debug, error, trace}; +use tracing::{error, trace}; use crate::{ body::{BodySize, BoxBody, MessageBody}, config::ServiceConfig, error::{DispatchError, ParseError, PayloadError}, service::HttpFlow, - ConnectionType, Error, Extensions, OnConnectData, Request, Response, StatusCode, + Error, Extensions, OnConnectData, Request, Response, StatusCode, }; use super::{ @@ -691,74 +691,12 @@ where let can_not_read = !self.can_read(cx); // limit amount of non-processed requests - if pipeline_queue_full { + if pipeline_queue_full || can_not_read { return Ok(false); } let mut this = self.as_mut().project(); - if can_not_read { - debug!("cannot read request payload"); - - if let Some(sender) = &this.payload { - // ...maybe handler does not want to read any more payload... - if let PayloadStatus::Dropped = sender.need_read(cx) { - debug!("handler dropped payload early; attempt to clean connection"); - // ...in which case poll request payload a few times - loop { - match this.codec.decode(this.read_buf)? { - Some(msg) => { - match msg { - // payload decoded did not yield EOF yet - Message::Chunk(Some(_)) => { - // if non-clean connection, next loop iter will detect empty - // read buffer and close connection - } - - // connection is in clean state for next request - Message::Chunk(None) => { - debug!("connection successfully cleaned"); - - // reset dispatcher state - let _ = this.payload.take(); - this.state.set(State::None); - - // break out of payload decode loop - break; - } - - // Either whole payload is read and loop is broken or more data - // was expected in which case connection is closed. In both - // situations dispatcher cannot get here. - Message::Item(_) => { - unreachable!("dispatcher is in payload receive state") - } - } - } - - // not enough info to decide if connection is going to be clean or not - None => { - error!( - "handler did not read whole payload and dispatcher could not \ - drain read buf; return 500 and close connection" - ); - - this.flags.insert(Flags::SHUTDOWN); - let mut res = Response::internal_server_error().drop_body(); - res.head_mut().set_connection_type(ConnectionType::Close); - this.messages.push_back(DispatcherMessage::Error(res)); - *this.error = Some(DispatchError::HandlerDroppedPayload); - return Ok(true); - } - } - } - } - } else { - // can_not_read and no request payload - return Ok(false); - } - } - let mut updated = false; // decode from read buf as many full requests as possible @@ -904,10 +842,7 @@ where if timer.as_mut().poll(cx).is_ready() { // timeout on first request (slow request) return 408 - trace!( - "timed out on slow request; \ - replying with 408 and closing connection" - ); + trace!("timed out on slow request; replying with 408 and closing connection"); let _ = self.as_mut().send_error_response( Response::with_body(StatusCode::REQUEST_TIMEOUT, ()), diff --git a/actix-http/src/h1/dispatcher_tests.rs b/actix-http/src/h1/dispatcher_tests.rs index 40454d45a..b3ee3d2bb 100644 --- a/actix-http/src/h1/dispatcher_tests.rs +++ b/actix-http/src/h1/dispatcher_tests.rs @@ -783,6 +783,9 @@ async fn upgrade_handling() { .await; } +// fix in #2624 reverted temporarily +// complete fix tracked in #2745 +#[ignore] #[actix_rt::test] async fn handler_drop_payload() { let _ = env_logger::try_init(); From 43671ae4aaf2c20150fd1e5ca54055eb5d114273 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sat, 11 Jun 2022 16:15:43 +0100 Subject: [PATCH 139/170] release 4.1 group (#2781) --- actix-files/CHANGES.md | 3 +++ actix-files/Cargo.toml | 4 ++-- actix-files/README.md | 4 ++-- actix-http/CHANGES.md | 5 +++++ actix-http/Cargo.toml | 4 ++-- actix-http/README.md | 4 ++-- actix-multipart/Cargo.toml | 2 +- actix-web-codegen/CHANGES.md | 5 ++++- actix-web-codegen/Cargo.toml | 2 +- actix-web-codegen/README.md | 4 ++-- actix-web/CHANGES.md | 3 +++ actix-web/Cargo.toml | 2 +- actix-web/README.md | 4 ++-- 13 files changed, 30 insertions(+), 16 deletions(-) diff --git a/actix-files/CHANGES.md b/actix-files/CHANGES.md index 7a21e0aba..ff3ec13ac 100644 --- a/actix-files/CHANGES.md +++ b/actix-files/CHANGES.md @@ -1,6 +1,9 @@ # Changes ## Unreleased - 2021-xx-xx + + +## 0.6.1 - 2022-06-11 - Add `NamedFile::{modified, metadata, content_type, content_disposition, encoding}()` getters. [#2021] - Update `tokio-uring` dependency to `0.3`. - Audio files now use `Content-Disposition: inline` instead of `attachment`. [#2645] diff --git a/actix-files/Cargo.toml b/actix-files/Cargo.toml index 3eb1edf29..02543095f 100644 --- a/actix-files/Cargo.toml +++ b/actix-files/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-files" -version = "0.6.0" +version = "0.6.1" authors = [ "Nikolay Kim ", "fakeshadow <24548779@qq.com>", @@ -47,5 +47,5 @@ actix-server = { version = "2.1", optional = true } # ensure matching tokio-urin [dev-dependencies] actix-rt = "2.7" actix-test = "0.1.0-beta.13" -actix-web = "4.0.0" +actix-web = "4" tempfile = "3.2" diff --git a/actix-files/README.md b/actix-files/README.md index 5035cad9e..737c0edef 100644 --- a/actix-files/README.md +++ b/actix-files/README.md @@ -3,11 +3,11 @@ > Static file serving for Actix Web [![crates.io](https://img.shields.io/crates/v/actix-files?label=latest)](https://crates.io/crates/actix-files) -[![Documentation](https://docs.rs/actix-files/badge.svg?version=0.6.0)](https://docs.rs/actix-files/0.6.0) +[![Documentation](https://docs.rs/actix-files/badge.svg?version=0.6.1)](https://docs.rs/actix-files/0.6.1) ![Version](https://img.shields.io/badge/rustc-1.56+-ab6000.svg) ![License](https://img.shields.io/crates/l/actix-files.svg)
-[![dependency status](https://deps.rs/crate/actix-files/0.6.0/status.svg)](https://deps.rs/crate/actix-files/0.6.0) +[![dependency status](https://deps.rs/crate/actix-files/0.6.1/status.svg)](https://deps.rs/crate/actix-files/0.6.1) [![Download](https://img.shields.io/crates/d/actix-files.svg)](https://crates.io/crates/actix-files) [![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index d431ec5f3..980997a06 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -1,12 +1,17 @@ # Changes ## Unreleased - 2021-xx-xx + + +## 3.1.0 - 2022-06-11 ### Changed - Minimum supported Rust version (MSRV) is now 1.56 due to transitive `hashbrown` dependency. ### Fixed - Revert broken fix in [#2624] that caused erroneous 500 error responses. Temporarily re-introduces [#2357] bug. [#2779] +[#2357]: https://github.com/actix/actix-web/issues/2357 +[#2624]: https://github.com/actix/actix-web/issues/2624 [#2779]: https://github.com/actix/actix-web/issues/2779 diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 8bdd72799..2a4966884 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-http" -version = "3.0.4" +version = "3.1.0" authors = [ "Nikolay Kim ", "Rob Ede ", @@ -100,7 +100,7 @@ zstd = { version = "0.11", optional = true } actix-http-test = { version = "3.0.0-beta.13", features = ["openssl"] } actix-server = "2" actix-tls = { version = "3", features = ["openssl"] } -actix-web = "4.0.0" +actix-web = "4" async-stream = "0.3" criterion = { version = "0.3", features = ["html_reports"] } diff --git a/actix-http/README.md b/actix-http/README.md index 136582352..388761aee 100644 --- a/actix-http/README.md +++ b/actix-http/README.md @@ -3,11 +3,11 @@ > HTTP primitives for the Actix ecosystem. [![crates.io](https://img.shields.io/crates/v/actix-http?label=latest)](https://crates.io/crates/actix-http) -[![Documentation](https://docs.rs/actix-http/badge.svg?version=3.0.4)](https://docs.rs/actix-http/3.0.4) +[![Documentation](https://docs.rs/actix-http/badge.svg?version=3.1.0)](https://docs.rs/actix-http/3.1.0) ![Version](https://img.shields.io/badge/rustc-1.56+-ab6000.svg) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-http.svg)
-[![dependency status](https://deps.rs/crate/actix-http/3.0.4/status.svg)](https://deps.rs/crate/actix-http/3.0.4) +[![dependency status](https://deps.rs/crate/actix-http/3.1.0/status.svg)](https://deps.rs/crate/actix-http/3.1.0) [![Download](https://img.shields.io/crates/d/actix-http.svg)](https://crates.io/crates/actix-http) [![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) diff --git a/actix-multipart/Cargo.toml b/actix-multipart/Cargo.toml index e93e22941..507e92752 100644 --- a/actix-multipart/Cargo.toml +++ b/actix-multipart/Cargo.toml @@ -15,7 +15,7 @@ path = "src/lib.rs" [dependencies] actix-utils = "3" -actix-web = { version = "4.0.0", default-features = false } +actix-web = { version = "4", default-features = false } bytes = "1" derive_more = "0.99.5" diff --git a/actix-web-codegen/CHANGES.md b/actix-web-codegen/CHANGES.md index 14b368064..24ab212a8 100644 --- a/actix-web-codegen/CHANGES.md +++ b/actix-web-codegen/CHANGES.md @@ -1,10 +1,13 @@ # Changes ## Unreleased - 2021-xx-xx + + +## 4.0.1 - 2022-06-11 - Fix support for guard paths in route handler macros. [#2771] - Minimum supported Rust version (MSRV) is now 1.56 due to transitive `hashbrown` dependency. -[#2771] https://github.com/actix/actix-web/pull/2771 +[#2771]: https://github.com/actix/actix-web/pull/2771 ## 4.0.0 - 2022-02-24 diff --git a/actix-web-codegen/Cargo.toml b/actix-web-codegen/Cargo.toml index 0d8b86459..52094443b 100644 --- a/actix-web-codegen/Cargo.toml +++ b/actix-web-codegen/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-web-codegen" -version = "4.0.0" +version = "4.0.1" description = "Routing and runtime macros for Actix Web" homepage = "https://actix.rs" repository = "https://github.com/actix/actix-web.git" diff --git a/actix-web-codegen/README.md b/actix-web-codegen/README.md index 178bb8c67..f02e8eb35 100644 --- a/actix-web-codegen/README.md +++ b/actix-web-codegen/README.md @@ -3,11 +3,11 @@ > Routing and runtime macros for Actix Web. [![crates.io](https://img.shields.io/crates/v/actix-web-codegen?label=latest)](https://crates.io/crates/actix-web-codegen) -[![Documentation](https://docs.rs/actix-web-codegen/badge.svg?version=4.0.0)](https://docs.rs/actix-web-codegen/4.0.0) +[![Documentation](https://docs.rs/actix-web-codegen/badge.svg?version=4.0.1)](https://docs.rs/actix-web-codegen/4.0.1) ![Version](https://img.shields.io/badge/rustc-1.56+-ab6000.svg) ![License](https://img.shields.io/crates/l/actix-web-codegen.svg)
-[![dependency status](https://deps.rs/crate/actix-web-codegen/4.0.0/status.svg)](https://deps.rs/crate/actix-web-codegen/4.0.0) +[![dependency status](https://deps.rs/crate/actix-web-codegen/4.0.1/status.svg)](https://deps.rs/crate/actix-web-codegen/4.0.1) [![Download](https://img.shields.io/crates/d/actix-web-codegen.svg)](https://crates.io/crates/actix-web-codegen) [![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) diff --git a/actix-web/CHANGES.md b/actix-web/CHANGES.md index 86ded5729..307bba9ba 100644 --- a/actix-web/CHANGES.md +++ b/actix-web/CHANGES.md @@ -1,6 +1,9 @@ # Changelog ## Unreleased - 2021-xx-xx + + +## 4.1.0 - 2022-06-11 ### Added - Add `ServiceRequest::extract()` to make it easier to use extractors when writing middlewares. [#2647] - Add `Route::wrap()` to allow individual routes to use middleware. [#2725] diff --git a/actix-web/Cargo.toml b/actix-web/Cargo.toml index 702fd6743..8cdf0f611 100644 --- a/actix-web/Cargo.toml +++ b/actix-web/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-web" -version = "4.0.1" +version = "4.1.0" authors = [ "Nikolay Kim ", "Rob Ede ", diff --git a/actix-web/README.md b/actix-web/README.md index 1eaaa2049..65ee4efca 100644 --- a/actix-web/README.md +++ b/actix-web/README.md @@ -6,10 +6,10 @@

[![crates.io](https://img.shields.io/crates/v/actix-web?label=latest)](https://crates.io/crates/actix-web) -[![Documentation](https://docs.rs/actix-web/badge.svg?version=4.0.1)](https://docs.rs/actix-web/4.0.1) +[![Documentation](https://docs.rs/actix-web/badge.svg?version=4.1.0)](https://docs.rs/actix-web/4.1.0) ![MSRV](https://img.shields.io/badge/rustc-1.56+-ab6000.svg) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-web.svg) -[![Dependency Status](https://deps.rs/crate/actix-web/4.0.1/status.svg)](https://deps.rs/crate/actix-web/4.0.1) +[![Dependency Status](https://deps.rs/crate/actix-web/4.1.0/status.svg)](https://deps.rs/crate/actix-web/4.1.0)
[![CI](https://github.com/actix/actix-web/actions/workflows/ci.yml/badge.svg)](https://github.com/actix/actix-web/actions/workflows/ci.yml) [![codecov](https://codecov.io/gh/actix/actix-web/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-web) From 392641658064469609edd366b6ad11c343ea0c88 Mon Sep 17 00:00:00 2001 From: Yuki Okushi Date: Sun, 12 Jun 2022 00:48:08 +0900 Subject: [PATCH 140/170] actix-http: Pull actix-web dev-dep from Git repo The published version of actix-web depends on a buggy version of zstd crate, temporarily use actix-web on git repo to avoid the build failure. Signed-off-by: Yuki Okushi --- actix-http/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 2a4966884..6afffd002 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -100,7 +100,7 @@ zstd = { version = "0.11", optional = true } actix-http-test = { version = "3.0.0-beta.13", features = ["openssl"] } actix-server = "2" actix-tls = { version = "3", features = ["openssl"] } -actix-web = "4" +actix-web = { git = "https://github.com/actix/actix-web", rev = "43671ae4aaf2c20150fd1e5ca54055eb5d114273" } async-stream = "0.3" criterion = { version = "0.3", features = ["html_reports"] } From 062127a210659a62bb6fc9827a0f9cab56b41752 Mon Sep 17 00:00:00 2001 From: Yuki Okushi Date: Sun, 12 Jun 2022 00:55:06 +0900 Subject: [PATCH 141/170] Revert "actix-http: Pull actix-web dev-dep from Git repo" This reverts commit 392641658064469609edd366b6ad11c343ea0c88. --- actix-http/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 6afffd002..2a4966884 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -100,7 +100,7 @@ zstd = { version = "0.11", optional = true } actix-http-test = { version = "3.0.0-beta.13", features = ["openssl"] } actix-server = "2" actix-tls = { version = "3", features = ["openssl"] } -actix-web = { git = "https://github.com/actix/actix-web", rev = "43671ae4aaf2c20150fd1e5ca54055eb5d114273" } +actix-web = "4" async-stream = "0.3" criterion = { version = "0.3", features = ["html_reports"] } From 265fa0d050ffe07306accaa25ceffca3a8a15dda Mon Sep 17 00:00:00 2001 From: Isabel Atkinson Date: Wed, 15 Jun 2022 17:38:10 -0400 Subject: [PATCH 142/170] Add link to MongoDB example in README (#2783) --- actix-web/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/actix-web/README.md b/actix-web/README.md index 65ee4efca..3fd0108ce 100644 --- a/actix-web/README.md +++ b/actix-web/README.md @@ -79,6 +79,7 @@ async fn main() -> std::io::Result<()> { - [Application State](https://github.com/actix/examples/tree/master/basics/state) - [JSON Handling](https://github.com/actix/examples/tree/master/json/json) - [Multipart Streams](https://github.com/actix/examples/tree/master/forms/multipart) +- [MongoDB Integration](https://github.com/actix/examples/tree/master/databases/mongodb) - [Diesel Integration](https://github.com/actix/examples/tree/master/databases/diesel) - [SQLite Integration](https://github.com/actix/examples/tree/master/databases/sqlite) - [Postgres Integration](https://github.com/actix/examples/tree/master/databases/postgres) From 6b7196225e16f30c2bd99d5a4d8d3f25e040aac9 Mon Sep 17 00:00:00 2001 From: Yuki Okushi Date: Wed, 22 Jun 2022 20:08:06 +0900 Subject: [PATCH 143/170] Bump up MSRV to 1.57 (#2789) --- .github/workflows/ci.yml | 2 +- actix-files/CHANGES.md | 3 ++- actix-files/README.md | 2 +- actix-http-test/CHANGES.md | 4 ++-- actix-http-test/README.md | 2 +- actix-http/CHANGES.md | 3 ++- actix-http/README.md | 2 +- actix-multipart/CHANGES.md | 4 ++-- actix-multipart/README.md | 2 +- actix-router/CHANGES.md | 4 ++-- actix-test/CHANGES.md | 4 ++-- actix-web-actors/CHANGES.md | 4 ++-- actix-web-actors/README.md | 2 +- actix-web-codegen/CHANGES.md | 3 ++- actix-web-codegen/README.md | 2 +- actix-web-codegen/tests/trybuild.rs | 2 +- .../tests/trybuild/route-duplicate-method-fail.stderr | 8 +++++--- .../tests/trybuild/route-missing-method-fail.stderr | 4 +++- .../tests/trybuild/route-unexpected-method-fail.stderr | 8 +++++--- actix-web/CHANGES.md | 3 ++- actix-web/README.md | 4 ++-- awc/CHANGES.md | 4 ++-- clippy.toml | 2 +- 23 files changed, 44 insertions(+), 34 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 95dc6ba99..49ad25ccf 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,7 +16,7 @@ jobs: - { name: macOS, os: macos-latest, triple: x86_64-apple-darwin } - { name: Windows, os: windows-2022, triple: x86_64-pc-windows-msvc } version: - - 1.56.0 # MSRV + - 1.57.0 # MSRV - stable name: ${{ matrix.target.name }} / ${{ matrix.version }} diff --git a/actix-files/CHANGES.md b/actix-files/CHANGES.md index ff3ec13ac..b127cd9ea 100644 --- a/actix-files/CHANGES.md +++ b/actix-files/CHANGES.md @@ -1,6 +1,7 @@ # Changes -## Unreleased - 2021-xx-xx +## Unreleased - 2022-xx-xx +- Minimum supported Rust version (MSRV) is now 1.57 due to transitive `time` dependency. ## 0.6.1 - 2022-06-11 diff --git a/actix-files/README.md b/actix-files/README.md index 737c0edef..35db41c9a 100644 --- a/actix-files/README.md +++ b/actix-files/README.md @@ -4,7 +4,7 @@ [![crates.io](https://img.shields.io/crates/v/actix-files?label=latest)](https://crates.io/crates/actix-files) [![Documentation](https://docs.rs/actix-files/badge.svg?version=0.6.1)](https://docs.rs/actix-files/0.6.1) -![Version](https://img.shields.io/badge/rustc-1.56+-ab6000.svg) +![Version](https://img.shields.io/badge/rustc-1.57+-ab6000.svg) ![License](https://img.shields.io/crates/l/actix-files.svg)
[![dependency status](https://deps.rs/crate/actix-files/0.6.1/status.svg)](https://deps.rs/crate/actix-files/0.6.1) diff --git a/actix-http-test/CHANGES.md b/actix-http-test/CHANGES.md index 3f0be5356..f91ef4081 100644 --- a/actix-http-test/CHANGES.md +++ b/actix-http-test/CHANGES.md @@ -1,7 +1,7 @@ # Changes -## Unreleased - 2021-xx-xx -- Minimum supported Rust version (MSRV) is now 1.56 due to transitive `hashbrown` dependency. +## Unreleased - 2022-xx-xx +- Minimum supported Rust version (MSRV) is now 1.57 due to transitive `time` dependency. ## 3.0.0-beta.13 - 2022-02-16 diff --git a/actix-http-test/README.md b/actix-http-test/README.md index 8b8cacc2e..9429bb760 100644 --- a/actix-http-test/README.md +++ b/actix-http-test/README.md @@ -4,7 +4,7 @@ [![crates.io](https://img.shields.io/crates/v/actix-http-test?label=latest)](https://crates.io/crates/actix-http-test) [![Documentation](https://docs.rs/actix-http-test/badge.svg?version=3.0.0-beta.13)](https://docs.rs/actix-http-test/3.0.0-beta.13) -![Version](https://img.shields.io/badge/rustc-1.56+-ab6000.svg) +![Version](https://img.shields.io/badge/rustc-1.57+-ab6000.svg) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-http-test)
[![Dependency Status](https://deps.rs/crate/actix-http-test/3.0.0-beta.13/status.svg)](https://deps.rs/crate/actix-http-test/3.0.0-beta.13) diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 980997a06..209f86cad 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -1,6 +1,7 @@ # Changes -## Unreleased - 2021-xx-xx +## Unreleased - 2022-xx-xx +- Minimum supported Rust version (MSRV) is now 1.57 due to transitive `time` dependency. ## 3.1.0 - 2022-06-11 diff --git a/actix-http/README.md b/actix-http/README.md index 388761aee..211f433e8 100644 --- a/actix-http/README.md +++ b/actix-http/README.md @@ -4,7 +4,7 @@ [![crates.io](https://img.shields.io/crates/v/actix-http?label=latest)](https://crates.io/crates/actix-http) [![Documentation](https://docs.rs/actix-http/badge.svg?version=3.1.0)](https://docs.rs/actix-http/3.1.0) -![Version](https://img.shields.io/badge/rustc-1.56+-ab6000.svg) +![Version](https://img.shields.io/badge/rustc-1.57+-ab6000.svg) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-http.svg)
[![dependency status](https://deps.rs/crate/actix-http/3.1.0/status.svg)](https://deps.rs/crate/actix-http/3.1.0) diff --git a/actix-multipart/CHANGES.md b/actix-multipart/CHANGES.md index 53fbf9393..ed5c97e1d 100644 --- a/actix-multipart/CHANGES.md +++ b/actix-multipart/CHANGES.md @@ -1,7 +1,7 @@ # Changes -## Unreleased - 2021-xx-xx -- Minimum supported Rust version (MSRV) is now 1.56 due to transitive `hashbrown` dependency. +## Unreleased - 2022-xx-xx +- Minimum supported Rust version (MSRV) is now 1.57 due to transitive `time` dependency. ## 0.4.0 - 2022-02-25 diff --git a/actix-multipart/README.md b/actix-multipart/README.md index 0b375bf8d..0b1e2df17 100644 --- a/actix-multipart/README.md +++ b/actix-multipart/README.md @@ -4,7 +4,7 @@ [![crates.io](https://img.shields.io/crates/v/actix-multipart?label=latest)](https://crates.io/crates/actix-multipart) [![Documentation](https://docs.rs/actix-multipart/badge.svg?version=0.4.0)](https://docs.rs/actix-multipart/0.4.0) -![Version](https://img.shields.io/badge/rustc-1.56+-ab6000.svg) +![Version](https://img.shields.io/badge/rustc-1.57+-ab6000.svg) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-multipart.svg)
[![dependency status](https://deps.rs/crate/actix-multipart/0.4.0/status.svg)](https://deps.rs/crate/actix-multipart/0.4.0) diff --git a/actix-router/CHANGES.md b/actix-router/CHANGES.md index 39ff98c39..1e4fc41f2 100644 --- a/actix-router/CHANGES.md +++ b/actix-router/CHANGES.md @@ -1,7 +1,7 @@ # Changes -## Unreleased - 2021-xx-xx -- Minimum supported Rust version (MSRV) is now 1.56 due to transitive `hashbrown` dependency. +## Unreleased - 2022-xx-xx +- Minimum supported Rust version (MSRV) is now 1.57 due to transitive `time` dependency. ## 0.5.0 - 2022-02-22 diff --git a/actix-test/CHANGES.md b/actix-test/CHANGES.md index 9b84f04b0..43e306bb1 100644 --- a/actix-test/CHANGES.md +++ b/actix-test/CHANGES.md @@ -1,7 +1,7 @@ # Changes -## Unreleased - 2021-xx-xx -- Minimum supported Rust version (MSRV) is now 1.56 due to transitive `hashbrown` dependency. +## Unreleased - 2022-xx-xx +- Minimum supported Rust version (MSRV) is now 1.57 due to transitive `time` dependency. ## 0.1.0-beta.13 - 2022-02-16 diff --git a/actix-web-actors/CHANGES.md b/actix-web-actors/CHANGES.md index f143be29c..33d4712f8 100644 --- a/actix-web-actors/CHANGES.md +++ b/actix-web-actors/CHANGES.md @@ -1,7 +1,7 @@ # Changes -## Unreleased - 2021-xx-xx -- Minimum supported Rust version (MSRV) is now 1.56 due to transitive `hashbrown` dependency. +## Unreleased - 2022-xx-xx +- Minimum supported Rust version (MSRV) is now 1.57 due to transitive `time` dependency. ## 4.1.0 - 2022-03-02 diff --git a/actix-web-actors/README.md b/actix-web-actors/README.md index 39a10a4e2..8d64c0851 100644 --- a/actix-web-actors/README.md +++ b/actix-web-actors/README.md @@ -4,7 +4,7 @@ [![crates.io](https://img.shields.io/crates/v/actix-web-actors?label=latest)](https://crates.io/crates/actix-web-actors) [![Documentation](https://docs.rs/actix-web-actors/badge.svg?version=4.1.0)](https://docs.rs/actix-web-actors/4.1.0) -![Version](https://img.shields.io/badge/rustc-1.56+-ab6000.svg) +![Version](https://img.shields.io/badge/rustc-1.57+-ab6000.svg) ![License](https://img.shields.io/crates/l/actix-web-actors.svg)
[![dependency status](https://deps.rs/crate/actix-web-actors/4.1.0/status.svg)](https://deps.rs/crate/actix-web-actors/4.1.0) diff --git a/actix-web-codegen/CHANGES.md b/actix-web-codegen/CHANGES.md index 24ab212a8..a85d6c454 100644 --- a/actix-web-codegen/CHANGES.md +++ b/actix-web-codegen/CHANGES.md @@ -1,6 +1,7 @@ # Changes -## Unreleased - 2021-xx-xx +## Unreleased - 2022-xx-xx +- Minimum supported Rust version (MSRV) is now 1.57 due to transitive `time` dependency. ## 4.0.1 - 2022-06-11 diff --git a/actix-web-codegen/README.md b/actix-web-codegen/README.md index f02e8eb35..26f070f18 100644 --- a/actix-web-codegen/README.md +++ b/actix-web-codegen/README.md @@ -4,7 +4,7 @@ [![crates.io](https://img.shields.io/crates/v/actix-web-codegen?label=latest)](https://crates.io/crates/actix-web-codegen) [![Documentation](https://docs.rs/actix-web-codegen/badge.svg?version=4.0.1)](https://docs.rs/actix-web-codegen/4.0.1) -![Version](https://img.shields.io/badge/rustc-1.56+-ab6000.svg) +![Version](https://img.shields.io/badge/rustc-1.57+-ab6000.svg) ![License](https://img.shields.io/crates/l/actix-web-codegen.svg)
[![dependency status](https://deps.rs/crate/actix-web-codegen/4.0.1/status.svg)](https://deps.rs/crate/actix-web-codegen/4.0.1) diff --git a/actix-web-codegen/tests/trybuild.rs b/actix-web-codegen/tests/trybuild.rs index 13eb84559..976b3d52c 100644 --- a/actix-web-codegen/tests/trybuild.rs +++ b/actix-web-codegen/tests/trybuild.rs @@ -1,4 +1,4 @@ -#[rustversion::stable(1.56)] // MSRV +#[rustversion::stable(1.57)] // MSRV #[test] fn compile_macros() { let t = trybuild::TestCases::new(); diff --git a/actix-web-codegen/tests/trybuild/route-duplicate-method-fail.stderr b/actix-web-codegen/tests/trybuild/route-duplicate-method-fail.stderr index 90cff1b1c..fe9274bc8 100644 --- a/actix-web-codegen/tests/trybuild/route-duplicate-method-fail.stderr +++ b/actix-web-codegen/tests/trybuild/route-duplicate-method-fail.stderr @@ -1,11 +1,13 @@ error: HTTP method defined more than once: `GET` - --> $DIR/route-duplicate-method-fail.rs:3:35 + --> tests/trybuild/route-duplicate-method-fail.rs:3:35 | 3 | #[route("/", method="GET", method="GET")] | ^^^^^ error[E0277]: the trait bound `fn() -> impl std::future::Future {index}: HttpServiceFactory` is not satisfied - --> $DIR/route-duplicate-method-fail.rs:12:55 + --> tests/trybuild/route-duplicate-method-fail.rs:12:55 | 12 | let srv = actix_test::start(|| App::new().service(index)); - | ^^^^^ the trait `HttpServiceFactory` is not implemented for `fn() -> impl std::future::Future {index}` + | ------- ^^^^^ the trait `HttpServiceFactory` is not implemented for `fn() -> impl std::future::Future {index}` + | | + | required by a bound introduced by this call diff --git a/actix-web-codegen/tests/trybuild/route-missing-method-fail.stderr b/actix-web-codegen/tests/trybuild/route-missing-method-fail.stderr index b1cefafde..284b2cf4a 100644 --- a/actix-web-codegen/tests/trybuild/route-missing-method-fail.stderr +++ b/actix-web-codegen/tests/trybuild/route-missing-method-fail.stderr @@ -10,4 +10,6 @@ error[E0277]: the trait bound `fn() -> impl std::future::Future {index}: HttpSer --> tests/trybuild/route-missing-method-fail.rs:12:55 | 12 | let srv = actix_test::start(|| App::new().service(index)); - | ^^^^^ the trait `HttpServiceFactory` is not implemented for `fn() -> impl std::future::Future {index}` + | ------- ^^^^^ the trait `HttpServiceFactory` is not implemented for `fn() -> impl std::future::Future {index}` + | | + | required by a bound introduced by this call diff --git a/actix-web-codegen/tests/trybuild/route-unexpected-method-fail.stderr b/actix-web-codegen/tests/trybuild/route-unexpected-method-fail.stderr index dda366067..804ba69f3 100644 --- a/actix-web-codegen/tests/trybuild/route-unexpected-method-fail.stderr +++ b/actix-web-codegen/tests/trybuild/route-unexpected-method-fail.stderr @@ -1,11 +1,13 @@ error: Unexpected HTTP method: `UNEXPECTED` - --> $DIR/route-unexpected-method-fail.rs:3:21 + --> tests/trybuild/route-unexpected-method-fail.rs:3:21 | 3 | #[route("/", method="UNEXPECTED")] | ^^^^^^^^^^^^ error[E0277]: the trait bound `fn() -> impl std::future::Future {index}: HttpServiceFactory` is not satisfied - --> $DIR/route-unexpected-method-fail.rs:12:55 + --> tests/trybuild/route-unexpected-method-fail.rs:12:55 | 12 | let srv = actix_test::start(|| App::new().service(index)); - | ^^^^^ the trait `HttpServiceFactory` is not implemented for `fn() -> impl std::future::Future {index}` + | ------- ^^^^^ the trait `HttpServiceFactory` is not implemented for `fn() -> impl std::future::Future {index}` + | | + | required by a bound introduced by this call diff --git a/actix-web/CHANGES.md b/actix-web/CHANGES.md index 307bba9ba..fb27cddfd 100644 --- a/actix-web/CHANGES.md +++ b/actix-web/CHANGES.md @@ -1,6 +1,7 @@ # Changelog -## Unreleased - 2021-xx-xx +## Unreleased - 2022-xx-xx +- Minimum supported Rust version (MSRV) is now 1.57 due to transitive `time` dependency. ## 4.1.0 - 2022-06-11 diff --git a/actix-web/README.md b/actix-web/README.md index 3fd0108ce..fdd4a8648 100644 --- a/actix-web/README.md +++ b/actix-web/README.md @@ -7,7 +7,7 @@ [![crates.io](https://img.shields.io/crates/v/actix-web?label=latest)](https://crates.io/crates/actix-web) [![Documentation](https://docs.rs/actix-web/badge.svg?version=4.1.0)](https://docs.rs/actix-web/4.1.0) -![MSRV](https://img.shields.io/badge/rustc-1.56+-ab6000.svg) +![MSRV](https://img.shields.io/badge/rustc-1.57+-ab6000.svg) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-web.svg) [![Dependency Status](https://deps.rs/crate/actix-web/4.1.0/status.svg)](https://deps.rs/crate/actix-web/4.1.0)
@@ -33,7 +33,7 @@ - SSL support using OpenSSL or Rustls - Middlewares ([Logger, Session, CORS, etc](https://actix.rs/docs/middleware/)) - Integrates with the [`awc` HTTP client](https://docs.rs/awc/) -- Runs on stable Rust 1.56+ +- Runs on stable Rust 1.57+ ## Documentation diff --git a/awc/CHANGES.md b/awc/CHANGES.md index 622388286..e229a6d96 100644 --- a/awc/CHANGES.md +++ b/awc/CHANGES.md @@ -1,8 +1,8 @@ # Changes -## Unreleased - 2021-xx-xx +## Unreleased - 2022-xx-xx ### Changed -- Minimum supported Rust version (MSRV) is now 1.56 due to transitive `hashbrown` dependency. +- Minimum supported Rust version (MSRV) is now 1.57 due to transitive `time` dependency. ## 3.0.0 - 2022-03-07 diff --git a/clippy.toml b/clippy.toml index 62ca74234..5cccb362c 100644 --- a/clippy.toml +++ b/clippy.toml @@ -1 +1 @@ -msrv = "1.56" +msrv = "1.57" From 5d0e8138eeab63647e1e36001e6ae8e2a8ac3722 Mon Sep 17 00:00:00 2001 From: e-rhodes <33500135+e-rhodes@users.noreply.github.com> Date: Wed, 22 Jun 2022 14:02:03 -0600 Subject: [PATCH 144/170] Add getters for `&ServiceRequest` (#2786) --- actix-web/CHANGES.md | 3 +++ actix-web/src/service.rs | 12 ++++++++++++ 2 files changed, 15 insertions(+) diff --git a/actix-web/CHANGES.md b/actix-web/CHANGES.md index fb27cddfd..3fa2f8f21 100644 --- a/actix-web/CHANGES.md +++ b/actix-web/CHANGES.md @@ -2,7 +2,10 @@ ## Unreleased - 2022-xx-xx - Minimum supported Rust version (MSRV) is now 1.57 due to transitive `time` dependency. +### Added +- Add `ServiceRequest::{parts, request}()` getter methods. [#2786] +[#2786]: https://github.com/actix/actix-web/pull/2786 ## 4.1.0 - 2022-06-11 ### Added diff --git a/actix-web/src/service.rs b/actix-web/src/service.rs index a9e809bf9..0ad92d8a1 100644 --- a/actix-web/src/service.rs +++ b/actix-web/src/service.rs @@ -95,6 +95,18 @@ impl ServiceRequest { (&mut self.req, &mut self.payload) } + /// Returns immutable accessors to inner parts. + #[inline] + pub fn parts(&self) -> (&HttpRequest, &Payload) { + (&self.req, &self.payload) + } + + /// Returns immutable accessor to inner [`HttpRequest`]. + #[inline] + pub fn request(&self) -> &HttpRequest { + &self.req + } + /// Derives a type from this request using an [extractor](crate::FromRequest). /// /// Returns the `T` extractor's `Future` type which can be `await`ed. This is particularly handy From de92b3be2e6894c353816c6bd29d2a4768ccb92f Mon Sep 17 00:00:00 2001 From: oatoam <31235342+oatoam@users.noreply.github.com> Date: Fri, 24 Jun 2022 11:46:17 +0800 Subject: [PATCH 145/170] fix unrecoverable Err(Overflow) in websocket frame parser (#2790) --- actix-http/CHANGES.md | 6 +++++ actix-http/src/ws/frame.rs | 49 +++++++++++++++++++++++++++----------- 2 files changed, 41 insertions(+), 14 deletions(-) diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 209f86cad..dd6051b85 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -1,8 +1,14 @@ # Changes ## Unreleased - 2022-xx-xx +### Fixed +- Websocket parser no longer throws endless overflow errors after receiving an oversized frame. [#2790] + +### Changed - Minimum supported Rust version (MSRV) is now 1.57 due to transitive `time` dependency. +[#2790]: https://github.com/actix/actix-web/pull/2790 + ## 3.1.0 - 2022-06-11 ### Changed diff --git a/actix-http/src/ws/frame.rs b/actix-http/src/ws/frame.rs index 17e34e2ba..3659b6c3b 100644 --- a/actix-http/src/ws/frame.rs +++ b/actix-http/src/ws/frame.rs @@ -17,7 +17,6 @@ impl Parser { fn parse_metadata( src: &[u8], server: bool, - max_size: usize, ) -> Result)>, ProtocolError> { let chunk_len = src.len(); @@ -60,20 +59,12 @@ impl Parser { return Ok(None); } let len = u64::from_be_bytes(TryFrom::try_from(&src[idx..idx + 8]).unwrap()); - if len > max_size as u64 { - return Err(ProtocolError::Overflow); - } idx += 8; len as usize } else { len as usize }; - // check for max allowed size - if length > max_size { - return Err(ProtocolError::Overflow); - } - let mask = if server { if chunk_len < idx + 4 { return Ok(None); @@ -98,11 +89,10 @@ impl Parser { max_size: usize, ) -> Result)>, ProtocolError> { // try to parse ws frame metadata - let (idx, finished, opcode, length, mask) = - match Parser::parse_metadata(src, server, max_size)? { - None => return Ok(None), - Some(res) => res, - }; + let (idx, finished, opcode, length, mask) = match Parser::parse_metadata(src, server)? { + None => return Ok(None), + Some(res) => res, + }; // not enough data if src.len() < idx + length { @@ -112,6 +102,13 @@ impl Parser { // remove prefix src.advance(idx); + // check for max allowed size + if length > max_size { + // drop the payload + src.advance(length); + return Err(ProtocolError::Overflow); + } + // no need for body if length == 0 { return Ok(Some((finished, opcode, None))); @@ -339,6 +336,30 @@ mod tests { } } + #[test] + fn test_parse_frame_max_size_recoverability() { + let mut buf = BytesMut::new(); + // The first text frame with length == 2, payload doesn't matter. + buf.extend(&[0b0000_0001u8, 0b0000_0010u8, 0b0000_0000u8, 0b0000_0000u8]); + // Next binary frame with length == 2 and payload == `[0x1111_1111u8, 0x1111_1111u8]`. + buf.extend(&[0b0000_0010u8, 0b0000_0010u8, 0b1111_1111u8, 0b1111_1111u8]); + + assert_eq!(buf.len(), 8); + assert!(matches!( + Parser::parse(&mut buf, false, 1), + Err(ProtocolError::Overflow) + )); + assert_eq!(buf.len(), 4); + let frame = extract(Parser::parse(&mut buf, false, 2)); + assert!(!frame.finished); + assert_eq!(frame.opcode, OpCode::Binary); + assert_eq!( + frame.payload, + Bytes::from(vec![0b1111_1111u8, 0b1111_1111u8]) + ); + assert_eq!(buf.len(), 0); + } + #[test] fn test_ping_frame() { let mut buf = BytesMut::new(); From 8dbf7da89feb4f2127f72b97b9d51b7e7847a067 Mon Sep 17 00:00:00 2001 From: PeterPierinakos <101414157+PeterPierinakos@users.noreply.github.com> Date: Sat, 25 Jun 2022 14:01:06 +0000 Subject: [PATCH 146/170] Fix common grammar mistakes and add small documentation for AppConfig's Default implementation (#2793) --- actix-web/src/app.rs | 2 +- actix-web/src/app_service.rs | 2 +- actix-web/src/config.rs | 10 ++++++++++ actix-web/src/guard.rs | 4 ++-- actix-web/src/handler.rs | 2 +- actix-web/src/response/response.rs | 2 +- 6 files changed, 16 insertions(+), 6 deletions(-) diff --git a/actix-web/src/app.rs b/actix-web/src/app.rs index 119980a03..213c8beff 100644 --- a/actix-web/src/app.rs +++ b/actix-web/src/app.rs @@ -60,7 +60,7 @@ where /// [`HttpRequest::app_data`](crate::HttpRequest::app_data) method at runtime. /// /// # [`Data`] - /// Any [`Data`] type added here can utilize it's extractor implementation in handlers. + /// Any [`Data`] type added here can utilize its extractor implementation in handlers. /// Types not wrapped in `Data` cannot use this extractor. See [its docs](Data) for more /// about its usage and patterns. /// diff --git a/actix-web/src/app_service.rs b/actix-web/src/app_service.rs index 3ef31ac75..28ff8c614 100644 --- a/actix-web/src/app_service.rs +++ b/actix-web/src/app_service.rs @@ -257,7 +257,7 @@ impl ServiceFactory for AppRoutingFactory { type Future = LocalBoxFuture<'static, Result>; fn new_service(&self, _: ()) -> Self::Future { - // construct all services factory future with it's resource def and guards. + // construct all services factory future with its resource def and guards. let factory_fut = join_all(self.services.iter().map(|(path, factory, guards)| { let path = path.clone(); let guards = guards.borrow_mut().take().unwrap_or_default(); diff --git a/actix-web/src/config.rs b/actix-web/src/config.rs index dab309175..58a099c75 100644 --- a/actix-web/src/config.rs +++ b/actix-web/src/config.rs @@ -153,6 +153,16 @@ impl AppConfig { } impl Default for AppConfig { + /// Returns the default AppConfig. + /// Note: The included socket address is "127.0.0.1". + /// + /// 127.0.0.1: non-routable meta address that denotes an unknown, invalid or non-applicable target. + /// If you need a service only accessed by itself, use a loopback address. + /// A loopback address for IPv4 is any loopback address that begins with "127". + /// Loopback addresses should be only used to test your application locally. + /// The default configuration provides a loopback address. + /// + /// 0.0.0.0: if configured to use this special address, the application will listen to any IP address configured on the machine. fn default() -> Self { AppConfig::new( false, diff --git a/actix-web/src/guard.rs b/actix-web/src/guard.rs index 9f7514644..ef1301075 100644 --- a/actix-web/src/guard.rs +++ b/actix-web/src/guard.rs @@ -254,7 +254,7 @@ impl Guard for AllGuard { } } -/// Wraps a guard and inverts the outcome of it's `Guard` implementation. +/// Wraps a guard and inverts the outcome of its `Guard` implementation. /// /// # Examples /// The handler below will be called for any request method apart from `GET`. @@ -459,7 +459,7 @@ impl Guard for HostGuard { return scheme == req_host_uri_scheme; } - // TODO: is the the correct behavior? + // TODO: is this the correct behavior? // falls through if scheme cannot be determined } diff --git a/actix-web/src/handler.rs b/actix-web/src/handler.rs index cf86cb38b..522a48b82 100644 --- a/actix-web/src/handler.rs +++ b/actix-web/src/handler.rs @@ -37,7 +37,7 @@ use crate::{ /// Thanks to Rust's type system, Actix Web can infer the function parameter types. During the /// extraction step, the parameter types are described as a tuple type, [`from_request`] is run on /// that tuple, and the `Handler::call` implementation for that particular function arity -/// destructures the tuple into it's component types and calls your handler function with them. +/// destructures the tuple into its component types and calls your handler function with them. /// /// In pseudo-code the process looks something like this: /// ```ignore diff --git a/actix-web/src/response/response.rs b/actix-web/src/response/response.rs index 630acc3f2..ead8badba 100644 --- a/actix-web/src/response/response.rs +++ b/actix-web/src/response/response.rs @@ -343,7 +343,7 @@ mod response_fut_impl { // Future is only implemented for BoxBody payload type because it's the most useful for making // simple handlers without async blocks. Making it generic over all MessageBody types requires a - // future impl on Response which would cause it's body field to be, undesirably, Option. + // future impl on Response which would cause its body field to be, undesirably, Option. // // This impl is not particularly efficient due to the Response construction and should probably // not be invoked if performance is important. Prefer an async fn/block in such cases. From 3d6ea7fe9b9027205ee88dd64c3d9c932240928e Mon Sep 17 00:00:00 2001 From: nerix Date: Sun, 26 Jun 2022 18:45:02 +0200 Subject: [PATCH 147/170] Improve documentation for `actix-web-actors` (#2788) --- actix-web-actors/Cargo.toml | 3 + actix-web-actors/src/context.rs | 56 +++++++++++++++- actix-web-actors/src/lib.rs | 55 ++++++++++++++++ actix-web-actors/src/ws.rs | 109 ++++++++++++++++++++++++++++---- 4 files changed, 210 insertions(+), 13 deletions(-) diff --git a/actix-web-actors/Cargo.toml b/actix-web-actors/Cargo.toml index c939f6ab5..284351ed3 100644 --- a/actix-web-actors/Cargo.toml +++ b/actix-web-actors/Cargo.toml @@ -29,6 +29,9 @@ tokio = { version = "1.13.1", features = ["sync"] } actix-rt = "2.2" actix-test = "0.1.0-beta.13" awc = { version = "3", default-features = false } +actix-web = { version = "4", features = ["macros"] } + +mime = "0.3" env_logger = "0.9" futures-util = { version = "0.3.7", default-features = false } diff --git a/actix-web-actors/src/context.rs b/actix-web-actors/src/context.rs index d83969ff7..f7b11c780 100644 --- a/actix-web-actors/src/context.rs +++ b/actix-web-actors/src/context.rs @@ -14,6 +14,58 @@ use futures_core::Stream; use tokio::sync::oneshot::Sender; /// Execution context for HTTP actors +/// +/// # Example +/// +/// A demonstration of [server-sent events](https://developer.mozilla.org/docs/Web/API/Server-sent_events) using actors: +/// +/// ```no_run +/// use std::time::Duration; +/// +/// use actix::{Actor, AsyncContext}; +/// use actix_web::{get, http::header, App, HttpResponse, HttpServer}; +/// use actix_web_actors::HttpContext; +/// use bytes::Bytes; +/// +/// struct MyActor { +/// count: usize, +/// } +/// +/// impl Actor for MyActor { +/// type Context = HttpContext; +/// +/// fn started(&mut self, ctx: &mut Self::Context) { +/// ctx.run_later(Duration::from_millis(100), Self::write); +/// } +/// } +/// +/// impl MyActor { +/// fn write(&mut self, ctx: &mut HttpContext) { +/// self.count += 1; +/// if self.count > 3 { +/// ctx.write_eof() +/// } else { +/// ctx.write(Bytes::from(format!("event: count\ndata: {}\n\n", self.count))); +/// ctx.run_later(Duration::from_millis(100), Self::write); +/// } +/// } +/// } +/// +/// #[get("/")] +/// async fn index() -> HttpResponse { +/// HttpResponse::Ok() +/// .insert_header(header::ContentType(mime::TEXT_EVENT_STREAM)) +/// .streaming(HttpContext::create(MyActor { count: 0 })) +/// } +/// +/// #[actix_web::main] +/// async fn main() -> std::io::Result<()> { +/// HttpServer::new(|| App::new().service(index)) +/// .bind(("127.0.0.1", 8080))? +/// .run() +/// .await +/// } +/// ``` pub struct HttpContext where A: Actor>, @@ -210,7 +262,7 @@ mod tests { type Context = HttpContext; fn started(&mut self, ctx: &mut Self::Context) { - ctx.run_later(Duration::from_millis(100), |slf, ctx| slf.write(ctx)); + ctx.run_later(Duration::from_millis(100), Self::write); } } @@ -221,7 +273,7 @@ mod tests { ctx.write_eof() } else { ctx.write(Bytes::from(format!("LINE-{}", self.count))); - ctx.run_later(Duration::from_millis(100), |slf, ctx| slf.write(ctx)); + ctx.run_later(Duration::from_millis(100), Self::write); } } } diff --git a/actix-web-actors/src/lib.rs b/actix-web-actors/src/lib.rs index 70c957020..106bc5202 100644 --- a/actix-web-actors/src/lib.rs +++ b/actix-web-actors/src/lib.rs @@ -1,4 +1,59 @@ //! Actix actors support for Actix Web. +//! +//! # Examples +//! +//! ```no_run +//! use actix::{Actor, StreamHandler}; +//! use actix_web::{get, web, App, Error, HttpRequest, HttpResponse, HttpServer}; +//! use actix_web_actors::ws; +//! +//! /// Define Websocket actor +//! struct MyWs; +//! +//! impl Actor for MyWs { +//! type Context = ws::WebsocketContext; +//! } +//! +//! /// Handler for ws::Message message +//! impl StreamHandler> for MyWs { +//! fn handle(&mut self, msg: Result, ctx: &mut Self::Context) { +//! match msg { +//! Ok(ws::Message::Ping(msg)) => ctx.pong(&msg), +//! Ok(ws::Message::Text(text)) => ctx.text(text), +//! Ok(ws::Message::Binary(bin)) => ctx.binary(bin), +//! _ => (), +//! } +//! } +//! } +//! +//! #[get("/ws")] +//! async fn index(req: HttpRequest, stream: web::Payload) -> Result { +//! ws::start(MyWs, &req, stream) +//! } +//! +//! #[actix_web::main] +//! async fn main() -> std::io::Result<()> { +//! HttpServer::new(|| App::new().service(index)) +//! .bind(("127.0.0.1", 8080))? +//! .run() +//! .await +//! } +//! ``` +//! +//! # Documentation & Community Resources +//! In addition to this API documentation, several other resources are available: +//! +//! * [Website & User Guide](https://actix.rs/) +//! * [Documentation for `actix_web`](actix_web) +//! * [Examples Repository](https://github.com/actix/examples) +//! * [Community Chat on Discord](https://discord.gg/NWpN5mmg3x) +//! +//! To get started navigating the API docs, you may consider looking at the following pages first: +//! +//! * [`ws`]: This module provides actor support for WebSockets. +//! +//! * [`HttpContext`]: This struct provides actor support for streaming HTTP responses. +//! #![deny(rust_2018_idioms, nonstandard_style)] #![warn(future_incompatible)] diff --git a/actix-web-actors/src/ws.rs b/actix-web-actors/src/ws.rs index 6fde10192..9a4880159 100644 --- a/actix-web-actors/src/ws.rs +++ b/actix-web-actors/src/ws.rs @@ -1,4 +1,60 @@ //! Websocket integration. +//! +//! # Examples +//! +//! ```no_run +//! use actix::{Actor, StreamHandler}; +//! use actix_web::{get, web, App, Error, HttpRequest, HttpResponse, HttpServer}; +//! use actix_web_actors::ws; +//! +//! /// Define Websocket actor +//! struct MyWs; +//! +//! impl Actor for MyWs { +//! type Context = ws::WebsocketContext; +//! } +//! +//! /// Handler for ws::Message message +//! impl StreamHandler> for MyWs { +//! fn handle(&mut self, msg: Result, ctx: &mut Self::Context) { +//! match msg { +//! Ok(ws::Message::Ping(msg)) => ctx.pong(&msg), +//! Ok(ws::Message::Text(text)) => ctx.text(text), +//! Ok(ws::Message::Binary(bin)) => ctx.binary(bin), +//! _ => (), +//! } +//! } +//! } +//! +//! #[get("/ws")] +//! async fn websocket(req: HttpRequest, stream: web::Payload) -> Result { +//! ws::start(MyWs, &req, stream) +//! } +//! +//! const MAX_FRAME_SIZE: usize = 16_384; // 16KiB +//! +//! #[get("/custom-ws")] +//! async fn custom_websocket(req: HttpRequest, stream: web::Payload) -> Result { +//! // Create a Websocket session with a specific max frame size, and protocols. +//! ws::WsResponseBuilder::new(MyWs, &req, stream) +//! .frame_size(MAX_FRAME_SIZE) +//! .protocols(&["A", "B"]) +//! .start() +//! } +//! +//! #[actix_web::main] +//! async fn main() -> std::io::Result<()> { +//! HttpServer::new(|| { +//! App::new() +//! .service(websocket) +//! .service(custom_websocket) +//! }) +//! .bind(("127.0.0.1", 8080))? +//! .run() +//! .await +//! } +//! ``` +//! use std::{ collections::VecDeque, @@ -41,20 +97,51 @@ use tokio::sync::oneshot; /// /// # Examples /// -/// Create a Websocket session response with default configuration. -/// ```ignore -/// WsResponseBuilder::new(WsActor, &req, stream).start() -/// ``` +/// ```no_run +/// # use actix::{Actor, StreamHandler}; +/// # use actix_web::{get, web, App, Error, HttpRequest, HttpResponse, HttpServer}; +/// # use actix_web_actors::ws; +/// # +/// # struct MyWs; +/// # +/// # impl Actor for MyWs { +/// # type Context = ws::WebsocketContext; +/// # } +/// # +/// # /// Handler for ws::Message message +/// # impl StreamHandler> for MyWs { +/// # fn handle(&mut self, msg: Result, ctx: &mut Self::Context) {} +/// # } +/// # +/// #[get("/ws")] +/// async fn websocket(req: HttpRequest, stream: web::Payload) -> Result { +/// ws::WsResponseBuilder::new(MyWs, &req, stream).start() +/// } /// -/// Create a Websocket session with a specific max frame size, [`Codec`], and protocols. -/// ```ignore /// const MAX_FRAME_SIZE: usize = 16_384; // 16KiB /// -/// ws::WsResponseBuilder::new(WsActor, &req, stream) -/// .codec(Codec::new()) -/// .protocols(&["A", "B"]) -/// .frame_size(MAX_FRAME_SIZE) -/// .start() +/// #[get("/custom-ws")] +/// async fn custom_websocket(req: HttpRequest, stream: web::Payload) -> Result { +/// // Create a Websocket session with a specific max frame size, codec, and protocols. +/// ws::WsResponseBuilder::new(MyWs, &req, stream) +/// .codec(actix_http::ws::Codec::new()) +/// // This will overwrite the codec's max frame-size +/// .frame_size(MAX_FRAME_SIZE) +/// .protocols(&["A", "B"]) +/// .start() +/// } +/// # +/// # #[actix_web::main] +/// # async fn main() -> std::io::Result<()> { +/// # HttpServer::new(|| { +/// # App::new() +/// # .service(websocket) +/// # .service(custom_websocket) +/// # }) +/// # .bind(("127.0.0.1", 8080))? +/// # .run() +/// # .await +/// # } /// ``` pub struct WsResponseBuilder<'a, A, T> where From f7d7d92984f0ed9204c7e69a8e16d0582a7efdcd Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Mon, 27 Jun 2022 03:12:36 +0100 Subject: [PATCH 148/170] address clippy lints --- actix-http/src/body/message_body.rs | 1 + actix-http/src/responses/head.rs | 4 ++-- actix-web/src/types/path.rs | 1 + actix-web/src/types/payload.rs | 4 ++-- 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/actix-http/src/body/message_body.rs b/actix-http/src/body/message_body.rs index 9090e34d5..ab742b9cd 100644 --- a/actix-http/src/body/message_body.rs +++ b/actix-http/src/body/message_body.rs @@ -481,6 +481,7 @@ mod tests { assert_poll_next_none!(pl); } + #[allow(clippy::let_unit_value)] #[actix_rt::test] async fn test_unit() { let pl = (); diff --git a/actix-http/src/responses/head.rs b/actix-http/src/responses/head.rs index cb47c4b7a..01bca6c5b 100644 --- a/actix-http/src/responses/head.rs +++ b/actix-http/src/responses/head.rs @@ -237,7 +237,7 @@ mod tests { .await; let mut stream = net::TcpStream::connect(srv.addr()).unwrap(); - let _ = stream + stream .write_all(b"GET /camel HTTP/1.1\r\nConnection: Close\r\n\r\n") .unwrap(); let mut data = vec![]; @@ -251,7 +251,7 @@ mod tests { assert!(memmem::find(&data, b"content-length").is_none()); let mut stream = net::TcpStream::connect(srv.addr()).unwrap(); - let _ = stream + stream .write_all(b"GET /lower HTTP/1.1\r\nConnection: Close\r\n\r\n") .unwrap(); let mut data = vec![]; diff --git a/actix-web/src/types/path.rs b/actix-web/src/types/path.rs index 0fcac2c19..34c335ba9 100644 --- a/actix-web/src/types/path.rs +++ b/actix-web/src/types/path.rs @@ -183,6 +183,7 @@ mod tests { assert!(Path::::from_request(&req, &mut pl).await.is_err()); } + #[allow(clippy::let_unit_value)] #[actix_rt::test] async fn test_tuple_extract() { let resource = ResourceDef::new("/{key}/{value}/"); diff --git a/actix-web/src/types/payload.rs b/actix-web/src/types/payload.rs index b47a39e97..af195b867 100644 --- a/actix-web/src/types/payload.rs +++ b/actix-web/src/types/payload.rs @@ -113,7 +113,7 @@ pub struct BytesExtractFut { body_fut: HttpMessageBody, } -impl<'a> Future for BytesExtractFut { +impl Future for BytesExtractFut { type Output = Result; fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { @@ -167,7 +167,7 @@ pub struct StringExtractFut { encoding: &'static Encoding, } -impl<'a> Future for StringExtractFut { +impl Future for StringExtractFut { type Output = Result; fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { From 0dba6310c66c3a0fd5bc02b072fc1ec696814bbf Mon Sep 17 00:00:00 2001 From: Ulf Lilleengen Date: Mon, 27 Jun 2022 04:57:21 +0200 Subject: [PATCH 149/170] Expose option for setting TLS handshake timeout (#2752) Co-authored-by: Rob Ede --- actix-http/src/lib.rs | 3 ++ actix-http/src/service.rs | 65 +++++++++++++++++++++++++++++++- actix-http/tests/test_openssl.rs | 9 +++-- actix-http/tests/test_rustls.rs | 8 +++- actix-web/CHANGES.md | 7 +++- actix-web/src/server.rs | 39 ++++++++++++++++++- 6 files changed, 121 insertions(+), 10 deletions(-) diff --git a/actix-http/src/lib.rs b/actix-http/src/lib.rs index 360cb86fc..184049860 100644 --- a/actix-http/src/lib.rs +++ b/actix-http/src/lib.rs @@ -25,6 +25,7 @@ )] #![doc(html_logo_url = "https://actix.rs/img/logo.png")] #![doc(html_favicon_url = "https://actix.rs/favicon.ico")] +#![cfg_attr(docsrs, feature(doc_cfg))] pub use ::http::{uri, uri::Uri}; pub use ::http::{Method, StatusCode, Version}; @@ -69,6 +70,8 @@ pub use self::payload::{BoxedPayloadStream, Payload, PayloadStream}; pub use self::requests::{Request, RequestHead, RequestHeadType}; pub use self::responses::{Response, ResponseBuilder, ResponseHead}; pub use self::service::HttpService; +#[cfg(any(feature = "openssl", feature = "rustls"))] +pub use self::service::TlsAcceptorConfig; /// A major HTTP protocol version. #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] diff --git a/actix-http/src/service.rs b/actix-http/src/service.rs index f4fe625a3..27029cb8e 100644 --- a/actix-http/src/service.rs +++ b/actix-http/src/service.rs @@ -181,6 +181,25 @@ where } } +/// Configuration options used when accepting TLS connection. +#[cfg(any(feature = "openssl", feature = "rustls"))] +#[cfg_attr(docsrs, doc(cfg(any(feature = "openssl", feature = "rustls"))))] +#[derive(Debug, Default)] +pub struct TlsAcceptorConfig { + pub(crate) handshake_timeout: Option, +} + +#[cfg(any(feature = "openssl", feature = "rustls"))] +impl TlsAcceptorConfig { + /// Set TLS handshake timeout duration. + pub fn handshake_timeout(self, dur: std::time::Duration) -> Self { + Self { + handshake_timeout: Some(dur), + // ..self + } + } +} + #[cfg(feature = "openssl")] mod openssl { use actix_service::ServiceFactoryExt as _; @@ -230,7 +249,28 @@ mod openssl { Error = TlsError, InitError = (), > { - Acceptor::new(acceptor) + self.openssl_with_config(acceptor, TlsAcceptorConfig::default()) + } + + /// Create OpenSSL based service with custom TLS acceptor configuration. + pub fn openssl_with_config( + self, + acceptor: SslAcceptor, + tls_acceptor_config: TlsAcceptorConfig, + ) -> impl ServiceFactory< + TcpStream, + Config = (), + Response = (), + Error = TlsError, + InitError = (), + > { + let mut acceptor = Acceptor::new(acceptor); + + if let Some(handshake_timeout) = tls_acceptor_config.handshake_timeout { + acceptor.set_handshake_timeout(handshake_timeout); + } + + acceptor .map_init_err(|_| { unreachable!("TLS acceptor service factory does not error on init") }) @@ -293,8 +333,23 @@ mod rustls { { /// Create Rustls based service. pub fn rustls( + self, + config: ServerConfig, + ) -> impl ServiceFactory< + TcpStream, + Config = (), + Response = (), + Error = TlsError, + InitError = (), + > { + self.rustls_with_config(config, TlsAcceptorConfig::default()) + } + + /// Create Rustls based service with custom TLS acceptor configuration. + pub fn rustls_with_config( self, mut config: ServerConfig, + tls_acceptor_config: TlsAcceptorConfig, ) -> impl ServiceFactory< TcpStream, Config = (), @@ -306,7 +361,13 @@ mod rustls { protos.extend_from_slice(&config.alpn_protocols); config.alpn_protocols = protos; - Acceptor::new(config) + let mut acceptor = Acceptor::new(config); + + if let Some(handshake_timeout) = tls_acceptor_config.handshake_timeout { + acceptor.set_handshake_timeout(handshake_timeout); + } + + acceptor .map_init_err(|_| { unreachable!("TLS acceptor service factory does not error on init") }) diff --git a/actix-http/tests/test_openssl.rs b/actix-http/tests/test_openssl.rs index 35321ac98..b97b2e45b 100644 --- a/actix-http/tests/test_openssl.rs +++ b/actix-http/tests/test_openssl.rs @@ -2,13 +2,13 @@ extern crate tls_openssl as openssl; -use std::{convert::Infallible, io}; +use std::{convert::Infallible, io, time::Duration}; use actix_http::{ body::{BodyStream, BoxBody, SizedStream}, error::PayloadError, header::{self, HeaderValue}, - Error, HttpService, Method, Request, Response, StatusCode, Version, + Error, HttpService, Method, Request, Response, StatusCode, TlsAcceptorConfig, Version, }; use actix_http_test::test_server; use actix_service::{fn_service, ServiceFactoryExt}; @@ -89,7 +89,10 @@ async fn h2_1() -> io::Result<()> { assert_eq!(req.version(), Version::HTTP_2); ok::<_, Error>(Response::ok()) }) - .openssl(tls_config()) + .openssl_with_config( + tls_config(), + TlsAcceptorConfig::default().handshake_timeout(Duration::from_secs(5)), + ) .map_err(|_| ()) }) .await; diff --git a/actix-http/tests/test_rustls.rs b/actix-http/tests/test_rustls.rs index 550375296..2bbf1524b 100644 --- a/actix-http/tests/test_rustls.rs +++ b/actix-http/tests/test_rustls.rs @@ -8,13 +8,14 @@ use std::{ net::{SocketAddr, TcpStream as StdTcpStream}, sync::Arc, task::Poll, + time::Duration, }; use actix_http::{ body::{BodyStream, BoxBody, SizedStream}, error::PayloadError, header::{self, HeaderName, HeaderValue}, - Error, HttpService, Method, Request, Response, StatusCode, Version, + Error, HttpService, Method, Request, Response, StatusCode, TlsAcceptorConfig, Version, }; use actix_http_test::test_server; use actix_rt::pin; @@ -160,7 +161,10 @@ async fn h2_1() -> io::Result<()> { assert_eq!(req.version(), Version::HTTP_2); ok::<_, Error>(Response::ok()) }) - .rustls(tls_config()) + .rustls_with_config( + tls_config(), + TlsAcceptorConfig::default().handshake_timeout(Duration::from_secs(5)), + ) }) .await; diff --git a/actix-web/CHANGES.md b/actix-web/CHANGES.md index 3fa2f8f21..0144cb912 100644 --- a/actix-web/CHANGES.md +++ b/actix-web/CHANGES.md @@ -1,12 +1,17 @@ # Changelog ## Unreleased - 2022-xx-xx -- Minimum supported Rust version (MSRV) is now 1.57 due to transitive `time` dependency. ### Added - Add `ServiceRequest::{parts, request}()` getter methods. [#2786] +- Add configuration options for TLS handshake timeout via `HttpServer::{rustls, openssl}_with_config` methods. [#2752] +### Changed +- Minimum supported Rust version (MSRV) is now 1.57 due to transitive `time` dependency. + +[#2752]: https://github.com/actix/actix-web/pull/2752 [#2786]: https://github.com/actix/actix-web/pull/2786 + ## 4.1.0 - 2022-06-11 ### Added - Add `ServiceRequest::extract()` to make it easier to use extractors when writing middlewares. [#2647] diff --git a/actix-web/src/server.rs b/actix-web/src/server.rs index 99812600c..169eafab0 100644 --- a/actix-web/src/server.rs +++ b/actix-web/src/server.rs @@ -18,6 +18,9 @@ use actix_tls::accept::openssl::reexports::{AlpnError, SslAcceptor, SslAcceptorB #[cfg(feature = "rustls")] use actix_tls::accept::rustls::reexports::ServerConfig as RustlsServerConfig; +#[cfg(any(feature = "openssl", feature = "rustls"))] +use actix_http::TlsAcceptorConfig; + use crate::{config::AppConfig, Error}; struct Socket { @@ -30,6 +33,8 @@ struct Config { keep_alive: KeepAlive, client_request_timeout: Duration, client_disconnect_timeout: Duration, + #[cfg(any(feature = "openssl", feature = "rustls"))] + tls_handshake_timeout: Option, } /// An HTTP Server. @@ -92,6 +97,8 @@ where keep_alive: KeepAlive::default(), client_request_timeout: Duration::from_secs(5), client_disconnect_timeout: Duration::from_secs(1), + #[cfg(any(feature = "rustls", feature = "openssl"))] + tls_handshake_timeout: None, })), backlog: 1024, sockets: Vec::new(), @@ -225,6 +232,24 @@ where self } + /// Set TLS handshake timeout. + /// + /// Defines a timeout for TLS handshake. If the TLS handshake does not complete + /// within this time, the connection is closed. + /// + /// By default handshake timeout is set to 3000 milliseconds. + #[cfg(any(feature = "openssl", feature = "rustls"))] + #[cfg_attr(docsrs, doc(cfg(any(feature = "openssl", feature = "rustls"))))] + pub fn tls_handshake_timeout(self, dur: Duration) -> Self { + self.config + .lock() + .unwrap() + .tls_handshake_timeout + .replace(dur); + + self + } + #[doc(hidden)] #[deprecated(since = "4.0.0", note = "Renamed to `client_disconnect_timeout`.")] pub fn client_shutdown(self, dur: u64) -> Self { @@ -376,10 +401,15 @@ where .into_factory() .map_err(|err| err.into().error_response()); + let acceptor_config = match c.tls_handshake_timeout { + Some(dur) => TlsAcceptorConfig::default().handshake_timeout(dur), + None => TlsAcceptorConfig::default(), + }; + svc.finish(map_config(fac, move |_| { AppConfig::new(true, host.clone(), addr) })) - .openssl(acceptor.clone()) + .openssl_with_config(acceptor.clone(), acceptor_config) })?; Ok(self) @@ -434,10 +464,15 @@ where .into_factory() .map_err(|err| err.into().error_response()); + let acceptor_config = match c.tls_handshake_timeout { + Some(dur) => TlsAcceptorConfig::default().handshake_timeout(dur), + None => TlsAcceptorConfig::default(), + }; + svc.finish(map_config(fac, move |_| { AppConfig::new(true, host.clone(), addr) })) - .rustls(config.clone()) + .rustls_with_config(config.clone(), acceptor_config) })?; Ok(self) From 06c79458012457b2ef6e106b7fbe283a63683e73 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Thu, 30 Jun 2022 09:19:16 +0100 Subject: [PATCH 150/170] retain previously set vary headers when using compress (#2798) * retain previously set vary headers when using compress --- actix-http/CHANGES.md | 8 +++++--- actix-http/src/encoding/encoder.rs | 2 +- actix-web/src/middleware/compress.rs | 25 +++++++++++++++++++++++++ 3 files changed, 31 insertions(+), 4 deletions(-) diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index dd6051b85..c9ab687f6 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -1,13 +1,15 @@ # Changes ## Unreleased - 2022-xx-xx -### Fixed -- Websocket parser no longer throws endless overflow errors after receiving an oversized frame. [#2790] - ### Changed - Minimum supported Rust version (MSRV) is now 1.57 due to transitive `time` dependency. +### Fixed +- Websocket parser no longer throws endless overflow errors after receiving an oversized frame. [#2790] +- Retain previously set Vary headers when using compression encoder. [#2798] + [#2790]: https://github.com/actix/actix-web/pull/2790 +[#2798]: https://github.com/actix/actix-web/pull/2798 ## 3.1.0 - 2022-06-11 diff --git a/actix-http/src/encoding/encoder.rs b/actix-http/src/encoding/encoder.rs index 0bbb1c106..bbe53e8e8 100644 --- a/actix-http/src/encoding/encoder.rs +++ b/actix-http/src/encoding/encoder.rs @@ -257,7 +257,7 @@ fn update_head(encoding: ContentEncoding, head: &mut ResponseHead) { head.headers_mut() .insert(header::CONTENT_ENCODING, encoding.to_header_value()); head.headers_mut() - .insert(header::VARY, HeaderValue::from_static("accept-encoding")); + .append(header::VARY, HeaderValue::from_static("accept-encoding")); head.no_chunking(false); } diff --git a/actix-web/src/middleware/compress.rs b/actix-web/src/middleware/compress.rs index 4fdd74779..ed4291bed 100644 --- a/actix-web/src/middleware/compress.rs +++ b/actix-web/src/middleware/compress.rs @@ -251,6 +251,8 @@ static SUPPORTED_ENCODINGS: Lazy> = Lazy::new(|| { #[cfg(feature = "compress-gzip")] #[cfg(test)] mod tests { + use std::collections::HashSet; + use super::*; use crate::{middleware::DefaultHeaders, test, web, App}; @@ -305,4 +307,27 @@ mod tests { let bytes = test::read_body(res).await; assert_eq!(gzip_decode(bytes), DATA.as_bytes()); } + + #[actix_rt::test] + async fn retains_previously_set_vary_header() { + let app = test::init_service({ + App::new() + .wrap(Compress::default()) + .default_service(web::to(move || { + HttpResponse::Ok() + .insert_header((header::VARY, "x-test")) + .finish() + })) + }) + .await; + + let req = test::TestRequest::default() + .insert_header((header::ACCEPT_ENCODING, "gzip")) + .to_request(); + let res = test::call_service(&app, req).await; + assert_eq!(res.status(), StatusCode::OK); + let vary_headers = res.headers().get_all(header::VARY).collect::>(); + assert!(vary_headers.contains(&HeaderValue::from_static("x-test"))); + assert!(vary_headers.contains(&HeaderValue::from_static("accept-encoding"))); + } } From c6eba2da9b6ead6112433f7d2aaa1f2d19a22395 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Fri, 1 Jul 2022 06:16:17 +0100 Subject: [PATCH 151/170] prepare actix-http release 3.2.0 (#2801) --- actix-http/CHANGES.md | 3 +++ actix-http/Cargo.toml | 2 +- actix-http/README.md | 4 ++-- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index c9ab687f6..024a731d8 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -1,6 +1,9 @@ # Changes ## Unreleased - 2022-xx-xx + + +## 3.2.0 - 2022-06-30 ### Changed - Minimum supported Rust version (MSRV) is now 1.57 due to transitive `time` dependency. diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 2a4966884..3fccbfa03 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-http" -version = "3.1.0" +version = "3.2.0" authors = [ "Nikolay Kim ", "Rob Ede ", diff --git a/actix-http/README.md b/actix-http/README.md index 211f433e8..3179258af 100644 --- a/actix-http/README.md +++ b/actix-http/README.md @@ -3,11 +3,11 @@ > HTTP primitives for the Actix ecosystem. [![crates.io](https://img.shields.io/crates/v/actix-http?label=latest)](https://crates.io/crates/actix-http) -[![Documentation](https://docs.rs/actix-http/badge.svg?version=3.1.0)](https://docs.rs/actix-http/3.1.0) +[![Documentation](https://docs.rs/actix-http/badge.svg?version=3.2.0)](https://docs.rs/actix-http/3.2.0) ![Version](https://img.shields.io/badge/rustc-1.57+-ab6000.svg) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-http.svg)
-[![dependency status](https://deps.rs/crate/actix-http/3.1.0/status.svg)](https://deps.rs/crate/actix-http/3.1.0) +[![dependency status](https://deps.rs/crate/actix-http/3.2.0/status.svg)](https://deps.rs/crate/actix-http/3.2.0) [![Download](https://img.shields.io/crates/d/actix-http.svg)](https://crates.io/crates/actix-http) [![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) From 8f9a12ed5d30c1f0a44cbcb5dfb0c3fecd1f6348 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Fri, 1 Jul 2022 08:23:40 +0100 Subject: [PATCH 152/170] fix parsing ambiguities for HTTP/1.0 requests (#2794) * fix HRS vuln when first CL header is 0 * ignore TE headers in http/1.0 reqs * update changelog * disallow HTTP/1.0 requests without a CL header * fix test * broken fix for http1.0 post requests --- actix-http/CHANGES.md | 4 + actix-http/src/h1/decoder.rs | 156 +++++++++++++++++++++++++++++++---- 2 files changed, 142 insertions(+), 18 deletions(-) diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 024a731d8..5d441919d 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -1,6 +1,10 @@ # Changes ## Unreleased - 2022-xx-xx +### Fixed +- Fix parsing ambiguity in Transfer-Encoding and Content-Length headers for HTTP/1.0 requests. [#2794] + +[#2794]: https://github.com/actix/actix-web/pull/2794 ## 3.2.0 - 2022-06-30 diff --git a/actix-http/src/h1/decoder.rs b/actix-http/src/h1/decoder.rs index a9443997e..91a93d22c 100644 --- a/actix-http/src/h1/decoder.rs +++ b/actix-http/src/h1/decoder.rs @@ -46,6 +46,23 @@ pub(crate) enum PayloadLength { None, } +impl PayloadLength { + /// Returns true if variant is `None`. + fn is_none(&self) -> bool { + matches!(self, Self::None) + } + + /// Returns true if variant is represents zero-length (not none) payload. + fn is_zero(&self) -> bool { + matches!( + self, + PayloadLength::Payload(PayloadType::Payload(PayloadDecoder { + kind: Kind::Length(0) + })) + ) + } +} + pub(crate) trait MessageType: Sized { fn set_connection_type(&mut self, conn_type: Option); @@ -59,6 +76,7 @@ pub(crate) trait MessageType: Sized { &mut self, slice: &Bytes, raw_headers: &[HeaderIndex], + version: Version, ) -> Result { let mut ka = None; let mut has_upgrade_websocket = false; @@ -87,21 +105,23 @@ pub(crate) trait MessageType: Sized { return Err(ParseError::Header); } - header::CONTENT_LENGTH => match value.to_str() { - Ok(s) if s.trim().starts_with('+') => { - debug!("illegal Content-Length: {:?}", s); + header::CONTENT_LENGTH => match value.to_str().map(str::trim) { + Ok(val) if val.starts_with('+') => { + debug!("illegal Content-Length: {:?}", val); return Err(ParseError::Header); } - Ok(s) => { - if let Ok(len) = s.parse::() { - if len != 0 { - content_length = Some(len); - } + + Ok(val) => { + if let Ok(len) = val.parse::() { + // accept 0 lengths here and remove them in `decode` after all + // headers have been processed to prevent request smuggling issues + content_length = Some(len); } else { - debug!("illegal Content-Length: {:?}", s); + debug!("illegal Content-Length: {:?}", val); return Err(ParseError::Header); } } + Err(_) => { debug!("illegal Content-Length: {:?}", value); return Err(ParseError::Header); @@ -114,22 +134,23 @@ pub(crate) trait MessageType: Sized { return Err(ParseError::Header); } - header::TRANSFER_ENCODING => { + header::TRANSFER_ENCODING if version == Version::HTTP_11 => { seen_te = true; - if let Ok(s) = value.to_str().map(str::trim) { - if s.eq_ignore_ascii_case("chunked") { + if let Ok(val) = value.to_str().map(str::trim) { + if val.eq_ignore_ascii_case("chunked") { chunked = true; - } else if s.eq_ignore_ascii_case("identity") { + } else if val.eq_ignore_ascii_case("identity") { // allow silently since multiple TE headers are already checked } else { - debug!("illegal Transfer-Encoding: {:?}", s); + debug!("illegal Transfer-Encoding: {:?}", val); return Err(ParseError::Header); } } else { return Err(ParseError::Header); } } + // connection keep-alive state header::CONNECTION => { ka = if let Ok(conn) = value.to_str().map(str::trim) { @@ -146,6 +167,7 @@ pub(crate) trait MessageType: Sized { None }; } + header::UPGRADE => { if let Ok(val) = value.to_str().map(str::trim) { if val.eq_ignore_ascii_case("websocket") { @@ -153,19 +175,23 @@ pub(crate) trait MessageType: Sized { } } } + header::EXPECT => { let bytes = value.as_bytes(); if bytes.len() >= 4 && &bytes[0..4] == b"100-" { expect = true; } } + _ => {} } headers.append(name, value); } } + self.set_connection_type(ka); + if expect { self.set_expect() } @@ -249,7 +275,22 @@ impl MessageType for Request { let mut msg = Request::new(); // convert headers - let length = msg.set_headers(&src.split_to(len).freeze(), &headers[..h_len])?; + let mut length = + msg.set_headers(&src.split_to(len).freeze(), &headers[..h_len], ver)?; + + // disallow HTTP/1.0 POST requests that do not contain a Content-Length headers + // see https://datatracker.ietf.org/doc/html/rfc1945#section-7.2.2 + if ver == Version::HTTP_10 && method == Method::POST && length.is_none() { + debug!("no Content-Length specified for HTTP/1.0 POST request"); + return Err(ParseError::Header); + } + + // Remove CL value if 0 now that all headers and HTTP/1.0 special cases are processed. + // Protects against some request smuggling attacks. + // See https://github.com/actix/actix-web/issues/2767. + if length.is_zero() { + length = PayloadLength::None; + } // payload decoder let decoder = match length { @@ -337,7 +378,15 @@ impl MessageType for ResponseHead { msg.version = ver; // convert headers - let length = msg.set_headers(&src.split_to(len).freeze(), &headers[..h_len])?; + let mut length = + msg.set_headers(&src.split_to(len).freeze(), &headers[..h_len], ver)?; + + // Remove CL value if 0 now that all headers and HTTP/1.0 special cases are processed. + // Protects against some request smuggling attacks. + // See https://github.com/actix/actix-web/issues/2767. + if length.is_zero() { + length = PayloadLength::None; + } // message payload let decoder = if let PayloadLength::Payload(pl) = length { @@ -606,14 +655,40 @@ mod tests { } #[test] - fn test_parse_post() { - let mut buf = BytesMut::from("POST /test2 HTTP/1.0\r\n\r\n"); + fn parse_h10_post() { + let mut buf = BytesMut::from( + "POST /test1 HTTP/1.0\r\n\ + Content-Length: 3\r\n\ + \r\n\ + abc", + ); + + let mut reader = MessageDecoder::::default(); + let (req, _) = reader.decode(&mut buf).unwrap().unwrap(); + assert_eq!(req.version(), Version::HTTP_10); + assert_eq!(*req.method(), Method::POST); + assert_eq!(req.path(), "/test1"); + + let mut buf = BytesMut::from( + "POST /test2 HTTP/1.0\r\n\ + Content-Length: 0\r\n\ + \r\n", + ); let mut reader = MessageDecoder::::default(); let (req, _) = reader.decode(&mut buf).unwrap().unwrap(); assert_eq!(req.version(), Version::HTTP_10); assert_eq!(*req.method(), Method::POST); assert_eq!(req.path(), "/test2"); + + let mut buf = BytesMut::from( + "POST /test3 HTTP/1.0\r\n\ + \r\n", + ); + + let mut reader = MessageDecoder::::default(); + let err = reader.decode(&mut buf).unwrap_err(); + assert!(err.to_string().contains("Header")) } #[test] @@ -981,6 +1056,17 @@ mod tests { ); expect_parse_err!(&mut buf); + + let mut buf = BytesMut::from( + "GET / HTTP/1.1\r\n\ + Host: example.com\r\n\ + Content-Length: 0\r\n\ + Content-Length: 2\r\n\ + \r\n\ + ab", + ); + + expect_parse_err!(&mut buf); } #[test] @@ -996,6 +1082,40 @@ mod tests { expect_parse_err!(&mut buf); } + #[test] + fn hrs_te_http10() { + // in HTTP/1.0 transfer encoding is ignored and must therefore contain a CL header + + let mut buf = BytesMut::from( + "POST / HTTP/1.0\r\n\ + Host: example.com\r\n\ + Transfer-Encoding: chunked\r\n\ + \r\n\ + 3\r\n\ + aaa\r\n\ + 0\r\n\ + ", + ); + + expect_parse_err!(&mut buf); + } + + #[test] + fn hrs_cl_and_te_http10() { + // in HTTP/1.0 transfer encoding is simply ignored so it's fine to have both + + let mut buf = BytesMut::from( + "GET / HTTP/1.0\r\n\ + Host: example.com\r\n\ + Content-Length: 3\r\n\ + Transfer-Encoding: chunked\r\n\ + \r\n\ + 000", + ); + + parse_ready!(&mut buf); + } + #[test] fn hrs_unknown_transfer_encoding() { let mut buf = BytesMut::from( From 7e990e423fdf4b6b4b8cf24a7bc412351d8e995b Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Fri, 1 Jul 2022 08:24:45 +0100 Subject: [PATCH 153/170] add http/1.0 GET parsing tests --- actix-http/src/h1/decoder.rs | 40 ++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/actix-http/src/h1/decoder.rs b/actix-http/src/h1/decoder.rs index 91a93d22c..c718f26a8 100644 --- a/actix-http/src/h1/decoder.rs +++ b/actix-http/src/h1/decoder.rs @@ -654,6 +654,46 @@ mod tests { assert_eq!(req.path(), "/test"); } + #[test] + fn parse_h10_get() { + let mut buf = BytesMut::from( + "GET /test1 HTTP/1.0\r\n\ + \r\n\ + abc", + ); + + let mut reader = MessageDecoder::::default(); + let (req, _) = reader.decode(&mut buf).unwrap().unwrap(); + assert_eq!(req.version(), Version::HTTP_10); + assert_eq!(*req.method(), Method::GET); + assert_eq!(req.path(), "/test1"); + + let mut buf = BytesMut::from( + "GET /test2 HTTP/1.0\r\n\ + Content-Length: 0\r\n\ + \r\n", + ); + + let mut reader = MessageDecoder::::default(); + let (req, _) = reader.decode(&mut buf).unwrap().unwrap(); + assert_eq!(req.version(), Version::HTTP_10); + assert_eq!(*req.method(), Method::GET); + assert_eq!(req.path(), "/test2"); + + let mut buf = BytesMut::from( + "GET /test3 HTTP/1.0\r\n\ + Content-Length: 3\r\n\ + \r\n + abc", + ); + + let mut reader = MessageDecoder::::default(); + let (req, _) = reader.decode(&mut buf).unwrap().unwrap(); + assert_eq!(req.version(), Version::HTTP_10); + assert_eq!(*req.method(), Method::GET); + assert_eq!(req.path(), "/test3"); + } + #[test] fn parse_h10_post() { let mut buf = BytesMut::from( From e524fc86eaef37c36b40c25c20b9880893c76508 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Fri, 1 Jul 2022 09:03:57 +0100 Subject: [PATCH 154/170] add HTTP/0.9 rejection test --- actix-http/src/h1/decoder.rs | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/actix-http/src/h1/decoder.rs b/actix-http/src/h1/decoder.rs index c718f26a8..edfc00fd6 100644 --- a/actix-http/src/h1/decoder.rs +++ b/actix-http/src/h1/decoder.rs @@ -654,12 +654,32 @@ mod tests { assert_eq!(req.path(), "/test"); } + #[test] + fn parse_h09_reject() { + let mut buf = BytesMut::from( + "GET /test1 HTTP/0.9\r\n\ + \r\n", + ); + + let mut reader = MessageDecoder::::default(); + reader.decode(&mut buf).unwrap_err(); + + let mut buf = BytesMut::from( + "POST /test2 HTTP/0.9\r\n\ + Content-Length: 3\r\n\ + \r\n + abc", + ); + + let mut reader = MessageDecoder::::default(); + reader.decode(&mut buf).unwrap_err(); + } + #[test] fn parse_h10_get() { let mut buf = BytesMut::from( "GET /test1 HTTP/1.0\r\n\ - \r\n\ - abc", + \r\n", ); let mut reader = MessageDecoder::::default(); From 226ea696ce43283d259cdea316fcc9efca6e25b9 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Fri, 1 Jul 2022 10:19:28 +0100 Subject: [PATCH 155/170] update dev deps --- actix-http/Cargo.toml | 4 ++-- actix-web/Cargo.toml | 6 +++--- awc/Cargo.toml | 6 +++--- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 3fccbfa03..abb1e4603 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -108,10 +108,10 @@ env_logger = "0.9" futures-util = { version = "0.3.7", default-features = false, features = ["alloc"] } memchr = "2.4" once_cell = "1.9" -rcgen = "0.8" +rcgen = "0.9" regex = "1.3" rustversion = "1" -rustls-pemfile = "0.2" +rustls-pemfile = "1" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" static_assertions = "1" diff --git a/actix-web/Cargo.toml b/actix-web/Cargo.toml index 8cdf0f611..c58e1604b 100644 --- a/actix-web/Cargo.toml +++ b/actix-web/Cargo.toml @@ -105,14 +105,14 @@ actix-test = { version = "0.1.0-beta.13", features = ["openssl", "rustls"] } awc = { version = "3", features = ["openssl"] } brotli = "3.3.3" -const-str = "0.3" +const-str = "0.4" criterion = { version = "0.3", features = ["html_reports"] } env_logger = "0.9" flate2 = "1.0.13" futures-util = { version = "0.3.7", default-features = false, features = ["std"] } rand = "0.8" -rcgen = "0.8" -rustls-pemfile = "0.2" +rcgen = "0.9" +rustls-pemfile = "1" serde = { version = "1.0", features = ["derive"] } static_assertions = "1" tls-openssl = { package = "openssl", version = "0.10.9" } diff --git a/awc/Cargo.toml b/awc/Cargo.toml index ba0fc14e3..1a69fd49e 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -102,13 +102,13 @@ actix-utils = "3" actix-web = { version = "4", features = ["openssl"] } brotli = "3.3.3" -const-str = "0.3" +const-str = "0.4" env_logger = "0.9" flate2 = "1.0.13" futures-util = { version = "0.3.7", default-features = false } static_assertions = "1.1" -rcgen = "0.8" -rustls-pemfile = "0.2" +rcgen = "0.9" +rustls-pemfile = "1" tokio = { version = "1.13.1", features = ["rt-multi-thread", "macros"] } zstd = "0.11" From df5257c3730ccf270dbe0799501826fc2d4a1072 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Fri, 1 Jul 2022 10:21:46 +0100 Subject: [PATCH 156/170] update trust dns resolver --- awc/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/awc/Cargo.toml b/awc/Cargo.toml index 1a69fd49e..9da103cb0 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -90,7 +90,7 @@ cookie = { version = "0.16", features = ["percent-encode"], optional = true } tls-openssl = { package = "openssl", version = "0.10.9", optional = true } tls-rustls = { package = "rustls", version = "0.20.0", optional = true, features = ["dangerous_configuration"] } -trust-dns-resolver = { version = "0.20.0", optional = true } +trust-dns-resolver = { version = "0.21", optional = true } [dev-dependencies] actix-http = { version = "3", features = ["openssl"] } From b62f1b4ef74fc28881bdf070e35395065189bd26 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Fri, 1 Jul 2022 12:40:00 +0100 Subject: [PATCH 157/170] migrate deprecated method in docs --- actix-router/src/resource.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actix-router/src/resource.rs b/actix-router/src/resource.rs index bc082273c..3c6754aeb 100644 --- a/actix-router/src/resource.rs +++ b/actix-router/src/resource.rs @@ -649,7 +649,7 @@ impl ResourceDef { /// resource.capture_match_info_fn( /// path, /// // when env var is not set, reject when path contains "admin" - /// |res| !(!admin_allowed && res.path().contains("admin")), + /// |path| !(!admin_allowed && path.as_str().contains("admin")), /// ) /// } /// From 987067698b11db82f113ae350356cb11d1d7d1f0 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Fri, 1 Jul 2022 12:45:26 +0100 Subject: [PATCH 158/170] use sparse registry in CI --- .github/workflows/ci-post-merge.yml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/.github/workflows/ci-post-merge.yml b/.github/workflows/ci-post-merge.yml index 9fce98f4c..d8752d8aa 100644 --- a/.github/workflows/ci-post-merge.yml +++ b/.github/workflows/ci-post-merge.yml @@ -23,6 +23,7 @@ jobs: CI: 1 CARGO_INCREMENTAL: 0 VCPKGRS_DYNAMIC: 1 + CARGO_UNSTABLE_SPARSE_REGISTRY: true steps: - uses: actions/checkout@v2 @@ -86,6 +87,11 @@ jobs: ci_feature_powerset_check: name: Verify Feature Combinations runs-on: ubuntu-latest + + env: + CI: 1 + CARGO_INCREMENTAL: 0 + steps: - uses: actions/checkout@v2 @@ -119,6 +125,11 @@ jobs: nextest: name: nextest runs-on: ubuntu-latest + + env: + CI: 1 + CARGO_INCREMENTAL: 0 + steps: - uses: actions/checkout@v2 From f3f41a0cc70e43564f8243b3ff425195566b5f16 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sat, 2 Jul 2022 16:50:54 +0100 Subject: [PATCH 159/170] prepare actix-http release 3.2.1 --- actix-http/CHANGES.md | 3 +++ actix-http/Cargo.toml | 2 +- actix-http/README.md | 4 ++-- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 5d441919d..7e6604046 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -1,6 +1,9 @@ # Changes ## Unreleased - 2022-xx-xx + + +## 3.2.1 - 2022-07-02 ### Fixed - Fix parsing ambiguity in Transfer-Encoding and Content-Length headers for HTTP/1.0 requests. [#2794] diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index abb1e4603..03767ca4e 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-http" -version = "3.2.0" +version = "3.2.1" authors = [ "Nikolay Kim ", "Rob Ede ", diff --git a/actix-http/README.md b/actix-http/README.md index 3179258af..787d2f653 100644 --- a/actix-http/README.md +++ b/actix-http/README.md @@ -3,11 +3,11 @@ > HTTP primitives for the Actix ecosystem. [![crates.io](https://img.shields.io/crates/v/actix-http?label=latest)](https://crates.io/crates/actix-http) -[![Documentation](https://docs.rs/actix-http/badge.svg?version=3.2.0)](https://docs.rs/actix-http/3.2.0) +[![Documentation](https://docs.rs/actix-http/badge.svg?version=3.2.1)](https://docs.rs/actix-http/3.2.1) ![Version](https://img.shields.io/badge/rustc-1.57+-ab6000.svg) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-http.svg)
-[![dependency status](https://deps.rs/crate/actix-http/3.2.0/status.svg)](https://deps.rs/crate/actix-http/3.2.0) +[![dependency status](https://deps.rs/crate/actix-http/3.2.1/status.svg)](https://deps.rs/crate/actix-http/3.2.1) [![Download](https://img.shields.io/crates/d/actix-http.svg)](https://crates.io/crates/actix-http) [![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) From 2f79daec1608f079802b86a0ff452f380a62a67e Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sat, 2 Jul 2022 17:05:48 +0100 Subject: [PATCH 160/170] only run tests on stable --- .github/workflows/ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 49ad25ccf..15d98aae7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -68,6 +68,7 @@ jobs: with: { command: ci-check-default } - name: tests + if: matrix.version == 'stable' # temp? timeout-minutes: 60 run: | cargo test --lib --tests -p=actix-router --all-features From e0845d9ad9540818e2a6eec170fa2028d31bf607 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sat, 2 Jul 2022 17:12:24 +0100 Subject: [PATCH 161/170] add msrv workarounds to ci --- .github/workflows/ci.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 15d98aae7..10ba660f3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -47,6 +47,12 @@ jobs: profile: minimal override: true + - name: workaround MSRV issues + if: matrix.version != 'stable' + run: | + cargo add const-str@0.3 --dev -p=actix-web + cargo add const-str@0.3 --dev -p=awc + - name: Generate Cargo.lock uses: actions-rs/cargo@v1 with: { command: generate-lockfile } @@ -68,7 +74,6 @@ jobs: with: { command: ci-check-default } - name: tests - if: matrix.version == 'stable' # temp? timeout-minutes: 60 run: | cargo test --lib --tests -p=actix-router --all-features From f7d629a61ae04cc5495da4b4d5daabd254396215 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sat, 2 Jul 2022 17:20:46 +0100 Subject: [PATCH 162/170] fix cargo-add in CI --- .github/workflows/ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 10ba660f3..35a829ee7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -50,6 +50,7 @@ jobs: - name: workaround MSRV issues if: matrix.version != 'stable' run: | + cargo install cargo-add cargo add const-str@0.3 --dev -p=actix-web cargo add const-str@0.3 --dev -p=awc From 23ef51609e1e6797ae1905620421f6ebf758a0d6 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sat, 2 Jul 2022 17:29:06 +0100 Subject: [PATCH 163/170] s/cargo-add/cargo-edit --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 35a829ee7..f6fafc67c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -50,7 +50,7 @@ jobs: - name: workaround MSRV issues if: matrix.version != 'stable' run: | - cargo install cargo-add + cargo install cargo-edit cargo add const-str@0.3 --dev -p=actix-web cargo add const-str@0.3 --dev -p=awc From 9a2f8450e0bfa8c15db6fe8447a7b25606eb2c8e Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sat, 2 Jul 2022 17:40:03 +0100 Subject: [PATCH 164/170] install older cargo-edit --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f6fafc67c..cb103e21a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -50,7 +50,7 @@ jobs: - name: workaround MSRV issues if: matrix.version != 'stable' run: | - cargo install cargo-edit + cargo install cargo-edit --version=0.8.0 cargo add const-str@0.3 --dev -p=actix-web cargo add const-str@0.3 --dev -p=awc From 8e2ae8cd40763cbec6bf78a6c9ae35ae0deb01cc Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sat, 2 Jul 2022 18:38:08 +0100 Subject: [PATCH 165/170] install nextest faster --- .github/workflows/ci-post-merge.yml | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci-post-merge.yml b/.github/workflows/ci-post-merge.yml index d8752d8aa..008ba3296 100644 --- a/.github/workflows/ci-post-merge.yml +++ b/.github/workflows/ci-post-merge.yml @@ -146,11 +146,8 @@ jobs: - name: Cache Dependencies uses: Swatinem/rust-cache@v1.3.0 - - name: Install cargo-nextest - uses: actions-rs/cargo@v1 - with: - command: install - args: cargo-nextest + - name: Install nextest + uses: taiki-e/install-action@nextest - name: Test with cargo-nextest uses: actions-rs/cargo@v1 From 9b51624b2707cb33abad329bd8c5f7ed9ca6ef20 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sat, 2 Jul 2022 18:43:19 +0100 Subject: [PATCH 166/170] update cargo-cache to 0.8.2 --- .github/workflows/ci-post-merge.yml | 2 +- .github/workflows/ci.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci-post-merge.yml b/.github/workflows/ci-post-merge.yml index 008ba3296..430c3b1d8 100644 --- a/.github/workflows/ci-post-merge.yml +++ b/.github/workflows/ci-post-merge.yml @@ -81,7 +81,7 @@ jobs: - name: Clear the cargo caches run: | - cargo install cargo-cache --version 0.6.3 --no-default-features --features ci-autoclean + cargo install cargo-cache --version 0.8.2 --no-default-features --features ci-autoclean cargo-cache ci_feature_powerset_check: diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cb103e21a..c956c2f0e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -90,7 +90,7 @@ jobs: - name: Clear the cargo caches run: | - cargo install cargo-cache --version 0.6.3 --no-default-features --features ci-autoclean + cargo install cargo-cache --version 0.8.2 --no-default-features --features ci-autoclean cargo-cache io-uring: From 75517cce822f9a78eea507229743fbab338eb740 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sat, 2 Jul 2022 20:00:59 +0100 Subject: [PATCH 167/170] install cargo hack in CI faster --- .github/workflows/ci-post-merge.yml | 24 +++++++++--------------- .github/workflows/ci.yml | 9 +++------ 2 files changed, 12 insertions(+), 21 deletions(-) diff --git a/.github/workflows/ci-post-merge.yml b/.github/workflows/ci-post-merge.yml index 430c3b1d8..1ee97b591 100644 --- a/.github/workflows/ci-post-merge.yml +++ b/.github/workflows/ci-post-merge.yml @@ -45,18 +45,15 @@ jobs: profile: minimal override: true + - name: Install cargo-hack + uses: taiki-e/install-action@cargo-hack + - name: Generate Cargo.lock uses: actions-rs/cargo@v1 with: { command: generate-lockfile } - name: Cache Dependencies uses: Swatinem/rust-cache@v1.2.0 - - name: Install cargo-hack - uses: actions-rs/cargo@v1 - with: - command: install - args: cargo-hack - - name: check minimal uses: actions-rs/cargo@v1 with: { command: ci-check-min } @@ -102,18 +99,15 @@ jobs: profile: minimal override: true + - name: Install cargo-hack + uses: taiki-e/install-action@cargo-hack + - name: Generate Cargo.lock uses: actions-rs/cargo@v1 with: { command: generate-lockfile } - name: Cache Dependencies uses: Swatinem/rust-cache@v1.2.0 - - name: Install cargo-hack - uses: actions-rs/cargo@v1 - with: - command: install - args: cargo-hack - - name: check feature combinations uses: actions-rs/cargo@v1 with: { command: ci-check-all-feature-powerset } @@ -140,15 +134,15 @@ jobs: profile: minimal override: true + - name: Install nextest + uses: taiki-e/install-action@nextest + - name: Generate Cargo.lock uses: actions-rs/cargo@v1 with: { command: generate-lockfile } - name: Cache Dependencies uses: Swatinem/rust-cache@v1.3.0 - - name: Install nextest - uses: taiki-e/install-action@nextest - - name: Test with cargo-nextest uses: actions-rs/cargo@v1 with: diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c956c2f0e..2ea920808 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -47,6 +47,9 @@ jobs: profile: minimal override: true + - name: Install cargo-hack + uses: taiki-e/install-action@cargo-hack + - name: workaround MSRV issues if: matrix.version != 'stable' run: | @@ -60,12 +63,6 @@ jobs: - name: Cache Dependencies uses: Swatinem/rust-cache@v1.2.0 - - name: Install cargo-hack - uses: actions-rs/cargo@v1 - with: - command: install - args: cargo-hack - - name: check minimal uses: actions-rs/cargo@v1 with: { command: ci-check-min } From 40eab1f091c03ac00c0f1333b2a7a8e336f9f7e8 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sat, 2 Jul 2022 20:07:27 +0100 Subject: [PATCH 168/170] simplify simple decoder tests --- actix-http/src/h1/decoder.rs | 151 +++++++++++++---------------------- 1 file changed, 54 insertions(+), 97 deletions(-) diff --git a/actix-http/src/h1/decoder.rs b/actix-http/src/h1/decoder.rs index edfc00fd6..40cabf9cb 100644 --- a/actix-http/src/h1/decoder.rs +++ b/actix-http/src/h1/decoder.rs @@ -844,121 +844,98 @@ mod tests { #[test] fn test_conn_default_1_0() { - let mut buf = BytesMut::from("GET /test HTTP/1.0\r\n\r\n"); - let req = parse_ready!(&mut buf); - + let req = parse_ready!(&mut BytesMut::from("GET /test HTTP/1.0\r\n\r\n")); assert_eq!(req.head().connection_type(), ConnectionType::Close); } #[test] fn test_conn_default_1_1() { - let mut buf = BytesMut::from("GET /test HTTP/1.1\r\n\r\n"); - let req = parse_ready!(&mut buf); - + let req = parse_ready!(&mut BytesMut::from("GET /test HTTP/1.1\r\n\r\n")); assert_eq!(req.head().connection_type(), ConnectionType::KeepAlive); } #[test] fn test_conn_close() { - let mut buf = BytesMut::from( + let req = parse_ready!(&mut BytesMut::from( "GET /test HTTP/1.1\r\n\ connection: close\r\n\r\n", - ); - let req = parse_ready!(&mut buf); - + )); assert_eq!(req.head().connection_type(), ConnectionType::Close); - let mut buf = BytesMut::from( + let req = parse_ready!(&mut BytesMut::from( "GET /test HTTP/1.1\r\n\ connection: Close\r\n\r\n", - ); - let req = parse_ready!(&mut buf); - + )); assert_eq!(req.head().connection_type(), ConnectionType::Close); } #[test] fn test_conn_close_1_0() { - let mut buf = BytesMut::from( + let req = parse_ready!(&mut BytesMut::from( "GET /test HTTP/1.0\r\n\ connection: close\r\n\r\n", - ); - - let req = parse_ready!(&mut buf); - + )); assert_eq!(req.head().connection_type(), ConnectionType::Close); } #[test] fn test_conn_keep_alive_1_0() { - let mut buf = BytesMut::from( + let req = parse_ready!(&mut BytesMut::from( "GET /test HTTP/1.0\r\n\ connection: keep-alive\r\n\r\n", - ); - let req = parse_ready!(&mut buf); - + )); assert_eq!(req.head().connection_type(), ConnectionType::KeepAlive); - let mut buf = BytesMut::from( + let req = parse_ready!(&mut BytesMut::from( "GET /test HTTP/1.0\r\n\ connection: Keep-Alive\r\n\r\n", - ); - let req = parse_ready!(&mut buf); - + )); assert_eq!(req.head().connection_type(), ConnectionType::KeepAlive); } #[test] fn test_conn_keep_alive_1_1() { - let mut buf = BytesMut::from( + let req = parse_ready!(&mut BytesMut::from( "GET /test HTTP/1.1\r\n\ connection: keep-alive\r\n\r\n", - ); - let req = parse_ready!(&mut buf); - + )); assert_eq!(req.head().connection_type(), ConnectionType::KeepAlive); } #[test] fn test_conn_other_1_0() { - let mut buf = BytesMut::from( + let req = parse_ready!(&mut BytesMut::from( "GET /test HTTP/1.0\r\n\ connection: other\r\n\r\n", - ); - let req = parse_ready!(&mut buf); - + )); assert_eq!(req.head().connection_type(), ConnectionType::Close); } #[test] fn test_conn_other_1_1() { - let mut buf = BytesMut::from( + let req = parse_ready!(&mut BytesMut::from( "GET /test HTTP/1.1\r\n\ connection: other\r\n\r\n", - ); - let req = parse_ready!(&mut buf); - + )); assert_eq!(req.head().connection_type(), ConnectionType::KeepAlive); } #[test] fn test_conn_upgrade() { - let mut buf = BytesMut::from( + let req = parse_ready!(&mut BytesMut::from( "GET /test HTTP/1.1\r\n\ upgrade: websockets\r\n\ connection: upgrade\r\n\r\n", - ); - let req = parse_ready!(&mut buf); + )); assert!(req.upgrade()); assert_eq!(req.head().connection_type(), ConnectionType::Upgrade); - let mut buf = BytesMut::from( + let req = parse_ready!(&mut BytesMut::from( "GET /test HTTP/1.1\r\n\ upgrade: Websockets\r\n\ connection: Upgrade\r\n\r\n", - ); - let req = parse_ready!(&mut buf); + )); assert!(req.upgrade()); assert_eq!(req.head().connection_type(), ConnectionType::Upgrade); @@ -966,59 +943,54 @@ mod tests { #[test] fn test_conn_upgrade_connect_method() { - let mut buf = BytesMut::from( + let req = parse_ready!(&mut BytesMut::from( "CONNECT /test HTTP/1.1\r\n\ content-type: text/plain\r\n\r\n", - ); - let req = parse_ready!(&mut buf); + )); assert!(req.upgrade()); } #[test] - fn test_headers_content_length_err_1() { - let mut buf = BytesMut::from( + fn test_headers_bad_content_length() { + // string CL + expect_parse_err!(&mut BytesMut::from( "GET /test HTTP/1.1\r\n\ content-length: line\r\n\r\n", - ); + )); - expect_parse_err!(&mut buf) - } - - #[test] - fn test_headers_content_length_err_2() { - let mut buf = BytesMut::from( + // negative CL + expect_parse_err!(&mut BytesMut::from( "GET /test HTTP/1.1\r\n\ content-length: -1\r\n\r\n", - ); + )); - expect_parse_err!(&mut buf); + // octal CL + // expect_parse_err!(&mut BytesMut::from( + // "GET /test HTTP/1.1\r\n\ + // content-length: 0123\r\n\r\n", + // )); } #[test] fn test_invalid_header() { - let mut buf = BytesMut::from( + expect_parse_err!(&mut BytesMut::from( "GET /test HTTP/1.1\r\n\ test line\r\n\r\n", - ); - - expect_parse_err!(&mut buf); + )); } #[test] fn test_invalid_name() { - let mut buf = BytesMut::from( + expect_parse_err!(&mut BytesMut::from( "GET /test HTTP/1.1\r\n\ test[]: line\r\n\r\n", - ); - - expect_parse_err!(&mut buf); + )); } #[test] fn test_http_request_bad_status_line() { - let mut buf = BytesMut::from("getpath \r\n\r\n"); - expect_parse_err!(&mut buf); + expect_parse_err!(&mut BytesMut::from("getpath \r\n\r\n")); } #[test] @@ -1058,11 +1030,10 @@ mod tests { #[test] fn test_http_request_parser_utf8() { - let mut buf = BytesMut::from( + let req = parse_ready!(&mut BytesMut::from( "GET /test HTTP/1.1\r\n\ x-test: ั‚ะตัั‚\r\n\r\n", - ); - let req = parse_ready!(&mut buf); + )); assert_eq!( req.headers().get("x-test").unwrap().as_bytes(), @@ -1072,24 +1043,18 @@ mod tests { #[test] fn test_http_request_parser_two_slashes() { - let mut buf = BytesMut::from("GET //path HTTP/1.1\r\n\r\n"); - let req = parse_ready!(&mut buf); - + let req = parse_ready!(&mut BytesMut::from("GET //path HTTP/1.1\r\n\r\n")); assert_eq!(req.path(), "//path"); } #[test] fn test_http_request_parser_bad_method() { - let mut buf = BytesMut::from("!12%()+=~$ /get HTTP/1.1\r\n\r\n"); - - expect_parse_err!(&mut buf); + expect_parse_err!(&mut BytesMut::from("!12%()+=~$ /get HTTP/1.1\r\n\r\n")); } #[test] fn test_http_request_parser_bad_version() { - let mut buf = BytesMut::from("GET //get HT/11\r\n\r\n"); - - expect_parse_err!(&mut buf); + expect_parse_err!(&mut BytesMut::from("GET //get HT/11\r\n\r\n")); } #[test] @@ -1106,47 +1071,41 @@ mod tests { #[test] fn hrs_multiple_content_length() { - let mut buf = BytesMut::from( + expect_parse_err!(&mut BytesMut::from( "GET / HTTP/1.1\r\n\ Host: example.com\r\n\ Content-Length: 4\r\n\ Content-Length: 2\r\n\ \r\n\ abcd", - ); + )); - expect_parse_err!(&mut buf); - - let mut buf = BytesMut::from( + expect_parse_err!(&mut BytesMut::from( "GET / HTTP/1.1\r\n\ Host: example.com\r\n\ Content-Length: 0\r\n\ Content-Length: 2\r\n\ \r\n\ ab", - ); - - expect_parse_err!(&mut buf); + )); } #[test] fn hrs_content_length_plus() { - let mut buf = BytesMut::from( + expect_parse_err!(&mut BytesMut::from( "GET / HTTP/1.1\r\n\ Host: example.com\r\n\ Content-Length: +3\r\n\ \r\n\ 000", - ); - - expect_parse_err!(&mut buf); + )); } #[test] fn hrs_te_http10() { // in HTTP/1.0 transfer encoding is ignored and must therefore contain a CL header - let mut buf = BytesMut::from( + expect_parse_err!(&mut BytesMut::from( "POST / HTTP/1.0\r\n\ Host: example.com\r\n\ Transfer-Encoding: chunked\r\n\ @@ -1155,9 +1114,7 @@ mod tests { aaa\r\n\ 0\r\n\ ", - ); - - expect_parse_err!(&mut buf); + )); } #[test] From c0d5d7bdb54a1983942d5a25056d4c669eb03b51 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sat, 2 Jul 2022 21:04:37 +0100 Subject: [PATCH 169/170] add octal-ish CL test --- actix-http/src/h1/decoder.rs | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/actix-http/src/h1/decoder.rs b/actix-http/src/h1/decoder.rs index 40cabf9cb..203b6c531 100644 --- a/actix-http/src/h1/decoder.rs +++ b/actix-http/src/h1/decoder.rs @@ -964,12 +964,20 @@ mod tests { "GET /test HTTP/1.1\r\n\ content-length: -1\r\n\r\n", )); + } - // octal CL - // expect_parse_err!(&mut BytesMut::from( - // "GET /test HTTP/1.1\r\n\ - // content-length: 0123\r\n\r\n", - // )); + #[test] + fn octal_ish_cl_parsed_as_decimal() { + let mut buf = BytesMut::from( + "POST /test HTTP/1.1\r\n\ + content-length: 011\r\n\r\n", + ); + let mut reader = MessageDecoder::::default(); + let (_req, pl) = reader.decode(&mut buf).unwrap().unwrap(); + assert!(matches!( + pl, + PayloadType::Payload(pl) if pl == PayloadDecoder::length(11) + )); } #[test] From 8759d79b03510f0cae2aac1cb459e79802c85fc6 Mon Sep 17 00:00:00 2001 From: Roland Fredenhagen Date: Mon, 4 Jul 2022 06:31:49 +0200 Subject: [PATCH 170/170] `routes` macro allowing multiple paths per handler (#2718) * WIP: basic implementation for `routes` macro * chore: changelog, docs, tests * error on missing methods * Apply suggestions from code review Co-authored-by: Igor Aleksanov * update test stderr expectation * add additional tests * fix stderr output * remove useless ResourceType this is dead code from back when .to and .to_async were different ways to add a service Co-authored-by: Igor Aleksanov Co-authored-by: Rob Ede --- actix-web-codegen/CHANGES.md | 3 + actix-web-codegen/Cargo.toml | 2 +- actix-web-codegen/src/lib.rs | 48 ++- actix-web-codegen/src/route.rs | 274 +++++++++++------- actix-web-codegen/tests/test_macro.rs | 76 ++++- actix-web-codegen/tests/trybuild.rs | 4 + .../trybuild/routes-missing-args-fail.rs | 14 + .../trybuild/routes-missing-args-fail.stderr | 21 ++ .../trybuild/routes-missing-method-fail.rs | 13 + .../routes-missing-method-fail.stderr | 15 + actix-web-codegen/tests/trybuild/routes-ok.rs | 23 ++ actix-web/CHANGES.md | 2 + actix-web/src/lib.rs | 1 + 13 files changed, 384 insertions(+), 112 deletions(-) create mode 100644 actix-web-codegen/tests/trybuild/routes-missing-args-fail.rs create mode 100644 actix-web-codegen/tests/trybuild/routes-missing-args-fail.stderr create mode 100644 actix-web-codegen/tests/trybuild/routes-missing-method-fail.rs create mode 100644 actix-web-codegen/tests/trybuild/routes-missing-method-fail.stderr create mode 100644 actix-web-codegen/tests/trybuild/routes-ok.rs diff --git a/actix-web-codegen/CHANGES.md b/actix-web-codegen/CHANGES.md index a85d6c454..6b525a441 100644 --- a/actix-web-codegen/CHANGES.md +++ b/actix-web-codegen/CHANGES.md @@ -1,8 +1,11 @@ # Changes ## Unreleased - 2022-xx-xx +- Add `#[routes]` macro to support multiple paths for one handler. [#2718] - Minimum supported Rust version (MSRV) is now 1.57 due to transitive `time` dependency. +[#2718]: https://github.com/actix/actix-web/pull/2718 + ## 4.0.1 - 2022-06-11 - Fix support for guard paths in route handler macros. [#2771] diff --git a/actix-web-codegen/Cargo.toml b/actix-web-codegen/Cargo.toml index 52094443b..a2aac7e68 100644 --- a/actix-web-codegen/Cargo.toml +++ b/actix-web-codegen/Cargo.toml @@ -18,7 +18,7 @@ proc-macro = true actix-router = "0.5.0" proc-macro2 = "1" quote = "1" -syn = { version = "1", features = ["full", "parsing"] } +syn = { version = "1", features = ["full", "extra-traits"] } [dev-dependencies] actix-macros = "0.2.3" diff --git a/actix-web-codegen/src/lib.rs b/actix-web-codegen/src/lib.rs index 5ca5616b6..4b6dc43c5 100644 --- a/actix-web-codegen/src/lib.rs +++ b/actix-web-codegen/src/lib.rs @@ -46,9 +46,20 @@ //! ``` //! //! # Multiple Path Handlers -//! There are no macros to generate multi-path handlers. Let us know in [this issue]. +//! Acts as a wrapper for multiple single method handler macros. It takes no arguments and +//! delegates those to the macros for the individual methods. See [macro@routes] macro docs. //! -//! [this issue]: https://github.com/actix/actix-web/issues/1709 +//! ``` +//! # use actix_web::HttpResponse; +//! # use actix_web_codegen::routes; +//! #[routes] +//! #[get("/test")] +//! #[get("/test2")] +//! #[delete("/test")] +//! async fn example() -> HttpResponse { +//! HttpResponse::Ok().finish() +//! } +//! ``` //! //! [actix-web attributes docs]: https://docs.rs/actix-web/latest/actix_web/#attributes //! [GET]: macro@get @@ -104,6 +115,39 @@ pub fn route(args: TokenStream, input: TokenStream) -> TokenStream { route::with_method(None, args, input) } +/// Creates resource handler, allowing multiple HTTP methods and paths. +/// +/// # Syntax +/// ```plain +/// #[routes] +/// #[("path", ...)] +/// #[("path", ...)] +/// ... +/// ``` +/// +/// # Attributes +/// The `routes` macro itself has no parameters, but allows specifying the attribute macros for +/// the multiple paths and/or methods, e.g. [`GET`](macro@get) and [`POST`](macro@post). +/// +/// These helper attributes take the same parameters as the [single method handlers](crate#single-method-handler). +/// +/// # Examples +/// ``` +/// # use actix_web::HttpResponse; +/// # use actix_web_codegen::routes; +/// #[routes] +/// #[get("/test")] +/// #[get("/test2")] +/// #[delete("/test")] +/// async fn example() -> HttpResponse { +/// HttpResponse::Ok().finish() +/// } +/// ``` +#[proc_macro_attribute] +pub fn routes(_: TokenStream, input: TokenStream) -> TokenStream { + route::with_methods(input) +} + macro_rules! method_macro { ($variant:ident, $method:ident) => { #[doc = concat!("Creates route handler with `actix_web::guard::", stringify!($variant), "`.")] diff --git a/actix-web-codegen/src/route.rs b/actix-web-codegen/src/route.rs index cae3cbd55..7a0658468 100644 --- a/actix-web-codegen/src/route.rs +++ b/actix-web-codegen/src/route.rs @@ -3,24 +3,12 @@ use std::{collections::HashSet, convert::TryFrom}; use actix_router::ResourceDef; use proc_macro::TokenStream; use proc_macro2::{Span, TokenStream as TokenStream2}; -use quote::{format_ident, quote, ToTokens, TokenStreamExt}; -use syn::{parse_macro_input, AttributeArgs, Ident, LitStr, NestedMeta, Path}; - -enum ResourceType { - Async, - Sync, -} - -impl ToTokens for ResourceType { - fn to_tokens(&self, stream: &mut TokenStream2) { - let ident = format_ident!("to"); - stream.append(ident); - } -} +use quote::{quote, ToTokens, TokenStreamExt}; +use syn::{parse_macro_input, AttributeArgs, Ident, LitStr, Meta, NestedMeta, Path}; macro_rules! method_type { ( - $($variant:ident, $upper:ident,)+ + $($variant:ident, $upper:ident, $lower:ident,)+ ) => { #[derive(Debug, PartialEq, Eq, Hash)] pub enum MethodType { @@ -42,20 +30,27 @@ macro_rules! method_type { _ => Err(format!("Unexpected HTTP method: `{}`", method)), } } + + fn from_path(method: &Path) -> Result { + match () { + $(_ if method.is_ident(stringify!($lower)) => Ok(Self::$variant),)+ + _ => Err(()), + } + } } }; } method_type! { - Get, GET, - Post, POST, - Put, PUT, - Delete, DELETE, - Head, HEAD, - Connect, CONNECT, - Options, OPTIONS, - Trace, TRACE, - Patch, PATCH, + Get, GET, get, + Post, POST, post, + Put, PUT, put, + Delete, DELETE, delete, + Head, HEAD, head, + Connect, CONNECT, connect, + Options, OPTIONS, options, + Trace, TRACE, trace, + Patch, PATCH, patch, } impl ToTokens for MethodType { @@ -90,6 +85,18 @@ impl Args { let mut wrappers = Vec::new(); let mut methods = HashSet::new(); + if args.is_empty() { + return Err(syn::Error::new( + Span::call_site(), + format!( + r#"invalid service definition, expected #[{}("")]"#, + method + .map_or("route", |it| it.as_str()) + .to_ascii_lowercase() + ), + )); + } + let is_route_macro = method.is_none(); if let Some(method) = method { methods.insert(method); @@ -183,55 +190,27 @@ impl Args { } pub struct Route { + /// Name of the handler function being annotated. name: syn::Ident, - args: Args, + + /// Args passed to routing macro. + /// + /// When using `#[routes]`, this will contain args for each specific routing macro. + args: Vec, + + /// AST of the handler function being annotated. ast: syn::ItemFn, - resource_type: ResourceType, /// The doc comment attributes to copy to generated struct, if any. doc_attributes: Vec, } -fn guess_resource_type(typ: &syn::Type) -> ResourceType { - let mut guess = ResourceType::Sync; - - if let syn::Type::ImplTrait(typ) = typ { - for bound in typ.bounds.iter() { - if let syn::TypeParamBound::Trait(bound) = bound { - for bound in bound.path.segments.iter() { - if bound.ident == "Future" { - guess = ResourceType::Async; - break; - } else if bound.ident == "Responder" { - guess = ResourceType::Sync; - break; - } - } - } - } - } - - guess -} - impl Route { pub fn new( args: AttributeArgs, ast: syn::ItemFn, method: Option, ) -> syn::Result { - if args.is_empty() { - return Err(syn::Error::new( - Span::call_site(), - format!( - r#"invalid service definition, expected #[{}("")]"#, - method - .map_or("route", |it| it.as_str()) - .to_ascii_lowercase() - ), - )); - } - let name = ast.sig.ident.clone(); // Try and pull out the doc comments so that we can reapply them to the generated struct. @@ -244,6 +223,7 @@ impl Route { .collect(); let args = Args::new(args, method)?; + if args.methods.is_empty() { return Err(syn::Error::new( Span::call_site(), @@ -251,25 +231,44 @@ impl Route { )); } - let resource_type = if ast.sig.asyncness.is_some() { - ResourceType::Async - } else { - match ast.sig.output { - syn::ReturnType::Default => { - return Err(syn::Error::new_spanned( - ast, - "Function has no return type. Cannot be used as handler", - )); - } - syn::ReturnType::Type(_, ref typ) => guess_resource_type(typ.as_ref()), - } - }; + if matches!(ast.sig.output, syn::ReturnType::Default) { + return Err(syn::Error::new_spanned( + ast, + "Function has no return type. Cannot be used as handler", + )); + } + + Ok(Self { + name, + args: vec![args], + ast, + doc_attributes, + }) + } + + fn multiple(args: Vec, ast: syn::ItemFn) -> syn::Result { + let name = ast.sig.ident.clone(); + + // Try and pull out the doc comments so that we can reapply them to the generated struct. + // Note that multi line doc comments are converted to multiple doc attributes. + let doc_attributes = ast + .attrs + .iter() + .filter(|attr| attr.path.is_ident("doc")) + .cloned() + .collect(); + + if matches!(ast.sig.output, syn::ReturnType::Default) { + return Err(syn::Error::new_spanned( + ast, + "Function has no return type. Cannot be used as handler", + )); + } Ok(Self { name, args, ast, - resource_type, doc_attributes, }) } @@ -280,38 +279,57 @@ impl ToTokens for Route { let Self { name, ast, - args: - Args { + args, + doc_attributes, + } = self; + + let registrations: TokenStream2 = args + .iter() + .map(|args| { + let Args { path, resource_name, guards, wrappers, methods, - }, - resource_type, - doc_attributes, - } = self; - let resource_name = resource_name - .as_ref() - .map_or_else(|| name.to_string(), LitStr::value); - let method_guards = { - let mut others = methods.iter(); - // unwrapping since length is checked to be at least one - let first = others.next().unwrap(); + } = args; + + let resource_name = resource_name + .as_ref() + .map_or_else(|| name.to_string(), LitStr::value); + + let method_guards = { + let mut others = methods.iter(); + + // unwrapping since length is checked to be at least one + let first = others.next().unwrap(); + + if methods.len() > 1 { + quote! { + .guard( + ::actix_web::guard::Any(::actix_web::guard::#first()) + #(.or(::actix_web::guard::#others()))* + ) + } + } else { + quote! { + .guard(::actix_web::guard::#first()) + } + } + }; - if methods.len() > 1 { quote! { - .guard( - ::actix_web::guard::Any(::actix_web::guard::#first()) - #(.or(::actix_web::guard::#others()))* - ) + let __resource = ::actix_web::Resource::new(#path) + .name(#resource_name) + #method_guards + #(.guard(::actix_web::guard::fn_guard(#guards)))* + #(.wrap(#wrappers))* + .to(#name); + + ::actix_web::dev::HttpServiceFactory::register(__resource, __config); } - } else { - quote! { - .guard(::actix_web::guard::#first()) - } - } - }; + }) + .collect(); let stream = quote! { #(#doc_attributes)* @@ -321,14 +339,7 @@ impl ToTokens for Route { impl ::actix_web::dev::HttpServiceFactory for #name { fn register(self, __config: &mut actix_web::dev::AppService) { #ast - let __resource = ::actix_web::Resource::new(#path) - .name(#resource_name) - #method_guards - #(.guard(::actix_web::guard::fn_guard(#guards)))* - #(.wrap(#wrappers))* - .#resource_type(#name); - - ::actix_web::dev::HttpServiceFactory::register(__resource, __config) + #registrations } } }; @@ -357,6 +368,57 @@ pub(crate) fn with_method( } } +pub(crate) fn with_methods(input: TokenStream) -> TokenStream { + let mut ast = match syn::parse::(input.clone()) { + Ok(ast) => ast, + // on parse error, make IDEs happy; see fn docs + Err(err) => return input_and_compile_error(input, err), + }; + + let (methods, others) = ast + .attrs + .into_iter() + .map(|attr| match MethodType::from_path(&attr.path) { + Ok(method) => Ok((method, attr)), + Err(_) => Err(attr), + }) + .partition::, _>(Result::is_ok); + + ast.attrs = others.into_iter().map(Result::unwrap_err).collect(); + + let methods = + match methods + .into_iter() + .map(Result::unwrap) + .map(|(method, attr)| { + attr.parse_meta().and_then(|args| { + if let Meta::List(args) = args { + Args::new(args.nested.into_iter().collect(), Some(method)) + } else { + Err(syn::Error::new_spanned(attr, "Invalid input for macro")) + } + }) + }) + .collect::, _>>() + { + Ok(methods) if methods.is_empty() => return input_and_compile_error( + input, + syn::Error::new( + Span::call_site(), + "The #[routes] macro requires at least one `#[(..)]` attribute.", + ), + ), + Ok(methods) => methods, + Err(err) => return input_and_compile_error(input, err), + }; + + match Route::multiple(methods, ast) { + Ok(route) => route.into_token_stream().into(), + // on macro related error, make IDEs happy; see fn docs + Err(err) => input_and_compile_error(input, err), + } +} + /// Converts the error to a token stream and appends it to the original input. /// /// Returning the original input in addition to the error is good for IDEs which can gracefully diff --git a/actix-web-codegen/tests/test_macro.rs b/actix-web-codegen/tests/test_macro.rs index 55c2417b2..10e906967 100644 --- a/actix-web-codegen/tests/test_macro.rs +++ b/actix-web-codegen/tests/test_macro.rs @@ -8,9 +8,11 @@ use actix_web::{ header::{HeaderName, HeaderValue}, StatusCode, }, - web, App, Error, HttpResponse, Responder, + web, App, Error, HttpRequest, HttpResponse, Responder, +}; +use actix_web_codegen::{ + connect, delete, get, head, options, patch, post, put, route, routes, trace, }; -use actix_web_codegen::{connect, delete, get, head, options, patch, post, put, route, trace}; use futures_core::future::LocalBoxFuture; // Make sure that we can name function as 'config' @@ -89,8 +91,41 @@ async fn route_test() -> impl Responder { HttpResponse::Ok() } +#[routes] +#[get("/routes/test")] +#[get("/routes/test2")] +#[post("/routes/test")] +async fn routes_test() -> impl Responder { + HttpResponse::Ok() +} + +// routes overlap with the more specific route first, therefore accessible +#[routes] +#[get("/routes/overlap/test")] +#[get("/routes/overlap/{foo}")] +async fn routes_overlapping_test(req: HttpRequest) -> impl Responder { + // foo is only populated when route is not /routes/overlap/test + match req.match_info().get("foo") { + None => assert!(req.uri() == "/routes/overlap/test"), + Some(_) => assert!(req.uri() != "/routes/overlap/test"), + } + + HttpResponse::Ok() +} + +// routes overlap with the more specific route last, therefore inaccessible +#[routes] +#[get("/routes/overlap2/{foo}")] +#[get("/routes/overlap2/test")] +async fn routes_overlapping_inaccessible_test(req: HttpRequest) -> impl Responder { + // foo is always populated even when path is /routes/overlap2/test + assert!(req.match_info().get("foo").is_some()); + + HttpResponse::Ok() +} + #[get("/custom_resource_name", name = "custom")] -async fn custom_resource_name_test<'a>(req: actix_web::HttpRequest) -> impl Responder { +async fn custom_resource_name_test<'a>(req: HttpRequest) -> impl Responder { assert!(req.url_for_static("custom").is_ok()); assert!(req.url_for_static("custom_resource_name_test").is_err()); HttpResponse::Ok() @@ -201,6 +236,9 @@ async fn test_body() { .service(patch_test) .service(test_handler) .service(route_test) + .service(routes_overlapping_test) + .service(routes_overlapping_inaccessible_test) + .service(routes_test) .service(custom_resource_name_test) .service(guard_test) }); @@ -258,6 +296,38 @@ async fn test_body() { let response = request.send().await.unwrap(); assert!(!response.status().is_success()); + let request = srv.request(http::Method::GET, srv.url("/routes/test")); + let response = request.send().await.unwrap(); + assert!(response.status().is_success()); + + let request = srv.request(http::Method::GET, srv.url("/routes/test2")); + let response = request.send().await.unwrap(); + assert!(response.status().is_success()); + + let request = srv.request(http::Method::POST, srv.url("/routes/test")); + let response = request.send().await.unwrap(); + assert!(response.status().is_success()); + + let request = srv.request(http::Method::GET, srv.url("/routes/not-set")); + let response = request.send().await.unwrap(); + assert!(response.status().is_client_error()); + + let request = srv.request(http::Method::GET, srv.url("/routes/overlap/test")); + let response = request.send().await.unwrap(); + assert!(response.status().is_success()); + + let request = srv.request(http::Method::GET, srv.url("/routes/overlap/bar")); + let response = request.send().await.unwrap(); + assert!(response.status().is_success()); + + let request = srv.request(http::Method::GET, srv.url("/routes/overlap2/test")); + let response = request.send().await.unwrap(); + assert!(response.status().is_success()); + + let request = srv.request(http::Method::GET, srv.url("/routes/overlap2/bar")); + let response = request.send().await.unwrap(); + assert!(response.status().is_success()); + let request = srv.request(http::Method::GET, srv.url("/custom_resource_name")); let response = request.send().await.unwrap(); assert!(response.status().is_success()); diff --git a/actix-web-codegen/tests/trybuild.rs b/actix-web-codegen/tests/trybuild.rs index 976b3d52c..1f7996fd0 100644 --- a/actix-web-codegen/tests/trybuild.rs +++ b/actix-web-codegen/tests/trybuild.rs @@ -12,6 +12,10 @@ fn compile_macros() { t.compile_fail("tests/trybuild/route-unexpected-method-fail.rs"); t.compile_fail("tests/trybuild/route-malformed-path-fail.rs"); + t.pass("tests/trybuild/routes-ok.rs"); + t.compile_fail("tests/trybuild/routes-missing-method-fail.rs"); + t.compile_fail("tests/trybuild/routes-missing-args-fail.rs"); + t.pass("tests/trybuild/docstring-ok.rs"); t.pass("tests/trybuild/test-runtime.rs"); diff --git a/actix-web-codegen/tests/trybuild/routes-missing-args-fail.rs b/actix-web-codegen/tests/trybuild/routes-missing-args-fail.rs new file mode 100644 index 000000000..65573cf79 --- /dev/null +++ b/actix-web-codegen/tests/trybuild/routes-missing-args-fail.rs @@ -0,0 +1,14 @@ +use actix_web_codegen::*; + +#[routes] +#[get] +async fn index() -> String { + "Hello World!".to_owned() +} + +#[actix_web::main] +async fn main() { + use actix_web::App; + + let srv = actix_test::start(|| App::new().service(index)); +} diff --git a/actix-web-codegen/tests/trybuild/routes-missing-args-fail.stderr b/actix-web-codegen/tests/trybuild/routes-missing-args-fail.stderr new file mode 100644 index 000000000..8efe0682b --- /dev/null +++ b/actix-web-codegen/tests/trybuild/routes-missing-args-fail.stderr @@ -0,0 +1,21 @@ +error: invalid service definition, expected #[get("")] + --> tests/trybuild/routes-missing-args-fail.rs:4:1 + | +4 | #[get] + | ^^^^^^ + | + = note: this error originates in the attribute macro `get` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: Invalid input for macro + --> tests/trybuild/routes-missing-args-fail.rs:4:1 + | +4 | #[get] + | ^^^^^^ + +error[E0277]: the trait bound `fn() -> impl std::future::Future {index}: HttpServiceFactory` is not satisfied + --> tests/trybuild/routes-missing-args-fail.rs:13:55 + | +13 | let srv = actix_test::start(|| App::new().service(index)); + | ------- ^^^^^ the trait `HttpServiceFactory` is not implemented for `fn() -> impl std::future::Future {index}` + | | + | required by a bound introduced by this call diff --git a/actix-web-codegen/tests/trybuild/routes-missing-method-fail.rs b/actix-web-codegen/tests/trybuild/routes-missing-method-fail.rs new file mode 100644 index 000000000..f0271ef44 --- /dev/null +++ b/actix-web-codegen/tests/trybuild/routes-missing-method-fail.rs @@ -0,0 +1,13 @@ +use actix_web_codegen::*; + +#[routes] +async fn index() -> String { + "Hello World!".to_owned() +} + +#[actix_web::main] +async fn main() { + use actix_web::App; + + let srv = actix_test::start(|| App::new().service(index)); +} diff --git a/actix-web-codegen/tests/trybuild/routes-missing-method-fail.stderr b/actix-web-codegen/tests/trybuild/routes-missing-method-fail.stderr new file mode 100644 index 000000000..b3795d74a --- /dev/null +++ b/actix-web-codegen/tests/trybuild/routes-missing-method-fail.stderr @@ -0,0 +1,15 @@ +error: The #[routes] macro requires at least one `#[(..)]` attribute. + --> tests/trybuild/routes-missing-method-fail.rs:3:1 + | +3 | #[routes] + | ^^^^^^^^^ + | + = note: this error originates in the attribute macro `routes` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0277]: the trait bound `fn() -> impl std::future::Future {index}: HttpServiceFactory` is not satisfied + --> tests/trybuild/routes-missing-method-fail.rs:12:55 + | +12 | let srv = actix_test::start(|| App::new().service(index)); + | ------- ^^^^^ the trait `HttpServiceFactory` is not implemented for `fn() -> impl std::future::Future {index}` + | | + | required by a bound introduced by this call diff --git a/actix-web-codegen/tests/trybuild/routes-ok.rs b/actix-web-codegen/tests/trybuild/routes-ok.rs new file mode 100644 index 000000000..98b5e553e --- /dev/null +++ b/actix-web-codegen/tests/trybuild/routes-ok.rs @@ -0,0 +1,23 @@ +use actix_web_codegen::*; + +#[routes] +#[get("/")] +#[post("/")] +async fn index() -> String { + "Hello World!".to_owned() +} + +#[actix_web::main] +async fn main() { + use actix_web::App; + + let srv = actix_test::start(|| App::new().service(index)); + + let request = srv.get("/"); + let response = request.send().await.unwrap(); + assert!(response.status().is_success()); + + let request = srv.post("/"); + let response = request.send().await.unwrap(); + assert!(response.status().is_success()); +} diff --git a/actix-web/CHANGES.md b/actix-web/CHANGES.md index 0144cb912..f38282b41 100644 --- a/actix-web/CHANGES.md +++ b/actix-web/CHANGES.md @@ -2,12 +2,14 @@ ## Unreleased - 2022-xx-xx ### Added +- Add `#[routes]` macro to support multiple paths for one handler. [#2718] - Add `ServiceRequest::{parts, request}()` getter methods. [#2786] - Add configuration options for TLS handshake timeout via `HttpServer::{rustls, openssl}_with_config` methods. [#2752] ### Changed - Minimum supported Rust version (MSRV) is now 1.57 due to transitive `time` dependency. +[#2718]: https://github.com/actix/actix-web/pull/2718 [#2752]: https://github.com/actix/actix-web/pull/2752 [#2786]: https://github.com/actix/actix-web/pull/2786 diff --git a/actix-web/src/lib.rs b/actix-web/src/lib.rs index 4eab24cec..8d9e2dbcd 100644 --- a/actix-web/src/lib.rs +++ b/actix-web/src/lib.rs @@ -132,6 +132,7 @@ macro_rules! codegen_reexport { codegen_reexport!(main); codegen_reexport!(test); codegen_reexport!(route); +codegen_reexport!(routes); codegen_reexport!(head); codegen_reexport!(get); codegen_reexport!(post);