From c836de44af0667246a2691a01df9f0dc55bcafd7 Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Sun, 28 Feb 2021 10:17:08 -0800 Subject: [PATCH 01/34] add client middleware (#2013) --- awc/Cargo.toml | 3 +- awc/src/builder.rs | 59 +++++- awc/src/connect.rs | 123 ++++++++---- awc/src/lib.rs | 13 +- awc/src/middleware/mod.rs | 71 +++++++ awc/src/middleware/redirect.rs | 350 +++++++++++++++++++++++++++++++++ awc/src/response.rs | 4 +- src/types/form.rs | 8 +- src/types/json.rs | 4 +- 9 files changed, 570 insertions(+), 65 deletions(-) create mode 100644 awc/src/middleware/mod.rs create mode 100644 awc/src/middleware/redirect.rs diff --git a/awc/Cargo.toml b/awc/Cargo.toml index 8cbba432c..ca345d3cb 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -47,7 +47,7 @@ trust-dns = ["actix-http/trust-dns"] actix-codec = "0.4.0-beta.1" actix-service = "2.0.0-beta.4" actix-http = "3.0.0-beta.3" -actix-rt = "2.1" +actix-rt = { version = "2.1", default-features = false } base64 = "0.13" bytes = "1" @@ -57,6 +57,7 @@ futures-core = { version = "0.3.7", default-features = false } log =" 0.4" mime = "0.3" percent-encoding = "2.1" +pin-project-lite = "0.2" rand = "0.8" serde = "1.0" serde_json = "1.0" diff --git a/awc/src/builder.rs b/awc/src/builder.rs index b7cdefd40..363056c02 100644 --- a/awc/src/builder.rs +++ b/awc/src/builder.rs @@ -10,24 +10,27 @@ use actix_http::{ http::{self, header, Error as HttpError, HeaderMap, HeaderName, Uri}, }; use actix_rt::net::TcpStream; -use actix_service::Service; +use actix_service::{boxed, Service}; -use crate::connect::ConnectorWrapper; -use crate::{Client, ClientConfig}; +use crate::connect::DefaultConnector; +use crate::error::SendRequestError; +use crate::middleware::{NestTransform, Transform}; +use crate::{Client, ClientConfig, ConnectRequest, ConnectResponse, ConnectorService}; /// An HTTP Client builder /// /// This type can be used to construct an instance of `Client` through a /// builder-like pattern. -pub struct ClientBuilder { +pub struct ClientBuilder { default_headers: bool, max_http_version: Option, stream_window_size: Option, conn_window_size: Option, headers: HeaderMap, timeout: Option, + connector: Connector, + middleware: M, local_address: Option, - connector: Connector, } impl ClientBuilder { @@ -39,8 +42,10 @@ impl ClientBuilder { Error = TcpConnectError, > + Clone, TcpStream, + (), > { ClientBuilder { + middleware: (), default_headers: true, headers: HeaderMap::new(), timeout: Some(Duration::from_secs(5)), @@ -53,7 +58,7 @@ impl ClientBuilder { } } -impl ClientBuilder +impl ClientBuilder where S: Service, Response = TcpConnection, Error = TcpConnectError> + Clone @@ -61,7 +66,7 @@ where Io: AsyncRead + AsyncWrite + Unpin + fmt::Debug + 'static, { /// Use custom connector service. - pub fn connector(self, connector: Connector) -> ClientBuilder + pub fn connector(self, connector: Connector) -> ClientBuilder where S1: Service< TcpConnect, @@ -72,10 +77,11 @@ where Io1: AsyncRead + AsyncWrite + Unpin + fmt::Debug + 'static, { ClientBuilder { + middleware: self.middleware, default_headers: self.default_headers, headers: self.headers, timeout: self.timeout, - local_address: None, + local_address: self.local_address, connector, max_http_version: self.max_http_version, stream_window_size: self.stream_window_size, @@ -181,8 +187,38 @@ where self.header(header::AUTHORIZATION, format!("Bearer {}", token)) } + /// Registers middleware, in the form of a middleware component (type), + /// that runs during inbound and/or outbound processing in the request + /// life-cycle (request -> response), modifying request/response as + /// necessary, across all requests managed by the Client. + pub fn wrap( + self, + mw: M1, + ) -> ClientBuilder> + where + M: Transform, + M1: Transform, + { + ClientBuilder { + middleware: NestTransform::new(self.middleware, mw), + default_headers: self.default_headers, + max_http_version: self.max_http_version, + stream_window_size: self.stream_window_size, + conn_window_size: self.conn_window_size, + headers: self.headers, + timeout: self.timeout, + connector: self.connector, + local_address: self.local_address, + } + } + /// Finish build process and create `Client` instance. - pub fn finish(self) -> Client { + pub fn finish(self) -> Client + where + M: Transform + 'static, + M::Transform: + Service, + { let mut connector = self.connector; if let Some(val) = self.max_http_version { @@ -198,10 +234,13 @@ where connector = connector.local_address(val); } + let connector = boxed::service(DefaultConnector::new(connector.finish())); + let connector = boxed::service(self.middleware.new_transform(connector)); + let config = ClientConfig { headers: self.headers, timeout: self.timeout, - connector: Box::new(ConnectorWrapper::new(connector.finish())) as _, + connector, }; Client(Rc::new(config)) diff --git a/awc/src/connect.rs b/awc/src/connect.rs index 97af2d1cc..a4abbc46b 100644 --- a/awc/src/connect.rs +++ b/awc/src/connect.rs @@ -1,5 +1,7 @@ use std::{ - fmt, io, net, + fmt, + future::Future, + io, net, pin::Pin, task::{Context, Poll}, }; @@ -9,24 +11,14 @@ use actix_http::{ body::Body, client::{Connect as ClientConnect, ConnectError, Connection, SendRequestError}, h1::ClientCodec, - RequestHead, RequestHeadType, ResponseHead, + Payload, RequestHead, RequestHeadType, ResponseHead, }; use actix_service::Service; -use futures_core::future::LocalBoxFuture; +use futures_core::{future::LocalBoxFuture, ready}; use crate::response::ClientResponse; -pub(crate) struct ConnectorWrapper { - connector: T, -} - -impl ConnectorWrapper { - pub(crate) fn new(connector: T) -> Self { - Self { connector } - } -} - -pub type ConnectService = Box< +pub type ConnectorService = Box< dyn Service< ConnectRequest, Response = ConnectResponse, @@ -65,16 +57,25 @@ impl ConnectResponse { } } -impl Service for ConnectorWrapper +pub(crate) struct DefaultConnector { + connector: S, +} + +impl DefaultConnector { + pub(crate) fn new(connector: S) -> Self { + Self { connector } + } +} + +impl Service for DefaultConnector where - T: Service, - T::Response: Connection, - ::Io: 'static, - T::Future: 'static, + S: Service, + S::Response: Connection, + ::Io: 'static, { type Response = ConnectResponse; type Error = SendRequestError; - type Future = LocalBoxFuture<'static, Result>; + type Future = ConnectRequestFuture::Io>; actix_service::forward_ready!(connector); @@ -91,26 +92,76 @@ where }), }; - Box::pin(async move { - let connection = fut.await?; + ConnectRequestFuture::Connection { + fut, + req: Some(req), + } + } +} - match req { - ConnectRequest::Client(head, body, ..) => { - // send request - let (head, payload) = connection.send_request(head, body).await?; +pin_project_lite::pin_project! { + #[project = ConnectRequestProj] + pub(crate) enum ConnectRequestFuture { + Connection { + #[pin] + fut: Fut, + req: Option + }, + Client { + fut: LocalBoxFuture<'static, Result<(ResponseHead, Payload), SendRequestError>> + }, + Tunnel { + fut: LocalBoxFuture< + 'static, + Result<(ResponseHead, Framed), SendRequestError>, + >, + } + } +} - Ok(ConnectResponse::Client(ClientResponse::new(head, payload))) - } - ConnectRequest::Tunnel(head, ..) => { - // send request - let (head, framed) = - connection.open_tunnel(RequestHeadType::from(head)).await?; - - let framed = framed.into_map_io(|io| BoxedSocket(Box::new(Socket(io)))); - Ok(ConnectResponse::Tunnel(head, framed)) +impl Future for ConnectRequestFuture +where + Fut: Future>, + C: Connection, + Io: AsyncRead + AsyncWrite + Unpin + 'static, +{ + type Output = Result; + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + match self.as_mut().project() { + ConnectRequestProj::Connection { fut, req } => { + let connection = ready!(fut.poll(cx))?; + let req = req.take().unwrap(); + match req { + ConnectRequest::Client(head, body, ..) => { + // send request + let fut = ConnectRequestFuture::Client { + fut: connection.send_request(head, body), + }; + self.as_mut().set(fut); + } + ConnectRequest::Tunnel(head, ..) => { + // send request + let fut = ConnectRequestFuture::Tunnel { + fut: connection.open_tunnel(RequestHeadType::from(head)), + }; + self.as_mut().set(fut); + } } + self.poll(cx) } - }) + ConnectRequestProj::Client { fut } => { + let (head, payload) = ready!(fut.as_mut().poll(cx))?; + Poll::Ready(Ok(ConnectResponse::Client(ClientResponse::new( + head, payload, + )))) + } + ConnectRequestProj::Tunnel { fut } => { + let (head, framed) = ready!(fut.as_mut().poll(cx))?; + let framed = framed.into_map_io(|io| BoxedSocket(Box::new(Socket(io)))); + Poll::Ready(Ok(ConnectResponse::Tunnel(head, framed))) + } + } } } diff --git a/awc/src/lib.rs b/awc/src/lib.rs index 66ff55402..2f48dca79 100644 --- a/awc/src/lib.rs +++ b/awc/src/lib.rs @@ -107,12 +107,13 @@ use actix_http::{ RequestHead, }; use actix_rt::net::TcpStream; -use actix_service::Service; +use actix_service::{boxed, Service}; mod builder; mod connect; pub mod error; mod frozen; +pub mod middleware; mod request; mod response; mod sender; @@ -120,14 +121,12 @@ pub mod test; pub mod ws; pub use self::builder::ClientBuilder; -pub use self::connect::{BoxedSocket, ConnectRequest, ConnectResponse, ConnectService}; +pub use self::connect::{BoxedSocket, ConnectRequest, ConnectResponse, ConnectorService}; pub use self::frozen::{FrozenClientRequest, FrozenSendBuilder}; pub use self::request::ClientRequest; pub use self::response::{ClientResponse, JsonBody, MessageBody}; pub use self::sender::SendClientRequest; -use self::connect::ConnectorWrapper; - /// An asynchronous HTTP and WebSocket client. /// /// ## Examples @@ -151,7 +150,7 @@ use self::connect::ConnectorWrapper; pub struct Client(Rc); pub(crate) struct ClientConfig { - pub(crate) connector: ConnectService, + pub(crate) connector: ConnectorService, pub(crate) headers: HeaderMap, pub(crate) timeout: Option, } @@ -159,7 +158,9 @@ pub(crate) struct ClientConfig { impl Default for Client { fn default() -> Self { Client(Rc::new(ClientConfig { - connector: Box::new(ConnectorWrapper::new(Connector::new().finish())), + connector: boxed::service(self::connect::DefaultConnector::new( + Connector::new().finish(), + )), headers: HeaderMap::new(), timeout: Some(Duration::from_secs(5)), })) diff --git a/awc/src/middleware/mod.rs b/awc/src/middleware/mod.rs new file mode 100644 index 000000000..330e3b7fe --- /dev/null +++ b/awc/src/middleware/mod.rs @@ -0,0 +1,71 @@ +mod redirect; + +pub use self::redirect::Redirect; + +use std::marker::PhantomData; + +use actix_service::Service; + +/// Trait for transform a type to another one. +/// Both the input and output type should impl [actix_service::Service] trait. +pub trait Transform { + type Transform: Service; + + /// Creates and returns a new Transform component. + fn new_transform(self, service: S) -> Self::Transform; +} + +#[doc(hidden)] +/// Helper struct for constructing Nested types that would call `Transform::new_transform` +/// in a chain. +/// +/// The child field would be called first and the output `Service` type is +/// passed to parent as input type. +pub struct NestTransform +where + T1: Transform, + T2: Transform, +{ + child: T1, + parent: T2, + _service: PhantomData<(S, Req)>, +} + +impl NestTransform +where + T1: Transform, + T2: Transform, +{ + pub(crate) fn new(child: T1, parent: T2) -> Self { + NestTransform { + child, + parent, + _service: PhantomData, + } + } +} + +impl Transform for NestTransform +where + T1: Transform, + T2: Transform, +{ + type Transform = T2::Transform; + + fn new_transform(self, service: S) -> Self::Transform { + let service = self.child.new_transform(service); + self.parent.new_transform(service) + } +} + +/// Dummy impl for kick start `NestTransform` type in `ClientBuilder` type +impl Transform for () +where + S: Service, +{ + type Transform = S; + + fn new_transform(self, service: S) -> Self::Transform { + service + } +} diff --git a/awc/src/middleware/redirect.rs b/awc/src/middleware/redirect.rs new file mode 100644 index 000000000..1d0ace166 --- /dev/null +++ b/awc/src/middleware/redirect.rs @@ -0,0 +1,350 @@ +use std::{ + convert::TryFrom, + future::Future, + net::SocketAddr, + pin::Pin, + rc::Rc, + task::{Context, Poll}, +}; + +use actix_http::{ + body::Body, + client::{InvalidUrl, SendRequestError}, + http::{header, Method, StatusCode, Uri}, + RequestHead, RequestHeadType, +}; +use actix_service::Service; +use bytes::Bytes; +use futures_core::ready; + +use super::Transform; + +use crate::connect::{ConnectRequest, ConnectResponse}; +use crate::ClientResponse; + +pub struct Redirect { + max_redirect_times: u8, +} + +impl Default for Redirect { + fn default() -> Self { + Self::new() + } +} + +impl Redirect { + pub fn new() -> Self { + Self { + max_redirect_times: 10, + } + } + + pub fn max_redirect_times(mut self, times: u8) -> Self { + self.max_redirect_times = times; + self + } +} + +impl Transform for Redirect +where + S: Service + 'static, +{ + type Transform = RedirectService; + + fn new_transform(self, service: S) -> Self::Transform { + RedirectService { + max_redirect_times: self.max_redirect_times, + connector: Rc::new(service), + } + } +} + +pub struct RedirectService { + max_redirect_times: u8, + connector: Rc, +} + +impl Service for RedirectService +where + S: Service + 'static, +{ + type Response = S::Response; + type Error = S::Error; + type Future = RedirectServiceFuture; + + actix_service::forward_ready!(connector); + + fn call(&self, req: ConnectRequest) -> Self::Future { + match req { + ConnectRequest::Tunnel(head, addr) => { + let fut = self.connector.call(ConnectRequest::Tunnel(head, addr)); + RedirectServiceFuture::Tunnel { fut } + } + ConnectRequest::Client(head, body, addr) => { + let connector = self.connector.clone(); + let max_redirect_times = self.max_redirect_times; + + // backup the uri and method for reuse schema and authority. + let (uri, method) = match head { + RequestHeadType::Owned(ref head) => (head.uri.clone(), head.method.clone()), + RequestHeadType::Rc(ref head, ..) => { + (head.uri.clone(), head.method.clone()) + } + }; + + let body_opt = match body { + Body::Bytes(ref b) => Some(b.clone()), + _ => None, + }; + + let fut = connector.call(ConnectRequest::Client(head, body, addr)); + + RedirectServiceFuture::Client { + fut, + max_redirect_times, + uri: Some(uri), + method: Some(method), + body: body_opt, + addr, + connector: Some(connector), + } + } + } + } +} + +pin_project_lite::pin_project! { + #[project = RedirectServiceProj] + pub enum RedirectServiceFuture + where + S: Service, + S: 'static + { + Tunnel { #[pin] fut: S::Future }, + Client { + #[pin] + fut: S::Future, + max_redirect_times: u8, + uri: Option, + method: Option, + body: Option, + addr: Option, + connector: Option> + } + } +} + +impl Future for RedirectServiceFuture +where + S: Service + 'static, +{ + type Output = Result; + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + match self.as_mut().project() { + RedirectServiceProj::Tunnel { fut } => fut.poll(cx), + RedirectServiceProj::Client { + fut, + max_redirect_times, + uri, + method, + body, + addr, + connector, + } => match ready!(fut.poll(cx))? { + ConnectResponse::Client(res) => match res.head().status { + StatusCode::MOVED_PERMANENTLY + | StatusCode::FOUND + | StatusCode::SEE_OTHER + if *max_redirect_times > 0 => + { + let org_uri = uri.take().unwrap(); + // rebuild uri from the location header value. + let uri = rebuild_uri(&res, org_uri)?; + + // reset method + let method = method.take().unwrap(); + let method = match method { + Method::GET | Method::HEAD => method, + _ => Method::GET, + }; + + // take ownership of states that could be reused + let addr = addr.take(); + let connector = connector.take(); + let mut max_redirect_times = *max_redirect_times; + + // use a new request head. + let mut head = RequestHead::default(); + head.uri = uri.clone(); + head.method = method.clone(); + + let head = RequestHeadType::Owned(head); + + max_redirect_times -= 1; + + let fut = connector + .as_ref() + .unwrap() + // remove body + .call(ConnectRequest::Client(head, Body::None, addr)); + + self.as_mut().set(RedirectServiceFuture::Client { + fut, + max_redirect_times, + uri: Some(uri), + method: Some(method), + // body is dropped on 301,302,303 + body: None, + addr, + connector, + }); + + self.poll(cx) + } + StatusCode::TEMPORARY_REDIRECT | StatusCode::PERMANENT_REDIRECT + if *max_redirect_times > 0 => + { + let org_uri = uri.take().unwrap(); + // rebuild uri from the location header value. + let uri = rebuild_uri(&res, org_uri)?; + + // try to reuse body + let body = body.take(); + let body_new = match body { + Some(ref bytes) => Body::Bytes(bytes.clone()), + // TODO: should this be Body::Empty or Body::None. + _ => Body::Empty, + }; + + let addr = addr.take(); + let method = method.take().unwrap(); + let connector = connector.take(); + let mut max_redirect_times = *max_redirect_times; + + // use a new request head. + let mut head = RequestHead::default(); + head.uri = uri.clone(); + head.method = method.clone(); + + let head = RequestHeadType::Owned(head); + + max_redirect_times -= 1; + + let fut = connector + .as_ref() + .unwrap() + .call(ConnectRequest::Client(head, body_new, addr)); + + self.as_mut().set(RedirectServiceFuture::Client { + fut, + max_redirect_times, + uri: Some(uri), + method: Some(method), + body, + addr, + connector, + }); + + self.poll(cx) + } + _ => Poll::Ready(Ok(ConnectResponse::Client(res))), + }, + _ => unreachable!("ConnectRequest::Tunnel is not handled by Redirect"), + }, + } + } +} + +fn rebuild_uri(res: &ClientResponse, org_uri: Uri) -> Result { + let uri = res + .headers() + .get(header::LOCATION) + .map(|value| { + // try to parse the location to a full uri + let uri = Uri::try_from(value.as_bytes()) + .map_err(|e| SendRequestError::Url(InvalidUrl::HttpError(e.into())))?; + if uri.scheme().is_none() || uri.authority().is_none() { + let uri = Uri::builder() + .scheme(org_uri.scheme().cloned().unwrap()) + .authority(org_uri.authority().cloned().unwrap()) + .path_and_query(value.as_bytes()) + .build()?; + Ok::<_, SendRequestError>(uri) + } else { + Ok(uri) + } + }) + // TODO: this error type is wrong. + .ok_or(SendRequestError::Url(InvalidUrl::MissingScheme))??; + + Ok(uri) +} + +#[cfg(test)] +mod tests { + use actix_web::{test::start, web, App, Error, HttpResponse}; + + use super::*; + + use crate::ClientBuilder; + + #[actix_rt::test] + async fn test_basic_redirect() { + let client = ClientBuilder::new() + .connector(crate::Connector::new()) + .wrap(Redirect::new().max_redirect_times(10)) + .finish(); + + let srv = start(|| { + App::new() + .service(web::resource("/test").route(web::to(|| async { + Ok::<_, Error>(HttpResponse::BadRequest()) + }))) + .service(web::resource("/").route(web::to(|| async { + Ok::<_, Error>( + HttpResponse::Found() + .append_header(("location", "/test")) + .finish(), + ) + }))) + }); + + let res = client.get(srv.url("/")).send().await.unwrap(); + + assert_eq!(res.status().as_u16(), 400); + } + + #[actix_rt::test] + async fn test_redirect_limit() { + let client = ClientBuilder::new() + .wrap(Redirect::new().max_redirect_times(1)) + .connector(crate::Connector::new()) + .finish(); + + let srv = start(|| { + App::new() + .service(web::resource("/").route(web::to(|| async { + Ok::<_, Error>( + HttpResponse::Found() + .append_header(("location", "/test")) + .finish(), + ) + }))) + .service(web::resource("/test").route(web::to(|| async { + Ok::<_, Error>( + HttpResponse::Found() + .append_header(("location", "/test2")) + .finish(), + ) + }))) + .service(web::resource("/test2").route(web::to(|| async { + Ok::<_, Error>(HttpResponse::BadRequest()) + }))) + }); + + let res = client.get(srv.url("/")).send().await.unwrap(); + + assert_eq!(res.status().as_u16(), 302); + } +} diff --git a/awc/src/response.rs b/awc/src/response.rs index 514b8a90b..40de3dc17 100644 --- a/awc/src/response.rs +++ b/awc/src/response.rs @@ -492,9 +492,7 @@ mod tests { JsonPayloadError::Payload(PayloadError::Overflow) => { matches!(other, JsonPayloadError::Payload(PayloadError::Overflow)) } - JsonPayloadError::ContentType => { - matches!(other, JsonPayloadError::ContentType) - } + JsonPayloadError::ContentType => matches!(other, JsonPayloadError::ContentType), _ => false, } } diff --git a/src/types/form.rs b/src/types/form.rs index 0b5c3c1b4..4f3ecbe7c 100644 --- a/src/types/form.rs +++ b/src/types/form.rs @@ -400,12 +400,8 @@ mod tests { UrlencodedError::Overflow { .. } => { matches!(other, UrlencodedError::Overflow { .. }) } - UrlencodedError::UnknownLength => { - matches!(other, UrlencodedError::UnknownLength) - } - UrlencodedError::ContentType => { - matches!(other, UrlencodedError::ContentType) - } + UrlencodedError::UnknownLength => matches!(other, UrlencodedError::UnknownLength), + UrlencodedError::ContentType => matches!(other, UrlencodedError::ContentType), _ => false, } } diff --git a/src/types/json.rs b/src/types/json.rs index 31ff680f4..866d835f2 100644 --- a/src/types/json.rs +++ b/src/types/json.rs @@ -441,9 +441,7 @@ mod tests { fn json_eq(err: JsonPayloadError, other: JsonPayloadError) -> bool { match err { JsonPayloadError::Overflow => matches!(other, JsonPayloadError::Overflow), - JsonPayloadError::ContentType => { - matches!(other, JsonPayloadError::ContentType) - } + JsonPayloadError::ContentType => matches!(other, JsonPayloadError::ContentType), _ => false, } } From cd652dca756ce7748ad0ba0dc4270110fb11b198 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sun, 28 Feb 2021 19:55:34 +0000 Subject: [PATCH 02/34] refactor websocket key hashing (#2035) --- actix-http/CHANGES.md | 2 + actix-http/Cargo.toml | 4 ++ actix-http/examples/ws.rs | 107 +++++++++++++++++++++++++++++ actix-http/src/ws/mod.rs | 27 +++++--- actix-http/src/ws/proto.rs | 134 +++++++++++++++++++++---------------- actix-web-actors/src/ws.rs | 11 ++- awc/src/error.rs | 17 +++-- awc/src/ws.rs | 8 ++- 8 files changed, 234 insertions(+), 76 deletions(-) create mode 100644 actix-http/examples/ws.rs diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 6ba111eb3..165b004a6 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -3,6 +3,7 @@ ## Unreleased - 2021-xx-xx ### Changed * Feature `cookies` is now optional and disabled by default. [#1981] +* `ws::hash_key` now returns array. [#2035] ### Removed * re-export of `futures_channel::oneshot::Canceled` is removed from `error` mod. [#1994] @@ -10,6 +11,7 @@ [#1981]: https://github.com/actix/actix-web/pull/1981 [#1994]: https://github.com/actix/actix-web/pull/1994 +[#2035]: https://github.com/actix/actix-web/pull/2035 ## 3.0.0-beta.3 - 2021-02-10 diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 96967e18a..d7915ddf9 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -103,6 +103,10 @@ version = "0.10.9" package = "openssl" features = ["vendored"] +[[example]] +name = "ws" +required-features = ["rustls"] + [[bench]] name = "write-camel-case" harness = false diff --git a/actix-http/examples/ws.rs b/actix-http/examples/ws.rs new file mode 100644 index 000000000..4e03aa8ab --- /dev/null +++ b/actix-http/examples/ws.rs @@ -0,0 +1,107 @@ +//! Sets up a WebSocket server over TCP and TLS. +//! Sends a heartbeat message every 4 seconds but does not respond to any incoming frames. + +extern crate tls_rustls as rustls; + +use std::{ + env, io, + pin::Pin, + task::{Context, Poll}, + time::Duration, +}; + +use actix_codec::Encoder; +use actix_http::{error::Error, ws, HttpService, Request, Response}; +use actix_rt::time::{interval, Interval}; +use actix_server::Server; +use bytes::{Bytes, BytesMut}; +use bytestring::ByteString; +use futures_core::{ready, Stream}; + +#[actix_rt::main] +async fn main() -> io::Result<()> { + env::set_var("RUST_LOG", "actix=info,h2_ws=info"); + env_logger::init(); + + Server::build() + .bind("tcp", ("127.0.0.1", 8080), || { + HttpService::build().h1(handler).tcp() + })? + .bind("tls", ("127.0.0.1", 8443), || { + HttpService::build().finish(handler).rustls(tls_config()) + })? + .run() + .await +} + +async fn handler(req: Request) -> Result { + log::info!("handshaking"); + let mut res = ws::handshake(req.head())?; + + // handshake will always fail under HTTP/2 + + log::info!("responding"); + Ok(res.streaming(Heartbeat::new(ws::Codec::new()))) +} + +struct Heartbeat { + codec: ws::Codec, + interval: Interval, +} + +impl Heartbeat { + fn new(codec: ws::Codec) -> Self { + Self { + codec, + interval: interval(Duration::from_secs(4)), + } + } +} + +impl Stream for Heartbeat { + type Item = Result; + + fn poll_next( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + ) -> Poll> { + log::trace!("poll"); + + ready!(self.as_mut().interval.poll_tick(cx)); + + let mut buffer = BytesMut::new(); + + self.as_mut() + .codec + .encode( + ws::Message::Text(ByteString::from_static("hello world")), + &mut buffer, + ) + .unwrap(); + + Poll::Ready(Some(Ok(buffer.freeze()))) + } +} + +fn tls_config() -> rustls::ServerConfig { + use std::io::BufReader; + + use rustls::{ + internal::pemfile::{certs, pkcs8_private_keys}, + NoClientAuth, ServerConfig, + }; + + let cert = rcgen::generate_simple_self_signed(vec!["localhost".to_owned()]).unwrap(); + let cert_file = cert.serialize_pem().unwrap(); + let key_file = cert.serialize_private_key_pem(); + + let mut config = ServerConfig::new(NoClientAuth::new()); + let cert_file = &mut BufReader::new(cert_file.as_bytes()); + let key_file = &mut BufReader::new(key_file.as_bytes()); + + let cert_chain = certs(cert_file).unwrap(); + let mut keys = pkcs8_private_keys(key_file).unwrap(); + config.set_single_cert(cert_chain, keys.remove(0)).unwrap(); + + config +} diff --git a/actix-http/src/ws/mod.rs b/actix-http/src/ws/mod.rs index 0490163d5..cec73db96 100644 --- a/actix-http/src/ws/mod.rs +++ b/actix-http/src/ws/mod.rs @@ -1,4 +1,4 @@ -//! WebSocket protocol. +//! WebSocket protocol implementation. //! //! To setup a WebSocket, first perform the WebSocket handshake then on success convert `Payload` into a //! `WsStream` stream and then use `WsWriter` to communicate with the peer. @@ -8,9 +8,12 @@ use std::io; use derive_more::{Display, Error, From}; use http::{header, Method, StatusCode}; -use crate::error::ResponseError; -use crate::message::RequestHead; -use crate::response::{Response, ResponseBuilder}; +use crate::{ + error::ResponseError, + header::HeaderValue, + message::RequestHead, + response::{Response, ResponseBuilder}, +}; mod codec; mod dispatcher; @@ -89,7 +92,7 @@ pub enum HandshakeError { NoVersionHeader, /// Unsupported WebSocket version. - #[display(fmt = "Unsupported version.")] + #[display(fmt = "Unsupported WebSocket version.")] UnsupportedVersion, /// WebSocket key is not set or wrong. @@ -105,19 +108,19 @@ impl ResponseError for HandshakeError { .finish(), HandshakeError::NoWebsocketUpgrade => Response::BadRequest() - .reason("No WebSocket UPGRADE header found") + .reason("No WebSocket Upgrade header found") .finish(), HandshakeError::NoConnectionUpgrade => Response::BadRequest() - .reason("No CONNECTION upgrade") + .reason("No Connection upgrade") .finish(), HandshakeError::NoVersionHeader => Response::BadRequest() - .reason("Websocket version header is required") + .reason("WebSocket version header is required") .finish(), HandshakeError::UnsupportedVersion => Response::BadRequest() - .reason("Unsupported version") + .reason("Unsupported WebSocket version") .finish(), HandshakeError::BadWebsocketKey => { @@ -193,7 +196,11 @@ pub fn handshake_response(req: &RequestHead) -> ResponseBuilder { Response::build(StatusCode::SWITCHING_PROTOCOLS) .upgrade("websocket") .insert_header((header::TRANSFER_ENCODING, "chunked")) - .insert_header((header::SEC_WEBSOCKET_ACCEPT, key)) + .insert_header(( + header::SEC_WEBSOCKET_ACCEPT, + // key is known to be header value safe ascii + HeaderValue::from_bytes(&key).unwrap(), + )) .take() } diff --git a/actix-http/src/ws/proto.rs b/actix-http/src/ws/proto.rs index 1e8bf7af3..fdcde5eac 100644 --- a/actix-http/src/ws/proto.rs +++ b/actix-http/src/ws/proto.rs @@ -1,5 +1,7 @@ -use std::convert::{From, Into}; -use std::fmt; +use std::{ + convert::{From, Into}, + fmt, +}; /// Operation codes as part of RFC6455. #[derive(Debug, Eq, PartialEq, Clone, Copy)] @@ -28,8 +30,9 @@ pub enum OpCode { impl fmt::Display for OpCode { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - use self::OpCode::*; - match *self { + use OpCode::*; + + match self { Continue => write!(f, "CONTINUE"), Text => write!(f, "TEXT"), Binary => write!(f, "BINARY"), @@ -44,6 +47,7 @@ impl fmt::Display for OpCode { impl From for u8 { fn from(op: OpCode) -> u8 { use self::OpCode::*; + match op { Continue => 0, Text => 1, @@ -62,6 +66,7 @@ impl From for u8 { impl From for OpCode { fn from(byte: u8) -> OpCode { use self::OpCode::*; + match byte { 0 => Continue, 1 => Text, @@ -77,63 +82,66 @@ impl From for OpCode { /// Status code used to indicate why an endpoint is closing the WebSocket connection. #[derive(Debug, Eq, PartialEq, Clone, Copy)] pub enum CloseCode { - /// Indicates a normal closure, meaning that the purpose for - /// which the connection was established has been fulfilled. + /// Indicates a normal closure, meaning that the purpose for which the connection was + /// established has been fulfilled. Normal, - /// Indicates that an endpoint is "going away", such as a server - /// going down or a browser having navigated away from a page. + + /// Indicates that an endpoint is "going away", such as a server going down or a browser having + /// navigated away from a page. Away, - /// Indicates that an endpoint is terminating the connection due - /// to a protocol error. + + /// Indicates that an endpoint is terminating the connection due to a protocol error. Protocol, - /// Indicates that an endpoint is terminating the connection - /// because it has received a type of data it cannot accept (e.g., an - /// endpoint that understands only text data MAY send this if it + + /// Indicates that an endpoint is terminating the connection because it has received a type of + /// data it cannot accept (e.g., an endpoint that understands only text data MAY send this if it /// receives a binary message). Unsupported, - /// Indicates an abnormal closure. If the abnormal closure was due to an - /// error, this close code will not be used. Instead, the `on_error` method - /// of the handler will be called with the error. However, if the connection - /// is simply dropped, without an error, this close code will be sent to the - /// handler. + + /// Indicates an abnormal closure. If the abnormal closure was due to an error, this close code + /// will not be used. Instead, the `on_error` method of the handler will be called with + /// the error. However, if the connection is simply dropped, without an error, this close code + /// will be sent to the handler. Abnormal, - /// Indicates that an endpoint is terminating the connection - /// because it has received data within a message that was not - /// consistent with the type of the message (e.g., non-UTF-8 \[RFC3629\] + + /// Indicates that an endpoint is terminating the connection because it has received data within + /// a message that was not consistent with the type of the message (e.g., non-UTF-8 \[RFC3629\] /// data within a text message). Invalid, - /// Indicates that an endpoint is terminating the connection - /// because it has received a message that violates its policy. This - /// is a generic status code that can be returned when there is no - /// other more suitable status code (e.g., Unsupported or Size) or if there - /// is a need to hide specific details about the policy. + + /// Indicates that an endpoint is terminating the connection because it has received a message + /// that violates its policy. This is a generic status code that can be returned when there is + /// no other more suitable status code (e.g., Unsupported or Size) or if there is a need to hide + /// specific details about the policy. Policy, - /// Indicates that an endpoint is terminating the connection - /// because it has received a message that is too big for it to - /// process. + + /// Indicates that an endpoint is terminating the connection because it has received a message + /// that is too big for it to process. Size, - /// Indicates that an endpoint (client) is terminating the - /// connection because it has expected the server to negotiate one or - /// more extension, but the server didn't return them in the response - /// message of the WebSocket handshake. The list of extensions that - /// are needed should be given as the reason for closing. - /// Note that this status code is not used by the server, because it - /// can fail the WebSocket handshake instead. + + /// Indicates that an endpoint (client) is terminating the connection because it has expected + /// the server to negotiate one or more extension, but the server didn't return them in the + /// response message of the WebSocket handshake. The list of extensions that are needed should + /// be given as the reason for closing. Note that this status code is not used by the server, + /// because it can fail the WebSocket handshake instead. Extension, - /// Indicates that a server is terminating the connection because - /// it encountered an unexpected condition that prevented it from - /// fulfilling the request. + + /// Indicates that a server is terminating the connection because it encountered an unexpected + /// condition that prevented it from fulfilling the request. Error, - /// Indicates that the server is restarting. A client may choose to - /// reconnect, and if it does, it should use a randomized delay of 5-30 - /// seconds between attempts. + + /// Indicates that the server is restarting. A client may choose to reconnect, and if it does, + /// it should use a randomized delay of 5-30 seconds between attempts. Restart, - /// Indicates that the server is overloaded and the client should either - /// connect to a different IP (when multiple targets exist), or - /// reconnect to the same IP when a user has performed an action. + + /// Indicates that the server is overloaded and the client should either connect to a different + /// IP (when multiple targets exist), or reconnect to the same IP when a user has performed + /// an action. Again, + #[doc(hidden)] Tls, + #[doc(hidden)] Other(u16), } @@ -141,6 +149,7 @@ pub enum CloseCode { impl From for u16 { fn from(code: CloseCode) -> u16 { use self::CloseCode::*; + match code { Normal => 1000, Away => 1001, @@ -163,6 +172,7 @@ impl From for u16 { impl From for CloseCode { fn from(code: u16) -> CloseCode { use self::CloseCode::*; + match code { 1000 => Normal, 1001 => Away, @@ -210,17 +220,29 @@ impl> From<(CloseCode, T)> for CloseReason { } } -static WS_GUID: &str = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; +/// The WebSocket GUID as stated in the spec. See https://tools.ietf.org/html/rfc6455#section-1.3. +static WS_GUID: &[u8] = b"258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; -// TODO: hash is always same size, we don't need String -pub fn hash_key(key: &[u8]) -> String { - use sha1::Digest; - let mut hasher = sha1::Sha1::new(); +/// Hashes the `Sec-WebSocket-Key` header according to the WebSocket spec. +/// +/// Result is a Base64 encoded byte array. `base64(sha1(input))` is always 28 bytes. +pub fn hash_key(key: &[u8]) -> [u8; 28] { + let hash = { + use sha1::Digest as _; - hasher.update(key); - hasher.update(WS_GUID.as_bytes()); + let mut hasher = sha1::Sha1::new(); - base64::encode(&hasher.finalize()) + hasher.update(key); + hasher.update(WS_GUID); + + hasher.finalize() + }; + + let mut hash_b64 = [0; 28]; + let n = base64::encode_config_slice(&hash, base64::STANDARD, &mut hash_b64); + assert_eq!(n, 28); + + hash_b64 } #[cfg(test)] @@ -288,11 +310,11 @@ mod test { #[test] fn test_hash_key() { let hash = hash_key(b"hello actix-web"); - assert_eq!(&hash, "cR1dlyUUJKp0s/Bel25u5TgvC3E="); + assert_eq!(&hash, b"cR1dlyUUJKp0s/Bel25u5TgvC3E="); } #[test] - fn closecode_from_u16() { + fn close_code_from_u16() { assert_eq!(CloseCode::from(1000u16), CloseCode::Normal); assert_eq!(CloseCode::from(1001u16), CloseCode::Away); assert_eq!(CloseCode::from(1002u16), CloseCode::Protocol); @@ -310,7 +332,7 @@ mod test { } #[test] - fn closecode_into_u16() { + fn close_code_into_u16() { assert_eq!(1000u16, Into::::into(CloseCode::Normal)); assert_eq!(1001u16, Into::::into(CloseCode::Away)); assert_eq!(1002u16, Into::::into(CloseCode::Protocol)); diff --git a/actix-web-actors/src/ws.rs b/actix-web-actors/src/ws.rs index 1ab4cfce5..de2802d21 100644 --- a/actix-web-actors/src/ws.rs +++ b/actix-web-actors/src/ws.rs @@ -15,10 +15,13 @@ use actix::{ SpawnHandle, }; use actix_codec::{Decoder, Encoder}; -use actix_http::ws::{hash_key, Codec}; pub use actix_http::ws::{ CloseCode, CloseReason, Frame, HandshakeError, Message, ProtocolError, }; +use actix_http::{ + http::HeaderValue, + ws::{hash_key, Codec}, +}; use actix_web::dev::HttpResponseBuilder; use actix_web::error::{Error, PayloadError}; use actix_web::http::{header, Method, StatusCode}; @@ -162,7 +165,11 @@ pub fn handshake_with_protocols( let mut response = HttpResponse::build(StatusCode::SWITCHING_PROTOCOLS) .upgrade("websocket") - .insert_header((header::SEC_WEBSOCKET_ACCEPT, key)) + .insert_header(( + header::SEC_WEBSOCKET_ACCEPT, + // key is known to be header value safe ascii + HeaderValue::from_bytes(&key).unwrap(), + )) .take(); if let Some(protocol) = protocol { diff --git a/awc/src/error.rs b/awc/src/error.rs index f86224e62..b715f6213 100644 --- a/awc/src/error.rs +++ b/awc/src/error.rs @@ -18,24 +18,31 @@ pub enum WsClientError { /// Invalid response status #[display(fmt = "Invalid response status")] InvalidResponseStatus(StatusCode), + /// Invalid upgrade header #[display(fmt = "Invalid upgrade header")] InvalidUpgradeHeader, + /// Invalid connection header #[display(fmt = "Invalid connection header")] InvalidConnectionHeader(HeaderValue), - /// Missing CONNECTION header - #[display(fmt = "Missing CONNECTION header")] + + /// Missing Connection header + #[display(fmt = "Missing Connection header")] MissingConnectionHeader, - /// Missing SEC-WEBSOCKET-ACCEPT header - #[display(fmt = "Missing SEC-WEBSOCKET-ACCEPT header")] + + /// Missing Sec-Websocket-Accept header + #[display(fmt = "Missing Sec-Websocket-Accept header")] MissingWebSocketAcceptHeader, + /// Invalid challenge response #[display(fmt = "Invalid challenge response")] - InvalidChallengeResponse(String, HeaderValue), + InvalidChallengeResponse([u8; 28], HeaderValue), + /// Protocol error #[display(fmt = "{}", _0)] Protocol(WsProtocolError), + /// Send request error #[display(fmt = "{}", _0)] SendRequest(SendRequestError), diff --git a/awc/src/ws.rs b/awc/src/ws.rs index 5f4570963..1aa426ac7 100644 --- a/awc/src/ws.rs +++ b/awc/src/ws.rs @@ -381,12 +381,14 @@ impl WebsocketsRequest { if let Some(hdr_key) = head.headers.get(&header::SEC_WEBSOCKET_ACCEPT) { let encoded = ws::hash_key(key.as_ref()); - if hdr_key.as_bytes() != encoded.as_bytes() { + + if hdr_key.as_bytes() != &encoded { log::trace!( - "Invalid challenge response: expected: {} received: {:?}", - encoded, + "Invalid challenge response: expected: {:?} received: {:?}", + &encoded, key ); + return Err(WsClientError::InvalidChallengeResponse( encoded, hdr_key.clone(), From abc7fd374b7c603a01033ed0a6dc2f20987bc9fb Mon Sep 17 00:00:00 2001 From: "Daniel T. Rodrigues" <45438149+danitrod@users.noreply.github.com> Date: Sun, 28 Feb 2021 18:41:07 -0300 Subject: [PATCH 03/34] update example links (#2036) Co-authored-by: Rob Ede --- actix-files/README.md | 2 +- awc/README.md | 3 ++- examples/on_connect.rs | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/actix-files/README.md b/actix-files/README.md index 463f20224..a4f0445aa 100644 --- a/actix-files/README.md +++ b/actix-files/README.md @@ -14,6 +14,6 @@ ## Documentation & Resources - [API Documentation](https://docs.rs/actix-files/) -- [Example Project](https://github.com/actix/examples/tree/master/static_index) +- [Example Project](https://github.com/actix/examples/tree/master/basics/static_index) - [Chat on Gitter](https://gitter.im/actix/actix-web) - Minimum supported Rust version: 1.46 or later diff --git a/awc/README.md b/awc/README.md index 043ae6a41..1f6e3b8fb 100644 --- a/awc/README.md +++ b/awc/README.md @@ -11,11 +11,12 @@ ## Documentation & Resources - [API Documentation](https://docs.rs/awc) -- [Example Project](https://github.com/actix/examples/tree/HEAD/awc_https) +- [Example Project](https://github.com/actix/examples/tree/HEAD/security/awc_https) - [Chat on Gitter](https://gitter.im/actix/actix-web) - Minimum Supported Rust Version (MSRV): 1.46.0 ## Example + ```rust use actix_rt::System; use awc::Client; diff --git a/examples/on_connect.rs b/examples/on_connect.rs index ba5a18f3f..24ac86c6b 100644 --- a/examples/on_connect.rs +++ b/examples/on_connect.rs @@ -2,7 +2,7 @@ //! properties and pass them to a handler through request-local data. //! //! For an example of extracting a client TLS certificate, see: -//! +//! use std::{any::Any, io, net::SocketAddr}; From fb019f15b4362c9c1d6b1e89649eec9bb0e57bb2 Mon Sep 17 00:00:00 2001 From: Florian Dreschner Date: Mon, 1 Mar 2021 00:01:59 +0100 Subject: [PATCH 04/34] test(files): Fix test and remove outdated case (#2037) Co-authored-by: Rob Ede --- actix-files/src/lib.rs | 89 ++++++++++++------------------------------ 1 file changed, 26 insertions(+), 63 deletions(-) diff --git a/actix-files/src/lib.rs b/actix-files/src/lib.rs index 04dd9f07f..3c34c0403 100644 --- a/actix-files/src/lib.rs +++ b/actix-files/src/lib.rs @@ -662,8 +662,12 @@ mod tests { #[actix_rt::test] async fn test_static_files_bad_directory() { - let _st: Files = Files::new("/", "missing"); - let _st: Files = Files::new("/", "Cargo.toml"); + let service = Files::new("/", "./missing").new_service(()).await.unwrap(); + + let req = TestRequest::with_uri("/").to_srv_request(); + let resp = test::call_service(&service, req).await; + + assert_eq!(resp.status(), StatusCode::NOT_FOUND); } #[actix_rt::test] @@ -676,75 +680,34 @@ mod tests { .await .unwrap(); let req = TestRequest::with_uri("/missing").to_srv_request(); - let resp = test::call_service(&st, req).await; + assert_eq!(resp.status(), StatusCode::OK); let bytes = test::read_body(resp).await; assert_eq!(bytes, web::Bytes::from_static(b"default content")); } - // #[actix_rt::test] - // async fn test_serve_index() { - // let st = Files::new(".").index_file("test.binary"); - // let req = TestRequest::default().uri("/tests").finish(); + #[actix_rt::test] + async fn test_serve_index_nested() { + let service = Files::new(".", ".") + .index_file("lib.rs") + .new_service(()) + .await + .unwrap(); - // let resp = st.handle(&req).respond_to(&req).unwrap(); - // let resp = resp.as_msg(); - // assert_eq!(resp.status(), StatusCode::OK); - // assert_eq!( - // resp.headers() - // .get(header::CONTENT_TYPE) - // .expect("content type"), - // "application/octet-stream" - // ); - // assert_eq!( - // resp.headers() - // .get(header::CONTENT_DISPOSITION) - // .expect("content disposition"), - // "attachment; filename=\"test.binary\"" - // ); + let req = TestRequest::default().uri("/src").to_srv_request(); + let resp = test::call_service(&service, req).await; - // let req = TestRequest::default().uri("/tests/").finish(); - // let resp = st.handle(&req).respond_to(&req).unwrap(); - // let resp = resp.as_msg(); - // assert_eq!(resp.status(), StatusCode::OK); - // assert_eq!( - // resp.headers().get(header::CONTENT_TYPE).unwrap(), - // "application/octet-stream" - // ); - // assert_eq!( - // resp.headers().get(header::CONTENT_DISPOSITION).unwrap(), - // "attachment; filename=\"test.binary\"" - // ); - - // // nonexistent index file - // let req = TestRequest::default().uri("/tests/unknown").finish(); - // let resp = st.handle(&req).respond_to(&req).unwrap(); - // let resp = resp.as_msg(); - // assert_eq!(resp.status(), StatusCode::NOT_FOUND); - - // let req = TestRequest::default().uri("/tests/unknown/").finish(); - // let resp = st.handle(&req).respond_to(&req).unwrap(); - // let resp = resp.as_msg(); - // assert_eq!(resp.status(), StatusCode::NOT_FOUND); - // } - - // #[actix_rt::test] - // async fn test_serve_index_nested() { - // let st = Files::new(".").index_file("mod.rs"); - // let req = TestRequest::default().uri("/src/client").finish(); - // let resp = st.handle(&req).respond_to(&req).unwrap(); - // let resp = resp.as_msg(); - // assert_eq!(resp.status(), StatusCode::OK); - // assert_eq!( - // resp.headers().get(header::CONTENT_TYPE).unwrap(), - // "text/x-rust" - // ); - // assert_eq!( - // resp.headers().get(header::CONTENT_DISPOSITION).unwrap(), - // "inline; filename=\"mod.rs\"" - // ); - // } + assert_eq!(resp.status(), StatusCode::OK); + assert_eq!( + resp.headers().get(header::CONTENT_TYPE).unwrap(), + "text/x-rust" + ); + assert_eq!( + resp.headers().get(header::CONTENT_DISPOSITION).unwrap(), + "inline; filename=\"lib.rs\"" + ); + } #[actix_rt::test] async fn integration_serve_index() { From 01958247940264639758ef88cca4897ea334bbb3 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Tue, 2 Mar 2021 13:18:16 +0000 Subject: [PATCH 05/34] Update codecov.yml --- codecov.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/codecov.yml b/codecov.yml index e6bc40203..e45672bfc 100644 --- a/codecov.yml +++ b/codecov.yml @@ -4,10 +4,10 @@ coverage: status: project: default: - threshold: 10% # make CI green + threshold: 100% # make CI green patch: default: - threshold: 10% # make CI green + threshold: 100% # make CI green ignore: # ignore code coverage on following paths - "**/tests" From 14b249b804d371007a0ab8ed45d8e4a47fe49818 Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Thu, 4 Mar 2021 03:39:29 -0800 Subject: [PATCH 06/34] update to actix-0.11.0-beta.3 for actix-web-actors (#2042) --- actix-web-actors/Cargo.toml | 2 +- actix-web-actors/src/context.rs | 4 ++-- actix-web-actors/src/ws.rs | 5 ++--- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/actix-web-actors/Cargo.toml b/actix-web-actors/Cargo.toml index 58508071a..e0c4fc073 100644 --- a/actix-web-actors/Cargo.toml +++ b/actix-web-actors/Cargo.toml @@ -16,7 +16,7 @@ name = "actix_web_actors" path = "src/lib.rs" [dependencies] -actix = { version = "0.11.0-beta.2", default-features = false } +actix = { version = "0.11.0-beta.3", default-features = false } actix-codec = "0.4.0-beta.1" actix-http = "3.0.0-beta.3" actix-web = { version = "4.0.0-beta.3", default-features = false } diff --git a/actix-web-actors/src/context.rs b/actix-web-actors/src/context.rs index bd6635cba..d7459aea4 100644 --- a/actix-web-actors/src/context.rs +++ b/actix-web-actors/src/context.rs @@ -44,7 +44,7 @@ where #[inline] fn spawn(&mut self, fut: F) -> SpawnHandle where - F: ActorFuture + 'static, + F: ActorFuture + 'static, { self.inner.spawn(fut) } @@ -52,7 +52,7 @@ where #[inline] fn wait(&mut self, fut: F) where - F: ActorFuture + 'static, + F: ActorFuture + 'static, { self.inner.wait(fut) } diff --git a/actix-web-actors/src/ws.rs b/actix-web-actors/src/ws.rs index de2802d21..77d7041f0 100644 --- a/actix-web-actors/src/ws.rs +++ b/actix-web-actors/src/ws.rs @@ -211,14 +211,14 @@ where { fn spawn(&mut self, fut: F) -> SpawnHandle where - F: ActorFuture + 'static, + F: ActorFuture + 'static, { self.inner.spawn(fut) } fn wait(&mut self, fut: F) where - F: ActorFuture + 'static, + F: ActorFuture + 'static, { self.inner.wait(fut) } @@ -495,7 +495,6 @@ where if !*this.closed { loop { - this = self.as_mut().project(); match Pin::new(&mut this.stream).poll_next(cx) { Poll::Ready(Some(Ok(chunk))) => { this.buf.extend_from_slice(&chunk[..]); From fc6f974617a606077ab1d99b8ad06b9b75492682 Mon Sep 17 00:00:00 2001 From: Richard Chien Date: Thu, 4 Mar 2021 20:38:47 +0800 Subject: [PATCH 07/34] Add "name" attribute to route macro (#1934) --- actix-web-codegen/CHANGES.md | 2 ++ actix-web-codegen/src/lib.rs | 2 ++ actix-web-codegen/src/route.rs | 19 +++++++++++++++++-- actix-web-codegen/tests/test_macro.rs | 12 ++++++++++++ 4 files changed, 33 insertions(+), 2 deletions(-) diff --git a/actix-web-codegen/CHANGES.md b/actix-web-codegen/CHANGES.md index f1dbf8e5f..7c6543d49 100644 --- a/actix-web-codegen/CHANGES.md +++ b/actix-web-codegen/CHANGES.md @@ -2,8 +2,10 @@ ## Unreleased - 2021-xx-xx * Preserve doc comments when using route macros. [#2022] +* Add `name` attribute to `route` macro. [#1934] [#2022]: https://github.com/actix/actix-web/pull/2022 +[#1934]: https://github.com/actix/actix-web/pull/1934 ## 0.5.0-beta.1 - 2021-02-10 diff --git a/actix-web-codegen/src/lib.rs b/actix-web-codegen/src/lib.rs index 670d82ce9..48414d491 100644 --- a/actix-web-codegen/src/lib.rs +++ b/actix-web-codegen/src/lib.rs @@ -71,6 +71,7 @@ mod route; /// /// # Attributes /// - `"path"` - Raw literal string with path for which to register handler. +/// - `name="resource_name"` - Specifies resource name for the handler. If not set, the function name of handler is used. /// - `method="HTTP_METHOD"` - Registers HTTP method to provide guard for. Upper-case string, "GET", "POST" for example. /// - `guard="function_name"` - Registers function as guard using `actix_web::guard::fn_guard` /// - `wrap="Middleware"` - Registers a resource middleware. @@ -116,6 +117,7 @@ Creates route handler with `actix_web::guard::", stringify!($variant), "`. # Attributes - `"path"` - Raw literal string with path for which to register handler. +- `name="resource_name"` - Specifies resource name for the handler. If not set, the function name of handler is used. - `guard="function_name"` - Registers function as guard using `actix_web::guard::fn_guard`. - `wrap="Middleware"` - Registers a resource middleware. diff --git a/actix-web-codegen/src/route.rs b/actix-web-codegen/src/route.rs index e3c5f0368..ac0b7cea1 100644 --- a/actix-web-codegen/src/route.rs +++ b/actix-web-codegen/src/route.rs @@ -78,6 +78,7 @@ impl TryFrom<&syn::LitStr> for MethodType { struct Args { path: syn::LitStr, + resource_name: Option, guards: Vec, wrappers: Vec, methods: HashSet, @@ -86,6 +87,7 @@ struct Args { impl Args { fn new(args: AttributeArgs, method: Option) -> syn::Result { let mut path = None; + let mut resource_name = None; let mut guards = Vec::new(); let mut wrappers = Vec::new(); let mut methods = HashSet::new(); @@ -109,7 +111,16 @@ impl Args { } }, NestedMeta::Meta(syn::Meta::NameValue(nv)) => { - if nv.path.is_ident("guard") { + if nv.path.is_ident("name") { + if let syn::Lit::Str(lit) = nv.lit { + resource_name = Some(lit); + } else { + return Err(syn::Error::new_spanned( + nv.lit, + "Attribute name expects literal string!", + )); + } + } else if nv.path.is_ident("guard") { if let syn::Lit::Str(lit) = nv.lit { guards.push(Ident::new(&lit.value(), Span::call_site())); } else { @@ -164,6 +175,7 @@ impl Args { } Ok(Args { path: path.unwrap(), + resource_name, guards, wrappers, methods, @@ -276,6 +288,7 @@ impl ToTokens for Route { args: Args { path, + resource_name, guards, wrappers, methods, @@ -283,7 +296,9 @@ impl ToTokens for Route { resource_type, doc_attributes, } = self; - let resource_name = name.to_string(); + let resource_name = resource_name + .as_ref() + .map_or_else(|| name.to_string(), |n| n.value()); let method_guards = { let mut others = methods.iter(); // unwrapping since length is checked to be at least one diff --git a/actix-web-codegen/tests/test_macro.rs b/actix-web-codegen/tests/test_macro.rs index 34cdad649..0cbb64ba5 100644 --- a/actix-web-codegen/tests/test_macro.rs +++ b/actix-web-codegen/tests/test_macro.rs @@ -83,6 +83,13 @@ async fn route_test() -> impl Responder { HttpResponse::Ok() } +#[get("/custom_resource_name", name = "custom")] +async fn custom_resource_name_test<'a>(req: actix_web::HttpRequest) -> impl Responder { + assert!(req.url_for_static("custom").is_ok()); + assert!(req.url_for_static("custom_resource_name_test").is_err()); + HttpResponse::Ok() +} + pub struct ChangeStatusCode; impl Transform for ChangeStatusCode @@ -174,6 +181,7 @@ async fn test_body() { .service(patch_test) .service(test_handler) .service(route_test) + .service(custom_resource_name_test) }); let request = srv.request(http::Method::GET, srv.url("/test")); let response = request.send().await.unwrap(); @@ -228,6 +236,10 @@ async fn test_body() { let request = srv.request(http::Method::PATCH, srv.url("/multi")); let response = request.send().await.unwrap(); assert!(!response.status().is_success()); + + let request = srv.request(http::Method::GET, srv.url("/custom_resource_name")); + let response = request.send().await.unwrap(); + assert!(response.status().is_success()); } #[actix_rt::test] From c1c4400c4a235d6d8bd14217aac61161d98b9699 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Thu, 4 Mar 2021 13:27:54 +0000 Subject: [PATCH 08/34] fix h2 tests (#2034) --- actix-http/Cargo.toml | 2 +- actix-http/tests/test_openssl.rs | 32 +++++++++++++++++++------------- actix-http/tests/test_rustls.rs | 30 ++++++++++++++++++------------ 3 files changed, 38 insertions(+), 26 deletions(-) diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index d7915ddf9..c79ad11b2 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -61,7 +61,7 @@ derive_more = "0.99.5" encoding_rs = "0.8" futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] } futures-util = { version = "0.3.7", default-features = false, features = ["alloc", "sink"] } -h2 = "=0.3.0" +h2 = "0.3.1" http = "0.2.2" httparse = "1.3" itoa = "0.4" diff --git a/actix-http/tests/test_openssl.rs b/actix-http/tests/test_openssl.rs index f44968baa..d5ec645a4 100644 --- a/actix-http/tests/test_openssl.rs +++ b/actix-http/tests/test_openssl.rs @@ -123,16 +123,14 @@ async fn test_h2_content_length() { let srv = test_server(move || { HttpService::build() .h2(|req: Request| { - let indx: usize = req.uri().path()[1..].parse().unwrap(); + let idx: usize = req.uri().path()[1..].parse().unwrap(); let statuses = [ - StatusCode::NO_CONTENT, StatusCode::CONTINUE, - StatusCode::SWITCHING_PROTOCOLS, - StatusCode::PROCESSING, + StatusCode::NO_CONTENT, StatusCode::OK, StatusCode::NOT_FOUND, ]; - ok::<_, ()>(Response::new(statuses[indx])) + ok::<_, ()>(Response::new(statuses[idx])) }) .openssl(tls_config()) .map_err(|_| ()) @@ -143,21 +141,29 @@ async fn test_h2_content_length() { let value = HeaderValue::from_static("0"); { - for i in 0..4 { + for &i in &[0] { + let req = srv + .request(Method::HEAD, srv.surl(&format!("/{}", i))) + .send(); + let _response = req.await.expect_err("should timeout on recv 1xx frame"); + // assert_eq!(response.headers().get(&header), None); + + let req = srv + .request(Method::GET, srv.surl(&format!("/{}", i))) + .send(); + let _response = req.await.expect_err("should timeout on recv 1xx frame"); + // assert_eq!(response.headers().get(&header), None); + } + + for &i in &[1] { let req = srv .request(Method::GET, srv.surl(&format!("/{}", i))) .send(); let response = req.await.unwrap(); assert_eq!(response.headers().get(&header), None); - - let req = srv - .request(Method::HEAD, srv.surl(&format!("/{}", i))) - .send(); - let response = req.await.unwrap(); - assert_eq!(response.headers().get(&header), None); } - for i in 4..6 { + for &i in &[2, 3] { let req = srv .request(Method::GET, srv.surl(&format!("/{}", i))) .send(); diff --git a/actix-http/tests/test_rustls.rs b/actix-http/tests/test_rustls.rs index a36400910..81edb5c18 100644 --- a/actix-http/tests/test_rustls.rs +++ b/actix-http/tests/test_rustls.rs @@ -17,7 +17,6 @@ use rustls::{ NoClientAuth, ServerConfig as RustlsServerConfig, }; -use std::fs::File; use std::io::{self, BufReader}; async fn load_body(mut stream: S) -> Result @@ -139,10 +138,8 @@ async fn test_h2_content_length() { .h2(|req: Request| { let indx: usize = req.uri().path()[1..].parse().unwrap(); let statuses = [ - StatusCode::NO_CONTENT, StatusCode::CONTINUE, - StatusCode::SWITCHING_PROTOCOLS, - StatusCode::PROCESSING, + StatusCode::NO_CONTENT, StatusCode::OK, StatusCode::NOT_FOUND, ]; @@ -154,22 +151,31 @@ async fn test_h2_content_length() { let header = HeaderName::from_static("content-length"); let value = HeaderValue::from_static("0"); + { - for i in 0..4 { + for &i in &[0] { + let req = srv + .request(Method::HEAD, srv.surl(&format!("/{}", i))) + .send(); + let _response = req.await.expect_err("should timeout on recv 1xx frame"); + // assert_eq!(response.headers().get(&header), None); + + let req = srv + .request(Method::GET, srv.surl(&format!("/{}", i))) + .send(); + let _response = req.await.expect_err("should timeout on recv 1xx frame"); + // assert_eq!(response.headers().get(&header), None); + } + + for &i in &[1] { let req = srv .request(Method::GET, srv.surl(&format!("/{}", i))) .send(); let response = req.await.unwrap(); assert_eq!(response.headers().get(&header), None); - - let req = srv - .request(Method::HEAD, srv.surl(&format!("/{}", i))) - .send(); - let response = req.await.unwrap(); - assert_eq!(response.headers().get(&header), None); } - for i in 4..6 { + for &i in &[2, 3] { let req = srv .request(Method::GET, srv.surl(&format!("/{}", i))) .send(); From 78384c3ff57f2fee1820cdb13a720cbf75de54e9 Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Thu, 4 Mar 2021 11:19:01 -0800 Subject: [PATCH 09/34] make actix_http::ws::Codec::new const (#2043) --- actix-http/src/ws/codec.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actix-http/src/ws/codec.rs b/actix-http/src/ws/codec.rs index 54d850854..8655216fa 100644 --- a/actix-http/src/ws/codec.rs +++ b/actix-http/src/ws/codec.rs @@ -80,7 +80,7 @@ bitflags! { impl Codec { /// Create new WebSocket frames decoder. - pub fn new() -> Codec { + pub const fn new() -> Codec { Codec { max_size: 65_536, flags: Flags::SERVER, From 880b863f958fdbead8f604fd935e928eb442120d Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Sun, 7 Mar 2021 10:33:16 -0800 Subject: [PATCH 10/34] fix h1 client for handling expect header request (#2049) --- actix-http/src/client/h1proto.rs | 83 ++++++++++++++++++++++++-------- actix-http/tests/test_client.rs | 61 ++++++++++++++++++++++- 2 files changed, 121 insertions(+), 23 deletions(-) diff --git a/actix-http/src/client/h1proto.rs b/actix-http/src/client/h1proto.rs index 082c4b8e2..d2db18cec 100644 --- a/actix-http/src/client/h1proto.rs +++ b/actix-http/src/client/h1proto.rs @@ -7,13 +7,15 @@ use actix_codec::{AsyncRead, AsyncWrite, Framed, ReadBuf}; use bytes::buf::BufMut; use bytes::{Bytes, BytesMut}; use futures_core::Stream; -use futures_util::future::poll_fn; -use futures_util::{SinkExt, StreamExt}; +use futures_util::{future::poll_fn, SinkExt, StreamExt}; use crate::error::PayloadError; use crate::h1; use crate::header::HeaderMap; -use crate::http::header::{IntoHeaderValue, HOST}; +use crate::http::{ + header::{IntoHeaderValue, EXPECT, HOST}, + StatusCode, +}; use crate::message::{RequestHeadType, ResponseHead}; use crate::payload::{Payload, PayloadStream}; @@ -66,33 +68,72 @@ where io: Some(io), }; - // create Framed and send request - let mut framed_inner = Framed::new(io, h1::ClientCodec::default()); - framed_inner.send((head, body.size()).into()).await?; + // create Framed and prepare sending request + let mut framed = Framed::new(io, h1::ClientCodec::default()); - // send request body - match body.size() { - BodySize::None | BodySize::Empty | BodySize::Sized(0) => {} - _ => send_body(body, Pin::new(&mut framed_inner)).await?, - }; + // Check EXPECT header and enable expect handle flag accordingly. + // + // RFC: https://tools.ietf.org/html/rfc7231#section-5.1.1 + let is_expect = if head.as_ref().headers.contains_key(EXPECT) { + match body.size() { + BodySize::None | BodySize::Empty | BodySize::Sized(0) => { + let pin_framed = Pin::new(&mut framed); - // read response and init read body - let res = Pin::new(&mut framed_inner).into_future().await; - let (head, framed) = if let (Some(result), framed) = res { - let item = result.map_err(SendRequestError::from)?; - (item, framed) + let force_close = !pin_framed.codec_ref().keepalive(); + release_connection(pin_framed, force_close); + + // TODO: use a new variant or a new type better describing error violate + // `Requirements for clients` session of above RFC + return Err(SendRequestError::Connect(ConnectError::Disconnected)); + } + _ => true, + } } else { - return Err(SendRequestError::from(ConnectError::Disconnected)); + false }; - match framed.codec_ref().message_type() { + framed.send((head, body.size()).into()).await?; + + let mut pin_framed = Pin::new(&mut framed); + + // special handle for EXPECT request. + let (do_send, mut res_head) = if is_expect { + let head = poll_fn(|cx| pin_framed.as_mut().poll_next(cx)) + .await + .ok_or(ConnectError::Disconnected)??; + + // return response head in case status code is not continue + // and current head would be used as final response head. + (head.status == StatusCode::CONTINUE, Some(head)) + } else { + (true, None) + }; + + if do_send { + // send request body + match body.size() { + BodySize::None | BodySize::Empty | BodySize::Sized(0) => {} + _ => send_body(body, pin_framed.as_mut()).await?, + }; + + // read response and init read body + let head = poll_fn(|cx| pin_framed.as_mut().poll_next(cx)) + .await + .ok_or(ConnectError::Disconnected)??; + + res_head = Some(head); + } + + let head = res_head.unwrap(); + + match pin_framed.codec_ref().message_type() { h1::MessageType::None => { - let force_close = !framed.codec_ref().keepalive(); - release_connection(framed, force_close); + let force_close = !pin_framed.codec_ref().keepalive(); + release_connection(pin_framed, force_close); Ok((head, Payload::None)) } _ => { - let pl: PayloadStream = PlStream::new(framed_inner).boxed_local(); + let pl: PayloadStream = Box::pin(PlStream::new(framed)); Ok((head, pl.into())) } } diff --git a/actix-http/tests/test_client.rs b/actix-http/tests/test_client.rs index 91b2412f4..a50f2404d 100644 --- a/actix-http/tests/test_client.rs +++ b/actix-http/tests/test_client.rs @@ -1,8 +1,13 @@ -use actix_http::{http, HttpService, Request, Response}; +use actix_http::{ + error, http, http::StatusCode, HttpMessage, HttpService, Request, Response, +}; use actix_http_test::test_server; use actix_service::ServiceFactoryExt; use bytes::Bytes; -use futures_util::future::{self, ok}; +use futures_util::{ + future::{self, ok}, + StreamExt, +}; const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ Hello World Hello World Hello World Hello World Hello World \ @@ -88,3 +93,55 @@ async fn test_with_query_parameter() { let response = request.send().await.unwrap(); assert!(response.status().is_success()); } + +#[actix_rt::test] +async fn test_h1_expect() { + let srv = test_server(move || { + HttpService::build() + .expect(|req: Request| async { + if req.headers().contains_key("AUTH") { + Ok(req) + } else { + Err(error::ErrorExpectationFailed("expect failed")) + } + }) + .h1(|req: Request| async move { + let (_, mut body) = req.into_parts(); + let mut buf = Vec::new(); + while let Some(Ok(chunk)) = body.next().await { + buf.extend_from_slice(&chunk); + } + let str = std::str::from_utf8(&buf).unwrap(); + assert_eq!(str, "expect body"); + + Ok::<_, ()>(Response::Ok().finish()) + }) + .tcp() + }) + .await; + + // test expect without payload. + let request = srv + .request(http::Method::GET, srv.url("/")) + .insert_header(("Expect", "100-continue")); + + let response = request.send().await; + assert!(response.is_err()); + + // test expect would fail to continue + let request = srv + .request(http::Method::GET, srv.url("/")) + .insert_header(("Expect", "100-continue")); + + let response = request.send_body("expect body").await.unwrap(); + assert_eq!(response.status(), StatusCode::EXPECTATION_FAILED); + + // test exepct would continue + let request = srv + .request(http::Method::GET, srv.url("/")) + .insert_header(("Expect", "100-continue")) + .insert_header(("AUTH", "996")); + + let response = request.send_body("expect body").await.unwrap(); + assert!(response.status().is_success()); +} From ca69b6577e0871d65721582cc452a010d6ed1fe4 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sun, 7 Mar 2021 19:29:02 +0000 Subject: [PATCH 11/34] use iota for more content-length insertions (#2050) --- actix-http/src/client/h2proto.rs | 18 +++++++++++------ actix-http/src/h1/encoder.rs | 5 ++--- actix-http/src/h2/dispatcher.rs | 25 ++++++++++++------------ actix-http/src/response.rs | 3 ++- awc/CHANGES.md | 3 +++ awc/Cargo.toml | 3 ++- awc/src/request.rs | 33 +++++++++++++------------------- awc/tests/test_client.rs | 4 ++-- 8 files changed, 49 insertions(+), 45 deletions(-) diff --git a/actix-http/src/client/h2proto.rs b/actix-http/src/client/h2proto.rs index 0deb5c014..7292972de 100644 --- a/actix-http/src/client/h2proto.rs +++ b/actix-http/src/client/h2proto.rs @@ -1,4 +1,3 @@ -use std::convert::TryFrom; use std::future::Future; use std::time; @@ -61,10 +60,14 @@ where BodySize::Empty => req .headers_mut() .insert(CONTENT_LENGTH, HeaderValue::from_static("0")), - BodySize::Sized(len) => req.headers_mut().insert( - CONTENT_LENGTH, - HeaderValue::try_from(format!("{}", len)).unwrap(), - ), + BodySize::Sized(len) => { + let mut buf = itoa::Buffer::new(); + + req.headers_mut().insert( + CONTENT_LENGTH, + HeaderValue::from_str(buf.format(len)).unwrap(), + ) + } }; // Extracting extra headers from RequestHeadType. HeaderMap::new() does not allocate. @@ -87,7 +90,10 @@ where // copy headers for (key, value) in headers { match *key { - CONNECTION | TRANSFER_ENCODING => continue, // http2 specific + // TODO: consider skipping other headers according to: + // https://tools.ietf.org/html/rfc7540#section-8.1.2.2 + // omit HTTP/1.x only headers + CONNECTION | TRANSFER_ENCODING => continue, CONTENT_LENGTH if skip_len => continue, // DATE => has_date = true, _ => {} diff --git a/actix-http/src/h1/encoder.rs b/actix-http/src/h1/encoder.rs index 97916e7db..4e9903284 100644 --- a/actix-http/src/h1/encoder.rs +++ b/actix-http/src/h1/encoder.rs @@ -632,10 +632,9 @@ mod tests { let mut res: Response<()> = Response::new(StatusCode::SWITCHING_PROTOCOLS).into_body::<()>(); + res.headers_mut().insert(DATE, HeaderValue::from_static("")); res.headers_mut() - .insert(DATE, HeaderValue::from_static(&"")); - res.headers_mut() - .insert(CONTENT_LENGTH, HeaderValue::from_static(&"0")); + .insert(CONTENT_LENGTH, HeaderValue::from_static("0")); let _ = res.encode_headers( &mut bytes, diff --git a/actix-http/src/h2/dispatcher.rs b/actix-http/src/h2/dispatcher.rs index 5ccd2a9d1..8e01afcb5 100644 --- a/actix-http/src/h2/dispatcher.rs +++ b/actix-http/src/h2/dispatcher.rs @@ -1,10 +1,5 @@ -use std::future::Future; -use std::marker::PhantomData; -use std::net; -use std::pin::Pin; -use std::rc::Rc; use std::task::{Context, Poll}; -use std::{cmp, convert::TryFrom}; +use std::{cmp, future::Future, marker::PhantomData, net, pin::Pin, rc::Rc}; use actix_codec::{AsyncRead, AsyncWrite}; use actix_rt::time::{Instant, Sleep}; @@ -125,7 +120,7 @@ where let pl = Payload::::H2(pl); let mut req = Request::with_payload(pl); - let head = &mut req.head_mut(); + let head = req.head_mut(); head.uri = parts.uri; head.method = parts.method; head.version = parts.version; @@ -203,16 +198,22 @@ where BodySize::Empty => res .headers_mut() .insert(CONTENT_LENGTH, HeaderValue::from_static("0")), - BodySize::Sized(len) => res.headers_mut().insert( - CONTENT_LENGTH, - HeaderValue::try_from(format!("{}", len)).unwrap(), - ), + BodySize::Sized(len) => { + let mut buf = itoa::Buffer::new(); + + res.headers_mut().insert( + CONTENT_LENGTH, + HeaderValue::from_str(buf.format(*len)).unwrap(), + ) + } }; // copy headers for (key, value) in head.headers.iter() { match *key { - // omit HTTP/1 only headers + // TODO: consider skipping other headers according to: + // https://tools.ietf.org/html/rfc7540#section-8.1.2.2 + // omit HTTP/1.x only headers CONNECTION | TRANSFER_ENCODING => continue, CONTENT_LENGTH if skip_len => continue, DATE => has_date = true, diff --git a/actix-http/src/response.rs b/actix-http/src/response.rs index f96f8f9b6..c5a4aec13 100644 --- a/actix-http/src/response.rs +++ b/actix-http/src/response.rs @@ -498,7 +498,8 @@ impl ResponseBuilder { /// Disable chunked transfer encoding for HTTP/1.1 streaming responses. #[inline] pub fn no_chunking(&mut self, len: u64) -> &mut Self { - self.insert_header((header::CONTENT_LENGTH, len)); + let mut buf = itoa::Buffer::new(); + self.insert_header((header::CONTENT_LENGTH, buf.format(len))); if let Some(parts) = parts(&mut self.head, &self.err) { parts.no_chunking(true); diff --git a/awc/CHANGES.md b/awc/CHANGES.md index 9fbc6d042..211db5a80 100644 --- a/awc/CHANGES.md +++ b/awc/CHANGES.md @@ -8,6 +8,7 @@ ### Changed * Feature `cookies` is now optional and enabled by default. [#1981] * `ClientBuilder::connector` method would take `actix_http::client::Connector` type. [#2008] +* Basic auth password now takes blank passwords as an empty string instead of Option. [#2050] ### Removed * `ClientBuilder::default` function [#2008] @@ -17,6 +18,8 @@ [#1981]: https://github.com/actix/actix-web/pull/1981 [#2008]: https://github.com/actix/actix-web/pull/2008 [#2024]: https://github.com/actix/actix-web/pull/2024 +[#2050]: https://github.com/actix/actix-web/pull/2050 + ## 3.0.0-beta.2 - 2021-02-10 ### Added diff --git a/awc/Cargo.toml b/awc/Cargo.toml index ca345d3cb..761871ba2 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -54,6 +54,7 @@ bytes = "1" cfg-if = "1.0" derive_more = "0.99.5" futures-core = { version = "0.3.7", default-features = false } +itoa = "0.4" log =" 0.4" mime = "0.3" percent-encoding = "2.1" @@ -80,8 +81,8 @@ actix-server = "2.0.0-beta.3" actix-tls = { version = "3.0.0-beta.4", features = ["openssl", "rustls"] } brotli2 = "0.3.2" +env_logger = "0.8" flate2 = "1.0.13" futures-util = { version = "0.3.7", default-features = false } -env_logger = "0.8" rcgen = "0.8" webpki = "0.21" diff --git a/awc/src/request.rs b/awc/src/request.rs index 812c0e805..1b63f3687 100644 --- a/awc/src/request.rs +++ b/awc/src/request.rs @@ -248,30 +248,25 @@ impl ClientRequest { /// Set content length #[inline] pub fn content_length(self, len: u64) -> Self { - self.append_header((header::CONTENT_LENGTH, len)) + let mut buf = itoa::Buffer::new(); + self.insert_header((header::CONTENT_LENGTH, buf.format(len))) } - /// Set HTTP basic authorization header - pub fn basic_auth(self, username: U, password: Option<&str>) -> Self - where - U: fmt::Display, - { - let auth = match password { - Some(password) => format!("{}:{}", username, password), - None => format!("{}:", username), - }; - self.append_header(( + /// Set HTTP basic authorization header. + /// + /// If no password is needed, just provide an empty string. + pub fn basic_auth(self, username: impl fmt::Display, password: impl fmt::Display) -> Self { + let auth = format!("{}:{}", username, password); + + self.insert_header(( header::AUTHORIZATION, format!("Basic {}", base64::encode(&auth)), )) } /// Set HTTP bearer authentication header - pub fn bearer_auth(self, token: T) -> Self - where - T: fmt::Display, - { - self.append_header((header::AUTHORIZATION, format!("Bearer {}", token))) + pub fn bearer_auth(self, token: impl fmt::Display) -> Self { + self.insert_header((header::AUTHORIZATION, format!("Bearer {}", token))) } /// Set a cookie @@ -643,9 +638,7 @@ mod tests { #[actix_rt::test] async fn client_basic_auth() { - let req = Client::new() - .get("/") - .basic_auth("username", Some("password")); + let req = Client::new().get("/").basic_auth("username", "password"); assert_eq!( req.head .headers @@ -656,7 +649,7 @@ mod tests { "Basic dXNlcm5hbWU6cGFzc3dvcmQ=" ); - let req = Client::new().get("/").basic_auth("username", None); + let req = Client::new().get("/").basic_auth("username", ""); assert_eq!( req.head .headers diff --git a/awc/tests/test_client.rs b/awc/tests/test_client.rs index c7fa82de8..50d2b5eac 100644 --- a/awc/tests/test_client.rs +++ b/awc/tests/test_client.rs @@ -829,7 +829,7 @@ async fn client_basic_auth() { .unwrap() .to_str() .unwrap() - == "Basic dXNlcm5hbWU6cGFzc3dvcmQ=" + == format!("Basic {}", base64::encode("username:password")) { HttpResponse::Ok() } else { @@ -840,7 +840,7 @@ async fn client_basic_auth() { }); // set authorization header to Basic - let request = srv.get("/").basic_auth("username", Some("password")); + let request = srv.get("/").basic_auth("username", "password"); let response = request.send().await.unwrap(); assert!(response.status().is_success()); } From fe0b3f459fd40aa279b86f3e99a1e1c3b2ee30e4 Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Sun, 7 Mar 2021 13:23:42 -0800 Subject: [PATCH 12/34] remove localwaker from h1::payload (#2051) --- actix-http/src/h1/payload.rs | 63 +++++++++++++++++++++++++++++------- 1 file changed, 51 insertions(+), 12 deletions(-) diff --git a/actix-http/src/h1/payload.rs b/actix-http/src/h1/payload.rs index d4cfee146..32275ac6b 100644 --- a/actix-http/src/h1/payload.rs +++ b/actix-http/src/h1/payload.rs @@ -3,9 +3,8 @@ use std::cell::RefCell; use std::collections::VecDeque; use std::pin::Pin; use std::rc::{Rc, Weak}; -use std::task::{Context, Poll}; +use std::task::{Context, Poll, Waker}; -use actix_utils::task::LocalWaker; use bytes::Bytes; use futures_core::Stream; @@ -134,7 +133,7 @@ impl PayloadSender { if shared.borrow().need_read { PayloadStatus::Read } else { - shared.borrow_mut().io_task.register(cx.waker()); + shared.borrow_mut().register_io(cx); PayloadStatus::Pause } } else { @@ -150,8 +149,8 @@ struct Inner { err: Option, need_read: bool, items: VecDeque, - task: LocalWaker, - io_task: LocalWaker, + task: Option, + io_task: Option, } impl Inner { @@ -162,8 +161,48 @@ impl Inner { err: None, items: VecDeque::new(), need_read: true, - task: LocalWaker::new(), - io_task: LocalWaker::new(), + task: None, + io_task: None, + } + } + + /// Wake up future waiting for payload data to be available. + fn wake(&mut self) { + if let Some(waker) = self.task.take() { + waker.wake(); + } + } + + /// Wake up future feeding data to Payload. + fn wake_io(&mut self) { + if let Some(waker) = self.io_task.take() { + waker.wake(); + } + } + + /// Register future waiting data from payload. + /// Waker would be used in `Inner::wake` + fn register(&mut self, cx: &mut Context<'_>) { + if self + .task + .as_ref() + .map(|w| !cx.waker().will_wake(w)) + .unwrap_or(true) + { + self.task = Some(cx.waker().clone()); + } + } + + // Register future feeding data to payload. + /// Waker would be used in `Inner::wake_io` + fn register_io(&mut self, cx: &mut Context<'_>) { + if self + .io_task + .as_ref() + .map(|w| !cx.waker().will_wake(w)) + .unwrap_or(true) + { + self.io_task = Some(cx.waker().clone()); } } @@ -182,7 +221,7 @@ impl Inner { self.len += data.len(); self.items.push_back(data); self.need_read = self.len < MAX_BUFFER_SIZE; - self.task.wake(); + self.wake(); } #[cfg(test)] @@ -199,9 +238,9 @@ impl Inner { self.need_read = self.len < MAX_BUFFER_SIZE; if self.need_read && !self.eof { - self.task.register(cx.waker()); + self.register(cx); } - self.io_task.wake(); + self.wake_io(); Poll::Ready(Some(Ok(data))) } else if let Some(err) = self.err.take() { Poll::Ready(Some(Err(err))) @@ -209,8 +248,8 @@ impl Inner { Poll::Ready(None) } else { self.need_read = true; - self.task.register(cx.waker()); - self.io_task.wake(); + self.register(cx); + self.wake_io(); Poll::Pending } } From 2d3a0d6038a835af7917e11bdcd4e9dcfa8a52ec Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sun, 7 Mar 2021 22:11:39 +0000 Subject: [PATCH 13/34] json method receives plain serialize (#2052) --- actix-http/CHANGES.md | 2 ++ actix-http/src/response.rs | 16 ++++++++-------- src/data.rs | 13 +++++++++++++ src/types/form.rs | 12 ++++++++++++ src/types/json.rs | 12 ++++++++++++ 5 files changed, 47 insertions(+), 8 deletions(-) diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 165b004a6..14218289d 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -4,6 +4,7 @@ ### Changed * Feature `cookies` is now optional and disabled by default. [#1981] * `ws::hash_key` now returns array. [#2035] +* `ResponseBuilder::json` now takes `impl Serialize`. [#2052] ### Removed * re-export of `futures_channel::oneshot::Canceled` is removed from `error` mod. [#1994] @@ -12,6 +13,7 @@ [#1981]: https://github.com/actix/actix-web/pull/1981 [#1994]: https://github.com/actix/actix-web/pull/1994 [#2035]: https://github.com/actix/actix-web/pull/2035 +[#2052]: https://github.com/actix/actix-web/pull/2052 ## 3.0.0-beta.3 - 2021-02-10 diff --git a/actix-http/src/response.rs b/actix-http/src/response.rs index c5a4aec13..d581fd293 100644 --- a/actix-http/src/response.rs +++ b/actix-http/src/response.rs @@ -5,7 +5,6 @@ use std::{ convert::TryInto, fmt, future::Future, - ops, pin::Pin, str, task::{Context, Poll}, @@ -673,12 +672,8 @@ impl ResponseBuilder { /// Set a json body and generate `Response` /// /// `ResponseBuilder` can not be used after this call. - pub fn json(&mut self, value: T) -> Response - where - T: ops::Deref, - T::Target: Serialize, - { - match serde_json::to_string(&*value) { + pub fn json(&mut self, value: impl Serialize) -> Response { + match serde_json::to_string(&value) { Ok(body) => { let contains = if let Some(parts) = parts(&mut self.head, &self.err) { parts.headers.contains_key(header::CONTENT_TYPE) @@ -1007,7 +1002,12 @@ mod tests { #[test] fn test_json() { - let resp = Response::build(StatusCode::OK).json(vec!["v1", "v2", "v3"]); + let resp = Response::Ok().json(vec!["v1", "v2", "v3"]); + let ct = resp.headers().get(CONTENT_TYPE).unwrap(); + assert_eq!(ct, HeaderValue::from_static("application/json")); + assert_eq!(resp.body().get_ref(), b"[\"v1\",\"v2\",\"v3\"]"); + + let resp = Response::Ok().json(&["v1", "v2", "v3"]); let ct = resp.headers().get(CONTENT_TYPE).unwrap(); assert_eq!(ct, HeaderValue::from_static("application/json")); assert_eq!(resp.body().get_ref(), b"[\"v1\",\"v2\",\"v3\"]"); diff --git a/src/data.rs b/src/data.rs index 133248212..0336553ca 100644 --- a/src/data.rs +++ b/src/data.rs @@ -5,6 +5,7 @@ use std::sync::Arc; use actix_http::error::{Error, ErrorInternalServerError}; use actix_http::Extensions; use futures_util::future::{err, ok, LocalBoxFuture, Ready}; +use serde::Serialize; use crate::dev::Payload; use crate::extract::FromRequest; @@ -102,6 +103,18 @@ impl From> for Data { } } +impl Serialize for Data +where + T: Serialize, +{ + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + self.0.serialize(serializer) + } +} + impl FromRequest for Data { type Config = (); type Error = Error; diff --git a/src/types/form.rs b/src/types/form.rs index 4f3ecbe7c..57a742e38 100644 --- a/src/types/form.rs +++ b/src/types/form.rs @@ -106,6 +106,18 @@ impl ops::DerefMut for Form { } } +impl Serialize for Form +where + T: Serialize, +{ + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + self.0.serialize(serializer) + } +} + /// See [here](#extractor) for example of usage as an extractor. impl FromRequest for Form where diff --git a/src/types/json.rs b/src/types/json.rs index 866d835f2..d8ce3cb71 100644 --- a/src/types/json.rs +++ b/src/types/json.rs @@ -114,6 +114,18 @@ where } } +impl Serialize for Json +where + T: Serialize, +{ + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + self.0.serialize(serializer) + } +} + /// Creates response with OK status code, correct content type header, and serialized JSON payload. /// /// If serialization failed From 5b4105e1e6e5c69e7f71efaae20923fe7746f792 Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Sun, 7 Mar 2021 15:57:32 -0800 Subject: [PATCH 14/34] Refactor/client builder (#2053) --- awc/CHANGES.md | 1 - awc/src/builder.rs | 38 +++++++++++++++++++++++++++++++++- awc/src/lib.rs | 10 ++------- awc/src/middleware/redirect.rs | 3 ++- 4 files changed, 41 insertions(+), 11 deletions(-) diff --git a/awc/CHANGES.md b/awc/CHANGES.md index 211db5a80..b11d64628 100644 --- a/awc/CHANGES.md +++ b/awc/CHANGES.md @@ -12,7 +12,6 @@ ### Removed * `ClientBuilder::default` function [#2008] -* `ClientBuilder::disable_redirects` and `ClientBuilder::max_redirects` method [#2008] [#1931]: https://github.com/actix/actix-web/pull/1931 [#1981]: https://github.com/actix/actix-web/pull/1981 diff --git a/awc/src/builder.rs b/awc/src/builder.rs index 363056c02..72a0f4f04 100644 --- a/awc/src/builder.rs +++ b/awc/src/builder.rs @@ -14,7 +14,7 @@ use actix_service::{boxed, Service}; use crate::connect::DefaultConnector; use crate::error::SendRequestError; -use crate::middleware::{NestTransform, Transform}; +use crate::middleware::{NestTransform, Redirect, Transform}; use crate::{Client, ClientConfig, ConnectRequest, ConnectResponse, ConnectorService}; /// An HTTP Client builder @@ -31,6 +31,7 @@ pub struct ClientBuilder { connector: Connector, middleware: M, local_address: Option, + max_redirects: u8, } impl ClientBuilder { @@ -54,6 +55,7 @@ impl ClientBuilder { max_http_version: None, stream_window_size: None, conn_window_size: None, + max_redirects: 10, } } } @@ -86,6 +88,7 @@ where max_http_version: self.max_http_version, stream_window_size: self.stream_window_size, conn_window_size: self.conn_window_size, + max_redirects: self.max_redirects, } } @@ -118,6 +121,22 @@ where self } + /// Do not follow redirects. + /// + /// Redirects are allowed by default. + pub fn disable_redirects(mut self) -> Self { + self.max_redirects = 0; + self + } + + /// Set max number of redirects. + /// + /// Max redirects is set to 10 by default. + pub fn max_redirects(mut self, num: u8) -> Self { + self.max_redirects = num; + self + } + /// Indicates the initial window size (in octets) for /// HTTP2 stream-level flow control for received data. /// @@ -209,11 +228,28 @@ where timeout: self.timeout, connector: self.connector, local_address: self.local_address, + max_redirects: self.max_redirects, } } /// Finish build process and create `Client` instance. pub fn finish(self) -> Client + where + M: Transform + 'static, + M::Transform: + Service, + { + let redirect_time = self.max_redirects; + + if redirect_time > 0 { + self.wrap(Redirect::new().max_redirect_times(redirect_time)) + ._finish() + } else { + self._finish() + } + } + + fn _finish(self) -> Client where M: Transform + 'static, M::Transform: diff --git a/awc/src/lib.rs b/awc/src/lib.rs index 2f48dca79..4cd1d5bb2 100644 --- a/awc/src/lib.rs +++ b/awc/src/lib.rs @@ -107,7 +107,7 @@ use actix_http::{ RequestHead, }; use actix_rt::net::TcpStream; -use actix_service::{boxed, Service}; +use actix_service::Service; mod builder; mod connect; @@ -157,13 +157,7 @@ pub(crate) struct ClientConfig { impl Default for Client { fn default() -> Self { - Client(Rc::new(ClientConfig { - connector: boxed::service(self::connect::DefaultConnector::new( - Connector::new().finish(), - )), - headers: HeaderMap::new(), - timeout: Some(Duration::from_secs(5)), - })) + ClientBuilder::new().finish() } } diff --git a/awc/src/middleware/redirect.rs b/awc/src/middleware/redirect.rs index 1d0ace166..f8bdd2def 100644 --- a/awc/src/middleware/redirect.rs +++ b/awc/src/middleware/redirect.rs @@ -292,7 +292,7 @@ mod tests { #[actix_rt::test] async fn test_basic_redirect() { let client = ClientBuilder::new() - .connector(crate::Connector::new()) + .disable_redirects() .wrap(Redirect::new().max_redirect_times(10)) .finish(); @@ -318,6 +318,7 @@ mod tests { #[actix_rt::test] async fn test_redirect_limit() { let client = ClientBuilder::new() + .disable_redirects() .wrap(Redirect::new().max_redirect_times(1)) .connector(crate::Connector::new()) .finish(); From 5e811053173a6442e1e228ef8025f4704e4a9216 Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Mon, 8 Mar 2021 12:00:20 -0800 Subject: [PATCH 15/34] remove ka timer from h2 dispatcher (#2057) --- actix-http/src/h2/dispatcher.rs | 110 +++++++++++--------------------- actix-http/src/h2/service.rs | 1 - actix-http/src/service.rs | 1 - 3 files changed, 37 insertions(+), 75 deletions(-) diff --git a/actix-http/src/h2/dispatcher.rs b/actix-http/src/h2/dispatcher.rs index 8e01afcb5..958c761d5 100644 --- a/actix-http/src/h2/dispatcher.rs +++ b/actix-http/src/h2/dispatcher.rs @@ -2,7 +2,6 @@ use std::task::{Context, Poll}; use std::{cmp, future::Future, marker::PhantomData, net, pin::Pin, rc::Rc}; use actix_codec::{AsyncRead, AsyncWrite}; -use actix_rt::time::{Instant, Sleep}; use actix_service::Service; use bytes::{Bytes, BytesMut}; use futures_core::ready; @@ -36,8 +35,6 @@ where on_connect_data: OnConnectData, config: ServiceConfig, peer_addr: Option, - ka_expire: Instant, - ka_timer: Option, _phantom: PhantomData, } @@ -54,33 +51,14 @@ where connection: Connection, on_connect_data: OnConnectData, config: ServiceConfig, - timeout: Option, peer_addr: Option, ) -> Self { - // let keepalive = config.keep_alive_enabled(); - // let flags = if keepalive { - // Flags::KEEPALIVE | Flags::KEEPALIVE_ENABLED - // } else { - // Flags::empty() - // }; - - // keep-alive timer - let (ka_expire, ka_timer) = if let Some(delay) = timeout { - (delay.deadline(), Some(delay)) - } else if let Some(delay) = config.keep_alive_timer() { - (delay.deadline(), Some(delay)) - } else { - (config.now(), None) - }; - Dispatcher { flow, config, peer_addr, connection, on_connect_data, - ka_expire, - ka_timer, _phantom: PhantomData, } } @@ -108,13 +86,6 @@ where Some(Err(err)) => return Poll::Ready(Err(err.into())), Some(Ok((req, res))) => { - // update keep-alive expire - if this.ka_timer.is_some() { - if let Some(expire) = this.config.keep_alive_expire() { - this.ka_expire = expire; - } - } - let (parts, body) = req.into_parts(); let pl = crate::h2::Payload::new(body); let pl = Payload::::H2(pl); @@ -130,7 +101,7 @@ where // merge on_connect_ext data into request extensions this.on_connect_data.merge_into(&mut req); - let svc = ServiceResponse:: { + let svc = ServiceResponse { state: ServiceResponseState::ServiceCall( this.flow.service.call(req), Some(res), @@ -312,57 +283,50 @@ where ServiceResponseStateProj::SendPayload(ref mut stream, ref mut body) => { loop { - loop { - match this.buffer { - Some(ref mut buffer) => { - match ready!(stream.poll_capacity(cx)) { - None => return Poll::Ready(()), + match this.buffer { + Some(ref mut buffer) => match ready!(stream.poll_capacity(cx)) { + None => return Poll::Ready(()), - Some(Ok(cap)) => { - let len = buffer.len(); - let bytes = buffer.split_to(cmp::min(cap, len)); + Some(Ok(cap)) => { + let len = buffer.len(); + let bytes = buffer.split_to(cmp::min(cap, len)); - if let Err(e) = stream.send_data(bytes, false) { - warn!("{:?}", e); - return Poll::Ready(()); - } else if !buffer.is_empty() { - let cap = cmp::min(buffer.len(), CHUNK_SIZE); - stream.reserve_capacity(cap); - } else { - this.buffer.take(); - } - } - - Some(Err(e)) => { - warn!("{:?}", e); - return Poll::Ready(()); - } + if let Err(e) = stream.send_data(bytes, false) { + warn!("{:?}", e); + return Poll::Ready(()); + } else if !buffer.is_empty() { + let cap = cmp::min(buffer.len(), CHUNK_SIZE); + stream.reserve_capacity(cap); + } else { + this.buffer.take(); } } - None => match ready!(body.as_mut().poll_next(cx)) { - None => { - if let Err(e) = stream.send_data(Bytes::new(), true) - { - warn!("{:?}", e); - } - return Poll::Ready(()); - } + Some(Err(e)) => { + warn!("{:?}", e); + return Poll::Ready(()); + } + }, - Some(Ok(chunk)) => { - stream.reserve_capacity(cmp::min( - chunk.len(), - CHUNK_SIZE, - )); - *this.buffer = Some(chunk); + None => match ready!(body.as_mut().poll_next(cx)) { + None => { + if let Err(e) = stream.send_data(Bytes::new(), true) { + warn!("{:?}", e); } + return Poll::Ready(()); + } - Some(Err(e)) => { - error!("Response payload stream error: {:?}", e); - return Poll::Ready(()); - } - }, - } + Some(Ok(chunk)) => { + stream + .reserve_capacity(cmp::min(chunk.len(), CHUNK_SIZE)); + *this.buffer = Some(chunk); + } + + Some(Err(e)) => { + error!("Response payload stream error: {:?}", e); + return Poll::Ready(()); + } + }, } } } diff --git a/actix-http/src/h2/service.rs b/actix-http/src/h2/service.rs index 0984b3f23..1dc290e49 100644 --- a/actix-http/src/h2/service.rs +++ b/actix-http/src/h2/service.rs @@ -368,7 +368,6 @@ where conn, on_connect_data, config.take().unwrap(), - None, *peer_addr, )); self.poll(cx) diff --git a/actix-http/src/service.rs b/actix-http/src/service.rs index 402affb7e..89f3e3bb1 100644 --- a/actix-http/src/service.rs +++ b/actix-http/src/service.rs @@ -658,7 +658,6 @@ where conn, on_connect_data, cfg, - None, peer_addr, ))); self.poll(cx) From 95130fcfd00a4344a44ebaccee54eb967098bf89 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Mon, 8 Mar 2021 20:32:19 +0000 Subject: [PATCH 16/34] address clippy warnings --- awc/src/ws.rs | 2 +- examples/basic.rs | 2 +- examples/uds.rs | 2 +- src/middleware/condition.rs | 1 + src/middleware/err_handlers.rs | 2 ++ tests/test_httpserver.rs | 4 ++-- 6 files changed, 8 insertions(+), 5 deletions(-) diff --git a/awc/src/ws.rs b/awc/src/ws.rs index 1aa426ac7..f64e9e19a 100644 --- a/awc/src/ws.rs +++ b/awc/src/ws.rs @@ -382,7 +382,7 @@ impl WebsocketsRequest { if let Some(hdr_key) = head.headers.get(&header::SEC_WEBSOCKET_ACCEPT) { let encoded = ws::hash_key(key.as_ref()); - if hdr_key.as_bytes() != &encoded { + if hdr_key.as_bytes() != encoded { log::trace!( "Invalid challenge response: expected: {:?} received: {:?}", &encoded, diff --git a/examples/basic.rs b/examples/basic.rs index 99eef3ee1..796f002e8 100644 --- a/examples/basic.rs +++ b/examples/basic.rs @@ -30,7 +30,7 @@ async fn main() -> std::io::Result<()> { .service( web::resource("/resource2/index.html") .wrap(middleware::DefaultHeaders::new().header("X-Version-R2", "0.3")) - .default_service(web::route().to(|| HttpResponse::MethodNotAllowed())) + .default_service(web::route().to(HttpResponse::MethodNotAllowed)) .route(web::get().to(index_async)), ) .service(web::resource("/test1.html").to(|| async { "Test\r\n" })) diff --git a/examples/uds.rs b/examples/uds.rs index 096781984..1db252fef 100644 --- a/examples/uds.rs +++ b/examples/uds.rs @@ -34,7 +34,7 @@ async fn main() -> std::io::Result<()> { .service( web::resource("/resource2/index.html") .wrap(middleware::DefaultHeaders::new().header("X-Version-R2", "0.3")) - .default_service(web::route().to(|| HttpResponse::MethodNotAllowed())) + .default_service(web::route().to(HttpResponse::MethodNotAllowed)) .route(web::get().to(index_async)), ) .service(web::resource("/test1.html").to(|| async { "Test\r\n" })) diff --git a/src/middleware/condition.rs b/src/middleware/condition.rs index f0c344062..63a90c853 100644 --- a/src/middleware/condition.rs +++ b/src/middleware/condition.rs @@ -106,6 +106,7 @@ mod tests { HttpResponse, }; + #[allow(clippy::unnecessary_wraps)] fn render_500(mut res: ServiceResponse) -> Result> { res.response_mut() .headers_mut() diff --git a/src/middleware/err_handlers.rs b/src/middleware/err_handlers.rs index 4673ed4ce..06e88cefd 100644 --- a/src/middleware/err_handlers.rs +++ b/src/middleware/err_handlers.rs @@ -182,6 +182,7 @@ mod tests { use crate::test::{self, TestRequest}; use crate::HttpResponse; + #[allow(clippy::unnecessary_wraps)] fn render_500(mut res: ServiceResponse) -> Result> { res.response_mut() .headers_mut() @@ -205,6 +206,7 @@ mod tests { assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0001"); } + #[allow(clippy::unnecessary_wraps)] fn render_500_async( mut res: ServiceResponse, ) -> Result> { diff --git a/tests/test_httpserver.rs b/tests/test_httpserver.rs index 043159376..b6c0528dd 100644 --- a/tests/test_httpserver.rs +++ b/tests/test_httpserver.rs @@ -72,7 +72,7 @@ async fn test_start() { } #[cfg(feature = "openssl")] -fn ssl_acceptor() -> std::io::Result { +fn ssl_acceptor() -> SslAcceptorBuilder { use openssl::{ pkey::PKey, ssl::{SslAcceptor, SslMethod}, @@ -102,7 +102,7 @@ async fn test_start_ssl() { thread::spawn(move || { let sys = actix_rt::System::new(); - let builder = ssl_acceptor().unwrap(); + let builder = ssl_acceptor(); let srv = HttpServer::new(|| { App::new().service(web::resource("/").route(web::to(|req: HttpRequest| { From effacf8fc8ee8aedf1a5c809dbb1b066aad7a862 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Mon, 8 Mar 2021 20:51:50 +0000 Subject: [PATCH 17/34] fix ssl test --- actix-http/tests/test_server.rs | 4 ++-- tests/test_httpserver.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/actix-http/tests/test_server.rs b/actix-http/tests/test_server.rs index 910fa81f2..a4c1f92b5 100644 --- a/actix-http/tests/test_server.rs +++ b/actix-http/tests/test_server.rs @@ -126,7 +126,7 @@ async fn test_chunked_payload() { .take_payload() .map(|res| match res { Ok(pl) => pl, - Err(e) => panic!(format!("Error reading payload: {}", e)), + Err(e) => panic!("Error reading payload: {}", e), }) .fold(0usize, |acc, chunk| ready(acc + chunk.len())) .map(|req_size| { @@ -162,7 +162,7 @@ async fn test_chunked_payload() { let re = Regex::new(r"size=(\d+)").unwrap(); let size: usize = match re.captures(&data) { Some(caps) => caps.get(1).unwrap().as_str().parse().unwrap(), - None => panic!(format!("Failed to find size in HTTP Response: {}", data)), + None => panic!("Failed to find size in HTTP Response: {}", data), }; size }; diff --git a/tests/test_httpserver.rs b/tests/test_httpserver.rs index b6c0528dd..aa2b2ca74 100644 --- a/tests/test_httpserver.rs +++ b/tests/test_httpserver.rs @@ -89,7 +89,7 @@ fn ssl_acceptor() -> SslAcceptorBuilder { builder.set_certificate(&cert).unwrap(); builder.set_private_key(&key).unwrap(); - Ok(builder) + builder } #[actix_rt::test] From fc31b091e4e00067c52aba2801d79c25bb93a2c9 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Mon, 8 Mar 2021 23:07:40 +0000 Subject: [PATCH 18/34] prepare http release 3.0.0-beta.4 --- Cargo.toml | 2 +- actix-http-test/Cargo.toml | 2 +- actix-http/CHANGES.md | 5 ++++- actix-http/Cargo.toml | 2 +- actix-http/README.md | 4 ++-- actix-multipart/Cargo.toml | 2 +- actix-web-actors/Cargo.toml | 2 +- awc/Cargo.toml | 4 ++-- 8 files changed, 13 insertions(+), 10 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index bd758ab10..28bb432c7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -87,7 +87,7 @@ actix-utils = "3.0.0-beta.2" actix-tls = { version = "3.0.0-beta.4", default-features = false, optional = true } actix-web-codegen = "0.5.0-beta.1" -actix-http = "3.0.0-beta.3" +actix-http = "3.0.0-beta.4" awc = { version = "3.0.0-beta.2", default-features = false } ahash = "0.7" diff --git a/actix-http-test/Cargo.toml b/actix-http-test/Cargo.toml index 900960215..fd3edd9b8 100644 --- a/actix-http-test/Cargo.toml +++ b/actix-http-test/Cargo.toml @@ -58,4 +58,4 @@ optional = true [dev-dependencies] actix-web = { version = "4.0.0-beta.3", default-features = false, features = ["cookies"] } -actix-http = "3.0.0-beta.3" +actix-http = "3.0.0-beta.4" diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 14218289d..a5e69eda3 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -1,13 +1,16 @@ # Changes ## Unreleased - 2021-xx-xx + + +## 3.0.0-beta.4 - 2021-03-08 ### Changed * Feature `cookies` is now optional and disabled by default. [#1981] * `ws::hash_key` now returns array. [#2035] * `ResponseBuilder::json` now takes `impl Serialize`. [#2052] ### Removed -* re-export of `futures_channel::oneshot::Canceled` is removed from `error` mod. [#1994] +* Re-export of `futures_channel::oneshot::Canceled` is removed from `error` mod. [#1994] * `ResponseError` impl for `futures_channel::oneshot::Canceled` is removed. [#1994] [#1981]: https://github.com/actix/actix-web/pull/1981 diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index c79ad11b2..d10e1ebdf 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-http" -version = "3.0.0-beta.3" +version = "3.0.0-beta.4" authors = ["Nikolay Kim "] description = "HTTP primitives for the Actix ecosystem" readme = "README.md" diff --git a/actix-http/README.md b/actix-http/README.md index 881fbc8c5..53fedd40e 100644 --- a/actix-http/README.md +++ b/actix-http/README.md @@ -3,11 +3,11 @@ > HTTP primitives for the Actix ecosystem. [![crates.io](https://img.shields.io/crates/v/actix-http?label=latest)](https://crates.io/crates/actix-http) -[![Documentation](https://docs.rs/actix-http/badge.svg?version=3.0.0-beta.3)](https://docs.rs/actix-http/3.0.0-beta.3) +[![Documentation](https://docs.rs/actix-http/badge.svg?version=3.0.0-beta.4)](https://docs.rs/actix-http/3.0.0-beta.4) [![Version](https://img.shields.io/badge/rustc-1.46+-ab6000.svg)](https://blog.rust-lang.org/2020/03/12/Rust-1.46.html) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-http.svg)
-[![dependency status](https://deps.rs/crate/actix-http/3.0.0-beta.3/status.svg)](https://deps.rs/crate/actix-http/3.0.0-beta.3) +[![dependency status](https://deps.rs/crate/actix-http/3.0.0-beta.4/status.svg)](https://deps.rs/crate/actix-http/3.0.0-beta.4) [![Download](https://img.shields.io/crates/d/actix-http.svg)](https://crates.io/crates/actix-http) [![Join the chat at https://gitter.im/actix/actix](https://badges.gitter.im/actix/actix.svg)](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) diff --git a/actix-multipart/Cargo.toml b/actix-multipart/Cargo.toml index fcbadde36..34817e087 100644 --- a/actix-multipart/Cargo.toml +++ b/actix-multipart/Cargo.toml @@ -29,6 +29,6 @@ twoway = "0.2" [dev-dependencies] actix-rt = "2.1" -actix-http = "3.0.0-beta.3" +actix-http = "3.0.0-beta.4" tokio = { version = "1", features = ["sync"] } tokio-stream = "0.1" diff --git a/actix-web-actors/Cargo.toml b/actix-web-actors/Cargo.toml index e0c4fc073..5ddbe53ce 100644 --- a/actix-web-actors/Cargo.toml +++ b/actix-web-actors/Cargo.toml @@ -18,7 +18,7 @@ path = "src/lib.rs" [dependencies] actix = { version = "0.11.0-beta.3", default-features = false } actix-codec = "0.4.0-beta.1" -actix-http = "3.0.0-beta.3" +actix-http = "3.0.0-beta.4" actix-web = { version = "4.0.0-beta.3", default-features = false } bytes = "1" diff --git a/awc/Cargo.toml b/awc/Cargo.toml index 761871ba2..166a08fd5 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -46,7 +46,7 @@ trust-dns = ["actix-http/trust-dns"] [dependencies] actix-codec = "0.4.0-beta.1" actix-service = "2.0.0-beta.4" -actix-http = "3.0.0-beta.3" +actix-http = "3.0.0-beta.4" actix-rt = { version = "2.1", default-features = false } base64 = "0.13" @@ -74,7 +74,7 @@ optional = true [dev-dependencies] actix-web = { version = "4.0.0-beta.3", features = ["openssl"] } -actix-http = { version = "3.0.0-beta.3", features = ["openssl"] } +actix-http = { version = "3.0.0-beta.4", features = ["openssl"] } actix-http-test = { version = "3.0.0-beta.2", features = ["openssl"] } actix-utils = "3.0.0-beta.1" actix-server = "2.0.0-beta.3" From 23b0e64199eaf4d10cdc1727c68d7ad3da298eb2 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Mon, 8 Mar 2021 23:15:53 +0000 Subject: [PATCH 19/34] prepare awc release 3.0.0-beta.3 --- Cargo.toml | 2 +- actix-http-test/Cargo.toml | 2 +- awc/CHANGES.md | 3 +++ awc/Cargo.toml | 2 +- awc/README.md | 4 ++-- 5 files changed, 8 insertions(+), 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 28bb432c7..48606f6ed 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -88,7 +88,7 @@ actix-tls = { version = "3.0.0-beta.4", default-features = false, optional = tru actix-web-codegen = "0.5.0-beta.1" actix-http = "3.0.0-beta.4" -awc = { version = "3.0.0-beta.2", default-features = false } +awc = { version = "3.0.0-beta.3", default-features = false } ahash = "0.7" bytes = "1" diff --git a/actix-http-test/Cargo.toml b/actix-http-test/Cargo.toml index fd3edd9b8..ca51bd325 100644 --- a/actix-http-test/Cargo.toml +++ b/actix-http-test/Cargo.toml @@ -35,7 +35,7 @@ actix-tls = "3.0.0-beta.4" actix-utils = "3.0.0-beta.2" actix-rt = "2.1" actix-server = "2.0.0-beta.3" -awc = { version = "3.0.0-beta.2", default-features = false } +awc = { version = "3.0.0-beta.3", default-features = false } base64 = "0.13" bytes = "1" diff --git a/awc/CHANGES.md b/awc/CHANGES.md index b11d64628..bf7bffc49 100644 --- a/awc/CHANGES.md +++ b/awc/CHANGES.md @@ -1,6 +1,9 @@ # Changes ## Unreleased - 2021-xx-xx + + +## 3.0.0-beta.3 - 2021-03-08 ### Added * `ClientResponse::timeout` for set the timeout of collecting response body. [#1931] * `ClientBuilder::local_address` for bind to a local ip address for this client. [#2024] diff --git a/awc/Cargo.toml b/awc/Cargo.toml index 166a08fd5..c55465b5d 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "awc" -version = "3.0.0-beta.2" +version = "3.0.0-beta.3" authors = ["Nikolay Kim "] description = "Async HTTP and WebSocket client library built on the Actix ecosystem" readme = "README.md" diff --git a/awc/README.md b/awc/README.md index 1f6e3b8fb..ec3984d60 100644 --- a/awc/README.md +++ b/awc/README.md @@ -3,9 +3,9 @@ > Async HTTP and WebSocket client library. [![crates.io](https://img.shields.io/crates/v/awc?label=latest)](https://crates.io/crates/awc) -[![Documentation](https://docs.rs/awc/badge.svg?version=3.0.0-beta.2)](https://docs.rs/awc/3.0.0-beta.2) +[![Documentation](https://docs.rs/awc/badge.svg?version=3.0.0-beta.3)](https://docs.rs/awc/3.0.0-beta.3) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/awc) -[![Dependency Status](https://deps.rs/crate/awc/3.0.0-beta.2/status.svg)](https://deps.rs/crate/awc/3.0.0-beta.2) +[![Dependency Status](https://deps.rs/crate/awc/3.0.0-beta.3/status.svg)](https://deps.rs/crate/awc/3.0.0-beta.3) [![Join the chat at https://gitter.im/actix/actix-web](https://badges.gitter.im/actix/actix-web.svg)](https://gitter.im/actix/actix-web?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) ## Documentation & Resources From c4e565121569880e6e6b60e8d755ca3fa637f230 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Mon, 8 Mar 2021 23:49:12 +0000 Subject: [PATCH 20/34] update docs --- docs/graphs/web-focus.dot | 53 +++++++++++++++++++++++---------------- 1 file changed, 31 insertions(+), 22 deletions(-) diff --git a/docs/graphs/web-focus.dot b/docs/graphs/web-focus.dot index ec0f7a946..2c6e2779b 100644 --- a/docs/graphs/web-focus.dot +++ b/docs/graphs/web-focus.dot @@ -1,35 +1,44 @@ digraph { subgraph cluster_web { - label="actix/actix-web" + label="actix/web" "awc" - "actix-web" - "actix-files" - "actix-http" - "actix-multipart" - "actix-web-actors" - "actix-web-codegen" - "actix-http-test" + "web" + "files" + "http" + "multipart" + "web-actors" + "web-codegen" + "http-test" + + { rank=same; "multipart" "web-actors" "http-test" }; + { rank=same; "files" "awc" "web" }; + { rank=same; "web-codegen" "http" }; } - "actix-web" -> { "actix-codec" "actix-service" "actix-utils" "actix-router" "actix-rt" "actix-server" "macros" "threadpool" "actix-tls" "actix-web-codegen" "actix-http" "awc" } - "awc" -> { "actix-codec" "actix-service" "actix-http" "actix-rt" } - "actix-web-actors" -> { "actix" "actix-web" "actix-http" "actix-codec" } - "actix-multipart" -> { "actix-web" "actix-service" "actix-utils" } - "actix-http" -> { "actix-service" "actix-codec" "actix-tls" "actix-utils" "actix-rt" "threadpool" } - "actix-http" -> { "actix-tls" }[color=blue] // optional - "actix-files" -> { "actix-web" } - "actix-http-test" -> { "actix-service" "actix-codec" "actix-tls" "actix-utils" "actix-rt" "actix-server" "awc" } + "web" -> { "codec" "service" "utils" "router" "rt" "server" "macros" "web-codegen" "http" "awc" } + "web" -> { "tls" }[color=blue] // optional + "awc" -> { "codec" "service" "http" "rt" } + "web-actors" -> { "actix" "web" "http" "codec" } + "multipart" -> { "web" "service" "utils" } + "http" -> { "service" "codec" "utils" "rt" } + "http" -> { "tls" }[color=blue] // optional + "files" -> { "web" } + "http-test" -> { "service" "codec" "utils" "rt" "server" "awc" } + "http-test" -> { "tls" }[color=blue] // optional // net - "actix-utils" -> { "actix-service" "actix-rt" "actix-codec" } - "actix-tracing" -> { "actix-service" } - "actix-tls" -> { "actix-service" "actix-codec" "actix-utils" } - "actix-server" -> { "actix-service" "actix-rt" "actix-codec" "actix-utils" } - "actix-rt" -> { "macros" "threadpool" } + "utils" -> { "service" "rt" "codec" } + "tracing" -> { "service" } + "tls" -> { "service" "codec" "utils" } + "server" -> { "service" "rt" "codec" "utils" } + "rt" -> { "macros" } + + { rank=same; "utils" "codec" }; + { rank=same; "rt" "macros" "service" "router" }; // actix - "actix" -> { "actix-rt" } + "actix" -> { "rt" } } From b7c406637d7566547891843f45fd57991336b31f Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Tue, 9 Mar 2021 23:27:38 +0000 Subject: [PATCH 21/34] prepare actix-web-codegen release 0.5.0-beta.2 --- Cargo.toml | 2 +- actix-web-codegen/CHANGES.md | 3 +++ actix-web-codegen/Cargo.toml | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 48606f6ed..d002ce676 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -86,7 +86,7 @@ actix-service = "2.0.0-beta.4" actix-utils = "3.0.0-beta.2" actix-tls = { version = "3.0.0-beta.4", default-features = false, optional = true } -actix-web-codegen = "0.5.0-beta.1" +actix-web-codegen = "0.5.0-beta.2" actix-http = "3.0.0-beta.4" awc = { version = "3.0.0-beta.3", default-features = false } diff --git a/actix-web-codegen/CHANGES.md b/actix-web-codegen/CHANGES.md index 7c6543d49..f2c9b44b5 100644 --- a/actix-web-codegen/CHANGES.md +++ b/actix-web-codegen/CHANGES.md @@ -1,6 +1,9 @@ # Changes ## Unreleased - 2021-xx-xx + + +## 0.5.0-beta.2 - 2021-03-09 * Preserve doc comments when using route macros. [#2022] * Add `name` attribute to `route` macro. [#1934] diff --git a/actix-web-codegen/Cargo.toml b/actix-web-codegen/Cargo.toml index e43e91e22..e74e0ed85 100644 --- a/actix-web-codegen/Cargo.toml +++ b/actix-web-codegen/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-web-codegen" -version = "0.5.0-beta.1" +version = "0.5.0-beta.2" description = "Routing and runtime macros for Actix Web" readme = "README.md" homepage = "https://actix.rs" From 4b46351d36454224e5d33cde001114ec54c36aa2 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Tue, 9 Mar 2021 23:31:44 +0000 Subject: [PATCH 22/34] prepare actix-web release 4.0.0-beta.4 --- CHANGES.md | 7 +++++-- Cargo.toml | 2 +- README.md | 4 ++-- actix-files/Cargo.toml | 4 ++-- actix-http-test/Cargo.toml | 2 +- actix-multipart/Cargo.toml | 2 +- actix-web-actors/Cargo.toml | 2 +- actix-web-codegen/Cargo.toml | 2 +- actix-web-codegen/README.md | 4 ++-- awc/Cargo.toml | 2 +- 10 files changed, 17 insertions(+), 14 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 819237ab7..a6d4487dc 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,10 +1,13 @@ # Changes ## Unreleased - 2021-xx-xx + + +## 4.0.0-beta.4 - 2021-03-09 ### Changed * Feature `cookies` is now optional and enabled by default. [#1981] -* `JsonBody::new` returns a default limit of 32kB to be consistent with `JsonConfig` and the - default behaviour of the `web::Json` extractor. [#2010] +* `JsonBody::new` returns a default limit of 32kB to be consistent with `JsonConfig` and the default + behaviour of the `web::Json` extractor. [#2010] [#1981]: https://github.com/actix/actix-web/pull/1981 [#2010]: https://github.com/actix/actix-web/pull/2010 diff --git a/Cargo.toml b/Cargo.toml index d002ce676..8477c8ede 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-web" -version = "4.0.0-beta.3" +version = "4.0.0-beta.4" authors = ["Nikolay Kim "] description = "Actix Web is a powerful, pragmatic, and extremely fast web framework for Rust" readme = "README.md" diff --git a/README.md b/README.md index b3448140a..64fd7d08d 100644 --- a/README.md +++ b/README.md @@ -6,10 +6,10 @@

[![crates.io](https://img.shields.io/crates/v/actix-web?label=latest)](https://crates.io/crates/actix-web) -[![Documentation](https://docs.rs/actix-web/badge.svg?version=4.0.0-beta.2)](https://docs.rs/actix-web/4.0.0-beta.2) +[![Documentation](https://docs.rs/actix-web/badge.svg?version=4.0.0-beta.4)](https://docs.rs/actix-web/4.0.0-beta.4) [![Version](https://img.shields.io/badge/rustc-1.46+-ab6000.svg)](https://blog.rust-lang.org/2020/03/12/Rust-1.46.html) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-web.svg) -[![Dependency Status](https://deps.rs/crate/actix-web/4.0.0-beta.2/status.svg)](https://deps.rs/crate/actix-web/4.0.0-beta.2) +[![Dependency Status](https://deps.rs/crate/actix-web/4.0.0-beta.4/status.svg)](https://deps.rs/crate/actix-web/4.0.0-beta.4)
[![build status](https://github.com/actix/actix-web/workflows/CI%20%28Linux%29/badge.svg?branch=master&event=push)](https://github.com/actix/actix-web/actions) [![codecov](https://codecov.io/gh/actix/actix-web/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-web) diff --git a/actix-files/Cargo.toml b/actix-files/Cargo.toml index 8f1a9ec5a..80b0310bc 100644 --- a/actix-files/Cargo.toml +++ b/actix-files/Cargo.toml @@ -17,7 +17,7 @@ name = "actix_files" path = "src/lib.rs" [dependencies] -actix-web = { version = "4.0.0-beta.3", default-features = false } +actix-web = { version = "4.0.0-beta.4", default-features = false } actix-service = "2.0.0-beta.4" askama_escape = "0.10" @@ -34,4 +34,4 @@ percent-encoding = "2.1" [dev-dependencies] actix-rt = "2.1" -actix-web = "4.0.0-beta.3" +actix-web = "4.0.0-beta.4" diff --git a/actix-http-test/Cargo.toml b/actix-http-test/Cargo.toml index ca51bd325..855f53fe9 100644 --- a/actix-http-test/Cargo.toml +++ b/actix-http-test/Cargo.toml @@ -57,5 +57,5 @@ features = ["vendored"] optional = true [dev-dependencies] -actix-web = { version = "4.0.0-beta.3", default-features = false, features = ["cookies"] } +actix-web = { version = "4.0.0-beta.4", default-features = false, features = ["cookies"] } actix-http = "3.0.0-beta.4" diff --git a/actix-multipart/Cargo.toml b/actix-multipart/Cargo.toml index 34817e087..23c41a0f3 100644 --- a/actix-multipart/Cargo.toml +++ b/actix-multipart/Cargo.toml @@ -16,7 +16,7 @@ name = "actix_multipart" path = "src/lib.rs" [dependencies] -actix-web = { version = "4.0.0-beta.3", default-features = false } +actix-web = { version = "4.0.0-beta.4", default-features = false } actix-utils = "3.0.0-beta.2" bytes = "1" diff --git a/actix-web-actors/Cargo.toml b/actix-web-actors/Cargo.toml index 5ddbe53ce..e305cd750 100644 --- a/actix-web-actors/Cargo.toml +++ b/actix-web-actors/Cargo.toml @@ -19,7 +19,7 @@ path = "src/lib.rs" actix = { version = "0.11.0-beta.3", default-features = false } actix-codec = "0.4.0-beta.1" actix-http = "3.0.0-beta.4" -actix-web = { version = "4.0.0-beta.3", default-features = false } +actix-web = { version = "4.0.0-beta.4", default-features = false } bytes = "1" bytestring = "1" diff --git a/actix-web-codegen/Cargo.toml b/actix-web-codegen/Cargo.toml index e74e0ed85..d8a189565 100644 --- a/actix-web-codegen/Cargo.toml +++ b/actix-web-codegen/Cargo.toml @@ -20,7 +20,7 @@ proc-macro2 = "1" [dev-dependencies] actix-rt = "2.1" -actix-web = "4.0.0-beta.3" +actix-web = "4.0.0-beta.4" futures-util = { version = "0.3.7", default-features = false } trybuild = "1" rustversion = "1" diff --git a/actix-web-codegen/README.md b/actix-web-codegen/README.md index 5820bb443..9552d4b56 100644 --- a/actix-web-codegen/README.md +++ b/actix-web-codegen/README.md @@ -3,11 +3,11 @@ > Routing and runtime macros for Actix Web. [![crates.io](https://img.shields.io/crates/v/actix-web-codegen?label=latest)](https://crates.io/crates/actix-web-codegen) -[![Documentation](https://docs.rs/actix-web-codegen/badge.svg?version=0.5.0-beta.1)](https://docs.rs/actix-web-codegen/0.5.0-beta.1) +[![Documentation](https://docs.rs/actix-web-codegen/badge.svg?version=0.5.0-beta.2)](https://docs.rs/actix-web-codegen/0.5.0-beta.2) [![Version](https://img.shields.io/badge/rustc-1.46+-ab6000.svg)](https://blog.rust-lang.org/2020/03/12/Rust-1.46.html) ![License](https://img.shields.io/crates/l/actix-web-codegen.svg)
-[![dependency status](https://deps.rs/crate/actix-web-codegen/0.5.0-beta.1/status.svg)](https://deps.rs/crate/actix-web-codegen/0.5.0-beta.1) +[![dependency status](https://deps.rs/crate/actix-web-codegen/0.5.0-beta.2/status.svg)](https://deps.rs/crate/actix-web-codegen/0.5.0-beta.2) [![Download](https://img.shields.io/crates/d/actix-web-codegen.svg)](https://crates.io/crates/actix-web-codegen) [![Join the chat at https://gitter.im/actix/actix](https://badges.gitter.im/actix/actix.svg)](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) diff --git a/awc/Cargo.toml b/awc/Cargo.toml index c55465b5d..bca3ada4a 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -73,7 +73,7 @@ features = ["vendored"] optional = true [dev-dependencies] -actix-web = { version = "4.0.0-beta.3", features = ["openssl"] } +actix-web = { version = "4.0.0-beta.4", features = ["openssl"] } actix-http = { version = "3.0.0-beta.4", features = ["openssl"] } actix-http-test = { version = "3.0.0-beta.2", features = ["openssl"] } actix-utils = "3.0.0-beta.1" From 18c3783a1c0bd74cac3e764fb264c4bf53986895 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Tue, 9 Mar 2021 23:35:42 +0000 Subject: [PATCH 23/34] prepare actix-http-test release 3.0.0-beta.3 --- actix-http-test/CHANGES.md | 4 ++++ actix-http-test/Cargo.toml | 2 +- actix-http/Cargo.toml | 2 +- awc/Cargo.toml | 2 +- 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/actix-http-test/CHANGES.md b/actix-http-test/CHANGES.md index 2f47d700d..d6a2cdd9b 100644 --- a/actix-http-test/CHANGES.md +++ b/actix-http-test/CHANGES.md @@ -3,6 +3,10 @@ ## Unreleased - 2021-xx-xx +## 3.0.0-beta.3 - 2021-03-09 +* No notable changes. + + ## 3.0.0-beta.2 - 2021-02-10 * No notable changes. diff --git a/actix-http-test/Cargo.toml b/actix-http-test/Cargo.toml index 855f53fe9..69b2a3335 100644 --- a/actix-http-test/Cargo.toml +++ b/actix-http-test/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-http-test" -version = "3.0.0-beta.2" +version = "3.0.0-beta.3" authors = ["Nikolay Kim "] description = "Various helpers for Actix applications to use during testing" readme = "README.md" diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index d10e1ebdf..edcc7efd5 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -89,7 +89,7 @@ trust-dns-resolver = { version = "0.20.0", optional = true } [dev-dependencies] actix-server = "2.0.0-beta.3" -actix-http-test = { version = "3.0.0-beta.2", features = ["openssl"] } +actix-http-test = { version = "3.0.0-beta.3", features = ["openssl"] } actix-tls = { version = "3.0.0-beta.4", features = ["openssl"] } criterion = "0.3" env_logger = "0.8" diff --git a/awc/Cargo.toml b/awc/Cargo.toml index bca3ada4a..981b93a52 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -75,7 +75,7 @@ optional = true [dev-dependencies] actix-web = { version = "4.0.0-beta.4", features = ["openssl"] } actix-http = { version = "3.0.0-beta.4", features = ["openssl"] } -actix-http-test = { version = "3.0.0-beta.2", features = ["openssl"] } +actix-http-test = { version = "3.0.0-beta.3", features = ["openssl"] } actix-utils = "3.0.0-beta.1" actix-server = "2.0.0-beta.3" actix-tls = { version = "3.0.0-beta.4", features = ["openssl", "rustls"] } From 3451d6874f3efea0ae5dfcf90643c9450686093f Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Tue, 9 Mar 2021 23:39:40 +0000 Subject: [PATCH 24/34] prepare actix-files release 0.6.0-beta.3 --- actix-files/CHANGES.md | 4 ++++ actix-files/Cargo.toml | 2 +- actix-files/README.md | 4 ++-- actix-http-test/README.md | 4 ++-- actix-multipart/README.md | 1 - 5 files changed, 9 insertions(+), 6 deletions(-) diff --git a/actix-files/CHANGES.md b/actix-files/CHANGES.md index 6e2a241ac..c035d5afe 100644 --- a/actix-files/CHANGES.md +++ b/actix-files/CHANGES.md @@ -3,6 +3,10 @@ ## Unreleased - 2021-xx-xx +## 0.6.0-beta.3 - 2021-03-09 +* No notable changes. + + ## 0.6.0-beta.2 - 2021-02-10 * Fix If-Modified-Since and If-Unmodified-Since to not compare using sub-second timestamps. [#1887] * Replace `v_htmlescape` with `askama_escape`. [#1953] diff --git a/actix-files/Cargo.toml b/actix-files/Cargo.toml index 80b0310bc..49cd6966c 100644 --- a/actix-files/Cargo.toml +++ b/actix-files/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-files" -version = "0.6.0-beta.2" +version = "0.6.0-beta.3" authors = ["Nikolay Kim "] description = "Static file serving for Actix Web" readme = "README.md" diff --git a/actix-files/README.md b/actix-files/README.md index a4f0445aa..c7b7424ec 100644 --- a/actix-files/README.md +++ b/actix-files/README.md @@ -3,11 +3,11 @@ > Static file serving for Actix Web [![crates.io](https://img.shields.io/crates/v/actix-files?label=latest)](https://crates.io/crates/actix-files) -[![Documentation](https://docs.rs/actix-files/badge.svg?version=0.5.0)](https://docs.rs/actix-files/0.5.0) +[![Documentation](https://docs.rs/actix-files/badge.svg?version=0.6.0-beta.3)](https://docs.rs/actix-files/0.6.0-beta.3) [![Version](https://img.shields.io/badge/rustc-1.46+-ab6000.svg)](https://blog.rust-lang.org/2020/03/12/Rust-1.46.html) ![License](https://img.shields.io/crates/l/actix-files.svg)
-[![dependency status](https://deps.rs/crate/actix-files/0.5.0/status.svg)](https://deps.rs/crate/actix-files/0.5.0) +[![dependency status](https://deps.rs/crate/actix-files/0.6.0-beta.3/status.svg)](https://deps.rs/crate/actix-files/0.6.0-beta.3) [![Download](https://img.shields.io/crates/d/actix-files.svg)](https://crates.io/crates/actix-files) [![Join the chat at https://gitter.im/actix/actix](https://badges.gitter.im/actix/actix.svg)](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) diff --git a/actix-http-test/README.md b/actix-http-test/README.md index 66f15979d..8cec94808 100644 --- a/actix-http-test/README.md +++ b/actix-http-test/README.md @@ -3,9 +3,9 @@ > Various helpers for Actix applications to use during testing. [![crates.io](https://img.shields.io/crates/v/actix-http-test?label=latest)](https://crates.io/crates/actix-http-test) -[![Documentation](https://docs.rs/actix-http-test/badge.svg?version=2.1.0)](https://docs.rs/actix-http-test/2.1.0) +[![Documentation](https://docs.rs/actix-http-test/badge.svg?version=3.0.0-beta.3)](https://docs.rs/actix-http-test/3.0.0-beta.3) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-http-test) -[![Dependency Status](https://deps.rs/crate/actix-http-test/2.1.0/status.svg)](https://deps.rs/crate/actix-http-test/2.1.0) +[![Dependency Status](https://deps.rs/crate/actix-http-test/3.0.0-beta.3/status.svg)](https://deps.rs/crate/actix-http-test/3.0.0-beta.3) [![Join the chat at https://gitter.im/actix/actix-web](https://badges.gitter.im/actix/actix-web.svg)](https://gitter.im/actix/actix-web?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) ## Documentation & Resources diff --git a/actix-multipart/README.md b/actix-multipart/README.md index defcf7828..d39d8496d 100644 --- a/actix-multipart/README.md +++ b/actix-multipart/README.md @@ -9,7 +9,6 @@
[![dependency status](https://deps.rs/crate/actix-multipart/0.4.0-beta.2/status.svg)](https://deps.rs/crate/actix-multipart/0.4.0-beta.2) [![Download](https://img.shields.io/crates/d/actix-multipart.svg)](https://crates.io/crates/actix-multipart) -[![Join the chat at https://gitter.im/actix/actix](https://badges.gitter.im/actix/actix.svg)](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) ## Documentation & Resources From 5e9a3eb6ae801a8b1370d917926ab20df713d421 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Tue, 9 Mar 2021 23:40:50 +0000 Subject: [PATCH 25/34] prepare actix-multipart release 0.4.0-beta.3 --- actix-multipart/CHANGES.md | 4 ++++ actix-multipart/Cargo.toml | 2 +- actix-multipart/README.md | 4 ++-- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/actix-multipart/CHANGES.md b/actix-multipart/CHANGES.md index 2142ebf4b..ce5bd57d7 100644 --- a/actix-multipart/CHANGES.md +++ b/actix-multipart/CHANGES.md @@ -3,6 +3,10 @@ ## Unreleased - 2021-xx-xx +## 0.4.0-beta.3 - 2021-03-09 +* No notable changes. + + ## 0.4.0-beta.2 - 2021-02-10 * No notable changes. diff --git a/actix-multipart/Cargo.toml b/actix-multipart/Cargo.toml index 23c41a0f3..9a3ea7bb5 100644 --- a/actix-multipart/Cargo.toml +++ b/actix-multipart/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-multipart" -version = "0.4.0-beta.2" +version = "0.4.0-beta.3" authors = ["Nikolay Kim "] description = "Multipart form support for Actix Web" readme = "README.md" diff --git a/actix-multipart/README.md b/actix-multipart/README.md index d39d8496d..e4a54ac47 100644 --- a/actix-multipart/README.md +++ b/actix-multipart/README.md @@ -3,11 +3,11 @@ > Multipart form support for Actix Web. [![crates.io](https://img.shields.io/crates/v/actix-multipart?label=latest)](https://crates.io/crates/actix-multipart) -[![Documentation](https://docs.rs/actix-multipart/badge.svg?version=0.4.0-beta.2)](https://docs.rs/actix-multipart/0.4.0-beta.2) +[![Documentation](https://docs.rs/actix-multipart/badge.svg?version=0.4.0-beta.3)](https://docs.rs/actix-multipart/0.4.0-beta.3) [![Version](https://img.shields.io/badge/rustc-1.46+-ab6000.svg)](https://blog.rust-lang.org/2020/03/12/Rust-1.46.html) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-multipart.svg)
-[![dependency status](https://deps.rs/crate/actix-multipart/0.4.0-beta.2/status.svg)](https://deps.rs/crate/actix-multipart/0.4.0-beta.2) +[![dependency status](https://deps.rs/crate/actix-multipart/0.4.0-beta.3/status.svg)](https://deps.rs/crate/actix-multipart/0.4.0-beta.3) [![Download](https://img.shields.io/crates/d/actix-multipart.svg)](https://crates.io/crates/actix-multipart) ## Documentation & Resources From b62da7e86b97ebe3669d3ba4dbc0628a2c90efc7 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Tue, 9 Mar 2021 23:44:26 +0000 Subject: [PATCH 26/34] prepare actix-web-actors release 4.0.0-beta.3 --- actix-web-actors/CHANGES.md | 4 ++++ actix-web-actors/Cargo.toml | 2 +- actix-web-actors/README.md | 4 ++-- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/actix-web-actors/CHANGES.md b/actix-web-actors/CHANGES.md index acd9ceada..80eef08c1 100644 --- a/actix-web-actors/CHANGES.md +++ b/actix-web-actors/CHANGES.md @@ -3,6 +3,10 @@ ## Unreleased - 2021-xx-xx +## 4.0.0-beta.3 - 2021-03-09 +* No notable changes. + + ## 4.0.0-beta.2 - 2021-02-10 * No notable changes. diff --git a/actix-web-actors/Cargo.toml b/actix-web-actors/Cargo.toml index e305cd750..77663540c 100644 --- a/actix-web-actors/Cargo.toml +++ b/actix-web-actors/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-web-actors" -version = "4.0.0-beta.2" +version = "4.0.0-beta.3" authors = ["Nikolay Kim "] description = "Actix actors support for Actix Web" readme = "README.md" diff --git a/actix-web-actors/README.md b/actix-web-actors/README.md index c9b588153..6d9d573d7 100644 --- a/actix-web-actors/README.md +++ b/actix-web-actors/README.md @@ -3,11 +3,11 @@ > Actix actors support for Actix Web. [![crates.io](https://img.shields.io/crates/v/actix-web-actors?label=latest)](https://crates.io/crates/actix-web-actors) -[![Documentation](https://docs.rs/actix-web-actors/badge.svg?version=0.5.0)](https://docs.rs/actix-web-actors/0.5.0) +[![Documentation](https://docs.rs/actix-web-actors/badge.svg?version=4.0.0-beta.3)](https://docs.rs/actix-web-actors/4.0.0-beta.3) [![Version](https://img.shields.io/badge/rustc-1.46+-ab6000.svg)](https://blog.rust-lang.org/2020/03/12/Rust-1.46.html) ![License](https://img.shields.io/crates/l/actix-web-actors.svg)
-[![dependency status](https://deps.rs/crate/actix-web-actors/0.5.0/status.svg)](https://deps.rs/crate/actix-web-actors/0.5.0) +[![dependency status](https://deps.rs/crate/actix-web-actors/4.0.0-beta.3/status.svg)](https://deps.rs/crate/actix-web-actors/4.0.0-beta.3) [![Download](https://img.shields.io/crates/d/actix-web-actors.svg)](https://crates.io/crates/actix-web-actors) [![Join the chat at https://gitter.im/actix/actix](https://badges.gitter.im/actix/actix.svg)](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) From d0c1f1a84cb0d1fdf86ccf715a30f7989c988a51 Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Tue, 9 Mar 2021 17:31:50 -0800 Subject: [PATCH 27/34] remove actix_http::client::pool::Protocol (#2061) --- actix-http/src/client/connector.rs | 24 +++++------------------- actix-http/src/client/mod.rs | 2 +- actix-http/src/client/pool.rs | 23 ++++------------------- 3 files changed, 10 insertions(+), 39 deletions(-) diff --git a/actix-http/src/client/connector.rs b/actix-http/src/client/connector.rs index 1a926fd6c..28c865ffa 100644 --- a/actix-http/src/client/connector.rs +++ b/actix-http/src/client/connector.rs @@ -20,8 +20,9 @@ use http::Uri; use super::config::ConnectorConfig; use super::connection::{Connection, EitherIoConnection}; use super::error::ConnectError; -use super::pool::{ConnectionPool, Protocol}; +use super::pool::ConnectionPool; use super::Connect; +use super::Protocol; #[cfg(feature = "openssl")] use actix_tls::connect::ssl::openssl::SslConnector as OpensslConnector; @@ -148,7 +149,7 @@ where + 'static, { /// Connection timeout, i.e. max time to connect to remote host including dns name resolution. - /// Set to 1 second by default. + /// Set to 5 second by default. pub fn timeout(mut self, timeout: Duration) -> Self { self.config.timeout = timeout; self @@ -162,6 +163,7 @@ where } #[cfg(feature = "rustls")] + /// Use custom `SslConnector` instance. pub fn rustls(mut self, connector: Arc) -> Self { self.ssl = SslConnector::Rustls(connector); self @@ -254,8 +256,7 @@ where /// its combinator chain. pub fn finish( self, - ) -> impl Service + Clone - { + ) -> impl Service { let local_address = self.config.local_address; let timeout = self.config.timeout; @@ -392,21 +393,6 @@ where tls_pool: Option>, } -impl Clone for InnerConnector -where - S1: Service + 'static, - S2: Service + 'static, - Io1: AsyncRead + AsyncWrite + Unpin + 'static, - Io2: AsyncRead + AsyncWrite + Unpin + 'static, -{ - fn clone(&self) -> Self { - InnerConnector { - tcp_pool: self.tcp_pool.clone(), - tls_pool: self.tls_pool.as_ref().cloned(), - } - } -} - impl Service for InnerConnector where S1: Service + 'static, diff --git a/actix-http/src/client/mod.rs b/actix-http/src/client/mod.rs index 5f5e57edb..c6f998c2a 100644 --- a/actix-http/src/client/mod.rs +++ b/actix-http/src/client/mod.rs @@ -17,7 +17,7 @@ pub use actix_tls::connect::{ pub use self::connection::Connection; pub use self::connector::Connector; pub use self::error::{ConnectError, FreezeRequestError, InvalidUrl, SendRequestError}; -pub use self::pool::Protocol; +pub use crate::Protocol; #[derive(Clone)] pub struct Connect { diff --git a/actix-http/src/client/pool.rs b/actix-http/src/client/pool.rs index 3800696fa..b5b4a30d3 100644 --- a/actix-http/src/client/pool.rs +++ b/actix-http/src/client/pool.rs @@ -25,13 +25,7 @@ use super::connection::{ConnectionType, H2Connection, IoConnection}; use super::error::ConnectError; use super::h2proto::handshake; use super::Connect; - -#[derive(Clone, Copy, PartialEq)] -/// Protocol version -pub enum Protocol { - Http1, - Http2, -} +use super::Protocol; #[derive(Hash, Eq, PartialEq, Clone, Debug)] pub(crate) struct Key { @@ -148,18 +142,6 @@ where } } -impl Clone for ConnectionPool -where - Io: AsyncWrite + Unpin + 'static, -{ - fn clone(&self) -> Self { - Self { - connector: self.connector.clone(), - inner: self.inner.clone(), - } - } -} - impl Service for ConnectionPool where S: Service + 'static, @@ -243,6 +225,9 @@ where None => { let (io, proto) = connector.call(req).await?; + // TODO: remove when http3 is added in support. + assert!(proto != Protocol::Http3); + if proto == Protocol::Http1 { Ok(IoConnection::new( ConnectionType::H1(io), From a2b0e866325b1492c2daa9fd0593ce5d91e40af8 Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Wed, 10 Mar 2021 15:57:32 -0800 Subject: [PATCH 28/34] simplify connector generic type (#2063) --- actix-http/CHANGES.md | 4 ++++ actix-http/src/client/connector.rs | 22 +++++++++++----------- awc/src/builder.rs | 11 +++++------ awc/src/lib.rs | 1 - 4 files changed, 20 insertions(+), 18 deletions(-) diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index a5e69eda3..e96c22274 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -1,6 +1,10 @@ # Changes ## Unreleased - 2021-xx-xx +### Chaged +* `client::Connector` type now only have one generic type for `actix_service::Service`. [#2063] + +[#2063]: https://github.com/actix/actix-web/pull/2063 ## 3.0.0-beta.4 - 2021-03-08 diff --git a/actix-http/src/client/connector.rs b/actix-http/src/client/connector.rs index 28c865ffa..ebdbbb6eb 100644 --- a/actix-http/src/client/connector.rs +++ b/actix-http/src/client/connector.rs @@ -1,7 +1,6 @@ use std::{ fmt, future::Future, - marker::PhantomData, net::IpAddr, pin::Pin, task::{Context, Poll}, @@ -15,6 +14,7 @@ use actix_tls::connect::{ new_connector, Connect as TcpConnect, Connection as TcpConnection, Resolver, }; use actix_utils::timeout::{TimeoutError, TimeoutService}; +use futures_core::ready; use http::Uri; use super::config::ConnectorConfig; @@ -54,18 +54,17 @@ type SslConnector = (); /// .timeout(Duration::from_secs(5)) /// .finish(); /// ``` -pub struct Connector { +pub struct Connector { connector: T, config: ConnectorConfig, #[allow(dead_code)] ssl: SslConnector, - _phantom: PhantomData, } pub trait Io: AsyncRead + AsyncWrite + Unpin {} impl Io for T {} -impl Connector<(), ()> { +impl Connector<()> { #[allow(clippy::new_ret_no_self, clippy::let_unit_value)] pub fn new() -> Connector< impl Service< @@ -73,13 +72,11 @@ impl Connector<(), ()> { Response = TcpConnection, Error = actix_tls::connect::ConnectError, > + Clone, - TcpStream, > { Connector { ssl: Self::build_ssl(vec![b"h2".to_vec(), b"http/1.1".to_vec()]), connector: new_connector(resolver::resolver()), config: ConnectorConfig::default(), - _phantom: PhantomData, } } @@ -118,9 +115,9 @@ impl Connector<(), ()> { fn build_ssl(_: Vec>) -> SslConnector {} } -impl Connector { +impl Connector { /// Use custom connector. - pub fn connector(self, connector: T1) -> Connector + pub fn connector(self, connector: T1) -> Connector where U1: AsyncRead + AsyncWrite + Unpin + fmt::Debug, T1: Service< @@ -133,12 +130,11 @@ impl Connector { connector, config: self.config, ssl: self.ssl, - _phantom: PhantomData, } } } -impl Connector +impl Connector where U: AsyncRead + AsyncWrite + Unpin + fmt::Debug + 'static, T: Service< @@ -405,7 +401,11 @@ where type Future = InnerConnectorResponse; fn poll_ready(&self, cx: &mut Context<'_>) -> Poll> { - self.tcp_pool.poll_ready(cx) + ready!(self.tcp_pool.poll_ready(cx))?; + if let Some(ref tls_pool) = self.tls_pool { + ready!(tls_pool.poll_ready(cx))?; + } + Poll::Ready(Ok(())) } fn call(&self, req: Connect) -> Self::Future { diff --git a/awc/src/builder.rs b/awc/src/builder.rs index 72a0f4f04..33f43ab93 100644 --- a/awc/src/builder.rs +++ b/awc/src/builder.rs @@ -21,14 +21,14 @@ use crate::{Client, ClientConfig, ConnectRequest, ConnectResponse, ConnectorServ /// /// This type can be used to construct an instance of `Client` through a /// builder-like pattern. -pub struct ClientBuilder { +pub struct ClientBuilder { default_headers: bool, max_http_version: Option, stream_window_size: Option, conn_window_size: Option, headers: HeaderMap, timeout: Option, - connector: Connector, + connector: Connector, middleware: M, local_address: Option, max_redirects: u8, @@ -42,7 +42,6 @@ impl ClientBuilder { Response = TcpConnection, Error = TcpConnectError, > + Clone, - TcpStream, (), > { ClientBuilder { @@ -60,7 +59,7 @@ impl ClientBuilder { } } -impl ClientBuilder +impl ClientBuilder where S: Service, Response = TcpConnection, Error = TcpConnectError> + Clone @@ -68,7 +67,7 @@ where Io: AsyncRead + AsyncWrite + Unpin + fmt::Debug + 'static, { /// Use custom connector service. - pub fn connector(self, connector: Connector) -> ClientBuilder + pub fn connector(self, connector: Connector) -> ClientBuilder where S1: Service< TcpConnect, @@ -213,7 +212,7 @@ where pub fn wrap( self, mw: M1, - ) -> ClientBuilder> + ) -> ClientBuilder> where M: Transform, M1: Transform, diff --git a/awc/src/lib.rs b/awc/src/lib.rs index 4cd1d5bb2..77e08fbbd 100644 --- a/awc/src/lib.rs +++ b/awc/src/lib.rs @@ -175,7 +175,6 @@ impl Client { Response = TcpConnection, Error = TcpConnectError, > + Clone, - TcpStream, > { ClientBuilder::new() } From 909ef0344b0fb5a30ead66420a89358e867b1933 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Thu, 11 Mar 2021 00:43:03 +0000 Subject: [PATCH 29/34] document client mod removal closes #2064 --- CHANGES.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index a6d4487dc..adaa273e8 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,8 @@ # Changes ## Unreleased - 2021-xx-xx +* The `client` mod was removed. Clients should now use `awc` directly. + [871ca5e4](https://github.com/actix/actix-web/commit/871ca5e4ae2bdc22d1ea02701c2992fa8d04aed7) ## 4.0.0-beta.4 - 2021-03-09 From 22dcc311937967b9da972b6304cb97d5684d2cae Mon Sep 17 00:00:00 2001 From: Ilya Averyanov Date: Thu, 11 Mar 2021 17:12:42 +0300 Subject: [PATCH 30/34] Fix logger middleware properly escape %% (#2067) --- CHANGES.md | 6 ++++++ src/middleware/logger.rs | 34 +++++++++++++++++++++++++++++++++- 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index adaa273e8..ef9ee900a 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,9 +1,15 @@ # Changes ## Unreleased - 2021-xx-xx +### Fixed +* Double ampersand in Logger format is escaped correctly. [#2067] + +### Removed * The `client` mod was removed. Clients should now use `awc` directly. [871ca5e4](https://github.com/actix/actix-web/commit/871ca5e4ae2bdc22d1ea02701c2992fa8d04aed7) +[#2067]: https://github.com/actix/actix-web/pull/2067 + ## 4.0.0-beta.4 - 2021-03-09 ### Changed diff --git a/src/middleware/logger.rs b/src/middleware/logger.rs index 5b5b5577c..cedf51197 100644 --- a/src/middleware/logger.rs +++ b/src/middleware/logger.rs @@ -363,7 +363,7 @@ impl Format { /// Returns `None` if the format string syntax is incorrect. pub fn new(s: &str) -> Format { log::trace!("Access log format: {}", s); - let fmt = Regex::new(r"%(\{([A-Za-z0-9\-_]+)\}([aioe]|xi)|[atPrUsbTD]?)").unwrap(); + let fmt = Regex::new(r"%(\{([A-Za-z0-9\-_]+)\}([aioe]|xi)|[%atPrUsbTD]?)").unwrap(); let mut idx = 0; let mut results = Vec::new(); @@ -639,6 +639,38 @@ mod tests { let _res = srv.call(req).await.unwrap(); } + #[actix_rt::test] + async fn test_escape_percent() { + let mut format = Format::new("%%{r}a"); + + let req = TestRequest::default() + .insert_header(( + header::FORWARDED, + header::HeaderValue::from_static("for=192.0.2.60;proto=http;by=203.0.113.43"), + )) + .to_srv_request(); + + let now = OffsetDateTime::now_utc(); + for unit in &mut format.0 { + unit.render_request(now, &req); + } + + let resp = HttpResponse::build(StatusCode::OK).force_close().finish(); + for unit in &mut format.0 { + unit.render_response(&resp); + } + + let entry_time = OffsetDateTime::now_utc(); + let render = |fmt: &mut fmt::Formatter<'_>| { + for unit in &format.0 { + unit.render(fmt, 1024, entry_time)?; + } + Ok(()) + }; + let s = format!("{}", FormatDisplay(&render)); + assert_eq!(s, "%{r}a"); + } + #[actix_rt::test] async fn test_url_path() { let mut format = Format::new("%T %U"); From 515d0e3fb45d2936fad00daf0ab709ab2cc62103 Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Sat, 13 Mar 2021 14:20:18 -0800 Subject: [PATCH 31/34] change behavior of default upgrade handler (#2071) --- actix-http/src/h1/dispatcher.rs | 39 ++++++++++++++++++-------- actix-http/src/h1/upgrade.rs | 8 +++--- src/server.rs | 49 +++++++++++++++++---------------- 3 files changed, 57 insertions(+), 39 deletions(-) diff --git a/actix-http/src/h1/dispatcher.rs b/actix-http/src/h1/dispatcher.rs index 6df579c0a..e5989e5ee 100644 --- a/actix-http/src/h1/dispatcher.rs +++ b/actix-http/src/h1/dispatcher.rs @@ -662,7 +662,7 @@ where // got timeout during shutdown, drop connection if this.flags.contains(Flags::SHUTDOWN) { return Err(DispatchError::DisconnectTimeout); - // exceed deadline. check for any outstanding tasks + // exceed deadline. check for any outstanding tasks } else if timer.deadline() >= *this.ka_expire { // have no task at hand. if this.state.is_empty() && this.write_buf.is_empty() { @@ -695,15 +695,15 @@ where this.flags.insert(Flags::STARTED | Flags::SHUTDOWN); this.state.set(State::None); } - // still have unfinished task. try to reset and register keep-alive. + // still have unfinished task. try to reset and register keep-alive. } else if let Some(deadline) = this.codec.config().keep_alive_expire() { timer.as_mut().reset(deadline); let _ = timer.poll(cx); } - // timer resolved but still have not met the keep-alive expire deadline. - // reset and register for later wakeup. + // timer resolved but still have not met the keep-alive expire deadline. + // reset and register for later wakeup. } else { timer.as_mut().reset(*this.ka_expire); let _ = timer.poll(cx); @@ -951,14 +951,15 @@ mod tests { use std::str; use actix_service::fn_service; - use futures_util::future::{lazy, ready}; + use futures_util::future::{lazy, ready, Ready}; use super::*; - use crate::test::TestBuffer; - use crate::{error::Error, KeepAlive}; use crate::{ + error::Error, h1::{ExpectHandler, UpgradeHandler}, - test::TestSeqBuffer, + http::Method, + test::{TestBuffer, TestSeqBuffer}, + HttpMessage, KeepAlive, }; fn find_slice(haystack: &[u8], needle: &[u8], from: usize) -> Option { @@ -1282,14 +1283,30 @@ mod tests { #[actix_rt::test] async fn test_upgrade() { + struct TestUpgrade; + + impl Service<(Request, Framed)> for TestUpgrade { + type Response = (); + type Error = Error; + type Future = Ready>; + + actix_service::always_ready!(); + + fn call(&self, (req, _framed): (Request, Framed)) -> Self::Future { + assert_eq!(req.method(), Method::GET); + assert!(req.upgrade()); + assert_eq!(req.headers().get("upgrade").unwrap(), "websocket"); + ready(Ok(())) + } + } + lazy(|cx| { let mut buf = TestSeqBuffer::empty(); let cfg = ServiceConfig::new(KeepAlive::Disabled, 0, 0, false, None); - let services = - HttpFlow::new(ok_service(), ExpectHandler, Some(UpgradeHandler)); + let services = HttpFlow::new(ok_service(), ExpectHandler, Some(TestUpgrade)); - let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new( + let h1 = Dispatcher::<_, _, _, _, TestUpgrade>::new( buf.clone(), cfg, services, diff --git a/actix-http/src/h1/upgrade.rs b/actix-http/src/h1/upgrade.rs index 5e24d84e3..fbfbc83c1 100644 --- a/actix-http/src/h1/upgrade.rs +++ b/actix-http/src/h1/upgrade.rs @@ -2,7 +2,7 @@ use std::task::Poll; use actix_codec::Framed; use actix_service::{Service, ServiceFactory}; -use futures_util::future::{ready, Ready}; +use futures_core::future::LocalBoxFuture; use crate::error::Error; use crate::h1::Codec; @@ -16,7 +16,7 @@ impl ServiceFactory<(Request, Framed)> for UpgradeHandler { type Config = (); type Service = UpgradeHandler; type InitError = Error; - type Future = Ready>; + type Future = LocalBoxFuture<'static, Result>; fn new_service(&self, _: ()) -> Self::Future { unimplemented!() @@ -26,11 +26,11 @@ impl ServiceFactory<(Request, Framed)> for UpgradeHandler { impl Service<(Request, Framed)> for UpgradeHandler { type Response = (); type Error = Error; - type Future = Ready>; + type Future = LocalBoxFuture<'static, Result>; actix_service::always_ready!(); fn call(&self, _: (Request, Framed)) -> Self::Future { - ready(Ok(())) + unimplemented!() } } diff --git a/src/server.rs b/src/server.rs index d69d6570d..99355593a 100644 --- a/src/server.rs +++ b/src/server.rs @@ -12,13 +12,6 @@ use actix_http::{ use actix_server::{Server, ServerBuilder}; use actix_service::{map_config, IntoServiceFactory, Service, ServiceFactory}; -#[cfg(unix)] -use actix_http::Protocol; -#[cfg(unix)] -use actix_service::pipeline_factory; -#[cfg(unix)] -use futures_util::future::ok; - #[cfg(feature = "openssl")] use actix_tls::accept::openssl::{AlpnError, SslAcceptor, SslAcceptorBuilder}; #[cfg(feature = "rustls")] @@ -489,7 +482,9 @@ where #[cfg(unix)] /// Start listening for unix domain (UDS) connections on existing listener. pub fn listen_uds(mut self, lst: std::os::unix::net::UnixListener) -> io::Result { + use actix_http::Protocol; use actix_rt::net::UnixStream; + use actix_service::pipeline_factory; let cfg = self.config.clone(); let factory = self.factory.clone(); @@ -511,19 +506,22 @@ where c.host.clone().unwrap_or_else(|| format!("{}", socket_addr)), ); - pipeline_factory(|io: UnixStream| ok((io, Protocol::Http1, None))).and_then({ - let svc = HttpService::build() - .keep_alive(c.keep_alive) - .client_timeout(c.client_timeout); + pipeline_factory(|io: UnixStream| async { Ok((io, Protocol::Http1, None)) }) + .and_then({ + let svc = HttpService::build() + .keep_alive(c.keep_alive) + .client_timeout(c.client_timeout); - let svc = if let Some(handler) = on_connect_fn.clone() { - svc.on_connect_ext(move |io: &_, ext: _| (&*handler)(io as &dyn Any, ext)) - } else { - svc - }; + let svc = if let Some(handler) = on_connect_fn.clone() { + svc.on_connect_ext(move |io: &_, ext: _| { + (&*handler)(io as &dyn Any, ext) + }) + } else { + svc + }; - svc.finish(map_config(factory(), move |_| config.clone())) - }) + svc.finish(map_config(factory(), move |_| config.clone())) + }) })?; Ok(self) } @@ -534,7 +532,9 @@ where where A: AsRef, { + use actix_http::Protocol; use actix_rt::net::UnixStream; + use actix_service::pipeline_factory; let cfg = self.config.clone(); let factory = self.factory.clone(); @@ -555,12 +555,13 @@ where socket_addr, c.host.clone().unwrap_or_else(|| format!("{}", socket_addr)), ); - pipeline_factory(|io: UnixStream| ok((io, Protocol::Http1, None))).and_then( - HttpService::build() - .keep_alive(c.keep_alive) - .client_timeout(c.client_timeout) - .finish(map_config(factory(), move |_| config.clone())), - ) + pipeline_factory(|io: UnixStream| async { Ok((io, Protocol::Http1, None)) }) + .and_then( + HttpService::build() + .keep_alive(c.keep_alive) + .client_timeout(c.client_timeout) + .finish(map_config(factory(), move |_| config.clone())), + ) }, )?; Ok(self) From a55e87faaa6ffe7b3f93aea082a783c6a64af9f7 Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Sun, 14 Mar 2021 19:33:51 -0700 Subject: [PATCH 32/34] refactor actix_http::helpers to generic over bufmut trait (#2069) --- actix-http/src/helpers.rs | 41 ++++++++++++++++++++++----------------- 1 file changed, 23 insertions(+), 18 deletions(-) diff --git a/actix-http/src/helpers.rs b/actix-http/src/helpers.rs index 13195f7db..74188717d 100644 --- a/actix-http/src/helpers.rs +++ b/actix-http/src/helpers.rs @@ -1,15 +1,15 @@ use std::io; -use bytes::{BufMut, BytesMut}; +use bytes::BufMut; use http::Version; const DIGITS_START: u8 = b'0'; -pub(crate) fn write_status_line(version: Version, n: u16, bytes: &mut BytesMut) { +pub(crate) fn write_status_line(version: Version, n: u16, buf: &mut B) { match version { - Version::HTTP_11 => bytes.put_slice(b"HTTP/1.1 "), - Version::HTTP_10 => bytes.put_slice(b"HTTP/1.0 "), - Version::HTTP_09 => bytes.put_slice(b"HTTP/0.9 "), + Version::HTTP_11 => buf.put_slice(b"HTTP/1.1 "), + Version::HTTP_10 => buf.put_slice(b"HTTP/1.0 "), + Version::HTTP_09 => buf.put_slice(b"HTTP/0.9 "), _ => { // other HTTP version handlers do not use this method } @@ -19,33 +19,36 @@ pub(crate) fn write_status_line(version: Version, n: u16, bytes: &mut BytesMut) let d10 = ((n / 10) % 10) as u8; let d1 = (n % 10) as u8; - bytes.put_u8(DIGITS_START + d100); - bytes.put_u8(DIGITS_START + d10); - bytes.put_u8(DIGITS_START + d1); + buf.put_u8(DIGITS_START + d100); + buf.put_u8(DIGITS_START + d10); + buf.put_u8(DIGITS_START + d1); // trailing space before reason - bytes.put_u8(b' '); + buf.put_u8(b' '); } /// NOTE: bytes object has to contain enough space -pub fn write_content_length(n: u64, bytes: &mut BytesMut) { +pub fn write_content_length(n: u64, buf: &mut B) { if n == 0 { - bytes.put_slice(b"\r\ncontent-length: 0\r\n"); + buf.put_slice(b"\r\ncontent-length: 0\r\n"); return; } - let mut buf = itoa::Buffer::new(); + let mut buffer = itoa::Buffer::new(); - bytes.put_slice(b"\r\ncontent-length: "); - bytes.put_slice(buf.format(n).as_bytes()); - bytes.put_slice(b"\r\n"); + buf.put_slice(b"\r\ncontent-length: "); + buf.put_slice(buffer.format(n).as_bytes()); + buf.put_slice(b"\r\n"); } -pub(crate) struct Writer<'a>(pub &'a mut BytesMut); +pub(crate) struct Writer<'a, B>(pub &'a mut B); -impl<'a> io::Write for Writer<'a> { +impl<'a, B> io::Write for Writer<'a, B> +where + B: BufMut, +{ fn write(&mut self, buf: &[u8]) -> io::Result { - self.0.extend_from_slice(buf); + self.0.put_slice(buf); Ok(buf.len()) } @@ -58,6 +61,8 @@ impl<'a> io::Write for Writer<'a> { mod tests { use std::str::from_utf8; + use bytes::BytesMut; + use super::*; #[test] From d93314a683d1a34826b23a3666e433283ffed850 Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Mon, 15 Mar 2021 03:59:42 -0700 Subject: [PATCH 33/34] fix awc readme example (#2076) --- awc/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/awc/README.md b/awc/README.md index ec3984d60..2e7dad320 100644 --- a/awc/README.md +++ b/awc/README.md @@ -27,7 +27,7 @@ fn main() { let res = client .get("http://www.rust-lang.org") // <- Create request builder - .header("User-Agent", "Actix-web") + .insert_header(("User-Agent", "Actix-web")) .send() // <- Send http request .await; From 69dd1a9bd643d8eb4f2689b6332eb16de4e06e90 Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Mon, 15 Mar 2021 19:56:23 -0700 Subject: [PATCH 34/34] Remove ConnectionLifetime trait. Simplify Acquired handling (#2072) --- actix-http/src/client/connection.rs | 26 +------ actix-http/src/client/h1proto.rs | 117 +++++++++++++--------------- actix-http/src/client/h2proto.rs | 22 +++--- actix-http/src/client/pool.rs | 18 ++--- actix-http/src/h1/expect.rs | 2 - actix-http/src/h1/upgrade.rs | 2 - src/app_service.rs | 1 - src/resource.rs | 1 - src/scope.rs | 1 - 9 files changed, 76 insertions(+), 114 deletions(-) diff --git a/actix-http/src/client/connection.rs b/actix-http/src/client/connection.rs index 97ecd0515..9fb5c58cc 100644 --- a/actix-http/src/client/connection.rs +++ b/actix-http/src/client/connection.rs @@ -94,14 +94,6 @@ pub trait Connection { >; } -pub(crate) trait ConnectionLifetime: AsyncRead + AsyncWrite + 'static { - /// Close connection - fn close(self: Pin<&mut Self>); - - /// Release connection to the connection pool - fn release(self: Pin<&mut Self>); -} - #[doc(hidden)] /// HTTP client connection pub struct IoConnection @@ -110,7 +102,7 @@ where { io: Option>, created: time::Instant, - pool: Option>, + pool: Acquired, } impl fmt::Debug for IoConnection @@ -130,7 +122,7 @@ impl IoConnection { pub(crate) fn new( io: ConnectionType, created: time::Instant, - pool: Option>, + pool: Acquired, ) -> Self { IoConnection { pool, @@ -139,13 +131,9 @@ impl IoConnection { } } - pub(crate) fn into_inner(self) -> (ConnectionType, time::Instant) { - (self.io.unwrap(), self.created) - } - #[cfg(test)] pub(crate) fn into_parts(self) -> (ConnectionType, time::Instant, Acquired) { - (self.io.unwrap(), self.created, self.pool.unwrap()) + (self.io.unwrap(), self.created, self.pool) } async fn send_request>( @@ -173,13 +161,7 @@ impl IoConnection { match self.io.take().unwrap() { ConnectionType::H1(io) => h1proto::open_tunnel(io, head.into()).await, ConnectionType::H2(io) => { - if let Some(mut pool) = self.pool.take() { - pool.release(IoConnection::new( - ConnectionType::H2(io), - self.created, - None, - )); - } + self.pool.release(ConnectionType::H2(io), self.created); Err(SendRequestError::TunnelNotSupported) } } diff --git a/actix-http/src/client/h1proto.rs b/actix-http/src/client/h1proto.rs index d2db18cec..cd0b45734 100644 --- a/actix-http/src/client/h1proto.rs +++ b/actix-http/src/client/h1proto.rs @@ -7,7 +7,7 @@ use actix_codec::{AsyncRead, AsyncWrite, Framed, ReadBuf}; use bytes::buf::BufMut; use bytes::{Bytes, BytesMut}; use futures_core::Stream; -use futures_util::{future::poll_fn, SinkExt, StreamExt}; +use futures_util::{future::poll_fn, SinkExt}; use crate::error::PayloadError; use crate::h1; @@ -19,7 +19,7 @@ use crate::http::{ use crate::message::{RequestHeadType, ResponseHead}; use crate::payload::{Payload, PayloadStream}; -use super::connection::{ConnectionLifetime, ConnectionType, IoConnection}; +use super::connection::ConnectionType; use super::error::{ConnectError, SendRequestError}; use super::pool::Acquired; use crate::body::{BodySize, MessageBody}; @@ -29,7 +29,7 @@ pub(crate) async fn send_request( mut head: RequestHeadType, body: B, created: time::Instant, - pool: Option>, + acquired: Acquired, ) -> Result<(ResponseHead, Payload), SendRequestError> where T: AsyncRead + AsyncWrite + Unpin + 'static, @@ -42,9 +42,9 @@ where if let Some(host) = head.as_ref().uri.host() { let mut wrt = BytesMut::with_capacity(host.len() + 5).writer(); - let _ = match head.as_ref().uri.port_u16() { - None | Some(80) | Some(443) => write!(wrt, "{}", host), - Some(port) => write!(wrt, "{}:{}", host, port), + match head.as_ref().uri.port_u16() { + None | Some(80) | Some(443) => write!(wrt, "{}", host)?, + Some(port) => write!(wrt, "{}:{}", host, port)?, }; match wrt.get_mut().split().freeze().try_into_value() { @@ -64,7 +64,7 @@ where let io = H1Connection { created, - pool, + acquired, io: Some(io), }; @@ -77,10 +77,8 @@ where let is_expect = if head.as_ref().headers.contains_key(EXPECT) { match body.size() { BodySize::None | BodySize::Empty | BodySize::Sized(0) => { - let pin_framed = Pin::new(&mut framed); - - let force_close = !pin_framed.codec_ref().keepalive(); - release_connection(pin_framed, force_close); + let keep_alive = framed.codec_ref().keepalive(); + framed.io_mut().on_release(keep_alive); // TODO: use a new variant or a new type better describing error violate // `Requirements for clients` session of above RFC @@ -128,8 +126,9 @@ where match pin_framed.codec_ref().message_type() { h1::MessageType::None => { - let force_close = !pin_framed.codec_ref().keepalive(); - release_connection(pin_framed, force_close); + let keep_alive = pin_framed.codec_ref().keepalive(); + pin_framed.io_mut().on_release(keep_alive); + Ok((head, Payload::None)) } _ => { @@ -151,12 +150,11 @@ where framed.send((head, BodySize::None).into()).await?; // read response - if let (Some(result), framed) = framed.into_future().await { - let head = result.map_err(SendRequestError::from)?; - Ok((head, framed)) - } else { - Err(SendRequestError::from(ConnectError::Disconnected)) - } + let head = poll_fn(|cx| Pin::new(&mut framed).poll_next(cx)) + .await + .ok_or(ConnectError::Disconnected)??; + + Ok((head, framed)) } /// send request body to the peer @@ -165,7 +163,7 @@ pub(crate) async fn send_body( mut framed: Pin<&mut Framed>, ) -> Result<(), SendRequestError> where - T: ConnectionLifetime + Unpin, + T: AsyncRead + AsyncWrite + Unpin + 'static, B: MessageBody, { actix_rt::pin!(body); @@ -200,7 +198,7 @@ where } } - SinkExt::flush(Pin::into_inner(framed)).await?; + SinkExt::flush(framed.get_mut()).await?; Ok(()) } @@ -208,41 +206,37 @@ where /// HTTP client connection pub struct H1Connection where - T: AsyncWrite + Unpin + 'static, + T: AsyncRead + AsyncWrite + Unpin + 'static, { /// T should be `Unpin` io: Option, created: time::Instant, - pool: Option>, + acquired: Acquired, } -impl ConnectionLifetime for H1Connection +impl H1Connection where T: AsyncRead + AsyncWrite + Unpin + 'static, { + fn on_release(&mut self, keep_alive: bool) { + if keep_alive { + self.release(); + } else { + self.close(); + } + } + /// Close connection - fn close(mut self: Pin<&mut Self>) { - if let Some(mut pool) = self.pool.take() { - if let Some(io) = self.io.take() { - pool.close(IoConnection::new( - ConnectionType::H1(io), - self.created, - None, - )); - } + fn close(&mut self) { + if let Some(io) = self.io.take() { + self.acquired.close(ConnectionType::H1(io)); } } /// Release this connection to the connection pool - fn release(mut self: Pin<&mut Self>) { - if let Some(mut pool) = self.pool.take() { - if let Some(io) = self.io.take() { - pool.release(IoConnection::new( - ConnectionType::H1(io), - self.created, - None, - )); - } + fn release(&mut self) { + if let Some(io) = self.io.take() { + self.acquired.release(ConnectionType::H1(io), self.created); } } } @@ -282,13 +276,19 @@ impl AsyncWrite for H1Connection } #[pin_project::pin_project] -pub(crate) struct PlStream { +pub(crate) struct PlStream +where + Io: AsyncRead + AsyncWrite + Unpin + 'static, +{ #[pin] - framed: Option>, + framed: Option, h1::ClientPayloadCodec>>, } -impl PlStream { - fn new(framed: Framed) -> Self { +impl PlStream +where + Io: AsyncRead + AsyncWrite + Unpin + 'static, +{ + fn new(framed: Framed, h1::ClientCodec>) -> Self { let framed = framed.into_map_codec(|codec| codec.into_payload_codec()); PlStream { @@ -297,24 +297,26 @@ impl PlStream { } } -impl Stream for PlStream { +impl Stream for PlStream +where + Io: AsyncRead + AsyncWrite + Unpin + 'static, +{ type Item = Result; fn poll_next( self: Pin<&mut Self>, cx: &mut Context<'_>, ) -> Poll> { - let mut this = self.project(); + let mut framed = self.project().framed.as_pin_mut().unwrap(); - match this.framed.as_mut().as_pin_mut().unwrap().next_item(cx)? { + match framed.as_mut().next_item(cx)? { Poll::Pending => Poll::Pending, Poll::Ready(Some(chunk)) => { if let Some(chunk) = chunk { Poll::Ready(Some(Ok(chunk))) } else { - let framed = this.framed.as_mut().as_pin_mut().unwrap(); - let force_close = !framed.codec_ref().keepalive(); - release_connection(framed, force_close); + let keep_alive = framed.codec_ref().keepalive(); + framed.io_mut().on_release(keep_alive); Poll::Ready(None) } } @@ -322,14 +324,3 @@ impl Stream for PlStream { } } } - -fn release_connection(framed: Pin<&mut Framed>, force_close: bool) -where - T: ConnectionLifetime, -{ - if !force_close && framed.is_read_buf_empty() && framed.is_write_buf_empty() { - framed.io_pin().release() - } else { - framed.io_pin().close() - } -} diff --git a/actix-http/src/client/h2proto.rs b/actix-http/src/client/h2proto.rs index 7292972de..82e81c7ff 100644 --- a/actix-http/src/client/h2proto.rs +++ b/actix-http/src/client/h2proto.rs @@ -17,7 +17,7 @@ use crate::message::{RequestHeadType, ResponseHead}; use crate::payload::Payload; use super::config::ConnectorConfig; -use super::connection::{ConnectionType, IoConnection}; +use super::connection::ConnectionType; use super::error::SendRequestError; use super::pool::Acquired; use crate::client::connection::H2Connection; @@ -27,7 +27,7 @@ pub(crate) async fn send_request( head: RequestHeadType, body: B, created: time::Instant, - pool: Option>, + acquired: Acquired, ) -> Result<(ResponseHead, Payload), SendRequestError> where T: AsyncRead + AsyncWrite + Unpin + 'static, @@ -103,13 +103,13 @@ where let res = poll_fn(|cx| io.poll_ready(cx)).await; if let Err(e) = res { - release(io, pool, created, e.is_io()); + release(io, acquired, created, e.is_io()); return Err(SendRequestError::from(e)); } let resp = match io.send_request(req, eof) { Ok((fut, send)) => { - release(io, pool, created, false); + release(io, acquired, created, false); if !eof { send_body(body, send).await?; @@ -117,7 +117,7 @@ where fut.await.map_err(SendRequestError::from)? } Err(e) => { - release(io, pool, created, e.is_io()); + release(io, acquired, created, e.is_io()); return Err(e.into()); } }; @@ -181,16 +181,14 @@ async fn send_body( /// release SendRequest object fn release( io: H2Connection, - pool: Option>, + acquired: Acquired, created: time::Instant, close: bool, ) { - if let Some(mut pool) = pool { - if close { - pool.close(IoConnection::new(ConnectionType::H2(io), created, None)); - } else { - pool.release(IoConnection::new(ConnectionType::H2(io), created, None)); - } + if close { + acquired.close(ConnectionType::H2(io)); + } else { + acquired.release(ConnectionType::H2(io), created); } } diff --git a/actix-http/src/client/pool.rs b/actix-http/src/client/pool.rs index b5b4a30d3..79adcf3e2 100644 --- a/actix-http/src/client/pool.rs +++ b/actix-http/src/client/pool.rs @@ -217,7 +217,7 @@ where // construct acquired. It's used to put Io type back to pool/ close the Io type. // permit is carried with the whole lifecycle of Acquired. - let acquired = Some(Acquired { key, inner, permit }); + let acquired = Acquired { key, inner, permit }; // match the connection and spawn new one if did not get anything. match conn { @@ -235,7 +235,7 @@ where acquired, )) } else { - let config = &acquired.as_ref().unwrap().inner.config; + let config = &acquired.inner.config; let (sender, connection) = handshake(io, config).await?; Ok(IoConnection::new( ConnectionType::H2(H2Connection::new(sender, connection)), @@ -346,14 +346,12 @@ where Io: AsyncRead + AsyncWrite + Unpin + 'static, { /// Close the IO. - pub(crate) fn close(&mut self, conn: IoConnection) { - let (conn, _) = conn.into_inner(); + pub(crate) fn close(&self, conn: ConnectionType) { self.inner.close(conn); } /// Release IO back into pool. - pub(crate) fn release(&mut self, conn: IoConnection) { - let (io, created) = conn.into_inner(); + pub(crate) fn release(&self, conn: ConnectionType, created: Instant) { let Acquired { key, inner, .. } = self; inner @@ -362,12 +360,12 @@ where .entry(key.clone()) .or_insert_with(VecDeque::new) .push_back(PooledConnection { - conn: io, + conn, created, used: Instant::now(), }); - let _ = &mut self.permit; + let _ = &self.permit; } } @@ -447,8 +445,8 @@ mod test { where T: AsyncRead + AsyncWrite + Unpin + 'static, { - let (conn, created, mut acquired) = conn.into_parts(); - acquired.release(IoConnection::new(conn, created, None)); + let (conn, created, acquired) = conn.into_parts(); + acquired.release(conn, created); } #[actix_rt::test] diff --git a/actix-http/src/h1/expect.rs b/actix-http/src/h1/expect.rs index 65856edf6..5015069bb 100644 --- a/actix-http/src/h1/expect.rs +++ b/actix-http/src/h1/expect.rs @@ -1,5 +1,3 @@ -use std::task::Poll; - use actix_service::{Service, ServiceFactory}; use futures_util::future::{ready, Ready}; diff --git a/actix-http/src/h1/upgrade.rs b/actix-http/src/h1/upgrade.rs index fbfbc83c1..e57ea8ae9 100644 --- a/actix-http/src/h1/upgrade.rs +++ b/actix-http/src/h1/upgrade.rs @@ -1,5 +1,3 @@ -use std::task::Poll; - use actix_codec::Framed; use actix_service::{Service, ServiceFactory}; use futures_core::future::LocalBoxFuture; diff --git a/src/app_service.rs b/src/app_service.rs index 9b4ae3354..be4ccf22f 100644 --- a/src/app_service.rs +++ b/src/app_service.rs @@ -1,6 +1,5 @@ use std::cell::RefCell; use std::rc::Rc; -use std::task::Poll; use actix_http::{Extensions, Request, Response}; use actix_router::{Path, ResourceDef, Router, Url}; diff --git a/src/resource.rs b/src/resource.rs index 944beeefa..1a5619de6 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -2,7 +2,6 @@ use std::cell::RefCell; use std::fmt; use std::future::Future; use std::rc::Rc; -use std::task::Poll; use actix_http::{Error, Extensions, Response}; use actix_router::IntoPattern; diff --git a/src/scope.rs b/src/scope.rs index dd02501b0..e96f5b9e8 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -2,7 +2,6 @@ use std::cell::RefCell; use std::fmt; use std::future::Future; use std::rc::Rc; -use std::task::Poll; use actix_http::Extensions; use actix_router::{ResourceDef, Router};