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 01/79] 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 02/79] 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 03/79] Remove ConnectionLifetime trait. Simplify Acquired handling (#2072) --- actix-http/src/client/connection.rs | 26 +------ actix-http/src/client/h1proto.rs | 117 +++++++++++++--------------- actix-http/src/client/h2proto.rs | 22 +++--- actix-http/src/client/pool.rs | 18 ++--- actix-http/src/h1/expect.rs | 2 - actix-http/src/h1/upgrade.rs | 2 - src/app_service.rs | 1 - src/resource.rs | 1 - src/scope.rs | 1 - 9 files changed, 76 insertions(+), 114 deletions(-) diff --git a/actix-http/src/client/connection.rs b/actix-http/src/client/connection.rs index 97ecd0515..9fb5c58cc 100644 --- a/actix-http/src/client/connection.rs +++ b/actix-http/src/client/connection.rs @@ -94,14 +94,6 @@ pub trait Connection { >; } -pub(crate) trait ConnectionLifetime: AsyncRead + AsyncWrite + 'static { - /// Close connection - fn close(self: Pin<&mut Self>); - - /// Release connection to the connection pool - fn release(self: Pin<&mut Self>); -} - #[doc(hidden)] /// HTTP client connection pub struct IoConnection @@ -110,7 +102,7 @@ where { io: Option>, created: time::Instant, - pool: Option>, + pool: Acquired, } impl fmt::Debug for IoConnection @@ -130,7 +122,7 @@ impl IoConnection { pub(crate) fn new( io: ConnectionType, created: time::Instant, - pool: Option>, + pool: Acquired, ) -> Self { IoConnection { pool, @@ -139,13 +131,9 @@ impl IoConnection { } } - pub(crate) fn into_inner(self) -> (ConnectionType, time::Instant) { - (self.io.unwrap(), self.created) - } - #[cfg(test)] pub(crate) fn into_parts(self) -> (ConnectionType, time::Instant, Acquired) { - (self.io.unwrap(), self.created, self.pool.unwrap()) + (self.io.unwrap(), self.created, self.pool) } async fn send_request>( @@ -173,13 +161,7 @@ impl IoConnection { match self.io.take().unwrap() { ConnectionType::H1(io) => h1proto::open_tunnel(io, head.into()).await, ConnectionType::H2(io) => { - if let Some(mut pool) = self.pool.take() { - pool.release(IoConnection::new( - ConnectionType::H2(io), - self.created, - None, - )); - } + self.pool.release(ConnectionType::H2(io), self.created); Err(SendRequestError::TunnelNotSupported) } } diff --git a/actix-http/src/client/h1proto.rs b/actix-http/src/client/h1proto.rs index d2db18cec..cd0b45734 100644 --- a/actix-http/src/client/h1proto.rs +++ b/actix-http/src/client/h1proto.rs @@ -7,7 +7,7 @@ use actix_codec::{AsyncRead, AsyncWrite, Framed, ReadBuf}; use bytes::buf::BufMut; use bytes::{Bytes, BytesMut}; use futures_core::Stream; -use futures_util::{future::poll_fn, SinkExt, StreamExt}; +use futures_util::{future::poll_fn, SinkExt}; use crate::error::PayloadError; use crate::h1; @@ -19,7 +19,7 @@ use crate::http::{ use crate::message::{RequestHeadType, ResponseHead}; use crate::payload::{Payload, PayloadStream}; -use super::connection::{ConnectionLifetime, ConnectionType, IoConnection}; +use super::connection::ConnectionType; use super::error::{ConnectError, SendRequestError}; use super::pool::Acquired; use crate::body::{BodySize, MessageBody}; @@ -29,7 +29,7 @@ pub(crate) async fn send_request( mut head: RequestHeadType, body: B, created: time::Instant, - pool: Option>, + acquired: Acquired, ) -> Result<(ResponseHead, Payload), SendRequestError> where T: AsyncRead + AsyncWrite + Unpin + 'static, @@ -42,9 +42,9 @@ where if let Some(host) = head.as_ref().uri.host() { let mut wrt = BytesMut::with_capacity(host.len() + 5).writer(); - let _ = match head.as_ref().uri.port_u16() { - None | Some(80) | Some(443) => write!(wrt, "{}", host), - Some(port) => write!(wrt, "{}:{}", host, port), + match head.as_ref().uri.port_u16() { + None | Some(80) | Some(443) => write!(wrt, "{}", host)?, + Some(port) => write!(wrt, "{}:{}", host, port)?, }; match wrt.get_mut().split().freeze().try_into_value() { @@ -64,7 +64,7 @@ where let io = H1Connection { created, - pool, + acquired, io: Some(io), }; @@ -77,10 +77,8 @@ where let is_expect = if head.as_ref().headers.contains_key(EXPECT) { match body.size() { BodySize::None | BodySize::Empty | BodySize::Sized(0) => { - let pin_framed = Pin::new(&mut framed); - - let force_close = !pin_framed.codec_ref().keepalive(); - release_connection(pin_framed, force_close); + let keep_alive = framed.codec_ref().keepalive(); + framed.io_mut().on_release(keep_alive); // TODO: use a new variant or a new type better describing error violate // `Requirements for clients` session of above RFC @@ -128,8 +126,9 @@ where match pin_framed.codec_ref().message_type() { h1::MessageType::None => { - let force_close = !pin_framed.codec_ref().keepalive(); - release_connection(pin_framed, force_close); + let keep_alive = pin_framed.codec_ref().keepalive(); + pin_framed.io_mut().on_release(keep_alive); + Ok((head, Payload::None)) } _ => { @@ -151,12 +150,11 @@ where framed.send((head, BodySize::None).into()).await?; // read response - if let (Some(result), framed) = framed.into_future().await { - let head = result.map_err(SendRequestError::from)?; - Ok((head, framed)) - } else { - Err(SendRequestError::from(ConnectError::Disconnected)) - } + let head = poll_fn(|cx| Pin::new(&mut framed).poll_next(cx)) + .await + .ok_or(ConnectError::Disconnected)??; + + Ok((head, framed)) } /// send request body to the peer @@ -165,7 +163,7 @@ pub(crate) async fn send_body( mut framed: Pin<&mut Framed>, ) -> Result<(), SendRequestError> where - T: ConnectionLifetime + Unpin, + T: AsyncRead + AsyncWrite + Unpin + 'static, B: MessageBody, { actix_rt::pin!(body); @@ -200,7 +198,7 @@ where } } - SinkExt::flush(Pin::into_inner(framed)).await?; + SinkExt::flush(framed.get_mut()).await?; Ok(()) } @@ -208,41 +206,37 @@ where /// HTTP client connection pub struct H1Connection where - T: AsyncWrite + Unpin + 'static, + T: AsyncRead + AsyncWrite + Unpin + 'static, { /// T should be `Unpin` io: Option, created: time::Instant, - pool: Option>, + acquired: Acquired, } -impl ConnectionLifetime for H1Connection +impl H1Connection where T: AsyncRead + AsyncWrite + Unpin + 'static, { + fn on_release(&mut self, keep_alive: bool) { + if keep_alive { + self.release(); + } else { + self.close(); + } + } + /// Close connection - fn close(mut self: Pin<&mut Self>) { - if let Some(mut pool) = self.pool.take() { - if let Some(io) = self.io.take() { - pool.close(IoConnection::new( - ConnectionType::H1(io), - self.created, - None, - )); - } + fn close(&mut self) { + if let Some(io) = self.io.take() { + self.acquired.close(ConnectionType::H1(io)); } } /// Release this connection to the connection pool - fn release(mut self: Pin<&mut Self>) { - if let Some(mut pool) = self.pool.take() { - if let Some(io) = self.io.take() { - pool.release(IoConnection::new( - ConnectionType::H1(io), - self.created, - None, - )); - } + fn release(&mut self) { + if let Some(io) = self.io.take() { + self.acquired.release(ConnectionType::H1(io), self.created); } } } @@ -282,13 +276,19 @@ impl AsyncWrite for H1Connection } #[pin_project::pin_project] -pub(crate) struct PlStream { +pub(crate) struct PlStream +where + Io: AsyncRead + AsyncWrite + Unpin + 'static, +{ #[pin] - framed: Option>, + framed: Option, h1::ClientPayloadCodec>>, } -impl PlStream { - fn new(framed: Framed) -> Self { +impl PlStream +where + Io: AsyncRead + AsyncWrite + Unpin + 'static, +{ + fn new(framed: Framed, h1::ClientCodec>) -> Self { let framed = framed.into_map_codec(|codec| codec.into_payload_codec()); PlStream { @@ -297,24 +297,26 @@ impl PlStream { } } -impl Stream for PlStream { +impl Stream for PlStream +where + Io: AsyncRead + AsyncWrite + Unpin + 'static, +{ type Item = Result; fn poll_next( self: Pin<&mut Self>, cx: &mut Context<'_>, ) -> Poll> { - let mut this = self.project(); + let mut framed = self.project().framed.as_pin_mut().unwrap(); - match this.framed.as_mut().as_pin_mut().unwrap().next_item(cx)? { + match framed.as_mut().next_item(cx)? { Poll::Pending => Poll::Pending, Poll::Ready(Some(chunk)) => { if let Some(chunk) = chunk { Poll::Ready(Some(Ok(chunk))) } else { - let framed = this.framed.as_mut().as_pin_mut().unwrap(); - let force_close = !framed.codec_ref().keepalive(); - release_connection(framed, force_close); + let keep_alive = framed.codec_ref().keepalive(); + framed.io_mut().on_release(keep_alive); Poll::Ready(None) } } @@ -322,14 +324,3 @@ impl Stream for PlStream { } } } - -fn release_connection(framed: Pin<&mut Framed>, force_close: bool) -where - T: ConnectionLifetime, -{ - if !force_close && framed.is_read_buf_empty() && framed.is_write_buf_empty() { - framed.io_pin().release() - } else { - framed.io_pin().close() - } -} diff --git a/actix-http/src/client/h2proto.rs b/actix-http/src/client/h2proto.rs index 7292972de..82e81c7ff 100644 --- a/actix-http/src/client/h2proto.rs +++ b/actix-http/src/client/h2proto.rs @@ -17,7 +17,7 @@ use crate::message::{RequestHeadType, ResponseHead}; use crate::payload::Payload; use super::config::ConnectorConfig; -use super::connection::{ConnectionType, IoConnection}; +use super::connection::ConnectionType; use super::error::SendRequestError; use super::pool::Acquired; use crate::client::connection::H2Connection; @@ -27,7 +27,7 @@ pub(crate) async fn send_request( head: RequestHeadType, body: B, created: time::Instant, - pool: Option>, + acquired: Acquired, ) -> Result<(ResponseHead, Payload), SendRequestError> where T: AsyncRead + AsyncWrite + Unpin + 'static, @@ -103,13 +103,13 @@ where let res = poll_fn(|cx| io.poll_ready(cx)).await; if let Err(e) = res { - release(io, pool, created, e.is_io()); + release(io, acquired, created, e.is_io()); return Err(SendRequestError::from(e)); } let resp = match io.send_request(req, eof) { Ok((fut, send)) => { - release(io, pool, created, false); + release(io, acquired, created, false); if !eof { send_body(body, send).await?; @@ -117,7 +117,7 @@ where fut.await.map_err(SendRequestError::from)? } Err(e) => { - release(io, pool, created, e.is_io()); + release(io, acquired, created, e.is_io()); return Err(e.into()); } }; @@ -181,16 +181,14 @@ async fn send_body( /// release SendRequest object fn release( io: H2Connection, - pool: Option>, + acquired: Acquired, created: time::Instant, close: bool, ) { - if let Some(mut pool) = pool { - if close { - pool.close(IoConnection::new(ConnectionType::H2(io), created, None)); - } else { - pool.release(IoConnection::new(ConnectionType::H2(io), created, None)); - } + if close { + acquired.close(ConnectionType::H2(io)); + } else { + acquired.release(ConnectionType::H2(io), created); } } diff --git a/actix-http/src/client/pool.rs b/actix-http/src/client/pool.rs index b5b4a30d3..79adcf3e2 100644 --- a/actix-http/src/client/pool.rs +++ b/actix-http/src/client/pool.rs @@ -217,7 +217,7 @@ where // construct acquired. It's used to put Io type back to pool/ close the Io type. // permit is carried with the whole lifecycle of Acquired. - let acquired = Some(Acquired { key, inner, permit }); + let acquired = Acquired { key, inner, permit }; // match the connection and spawn new one if did not get anything. match conn { @@ -235,7 +235,7 @@ where acquired, )) } else { - let config = &acquired.as_ref().unwrap().inner.config; + let config = &acquired.inner.config; let (sender, connection) = handshake(io, config).await?; Ok(IoConnection::new( ConnectionType::H2(H2Connection::new(sender, connection)), @@ -346,14 +346,12 @@ where Io: AsyncRead + AsyncWrite + Unpin + 'static, { /// Close the IO. - pub(crate) fn close(&mut self, conn: IoConnection) { - let (conn, _) = conn.into_inner(); + pub(crate) fn close(&self, conn: ConnectionType) { self.inner.close(conn); } /// Release IO back into pool. - pub(crate) fn release(&mut self, conn: IoConnection) { - let (io, created) = conn.into_inner(); + pub(crate) fn release(&self, conn: ConnectionType, created: Instant) { let Acquired { key, inner, .. } = self; inner @@ -362,12 +360,12 @@ where .entry(key.clone()) .or_insert_with(VecDeque::new) .push_back(PooledConnection { - conn: io, + conn, created, used: Instant::now(), }); - let _ = &mut self.permit; + let _ = &self.permit; } } @@ -447,8 +445,8 @@ mod test { where T: AsyncRead + AsyncWrite + Unpin + 'static, { - let (conn, created, mut acquired) = conn.into_parts(); - acquired.release(IoConnection::new(conn, created, None)); + let (conn, created, acquired) = conn.into_parts(); + acquired.release(conn, created); } #[actix_rt::test] diff --git a/actix-http/src/h1/expect.rs b/actix-http/src/h1/expect.rs index 65856edf6..5015069bb 100644 --- a/actix-http/src/h1/expect.rs +++ b/actix-http/src/h1/expect.rs @@ -1,5 +1,3 @@ -use std::task::Poll; - use actix_service::{Service, ServiceFactory}; use futures_util::future::{ready, Ready}; diff --git a/actix-http/src/h1/upgrade.rs b/actix-http/src/h1/upgrade.rs index fbfbc83c1..e57ea8ae9 100644 --- a/actix-http/src/h1/upgrade.rs +++ b/actix-http/src/h1/upgrade.rs @@ -1,5 +1,3 @@ -use std::task::Poll; - use actix_codec::Framed; use actix_service::{Service, ServiceFactory}; use futures_core::future::LocalBoxFuture; diff --git a/src/app_service.rs b/src/app_service.rs index 9b4ae3354..be4ccf22f 100644 --- a/src/app_service.rs +++ b/src/app_service.rs @@ -1,6 +1,5 @@ use std::cell::RefCell; use std::rc::Rc; -use std::task::Poll; use actix_http::{Extensions, Request, Response}; use actix_router::{Path, ResourceDef, Router, Url}; diff --git a/src/resource.rs b/src/resource.rs index 944beeefa..1a5619de6 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -2,7 +2,6 @@ use std::cell::RefCell; use std::fmt; use std::future::Future; use std::rc::Rc; -use std::task::Poll; use actix_http::{Error, Extensions, Response}; use actix_router::IntoPattern; diff --git a/src/scope.rs b/src/scope.rs index dd02501b0..e96f5b9e8 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -2,7 +2,6 @@ use std::cell::RefCell; use std::fmt; use std::future::Future; use std::rc::Rc; -use std::task::Poll; use actix_http::Extensions; use actix_router::{ResourceDef, Router}; From c8f6d37290e907a79a3acf1a20ad6327ba5bfd65 Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Tue, 16 Mar 2021 09:31:14 -0700 Subject: [PATCH 04/79] rename client io trait. reduce duplicate code (#2079) --- actix-http/src/client/connection.rs | 4 ++ actix-http/src/client/connector.rs | 22 ++++----- actix-http/src/client/mod.rs | 2 +- awc/src/connect.rs | 73 ++++------------------------- 4 files changed, 26 insertions(+), 75 deletions(-) diff --git a/actix-http/src/client/connection.rs b/actix-http/src/client/connection.rs index 9fb5c58cc..291148b5b 100644 --- a/actix-http/src/client/connection.rs +++ b/actix-http/src/client/connection.rs @@ -19,6 +19,10 @@ use super::error::SendRequestError; use super::pool::Acquired; use super::{h1proto, h2proto}; +pub trait ConnectionIo: AsyncRead + AsyncWrite + Unpin + 'static {} + +impl ConnectionIo for T {} + pub(crate) enum ConnectionType { H1(Io), H2(H2Connection), diff --git a/actix-http/src/client/connector.rs b/actix-http/src/client/connector.rs index ebdbbb6eb..0c9159b87 100644 --- a/actix-http/src/client/connector.rs +++ b/actix-http/src/client/connector.rs @@ -18,7 +18,7 @@ use futures_core::ready; use http::Uri; use super::config::ConnectorConfig; -use super::connection::{Connection, EitherIoConnection}; +use super::connection::{Connection, ConnectionIo, EitherIoConnection}; use super::error::ConnectError; use super::pool::ConnectionPool; use super::Connect; @@ -61,9 +61,6 @@ pub struct Connector { ssl: SslConnector, } -pub trait Io: AsyncRead + AsyncWrite + Unpin {} -impl Io for T {} - impl Connector<()> { #[allow(clippy::new_ret_no_self, clippy::let_unit_value)] pub fn new() -> Connector< @@ -281,16 +278,16 @@ where pub type DummyService = Box< dyn Service< Connect, - Response = (Box, Protocol), + Response = (Box, Protocol), Error = ConnectError, Future = futures_core::future::LocalBoxFuture< 'static, - Result<(Box, Protocol), ConnectError>, + Result<(Box, Protocol), ConnectError>, >, >, >; - InnerConnector::<_, DummyService, _, Box> { + InnerConnector::<_, DummyService, _, Box> { tcp_pool: ConnectionPool::new( tcp_service, self.config.no_disconnect_timeout(), @@ -334,9 +331,12 @@ where .map(|protos| protos.windows(2).any(|w| w == H2)) .unwrap_or(false); if h2 { - (Box::new(sock) as Box, Protocol::Http2) + ( + Box::new(sock) as Box, + Protocol::Http2, + ) } else { - (Box::new(sock) as Box, Protocol::Http1) + (Box::new(sock) as _, Protocol::Http1) } }) .map_err(ConnectError::from), @@ -354,9 +354,9 @@ where .map(|protos| protos.windows(2).any(|w| w == H2)) .unwrap_or(false); if h2 { - (Box::new(sock) as Box, Protocol::Http2) + (Box::new(sock) as _, Protocol::Http2) } else { - (Box::new(sock) as Box, Protocol::Http1) + (Box::new(sock) as _, Protocol::Http1) } }), ), diff --git a/actix-http/src/client/mod.rs b/actix-http/src/client/mod.rs index c6f998c2a..ce6aa3bc1 100644 --- a/actix-http/src/client/mod.rs +++ b/actix-http/src/client/mod.rs @@ -14,7 +14,7 @@ pub use actix_tls::connect::{ Connect as TcpConnect, ConnectError as TcpConnectError, Connection as TcpConnection, }; -pub use self::connection::Connection; +pub use self::connection::{Connection, ConnectionIo}; pub use self::connector::Connector; pub use self::error::{ConnectError, FreezeRequestError, InvalidUrl, SendRequestError}; pub use crate::Protocol; diff --git a/awc/src/connect.rs b/awc/src/connect.rs index a4abbc46b..d3bc01ed6 100644 --- a/awc/src/connect.rs +++ b/awc/src/connect.rs @@ -1,15 +1,16 @@ use std::{ - fmt, future::Future, - io, net, + net, pin::Pin, task::{Context, Poll}, }; -use actix_codec::{AsyncRead, AsyncWrite, Framed, ReadBuf}; +use actix_codec::Framed; use actix_http::{ body::Body, - client::{Connect as ClientConnect, ConnectError, Connection, SendRequestError}, + client::{ + Connect as ClientConnect, ConnectError, Connection, ConnectionIo, SendRequestError, + }, h1::ClientCodec, Payload, RequestHead, RequestHeadType, ResponseHead, }; @@ -123,7 +124,7 @@ impl Future for ConnectRequestFuture where Fut: Future>, C: Connection, - Io: AsyncRead + AsyncWrite + Unpin + 'static, + Io: ConnectionIo, { type Output = Result; @@ -138,14 +139,14 @@ where let fut = ConnectRequestFuture::Client { fut: connection.send_request(head, body), }; - self.as_mut().set(fut); + self.set(fut); } ConnectRequest::Tunnel(head, ..) => { // send request let fut = ConnectRequestFuture::Tunnel { fut: connection.open_tunnel(RequestHeadType::from(head)), }; - self.as_mut().set(fut); + self.set(fut); } } self.poll(cx) @@ -158,65 +159,11 @@ where } ConnectRequestProj::Tunnel { fut } => { let (head, framed) = ready!(fut.as_mut().poll(cx))?; - let framed = framed.into_map_io(|io| BoxedSocket(Box::new(Socket(io)))); + let framed = framed.into_map_io(|io| Box::new(io) as _); Poll::Ready(Ok(ConnectResponse::Tunnel(head, framed))) } } } } -trait AsyncSocket { - fn as_read(&self) -> &(dyn AsyncRead + Unpin); - fn as_read_mut(&mut self) -> &mut (dyn AsyncRead + Unpin); - fn as_write(&mut self) -> &mut (dyn AsyncWrite + Unpin); -} - -struct Socket(T); - -impl AsyncSocket for Socket { - fn as_read(&self) -> &(dyn AsyncRead + Unpin) { - &self.0 - } - fn as_read_mut(&mut self) -> &mut (dyn AsyncRead + Unpin) { - &mut self.0 - } - fn as_write(&mut self) -> &mut (dyn AsyncWrite + Unpin) { - &mut self.0 - } -} - -pub struct BoxedSocket(Box); - -impl fmt::Debug for BoxedSocket { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "BoxedSocket") - } -} - -impl AsyncRead for BoxedSocket { - fn poll_read( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - buf: &mut ReadBuf<'_>, - ) -> Poll> { - Pin::new(self.get_mut().0.as_read_mut()).poll_read(cx, buf) - } -} - -impl AsyncWrite for BoxedSocket { - fn poll_write( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - buf: &[u8], - ) -> Poll> { - Pin::new(self.get_mut().0.as_write()).poll_write(cx, buf) - } - - fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - Pin::new(self.get_mut().0.as_write()).poll_flush(cx) - } - - fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - Pin::new(self.get_mut().0.as_write()).poll_shutdown(cx) - } -} +pub type BoxedSocket = Box; From 3dc2d145efcde2feb3e5f20d9002390c283b1ea4 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Thu, 11 Mar 2021 03:48:38 +0000 Subject: [PATCH 05/79] import some traits as _ --- actix-http/examples/echo.rs | 2 +- actix-http/examples/echo2.rs | 2 +- actix-http/src/client/h1proto.rs | 4 ++-- actix-http/src/h2/dispatcher.rs | 6 ++++-- actix-http/src/h2/service.rs | 4 ++-- actix-http/src/service.rs | 19 ++++++++++++------- actix-http/src/time_parser.rs | 2 +- actix-http/tests/test_client.rs | 2 +- actix-http/tests/test_openssl.rs | 7 +++++-- actix-http/tests/test_rustls.rs | 3 ++- actix-http/tests/test_server.rs | 2 +- actix-http/tests/test_ws.rs | 2 +- 12 files changed, 33 insertions(+), 22 deletions(-) diff --git a/actix-http/examples/echo.rs b/actix-http/examples/echo.rs index 90d768cbe..176ac5c2b 100644 --- a/actix-http/examples/echo.rs +++ b/actix-http/examples/echo.rs @@ -3,7 +3,7 @@ use std::{env, io}; use actix_http::{Error, HttpService, Request, Response}; use actix_server::Server; use bytes::BytesMut; -use futures_util::StreamExt; +use futures_util::StreamExt as _; use http::header::HeaderValue; use log::info; diff --git a/actix-http/examples/echo2.rs b/actix-http/examples/echo2.rs index bc932ce8f..408a40114 100644 --- a/actix-http/examples/echo2.rs +++ b/actix-http/examples/echo2.rs @@ -4,7 +4,7 @@ use actix_http::http::HeaderValue; use actix_http::{Error, HttpService, Request, Response}; use actix_server::Server; use bytes::BytesMut; -use futures_util::StreamExt; +use futures_util::StreamExt as _; use log::info; async fn handle_request(mut req: Request) -> Result { diff --git a/actix-http/src/client/h1proto.rs b/actix-http/src/client/h1proto.rs index cd0b45734..75a3c1f3d 100644 --- a/actix-http/src/client/h1proto.rs +++ b/actix-http/src/client/h1proto.rs @@ -7,7 +7,7 @@ use actix_codec::{AsyncRead, AsyncWrite, Framed, ReadBuf}; use bytes::buf::BufMut; use bytes::{Bytes, BytesMut}; use futures_core::Stream; -use futures_util::{future::poll_fn, SinkExt}; +use futures_util::{future::poll_fn, SinkExt as _}; use crate::error::PayloadError; use crate::h1; @@ -198,7 +198,7 @@ where } } - SinkExt::flush(framed.get_mut()).await?; + framed.get_mut().flush().await?; Ok(()) } diff --git a/actix-http/src/h2/dispatcher.rs b/actix-http/src/h2/dispatcher.rs index 958c761d5..6e6cd5a2f 100644 --- a/actix-http/src/h2/dispatcher.rs +++ b/actix-http/src/h2/dispatcher.rs @@ -5,8 +5,10 @@ use actix_codec::{AsyncRead, AsyncWrite}; use actix_service::Service; use bytes::{Bytes, BytesMut}; use futures_core::ready; -use h2::server::{Connection, SendResponse}; -use h2::SendStream; +use h2::{ + server::{Connection, SendResponse}, + SendStream, +}; use http::header::{HeaderValue, CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCODING}; use log::{error, trace}; diff --git a/actix-http/src/h2/service.rs b/actix-http/src/h2/service.rs index 1dc290e49..c64139564 100644 --- a/actix-http/src/h2/service.rs +++ b/actix-http/src/h2/service.rs @@ -13,7 +13,7 @@ use actix_service::{ use bytes::Bytes; use futures_core::ready; use futures_util::future::ok; -use h2::server::{self, Handshake}; +use h2::server::{handshake, Handshake}; use log::error; use crate::body::MessageBody; @@ -307,7 +307,7 @@ where Some(self.cfg.clone()), addr, on_connect_data, - server::handshake(io), + handshake(io), ), } } diff --git a/actix-http/src/service.rs b/actix-http/src/service.rs index 89f3e3bb1..1a06cec3d 100644 --- a/actix-http/src/service.rs +++ b/actix-http/src/service.rs @@ -1,14 +1,19 @@ -use std::marker::PhantomData; -use std::pin::Pin; -use std::task::{Context, Poll}; -use std::{fmt, net, rc::Rc}; +use std::{ + fmt, + future::Future, + marker::PhantomData, + net, + pin::Pin, + rc::Rc, + task::{Context, Poll}, +}; use actix_codec::{AsyncRead, AsyncWrite, Framed}; use actix_rt::net::TcpStream; use actix_service::{pipeline_factory, IntoServiceFactory, Service, ServiceFactory}; use bytes::Bytes; -use futures_core::{ready, Future}; -use h2::server::{self, Handshake}; +use futures_core::ready; +use h2::server::{handshake, Handshake}; use pin_project::pin_project; use crate::body::MessageBody; @@ -562,7 +567,7 @@ where match proto { Protocol::Http2 => HttpServiceHandlerResponse { state: State::H2Handshake(Some(( - server::handshake(io), + handshake(io), self.cfg.clone(), self.flow.clone(), on_connect_data, diff --git a/actix-http/src/time_parser.rs b/actix-http/src/time_parser.rs index 46bf73037..fd82fd42e 100644 --- a/actix-http/src/time_parser.rs +++ b/actix-http/src/time_parser.rs @@ -1,7 +1,7 @@ use time::{Date, OffsetDateTime, PrimitiveDateTime}; /// Attempt to parse a `time` string as one of either RFC 1123, RFC 850, or asctime. -pub fn parse_http_date(time: &str) -> Option { +pub(crate) fn parse_http_date(time: &str) -> Option { try_parse_rfc_1123(time) .or_else(|| try_parse_rfc_850(time)) .or_else(|| try_parse_asctime(time)) diff --git a/actix-http/tests/test_client.rs b/actix-http/tests/test_client.rs index a50f2404d..758e39745 100644 --- a/actix-http/tests/test_client.rs +++ b/actix-http/tests/test_client.rs @@ -6,7 +6,7 @@ use actix_service::ServiceFactoryExt; use bytes::Bytes; use futures_util::{ future::{self, ok}, - StreamExt, + StreamExt as _, }; const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ diff --git a/actix-http/tests/test_openssl.rs b/actix-http/tests/test_openssl.rs index d5ec645a4..49a68a60d 100644 --- a/actix-http/tests/test_openssl.rs +++ b/actix-http/tests/test_openssl.rs @@ -12,8 +12,11 @@ use actix_http::{body, Error, HttpService, Request, Response}; use actix_http_test::test_server; use actix_service::{fn_service, ServiceFactoryExt}; use bytes::{Bytes, BytesMut}; -use futures_util::future::{err, ok, ready}; -use futures_util::stream::{once, Stream, StreamExt}; +use futures_core::Stream; +use futures_util::{ + future::{err, ok, ready}, + stream::{once, StreamExt as _}, +}; use openssl::{ pkey::PKey, ssl::{SslAcceptor, SslMethod}, diff --git a/actix-http/tests/test_rustls.rs b/actix-http/tests/test_rustls.rs index 81edb5c18..7a3cb1473 100644 --- a/actix-http/tests/test_rustls.rs +++ b/actix-http/tests/test_rustls.rs @@ -10,8 +10,9 @@ use actix_http_test::test_server; use actix_service::{fn_factory_with_config, fn_service}; use bytes::{Bytes, BytesMut}; +use futures_core::Stream; use futures_util::future::{self, err, ok}; -use futures_util::stream::{once, Stream, StreamExt}; +use futures_util::stream::{once, StreamExt as _}; use rustls::{ internal::pemfile::{certs, pkcs8_private_keys}, NoClientAuth, ServerConfig as RustlsServerConfig, diff --git a/actix-http/tests/test_server.rs b/actix-http/tests/test_server.rs index a4c1f92b5..6d145400c 100644 --- a/actix-http/tests/test_server.rs +++ b/actix-http/tests/test_server.rs @@ -7,7 +7,7 @@ use actix_rt::time::sleep; use actix_service::fn_service; use bytes::Bytes; use futures_util::future::{self, err, ok, ready, FutureExt}; -use futures_util::stream::{once, StreamExt}; +use futures_util::stream::{once, StreamExt as _}; use regex::Regex; use actix_http::HttpMessage; diff --git a/actix-http/tests/test_ws.rs b/actix-http/tests/test_ws.rs index 7ed9b0df1..3b90b4e54 100644 --- a/actix-http/tests/test_ws.rs +++ b/actix-http/tests/test_ws.rs @@ -12,7 +12,7 @@ use actix_utils::dispatcher::Dispatcher; use bytes::Bytes; use futures_util::future; use futures_util::task::{Context, Poll}; -use futures_util::{SinkExt, StreamExt}; +use futures_util::{SinkExt as _, StreamExt as _}; struct WsService(Arc, Cell)>>); From 983b6904a760134c00cff83edf2d83ffab96a590 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Wed, 17 Mar 2021 00:38:42 +0000 Subject: [PATCH 06/79] unvendor openssl --- .github/workflows/ci.yml | 15 +++++++++++++++ Cargo.toml | 6 ------ actix-http-test/Cargo.toml | 6 ------ actix-http/Cargo.toml | 5 ----- awc/Cargo.toml | 6 ------ 5 files changed, 15 insertions(+), 23 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7d0520d52..dc0ff0c19 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -26,6 +26,21 @@ jobs: steps: - uses: actions/checkout@v2 + # install OpenSSL on Windows + - name: Set vcpkg root + if: matrix.target.triple == 'x86_64-pc-windows-msvc' + run: echo "VCPKG_ROOT=$env:VCPKG_INSTALLATION_ROOT" | Out-File -FilePath $env:GITHUB_ENV -Append + - name: Install OpenSSL + if: matrix.target.triple == 'x86_64-pc-windows-msvc' + run: vcpkg install openssl:x64-windows + + - name: Install ${{ matrix.version }} + uses: actions-rs/toolchain@v1 + with: + toolchain: ${{ matrix.version }}-${{ matrix.target.triple }} + profile: minimal + override: true + - name: Install ${{ matrix.version }} uses: actions-rs/toolchain@v1 with: diff --git a/Cargo.toml b/Cargo.toml index 8477c8ede..0877edf9c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -111,12 +111,6 @@ tls-openssl = { package = "openssl", version = "0.10.9", optional = true } tls-rustls = { package = "rustls", version = "0.19.0", optional = true } url = "2.1" -[target.'cfg(windows)'.dependencies.tls-openssl] -version = "0.10.9" -package = "openssl" -features = ["vendored"] -optional = true - [dev-dependencies] brotli2 = "0.3.2" criterion = "0.3" diff --git a/actix-http-test/Cargo.toml b/actix-http-test/Cargo.toml index 69b2a3335..c1e61556b 100644 --- a/actix-http-test/Cargo.toml +++ b/actix-http-test/Cargo.toml @@ -50,12 +50,6 @@ serde_urlencoded = "0.7" time = { version = "0.2.23", default-features = false, features = ["std"] } tls-openssl = { version = "0.10.9", package = "openssl", optional = true } -[target.'cfg(windows)'.dependencies.tls-openssl] -version = "0.10.9" -package = "openssl" -features = ["vendored"] -optional = true - [dev-dependencies] actix-web = { version = "4.0.0-beta.4", default-features = false, features = ["cookies"] } actix-http = "3.0.0-beta.4" diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index edcc7efd5..e1aebb76b 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -98,11 +98,6 @@ serde_derive = "1.0" tls-openssl = { version = "0.10", package = "openssl" } tls-rustls = { version = "0.19", package = "rustls" } -[target.'cfg(windows)'.dev-dependencies.tls-openssl] -version = "0.10.9" -package = "openssl" -features = ["vendored"] - [[example]] name = "ws" required-features = ["rustls"] diff --git a/awc/Cargo.toml b/awc/Cargo.toml index 981b93a52..ec2e03a96 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -66,12 +66,6 @@ serde_urlencoded = "0.7" tls-openssl = { version = "0.10.9", package = "openssl", optional = true } tls-rustls = { version = "0.19.0", package = "rustls", optional = true, features = ["dangerous_configuration"] } -[target.'cfg(windows)'.dependencies.tls-openssl] -version = "0.10.9" -package = "openssl" -features = ["vendored"] -optional = true - [dev-dependencies] actix-web = { version = "4.0.0-beta.4", features = ["openssl"] } actix-http = { version = "3.0.0-beta.4", features = ["openssl"] } From abcb444dd90b82e96512bfa4ba41fe041860561e Mon Sep 17 00:00:00 2001 From: obayemi Date: Thu, 18 Mar 2021 14:21:44 +0100 Subject: [PATCH 07/79] fix routes in Path documentation (#2084) --- src/types/path.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/types/path.rs b/src/types/path.rs index 4ab124d53..294df6cf2 100644 --- a/src/types/path.rs +++ b/src/types/path.rs @@ -20,7 +20,7 @@ use crate::{dev::Payload, error::PathError, FromRequest, HttpRequest}; /// // extract path info from "/{name}/{count}/index.html" into tuple /// // {name} - deserialize a String /// // {count} - deserialize a u32 -/// #[get("/")] +/// #[get("/{name}/{count}/index.html")] /// async fn index(path: web::Path<(String, u32)>) -> String { /// let (name, count) = path.into_inner(); /// format!("Welcome {}! {}", name, count) @@ -40,7 +40,7 @@ use crate::{dev::Payload, error::PathError, FromRequest, HttpRequest}; /// } /// /// // extract `Info` from a path using serde -/// #[get("/")] +/// #[get("/{name}")] /// async fn index(info: web::Path) -> String { /// format!("Welcome {}!", info.name) /// } From b75b5114c30bf56692290775e012a152d13f0122 Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Thu, 18 Mar 2021 10:53:22 -0700 Subject: [PATCH 08/79] refactor actix_http connection types and connector services (#2081) --- actix-http/CHANGES.md | 6 + actix-http/src/client/config.rs | 2 + actix-http/src/client/connection.rs | 491 +++++++++++++++--------- actix-http/src/client/connector.rs | 575 +++++++++++++++++++--------- actix-http/src/client/h1proto.rs | 136 ++----- actix-http/src/client/h2proto.rs | 38 +- actix-http/src/client/mod.rs | 2 +- actix-http/src/client/pool.rs | 120 +++--- awc/CHANGES.md | 4 + awc/src/builder.rs | 20 +- awc/src/connect.rs | 30 +- awc/src/frozen.rs | 22 +- awc/src/lib.rs | 9 +- awc/src/middleware/redirect.rs | 4 +- awc/src/request.rs | 14 +- awc/src/ws.rs | 5 +- 16 files changed, 867 insertions(+), 611 deletions(-) diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index e96c22274..c4e0aec89 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -1,10 +1,16 @@ # Changes ## Unreleased - 2021-xx-xx +### Added +* `client::Connector::handshake_timeout` method for customize tls connection handshake timeout. [#2081] +* `client::ConnectorService` as `client::Connector::finish` method's return type [#2081] +* `client::ConnectionIo` trait alias [#2081] + ### Chaged * `client::Connector` type now only have one generic type for `actix_service::Service`. [#2063] [#2063]: https://github.com/actix/actix-web/pull/2063 +[#2081]: https://github.com/actix/actix-web/pull/2081 ## 3.0.0-beta.4 - 2021-03-08 diff --git a/actix-http/src/client/config.rs b/actix-http/src/client/config.rs index 0d54e1b49..1c0405cbc 100644 --- a/actix-http/src/client/config.rs +++ b/actix-http/src/client/config.rs @@ -8,6 +8,7 @@ const DEFAULT_H2_STREAM_WINDOW: u32 = 1024 * 1024; // 1MB #[derive(Clone)] pub(crate) struct ConnectorConfig { pub(crate) timeout: Duration, + pub(crate) handshake_timeout: Duration, pub(crate) conn_lifetime: Duration, pub(crate) conn_keep_alive: Duration, pub(crate) disconnect_timeout: Option, @@ -21,6 +22,7 @@ impl Default for ConnectorConfig { fn default() -> Self { Self { timeout: Duration::from_secs(5), + handshake_timeout: Duration::from_secs(5), conn_lifetime: Duration::from_secs(75), conn_keep_alive: Duration::from_secs(15), disconnect_timeout: Some(Duration::from_millis(3000)), diff --git a/actix-http/src/client/connection.rs b/actix-http/src/client/connection.rs index 291148b5b..89dfd59de 100644 --- a/actix-http/src/client/connection.rs +++ b/actix-http/src/client/connection.rs @@ -1,14 +1,16 @@ -use std::ops::{Deref, DerefMut}; -use std::pin::Pin; -use std::task::{Context, Poll}; -use std::{fmt, io, time}; +use std::{ + io, + ops::{Deref, DerefMut}, + pin::Pin, + task::{Context, Poll}, + time, +}; use actix_codec::{AsyncRead, AsyncWrite, Framed, ReadBuf}; use actix_rt::task::JoinHandle; use bytes::Bytes; use futures_core::future::LocalBoxFuture; use h2::client::SendRequest; -use pin_project::pin_project; use crate::body::MessageBody; use crate::h1::ClientCodec; @@ -19,32 +21,148 @@ use super::error::SendRequestError; use super::pool::Acquired; use super::{h1proto, h2proto}; +/// Trait alias for types impl [tokio::io::AsyncRead] and [tokio::io::AsyncWrite]. pub trait ConnectionIo: AsyncRead + AsyncWrite + Unpin + 'static {} impl ConnectionIo for T {} -pub(crate) enum ConnectionType { - H1(Io), - H2(H2Connection), +/// HTTP client connection +pub struct H1Connection { + io: Option, + created: time::Instant, + acquired: Acquired, } -/// `H2Connection` has two parts: `SendRequest` and `Connection`. +impl H1Connection { + /// close or release the connection to pool based on flag input + pub(super) fn on_release(&mut self, keep_alive: bool) { + if keep_alive { + self.release(); + } else { + self.close(); + } + } + + /// Close connection + fn close(&mut self) { + let io = self.io.take().unwrap(); + self.acquired.close(ConnectionInnerType::H1(io)); + } + + /// Release this connection to the connection pool + fn release(&mut self) { + let io = self.io.take().unwrap(); + self.acquired + .release(ConnectionInnerType::H1(io), self.created); + } + + fn io_pin_mut(self: Pin<&mut Self>) -> Pin<&mut Io> { + Pin::new(self.get_mut().io.as_mut().unwrap()) + } +} + +impl AsyncRead for H1Connection { + fn poll_read( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &mut ReadBuf<'_>, + ) -> Poll> { + self.io_pin_mut().poll_read(cx, buf) + } +} + +impl AsyncWrite for H1Connection { + fn poll_write( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &[u8], + ) -> Poll> { + self.io_pin_mut().poll_write(cx, buf) + } + + fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + self.io_pin_mut().poll_flush(cx) + } + + fn poll_shutdown( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + ) -> Poll> { + self.io_pin_mut().poll_shutdown(cx) + } + + fn poll_write_vectored( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + bufs: &[io::IoSlice<'_>], + ) -> Poll> { + self.io_pin_mut().poll_write_vectored(cx, bufs) + } + + fn is_write_vectored(&self) -> bool { + self.io.as_ref().unwrap().is_write_vectored() + } +} + +/// HTTP2 client connection +pub struct H2Connection { + io: Option, + created: time::Instant, + acquired: Acquired, +} + +impl Deref for H2Connection { + type Target = SendRequest; + + fn deref(&self) -> &Self::Target { + &self.io.as_ref().unwrap().sender + } +} + +impl DerefMut for H2Connection { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.io.as_mut().unwrap().sender + } +} + +impl H2Connection { + /// close or release the connection to pool based on flag input + pub(super) fn on_release(&mut self, close: bool) { + if close { + self.close(); + } else { + self.release(); + } + } + + /// Close connection + fn close(&mut self) { + let io = self.io.take().unwrap(); + self.acquired.close(ConnectionInnerType::H2(io)); + } + + /// Release this connection to the connection pool + fn release(&mut self) { + let io = self.io.take().unwrap(); + self.acquired + .release(ConnectionInnerType::H2(io), self.created); + } +} + +/// `H2ConnectionInner` has two parts: `SendRequest` and `Connection`. /// -/// `Connection` is spawned as an async task on runtime and `H2Connection` holds a handle for -/// this task. Therefore, it can wake up and quit the task when SendRequest is dropped. -pub(crate) struct H2Connection { +/// `Connection` is spawned as an async task on runtime and `H2ConnectionInner` holds a handle +/// for this task. Therefore, it can wake up and quit the task when SendRequest is dropped. +pub(super) struct H2ConnectionInner { handle: JoinHandle<()>, sender: SendRequest, } -impl H2Connection { - pub(crate) fn new( +impl H2ConnectionInner { + pub(super) fn new( sender: SendRequest, connection: h2::client::Connection, - ) -> Self - where - Io: AsyncRead + AsyncWrite + Unpin + 'static, - { + ) -> Self { let handle = actix_rt::spawn(async move { let _ = connection.await; }); @@ -53,143 +171,80 @@ impl H2Connection { } } -// cancel spawned connection task on drop. -impl Drop for H2Connection { +/// Cancel spawned connection task on drop. +impl Drop for H2ConnectionInner { fn drop(&mut self) { self.handle.abort(); } } -// only expose sender type to public. -impl Deref for H2Connection { - type Target = SendRequest; - - fn deref(&self) -> &Self::Target { - &self.sender - } -} - -impl DerefMut for H2Connection { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.sender - } -} - -pub trait Connection { - type Io: AsyncRead + AsyncWrite + Unpin; - - /// Send request and body - fn send_request( - self, - head: H, - body: B, - ) -> LocalBoxFuture<'static, Result<(ResponseHead, Payload), SendRequestError>> - where - B: MessageBody + 'static, - H: Into + 'static; - - /// Send request, returns Response and Framed - fn open_tunnel + 'static>( - self, - head: H, - ) -> LocalBoxFuture< - 'static, - Result<(ResponseHead, Framed), SendRequestError>, - >; -} - -#[doc(hidden)] -/// HTTP client connection -pub struct IoConnection -where - T: AsyncWrite + Unpin + 'static, -{ - io: Option>, - created: time::Instant, - pool: Acquired, -} - -impl fmt::Debug for IoConnection -where - T: AsyncWrite + Unpin + fmt::Debug + 'static, -{ - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self.io { - Some(ConnectionType::H1(ref io)) => write!(f, "H1Connection({:?})", io), - Some(ConnectionType::H2(_)) => write!(f, "H2Connection"), - None => write!(f, "Connection(Empty)"), - } - } -} - -impl IoConnection { - pub(crate) fn new( - io: ConnectionType, - created: time::Instant, - pool: Acquired, - ) -> Self { - IoConnection { - pool, - created, - io: Some(io), - } - } - - #[cfg(test)] - pub(crate) fn into_parts(self) -> (ConnectionType, time::Instant, Acquired) { - (self.io.unwrap(), self.created, self.pool) - } - - async fn send_request>( - mut self, - head: H, - body: B, - ) -> Result<(ResponseHead, Payload), SendRequestError> { - match self.io.take().unwrap() { - ConnectionType::H1(io) => { - h1proto::send_request(io, head.into(), body, self.created, self.pool) - .await - } - ConnectionType::H2(io) => { - h2proto::send_request(io, head.into(), body, self.created, self.pool) - .await - } - } - } - - /// Send request, returns Response and Framed - async fn open_tunnel>( - mut self, - head: H, - ) -> Result<(ResponseHead, Framed), SendRequestError> { - match self.io.take().unwrap() { - ConnectionType::H1(io) => h1proto::open_tunnel(io, head.into()).await, - ConnectionType::H2(io) => { - self.pool.release(ConnectionType::H2(io), self.created); - Err(SendRequestError::TunnelNotSupported) - } - } - } -} - #[allow(dead_code)] -pub(crate) enum EitherIoConnection +/// Unified connection type cover Http1 Plain/Tls and Http2 protocols +pub enum Connection> where - A: AsyncRead + AsyncWrite + Unpin + 'static, - B: AsyncRead + AsyncWrite + Unpin + 'static, + A: ConnectionIo, + B: ConnectionIo, { - A(IoConnection), - B(IoConnection), + Tcp(ConnectionType), + Tls(ConnectionType), } -impl Connection for EitherIoConnection -where - A: AsyncRead + AsyncWrite + Unpin + 'static, - B: AsyncRead + AsyncWrite + Unpin + 'static, -{ - type Io = EitherIo; +/// Unified connection type cover Http1/2 protocols +pub enum ConnectionType { + H1(H1Connection), + H2(H2Connection), +} - fn send_request( +/// Helper type for storing connection types in pool. +pub(super) enum ConnectionInnerType { + H1(Io), + H2(H2ConnectionInner), +} + +impl ConnectionType { + pub(super) fn from_pool( + inner: ConnectionInnerType, + created: time::Instant, + acquired: Acquired, + ) -> Self { + match inner { + ConnectionInnerType::H1(io) => Self::from_h1(io, created, acquired), + ConnectionInnerType::H2(io) => Self::from_h2(io, created, acquired), + } + } + + pub(super) fn from_h1( + io: Io, + created: time::Instant, + acquired: Acquired, + ) -> Self { + Self::H1(H1Connection { + io: Some(io), + created, + acquired, + }) + } + + pub(super) fn from_h2( + io: H2ConnectionInner, + created: time::Instant, + acquired: Acquired, + ) -> Self { + Self::H2(H2Connection { + io: Some(io), + created, + acquired, + }) + } +} + +impl Connection +where + A: ConnectionIo, + B: ConnectionIo, +{ + /// Send a request through connection. + pub fn send_request( self, head: H, body: RB, @@ -198,76 +253,106 @@ where RB: MessageBody + 'static, H: Into + 'static, { - match self { - EitherIoConnection::A(con) => Box::pin(con.send_request(head, body)), - EitherIoConnection::B(con) => Box::pin(con.send_request(head, body)), - } + Box::pin(async move { + match self { + Connection::Tcp(ConnectionType::H1(conn)) => { + h1proto::send_request(conn, head.into(), body).await + } + Connection::Tls(ConnectionType::H1(conn)) => { + h1proto::send_request(conn, head.into(), body).await + } + Connection::Tls(ConnectionType::H2(conn)) => { + h2proto::send_request(conn, head.into(), body).await + } + _ => unreachable!( + "Plain Tcp connection can be used only in Http1 protocol" + ), + } + }) } - /// Send request, returns Response and Framed - fn open_tunnel + 'static>( + /// Send request, returns Response and Framed tunnel. + pub fn open_tunnel + 'static>( self, head: H, ) -> LocalBoxFuture< 'static, - Result<(ResponseHead, Framed), SendRequestError>, + Result<(ResponseHead, Framed, ClientCodec>), SendRequestError>, > { - match self { - EitherIoConnection::A(con) => Box::pin(async { - let (head, framed) = con.open_tunnel(head).await?; - Ok((head, framed.into_map_io(EitherIo::A))) - }), - EitherIoConnection::B(con) => Box::pin(async { - let (head, framed) = con.open_tunnel(head).await?; - Ok((head, framed.into_map_io(EitherIo::B))) - }), - } + Box::pin(async move { + match self { + Connection::Tcp(ConnectionType::H1(ref _conn)) => { + let (head, framed) = h1proto::open_tunnel(self, head.into()).await?; + Ok((head, framed)) + } + Connection::Tls(ConnectionType::H1(ref _conn)) => { + let (head, framed) = h1proto::open_tunnel(self, head.into()).await?; + Ok((head, framed)) + } + Connection::Tls(ConnectionType::H2(mut conn)) => { + conn.release(); + Err(SendRequestError::TunnelNotSupported) + } + Connection::Tcp(ConnectionType::H2(_)) => { + unreachable!( + "Plain Tcp connection can be used only in Http1 protocol" + ) + } + } + }) } } -#[pin_project(project = EitherIoProj)] -pub enum EitherIo { - A(#[pin] A), - B(#[pin] B), -} - -impl AsyncRead for EitherIo +impl AsyncRead for Connection where - A: AsyncRead, - B: AsyncRead, + A: ConnectionIo, + B: ConnectionIo, { fn poll_read( self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &mut ReadBuf<'_>, ) -> Poll> { - match self.project() { - EitherIoProj::A(val) => val.poll_read(cx, buf), - EitherIoProj::B(val) => val.poll_read(cx, buf), + match self.get_mut() { + Connection::Tcp(ConnectionType::H1(conn)) => { + Pin::new(conn).poll_read(cx, buf) + } + Connection::Tls(ConnectionType::H1(conn)) => { + Pin::new(conn).poll_read(cx, buf) + } + _ => unreachable!("H2Connection can not impl AsyncRead trait"), } } } -impl AsyncWrite for EitherIo +const H2_UNREACHABLE_WRITE: &'static str = "H2Connection can not impl AsyncWrite trait"; + +impl AsyncWrite for Connection where - A: AsyncWrite, - B: AsyncWrite, + A: ConnectionIo, + B: ConnectionIo, { fn poll_write( self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &[u8], ) -> Poll> { - match self.project() { - EitherIoProj::A(val) => val.poll_write(cx, buf), - EitherIoProj::B(val) => val.poll_write(cx, buf), + match self.get_mut() { + Connection::Tcp(ConnectionType::H1(conn)) => { + Pin::new(conn).poll_write(cx, buf) + } + Connection::Tls(ConnectionType::H1(conn)) => { + Pin::new(conn).poll_write(cx, buf) + } + _ => unreachable!(H2_UNREACHABLE_WRITE), } } fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - match self.project() { - EitherIoProj::A(val) => val.poll_flush(cx), - EitherIoProj::B(val) => val.poll_flush(cx), + match self.get_mut() { + Connection::Tcp(ConnectionType::H1(conn)) => Pin::new(conn).poll_flush(cx), + Connection::Tls(ConnectionType::H1(conn)) => Pin::new(conn).poll_flush(cx), + _ => unreachable!(H2_UNREACHABLE_WRITE), } } @@ -275,9 +360,38 @@ where self: Pin<&mut Self>, cx: &mut Context<'_>, ) -> Poll> { - match self.project() { - EitherIoProj::A(val) => val.poll_shutdown(cx), - EitherIoProj::B(val) => val.poll_shutdown(cx), + match self.get_mut() { + Connection::Tcp(ConnectionType::H1(conn)) => { + Pin::new(conn).poll_shutdown(cx) + } + Connection::Tls(ConnectionType::H1(conn)) => { + Pin::new(conn).poll_shutdown(cx) + } + _ => unreachable!(H2_UNREACHABLE_WRITE), + } + } + + fn poll_write_vectored( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + bufs: &[io::IoSlice<'_>], + ) -> Poll> { + match self.get_mut() { + Connection::Tcp(ConnectionType::H1(conn)) => { + Pin::new(conn).poll_write_vectored(cx, bufs) + } + Connection::Tls(ConnectionType::H1(conn)) => { + Pin::new(conn).poll_write_vectored(cx, bufs) + } + _ => unreachable!(H2_UNREACHABLE_WRITE), + } + } + + fn is_write_vectored(&self) -> bool { + match *self { + Connection::Tcp(ConnectionType::H1(ref conn)) => conn.is_write_vectored(), + Connection::Tls(ConnectionType::H1(ref conn)) => conn.is_write_vectored(), + _ => unreachable!(H2_UNREACHABLE_WRITE), } } } @@ -300,10 +414,13 @@ mod test { let tcp = TcpStream::connect(local).await.unwrap(); let (sender, connection) = h2::client::handshake(tcp).await.unwrap(); - let conn = H2Connection::new(sender.clone(), connection); + let conn = H2ConnectionInner::new(sender.clone(), connection); assert!(sender.clone().ready().await.is_ok()); - assert!(h2::client::SendRequest::clone(&*conn).ready().await.is_ok()); + assert!(h2::client::SendRequest::clone(&conn.sender) + .ready() + .await + .is_ok()); drop(conn); diff --git a/actix-http/src/client/connector.rs b/actix-http/src/client/connector.rs index 0c9159b87..1a5f32880 100644 --- a/actix-http/src/client/connector.rs +++ b/actix-http/src/client/connector.rs @@ -3,22 +3,26 @@ use std::{ future::Future, net::IpAddr, pin::Pin, + rc::Rc, task::{Context, Poll}, time::Duration, }; -use actix_codec::{AsyncRead, AsyncWrite}; -use actix_rt::net::TcpStream; -use actix_service::{apply_fn, Service, ServiceExt}; -use actix_tls::connect::{ - new_connector, Connect as TcpConnect, Connection as TcpConnection, Resolver, +use actix_rt::{ + net::TcpStream, + time::{sleep, Sleep}, }; -use actix_utils::timeout::{TimeoutError, TimeoutService}; -use futures_core::ready; +use actix_service::Service; +use actix_tls::connect::{ + new_connector, Connect as TcpConnect, ConnectError as TcpConnectError, + Connection as TcpConnection, Resolver, +}; +use futures_core::{future::LocalBoxFuture, ready}; use http::Uri; +use pin_project::pin_project; use super::config::ConnectorConfig; -use super::connection::{Connection, ConnectionIo, EitherIoConnection}; +use super::connection::{Connection, ConnectionIo}; use super::error::ConnectError; use super::pool::ConnectionPool; use super::Connect; @@ -28,18 +32,15 @@ use super::Protocol; use actix_tls::connect::ssl::openssl::SslConnector as OpensslConnector; #[cfg(feature = "rustls")] use actix_tls::connect::ssl::rustls::ClientConfig; -#[cfg(feature = "rustls")] -use std::sync::Arc; -#[cfg(any(feature = "openssl", feature = "rustls"))] enum SslConnector { + #[allow(dead_code)] + None, #[cfg(feature = "openssl")] Openssl(OpensslConnector), #[cfg(feature = "rustls")] - Rustls(Arc), + Rustls(std::sync::Arc), } -#[cfg(not(any(feature = "openssl", feature = "rustls")))] -type SslConnector = (); /// Manages HTTP client network connectivity. /// @@ -104,23 +105,25 @@ impl Connector<()> { config.root_store.add_server_trust_anchors( &actix_tls::connect::ssl::rustls::TLS_SERVER_ROOTS, ); - SslConnector::Rustls(Arc::new(config)) + SslConnector::Rustls(std::sync::Arc::new(config)) } // ssl turned off, provides empty ssl connector #[cfg(not(any(feature = "openssl", feature = "rustls")))] - fn build_ssl(_: Vec>) -> SslConnector {} + fn build_ssl(_: Vec>) -> SslConnector { + SslConnector::None + } } -impl Connector { +impl Connector { /// Use custom connector. - pub fn connector(self, connector: T1) -> Connector + pub fn connector(self, connector: S1) -> Connector where - U1: AsyncRead + AsyncWrite + Unpin + fmt::Debug, - T1: Service< + Io1: ConnectionIo + fmt::Debug, + S1: Service< TcpConnect, - Response = TcpConnection, - Error = actix_tls::connect::ConnectError, + Response = TcpConnection, + Error = TcpConnectError, > + Clone, { Connector { @@ -131,23 +134,30 @@ impl Connector { } } -impl Connector +impl Connector where - U: AsyncRead + AsyncWrite + Unpin + fmt::Debug + 'static, - T: Service< + Io: ConnectionIo + fmt::Debug, + S: Service< TcpConnect, - Response = TcpConnection, - Error = actix_tls::connect::ConnectError, + Response = TcpConnection, + Error = TcpConnectError, > + Clone + 'static, { - /// Connection timeout, i.e. max time to connect to remote host including dns name resolution. - /// Set to 5 second by default. + /// Tcp connection timeout, i.e. max time to connect to remote host including dns name + /// resolution. Set to 5 second by default. pub fn timeout(mut self, timeout: Duration) -> Self { self.config.timeout = timeout; self } + /// Tls handshake timeout, i.e. max time to do tls handshake with remote host after tcp + /// connection established. Set to 5 second by default. + pub fn handshake_timeout(mut self, timeout: Duration) -> Self { + self.config.handshake_timeout = timeout; + self + } + #[cfg(feature = "openssl")] /// Use custom `SslConnector` instance. pub fn ssl(mut self, connector: OpensslConnector) -> Self { @@ -157,7 +167,7 @@ where #[cfg(feature = "rustls")] /// Use custom `SslConnector` instance. - pub fn rustls(mut self, connector: Arc) -> Self { + pub fn rustls(mut self, connector: std::sync::Arc) -> Self { self.ssl = SslConnector::Rustls(connector); self } @@ -247,158 +257,369 @@ where /// Finish configuration process and create connector service. /// The Connector builder always concludes by calling `finish()` last in /// its combinator chain. - pub fn finish( - self, - ) -> impl Service { + pub fn finish(self) -> ConnectorService { let local_address = self.config.local_address; let timeout = self.config.timeout; - let tcp_service = TimeoutService::new( - timeout, - apply_fn(self.connector.clone(), move |msg: Connect, srv| { - let mut req = TcpConnect::new(msg.uri).set_addr(msg.addr); + let tcp_service_inner = + TcpConnectorInnerService::new(self.connector, timeout, local_address); - if let Some(local_addr) = local_address { - req = req.set_local_addr(local_addr); + #[allow(clippy::redundant_clone)] + let tcp_service = TcpConnectorService { + service: tcp_service_inner.clone(), + }; + + let tls_service = match self.ssl { + SslConnector::None => None, + #[cfg(feature = "openssl")] + SslConnector::Openssl(tls) => { + const H2: &[u8] = b"h2"; + + use actix_tls::connect::ssl::openssl::{OpensslConnector, SslStream}; + + impl IntoConnectionIo for TcpConnection> { + fn into_connection_io(self) -> (Box, Protocol) { + let sock = self.into_parts().0; + let h2 = sock + .ssl() + .selected_alpn_protocol() + .map(|protos| protos.windows(2).any(|w| w == H2)) + .unwrap_or(false); + if h2 { + (Box::new(sock), Protocol::Http2) + } else { + (Box::new(sock), Protocol::Http1) + } + } } - srv.call(req) - }) - .map_err(ConnectError::from) - .map(|stream| (stream.into_parts().0, Protocol::Http1)), - ) - .map_err(|e| match e { - TimeoutError::Service(e) => e, - TimeoutError::Timeout => ConnectError::Timeout, - }); + let handshake_timeout = self.config.handshake_timeout; - #[cfg(not(any(feature = "openssl", feature = "rustls")))] - { - // A dummy service for annotate tls pool's type signature. - pub type DummyService = Box< - dyn Service< - Connect, - Response = (Box, Protocol), - Error = ConnectError, - Future = futures_core::future::LocalBoxFuture< - 'static, - Result<(Box, Protocol), ConnectError>, - >, - >, - >; + let tls_service = TlsConnectorService { + tcp_service: tcp_service_inner, + tls_service: OpensslConnector::service(tls), + timeout: handshake_timeout, + }; - InnerConnector::<_, DummyService, _, Box> { - tcp_pool: ConnectionPool::new( - tcp_service, - self.config.no_disconnect_timeout(), - ), - tls_pool: None, + Some(actix_service::boxed::rc_service(tls_service)) } - } - - #[cfg(any(feature = "openssl", feature = "rustls"))] - { - const H2: &[u8] = b"h2"; - use actix_service::{boxed::service, pipeline}; - #[cfg(feature = "openssl")] - use actix_tls::connect::ssl::openssl::OpensslConnector; #[cfg(feature = "rustls")] - use actix_tls::connect::ssl::rustls::{RustlsConnector, Session}; + SslConnector::Rustls(tls) => { + const H2: &[u8] = b"h2"; - let ssl_service = TimeoutService::new( - timeout, - pipeline( - apply_fn(self.connector.clone(), move |msg: Connect, srv| { - let mut req = TcpConnect::new(msg.uri).set_addr(msg.addr); + use actix_tls::connect::ssl::rustls::{ + RustlsConnector, Session, TlsStream, + }; - if let Some(local_addr) = local_address { - req = req.set_local_addr(local_addr); + impl IntoConnectionIo for TcpConnection> { + fn into_connection_io(self) -> (Box, Protocol) { + let sock = self.into_parts().0; + let h2 = sock + .get_ref() + .1 + .get_alpn_protocol() + .map(|protos| protos.windows(2).any(|w| w == H2)) + .unwrap_or(false); + if h2 { + (Box::new(sock), Protocol::Http2) + } else { + (Box::new(sock), Protocol::Http1) } + } + } - srv.call(req) - }) - .map_err(ConnectError::from), - ) - .and_then(match self.ssl { - #[cfg(feature = "openssl")] - SslConnector::Openssl(ssl) => service( - OpensslConnector::service(ssl) - .map(|stream| { - let sock = stream.into_parts().0; - let h2 = sock - .ssl() - .selected_alpn_protocol() - .map(|protos| protos.windows(2).any(|w| w == H2)) - .unwrap_or(false); - if h2 { - ( - Box::new(sock) as Box, - Protocol::Http2, - ) - } else { - (Box::new(sock) as _, Protocol::Http1) - } - }) - .map_err(ConnectError::from), - ), - #[cfg(feature = "rustls")] - SslConnector::Rustls(ssl) => service( - RustlsConnector::service(ssl) - .map_err(ConnectError::from) - .map(|stream| { - let sock = stream.into_parts().0; - let h2 = sock - .get_ref() - .1 - .get_alpn_protocol() - .map(|protos| protos.windows(2).any(|w| w == H2)) - .unwrap_or(false); - if h2 { - (Box::new(sock) as _, Protocol::Http2) - } else { - (Box::new(sock) as _, Protocol::Http1) - } - }), - ), - }), - ) - .map_err(|e| match e { - TimeoutError::Service(e) => e, - TimeoutError::Timeout => ConnectError::Timeout, - }); + let handshake_timeout = self.config.handshake_timeout; - InnerConnector { - tcp_pool: ConnectionPool::new( - tcp_service, - self.config.no_disconnect_timeout(), - ), - tls_pool: Some(ConnectionPool::new(ssl_service, self.config)), + let tls_service = TlsConnectorService { + tcp_service: tcp_service_inner, + tls_service: RustlsConnector::service(tls), + timeout: handshake_timeout, + }; + + Some(actix_service::boxed::rc_service(tls_service)) } + }; + + let tcp_config = self.config.no_disconnect_timeout(); + + let tcp_pool = ConnectionPool::new(tcp_service, tcp_config); + + let tls_config = self.config; + let tls_pool = tls_service + .map(move |tls_service| ConnectionPool::new(tls_service, tls_config)); + + ConnectorServicePriv { tcp_pool, tls_pool } + } +} + +/// tcp service for map `TcpConnection` type to `(Io, Protocol)` +#[derive(Clone)] +pub struct TcpConnectorService { + service: S, +} + +impl Service for TcpConnectorService +where + S: Service, Error = ConnectError> + + Clone + + 'static, +{ + type Response = (Io, Protocol); + type Error = ConnectError; + type Future = TcpConnectorFuture; + + actix_service::forward_ready!(service); + + fn call(&self, req: Connect) -> Self::Future { + TcpConnectorFuture { + fut: self.service.call(req), } } } -struct InnerConnector +#[pin_project] +pub struct TcpConnectorFuture { + #[pin] + fut: Fut, +} + +impl Future for TcpConnectorFuture where - S1: Service + 'static, - S2: Service + 'static, - Io1: AsyncRead + AsyncWrite + Unpin + 'static, - Io2: AsyncRead + AsyncWrite + Unpin + 'static, + Fut: Future, ConnectError>>, +{ + type Output = Result<(Io, Protocol), ConnectError>; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + self.project() + .fut + .poll(cx) + .map_ok(|res| (res.into_parts().0, Protocol::Http1)) + } +} + +/// service for establish tcp connection and do client tls handshake. +/// operation is canceled when timeout limit reached. +struct TlsConnectorService { + /// tcp connection is canceled on `TcpConnectorInnerService`'s timeout setting. + tcp_service: S, + /// tls connection is canceled on `TlsConnectorService`'s timeout setting. + tls_service: St, + timeout: Duration, +} + +impl Service for TlsConnectorService +where + S: Service, Error = ConnectError> + + Clone + + 'static, + St: Service, Response = Res, Error = std::io::Error> + + Clone + + 'static, + Io: ConnectionIo, + Res: IntoConnectionIo, +{ + type Response = (Box, Protocol); + type Error = ConnectError; + type Future = TlsConnectorFuture; + + fn poll_ready(&self, cx: &mut Context<'_>) -> Poll> { + ready!(self.tcp_service.poll_ready(cx))?; + ready!(self.tls_service.poll_ready(cx))?; + Poll::Ready(Ok(())) + } + + fn call(&self, req: Connect) -> Self::Future { + let fut = self.tcp_service.call(req); + let tls_service = self.tls_service.clone(); + let timeout = self.timeout; + + TlsConnectorFuture::TcpConnect { + fut, + tls_service: Some(tls_service), + timeout, + } + } +} + +#[pin_project(project = TlsConnectorProj)] +#[allow(clippy::large_enum_variant)] +enum TlsConnectorFuture { + TcpConnect { + #[pin] + fut: Fut1, + tls_service: Option, + timeout: Duration, + }, + TlsConnect { + #[pin] + fut: Fut2, + #[pin] + timeout: Sleep, + }, +} + +/// helper trait for generic over different TlsStream types between tls crates. +trait IntoConnectionIo { + fn into_connection_io(self) -> (Box, Protocol); +} + +impl Future for TlsConnectorFuture +where + S: Service< + TcpConnection, + Response = Res, + Error = std::io::Error, + Future = Fut2, + >, + Fut1: Future, ConnectError>>, + Fut2: Future>, + Io: ConnectionIo, + Res: IntoConnectionIo, +{ + type Output = Result<(Box, Protocol), ConnectError>; + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + match self.as_mut().project() { + TlsConnectorProj::TcpConnect { + fut, + tls_service, + timeout, + } => { + let res = ready!(fut.poll(cx))?; + let fut = tls_service + .take() + .expect("TlsConnectorFuture polled after complete") + .call(res); + let timeout = sleep(*timeout); + self.set(TlsConnectorFuture::TlsConnect { fut, timeout }); + self.poll(cx) + } + TlsConnectorProj::TlsConnect { fut, timeout } => match fut.poll(cx)? { + Poll::Ready(res) => Poll::Ready(Ok(res.into_connection_io())), + Poll::Pending => timeout.poll(cx).map(|_| Err(ConnectError::Timeout)), + }, + } + } +} + +/// service for establish tcp connection. +/// operation is canceled when timeout limit reached. +#[derive(Clone)] +pub struct TcpConnectorInnerService { + service: S, + timeout: Duration, + local_address: Option, +} + +impl TcpConnectorInnerService { + fn new( + service: S, + timeout: Duration, + local_address: Option, + ) -> Self { + Self { + service, + timeout, + local_address, + } + } +} + +impl Service for TcpConnectorInnerService +where + S: Service< + TcpConnect, + Response = TcpConnection, + Error = TcpConnectError, + > + Clone + + 'static, +{ + type Response = S::Response; + type Error = ConnectError; + type Future = TcpConnectorInnerFuture; + + actix_service::forward_ready!(service); + + fn call(&self, req: Connect) -> Self::Future { + let mut req = TcpConnect::new(req.uri).set_addr(req.addr); + + if let Some(local_addr) = self.local_address { + req = req.set_local_addr(local_addr); + } + + TcpConnectorInnerFuture { + fut: self.service.call(req), + timeout: sleep(self.timeout), + } + } +} + +#[pin_project] +pub struct TcpConnectorInnerFuture { + #[pin] + fut: Fut, + #[pin] + timeout: Sleep, +} + +impl Future for TcpConnectorInnerFuture +where + Fut: Future, TcpConnectError>>, +{ + type Output = Result, ConnectError>; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let this = self.project(); + match this.fut.poll(cx) { + Poll::Ready(res) => Poll::Ready(res.map_err(ConnectError::from)), + Poll::Pending => this.timeout.poll(cx).map(|_| Err(ConnectError::Timeout)), + } + } +} + +/// Connector service for pooled Plain/Tls Tcp connections. +pub type ConnectorService = ConnectorServicePriv< + TcpConnectorService>, + Rc< + dyn Service< + Connect, + Response = (Box, Protocol), + Error = ConnectError, + Future = LocalBoxFuture< + 'static, + Result<(Box, Protocol), ConnectError>, + >, + >, + >, + Io, + Box, +>; + +pub struct ConnectorServicePriv +where + S1: Service, + S2: Service, + Io1: ConnectionIo, + Io2: ConnectionIo, { tcp_pool: ConnectionPool, tls_pool: Option>, } -impl Service for InnerConnector +impl Service for ConnectorServicePriv where - S1: Service + 'static, - S2: Service + 'static, - Io1: AsyncRead + AsyncWrite + Unpin + 'static, - Io2: AsyncRead + AsyncWrite + Unpin + 'static, + S1: Service + + Clone + + 'static, + S2: Service + + Clone + + 'static, + Io1: ConnectionIo, + Io2: ConnectionIo, { - type Response = EitherIoConnection; + type Response = Connection; type Error = ConnectError; - type Future = InnerConnectorResponse; + type Future = ConnectorServiceFuture; fn poll_ready(&self, cx: &mut Context<'_>) -> Poll> { ready!(self.tcp_pool.poll_ready(cx))?; @@ -411,41 +632,49 @@ where fn call(&self, req: Connect) -> Self::Future { match req.uri.scheme_str() { Some("https") | Some("wss") => match self.tls_pool { - None => InnerConnectorResponse::SslIsNotSupported, - Some(ref pool) => InnerConnectorResponse::Io2(pool.call(req)), + None => ConnectorServiceFuture::SslIsNotSupported, + Some(ref pool) => ConnectorServiceFuture::Tls(pool.call(req)), }, - _ => InnerConnectorResponse::Io1(self.tcp_pool.call(req)), + _ => ConnectorServiceFuture::Tcp(self.tcp_pool.call(req)), } } } -#[pin_project::pin_project(project = InnerConnectorProj)] -enum InnerConnectorResponse +#[pin_project(project = ConnectorServiceProj)] +pub enum ConnectorServiceFuture where - S1: Service + 'static, - S2: Service + 'static, - Io1: AsyncRead + AsyncWrite + Unpin + 'static, - Io2: AsyncRead + AsyncWrite + Unpin + 'static, + S1: Service + + Clone + + 'static, + S2: Service + + Clone + + 'static, + Io1: ConnectionIo, + Io2: ConnectionIo, { - Io1(#[pin] as Service>::Future), - Io2(#[pin] as Service>::Future), + Tcp(#[pin] as Service>::Future), + Tls(#[pin] as Service>::Future), SslIsNotSupported, } -impl Future for InnerConnectorResponse +impl Future for ConnectorServiceFuture where - S1: Service + 'static, - S2: Service + 'static, - Io1: AsyncRead + AsyncWrite + Unpin + 'static, - Io2: AsyncRead + AsyncWrite + Unpin + 'static, + S1: Service + + Clone + + 'static, + S2: Service + + Clone + + 'static, + Io1: ConnectionIo, + Io2: ConnectionIo, { - type Output = Result, ConnectError>; + type Output = Result, ConnectError>; fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { match self.project() { - InnerConnectorProj::Io1(fut) => fut.poll(cx).map_ok(EitherIoConnection::A), - InnerConnectorProj::Io2(fut) => fut.poll(cx).map_ok(EitherIoConnection::B), - InnerConnectorProj::SslIsNotSupported => { + ConnectorServiceProj::Tcp(fut) => fut.poll(cx).map_ok(Connection::Tcp), + ConnectorServiceProj::Tls(fut) => fut.poll(cx).map_ok(Connection::Tls), + ConnectorServiceProj::SslIsNotSupported => { Poll::Ready(Err(ConnectError::SslIsNotSupported)) } } diff --git a/actix-http/src/client/h1proto.rs b/actix-http/src/client/h1proto.rs index 75a3c1f3d..01a6e1edf 100644 --- a/actix-http/src/client/h1proto.rs +++ b/actix-http/src/client/h1proto.rs @@ -1,9 +1,10 @@ -use std::io::Write; -use std::pin::Pin; -use std::task::{Context, Poll}; -use std::{io, time}; +use std::{ + io::Write, + pin::Pin, + task::{Context, Poll}, +}; -use actix_codec::{AsyncRead, AsyncWrite, Framed, ReadBuf}; +use actix_codec::Framed; use bytes::buf::BufMut; use bytes::{Bytes, BytesMut}; use futures_core::Stream; @@ -11,28 +12,24 @@ use futures_util::{future::poll_fn, SinkExt as _}; use crate::error::PayloadError; use crate::h1; -use crate::header::HeaderMap; use crate::http::{ - header::{IntoHeaderValue, EXPECT, HOST}, + header::{HeaderMap, IntoHeaderValue, EXPECT, HOST}, StatusCode, }; use crate::message::{RequestHeadType, ResponseHead}; use crate::payload::{Payload, PayloadStream}; -use super::connection::ConnectionType; +use super::connection::{ConnectionIo, H1Connection}; use super::error::{ConnectError, SendRequestError}; -use super::pool::Acquired; use crate::body::{BodySize, MessageBody}; -pub(crate) async fn send_request( - io: T, +pub(crate) async fn send_request( + io: H1Connection, mut head: RequestHeadType, body: B, - created: time::Instant, - acquired: Acquired, ) -> Result<(ResponseHead, Payload), SendRequestError> where - T: AsyncRead + AsyncWrite + Unpin + 'static, + Io: ConnectionIo, B: MessageBody, { // set request host header @@ -62,12 +59,6 @@ where } } - let io = H1Connection { - created, - acquired, - io: Some(io), - }; - // create Framed and prepare sending request let mut framed = Framed::new(io, h1::ClientCodec::default()); @@ -138,18 +129,18 @@ where } } -pub(crate) async fn open_tunnel( - io: T, +pub(crate) async fn open_tunnel( + io: Io, head: RequestHeadType, -) -> Result<(ResponseHead, Framed), SendRequestError> +) -> Result<(ResponseHead, Framed), SendRequestError> where - T: AsyncRead + AsyncWrite + Unpin + 'static, + Io: ConnectionIo, { - // create Framed and send request + // create Framed and send request. let mut framed = Framed::new(io, h1::ClientCodec::default()); framed.send((head, BodySize::None).into()).await?; - // read response + // read response head. let head = poll_fn(|cx| Pin::new(&mut framed).poll_next(cx)) .await .ok_or(ConnectError::Disconnected)??; @@ -158,12 +149,12 @@ where } /// send request body to the peer -pub(crate) async fn send_body( +pub(crate) async fn send_body( body: B, - mut framed: Pin<&mut Framed>, + mut framed: Pin<&mut Framed>, ) -> Result<(), SendRequestError> where - T: AsyncRead + AsyncWrite + Unpin + 'static, + Io: ConnectionIo, B: MessageBody, { actix_rt::pin!(body); @@ -202,92 +193,16 @@ where Ok(()) } -#[doc(hidden)] -/// HTTP client connection -pub struct H1Connection -where - T: AsyncRead + AsyncWrite + Unpin + 'static, -{ - /// T should be `Unpin` - io: Option, - created: time::Instant, - acquired: Acquired, -} - -impl H1Connection -where - T: AsyncRead + AsyncWrite + Unpin + 'static, -{ - fn on_release(&mut self, keep_alive: bool) { - if keep_alive { - self.release(); - } else { - self.close(); - } - } - - /// Close connection - fn close(&mut self) { - if let Some(io) = self.io.take() { - self.acquired.close(ConnectionType::H1(io)); - } - } - - /// Release this connection to the connection pool - fn release(&mut self) { - if let Some(io) = self.io.take() { - self.acquired.release(ConnectionType::H1(io), self.created); - } - } -} - -impl AsyncRead for H1Connection { - fn poll_read( - mut self: Pin<&mut Self>, - cx: &mut Context<'_>, - buf: &mut ReadBuf<'_>, - ) -> Poll> { - Pin::new(&mut self.io.as_mut().unwrap()).poll_read(cx, buf) - } -} - -impl AsyncWrite for H1Connection { - fn poll_write( - mut self: Pin<&mut Self>, - cx: &mut Context<'_>, - buf: &[u8], - ) -> Poll> { - Pin::new(&mut self.io.as_mut().unwrap()).poll_write(cx, buf) - } - - fn poll_flush( - mut self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll> { - Pin::new(self.io.as_mut().unwrap()).poll_flush(cx) - } - - fn poll_shutdown( - mut self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll> { - Pin::new(self.io.as_mut().unwrap()).poll_shutdown(cx) - } -} - #[pin_project::pin_project] -pub(crate) struct PlStream +pub(crate) struct PlStream where - Io: AsyncRead + AsyncWrite + Unpin + 'static, + Io: ConnectionIo, { #[pin] framed: Option, h1::ClientPayloadCodec>>, } -impl PlStream -where - Io: AsyncRead + AsyncWrite + Unpin + 'static, -{ +impl PlStream { fn new(framed: Framed, h1::ClientCodec>) -> Self { let framed = framed.into_map_codec(|codec| codec.into_payload_codec()); @@ -297,10 +212,7 @@ where } } -impl Stream for PlStream -where - Io: AsyncRead + AsyncWrite + Unpin + 'static, -{ +impl Stream for PlStream { type Item = Result; fn poll_next( diff --git a/actix-http/src/client/h2proto.rs b/actix-http/src/client/h2proto.rs index 82e81c7ff..437b9ae76 100644 --- a/actix-http/src/client/h2proto.rs +++ b/actix-http/src/client/h2proto.rs @@ -1,7 +1,5 @@ use std::future::Future; -use std::time; -use actix_codec::{AsyncRead, AsyncWrite}; use bytes::Bytes; use futures_util::future::poll_fn; use h2::{ @@ -17,20 +15,16 @@ use crate::message::{RequestHeadType, ResponseHead}; use crate::payload::Payload; use super::config::ConnectorConfig; -use super::connection::ConnectionType; +use super::connection::{ConnectionIo, H2Connection}; use super::error::SendRequestError; -use super::pool::Acquired; -use crate::client::connection::H2Connection; -pub(crate) async fn send_request( - mut io: H2Connection, +pub(crate) async fn send_request( + mut io: H2Connection, head: RequestHeadType, body: B, - created: time::Instant, - acquired: Acquired, ) -> Result<(ResponseHead, Payload), SendRequestError> where - T: AsyncRead + AsyncWrite + Unpin + 'static, + Io: ConnectionIo, B: MessageBody, { trace!("Sending client request: {:?} {:?}", head, body.size()); @@ -103,13 +97,13 @@ where let res = poll_fn(|cx| io.poll_ready(cx)).await; if let Err(e) = res { - release(io, acquired, created, e.is_io()); + io.on_release(e.is_io()); return Err(SendRequestError::from(e)); } let resp = match io.send_request(req, eof) { Ok((fut, send)) => { - release(io, acquired, created, false); + io.on_release(false); if !eof { send_body(body, send).await?; @@ -117,7 +111,7 @@ where fut.await.map_err(SendRequestError::from)? } Err(e) => { - release(io, acquired, created, e.is_io()); + io.on_release(e.is_io()); return Err(e.into()); } }; @@ -178,26 +172,10 @@ async fn send_body( } } -/// release SendRequest object -fn release( - io: H2Connection, - acquired: Acquired, - created: time::Instant, - close: bool, -) { - if close { - acquired.close(ConnectionType::H2(io)); - } else { - acquired.release(ConnectionType::H2(io), created); - } -} - -pub(crate) fn handshake( +pub(crate) fn handshake( io: Io, config: &ConnectorConfig, ) -> impl Future, Connection), h2::Error>> -where - Io: AsyncRead + AsyncWrite + Unpin + 'static, { let mut builder = Builder::new(); builder diff --git a/actix-http/src/client/mod.rs b/actix-http/src/client/mod.rs index ce6aa3bc1..41d5fef2a 100644 --- a/actix-http/src/client/mod.rs +++ b/actix-http/src/client/mod.rs @@ -15,7 +15,7 @@ pub use actix_tls::connect::{ }; pub use self::connection::{Connection, ConnectionIo}; -pub use self::connector::Connector; +pub use self::connector::{Connector, ConnectorService}; pub use self::error::{ConnectError, FreezeRequestError, InvalidUrl, SendRequestError}; pub use crate::Protocol; diff --git a/actix-http/src/client/pool.rs b/actix-http/src/client/pool.rs index 79adcf3e2..88188038f 100644 --- a/actix-http/src/client/pool.rs +++ b/actix-http/src/client/pool.rs @@ -1,34 +1,38 @@ //! Client connection pooling keyed on the authority part of the connection URI. -use std::collections::VecDeque; -use std::future::Future; -use std::ops::Deref; -use std::pin::Pin; -use std::rc::Rc; -use std::sync::Arc; -use std::task::{Context, Poll}; -use std::time::{Duration, Instant}; -use std::{cell::RefCell, io}; +use std::{ + cell::RefCell, + collections::VecDeque, + future::Future, + io, + ops::Deref, + pin::Pin, + rc::Rc, + sync::Arc, + task::{Context, Poll}, + time::{Duration, Instant}, +}; -use actix_codec::{AsyncRead, AsyncWrite}; +use actix_codec::{AsyncRead, AsyncWrite, ReadBuf}; use actix_rt::time::{sleep, Sleep}; use actix_service::Service; use ahash::AHashMap; use futures_core::future::LocalBoxFuture; use http::uri::Authority; use pin_project::pin_project; -use tokio::io::ReadBuf; use tokio::sync::{OwnedSemaphorePermit, Semaphore}; use super::config::ConnectorConfig; -use super::connection::{ConnectionType, H2Connection, IoConnection}; +use super::connection::{ + ConnectionInnerType, ConnectionIo, ConnectionType, H2ConnectionInner, +}; use super::error::ConnectError; use super::h2proto::handshake; use super::Connect; use super::Protocol; #[derive(Hash, Eq, PartialEq, Clone, Debug)] -pub(crate) struct Key { +pub struct Key { authority: Authority, } @@ -38,17 +42,18 @@ impl From for Key { } } +#[doc(hidden)] /// Connections pool for reuse Io type for certain [`http::uri::Authority`] as key. -pub(crate) struct ConnectionPool +pub struct ConnectionPool where Io: AsyncWrite + Unpin + 'static, { - connector: Rc, + connector: S, inner: ConnectionPoolInner, } /// wrapper type for check the ref count of Rc. -struct ConnectionPoolInner(Rc>) +pub struct ConnectionPoolInner(Rc>) where Io: AsyncWrite + Unpin + 'static; @@ -56,10 +61,21 @@ impl ConnectionPoolInner where Io: AsyncWrite + Unpin + 'static, { + fn new(config: ConnectorConfig) -> Self { + let permits = Arc::new(Semaphore::new(config.limit)); + let available = RefCell::new(AHashMap::default()); + + Self(Rc::new(ConnectionPoolInnerPriv { + config, + available, + permits, + })) + } + /// spawn a async for graceful shutdown h1 Io type with a timeout. - fn close(&self, conn: ConnectionType) { + fn close(&self, conn: ConnectionInnerType) { if let Some(timeout) = self.config.disconnect_timeout { - if let ConnectionType::H1(io) = conn { + if let ConnectionInnerType::H1(io) = conn { actix_rt::spawn(CloseConnection::new(io, timeout)); } } @@ -104,7 +120,7 @@ where } } -struct ConnectionPoolInnerPriv +pub struct ConnectionPoolInnerPriv where Io: AsyncWrite + Unpin + 'static, { @@ -128,15 +144,7 @@ where /// Any requests beyond limit would be wait in fifo order and get notified in async manner /// by [`tokio::sync::Semaphore`] pub(crate) fn new(connector: S, config: ConnectorConfig) -> Self { - let permits = Arc::new(Semaphore::new(config.limit)); - let available = RefCell::new(AHashMap::default()); - let connector = Rc::new(connector); - - let inner = ConnectionPoolInner(Rc::new(ConnectionPoolInnerPriv { - config, - available, - permits, - })); + let inner = ConnectionPoolInner::new(config); Self { connector, inner } } @@ -144,12 +152,14 @@ where impl Service for ConnectionPool where - S: Service + 'static, - Io: AsyncRead + AsyncWrite + Unpin + 'static, + S: Service + + Clone + + 'static, + Io: ConnectionIo, { - type Response = IoConnection; + type Response = ConnectionType; type Error = ConnectError; - type Future = LocalBoxFuture<'static, Result, ConnectError>>; + type Future = LocalBoxFuture<'static, Result>; actix_service::forward_ready!(connector); @@ -193,7 +203,7 @@ where inner.close(c.conn); } else { // check if the connection is still usable - if let ConnectionType::H1(ref mut io) = c.conn { + if let ConnectionInnerType::H1(ref mut io) = c.conn { let check = ConnectionCheckFuture { io }; match check.await { ConnectionState::Tainted => { @@ -221,7 +231,9 @@ where // match the connection and spawn new one if did not get anything. match conn { - Some(conn) => Ok(IoConnection::new(conn.conn, conn.created, acquired)), + Some(conn) => { + Ok(ConnectionType::from_pool(conn.conn, conn.created, acquired)) + } None => { let (io, proto) = connector.call(req).await?; @@ -229,19 +241,12 @@ where assert!(proto != Protocol::Http3); if proto == Protocol::Http1 { - Ok(IoConnection::new( - ConnectionType::H1(io), - Instant::now(), - acquired, - )) + Ok(ConnectionType::from_h1(io, Instant::now(), acquired)) } else { let config = &acquired.inner.config; let (sender, connection) = handshake(io, config).await?; - Ok(IoConnection::new( - ConnectionType::H2(H2Connection::new(sender, connection)), - Instant::now(), - acquired, - )) + let inner = H2ConnectionInner::new(sender, connection); + Ok(ConnectionType::from_h2(inner, Instant::now(), acquired)) } } } @@ -292,7 +297,7 @@ where } struct PooledConnection { - conn: ConnectionType, + conn: ConnectionInnerType, used: Instant, created: Instant, } @@ -332,26 +337,26 @@ where } } -pub(crate) struct Acquired +pub struct Acquired where Io: AsyncWrite + Unpin + 'static, { + /// authority key for identify connection. key: Key, + /// handle to connection pool. inner: ConnectionPoolInner, + /// permit for limit concurrent in-flight connection for a Client object. permit: OwnedSemaphorePermit, } -impl Acquired -where - Io: AsyncRead + AsyncWrite + Unpin + 'static, -{ +impl Acquired { /// Close the IO. - pub(crate) fn close(&self, conn: ConnectionType) { + pub(super) fn close(&self, conn: ConnectionInnerType) { self.inner.close(conn); } /// Release IO back into pool. - pub(crate) fn release(&self, conn: ConnectionType, created: Instant) { + pub(super) fn release(&self, conn: ConnectionInnerType, created: Instant) { let Acquired { key, inner, .. } = self; inner @@ -376,7 +381,7 @@ mod test { use http::Uri; use super::*; - use crate::client::connection::IoConnection; + use crate::client::connection::ConnectionType; /// A stream type that always returns pending on async read. /// @@ -423,6 +428,7 @@ mod test { } } + #[derive(Clone)] struct TestPoolConnector { generated: Rc>, } @@ -441,12 +447,14 @@ mod test { } } - fn release(conn: IoConnection) + fn release(conn: ConnectionType) where T: AsyncRead + AsyncWrite + Unpin + 'static, { - let (conn, created, acquired) = conn.into_parts(); - acquired.release(conn, created); + match conn { + ConnectionType::H1(mut conn) => conn.on_release(true), + ConnectionType::H2(mut conn) => conn.on_release(false), + } } #[actix_rt::test] diff --git a/awc/CHANGES.md b/awc/CHANGES.md index bf7bffc49..d2cb7c009 100644 --- a/awc/CHANGES.md +++ b/awc/CHANGES.md @@ -1,6 +1,10 @@ # Changes ## Unreleased - 2021-xx-xx +### Changed +* `ConnectorService` type is renamed to `BoxConnectorService` [#2081] + +[#2081]: https://github.com/actix/actix-web/pull/2081 ## 3.0.0-beta.3 - 2021-03-08 diff --git a/awc/src/builder.rs b/awc/src/builder.rs index 33f43ab93..925d9ae2a 100644 --- a/awc/src/builder.rs +++ b/awc/src/builder.rs @@ -6,7 +6,7 @@ use std::time::Duration; use actix_codec::{AsyncRead, AsyncWrite}; use actix_http::{ - client::{Connector, TcpConnect, TcpConnectError, TcpConnection}, + client::{Connector, ConnectorService, TcpConnect, TcpConnectError, TcpConnection}, http::{self, header, Error as HttpError, HeaderMap, HeaderName, Uri}, }; use actix_rt::net::TcpStream; @@ -15,7 +15,7 @@ use actix_service::{boxed, Service}; use crate::connect::DefaultConnector; use crate::error::SendRequestError; use crate::middleware::{NestTransform, Redirect, Transform}; -use crate::{Client, ClientConfig, ConnectRequest, ConnectResponse, ConnectorService}; +use crate::{Client, ClientConfig, ConnectRequest, ConnectResponse}; /// An HTTP Client builder /// @@ -234,7 +234,7 @@ where /// Finish build process and create `Client` instance. pub fn finish(self) -> Client where - M: Transform + 'static, + M: Transform>, ConnectRequest> + 'static, M::Transform: Service, { @@ -250,7 +250,7 @@ where fn _finish(self) -> Client where - M: Transform + 'static, + M: Transform>, ConnectRequest> + 'static, M::Transform: Service, { @@ -269,16 +269,14 @@ where connector = connector.local_address(val); } - let connector = boxed::service(DefaultConnector::new(connector.finish())); - let connector = boxed::service(self.middleware.new_transform(connector)); + let connector = DefaultConnector::new(connector.finish()); + let connector = boxed::rc_service(self.middleware.new_transform(connector)); - let config = ClientConfig { - headers: self.headers, + Client(ClientConfig { + headers: Rc::new(self.headers), timeout: self.timeout, connector, - }; - - Client(Rc::new(config)) + }) } } diff --git a/awc/src/connect.rs b/awc/src/connect.rs index d3bc01ed6..6a9fc4630 100644 --- a/awc/src/connect.rs +++ b/awc/src/connect.rs @@ -2,6 +2,7 @@ use std::{ future::Future, net, pin::Pin, + rc::Rc, task::{Context, Poll}, }; @@ -19,7 +20,7 @@ use futures_core::{future::LocalBoxFuture, ready}; use crate::response::ClientResponse; -pub type ConnectorService = Box< +pub type BoxConnectorService = Rc< dyn Service< ConnectRequest, Response = ConnectResponse, @@ -28,6 +29,8 @@ pub type ConnectorService = Box< >, >; +pub type BoxedSocket = Box; + pub enum ConnectRequest { Client(RequestHeadType, Body, Option), Tunnel(RequestHead, Option), @@ -58,7 +61,7 @@ impl ConnectResponse { } } -pub(crate) struct DefaultConnector { +pub struct DefaultConnector { connector: S, } @@ -68,15 +71,14 @@ impl DefaultConnector { } } -impl Service for DefaultConnector +impl Service for DefaultConnector where - S: Service, - S::Response: Connection, - ::Io: 'static, + S: Service>, + Io: ConnectionIo, { type Response = ConnectResponse; type Error = SendRequestError; - type Future = ConnectRequestFuture::Io>; + type Future = ConnectRequestFuture; actix_service::forward_ready!(connector); @@ -102,7 +104,10 @@ where pin_project_lite::pin_project! { #[project = ConnectRequestProj] - pub(crate) enum ConnectRequestFuture { + pub enum ConnectRequestFuture + where + Io: ConnectionIo + { Connection { #[pin] fut: Fut, @@ -114,16 +119,15 @@ pin_project_lite::pin_project! { Tunnel { fut: LocalBoxFuture< 'static, - Result<(ResponseHead, Framed), SendRequestError>, + Result<(ResponseHead, Framed, ClientCodec>), SendRequestError>, >, } } } -impl Future for ConnectRequestFuture +impl Future for ConnectRequestFuture where - Fut: Future>, - C: Connection, + Fut: Future, ConnectError>>, Io: ConnectionIo, { type Output = Result; @@ -165,5 +169,3 @@ where } } } - -pub type BoxedSocket = Box; diff --git a/awc/src/frozen.rs b/awc/src/frozen.rs index 46b4063a0..5fe8edb19 100644 --- a/awc/src/frozen.rs +++ b/awc/src/frozen.rs @@ -23,7 +23,7 @@ pub struct FrozenClientRequest { pub(crate) addr: Option, pub(crate) response_decompress: bool, pub(crate) timeout: Option, - pub(crate) config: Rc, + pub(crate) config: ClientConfig, } impl FrozenClientRequest { @@ -51,7 +51,7 @@ impl FrozenClientRequest { self.addr, self.response_decompress, self.timeout, - self.config.as_ref(), + &self.config, body, ) } @@ -62,7 +62,7 @@ impl FrozenClientRequest { self.addr, self.response_decompress, self.timeout, - self.config.as_ref(), + &self.config, value, ) } @@ -73,7 +73,7 @@ impl FrozenClientRequest { self.addr, self.response_decompress, self.timeout, - self.config.as_ref(), + &self.config, value, ) } @@ -88,7 +88,7 @@ impl FrozenClientRequest { self.addr, self.response_decompress, self.timeout, - self.config.as_ref(), + &self.config, stream, ) } @@ -99,7 +99,7 @@ impl FrozenClientRequest { self.addr, self.response_decompress, self.timeout, - self.config.as_ref(), + &self.config, ) } @@ -168,7 +168,7 @@ impl FrozenSendBuilder { self.req.addr, self.req.response_decompress, self.req.timeout, - self.req.config.as_ref(), + &self.req.config, body, ) } @@ -183,7 +183,7 @@ impl FrozenSendBuilder { self.req.addr, self.req.response_decompress, self.req.timeout, - self.req.config.as_ref(), + &self.req.config, value, ) } @@ -198,7 +198,7 @@ impl FrozenSendBuilder { self.req.addr, self.req.response_decompress, self.req.timeout, - self.req.config.as_ref(), + &self.req.config, value, ) } @@ -217,7 +217,7 @@ impl FrozenSendBuilder { self.req.addr, self.req.response_decompress, self.req.timeout, - self.req.config.as_ref(), + &self.req.config, stream, ) } @@ -232,7 +232,7 @@ impl FrozenSendBuilder { self.req.addr, self.req.response_decompress, self.req.timeout, - self.req.config.as_ref(), + &self.req.config, ) } } diff --git a/awc/src/lib.rs b/awc/src/lib.rs index 77e08fbbd..8addf4d8b 100644 --- a/awc/src/lib.rs +++ b/awc/src/lib.rs @@ -121,7 +121,7 @@ pub mod test; pub mod ws; pub use self::builder::ClientBuilder; -pub use self::connect::{BoxedSocket, ConnectRequest, ConnectResponse, ConnectorService}; +pub use self::connect::{BoxConnectorService, BoxedSocket, ConnectRequest, ConnectResponse}; pub use self::frozen::{FrozenClientRequest, FrozenSendBuilder}; pub use self::request::ClientRequest; pub use self::response::{ClientResponse, JsonBody, MessageBody}; @@ -147,11 +147,12 @@ pub use self::sender::SendClientRequest; /// } /// ``` #[derive(Clone)] -pub struct Client(Rc); +pub struct Client(ClientConfig); +#[derive(Clone)] pub(crate) struct ClientConfig { - pub(crate) connector: ConnectorService, - pub(crate) headers: HeaderMap, + pub(crate) connector: BoxConnectorService, + pub(crate) headers: Rc, pub(crate) timeout: Option, } diff --git a/awc/src/middleware/redirect.rs b/awc/src/middleware/redirect.rs index f8bdd2def..62ea1d0ac 100644 --- a/awc/src/middleware/redirect.rs +++ b/awc/src/middleware/redirect.rs @@ -189,7 +189,7 @@ where // remove body .call(ConnectRequest::Client(head, Body::None, addr)); - self.as_mut().set(RedirectServiceFuture::Client { + self.set(RedirectServiceFuture::Client { fut, max_redirect_times, uri: Some(uri), @@ -236,7 +236,7 @@ where .unwrap() .call(ConnectRequest::Client(head, body_new, addr)); - self.as_mut().set(RedirectServiceFuture::Client { + self.set(RedirectServiceFuture::Client { fut, max_redirect_times, uri: Some(uri), diff --git a/awc/src/request.rs b/awc/src/request.rs index 1b63f3687..3e9babb7d 100644 --- a/awc/src/request.rs +++ b/awc/src/request.rs @@ -57,7 +57,7 @@ pub struct ClientRequest { addr: Option, response_decompress: bool, timeout: Option, - config: Rc, + config: ClientConfig, #[cfg(feature = "cookies")] cookies: Option, @@ -65,7 +65,7 @@ pub struct ClientRequest { impl ClientRequest { /// Create new client request builder. - pub(crate) fn new(method: Method, uri: U, config: Rc) -> Self + pub(crate) fn new(method: Method, uri: U, config: ClientConfig) -> Self where Uri: TryFrom, >::Error: Into, @@ -398,7 +398,7 @@ impl ClientRequest { slf.addr, slf.response_decompress, slf.timeout, - slf.config.as_ref(), + &slf.config, body, ) } @@ -414,7 +414,7 @@ impl ClientRequest { slf.addr, slf.response_decompress, slf.timeout, - slf.config.as_ref(), + &slf.config, value, ) } @@ -432,7 +432,7 @@ impl ClientRequest { slf.addr, slf.response_decompress, slf.timeout, - slf.config.as_ref(), + &slf.config, value, ) } @@ -452,7 +452,7 @@ impl ClientRequest { slf.addr, slf.response_decompress, slf.timeout, - slf.config.as_ref(), + &slf.config, stream, ) } @@ -468,7 +468,7 @@ impl ClientRequest { slf.addr, slf.response_decompress, slf.timeout, - slf.config.as_ref(), + &slf.config, ) } diff --git a/awc/src/ws.rs b/awc/src/ws.rs index f64e9e19a..df25b7289 100644 --- a/awc/src/ws.rs +++ b/awc/src/ws.rs @@ -28,7 +28,6 @@ use std::convert::TryFrom; use std::net::SocketAddr; -use std::rc::Rc; use std::{fmt, str}; use actix_codec::Framed; @@ -56,7 +55,7 @@ pub struct WebsocketsRequest { addr: Option, max_size: usize, server_mode: bool, - config: Rc, + config: ClientConfig, #[cfg(feature = "cookies")] cookies: Option, @@ -64,7 +63,7 @@ pub struct WebsocketsRequest { impl WebsocketsRequest { /// Create new WebSocket connection - pub(crate) fn new(uri: U, config: Rc) -> Self + pub(crate) fn new(uri: U, config: ClientConfig) -> Self where Uri: TryFrom, >::Error: Into, From 81942d31d66b2a3fd4a5ed673ce31d21ae8ec5fa Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Fri, 19 Mar 2021 02:02:30 +0000 Subject: [PATCH 09/79] fix new dyn trait lint --- actix-http/src/error.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actix-http/src/error.rs b/actix-http/src/error.rs index d3095e68d..d9b97d6a6 100644 --- a/actix-http/src/error.rs +++ b/actix-http/src/error.rs @@ -54,7 +54,7 @@ impl Error { /// Similar to `as_response_error` but downcasts. pub fn as_error(&self) -> Option<&T> { - ResponseError::downcast_ref(self.cause.as_ref()) + ::downcast_ref(self.cause.as_ref()) } } From 78fcd0237a4033e9802313de0299ce9cbb0a614c Mon Sep 17 00:00:00 2001 From: Ibraheem Ahmed Date: Fri, 19 Mar 2021 00:08:23 -0400 Subject: [PATCH 10/79] Format extract macro (#2087) --- src/extract.rs | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/src/extract.rs b/src/extract.rs index 7a677bca4..921d9fc36 100644 --- a/src/extract.rs +++ b/src/extract.rs @@ -302,13 +302,13 @@ macro_rules! tuple_from_req ({$fut_type:ident, $(($n:tt, $T:ident)),+} => { } )+ - if ready { - Poll::Ready(Ok( - ($(this.items.$n.take().unwrap(),)+) - )) - } else { - Poll::Pending - } + if ready { + Poll::Ready(Ok( + ($(this.items.$n.take().unwrap(),)+) + )) + } else { + Poll::Pending + } } } } @@ -318,16 +318,16 @@ macro_rules! tuple_from_req ({$fut_type:ident, $(($n:tt, $T:ident)),+} => { mod m { use super::*; -tuple_from_req!(TupleFromRequest1, (0, A)); -tuple_from_req!(TupleFromRequest2, (0, A), (1, B)); -tuple_from_req!(TupleFromRequest3, (0, A), (1, B), (2, C)); -tuple_from_req!(TupleFromRequest4, (0, A), (1, B), (2, C), (3, D)); -tuple_from_req!(TupleFromRequest5, (0, A), (1, B), (2, C), (3, D), (4, E)); -tuple_from_req!(TupleFromRequest6, (0, A), (1, B), (2, C), (3, D), (4, E), (5, F)); -tuple_from_req!(TupleFromRequest7, (0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G)); -tuple_from_req!(TupleFromRequest8, (0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G), (7, H)); -tuple_from_req!(TupleFromRequest9, (0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G), (7, H), (8, I)); -tuple_from_req!(TupleFromRequest10, (0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G), (7, H), (8, I), (9, J)); + tuple_from_req!(TupleFromRequest1, (0, A)); + tuple_from_req!(TupleFromRequest2, (0, A), (1, B)); + tuple_from_req!(TupleFromRequest3, (0, A), (1, B), (2, C)); + tuple_from_req!(TupleFromRequest4, (0, A), (1, B), (2, C), (3, D)); + tuple_from_req!(TupleFromRequest5, (0, A), (1, B), (2, C), (3, D), (4, E)); + tuple_from_req!(TupleFromRequest6, (0, A), (1, B), (2, C), (3, D), (4, E), (5, F)); + tuple_from_req!(TupleFromRequest7, (0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G)); + tuple_from_req!(TupleFromRequest8, (0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G), (7, H)); + tuple_from_req!(TupleFromRequest9, (0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G), (7, H), (8, I)); + tuple_from_req!(TupleFromRequest10, (0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G), (7, H), (8, I), (9, J)); } #[cfg(test)] From 351286486c5b88d6fa879ba2d0ccc0335f41dfc1 Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Fri, 19 Mar 2021 04:25:35 -0700 Subject: [PATCH 11/79] fix clippy warning on nightly (#2088) * fix clippy warning on nightly --- actix-files/src/service.rs | 2 +- actix-http/src/client/connection.rs | 2 +- actix-http/src/config.rs | 20 +++++--------------- actix-http/src/header/shared/entity.rs | 5 ++--- actix-http/src/header/shared/extended.rs | 2 +- src/config.rs | 2 +- src/lib.rs | 12 ++---------- src/middleware/compress.rs | 17 +++++++++-------- src/resource.rs | 2 +- src/test.rs | 2 +- tests/test_httpserver.rs | 16 ++++++---------- 11 files changed, 30 insertions(+), 52 deletions(-) diff --git a/actix-files/src/service.rs b/actix-files/src/service.rs index 14eea6ebc..3214963ed 100644 --- a/actix-files/src/service.rs +++ b/actix-files/src/service.rs @@ -1,4 +1,4 @@ -use std::{fmt, io, path::PathBuf, rc::Rc, task::Poll}; +use std::{fmt, io, path::PathBuf, rc::Rc}; use actix_service::Service; use actix_web::{ diff --git a/actix-http/src/client/connection.rs b/actix-http/src/client/connection.rs index 89dfd59de..78101397d 100644 --- a/actix-http/src/client/connection.rs +++ b/actix-http/src/client/connection.rs @@ -325,7 +325,7 @@ where } } -const H2_UNREACHABLE_WRITE: &'static str = "H2Connection can not impl AsyncWrite trait"; +const H2_UNREACHABLE_WRITE: &str = "H2Connection can not impl AsyncWrite trait"; impl AsyncWrite for Connection where diff --git a/actix-http/src/config.rs b/actix-http/src/config.rs index 9f84b8694..9a2293e92 100644 --- a/actix-http/src/config.rs +++ b/actix-http/src/config.rs @@ -126,9 +126,7 @@ impl ServiceConfig { pub fn client_timer(&self) -> Option { let delay_time = self.0.client_timeout; if delay_time != 0 { - Some(sleep_until( - self.0.date_service.now() + Duration::from_millis(delay_time), - )) + Some(sleep_until(self.now() + Duration::from_millis(delay_time))) } else { None } @@ -138,7 +136,7 @@ impl ServiceConfig { pub fn client_timer_expire(&self) -> Option { let delay = self.0.client_timeout; if delay != 0 { - Some(self.0.date_service.now() + Duration::from_millis(delay)) + Some(self.now() + Duration::from_millis(delay)) } else { None } @@ -148,7 +146,7 @@ impl ServiceConfig { pub fn client_disconnect_timer(&self) -> Option { let delay = self.0.client_disconnect; if delay != 0 { - Some(self.0.date_service.now() + Duration::from_millis(delay)) + Some(self.now() + Duration::from_millis(delay)) } else { None } @@ -157,20 +155,12 @@ impl ServiceConfig { #[inline] /// Return keep-alive timer delay is configured. pub fn keep_alive_timer(&self) -> Option { - if let Some(ka) = self.0.keep_alive { - Some(sleep_until(self.0.date_service.now() + ka)) - } else { - None - } + self.keep_alive().map(|ka| sleep_until(self.now() + ka)) } /// Keep-alive expire time pub fn keep_alive_expire(&self) -> Option { - if let Some(ka) = self.0.keep_alive { - Some(self.0.date_service.now() + ka) - } else { - None - } + self.keep_alive().map(|ka| self.now() + ka) } #[inline] diff --git a/actix-http/src/header/shared/entity.rs b/actix-http/src/header/shared/entity.rs index eb383cd6f..2505216f2 100644 --- a/actix-http/src/header/shared/entity.rs +++ b/actix-http/src/header/shared/entity.rs @@ -127,9 +127,8 @@ impl Display for EntityTag { impl FromStr for EntityTag { type Err = crate::error::ParseError; - fn from_str(s: &str) -> Result { - let length: usize = s.len(); - let slice = &s[..]; + fn from_str(slice: &str) -> Result { + let length = slice.len(); // Early exits if it doesn't terminate in a DQUOTE. if !slice.ends_with('"') || slice.len() < 2 { return Err(crate::error::ParseError::Header); diff --git a/actix-http/src/header/shared/extended.rs b/actix-http/src/header/shared/extended.rs index 6bdcb7922..9fd4cdfb0 100644 --- a/actix-http/src/header/shared/extended.rs +++ b/actix-http/src/header/shared/extended.rs @@ -88,9 +88,9 @@ pub fn parse_extended_value( }; Ok(ExtendedValue { - value, charset, language_tag, + value, }) } diff --git a/src/config.rs b/src/config.rs index bd9a25c6f..cd14eb4cc 100644 --- a/src/config.rs +++ b/src/config.rs @@ -113,7 +113,7 @@ pub struct AppConfig { impl AppConfig { pub(crate) fn new(secure: bool, addr: SocketAddr, host: String) -> Self { - AppConfig { secure, addr, host } + AppConfig { secure, host, addr } } /// Server host name. diff --git a/src/lib.rs b/src/lib.rs index 16b2ab186..136c462b8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -173,11 +173,7 @@ pub mod dev { impl BodyEncoding for ResponseBuilder { fn get_encoding(&self) -> Option { - if let Some(ref enc) = self.extensions().get::() { - Some(enc.0) - } else { - None - } + self.extensions().get::().map(|enc| enc.0) } fn encoding(&mut self, encoding: ContentEncoding) -> &mut Self { @@ -188,11 +184,7 @@ pub mod dev { impl BodyEncoding for Response { fn get_encoding(&self) -> Option { - if let Some(ref enc) = self.extensions().get::() { - Some(enc.0) - } else { - None - } + self.extensions().get::().map(|enc| enc.0) } fn encoding(&mut self, encoding: ContentEncoding) -> &mut Self { diff --git a/src/middleware/compress.rs b/src/middleware/compress.rs index 698ba768e..fc1a85d30 100644 --- a/src/middleware/compress.rs +++ b/src/middleware/compress.rs @@ -197,22 +197,23 @@ impl AcceptEncoding { /// Parse a raw Accept-Encoding header value into an ordered list. pub fn parse(raw: &str, encoding: ContentEncoding) -> ContentEncoding { - let mut encodings: Vec<_> = raw + let mut encodings = raw .replace(' ', "") .split(',') .map(|l| AcceptEncoding::new(l)) - .collect(); + .flatten() + .collect::>(); + encodings.sort(); for enc in encodings { - if let Some(enc) = enc { - if encoding == ContentEncoding::Auto { - return enc.encoding; - } else if encoding == enc.encoding { - return encoding; - } + if encoding == ContentEncoding::Auto { + return enc.encoding; + } else if encoding == enc.encoding { + return encoding; } } + ContentEncoding::Identity } } diff --git a/src/resource.rs b/src/resource.rs index 1a5619de6..d35131cbb 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -449,9 +449,9 @@ impl ServiceFactory for ResourceFactory { .collect::, _>>()?; Ok(ResourceService { + routes, app_data, default, - routes, }) }) } diff --git a/src/test.rs b/src/test.rs index dd2426fec..2ebd64558 100644 --- a/src/test.rs +++ b/src/test.rs @@ -774,10 +774,10 @@ where }; TestServer { - ssl, addr, client, system, + ssl, server, } } diff --git a/tests/test_httpserver.rs b/tests/test_httpserver.rs index aa2b2ca74..12225b7e5 100644 --- a/tests/test_httpserver.rs +++ b/tests/test_httpserver.rs @@ -1,15 +1,11 @@ -use std::sync::mpsc; -use std::{thread, time::Duration}; - #[cfg(feature = "openssl")] extern crate tls_openssl as openssl; -#[cfg(feature = "rustls")] -extern crate tls_rustls as rustls; -#[cfg(feature = "openssl")] -use openssl::ssl::SslAcceptorBuilder; - -use actix_web::{test, web, App, HttpResponse, HttpServer}; +#[cfg(any(unix, feature = "openssl"))] +use { + actix_web::{test, web, App, HttpResponse, HttpServer}, + std::{sync::mpsc, thread, time::Duration}, +}; #[cfg(unix)] #[actix_rt::test] @@ -72,7 +68,7 @@ async fn test_start() { } #[cfg(feature = "openssl")] -fn ssl_acceptor() -> SslAcceptorBuilder { +fn ssl_acceptor() -> openssl::ssl::SslAcceptorBuilder { use openssl::{ pkey::PKey, ssl::{SslAcceptor, SslMethod}, From 9488757c29bf575a113041886050bafad2129a2d Mon Sep 17 00:00:00 2001 From: Thomas de Zeeuw Date: Fri, 19 Mar 2021 12:17:06 +0000 Subject: [PATCH 12/79] Update to socket2 v0.4 (#2092) --- Cargo.toml | 2 +- actix-http-test/Cargo.toml | 2 +- actix-http-test/src/lib.rs | 4 ++-- src/server.rs | 9 +++------ src/test.rs | 4 ++-- 5 files changed, 9 insertions(+), 12 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 0877edf9c..f3a6271ee 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -105,7 +105,7 @@ serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" serde_urlencoded = "0.7" smallvec = "1.6" -socket2 = "0.3.16" +socket2 = "0.4.0" time = { version = "0.2.23", default-features = false, features = ["std"] } tls-openssl = { package = "openssl", version = "0.10.9", optional = true } tls-rustls = { package = "rustls", version = "0.19.0", optional = true } diff --git a/actix-http-test/Cargo.toml b/actix-http-test/Cargo.toml index c1e61556b..a7efc5310 100644 --- a/actix-http-test/Cargo.toml +++ b/actix-http-test/Cargo.toml @@ -42,7 +42,7 @@ bytes = "1" futures-core = { version = "0.3.7", default-features = false } http = "0.2.2" log = "0.4" -socket2 = "0.3" +socket2 = "0.4" serde = "1.0" serde_json = "1.0" slab = "0.4" diff --git a/actix-http-test/src/lib.rs b/actix-http-test/src/lib.rs index 8de07c8d3..138f14313 100644 --- a/actix-http-test/src/lib.rs +++ b/actix-http-test/src/lib.rs @@ -118,10 +118,10 @@ pub async fn test_server_with_addr>( /// Get first available unused address pub fn unused_addr() -> net::SocketAddr { let addr: net::SocketAddr = "127.0.0.1:0".parse().unwrap(); - let socket = Socket::new(Domain::ipv4(), Type::stream(), Some(Protocol::tcp())).unwrap(); + let socket = Socket::new(Domain::IPV4, Type::STREAM, Some(Protocol::TCP)).unwrap(); socket.bind(&addr.into()).unwrap(); socket.set_reuse_address(true).unwrap(); - let tcp = socket.into_tcp_listener(); + let tcp = net::TcpListener::from(socket); tcp.local_addr().unwrap() } diff --git a/src/server.rs b/src/server.rs index 99355593a..cfd8ae4b4 100644 --- a/src/server.rs +++ b/src/server.rs @@ -607,17 +607,14 @@ where fn create_tcp_listener(addr: net::SocketAddr, backlog: u32) -> io::Result { use socket2::{Domain, Protocol, Socket, Type}; - let domain = match addr { - net::SocketAddr::V4(_) => Domain::ipv4(), - net::SocketAddr::V6(_) => Domain::ipv6(), - }; - let socket = Socket::new(domain, Type::stream(), Some(Protocol::tcp()))?; + let domain = Domain::for_address(addr); + let socket = Socket::new(domain, Type::STREAM, Some(Protocol::TCP))?; socket.set_reuse_address(true)?; socket.bind(&addr.into())?; // clamp backlog to max u32 that fits in i32 range let backlog = cmp::min(backlog, i32::MAX as u32) as i32; socket.listen(backlog)?; - Ok(socket.into_tcp_listener()) + Ok(net::TcpListener::from(socket)) } #[cfg(feature = "openssl")] diff --git a/src/test.rs b/src/test.rs index 2ebd64558..bc19296e2 100644 --- a/src/test.rs +++ b/src/test.rs @@ -862,10 +862,10 @@ impl TestServerConfig { /// Get first available unused address pub fn unused_addr() -> net::SocketAddr { let addr: net::SocketAddr = "127.0.0.1:0".parse().unwrap(); - let socket = Socket::new(Domain::ipv4(), Type::stream(), Some(Protocol::tcp())).unwrap(); + let socket = Socket::new(Domain::IPV4, Type::STREAM, Some(Protocol::TCP)).unwrap(); socket.bind(&addr.into()).unwrap(); socket.set_reuse_address(true).unwrap(); - let tcp = socket.into_tcp_listener(); + let tcp = net::TcpListener::from(socket); tcp.local_addr().unwrap() } From 8d9de768264e78cf62087be44872c17e24f5428a Mon Sep 17 00:00:00 2001 From: Ibraheem Ahmed Date: Fri, 19 Mar 2021 12:30:53 -0400 Subject: [PATCH 13/79] Simplify handler factory macro (#2086) --- src/handler.rs | 50 +++++++++++++++++++------------------------------- 1 file changed, 19 insertions(+), 31 deletions(-) diff --git a/src/handler.rs b/src/handler.rs index 0016b741e..7e3c5f47e 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -28,17 +28,6 @@ where fn call(&self, param: T) -> R; } -impl Handler<(), R> for F -where - F: Fn() -> R + Clone + 'static, - R: Future, - R::Output: Responder, -{ - fn call(&self, _: ()) -> R { - (self)() - } -} - #[doc(hidden)] /// Extract arguments from request, run factory function and make response. pub struct HandlerService @@ -177,30 +166,29 @@ where } /// FromRequest trait impl for tuples -macro_rules! factory_tuple ({ $(($n:tt, $T:ident)),+} => { - impl Handler<($($T,)+), Res> for Func - where Func: Fn($($T,)+) -> Res + Clone + 'static, +macro_rules! factory_tuple ({ $($param:ident)* } => { + impl Handler<($($param,)*), Res> for Func + where Func: Fn($($param),*) -> Res + Clone + 'static, Res: Future, Res::Output: Responder, { - fn call(&self, param: ($($T,)+)) -> Res { - (self)($(param.$n,)+) + #[allow(non_snake_case)] + fn call(&self, ($($param,)*): ($($param,)*)) -> Res { + (self)($($param,)*) } } }); -#[rustfmt::skip] -mod m { - use super::*; - - factory_tuple!((0, A)); - factory_tuple!((0, A), (1, B)); - factory_tuple!((0, A), (1, B), (2, C)); - factory_tuple!((0, A), (1, B), (2, C), (3, D)); - factory_tuple!((0, A), (1, B), (2, C), (3, D), (4, E)); - factory_tuple!((0, A), (1, B), (2, C), (3, D), (4, E), (5, F)); - factory_tuple!((0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G)); - factory_tuple!((0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G), (7, H)); - factory_tuple!((0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G), (7, H), (8, I)); - factory_tuple!((0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G), (7, H), (8, I), (9, J)); -} +factory_tuple! {} +factory_tuple! { A } +factory_tuple! { A B } +factory_tuple! { A B C } +factory_tuple! { A B C D } +factory_tuple! { A B C D E } +factory_tuple! { A B C D E F } +factory_tuple! { A B C D E F G } +factory_tuple! { A B C D E F G H } +factory_tuple! { A B C D E F G H I } +factory_tuple! { A B C D E F G H I J } +factory_tuple! { A B C D E F G H I J K } +factory_tuple! { A B C D E F G H I J K L } From 746d983849d7e5bb5ca4ac242276f6e65cca5643 Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Fri, 19 Mar 2021 22:18:06 -0700 Subject: [PATCH 14/79] handle header error with CustomResponder (#2093) --- CHANGES.md | 5 +++++ src/responder.rs | 31 +++++++++++++++---------------- 2 files changed, 20 insertions(+), 16 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index ef9ee900a..5df0b6d6d 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -4,11 +4,16 @@ ### Fixed * Double ampersand in Logger format is escaped correctly. [#2067] +### Changed +* `CustomResponder` would return error as `HttpResponse` when `CustomResponder::with_header` failed instead of skipping. + (Only the first error is kept when multiple error occur) [#2093] + ### Removed * The `client` mod was removed. Clients should now use `awc` directly. [871ca5e4](https://github.com/actix/actix-web/commit/871ca5e4ae2bdc22d1ea02701c2992fa8d04aed7) [#2067]: https://github.com/actix/actix-web/pull/2067 +[#2093]: https://github.com/actix/actix-web/pull/2093 ## 4.0.0-beta.4 - 2021-03-09 diff --git a/src/responder.rs b/src/responder.rs index 92945cdaa..0b6f47c6b 100644 --- a/src/responder.rs +++ b/src/responder.rs @@ -155,8 +155,7 @@ impl Responder for BytesMut { pub struct CustomResponder { responder: T, status: Option, - headers: Option, - error: Option, + headers: Result, } impl CustomResponder { @@ -164,8 +163,7 @@ impl CustomResponder { CustomResponder { responder, status: None, - headers: None, - error: None, + headers: Ok(HeaderMap::new()), } } @@ -206,32 +204,33 @@ impl CustomResponder { where H: IntoHeaderPair, { - if self.headers.is_none() { - self.headers = Some(HeaderMap::new()); + if let Ok(ref mut headers) = self.headers { + match header.try_into_header_pair() { + Ok((key, value)) => headers.append(key, value), + Err(e) => self.headers = Err(e.into()), + }; } - match header.try_into_header_pair() { - Ok((key, value)) => self.headers.as_mut().unwrap().append(key, value), - Err(e) => self.error = Some(e.into()), - }; - self } } impl Responder for CustomResponder { fn respond_to(self, req: &HttpRequest) -> HttpResponse { + let headers = match self.headers { + Ok(headers) => headers, + Err(err) => return HttpResponse::from_error(Error::from(err)), + }; + let mut res = self.responder.respond_to(req); if let Some(status) = self.status { *res.status_mut() = status; } - if let Some(ref headers) = self.headers { - for (k, v) in headers { - // TODO: before v4, decide if this should be append instead - res.headers_mut().insert(k.clone(), v.clone()); - } + for (k, v) in headers { + // TODO: before v4, decide if this should be append instead + res.headers_mut().insert(k, v); } res From 1be54efbebe0695e7ad104d2747a1f3f66fb5dea Mon Sep 17 00:00:00 2001 From: Ibraheem Ahmed Date: Tue, 23 Mar 2021 09:42:46 -0400 Subject: [PATCH 15/79] Simplify service factory macro (#2108) --- src/service.rs | 35 ++++++++++++++++------------------- 1 file changed, 16 insertions(+), 19 deletions(-) diff --git a/src/service.rs b/src/service.rs index fcbe61a02..491810286 100644 --- a/src/service.rs +++ b/src/service.rs @@ -573,31 +573,28 @@ macro_rules! services { } /// HttpServiceFactory trait impl for tuples -macro_rules! service_tuple ({ $(($n:tt, $T:ident)),+} => { +macro_rules! service_tuple ({ $($T:ident)+ } => { impl<$($T: HttpServiceFactory),+> HttpServiceFactory for ($($T,)+) { + #[allow(non_snake_case)] fn register(self, config: &mut AppService) { - $(self.$n.register(config);)+ + let ($($T,)*) = self; + $($T.register(config);)+ } } }); -#[rustfmt::skip] -mod m { - use super::*; - - service_tuple!((0, A)); - service_tuple!((0, A), (1, B)); - service_tuple!((0, A), (1, B), (2, C)); - service_tuple!((0, A), (1, B), (2, C), (3, D)); - service_tuple!((0, A), (1, B), (2, C), (3, D), (4, E)); - service_tuple!((0, A), (1, B), (2, C), (3, D), (4, E), (5, F)); - service_tuple!((0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G)); - service_tuple!((0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G), (7, H)); - service_tuple!((0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G), (7, H), (8, I)); - service_tuple!((0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G), (7, H), (8, I), (9, J)); - service_tuple!((0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G), (7, H), (8, I), (9, J), (10, K)); - service_tuple!((0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G), (7, H), (8, I), (9, J), (10, K), (11, L)); -} +service_tuple! { A } +service_tuple! { A B } +service_tuple! { A B C } +service_tuple! { A B C D } +service_tuple! { A B C D E } +service_tuple! { A B C D E F } +service_tuple! { A B C D E F G } +service_tuple! { A B C D E F G H } +service_tuple! { A B C D E F G H I } +service_tuple! { A B C D E F G H I J } +service_tuple! { A B C D E F G H I J K } +service_tuple! { A B C D E F G H I J K L } #[cfg(test)] mod tests { From 9704beddf86930251f97557ee5c058c95ec91e71 Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Wed, 24 Mar 2021 04:44:03 -0700 Subject: [PATCH 16/79] Relax MessageBody limit to 2048kb (#2110) * relax MessageBody limit to 2048kb * fix clippy * Update awc/src/response.rs Co-authored-by: Rob Ede * fix default body limit Co-authored-by: Rob Ede --- awc/src/response.rs | 83 +++++++++++++++++++++------------------------ 1 file changed, 38 insertions(+), 45 deletions(-) diff --git a/awc/src/response.rs b/awc/src/response.rs index 40de3dc17..994ddb761 100644 --- a/awc/src/response.rs +++ b/awc/src/response.rs @@ -228,12 +228,13 @@ impl fmt::Debug for ClientResponse { } } +const DEFAULT_BODY_LIMIT: usize = 2 * 1024 * 1024; + /// Future that resolves to a complete HTTP message body. pub struct MessageBody { length: Option, - err: Option, timeout: ResponseTimeout, - fut: Option>, + body: Result, Option>, } impl MessageBody @@ -242,41 +243,38 @@ where { /// Create `MessageBody` for request. pub fn new(res: &mut ClientResponse) -> MessageBody { - let mut len = None; - if let Some(l) = res.headers().get(&header::CONTENT_LENGTH) { - if let Ok(s) = l.to_str() { - if let Ok(l) = s.parse::() { - len = Some(l) - } else { - return Self::err(PayloadError::UnknownLength); + let length = match res.headers().get(&header::CONTENT_LENGTH) { + Some(value) => { + let len = value.to_str().ok().and_then(|s| s.parse::().ok()); + + match len { + None => return Self::err(PayloadError::UnknownLength), + len => len, } - } else { - return Self::err(PayloadError::UnknownLength); } - } + None => None, + }; MessageBody { - length: len, - err: None, + length, timeout: std::mem::take(&mut res.timeout), - fut: Some(ReadBody::new(res.take_payload(), 262_144)), + body: Ok(ReadBody::new(res.take_payload(), DEFAULT_BODY_LIMIT)), } } - /// Change max size of payload. By default max size is 256kB + /// Change max size of payload. By default max size is 2048kB pub fn limit(mut self, limit: usize) -> Self { - if let Some(ref mut fut) = self.fut { - fut.limit = limit; + if let Ok(ref mut body) = self.body { + body.limit = limit; } self } fn err(e: PayloadError) -> Self { MessageBody { - fut: None, - err: Some(e), length: None, timeout: ResponseTimeout::default(), + body: Err(Some(e)), } } } @@ -290,19 +288,20 @@ where fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { let this = self.get_mut(); - if let Some(err) = this.err.take() { - return Poll::Ready(Err(err)); - } + match this.body { + Err(ref mut err) => Poll::Ready(Err(err.take().unwrap())), + Ok(ref mut body) => { + if let Some(len) = this.length.take() { + if len > body.limit { + return Poll::Ready(Err(PayloadError::Overflow)); + } + } - if let Some(len) = this.length.take() { - if len > this.fut.as_ref().unwrap().limit { - return Poll::Ready(Err(PayloadError::Overflow)); + this.timeout.poll_timeout(cx)?; + + Pin::new(body).poll(cx) } } - - this.timeout.poll_timeout(cx)?; - - Pin::new(&mut this.fut.as_mut().unwrap()).poll(cx) } } @@ -415,7 +414,7 @@ impl ReadBody { fn new(stream: Payload, limit: usize) -> Self { Self { stream, - buf: BytesMut::with_capacity(std::cmp::min(limit, 32768)), + buf: BytesMut::new(), limit, } } @@ -430,20 +429,14 @@ where fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { let this = self.get_mut(); - loop { - return match Pin::new(&mut this.stream).poll_next(cx)? { - Poll::Ready(Some(chunk)) => { - if (this.buf.len() + chunk.len()) > this.limit { - Poll::Ready(Err(PayloadError::Overflow)) - } else { - this.buf.extend_from_slice(&chunk); - continue; - } - } - Poll::Ready(None) => Poll::Ready(Ok(this.buf.split().freeze())), - Poll::Pending => Poll::Pending, - }; + while let Some(chunk) = ready!(Pin::new(&mut this.stream).poll_next(cx)?) { + if (this.buf.len() + chunk.len()) > this.limit { + return Poll::Ready(Err(PayloadError::Overflow)); + } + this.buf.extend_from_slice(&chunk); } + + Poll::Ready(Ok(this.buf.split().freeze())) } } @@ -462,7 +455,7 @@ mod tests { _ => unreachable!("error"), } - let mut req = TestResponse::with_header(header::CONTENT_LENGTH, "1000000").finish(); + let mut req = TestResponse::with_header(header::CONTENT_LENGTH, "10000000").finish(); match req.body().await.err().unwrap() { PayloadError::Overflow => {} _ => unreachable!("error"), From 3188ef573159263e281f25416b4f960ee6d67cad Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Thu, 25 Mar 2021 08:45:52 +0000 Subject: [PATCH 17/79] don't use rust annotation on code doc blocks --- actix-files/src/files.rs | 2 +- actix-files/src/lib.rs | 2 +- actix-files/src/named.rs | 6 ++--- actix-http-test/src/lib.rs | 2 +- actix-http/src/client/connector.rs | 2 +- actix-http/src/error.rs | 2 +- actix-http/src/header/common/cache_control.rs | 2 +- actix-http/src/response.rs | 8 +++---- actix-http/src/test.rs | 2 +- actix-multipart/src/extractor.rs | 2 +- actix-web-codegen/src/lib.rs | 6 ++--- awc/src/lib.rs | 2 +- awc/src/request.rs | 6 ++--- benches/service.rs | 2 +- src/app.rs | 16 ++++++------- src/data.rs | 2 +- src/extract.rs | 4 ++-- src/guard.rs | 10 ++++---- src/lib.rs | 2 +- src/middleware/compat.rs | 2 +- src/middleware/compress.rs | 2 +- src/middleware/condition.rs | 2 +- src/middleware/default_headers.rs | 2 +- src/middleware/err_handlers.rs | 2 +- src/middleware/logger.rs | 4 ++-- src/middleware/normalize.rs | 2 +- src/request.rs | 6 ++--- src/request_data.rs | 2 +- src/resource.rs | 16 ++++++------- src/responder.rs | 8 +++---- src/route.rs | 8 +++---- src/scope.rs | 14 +++++------ src/server.rs | 4 ++-- src/service.rs | 2 +- src/test.rs | 18 +++++++------- src/web.rs | 24 +++++++++---------- 36 files changed, 99 insertions(+), 99 deletions(-) diff --git a/actix-files/src/files.rs b/actix-files/src/files.rs index 6f8b28bbf..292e3fdf3 100644 --- a/actix-files/src/files.rs +++ b/actix-files/src/files.rs @@ -19,7 +19,7 @@ use crate::{ /// /// `Files` service must be registered with `App::service()` method. /// -/// ```rust +/// ``` /// use actix_web::App; /// use actix_files::Files; /// diff --git a/actix-files/src/lib.rs b/actix-files/src/lib.rs index 3c34c0403..018079b21 100644 --- a/actix-files/src/lib.rs +++ b/actix-files/src/lib.rs @@ -3,7 +3,7 @@ //! Provides a non-blocking service for serving static files from disk. //! //! # Example -//! ```rust +//! ``` //! use actix_web::App; //! use actix_files::Files; //! diff --git a/actix-files/src/named.rs b/actix-files/src/named.rs index a688b2e6c..2846646a2 100644 --- a/actix-files/src/named.rs +++ b/actix-files/src/named.rs @@ -60,7 +60,7 @@ impl NamedFile { /// /// # Examples /// - /// ```rust + /// ``` /// use actix_files::NamedFile; /// use std::io::{self, Write}; /// use std::env; @@ -137,7 +137,7 @@ impl NamedFile { /// /// # Examples /// - /// ```rust + /// ``` /// use actix_files::NamedFile; /// /// let file = NamedFile::open("foo.txt"); @@ -156,7 +156,7 @@ impl NamedFile { /// /// # Examples /// - /// ```rust + /// ``` /// # use std::io; /// use actix_files::NamedFile; /// diff --git a/actix-http-test/src/lib.rs b/actix-http-test/src/lib.rs index 138f14313..3749b78ca 100644 --- a/actix-http-test/src/lib.rs +++ b/actix-http-test/src/lib.rs @@ -26,7 +26,7 @@ use socket2::{Domain, Protocol, Socket, Type}; /// /// # Examples /// -/// ```rust +/// ``` /// use actix_http::HttpService; /// use actix_http_test::TestServer; /// use actix_web::{web, App, HttpResponse, Error}; diff --git a/actix-http/src/client/connector.rs b/actix-http/src/client/connector.rs index 1a5f32880..6996677d2 100644 --- a/actix-http/src/client/connector.rs +++ b/actix-http/src/client/connector.rs @@ -47,7 +47,7 @@ enum SslConnector { /// The `Connector` type uses a builder-like combinator pattern for service /// construction that finishes by calling the `.finish()` method. /// -/// ```rust,ignore +/// ```ignore /// use std::time::Duration; /// use actix_http::client::Connector; /// diff --git a/actix-http/src/error.rs b/actix-http/src/error.rs index d9b97d6a6..1354e998e 100644 --- a/actix-http/src/error.rs +++ b/actix-http/src/error.rs @@ -483,7 +483,7 @@ where /// response as opposite to *INTERNAL SERVER ERROR* which is defined by /// default. /// -/// ```rust +/// ``` /// # use std::io; /// # use actix_http::*; /// diff --git a/actix-http/src/header/common/cache_control.rs b/actix-http/src/header/common/cache_control.rs index 94ce9a750..b19823d22 100644 --- a/actix-http/src/header/common/cache_control.rs +++ b/actix-http/src/header/common/cache_control.rs @@ -36,7 +36,7 @@ use crate::header::{ /// builder.insert_header(CacheControl(vec![CacheDirective::MaxAge(86400u32)])); /// ``` /// -/// ```rust +/// ``` /// use actix_http::Response; /// use actix_http::http::header::{CacheControl, CacheDirective}; /// diff --git a/actix-http/src/response.rs b/actix-http/src/response.rs index d581fd293..94f12bcc3 100644 --- a/actix-http/src/response.rs +++ b/actix-http/src/response.rs @@ -357,7 +357,7 @@ impl ResponseBuilder { /// Insert a header, replacing any that were set with an equivalent field name. /// - /// ```rust + /// ``` /// # use actix_http::Response; /// use actix_http::http::header::ContentType; /// @@ -384,7 +384,7 @@ impl ResponseBuilder { /// Append a header, keeping any that were set with an equivalent field name. /// - /// ```rust + /// ``` /// # use actix_http::Response; /// use actix_http::http::header::ContentType; /// @@ -525,7 +525,7 @@ impl ResponseBuilder { /// Set a cookie /// - /// ```rust + /// ``` /// use actix_http::{http, Request, Response}; /// /// fn index(req: Request) -> Response { @@ -555,7 +555,7 @@ impl ResponseBuilder { /// Remove cookie /// - /// ```rust + /// ``` /// use actix_http::{http, Request, Response, HttpMessage}; /// /// fn index(req: Request) -> Response { diff --git a/actix-http/src/test.rs b/actix-http/src/test.rs index 870a656df..ad3dc74b2 100644 --- a/actix-http/src/test.rs +++ b/actix-http/src/test.rs @@ -26,7 +26,7 @@ use crate::{ /// Test `Request` builder /// -/// ```rust,ignore +/// ```ignore /// # use http::{header, StatusCode}; /// # use actix_web::*; /// use actix_web::test::TestRequest; diff --git a/actix-multipart/src/extractor.rs b/actix-multipart/src/extractor.rs index 6aaa415c4..bffbe8a1b 100644 --- a/actix-multipart/src/extractor.rs +++ b/actix-multipart/src/extractor.rs @@ -10,7 +10,7 @@ use crate::server::Multipart; /// /// ## Server example /// -/// ```rust +/// ``` /// use futures_util::stream::{Stream, StreamExt}; /// use actix_web::{web, HttpResponse, Error}; /// use actix_multipart as mp; diff --git a/actix-web-codegen/src/lib.rs b/actix-web-codegen/src/lib.rs index 48414d491..336345014 100644 --- a/actix-web-codegen/src/lib.rs +++ b/actix-web-codegen/src/lib.rs @@ -82,7 +82,7 @@ mod route; /// /// # Example /// -/// ```rust +/// ``` /// # use actix_web::HttpResponse; /// # use actix_web_codegen::route; /// #[route("/test", method="GET", method="HEAD")] @@ -127,7 +127,7 @@ code, e.g `my_guard` or `my_module::my_guard`. # Example -```rust +``` # use actix_web::HttpResponse; # use actix_web_codegen::"#, stringify!($method), "; #[", stringify!($method), r#"("/")] @@ -162,7 +162,7 @@ method_macro! { /// This macro can be applied with `#[actix_web::main]` when used in Actix Web applications. /// /// # Examples -/// ```rust +/// ``` /// #[actix_web_codegen::main] /// async fn main() { /// async { println!("Hello world"); }.await diff --git a/awc/src/lib.rs b/awc/src/lib.rs index 8addf4d8b..c7bb68a8f 100644 --- a/awc/src/lib.rs +++ b/awc/src/lib.rs @@ -131,7 +131,7 @@ pub use self::sender::SendClientRequest; /// /// ## Examples /// -/// ```rust +/// ``` /// use awc::Client; /// /// #[actix_rt::main] diff --git a/awc/src/request.rs b/awc/src/request.rs index 3e9babb7d..a847b09a3 100644 --- a/awc/src/request.rs +++ b/awc/src/request.rs @@ -36,7 +36,7 @@ cfg_if::cfg_if! { /// This type can be used to construct an instance of `ClientRequest` through a /// builder-like pattern. /// -/// ```rust +/// ``` /// #[actix_rt::main] /// async fn main() { /// let response = awc::Client::new() @@ -190,7 +190,7 @@ impl ClientRequest { /// Append a header, keeping any that were set with an equivalent field name. /// - /// ```rust + /// ``` /// # #[actix_rt::main] /// # async fn main() { /// # use awc::Client; @@ -271,7 +271,7 @@ impl ClientRequest { /// Set a cookie /// - /// ```rust + /// ``` /// #[actix_rt::main] /// async fn main() { /// let resp = awc::Client::new().get("https://www.rust-lang.org") diff --git a/benches/service.rs b/benches/service.rs index 0d3264857..30708477d 100644 --- a/benches/service.rs +++ b/benches/service.rs @@ -9,7 +9,7 @@ use actix_web::test::{init_service, ok_service, TestRequest}; /// Criterion Benchmark for async Service /// Should be used from within criterion group: -/// ```rust,ignore +/// ```ignore /// let mut criterion: ::criterion::Criterion<_> = /// ::criterion::Criterion::default().configure_from_args(); /// bench_async_service(&mut criterion, ok_service(), "async_service_direct"); diff --git a/src/app.rs b/src/app.rs index 7a26a3a89..f2c6bce8a 100644 --- a/src/app.rs +++ b/src/app.rs @@ -79,7 +79,7 @@ where /// uses `Arc` so data could be created outside of app factory and clones could /// be stored via `App::app_data()` method. /// - /// ```rust + /// ``` /// use std::cell::Cell; /// use actix_web::{web, App, HttpResponse, Responder}; /// @@ -152,7 +152,7 @@ where /// different module or even library. For example, /// some of the resource's configuration could be moved to different module. /// - /// ```rust + /// ``` /// use actix_web::{web, App, HttpResponse}; /// /// // this function could be located in different module @@ -185,7 +185,7 @@ where /// This method can be used multiple times with same path, in that case /// multiple resources with one route would be registered for same resource path. /// - /// ```rust + /// ``` /// use actix_web::{web, App, HttpResponse}; /// /// async fn index(data: web::Path<(String, String)>) -> &'static str { @@ -228,7 +228,7 @@ where /// /// It is possible to use services like `Resource`, `Route`. /// - /// ```rust + /// ``` /// use actix_web::{web, App, HttpResponse}; /// /// async fn index() -> &'static str { @@ -246,7 +246,7 @@ where /// /// It is also possible to use static files as default service. /// - /// ```rust + /// ``` /// use actix_web::{web, App, HttpResponse}; /// /// fn main() { @@ -283,7 +283,7 @@ where /// and are never considered for matching at request time. Calls to /// `HttpRequest::url_for()` will work as expected. /// - /// ```rust + /// ``` /// use actix_web::{web, App, HttpRequest, HttpResponse, Result}; /// /// async fn index(req: HttpRequest) -> Result { @@ -325,7 +325,7 @@ where /// the builder chain. Consequently, the *first* middleware registered /// in the builder chain is the *last* to execute during request processing. /// - /// ```rust + /// ``` /// use actix_service::Service; /// use actix_web::{middleware, web, App}; /// use actix_web::http::{header::CONTENT_TYPE, HeaderValue}; @@ -382,7 +382,7 @@ where /// /// Use middleware when you need to read or modify *every* request or response in some way. /// - /// ```rust + /// ``` /// use actix_service::Service; /// use actix_web::{web, App}; /// use actix_web::http::{header::CONTENT_TYPE, HeaderValue}; diff --git a/src/data.rs b/src/data.rs index 0336553ca..56ecdb8ae 100644 --- a/src/data.rs +++ b/src/data.rs @@ -37,7 +37,7 @@ pub(crate) type FnDataFactory = /// If route data is not set for a handler, using `Data` extractor would cause *Internal /// Server Error* response. /// -/// ```rust +/// ``` /// use std::sync::Mutex; /// use actix_web::{web, App, HttpResponse, Responder}; /// diff --git a/src/extract.rs b/src/extract.rs index 921d9fc36..8851481e3 100644 --- a/src/extract.rs +++ b/src/extract.rs @@ -51,7 +51,7 @@ pub trait FromRequest: Sized { /// /// ## Example /// -/// ```rust +/// ``` /// use actix_web::{web, dev, App, Error, HttpRequest, FromRequest}; /// use actix_web::error::ErrorBadRequest; /// use futures_util::future::{ok, err, Ready}; @@ -143,7 +143,7 @@ where /// /// ## Example /// -/// ```rust +/// ``` /// use actix_web::{web, dev, App, Result, Error, HttpRequest, FromRequest}; /// use actix_web::error::ErrorBadRequest; /// use futures_util::future::{ok, err, Ready}; diff --git a/src/guard.rs b/src/guard.rs index 5d0de58c2..3a1f5bb14 100644 --- a/src/guard.rs +++ b/src/guard.rs @@ -12,7 +12,7 @@ //! to store extra attributes on a request by using the `Extensions` container. //! Extensions containers are available via the `RequestHead::extensions()` method. //! -//! ```rust +//! ``` //! use actix_web::{web, http, dev, guard, App, HttpResponse}; //! //! fn main() { @@ -42,7 +42,7 @@ pub trait Guard { /// Create guard object for supplied function. /// -/// ```rust +/// ``` /// use actix_web::{guard, web, App, HttpResponse}; /// /// fn main() { @@ -85,7 +85,7 @@ where /// Return guard that matches if any of supplied guards. /// -/// ```rust +/// ``` /// use actix_web::{web, guard, App, HttpResponse}; /// /// fn main() { @@ -124,7 +124,7 @@ impl Guard for AnyGuard { /// Return guard that matches if all of the supplied guards. /// -/// ```rust +/// ``` /// use actix_web::{guard, web, App, HttpResponse}; /// /// fn main() { @@ -259,7 +259,7 @@ impl Guard for HeaderGuard { /// Return predicate that matches if request contains specified Host name. /// -/// ```rust +/// ``` /// use actix_web::{web, guard::Host, App, HttpResponse}; /// /// fn main() { diff --git a/src/lib.rs b/src/lib.rs index 136c462b8..7a6498546 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,7 +2,7 @@ //! //! ## Example //! -//! ```rust,no_run +//! ```no_run //! use actix_web::{get, web, App, HttpServer, Responder}; //! //! #[get("/{id}/{name}/index.html")] diff --git a/src/middleware/compat.rs b/src/middleware/compat.rs index 6f60264b1..71193a5c5 100644 --- a/src/middleware/compat.rs +++ b/src/middleware/compat.rs @@ -16,7 +16,7 @@ use crate::{error::Error, service::ServiceResponse}; /// [`Scope::wrap`](crate::Scope::wrap) and [`Condition`](super::Condition). /// /// # Examples -/// ```rust +/// ``` /// use actix_web::middleware::{Logger, Compat}; /// use actix_web::{App, web}; /// diff --git a/src/middleware/compress.rs b/src/middleware/compress.rs index fc1a85d30..a397bccd6 100644 --- a/src/middleware/compress.rs +++ b/src/middleware/compress.rs @@ -31,7 +31,7 @@ use crate::{ /// encoding to `ContentEncoding::Identity`. /// /// # Examples -/// ```rust +/// ``` /// use actix_web::{web, middleware, App, HttpResponse}; /// /// let app = App::new() diff --git a/src/middleware/condition.rs b/src/middleware/condition.rs index 63a90c853..dd599a0cb 100644 --- a/src/middleware/condition.rs +++ b/src/middleware/condition.rs @@ -12,7 +12,7 @@ use futures_util::future::{Either, FutureExt, LocalBoxFuture}; /// middleware for a workaround. /// /// # Examples -/// ```rust +/// ``` /// use actix_web::middleware::{Condition, NormalizePath}; /// use actix_web::App; /// diff --git a/src/middleware/default_headers.rs b/src/middleware/default_headers.rs index a36cc2f29..12d70ab2c 100644 --- a/src/middleware/default_headers.rs +++ b/src/middleware/default_headers.rs @@ -29,7 +29,7 @@ use crate::{ /// Headers with the same key that are already set in a response will *not* be overwritten. /// /// # Examples -/// ```rust +/// ``` /// use actix_web::{web, http, middleware, App, HttpResponse}; /// /// fn main() { diff --git a/src/middleware/err_handlers.rs b/src/middleware/err_handlers.rs index 06e88cefd..fddd87a99 100644 --- a/src/middleware/err_handlers.rs +++ b/src/middleware/err_handlers.rs @@ -34,7 +34,7 @@ type ErrorHandler = dyn Fn(ServiceResponse) -> Result) -> String { "jwt_uid".to_owned() } /// Logger::new("example %{JWT_ID}xi") diff --git a/src/middleware/normalize.rs b/src/middleware/normalize.rs index ea21a7215..2a97a047b 100644 --- a/src/middleware/normalize.rs +++ b/src/middleware/normalize.rs @@ -54,7 +54,7 @@ impl Default for TrailingSlash { /// `TrailingSlash::Always` behavior), as shown in the example tests below. /// /// # Examples -/// ```rust +/// ``` /// use actix_web::{web, middleware, App}; /// /// # actix_web::rt::System::new().block_on(async { diff --git a/src/request.rs b/src/request.rs index 514b7466e..15c97345c 100644 --- a/src/request.rs +++ b/src/request.rs @@ -159,7 +159,7 @@ impl HttpRequest { /// Generate url for named resource /// - /// ```rust + /// ``` /// # use actix_web::{web, App, HttpRequest, HttpResponse}; /// # /// fn index(req: HttpRequest) -> HttpResponse { @@ -231,7 +231,7 @@ impl HttpRequest { /// /// If `App::data` was used to store object, use `Data`: /// - /// ```rust,ignore + /// ```ignore /// let opt_t = req.app_data::>(); /// ``` pub fn app_data(&self) -> Option<&T> { @@ -302,7 +302,7 @@ impl Drop for HttpRequest { /// /// ## Example /// -/// ```rust +/// ``` /// use actix_web::{web, App, HttpRequest}; /// use serde_derive::Deserialize; /// diff --git a/src/request_data.rs b/src/request_data.rs index beee8ac12..fc711d011 100644 --- a/src/request_data.rs +++ b/src/request_data.rs @@ -23,7 +23,7 @@ use crate::{dev::Payload, FromRequest, HttpRequest}; /// provided to make this potential foot-gun more obvious. /// /// # Example -/// ```rust,no_run +/// ```no_run /// # use actix_web::{web, HttpResponse, HttpRequest, Responder}; /// /// #[derive(Debug, Clone, PartialEq)] diff --git a/src/resource.rs b/src/resource.rs index d35131cbb..8f356c76d 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -35,7 +35,7 @@ type HttpNewService = BoxServiceFactory<(), ServiceRequest, ServiceResponse, Err /// and check guards for specific route, if request matches all /// guards, route considered matched and route handler get called. /// -/// ```rust +/// ``` /// use actix_web::{web, App, HttpResponse}; /// /// fn main() { @@ -97,7 +97,7 @@ where /// Add match guard to a resource. /// - /// ```rust + /// ``` /// use actix_web::{web, guard, App, HttpResponse}; /// /// async fn index(data: web::Path<(String, String)>) -> &'static str { @@ -130,7 +130,7 @@ where /// Register a new route. /// - /// ```rust + /// ``` /// use actix_web::{web, guard, App, HttpResponse}; /// /// fn main() { @@ -147,7 +147,7 @@ where /// Multiple routes could be added to a resource. Resource object uses /// match guards for route selection. /// - /// ```rust + /// ``` /// use actix_web::{web, guard, App}; /// /// fn main() { @@ -172,7 +172,7 @@ where /// Provided data is available for all routes registered for the current resource. /// Resource data overrides data registered by `App::data()` method. /// - /// ```rust + /// ``` /// use actix_web::{web, App, FromRequest}; /// /// /// extract text data from request @@ -211,7 +211,7 @@ where /// Register a new route and add handler. This route matches all requests. /// - /// ```rust + /// ``` /// use actix_web::*; /// /// fn index(req: HttpRequest) -> HttpResponse { @@ -223,7 +223,7 @@ where /// /// This is shortcut for: /// - /// ```rust + /// ``` /// # extern crate actix_web; /// # use actix_web::*; /// # fn index(req: HttpRequest) -> HttpResponse { unimplemented!() } @@ -289,7 +289,7 @@ where /// Resource level middlewares are not allowed to change response /// type (i.e modify response's body). /// - /// ```rust + /// ``` /// use actix_service::Service; /// use actix_web::{web, App}; /// use actix_web::http::{header::CONTENT_TYPE, HeaderValue}; diff --git a/src/responder.rs b/src/responder.rs index 0b6f47c6b..b75c95083 100644 --- a/src/responder.rs +++ b/src/responder.rs @@ -18,7 +18,7 @@ pub trait Responder { /// Override a status code for a Responder. /// - /// ```rust + /// ``` /// use actix_web::{http::StatusCode, HttpRequest, Responder}; /// /// fn index(req: HttpRequest) -> impl Responder { @@ -36,7 +36,7 @@ pub trait Responder { /// /// Overrides other headers with the same name. /// - /// ```rust + /// ``` /// use actix_web::{web, HttpRequest, Responder}; /// use serde::Serialize; /// @@ -169,7 +169,7 @@ impl CustomResponder { /// Override a status code for the Responder's response. /// - /// ```rust + /// ``` /// use actix_web::{HttpRequest, Responder, http::StatusCode}; /// /// fn index(req: HttpRequest) -> impl Responder { @@ -185,7 +185,7 @@ impl CustomResponder { /// /// Overrides other headers with the same name. /// - /// ```rust + /// ``` /// use actix_web::{web, HttpRequest, Responder}; /// use serde::Serialize; /// diff --git a/src/route.rs b/src/route.rs index c157025b8..0a297b456 100644 --- a/src/route.rs +++ b/src/route.rs @@ -90,7 +90,7 @@ impl Service for RouteService { impl Route { /// Add method guard to the route. /// - /// ```rust + /// ``` /// # use actix_web::*; /// # fn main() { /// App::new().service(web::resource("/path").route( @@ -110,7 +110,7 @@ impl Route { /// Add guard to the route. /// - /// ```rust + /// ``` /// # use actix_web::*; /// # fn main() { /// App::new().service(web::resource("/path").route( @@ -128,7 +128,7 @@ impl Route { /// Set handler function, use request extractors for parameters. /// - /// ```rust + /// ``` /// use actix_web::{web, http, App}; /// use serde_derive::Deserialize; /// @@ -152,7 +152,7 @@ impl Route { /// /// It is possible to use multiple extractors for one handler function. /// - /// ```rust + /// ``` /// # use std::collections::HashMap; /// # use serde_derive::Deserialize; /// use actix_web::{web, App}; diff --git a/src/scope.rs b/src/scope.rs index e96f5b9e8..693d6860f 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -40,7 +40,7 @@ type HttpNewService = BoxServiceFactory<(), ServiceRequest, ServiceResponse, Err /// You can get variable path segments from `HttpRequest::match_info()`. /// `Path` extractor also is able to extract scope level variable segments. /// -/// ```rust +/// ``` /// use actix_web::{web, App, HttpResponse}; /// /// fn main() { @@ -97,7 +97,7 @@ where { /// Add match guard to a scope. /// - /// ```rust + /// ``` /// use actix_web::{web, guard, App, HttpRequest, HttpResponse}; /// /// async fn index(data: web::Path<(String, String)>) -> &'static str { @@ -123,7 +123,7 @@ where /// Set or override application data. Application data could be accessed /// by using `Data` extractor where `T` is data type. /// - /// ```rust + /// ``` /// use std::cell::Cell; /// use actix_web::{web, App, HttpResponse, Responder}; /// @@ -168,7 +168,7 @@ where /// different module or even library. For example, /// some of the resource's configuration could be moved to different module. /// - /// ```rust + /// ``` /// # extern crate actix_web; /// use actix_web::{web, middleware, App, HttpResponse}; /// @@ -215,7 +215,7 @@ where /// * *Scope* is a set of resources with common root path. /// * "StaticFiles" is a service for static files support /// - /// ```rust + /// ``` /// use actix_web::{web, App, HttpRequest}; /// /// struct AppState; @@ -247,7 +247,7 @@ where /// This method can be called multiple times, in that case /// multiple resources with one route would be registered for same resource path. /// - /// ```rust + /// ``` /// use actix_web::{web, App, HttpResponse}; /// /// async fn index(data: web::Path<(String, String)>) -> &'static str { @@ -341,7 +341,7 @@ where /// to Route or Application level middleware, in that Scope-level middleware /// can not modify ServiceResponse. /// - /// ```rust + /// ``` /// use actix_service::Service; /// use actix_web::{web, App}; /// use actix_web::http::{header::CONTENT_TYPE, HeaderValue}; diff --git a/src/server.rs b/src/server.rs index cfd8ae4b4..97c4fdaa2 100644 --- a/src/server.rs +++ b/src/server.rs @@ -35,7 +35,7 @@ struct Config { /// /// Create new HTTP server with application factory. /// -/// ```rust,no_run +/// ```no_run /// use actix_web::{web, App, HttpResponse, HttpServer}; /// /// #[actix_rt::main] @@ -588,7 +588,7 @@ where /// This methods panics if no socket address can be bound or an `Actix` system is not yet /// configured. /// - /// ```rust,no_run + /// ```no_run /// use std::io; /// use actix_web::{web, App, HttpResponse, HttpServer}; /// diff --git a/src/service.rs b/src/service.rs index 491810286..32f152f7d 100644 --- a/src/service.rs +++ b/src/service.rs @@ -462,7 +462,7 @@ impl WebService { /// Add match guard to a web service. /// - /// ```rust + /// ``` /// use actix_web::{web, guard, dev, App, Error, HttpResponse}; /// /// async fn index(req: dev::ServiceRequest) -> Result { diff --git a/src/test.rs b/src/test.rs index bc19296e2..7e8bf1069 100644 --- a/src/test.rs +++ b/src/test.rs @@ -54,7 +54,7 @@ pub fn default_service( /// This method accepts application builder instance, and constructs /// service. /// -/// ```rust +/// ``` /// use actix_service::Service; /// use actix_web::{test, web, App, HttpResponse, http::StatusCode}; /// @@ -101,7 +101,7 @@ where /// Calls service and waits for response future completion. /// -/// ```rust +/// ``` /// use actix_web::{test, web, App, HttpResponse, http::StatusCode}; /// /// #[actix_rt::test] @@ -131,7 +131,7 @@ where /// Helper function that returns a response body of a TestRequest /// -/// ```rust +/// ``` /// use actix_web::{test, web, App, HttpResponse, http::header}; /// use bytes::Bytes; /// @@ -174,7 +174,7 @@ where /// Helper function that returns a response body of a ServiceResponse. /// -/// ```rust +/// ``` /// use actix_web::{test, web, App, HttpResponse, http::header}; /// use bytes::Bytes; /// @@ -212,7 +212,7 @@ where /// Helper function that returns a deserialized response body of a ServiceResponse. /// -/// ```rust +/// ``` /// use actix_web::{App, test, web, HttpResponse, http::header}; /// use serde::{Serialize, Deserialize}; /// @@ -271,7 +271,7 @@ where /// Helper function that returns a deserialized response body of a TestRequest /// -/// ```rust +/// ``` /// use actix_web::{App, test, web, HttpResponse, http::header}; /// use serde::{Serialize, Deserialize}; /// @@ -324,7 +324,7 @@ where /// * `TestRequest::to_srv_response` creates `ServiceResponse` instance. /// * `TestRequest::to_http_request` creates `HttpRequest` instance, which is used for testing handlers. /// -/// ```rust +/// ``` /// use actix_web::{test, HttpRequest, HttpResponse, HttpMessage}; /// use actix_web::http::{header, StatusCode}; /// @@ -572,7 +572,7 @@ impl TestRequest { /// /// # Examples /// -/// ```rust +/// ``` /// use actix_web::{web, test, App, HttpResponse, Error}; /// /// async fn my_handler() -> Result { @@ -612,7 +612,7 @@ where /// /// # Examples /// -/// ```rust +/// ``` /// use actix_web::{web, test, App, HttpResponse, Error}; /// /// async fn my_handler() -> Result { diff --git a/src/web.rs b/src/web.rs index 1cef37109..8662848a4 100644 --- a/src/web.rs +++ b/src/web.rs @@ -42,7 +42,7 @@ pub use crate::types::*; /// `/users/{userid}/{friend}` and store `userid` and `friend` in /// the exposed `Params` object: /// -/// ```rust +/// ``` /// # extern crate actix_web; /// use actix_web::{web, App, HttpResponse}; /// @@ -61,7 +61,7 @@ pub fn resource(path: T) -> Resource { /// Scopes collect multiple paths under a common path prefix. /// Scope path can contain variable path segments as resources. /// -/// ```rust +/// ``` /// use actix_web::{web, App, HttpResponse}; /// /// let app = App::new().service( @@ -88,7 +88,7 @@ pub fn route() -> Route { /// Create *route* with `GET` method guard. /// -/// ```rust +/// ``` /// use actix_web::{web, App, HttpResponse}; /// /// let app = App::new().service( @@ -106,7 +106,7 @@ pub fn get() -> Route { /// Create *route* with `POST` method guard. /// -/// ```rust +/// ``` /// use actix_web::{web, App, HttpResponse}; /// /// let app = App::new().service( @@ -124,7 +124,7 @@ pub fn post() -> Route { /// Create *route* with `PUT` method guard. /// -/// ```rust +/// ``` /// use actix_web::{web, App, HttpResponse}; /// /// let app = App::new().service( @@ -142,7 +142,7 @@ pub fn put() -> Route { /// Create *route* with `PATCH` method guard. /// -/// ```rust +/// ``` /// use actix_web::{web, App, HttpResponse}; /// /// let app = App::new().service( @@ -160,7 +160,7 @@ pub fn patch() -> Route { /// Create *route* with `DELETE` method guard. /// -/// ```rust +/// ``` /// use actix_web::{web, App, HttpResponse}; /// /// let app = App::new().service( @@ -178,7 +178,7 @@ pub fn delete() -> Route { /// Create *route* with `HEAD` method guard. /// -/// ```rust +/// ``` /// use actix_web::{web, App, HttpResponse}; /// /// let app = App::new().service( @@ -196,7 +196,7 @@ pub fn head() -> Route { /// Create *route* with `TRACE` method guard. /// -/// ```rust +/// ``` /// use actix_web::{web, App, HttpResponse}; /// /// let app = App::new().service( @@ -214,7 +214,7 @@ pub fn trace() -> Route { /// Create *route* and add method guard. /// -/// ```rust +/// ``` /// use actix_web::{web, http, App, HttpResponse}; /// /// let app = App::new().service( @@ -232,7 +232,7 @@ pub fn method(method: Method) -> Route { /// Create a new route and add handler. /// -/// ```rust +/// ``` /// use actix_web::{web, App, HttpResponse, Responder}; /// /// async fn index() -> impl Responder { @@ -256,7 +256,7 @@ where /// Create raw service for a specific path. /// -/// ```rust +/// ``` /// use actix_web::{dev, web, guard, App, Error, HttpResponse}; /// /// async fn my_service(req: dev::ServiceRequest) -> Result { From 8c2ce2dedb4b3fd98654ed7451641408ab535b09 Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Thu, 25 Mar 2021 15:47:37 -0700 Subject: [PATCH 18/79] fix awc compress feature (#2116) --- actix-http/Cargo.toml | 1 - awc/CHANGES.md | 4 +++- awc/Cargo.toml | 1 - awc/src/request.rs | 19 +++++++------------ 4 files changed, 10 insertions(+), 15 deletions(-) diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index e1aebb76b..c24878404 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -55,7 +55,6 @@ base64 = "0.13" bitflags = "1.2" bytes = "1" bytestring = "1" -cfg-if = "1" cookie = { version = "0.14.1", features = ["percent-encode"], optional = true } derive_more = "0.99.5" encoding_rs = "0.8" diff --git a/awc/CHANGES.md b/awc/CHANGES.md index d2cb7c009..4f72e3f93 100644 --- a/awc/CHANGES.md +++ b/awc/CHANGES.md @@ -2,9 +2,11 @@ ## Unreleased - 2021-xx-xx ### Changed -* `ConnectorService` type is renamed to `BoxConnectorService` [#2081] +* `ConnectorService` type is renamed to `BoxConnectorService`. [#2081] +* Fix http/https encoding when enabling `compress` feature. [#2116] [#2081]: https://github.com/actix/actix-web/pull/2081 +[#2116]: https://github.com/actix/actix-web/pull/2116 ## 3.0.0-beta.3 - 2021-03-08 diff --git a/awc/Cargo.toml b/awc/Cargo.toml index ec2e03a96..b555ebb22 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -51,7 +51,6 @@ actix-rt = { version = "2.1", default-features = false } base64 = "0.13" bytes = "1" -cfg-if = "1.0" derive_more = "0.99.5" futures-core = { version = "0.3.7", default-features = false } itoa = "0.4" diff --git a/awc/src/request.rs b/awc/src/request.rs index a847b09a3..8b896a00d 100644 --- a/awc/src/request.rs +++ b/awc/src/request.rs @@ -21,15 +21,10 @@ use crate::frozen::FrozenClientRequest; use crate::sender::{PrepForSendingError, RequestSender, SendClientRequest}; use crate::ClientConfig; -cfg_if::cfg_if! { - if #[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))] { - const HTTPS_ENCODING: &str = "br, gzip, deflate"; - } else if #[cfg(feature = "compress")] { - const HTTPS_ENCODING: &str = "br"; - } else { - const HTTPS_ENCODING: &str = "identity"; - } -} +#[cfg(feature = "compress")] +const HTTPS_ENCODING: &str = "br, gzip, deflate"; +#[cfg(not(feature = "compress"))] +const HTTPS_ENCODING: &str = "br"; /// An HTTP Client request builder /// @@ -521,11 +516,11 @@ impl ClientRequest { .unwrap_or(true); if https { - slf = slf.insert_header_if_none((header::ACCEPT_ENCODING, HTTPS_ENCODING)) + slf = slf.insert_header_if_none((header::ACCEPT_ENCODING, HTTPS_ENCODING)); } else { - #[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))] + #[cfg(feature = "compress")] { - slf = slf.insert_header_if_none((header::ACCEPT_ENCODING, "gzip, deflate")) + slf = slf.insert_header_if_none((header::ACCEPT_ENCODING, "gzip, deflate")); } }; } From 2f7f1fa97aef546a369495a177c6dc893c26f4bf Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Thu, 25 Mar 2021 17:05:31 -0700 Subject: [PATCH 19/79] fix broken pipe for h2 when client is instantly dropped (#2113) --- actix-http/src/client/connection.rs | 56 +++++++++++++++++++++++++---- 1 file changed, 49 insertions(+), 7 deletions(-) diff --git a/actix-http/src/client/connection.rs b/actix-http/src/client/connection.rs index 78101397d..0e3e97f3f 100644 --- a/actix-http/src/client/connection.rs +++ b/actix-http/src/client/connection.rs @@ -174,7 +174,13 @@ impl H2ConnectionInner { /// Cancel spawned connection task on drop. impl Drop for H2ConnectionInner { fn drop(&mut self) { - self.handle.abort(); + if self + .sender + .send_request(http::Request::new(()), true) + .is_err() + { + self.handle.abort(); + } } } @@ -398,9 +404,18 @@ where #[cfg(test)] mod test { - use std::net; + use std::{ + future::Future, + net, + pin::Pin, + task::{Context, Poll}, + time::{Duration, Instant}, + }; - use actix_rt::net::TcpStream; + use actix_rt::{ + net::TcpStream, + time::{interval, Interval}, + }; use super::*; @@ -424,9 +439,36 @@ mod test { drop(conn); - match sender.ready().await { - Ok(_) => panic!("connection should be gone and can not be ready"), - Err(e) => assert!(e.is_io()), - }; + struct DropCheck { + sender: h2::client::SendRequest, + interval: Interval, + start_from: Instant, + } + + impl Future for DropCheck { + type Output = (); + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let this = self.get_mut(); + match futures_core::ready!(this.sender.poll_ready(cx)) { + Ok(()) => { + if this.start_from.elapsed() > Duration::from_secs(10) { + panic!("connection should be gone and can not be ready"); + } else { + let _ = this.interval.poll_tick(cx); + Poll::Pending + } + } + Err(_) => Poll::Ready(()), + } + } + } + + DropCheck { + sender, + interval: interval(Duration::from_millis(100)), + start_from: Instant::now(), + } + .await; } } From 6822bf2f580a504afcd36bcaa3a95d12e628bc3b Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Fri, 26 Mar 2021 09:15:04 -0700 Subject: [PATCH 20/79] Refactor actix_http::h1::service (#2117) --- actix-http/src/h1/service.rs | 205 ++++++++++++----------------------- 1 file changed, 70 insertions(+), 135 deletions(-) diff --git a/actix-http/src/h1/service.rs b/actix-http/src/h1/service.rs index 51303886b..4fe79736b 100644 --- a/actix-http/src/h1/service.rs +++ b/actix-http/src/h1/service.rs @@ -1,6 +1,4 @@ -use std::future::Future; use std::marker::PhantomData; -use std::pin::Pin; use std::rc::Rc; use std::task::{Context, Poll}; use std::{fmt, net}; @@ -8,7 +6,7 @@ use std::{fmt, net}; use actix_codec::{AsyncRead, AsyncWrite, Framed}; use actix_rt::net::TcpStream; use actix_service::{pipeline_factory, IntoServiceFactory, Service, ServiceFactory}; -use futures_core::ready; +use futures_core::{future::LocalBoxFuture, ready}; use futures_util::future::ready; use crate::body::MessageBody; @@ -60,14 +58,17 @@ where impl H1Service where S: ServiceFactory, + S::Future: 'static, S::Error: Into, S::InitError: fmt::Debug, S::Response: Into>, B: MessageBody, X: ServiceFactory, + X::Future: 'static, X::Error: Into, X::InitError: fmt::Debug, U: ServiceFactory<(Request, Framed), Config = (), Response = ()>, + U::Future: 'static, U::Error: fmt::Display + Into, U::InitError: fmt::Debug, { @@ -94,17 +95,21 @@ mod openssl { use super::*; use actix_service::ServiceFactoryExt; - use actix_tls::accept::openssl::{Acceptor, SslAcceptor, SslError, TlsStream}; - use actix_tls::accept::TlsError; + use actix_tls::accept::{ + openssl::{Acceptor, SslAcceptor, SslError, TlsStream}, + TlsError, + }; impl H1Service, S, B, X, U> where S: ServiceFactory, + S::Future: 'static, S::Error: Into, S::InitError: fmt::Debug, S::Response: Into>, B: MessageBody, X: ServiceFactory, + X::Future: 'static, X::Error: Into, X::InitError: fmt::Debug, U: ServiceFactory< @@ -112,6 +117,7 @@ mod openssl { Config = (), Response = (), >, + U::Future: 'static, U::Error: fmt::Display + Into, U::InitError: fmt::Debug, { @@ -143,19 +149,25 @@ mod openssl { #[cfg(feature = "rustls")] mod rustls { use super::*; + + use std::io; + use actix_service::ServiceFactoryExt; - use actix_tls::accept::rustls::{Acceptor, ServerConfig, TlsStream}; - use actix_tls::accept::TlsError; - use std::{fmt, io}; + use actix_tls::accept::{ + rustls::{Acceptor, ServerConfig, TlsStream}, + TlsError, + }; impl H1Service, S, B, X, U> where S: ServiceFactory, + S::Future: 'static, S::Error: Into, S::InitError: fmt::Debug, S::Response: Into>, B: MessageBody, X: ServiceFactory, + X::Future: 'static, X::Error: Into, X::InitError: fmt::Debug, U: ServiceFactory< @@ -163,6 +175,7 @@ mod rustls { Config = (), Response = (), >, + U::Future: 'static, U::Error: fmt::Display + Into, U::InitError: fmt::Debug, { @@ -241,16 +254,19 @@ where impl ServiceFactory<(T, Option)> for H1Service where - T: AsyncRead + AsyncWrite + Unpin, + T: AsyncRead + AsyncWrite + Unpin + 'static, S: ServiceFactory, + S::Future: 'static, S::Error: Into, S::Response: Into>, S::InitError: fmt::Debug, B: MessageBody, X: ServiceFactory, + X::Future: 'static, X::Error: Into, X::InitError: fmt::Debug, U: ServiceFactory<(Request, Framed), Config = (), Response = ()>, + U::Future: 'static, U::Error: fmt::Display + Into, U::InitError: fmt::Debug, { @@ -259,103 +275,42 @@ where type Config = (); type Service = H1ServiceHandler; type InitError = (); - type Future = H1ServiceResponse; + type Future = LocalBoxFuture<'static, Result>; fn new_service(&self, _: ()) -> Self::Future { - H1ServiceResponse { - fut: self.srv.new_service(()), - fut_ex: Some(self.expect.new_service(())), - fut_upg: self.upgrade.as_ref().map(|f| f.new_service(())), - expect: None, - upgrade: None, - on_connect_ext: self.on_connect_ext.clone(), - cfg: Some(self.cfg.clone()), - _phantom: PhantomData, - } - } -} + let service = self.srv.new_service(()); + let expect = self.expect.new_service(()); + let upgrade = self.upgrade.as_ref().map(|s| s.new_service(())); + let on_connect_ext = self.on_connect_ext.clone(); + let cfg = self.cfg.clone(); -#[doc(hidden)] -#[pin_project::pin_project] -pub struct H1ServiceResponse -where - S: ServiceFactory, - S::Error: Into, - S::InitError: fmt::Debug, - X: ServiceFactory, - X::Error: Into, - X::InitError: fmt::Debug, - U: ServiceFactory<(Request, Framed), Response = ()>, - U::Error: fmt::Display, - U::InitError: fmt::Debug, -{ - #[pin] - fut: S::Future, - #[pin] - fut_ex: Option, - #[pin] - fut_upg: Option, - expect: Option, - upgrade: Option, - on_connect_ext: Option>>, - cfg: Option, - _phantom: PhantomData, -} + Box::pin(async move { + let expect = expect + .await + .map_err(|e| log::error!("Init http expect service error: {:?}", e))?; -impl Future for H1ServiceResponse -where - T: AsyncRead + AsyncWrite + Unpin, - S: ServiceFactory, - S::Error: Into, - S::Response: Into>, - S::InitError: fmt::Debug, - B: MessageBody, - X: ServiceFactory, - X::Error: Into, - X::InitError: fmt::Debug, - U: ServiceFactory<(Request, Framed), Response = ()>, - U::Error: fmt::Display, - U::InitError: fmt::Debug, -{ - type Output = Result, ()>; + let upgrade = match upgrade { + Some(upgrade) => { + let upgrade = upgrade.await.map_err(|e| { + log::error!("Init http upgrade service error: {:?}", e) + })?; + Some(upgrade) + } + None => None, + }; - fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - let mut this = self.as_mut().project(); + let service = service + .await + .map_err(|e| log::error!("Init http service error: {:?}", e))?; - if let Some(fut) = this.fut_ex.as_pin_mut() { - let expect = ready!(fut - .poll(cx) - .map_err(|e| log::error!("Init http service error: {:?}", e)))?; - this = self.as_mut().project(); - *this.expect = Some(expect); - this.fut_ex.set(None); - } - - if let Some(fut) = this.fut_upg.as_pin_mut() { - let upgrade = ready!(fut - .poll(cx) - .map_err(|e| log::error!("Init http service error: {:?}", e)))?; - this = self.as_mut().project(); - *this.upgrade = Some(upgrade); - this.fut_upg.set(None); - } - - let result = ready!(this - .fut - .poll(cx) - .map_err(|e| log::error!("Init http service error: {:?}", e))); - - Poll::Ready(result.map(|service| { - let this = self.as_mut().project(); - - H1ServiceHandler::new( - this.cfg.take().unwrap(), + Ok(H1ServiceHandler::new( + cfg, service, - this.expect.take().unwrap(), - this.upgrade.take(), - this.on_connect_ext.clone(), - ) - })) + expect, + upgrade, + on_connect_ext, + )) + }) } } @@ -417,47 +372,27 @@ where type Future = Dispatcher; fn poll_ready(&self, cx: &mut Context<'_>) -> Poll> { - let ready = self - .flow - .expect - .poll_ready(cx) - .map_err(|e| { - let e = e.into(); - log::error!("Http service readiness error: {:?}", e); - DispatchError::Service(e) - })? - .is_ready(); + ready!(self.flow.expect.poll_ready(cx)).map_err(|e| { + let e = e.into(); + log::error!("Http expect service readiness error: {:?}", e); + DispatchError::Service(e) + })?; - let ready = self - .flow - .service - .poll_ready(cx) - .map_err(|e| { + if let Some(ref upg) = self.flow.upgrade { + ready!(upg.poll_ready(cx)).map_err(|e| { let e = e.into(); - log::error!("Http service readiness error: {:?}", e); + log::error!("Http upgrade service readiness error: {:?}", e); DispatchError::Service(e) - })? - .is_ready() - && ready; - - let ready = if let Some(ref upg) = self.flow.upgrade { - upg.poll_ready(cx) - .map_err(|e| { - let e = e.into(); - log::error!("Http service readiness error: {:?}", e); - DispatchError::Service(e) - })? - .is_ready() - && ready - } else { - ready + })?; }; - if ready { - Poll::Ready(Ok(())) - } else { - Poll::Pending - } + ready!(self.flow.service.poll_ready(cx)).map_err(|e| { + let e = e.into(); + log::error!("Http service readiness error: {:?}", e); + DispatchError::Service(e) + })?; + + Poll::Ready(Ok(())) } fn call(&self, (io, addr): (T, Option)) -> Self::Future { From 60f9cfbb2a0fc77ba6dc918f199ee3a602c64549 Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Fri, 26 Mar 2021 11:24:51 -0700 Subject: [PATCH 21/79] Refactor actix_http::h2::service module. Reduce loc. (#2118) --- actix-http/src/h2/service.rs | 88 ++++++++++++------------------------ 1 file changed, 30 insertions(+), 58 deletions(-) diff --git a/actix-http/src/h2/service.rs b/actix-http/src/h2/service.rs index c64139564..db0b580b3 100644 --- a/actix-http/src/h2/service.rs +++ b/actix-http/src/h2/service.rs @@ -11,8 +11,8 @@ use actix_service::{ ServiceFactory, }; use bytes::Bytes; -use futures_core::ready; -use futures_util::future::ok; +use futures_core::{future::LocalBoxFuture, ready}; +use futures_util::future::ready; use h2::server::{handshake, Handshake}; use log::error; @@ -65,6 +65,7 @@ where impl H2Service where S: ServiceFactory, + S::Future: 'static, S::Error: Into + 'static, S::Response: Into> + 'static, >::Future: 'static, @@ -80,11 +81,11 @@ where Error = DispatchError, InitError = S::InitError, > { - pipeline_factory(fn_factory(|| async { - Ok::<_, S::InitError>(fn_service(|io: TcpStream| { + pipeline_factory(fn_factory(|| { + ready(Ok::<_, S::InitError>(fn_service(|io: TcpStream| { let peer_addr = io.peer_addr().ok(); - ok::<_, DispatchError>((io, peer_addr)) - })) + ready(Ok::<_, DispatchError>((io, peer_addr))) + }))) })) .and_then(self) } @@ -101,6 +102,7 @@ mod openssl { impl H2Service, S, B> where S: ServiceFactory, + S::Future: 'static, S::Error: Into + 'static, S::Response: Into> + 'static, >::Future: 'static, @@ -123,10 +125,12 @@ mod openssl { .map_init_err(|_| panic!()), ) .and_then(fn_factory(|| { - ok::<_, S::InitError>(fn_service(|io: TlsStream| { - let peer_addr = io.get_ref().peer_addr().ok(); - ok((io, peer_addr)) - })) + ready(Ok::<_, S::InitError>(fn_service( + |io: TlsStream| { + let peer_addr = io.get_ref().peer_addr().ok(); + ready(Ok((io, peer_addr))) + }, + ))) })) .and_then(self.map_err(TlsError::Service)) } @@ -144,6 +148,7 @@ mod rustls { impl H2Service, S, B> where S: ServiceFactory, + S::Future: 'static, S::Error: Into + 'static, S::Response: Into> + 'static, >::Future: 'static, @@ -169,10 +174,12 @@ mod rustls { .map_init_err(|_| panic!()), ) .and_then(fn_factory(|| { - ok::<_, S::InitError>(fn_service(|io: TlsStream| { - let peer_addr = io.get_ref().0.peer_addr().ok(); - ok((io, peer_addr)) - })) + ready(Ok::<_, S::InitError>(fn_service( + |io: TlsStream| { + let peer_addr = io.get_ref().0.peer_addr().ok(); + ready(Ok((io, peer_addr))) + }, + ))) })) .and_then(self.map_err(TlsError::Service)) } @@ -181,8 +188,9 @@ mod rustls { impl ServiceFactory<(T, Option)> for H2Service where - T: AsyncRead + AsyncWrite + Unpin, + T: AsyncRead + AsyncWrite + Unpin + 'static, S: ServiceFactory, + S::Future: 'static, S::Error: Into + 'static, S::Response: Into> + 'static, >::Future: 'static, @@ -193,52 +201,16 @@ where type Config = (); type Service = H2ServiceHandler; type InitError = S::InitError; - type Future = H2ServiceResponse; + type Future = LocalBoxFuture<'static, Result>; fn new_service(&self, _: ()) -> Self::Future { - H2ServiceResponse { - fut: self.srv.new_service(()), - cfg: Some(self.cfg.clone()), - on_connect_ext: self.on_connect_ext.clone(), - _phantom: PhantomData, - } - } -} + let service = self.srv.new_service(()); + let cfg = self.cfg.clone(); + let on_connect_ext = self.on_connect_ext.clone(); -#[doc(hidden)] -#[pin_project::pin_project] -pub struct H2ServiceResponse -where - S: ServiceFactory, -{ - #[pin] - fut: S::Future, - cfg: Option, - on_connect_ext: Option>>, - _phantom: PhantomData, -} - -impl Future for H2ServiceResponse -where - T: AsyncRead + AsyncWrite + Unpin, - S: ServiceFactory, - S::Error: Into + 'static, - S::Response: Into> + 'static, - >::Future: 'static, - B: MessageBody + 'static, -{ - type Output = Result, S::InitError>; - - fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - let this = self.as_mut().project(); - - this.fut.poll(cx).map_ok(|service| { - let this = self.as_mut().project(); - H2ServiceHandler::new( - this.cfg.take().unwrap(), - this.on_connect_ext.clone(), - service, - ) + Box::pin(async move { + let service = service.await?; + Ok(H2ServiceHandler::new(cfg, on_connect_ext, service)) }) } } From f954a30c341fba28ecfca598b0b29f4af3cb4386 Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Mon, 29 Mar 2021 11:18:05 +0200 Subject: [PATCH 22/79] Fix typo in CHANGES.md (#2124) --- actix-http/CHANGES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index c4e0aec89..2c71031ab 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -6,7 +6,7 @@ * `client::ConnectorService` as `client::Connector::finish` method's return type [#2081] * `client::ConnectionIo` trait alias [#2081] -### Chaged +### Changed * `client::Connector` type now only have one generic type for `actix_service::Service`. [#2063] [#2063]: https://github.com/actix/actix-web/pull/2063 From e8ce73b49674895624801265c2a8525bfbf6bc62 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Mon, 29 Mar 2021 11:52:59 +0100 Subject: [PATCH 23/79] update dep docs --- actix-http/tests/test_ws.rs | 2 +- docs/graphs/net-only.dot | 24 +++++++++++------------- 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/actix-http/tests/test_ws.rs b/actix-http/tests/test_ws.rs index 3b90b4e54..51238215a 100644 --- a/actix-http/tests/test_ws.rs +++ b/actix-http/tests/test_ws.rs @@ -3,6 +3,7 @@ use std::future::Future; use std::marker::PhantomData; use std::pin::Pin; use std::sync::{Arc, Mutex}; +use std::task::{Context, Poll}; use actix_codec::{AsyncRead, AsyncWrite, Framed}; use actix_http::{body, h1, ws, Error, HttpService, Request, Response}; @@ -11,7 +12,6 @@ use actix_service::{fn_factory, Service}; use actix_utils::dispatcher::Dispatcher; use bytes::Bytes; use futures_util::future; -use futures_util::task::{Context, Poll}; use futures_util::{SinkExt as _, StreamExt as _}; struct WsService(Arc, Cell)>>); diff --git a/docs/graphs/net-only.dot b/docs/graphs/net-only.dot index 9488f3fe7..babd612a6 100644 --- a/docs/graphs/net-only.dot +++ b/docs/graphs/net-only.dot @@ -1,21 +1,19 @@ digraph { subgraph cluster_net { label="actix/actix-net"; - "actix-codec" - "actix-macros" - "actix-rt" - "actix-server" - "actix-service" - "actix-threadpool" - "actix-tls" - "actix-tracing" - "actix-utils" - "actix-router" + "actix-codec" "actix-macros" "actix-rt" "actix-server" "actix-service" + "actix-tls" "actix-tracing" "actix-utils" "actix-router" + "local-channel" "local-waker" } - "actix-utils" -> { "actix-service" "actix-rt" "actix-codec" } + "actix-codec" -> { "actix-rt" "actix-service" "local-channel" "tokio" } + "actix-utils" -> { "actix-rt" "actix-service" "local-waker" } "actix-tracing" -> { "actix-service" } "actix-tls" -> { "actix-service" "actix-codec" "actix-utils" "actix-rt" } - "actix-server" -> { "actix-service" "actix-rt" "actix-codec" "actix-utils" } - "actix-rt" -> { "actix-macros" "actix-threadpool" } + "actix-server" -> { "actix-service" "actix-rt" "actix-codec" "actix-utils" "tokio" } + "actix-rt" -> { "actix-macros" "tokio" } + + "local-channel" -> { "local-waker" } + + "tokio" [fontcolor = darkgreen] } From 980ecc5f07bc70d1e179f8189c4c30e2ecfdc632 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Mon, 29 Mar 2021 13:01:37 +0100 Subject: [PATCH 24/79] fix openssl windows ci --- .github/workflows/ci.yml | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index dc0ff0c19..3aac6efa8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,6 +23,9 @@ jobs: name: ${{ matrix.target.name }} / ${{ matrix.version }} runs-on: ${{ matrix.target.os }} + env: + VCPKGRS_DYNAMIC: 1 + steps: - uses: actions/checkout@v2 @@ -65,7 +68,13 @@ jobs: uses: actions-rs/cargo@v1 with: command: hack - args: --clean-per-run check --workspace --no-default-features --tests + args: check --workspace --no-default-features + + - name: check minimal + tests + uses: actions-rs/cargo@v1 + with: + command: hack + args: check --workspace --no-default-features --tests --examples - name: check full uses: actions-rs/cargo@v1 From 222acfd070aeccd08d856e5d45090eb38b67ec4a Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Mon, 29 Mar 2021 05:45:48 -0700 Subject: [PATCH 25/79] Fix build for next actix-tls-beta release (#2122) --- Cargo.toml | 4 ++-- actix-files/Cargo.toml | 2 +- actix-http-test/Cargo.toml | 4 ++-- actix-http/Cargo.toml | 6 +++--- actix-http/src/client/connector.rs | 23 ++++++++++++++--------- actix-multipart/Cargo.toml | 2 +- actix-web-actors/Cargo.toml | 2 +- actix-web-codegen/Cargo.toml | 2 +- awc/Cargo.toml | 2 +- awc/src/builder.rs | 7 +++---- 10 files changed, 29 insertions(+), 25 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index f3a6271ee..7dd7635cd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -80,11 +80,11 @@ required-features = ["rustls"] actix-codec = "0.4.0-beta.1" actix-macros = "0.2.0" actix-router = "0.2.7" -actix-rt = "2.1" +actix-rt = "2.2" actix-server = "2.0.0-beta.3" actix-service = "2.0.0-beta.4" actix-utils = "3.0.0-beta.2" -actix-tls = { version = "3.0.0-beta.4", default-features = false, optional = true } +actix-tls = { version = "3.0.0-beta.5", default-features = false, optional = true } actix-web-codegen = "0.5.0-beta.2" actix-http = "3.0.0-beta.4" diff --git a/actix-files/Cargo.toml b/actix-files/Cargo.toml index 49cd6966c..472bd0362 100644 --- a/actix-files/Cargo.toml +++ b/actix-files/Cargo.toml @@ -33,5 +33,5 @@ mime_guess = "2.0.1" percent-encoding = "2.1" [dev-dependencies] -actix-rt = "2.1" +actix-rt = "2.2" actix-web = "4.0.0-beta.4" diff --git a/actix-http-test/Cargo.toml b/actix-http-test/Cargo.toml index a7efc5310..0e7d57fc3 100644 --- a/actix-http-test/Cargo.toml +++ b/actix-http-test/Cargo.toml @@ -31,9 +31,9 @@ openssl = ["tls-openssl", "awc/openssl"] [dependencies] actix-service = "2.0.0-beta.4" actix-codec = "0.4.0-beta.1" -actix-tls = "3.0.0-beta.4" +actix-tls = "3.0.0-beta.5" actix-utils = "3.0.0-beta.2" -actix-rt = "2.1" +actix-rt = "2.2" actix-server = "2.0.0-beta.3" awc = { version = "3.0.0-beta.3", default-features = false } diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index c24878404..679e8c992 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -47,8 +47,8 @@ trust-dns = ["trust-dns-resolver"] actix-service = "2.0.0-beta.4" actix-codec = "0.4.0-beta.1" actix-utils = "3.0.0-beta.2" -actix-rt = "2.1" -actix-tls = "3.0.0-beta.4" +actix-rt = "2.2" +actix-tls = "3.0.0-beta.5" ahash = "0.7" base64 = "0.13" @@ -89,7 +89,7 @@ trust-dns-resolver = { version = "0.20.0", optional = true } [dev-dependencies] actix-server = "2.0.0-beta.3" actix-http-test = { version = "3.0.0-beta.3", features = ["openssl"] } -actix-tls = { version = "3.0.0-beta.4", features = ["openssl"] } +actix-tls = { version = "3.0.0-beta.5", features = ["openssl"] } criterion = "0.3" env_logger = "0.8" rcgen = "0.8" diff --git a/actix-http/src/client/connector.rs b/actix-http/src/client/connector.rs index 6996677d2..508fe748b 100644 --- a/actix-http/src/client/connector.rs +++ b/actix-http/src/client/connector.rs @@ -9,7 +9,7 @@ use std::{ }; use actix_rt::{ - net::TcpStream, + net::{ActixStream, TcpStream}, time::{sleep, Sleep}, }; use actix_service::Service; @@ -119,7 +119,7 @@ impl Connector { /// Use custom connector. pub fn connector(self, connector: S1) -> Connector where - Io1: ConnectionIo + fmt::Debug, + Io1: ActixStream + fmt::Debug + 'static, S1: Service< TcpConnect, Response = TcpConnection, @@ -136,7 +136,14 @@ impl Connector { impl Connector where - Io: ConnectionIo + fmt::Debug, + // Note: + // Input Io type is bound to ActixStream trait but internally in client module they + // are bound to ConnectionIo trait alias. And latter is the trait exposed to public + // in the form of Box type. + // + // This remap is to hide ActixStream's trait methods. They are not meant to be called + // from user code. + Io: ActixStream + fmt::Debug + 'static, S: Service< TcpConnect, Response = TcpConnection, @@ -407,16 +414,14 @@ struct TlsConnectorService { timeout: Duration, } -impl Service for TlsConnectorService +impl Service for TlsConnectorService where S: Service, Error = ConnectError> + Clone + 'static, - St: Service, Response = Res, Error = std::io::Error> - + Clone - + 'static, + St: Service, Error = std::io::Error> + Clone + 'static, Io: ConnectionIo, - Res: IntoConnectionIo, + St::Response: IntoConnectionIo, { type Response = (Box, Protocol); type Error = ConnectError; @@ -471,10 +476,10 @@ where Error = std::io::Error, Future = Fut2, >, + S::Response: IntoConnectionIo, Fut1: Future, ConnectError>>, Fut2: Future>, Io: ConnectionIo, - Res: IntoConnectionIo, { type Output = Result<(Box, Protocol), ConnectError>; diff --git a/actix-multipart/Cargo.toml b/actix-multipart/Cargo.toml index 9a3ea7bb5..607e90849 100644 --- a/actix-multipart/Cargo.toml +++ b/actix-multipart/Cargo.toml @@ -28,7 +28,7 @@ mime = "0.3" twoway = "0.2" [dev-dependencies] -actix-rt = "2.1" +actix-rt = "2.2" actix-http = "3.0.0-beta.4" tokio = { version = "1", features = ["sync"] } tokio-stream = "0.1" diff --git a/actix-web-actors/Cargo.toml b/actix-web-actors/Cargo.toml index 77663540c..1f734582d 100644 --- a/actix-web-actors/Cargo.toml +++ b/actix-web-actors/Cargo.toml @@ -28,6 +28,6 @@ pin-project = "1.0.0" tokio = { version = "1", features = ["sync"] } [dev-dependencies] -actix-rt = "2.1" +actix-rt = "2.2" env_logger = "0.8" futures-util = { version = "0.3.7", default-features = false } diff --git a/actix-web-codegen/Cargo.toml b/actix-web-codegen/Cargo.toml index d8a189565..fdfb9f6ba 100644 --- a/actix-web-codegen/Cargo.toml +++ b/actix-web-codegen/Cargo.toml @@ -19,7 +19,7 @@ syn = { version = "1", features = ["full", "parsing"] } proc-macro2 = "1" [dev-dependencies] -actix-rt = "2.1" +actix-rt = "2.2" actix-web = "4.0.0-beta.4" futures-util = { version = "0.3.7", default-features = false } trybuild = "1" diff --git a/awc/Cargo.toml b/awc/Cargo.toml index b555ebb22..cc6841606 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -71,7 +71,7 @@ actix-http = { version = "3.0.0-beta.4", features = ["openssl"] } actix-http-test = { version = "3.0.0-beta.3", features = ["openssl"] } actix-utils = "3.0.0-beta.1" actix-server = "2.0.0-beta.3" -actix-tls = { version = "3.0.0-beta.4", features = ["openssl", "rustls"] } +actix-tls = { version = "3.0.0-beta.5", features = ["openssl", "rustls"] } brotli2 = "0.3.2" env_logger = "0.8" diff --git a/awc/src/builder.rs b/awc/src/builder.rs index 925d9ae2a..c594b4836 100644 --- a/awc/src/builder.rs +++ b/awc/src/builder.rs @@ -4,12 +4,11 @@ use std::net::IpAddr; use std::rc::Rc; use std::time::Duration; -use actix_codec::{AsyncRead, AsyncWrite}; use actix_http::{ client::{Connector, ConnectorService, TcpConnect, TcpConnectError, TcpConnection}, http::{self, header, Error as HttpError, HeaderMap, HeaderName, Uri}, }; -use actix_rt::net::TcpStream; +use actix_rt::net::{ActixStream, TcpStream}; use actix_service::{boxed, Service}; use crate::connect::DefaultConnector; @@ -64,7 +63,7 @@ where S: Service, Response = TcpConnection, Error = TcpConnectError> + Clone + 'static, - Io: AsyncRead + AsyncWrite + Unpin + fmt::Debug + 'static, + Io: ActixStream + fmt::Debug + 'static, { /// Use custom connector service. pub fn connector(self, connector: Connector) -> ClientBuilder @@ -75,7 +74,7 @@ where Error = TcpConnectError, > + Clone + 'static, - Io1: AsyncRead + AsyncWrite + Unpin + fmt::Debug + 'static, + Io1: ActixStream + fmt::Debug + 'static, { ClientBuilder { middleware: self.middleware, From 1281a748d03aa63fb7662c638d725d91b29a44c7 Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Mon, 29 Mar 2021 19:06:16 -0700 Subject: [PATCH 26/79] merge H1ServiceHandler requests into HttpServiceHandler (#2126) --- actix-http/src/h1/service.rs | 68 +-------- actix-http/src/service.rs | 260 ++++++++++++----------------------- 2 files changed, 95 insertions(+), 233 deletions(-) diff --git a/actix-http/src/h1/service.rs b/actix-http/src/h1/service.rs index 4fe79736b..f915bfa47 100644 --- a/actix-http/src/h1/service.rs +++ b/actix-http/src/h1/service.rs @@ -6,7 +6,7 @@ use std::{fmt, net}; use actix_codec::{AsyncRead, AsyncWrite, Framed}; use actix_rt::net::TcpStream; use actix_service::{pipeline_factory, IntoServiceFactory, Service, ServiceFactory}; -use futures_core::{future::LocalBoxFuture, ready}; +use futures_core::future::LocalBoxFuture; use futures_util::future::ready; use crate::body::MessageBody; @@ -14,7 +14,7 @@ use crate::config::ServiceConfig; use crate::error::{DispatchError, Error}; use crate::request::Request; use crate::response::Response; -use crate::service::HttpFlow; +use crate::service::HttpServiceHandler; use crate::{ConnectCallback, OnConnectData}; use super::codec::Codec; @@ -315,47 +315,10 @@ where } /// `Service` implementation for HTTP/1 transport -pub struct H1ServiceHandler -where - S: Service, - X: Service, - U: Service<(Request, Framed)>, -{ - flow: Rc>, - on_connect_ext: Option>>, - cfg: ServiceConfig, - _phantom: PhantomData, -} - -impl H1ServiceHandler -where - S: Service, - S::Error: Into, - S::Response: Into>, - B: MessageBody, - X: Service, - X::Error: Into, - U: Service<(Request, Framed), Response = ()>, - U::Error: fmt::Display, -{ - fn new( - cfg: ServiceConfig, - service: S, - expect: X, - upgrade: Option, - on_connect_ext: Option>>, - ) -> H1ServiceHandler { - H1ServiceHandler { - flow: HttpFlow::new(service, expect, upgrade), - cfg, - on_connect_ext, - _phantom: PhantomData, - } - } -} +pub type H1ServiceHandler = HttpServiceHandler; impl Service<(T, Option)> - for H1ServiceHandler + for HttpServiceHandler where T: AsyncRead + AsyncWrite + Unpin, S: Service, @@ -372,27 +335,10 @@ where type Future = Dispatcher; fn poll_ready(&self, cx: &mut Context<'_>) -> Poll> { - ready!(self.flow.expect.poll_ready(cx)).map_err(|e| { - let e = e.into(); - log::error!("Http expect service readiness error: {:?}", e); + self._poll_ready(cx).map_err(|e| { + log::error!("HTTP/1 service readiness error: {:?}", e); DispatchError::Service(e) - })?; - - if let Some(ref upg) = self.flow.upgrade { - ready!(upg.poll_ready(cx)).map_err(|e| { - let e = e.into(); - log::error!("Http upgrade service readiness error: {:?}", e); - DispatchError::Service(e) - })?; - }; - - ready!(self.flow.service.poll_ready(cx)).map_err(|e| { - let e = e.into(); - log::error!("Http service readiness error: {:?}", e); - DispatchError::Service(e) - })?; - - Poll::Ready(Ok(())) + }) } fn call(&self, (io, addr): (T, Option)) -> Self::Future { diff --git a/actix-http/src/service.rs b/actix-http/src/service.rs index 1a06cec3d..fd97fb5ef 100644 --- a/actix-http/src/service.rs +++ b/actix-http/src/service.rs @@ -12,7 +12,7 @@ use actix_codec::{AsyncRead, AsyncWrite, Framed}; use actix_rt::net::TcpStream; use actix_service::{pipeline_factory, IntoServiceFactory, Service, ServiceFactory}; use bytes::Bytes; -use futures_core::ready; +use futures_core::{future::LocalBoxFuture, ready}; use h2::server::{handshake, Handshake}; use pin_project::pin_project; @@ -107,7 +107,6 @@ where X1: ServiceFactory, X1::Error: Into, X1::InitError: fmt::Debug, - >::Future: 'static, { HttpService { expect, @@ -128,7 +127,6 @@ where U1: ServiceFactory<(Request, Framed), Config = (), Response = ()>, U1::Error: fmt::Display, U1::InitError: fmt::Debug, - )>>::Future: 'static, { HttpService { upgrade, @@ -150,23 +148,24 @@ where impl HttpService where S: ServiceFactory, + S::Future: 'static, S::Error: Into + 'static, S::InitError: fmt::Debug, S::Response: Into> + 'static, >::Future: 'static, B: MessageBody + 'static, X: ServiceFactory, + X::Future: 'static, X::Error: Into, X::InitError: fmt::Debug, - >::Future: 'static, U: ServiceFactory< (Request, Framed), Config = (), Response = (), >, + U::Future: 'static, U::Error: fmt::Display + Into, U::InitError: fmt::Debug, - )>>::Future: 'static, { /// Create simple tcp stream service pub fn tcp( @@ -196,23 +195,24 @@ mod openssl { impl HttpService, S, B, X, U> where S: ServiceFactory, + S::Future: 'static, S::Error: Into + 'static, S::InitError: fmt::Debug, S::Response: Into> + 'static, >::Future: 'static, B: MessageBody + 'static, X: ServiceFactory, + X::Future: 'static, X::Error: Into, X::InitError: fmt::Debug, - >::Future: 'static, U: ServiceFactory< (Request, Framed, h1::Codec>), Config = (), Response = (), >, + U::Future: 'static, U::Error: fmt::Display + Into, U::InitError: fmt::Debug, - , h1::Codec>)>>::Future: 'static, { /// Create openssl based service pub fn openssl( @@ -261,23 +261,24 @@ mod rustls { impl HttpService, S, B, X, U> where S: ServiceFactory, + S::Future: 'static, S::Error: Into + 'static, S::InitError: fmt::Debug, S::Response: Into> + 'static, >::Future: 'static, B: MessageBody + 'static, X: ServiceFactory, + X::Future: 'static, X::Error: Into, X::InitError: fmt::Debug, - >::Future: 'static, U: ServiceFactory< (Request, Framed, h1::Codec>), Config = (), Response = (), >, + U::Future: 'static, U::Error: fmt::Display + Into, U::InitError: fmt::Debug, - , h1::Codec>)>>::Future: 'static, { /// Create openssl based service pub fn rustls( @@ -319,137 +320,117 @@ mod rustls { impl ServiceFactory<(T, Protocol, Option)> for HttpService where - T: AsyncRead + AsyncWrite + Unpin, + T: AsyncRead + AsyncWrite + Unpin + 'static, S: ServiceFactory, + S::Future: 'static, S::Error: Into + 'static, S::InitError: fmt::Debug, S::Response: Into> + 'static, >::Future: 'static, B: MessageBody + 'static, X: ServiceFactory, + X::Future: 'static, X::Error: Into, X::InitError: fmt::Debug, - >::Future: 'static, U: ServiceFactory<(Request, Framed), Config = (), Response = ()>, + U::Future: 'static, U::Error: fmt::Display + Into, U::InitError: fmt::Debug, - )>>::Future: 'static, { type Response = (); type Error = DispatchError; type Config = (); type Service = HttpServiceHandler; type InitError = (); - type Future = HttpServiceResponse; + type Future = LocalBoxFuture<'static, Result>; fn new_service(&self, _: ()) -> Self::Future { - HttpServiceResponse { - fut: self.srv.new_service(()), - fut_ex: Some(self.expect.new_service(())), - fut_upg: self.upgrade.as_ref().map(|f| f.new_service(())), - expect: None, - upgrade: None, - on_connect_ext: self.on_connect_ext.clone(), - cfg: self.cfg.clone(), - _phantom: PhantomData, - } - } -} + let service = self.srv.new_service(()); + let expect = self.expect.new_service(()); + let upgrade = self.upgrade.as_ref().map(|s| s.new_service(())); + let on_connect_ext = self.on_connect_ext.clone(); + let cfg = self.cfg.clone(); -#[doc(hidden)] -#[pin_project] -pub struct HttpServiceResponse -where - S: ServiceFactory, - X: ServiceFactory, - U: ServiceFactory<(Request, Framed)>, -{ - #[pin] - fut: S::Future, - #[pin] - fut_ex: Option, - #[pin] - fut_upg: Option, - expect: Option, - upgrade: Option, - on_connect_ext: Option>>, - cfg: ServiceConfig, - _phantom: PhantomData, -} + Box::pin(async move { + let expect = expect + .await + .map_err(|e| log::error!("Init http expect service error: {:?}", e))?; -impl Future for HttpServiceResponse -where - T: AsyncRead + AsyncWrite + Unpin, - S: ServiceFactory, - S::Error: Into + 'static, - S::InitError: fmt::Debug, - S::Response: Into> + 'static, - >::Future: 'static, - B: MessageBody + 'static, - X: ServiceFactory, - X::Error: Into, - X::InitError: fmt::Debug, - >::Future: 'static, - U: ServiceFactory<(Request, Framed), Response = ()>, - U::Error: fmt::Display, - U::InitError: fmt::Debug, - )>>::Future: 'static, -{ - type Output = - Result, ()>; + let upgrade = match upgrade { + Some(upgrade) => { + let upgrade = upgrade.await.map_err(|e| { + log::error!("Init http upgrade service error: {:?}", e) + })?; + Some(upgrade) + } + None => None, + }; - fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - let mut this = self.as_mut().project(); + let service = service + .await + .map_err(|e| log::error!("Init http service error: {:?}", e))?; - if let Some(fut) = this.fut_ex.as_pin_mut() { - let expect = ready!(fut - .poll(cx) - .map_err(|e| log::error!("Init http service error: {:?}", e)))?; - this = self.as_mut().project(); - *this.expect = Some(expect); - this.fut_ex.set(None); - } - - if let Some(fut) = this.fut_upg.as_pin_mut() { - let upgrade = ready!(fut - .poll(cx) - .map_err(|e| log::error!("Init http service error: {:?}", e)))?; - this = self.as_mut().project(); - *this.upgrade = Some(upgrade); - this.fut_upg.set(None); - } - - let result = ready!(this - .fut - .poll(cx) - .map_err(|e| log::error!("Init http service error: {:?}", e))); - - Poll::Ready(result.map(|service| { - let this = self.as_mut().project(); - HttpServiceHandler::new( - this.cfg.clone(), + Ok(HttpServiceHandler::new( + cfg, service, - this.expect.take().unwrap(), - this.upgrade.take(), - this.on_connect_ext.clone(), - ) - })) + expect, + upgrade, + on_connect_ext, + )) + }) } } -/// `Service` implementation for HTTP transport +/// `Service` implementation for HTTP/1 and HTTP/2 transport pub struct HttpServiceHandler where S: Service, X: Service, U: Service<(Request, Framed)>, { - flow: Rc>, - cfg: ServiceConfig, - on_connect_ext: Option>>, + pub(super) flow: Rc>, + pub(super) cfg: ServiceConfig, + pub(super) on_connect_ext: Option>>, _phantom: PhantomData, } +impl HttpServiceHandler +where + S: Service, + S::Error: Into, + X: Service, + X::Error: Into, + U: Service<(Request, Framed)>, + U::Error: Into, +{ + pub(super) fn new( + cfg: ServiceConfig, + service: S, + expect: X, + upgrade: Option, + on_connect_ext: Option>>, + ) -> HttpServiceHandler { + HttpServiceHandler { + cfg, + on_connect_ext, + flow: HttpFlow::new(service, expect, upgrade), + _phantom: PhantomData, + } + } + + pub(super) fn _poll_ready(&self, cx: &mut Context<'_>) -> Poll> { + ready!(self.flow.expect.poll_ready(cx).map_err(Into::into))?; + + ready!(self.flow.service.poll_ready(cx).map_err(Into::into))?; + + if let Some(ref upg) = self.flow.upgrade { + ready!(upg.poll_ready(cx).map_err(Into::into))?; + }; + + Poll::Ready(Ok(())) + } +} + /// A collection of services that describe an HTTP request flow. pub(super) struct HttpFlow { pub(super) service: S, @@ -467,34 +448,6 @@ impl HttpFlow { } } -impl HttpServiceHandler -where - S: Service, - S::Error: Into + 'static, - S::Future: 'static, - S::Response: Into> + 'static, - B: MessageBody + 'static, - X: Service, - X::Error: Into, - U: Service<(Request, Framed), Response = ()>, - U::Error: fmt::Display, -{ - fn new( - cfg: ServiceConfig, - service: S, - expect: X, - upgrade: Option, - on_connect_ext: Option>>, - ) -> HttpServiceHandler { - HttpServiceHandler { - cfg, - on_connect_ext, - flow: HttpFlow::new(service, expect, upgrade), - _phantom: PhantomData, - } - } -} - impl Service<(T, Protocol, Option)> for HttpServiceHandler where @@ -514,47 +467,10 @@ where type Future = HttpServiceHandlerResponse; fn poll_ready(&self, cx: &mut Context<'_>) -> Poll> { - let ready = self - .flow - .expect - .poll_ready(cx) - .map_err(|e| { - let e = e.into(); - log::error!("Http service readiness error: {:?}", e); - DispatchError::Service(e) - })? - .is_ready(); - - let ready = self - .flow - .service - .poll_ready(cx) - .map_err(|e| { - let e = e.into(); - log::error!("Http service readiness error: {:?}", e); - DispatchError::Service(e) - })? - .is_ready() - && ready; - - let ready = if let Some(ref upg) = self.flow.upgrade { - upg.poll_ready(cx) - .map_err(|e| { - let e = e.into(); - log::error!("Http service readiness error: {:?}", e); - DispatchError::Service(e) - })? - .is_ready() - && ready - } else { - ready - }; - - if ready { - Poll::Ready(Ok(())) - } else { - Poll::Pending - } + self._poll_ready(cx).map_err(|e| { + log::error!("HTTP service readiness error: {:?}", e); + DispatchError::Service(e) + }) } fn call( From f66774e30b239546991fb6c0d54c8885171d1b8f Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Tue, 30 Mar 2021 03:32:22 +0100 Subject: [PATCH 27/79] remove `From` impl from HttpDate fully removes time crate from public api of -http --- .cargo/config.toml | 3 +++ actix-http/src/header/shared/httpdate.rs | 24 ++++++++++-------------- docs/graphs/net-only.dot | 2 +- 3 files changed, 14 insertions(+), 15 deletions(-) create mode 100644 .cargo/config.toml diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 000000000..9d0d9da8c --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,3 @@ +[alias] +chk = "hack check --workspace --tests --examples" +lint = "hack --clean-per-run clippy --workspace --tests --examples" diff --git a/actix-http/src/header/shared/httpdate.rs b/actix-http/src/header/shared/httpdate.rs index 72a225589..18278a6d8 100644 --- a/actix-http/src/header/shared/httpdate.rs +++ b/actix-http/src/header/shared/httpdate.rs @@ -1,18 +1,20 @@ -use std::fmt::{self, Display}; -use std::io::Write; -use std::str::FromStr; -use std::time::{SystemTime, UNIX_EPOCH}; +use std::{ + fmt, + io::Write, + str::FromStr, + time::{SystemTime, UNIX_EPOCH}, +}; use bytes::buf::BufMut; use bytes::BytesMut; use http::header::{HeaderValue, InvalidHeaderValue}; -use time::{offset, OffsetDateTime, PrimitiveDateTime}; +use time::{OffsetDateTime, PrimitiveDateTime, UtcOffset}; use crate::error::ParseError; use crate::header::IntoHeaderValue; use crate::time_parser; -/// A timestamp with HTTP formatting and parsing +/// A timestamp with HTTP formatting and parsing. #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] pub struct HttpDate(OffsetDateTime); @@ -27,18 +29,12 @@ impl FromStr for HttpDate { } } -impl Display for HttpDate { +impl fmt::Display for HttpDate { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Display::fmt(&self.0.format("%a, %d %b %Y %H:%M:%S GMT"), f) } } -impl From for HttpDate { - fn from(dt: OffsetDateTime) -> HttpDate { - HttpDate(dt) - } -} - impl From for HttpDate { fn from(sys: SystemTime) -> HttpDate { HttpDate(PrimitiveDateTime::from(sys).assume_utc()) @@ -54,7 +50,7 @@ impl IntoHeaderValue for HttpDate { wrt, "{}", self.0 - .to_offset(offset!(UTC)) + .to_offset(UtcOffset::UTC) .format("%a, %d %b %Y %H:%M:%S GMT") ) .unwrap(); diff --git a/docs/graphs/net-only.dot b/docs/graphs/net-only.dot index babd612a6..84227cdb0 100644 --- a/docs/graphs/net-only.dot +++ b/docs/graphs/net-only.dot @@ -7,7 +7,7 @@ digraph { } "actix-codec" -> { "actix-rt" "actix-service" "local-channel" "tokio" } - "actix-utils" -> { "actix-rt" "actix-service" "local-waker" } + "actix-utils" -> { "local-waker" } "actix-tracing" -> { "actix-service" } "actix-tls" -> { "actix-service" "actix-codec" "actix-utils" "actix-rt" } "actix-server" -> { "actix-service" "actix-rt" "actix-codec" "actix-utils" "tokio" } From c49fe79207aadd5ef7e2a8db5daadf19fab2366a Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Tue, 30 Mar 2021 07:46:09 -0700 Subject: [PATCH 28/79] Simplify lifetime annotation in HttpServiceBuilder. Simplify PlStream (#2129) --- actix-http/src/builder.rs | 6 ----- actix-http/src/client/h1proto.rs | 40 +++++++++++--------------------- 2 files changed, 14 insertions(+), 32 deletions(-) diff --git a/actix-http/src/builder.rs b/actix-http/src/builder.rs index fa430c4fe..623bfdda2 100644 --- a/actix-http/src/builder.rs +++ b/actix-http/src/builder.rs @@ -63,11 +63,9 @@ where X: ServiceFactory, X::Error: Into, X::InitError: fmt::Debug, - >::Future: 'static, U: ServiceFactory<(Request, Framed), Config = (), Response = ()>, U::Error: fmt::Display, U::InitError: fmt::Debug, - )>>::Future: 'static, { /// Set server keep-alive setting. /// @@ -127,7 +125,6 @@ where X1: ServiceFactory, X1::Error: Into, X1::InitError: fmt::Debug, - >::Future: 'static, { HttpServiceBuilder { keep_alive: self.keep_alive, @@ -152,7 +149,6 @@ where U1: ServiceFactory<(Request, Framed), Config = (), Response = ()>, U1::Error: fmt::Display, U1::InitError: fmt::Debug, - )>>::Future: 'static, { HttpServiceBuilder { keep_alive: self.keep_alive, @@ -211,7 +207,6 @@ where S::Error: Into + 'static, S::InitError: fmt::Debug, S::Response: Into> + 'static, - >::Future: 'static, { let cfg = ServiceConfig::new( self.keep_alive, @@ -233,7 +228,6 @@ where S::Error: Into + 'static, S::InitError: fmt::Debug, S::Response: Into> + 'static, - >::Future: 'static, { let cfg = ServiceConfig::new( self.keep_alive, diff --git a/actix-http/src/client/h1proto.rs b/actix-http/src/client/h1proto.rs index 01a6e1edf..8fb08b0ce 100644 --- a/actix-http/src/client/h1proto.rs +++ b/actix-http/src/client/h1proto.rs @@ -7,7 +7,7 @@ use std::{ use actix_codec::Framed; use bytes::buf::BufMut; use bytes::{Bytes, BytesMut}; -use futures_core::Stream; +use futures_core::{ready, Stream}; use futures_util::{future::poll_fn, SinkExt as _}; use crate::error::PayloadError; @@ -17,7 +17,7 @@ use crate::http::{ StatusCode, }; use crate::message::{RequestHeadType, ResponseHead}; -use crate::payload::{Payload, PayloadStream}; +use crate::payload::Payload; use super::connection::{ConnectionIo, H1Connection}; use super::error::{ConnectError, SendRequestError}; @@ -122,10 +122,7 @@ where Ok((head, Payload::None)) } - _ => { - let pl: PayloadStream = Box::pin(PlStream::new(framed)); - Ok((head, pl.into())) - } + _ => Ok((head, Payload::Stream(Box::pin(PlStream::new(framed))))), } } @@ -194,21 +191,16 @@ where } #[pin_project::pin_project] -pub(crate) struct PlStream -where - Io: ConnectionIo, -{ +pub(crate) struct PlStream { #[pin] - framed: Option, h1::ClientPayloadCodec>>, + framed: Framed, h1::ClientPayloadCodec>, } impl PlStream { fn new(framed: Framed, h1::ClientCodec>) -> Self { let framed = framed.into_map_codec(|codec| codec.into_payload_codec()); - PlStream { - framed: Some(framed), - } + PlStream { framed } } } @@ -219,20 +211,16 @@ impl Stream for PlStream { self: Pin<&mut Self>, cx: &mut Context<'_>, ) -> Poll> { - let mut framed = self.project().framed.as_pin_mut().unwrap(); + let mut this = self.project(); - match framed.as_mut().next_item(cx)? { - Poll::Pending => Poll::Pending, - Poll::Ready(Some(chunk)) => { - if let Some(chunk) = chunk { - Poll::Ready(Some(Ok(chunk))) - } else { - let keep_alive = framed.codec_ref().keepalive(); - framed.io_mut().on_release(keep_alive); - Poll::Ready(None) - } + match ready!(this.framed.as_mut().next_item(cx)?) { + Some(Some(chunk)) => Poll::Ready(Some(Ok(chunk))), + Some(None) => { + let keep_alive = this.framed.codec_ref().keepalive(); + this.framed.io_mut().on_release(keep_alive); + Poll::Ready(None) } - Poll::Ready(None) => Poll::Ready(None), + None => Poll::Ready(None), } } } From 1f1be6fd3d868f242ffd0ef72a7871f9e8f0d2b1 Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Wed, 31 Mar 2021 03:43:56 -0700 Subject: [PATCH 29/79] add Client::headers (#2114) --- awc/CHANGES.md | 4 ++++ awc/src/lib.rs | 8 ++++++++ 2 files changed, 12 insertions(+) diff --git a/awc/CHANGES.md b/awc/CHANGES.md index 4f72e3f93..b745e9868 100644 --- a/awc/CHANGES.md +++ b/awc/CHANGES.md @@ -1,11 +1,15 @@ # Changes ## Unreleased - 2021-xx-xx +### Added +* Add `Client::headers` to get default mut reference of `HeaderMap` of client object. [#2114] + ### Changed * `ConnectorService` type is renamed to `BoxConnectorService`. [#2081] * Fix http/https encoding when enabling `compress` feature. [#2116] [#2081]: https://github.com/actix/actix-web/pull/2081 +[#2114]: https://github.com/actix/actix-web/pull/2114 [#2116]: https://github.com/actix/actix-web/pull/2116 diff --git a/awc/src/lib.rs b/awc/src/lib.rs index c7bb68a8f..f1aecbd37 100644 --- a/awc/src/lib.rs +++ b/awc/src/lib.rs @@ -286,4 +286,12 @@ impl Client { } req } + + /// Get default HeaderMap of Client. + /// + /// Returns Some(&mut HeaderMap) when Client object is unique + /// (No other clone of client exists at the same time). + pub fn headers(&mut self) -> Option<&mut HeaderMap> { + Rc::get_mut(&mut self.0.headers) + } } From a807d33600e3797401caf1f0aedb04b50400e710 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Pokrywka?= Date: Thu, 1 Apr 2021 07:40:10 +0200 Subject: [PATCH 30/79] added TestServer::client_headers (#2097) Co-authored-by: fakeshadow <24548779@qq.com> Co-authored-by: Rob Ede --- CHANGES.md | 3 ++ actix-http-test/CHANGES.md | 3 +- actix-http-test/src/lib.rs | 12 ++++++- actix-web-actors/Cargo.toml | 1 + actix-web-actors/tests/test_ws.rs | 53 ++++++++++++++++++++++++++++++- src/test.rs | 10 +++++- 6 files changed, 78 insertions(+), 4 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 5df0b6d6d..39743be2b 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,9 @@ # Changes ## Unreleased - 2021-xx-xx +### Added +* Added `TestServer::client_headers` method. [#2097] + ### Fixed * Double ampersand in Logger format is escaped correctly. [#2067] diff --git a/actix-http-test/CHANGES.md b/actix-http-test/CHANGES.md index d6a2cdd9b..0fac84a6c 100644 --- a/actix-http-test/CHANGES.md +++ b/actix-http-test/CHANGES.md @@ -1,7 +1,8 @@ # Changes ## Unreleased - 2021-xx-xx - +### Added +* Added `TestServer::client_headers` method. [#2097] ## 3.0.0-beta.3 - 2021-03-09 * No notable changes. diff --git a/actix-http-test/src/lib.rs b/actix-http-test/src/lib.rs index 3749b78ca..9a5069c49 100644 --- a/actix-http-test/src/lib.rs +++ b/actix-http-test/src/lib.rs @@ -13,7 +13,9 @@ use std::{net, thread, time}; use actix_codec::{AsyncRead, AsyncWrite, Framed}; use actix_rt::{net::TcpStream, System}; use actix_server::{Server, ServiceFactory}; -use awc::{error::PayloadError, ws, Client, ClientRequest, ClientResponse, Connector}; +use awc::{ + error::PayloadError, http::HeaderMap, ws, Client, ClientRequest, ClientResponse, Connector, +}; use bytes::Bytes; use futures_core::stream::Stream; use http::Method; @@ -258,6 +260,14 @@ impl TestServer { self.ws_at("/").await } + /// Get default HeaderMap of Client. + /// + /// Returns Some(&mut HeaderMap) when Client object is unique + /// (No other clone of client exists at the same time). + pub fn client_headers(&mut self) -> Option<&mut HeaderMap> { + self.client.headers() + } + /// Stop HTTP server fn stop(&mut self) { self.system.stop(); diff --git a/actix-web-actors/Cargo.toml b/actix-web-actors/Cargo.toml index 1f734582d..e60d4301c 100644 --- a/actix-web-actors/Cargo.toml +++ b/actix-web-actors/Cargo.toml @@ -29,5 +29,6 @@ tokio = { version = "1", features = ["sync"] } [dev-dependencies] actix-rt = "2.2" +awc = { version = "3.0.0-beta.3", default-features = false } env_logger = "0.8" futures-util = { version = "0.3.7", default-features = false } diff --git a/actix-web-actors/tests/test_ws.rs b/actix-web-actors/tests/test_ws.rs index 912480ae4..7999beed9 100644 --- a/actix-web-actors/tests/test_ws.rs +++ b/actix-web-actors/tests/test_ws.rs @@ -1,5 +1,8 @@ use actix::prelude::*; -use actix_web::{test, web, App, HttpRequest}; +use actix_web::{ + http::{header, StatusCode}, + test, web, App, HttpRequest, HttpResponse, +}; use actix_web_actors::*; use bytes::Bytes; use futures_util::{SinkExt, StreamExt}; @@ -56,3 +59,51 @@ async fn test_simple() { let item = framed.next().await.unwrap().unwrap(); assert_eq!(item, ws::Frame::Close(Some(ws::CloseCode::Normal.into()))); } + +#[actix_rt::test] +async fn test_with_credentials() { + let mut srv = test::start(|| { + App::new().service(web::resource("/").to( + |req: HttpRequest, stream: web::Payload| async move { + if req.headers().contains_key("Authorization") { + ws::start(Ws, &req, stream) + } else { + Ok(HttpResponse::new(StatusCode::UNAUTHORIZED)) + } + }, + )) + }); + + // client service without credentials + match srv.ws().await { + Ok(_) => panic!("WebSocket client without credentials should panic"), + Err(awc::error::WsClientError::InvalidResponseStatus(status)) => { + assert_eq!(status, StatusCode::UNAUTHORIZED) + } + Err(e) => panic!("Invalid error from WebSocket client: {}", e), + } + + let headers = srv.client_headers().unwrap(); + headers.insert( + header::AUTHORIZATION, + header::HeaderValue::from_static("Bearer Something"), + ); + + // client service with credentials + let client = srv.ws(); + + let mut framed = client.await.unwrap(); + + framed.send(ws::Message::Text("text".into())).await.unwrap(); + + let item = framed.next().await.unwrap().unwrap(); + assert_eq!(item, ws::Frame::Text(Bytes::from_static(b"text"))); + + framed + .send(ws::Message::Close(Some(ws::CloseCode::Normal.into()))) + .await + .unwrap(); + + let item = framed.next().await.unwrap().unwrap(); + assert_eq!(item, ws::Frame::Close(Some(ws::CloseCode::Normal.into()))); +} diff --git a/src/test.rs b/src/test.rs index 7e8bf1069..18bf89cda 100644 --- a/src/test.rs +++ b/src/test.rs @@ -8,7 +8,7 @@ use std::{fmt, net, thread, time}; use actix_codec::{AsyncRead, AsyncWrite, Framed}; #[cfg(feature = "cookies")] use actix_http::cookie::Cookie; -use actix_http::http::header::{ContentType, IntoHeaderPair}; +use actix_http::http::header::{ContentType, HeaderMap, IntoHeaderPair}; use actix_http::http::{Method, StatusCode, Uri, Version}; use actix_http::test::TestRequest as HttpTestRequest; use actix_http::{ws, Extensions, HttpService, Request}; @@ -962,6 +962,14 @@ impl TestServer { self.ws_at("/").await } + /// Get default HeaderMap of Client. + /// + /// Returns Some(&mut HeaderMap) when Client object is unique + /// (No other clone of client exists at the same time). + pub fn client_headers(&mut self) -> Option<&mut HeaderMap> { + self.client.headers() + } + /// Gracefully stop HTTP server pub async fn stop(self) { self.server.stop(true).await; From c8ed8dd1a4baddc3071d5d3f297da49617faa8b0 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Thu, 1 Apr 2021 15:26:13 +0100 Subject: [PATCH 31/79] migrate to -utils beta 4 (#2127) --- .cargo/config.toml | 2 +- Cargo.toml | 2 +- actix-files/Cargo.toml | 4 +- actix-files/src/files.rs | 15 +- actix-files/src/lib.rs | 2 +- actix-files/src/path_buf.rs | 2 +- actix-files/src/service.rs | 48 ++-- actix-http-test/Cargo.toml | 2 +- actix-http/CHANGES.md | 4 + actix-http/Cargo.toml | 4 +- actix-http/examples/hello-world.rs | 2 +- actix-http/src/body/mod.rs | 3 +- actix-http/src/client/h1proto.rs | 3 +- actix-http/src/client/h2proto.rs | 2 +- actix-http/src/error.rs | 24 -- actix-http/src/h1/dispatcher.rs | 3 +- actix-http/src/h1/expect.rs | 2 +- actix-http/src/h1/payload.rs | 2 +- actix-http/src/h1/service.rs | 2 +- actix-http/src/h2/service.rs | 2 +- actix-http/src/ws/dispatcher.rs | 393 +++++++++++++++++++++++++- actix-http/tests/test_client.rs | 12 +- actix-http/tests/test_openssl.rs | 6 +- actix-http/tests/test_rustls.rs | 16 +- actix-http/tests/test_server.rs | 35 +-- actix-http/tests/test_ws.rs | 7 +- actix-multipart/Cargo.toml | 6 +- actix-multipart/src/extractor.rs | 17 +- actix-multipart/src/server.rs | 10 +- actix-web-actors/tests/test_ws.rs | 2 +- actix-web-codegen/Cargo.toml | 3 +- actix-web-codegen/tests/test_macro.rs | 3 +- awc/Cargo.toml | 2 +- awc/src/ws.rs | 2 +- awc/tests/test_client.rs | 5 +- awc/tests/test_rustls_client.rs | 2 +- awc/tests/test_ssl_client.rs | 2 +- awc/tests/test_ws.rs | 4 +- benches/responder.rs | 8 +- docs/graphs/net-only.dot | 28 +- src/app.rs | 4 +- src/data.rs | 5 +- src/extract.rs | 6 +- src/handler.rs | 4 +- src/middleware/compress.rs | 2 +- src/middleware/condition.rs | 10 +- src/middleware/default_headers.rs | 8 +- src/middleware/err_handlers.rs | 3 +- src/middleware/logger.rs | 7 +- src/middleware/normalize.rs | 2 +- src/request.rs | 2 +- src/request_data.rs | 8 +- src/resource.rs | 2 +- src/scope.rs | 2 +- src/service.rs | 2 +- src/test.rs | 4 +- src/types/either.rs | 3 +- src/types/form.rs | 6 +- src/types/json.rs | 2 +- src/types/path.rs | 2 +- src/types/payload.rs | 39 ++- src/types/query.rs | 2 +- src/types/readlines.rs | 2 +- tests/test_server.rs | 2 +- 64 files changed, 612 insertions(+), 210 deletions(-) diff --git a/.cargo/config.toml b/.cargo/config.toml index 9d0d9da8c..40fe3e573 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -1,3 +1,3 @@ [alias] -chk = "hack check --workspace --tests --examples" +chk = "hack check --workspace --all-features --tests --examples" lint = "hack --clean-per-run clippy --workspace --tests --examples" diff --git a/Cargo.toml b/Cargo.toml index 7dd7635cd..15f9f5fe8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -83,7 +83,7 @@ actix-router = "0.2.7" actix-rt = "2.2" actix-server = "2.0.0-beta.3" actix-service = "2.0.0-beta.4" -actix-utils = "3.0.0-beta.2" +actix-utils = "3.0.0-beta.4" actix-tls = { version = "3.0.0-beta.5", default-features = false, optional = true } actix-web-codegen = "0.5.0-beta.2" diff --git a/actix-files/Cargo.toml b/actix-files/Cargo.toml index 472bd0362..0cc02c6bd 100644 --- a/actix-files/Cargo.toml +++ b/actix-files/Cargo.toml @@ -19,12 +19,12 @@ path = "src/lib.rs" [dependencies] actix-web = { version = "4.0.0-beta.4", default-features = false } actix-service = "2.0.0-beta.4" +actix-utils = "3.0.0-beta.4" askama_escape = "0.10" bitflags = "1" bytes = "1" -futures-core = { version = "0.3.7", default-features = false } -futures-util = { version = "0.3.7", default-features = false } +futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] } http-range = "0.1.4" derive_more = "0.99.5" log = "0.4" diff --git a/actix-files/src/files.rs b/actix-files/src/files.rs index 292e3fdf3..ff4241340 100644 --- a/actix-files/src/files.rs +++ b/actix-files/src/files.rs @@ -1,6 +1,7 @@ use std::{cell::RefCell, fmt, io, path::PathBuf, rc::Rc}; use actix_service::{boxed, IntoServiceFactory, ServiceFactory, ServiceFactoryExt}; +use actix_utils::future::ok; use actix_web::{ dev::{AppService, HttpServiceFactory, ResourceDef, ServiceRequest, ServiceResponse}, error::Error, @@ -8,7 +9,7 @@ use actix_web::{ http::header::DispositionType, HttpRequest, }; -use futures_util::future::{ok, FutureExt, LocalBoxFuture}; +use futures_core::future::LocalBoxFuture; use crate::{ directory_listing, named, Directory, DirectoryRenderer, FilesService, HttpNewService, @@ -263,18 +264,18 @@ impl ServiceFactory for Files { }; if let Some(ref default) = *self.default.borrow() { - default - .new_service(()) - .map(move |result| match result { + let fut = default.new_service(()); + Box::pin(async { + match fut.await { Ok(default) => { srv.default = Some(default); Ok(srv) } Err(_) => Err(()), - }) - .boxed_local() + } + }) } else { - ok(srv).boxed_local() + Box::pin(ok(srv)) } } } diff --git a/actix-files/src/lib.rs b/actix-files/src/lib.rs index 018079b21..f8583febe 100644 --- a/actix-files/src/lib.rs +++ b/actix-files/src/lib.rs @@ -65,6 +65,7 @@ mod tests { }; use actix_service::ServiceFactory; + use actix_utils::future::ok; use actix_web::{ guard, http::{ @@ -76,7 +77,6 @@ mod tests { web::{self, Bytes}, App, HttpResponse, Responder, }; - use futures_util::future::ok; use super::*; diff --git a/actix-files/src/path_buf.rs b/actix-files/src/path_buf.rs index dd8e5b503..8a87acd5d 100644 --- a/actix-files/src/path_buf.rs +++ b/actix-files/src/path_buf.rs @@ -3,8 +3,8 @@ use std::{ str::FromStr, }; +use actix_utils::future::{ready, Ready}; use actix_web::{dev::Payload, FromRequest, HttpRequest}; -use futures_util::future::{ready, Ready}; use crate::error::UriSegmentError; diff --git a/actix-files/src/service.rs b/actix-files/src/service.rs index 3214963ed..d2db8503f 100644 --- a/actix-files/src/service.rs +++ b/actix-files/src/service.rs @@ -1,6 +1,7 @@ use std::{fmt, io, path::PathBuf, rc::Rc}; use actix_service::Service; +use actix_utils::future::ok; use actix_web::{ dev::{ServiceRequest, ServiceResponse}, error::Error, @@ -8,7 +9,7 @@ use actix_web::{ http::{header, Method}, HttpResponse, }; -use futures_util::future::{ok, Either, LocalBoxFuture, Ready}; +use futures_core::future::LocalBoxFuture; use crate::{ named, Directory, DirectoryRenderer, FilesError, HttpService, MimeOverride, NamedFile, @@ -29,19 +30,18 @@ pub struct FilesService { pub(crate) hidden_files: bool, } -type FilesServiceFuture = Either< - Ready>, - LocalBoxFuture<'static, Result>, ->; - impl FilesService { - fn handle_err(&self, e: io::Error, req: ServiceRequest) -> FilesServiceFuture { - log::debug!("Failed to handle {}: {}", req.path(), e); + fn handle_err( + &self, + err: io::Error, + req: ServiceRequest, + ) -> LocalBoxFuture<'static, Result> { + log::debug!("error handling {}: {}", req.path(), err); if let Some(ref default) = self.default { - Either::Right(default.call(req)) + Box::pin(default.call(req)) } else { - Either::Left(ok(req.error_response(e))) + Box::pin(ok(req.error_response(err))) } } } @@ -55,7 +55,7 @@ impl fmt::Debug for FilesService { impl Service for FilesService { type Response = ServiceResponse; type Error = Error; - type Future = FilesServiceFuture; + type Future = LocalBoxFuture<'static, Result>; actix_service::always_ready!(); @@ -69,7 +69,7 @@ impl Service for FilesService { }; if !is_method_valid { - return Either::Left(ok(req.into_response( + return Box::pin(ok(req.into_response( actix_web::HttpResponse::MethodNotAllowed() .insert_header(header::ContentType(mime::TEXT_PLAIN_UTF_8)) .body("Request did not meet this resource's requirements."), @@ -79,13 +79,13 @@ impl Service for FilesService { let real_path = match PathBufWrap::parse_path(req.match_info().path(), self.hidden_files) { Ok(item) => item, - Err(e) => return Either::Left(ok(req.error_response(e))), + Err(e) => return Box::pin(ok(req.error_response(e))), }; // full file path let path = match self.directory.join(&real_path).canonicalize() { Ok(path) => path, - Err(e) => return self.handle_err(e, req), + Err(err) => return Box::pin(self.handle_err(err, req)), }; if path.is_dir() { @@ -93,7 +93,7 @@ impl Service for FilesService { if self.redirect_to_slash && !req.path().ends_with('/') { let redirect_to = format!("{}/", req.path()); - return Either::Left(ok(req.into_response( + return Box::pin(ok(req.into_response( HttpResponse::Found() .insert_header((header::LOCATION, redirect_to)) .body("") @@ -114,9 +114,9 @@ impl Service for FilesService { let (req, _) = req.into_parts(); let res = named_file.into_response(&req); - Either::Left(ok(ServiceResponse::new(req, res))) + Box::pin(ok(ServiceResponse::new(req, res))) } - Err(e) => self.handle_err(e, req), + Err(err) => self.handle_err(err, req), } } else if self.show_index { let dir = Directory::new(self.directory.clone(), path); @@ -124,12 +124,12 @@ impl Service for FilesService { let (req, _) = req.into_parts(); let x = (self.renderer)(&dir, &req); - match x { - Ok(resp) => Either::Left(ok(resp)), - Err(e) => Either::Left(ok(ServiceResponse::from_err(e, req))), - } + Box::pin(match x { + Ok(resp) => ok(resp), + Err(err) => ok(ServiceResponse::from_err(err, req)), + }) } else { - Either::Left(ok(ServiceResponse::from_err( + Box::pin(ok(ServiceResponse::from_err( FilesError::IsDirectory, req.into_parts().0, ))) @@ -145,9 +145,9 @@ impl Service for FilesService { let (req, _) = req.into_parts(); let res = named_file.into_response(&req); - Either::Left(ok(ServiceResponse::new(req, res))) + Box::pin(ok(ServiceResponse::new(req, res))) } - Err(e) => self.handle_err(e, req), + Err(err) => self.handle_err(err, req), } } } diff --git a/actix-http-test/Cargo.toml b/actix-http-test/Cargo.toml index 0e7d57fc3..31a83eddb 100644 --- a/actix-http-test/Cargo.toml +++ b/actix-http-test/Cargo.toml @@ -32,7 +32,7 @@ openssl = ["tls-openssl", "awc/openssl"] actix-service = "2.0.0-beta.4" actix-codec = "0.4.0-beta.1" actix-tls = "3.0.0-beta.5" -actix-utils = "3.0.0-beta.2" +actix-utils = "3.0.0-beta.4" actix-rt = "2.2" actix-server = "2.0.0-beta.3" awc = { version = "3.0.0-beta.3", default-features = false } diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 2c71031ab..3a7b4f024 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -9,8 +9,12 @@ ### Changed * `client::Connector` type now only have one generic type for `actix_service::Service`. [#2063] +### Removed +* `ResponseError` impl for `actix_utils::timeout::TimeoutError`. [#2127] + [#2063]: https://github.com/actix/actix-web/pull/2063 [#2081]: https://github.com/actix/actix-web/pull/2081 +[#2127]: https://github.com/actix/actix-web/pull/2127 ## 3.0.0-beta.4 - 2021-03-08 diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 679e8c992..d9af75aa5 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -46,7 +46,7 @@ trust-dns = ["trust-dns-resolver"] [dependencies] actix-service = "2.0.0-beta.4" actix-codec = "0.4.0-beta.1" -actix-utils = "3.0.0-beta.2" +actix-utils = "3.0.0-beta.4" actix-rt = "2.2" actix-tls = "3.0.0-beta.5" @@ -65,11 +65,13 @@ http = "0.2.2" httparse = "1.3" itoa = "0.4" language-tags = "0.2" +local-channel = "0.1" once_cell = "1.5" log = "0.4" mime = "0.3" percent-encoding = "2.1" pin-project = "1.0.0" +pin-project-lite = "0.2" rand = "0.8" regex = "1.3" serde = "1.0" diff --git a/actix-http/examples/hello-world.rs b/actix-http/examples/hello-world.rs index a84e9aac6..a99ddae46 100644 --- a/actix-http/examples/hello-world.rs +++ b/actix-http/examples/hello-world.rs @@ -2,7 +2,7 @@ use std::{env, io}; use actix_http::{HttpService, Response}; use actix_server::Server; -use futures_util::future; +use actix_utils::future; use http::header::HeaderValue; use log::info; diff --git a/actix-http/src/body/mod.rs b/actix-http/src/body/mod.rs index a4d6ba2b6..fa43e1b03 100644 --- a/actix-http/src/body/mod.rs +++ b/actix-http/src/body/mod.rs @@ -20,8 +20,9 @@ mod tests { use std::pin::Pin; use actix_rt::pin; + use actix_utils::future::poll_fn; use bytes::{Bytes, BytesMut}; - use futures_util::{future::poll_fn, stream}; + use futures_util::stream; use super::*; diff --git a/actix-http/src/client/h1proto.rs b/actix-http/src/client/h1proto.rs index 8fb08b0ce..fa4469d35 100644 --- a/actix-http/src/client/h1proto.rs +++ b/actix-http/src/client/h1proto.rs @@ -5,10 +5,11 @@ use std::{ }; use actix_codec::Framed; +use actix_utils::future::poll_fn; use bytes::buf::BufMut; use bytes::{Bytes, BytesMut}; use futures_core::{ready, Stream}; -use futures_util::{future::poll_fn, SinkExt as _}; +use futures_util::SinkExt as _; use crate::error::PayloadError; use crate::h1; diff --git a/actix-http/src/client/h2proto.rs b/actix-http/src/client/h2proto.rs index 437b9ae76..8cb2e2522 100644 --- a/actix-http/src/client/h2proto.rs +++ b/actix-http/src/client/h2proto.rs @@ -1,7 +1,7 @@ use std::future::Future; +use actix_utils::future::poll_fn; use bytes::Bytes; -use futures_util::future::poll_fn; use h2::{ client::{Builder, Connection, SendRequest}, SendStream, diff --git a/actix-http/src/error.rs b/actix-http/src/error.rs index 1354e998e..0178be80c 100644 --- a/actix-http/src/error.rs +++ b/actix-http/src/error.rs @@ -6,9 +6,6 @@ use std::str::Utf8Error; use std::string::FromUtf8Error; use std::{fmt, io, result}; -use actix_codec::{Decoder, Encoder}; -use actix_utils::dispatcher::DispatcherError as FramedDispatcherError; -use actix_utils::timeout::TimeoutError; use bytes::BytesMut; use derive_more::{Display, From}; use http::uri::InvalidUri; @@ -148,19 +145,6 @@ impl From for Error { } } -/// Inspects the underlying enum and returns an appropriate status code. -/// -/// If the variant is [`TimeoutError::Service`], the error code of the service is returned. -/// Otherwise, [`StatusCode::GATEWAY_TIMEOUT`] is returned. -impl ResponseError for TimeoutError { - fn status_code(&self) -> StatusCode { - match self { - TimeoutError::Service(e) => e.status_code(), - TimeoutError::Timeout => StatusCode::GATEWAY_TIMEOUT, - } - } -} - #[derive(Debug, Display)] #[display(fmt = "UnknownError")] struct UnitError; @@ -469,14 +453,6 @@ impl ResponseError for ContentTypeError { } } -impl + Decoder, I> ResponseError for FramedDispatcherError -where - E: fmt::Debug + fmt::Display, - >::Error: fmt::Debug, - ::Error: fmt::Debug, -{ -} - /// Helper type that can wrap any error and generate custom response. /// /// In following example any `io::Error` will be converted into "BAD REQUEST" diff --git a/actix-http/src/h1/dispatcher.rs b/actix-http/src/h1/dispatcher.rs index e5989e5ee..bf0365693 100644 --- a/actix-http/src/h1/dispatcher.rs +++ b/actix-http/src/h1/dispatcher.rs @@ -951,7 +951,8 @@ mod tests { use std::str; use actix_service::fn_service; - use futures_util::future::{lazy, ready, Ready}; + use actix_utils::future::{ready, Ready}; + use futures_util::future::lazy; use super::*; use crate::{ diff --git a/actix-http/src/h1/expect.rs b/actix-http/src/h1/expect.rs index 5015069bb..bb8e28e95 100644 --- a/actix-http/src/h1/expect.rs +++ b/actix-http/src/h1/expect.rs @@ -1,5 +1,5 @@ use actix_service::{Service, ServiceFactory}; -use futures_util::future::{ready, Ready}; +use actix_utils::future::{ready, Ready}; use crate::error::Error; use crate::request::Request; diff --git a/actix-http/src/h1/payload.rs b/actix-http/src/h1/payload.rs index 32275ac6b..e72493fa2 100644 --- a/actix-http/src/h1/payload.rs +++ b/actix-http/src/h1/payload.rs @@ -263,7 +263,7 @@ impl Inner { #[cfg(test)] mod tests { use super::*; - use futures_util::future::poll_fn; + use actix_utils::future::poll_fn; #[actix_rt::test] async fn test_unread_data() { diff --git a/actix-http/src/h1/service.rs b/actix-http/src/h1/service.rs index f915bfa47..a98f6bd0a 100644 --- a/actix-http/src/h1/service.rs +++ b/actix-http/src/h1/service.rs @@ -6,8 +6,8 @@ use std::{fmt, net}; use actix_codec::{AsyncRead, AsyncWrite, Framed}; use actix_rt::net::TcpStream; use actix_service::{pipeline_factory, IntoServiceFactory, Service, ServiceFactory}; +use actix_utils::future::ready; use futures_core::future::LocalBoxFuture; -use futures_util::future::ready; use crate::body::MessageBody; use crate::config::ServiceConfig; diff --git a/actix-http/src/h2/service.rs b/actix-http/src/h2/service.rs index db0b580b3..8f202e752 100644 --- a/actix-http/src/h2/service.rs +++ b/actix-http/src/h2/service.rs @@ -10,9 +10,9 @@ use actix_service::{ fn_factory, fn_service, pipeline_factory, IntoServiceFactory, Service, ServiceFactory, }; +use actix_utils::future::ready; use bytes::Bytes; use futures_core::{future::LocalBoxFuture, ready}; -use futures_util::future::ready; use h2::server::{handshake, Handshake}; use log::error; diff --git a/actix-http/src/ws/dispatcher.rs b/actix-http/src/ws/dispatcher.rs index 7be7cf637..576851139 100644 --- a/actix-http/src/ws/dispatcher.rs +++ b/actix-http/src/ws/dispatcher.rs @@ -4,7 +4,6 @@ use std::task::{Context, Poll}; use actix_codec::{AsyncRead, AsyncWrite, Framed}; use actix_service::{IntoService, Service}; -use actix_utils::dispatcher::{Dispatcher as InnerDispatcher, DispatcherError}; use super::{Codec, Frame, Message}; @@ -15,7 +14,7 @@ where T: AsyncRead + AsyncWrite, { #[pin] - inner: InnerDispatcher, + inner: inner::Dispatcher, } impl Dispatcher @@ -27,13 +26,13 @@ where { pub fn new>(io: T, service: F) -> Self { Dispatcher { - inner: InnerDispatcher::new(Framed::new(io, Codec::new()), service), + inner: inner::Dispatcher::new(Framed::new(io, Codec::new()), service), } } pub fn with>(framed: Framed, service: F) -> Self { Dispatcher { - inner: InnerDispatcher::new(framed, service), + inner: inner::Dispatcher::new(framed, service), } } } @@ -45,9 +44,393 @@ where S::Future: 'static, S::Error: 'static, { - type Output = Result<(), DispatcherError>; + type Output = Result<(), inner::DispatcherError>; fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { self.project().inner.poll(cx) } } + +/// Framed dispatcher service and related utilities. +mod inner { + // allow dead code since this mod was ripped from actix-utils + #![allow(dead_code)] + + use core::{ + fmt, + future::Future, + mem, + pin::Pin, + task::{Context, Poll}, + }; + + use actix_service::{IntoService, Service}; + use futures_core::stream::Stream; + use local_channel::mpsc; + use log::debug; + use pin_project_lite::pin_project; + + use actix_codec::{AsyncRead, AsyncWrite, Decoder, Encoder, Framed}; + + use crate::ResponseError; + + /// Framed transport errors + pub enum DispatcherError + where + U: Encoder + Decoder, + { + /// Inner service error. + Service(E), + + /// Frame encoding error. + Encoder(>::Error), + + /// Frame decoding error. + Decoder(::Error), + } + + impl From for DispatcherError + where + U: Encoder + Decoder, + { + fn from(err: E) -> Self { + DispatcherError::Service(err) + } + } + + impl fmt::Debug for DispatcherError + where + E: fmt::Debug, + U: Encoder + Decoder, + >::Error: fmt::Debug, + ::Error: fmt::Debug, + { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + match *self { + DispatcherError::Service(ref e) => { + write!(fmt, "DispatcherError::Service({:?})", e) + } + DispatcherError::Encoder(ref e) => { + write!(fmt, "DispatcherError::Encoder({:?})", e) + } + DispatcherError::Decoder(ref e) => { + write!(fmt, "DispatcherError::Decoder({:?})", e) + } + } + } + } + + impl fmt::Display for DispatcherError + where + E: fmt::Display, + U: Encoder + Decoder, + >::Error: fmt::Debug, + ::Error: fmt::Debug, + { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + match *self { + DispatcherError::Service(ref e) => write!(fmt, "{}", e), + DispatcherError::Encoder(ref e) => write!(fmt, "{:?}", e), + DispatcherError::Decoder(ref e) => write!(fmt, "{:?}", e), + } + } + } + + impl ResponseError for DispatcherError + where + E: fmt::Debug + fmt::Display, + U: Encoder + Decoder, + >::Error: fmt::Debug, + ::Error: fmt::Debug, + { + } + + /// Message type wrapper for signalling end of message stream. + pub enum Message { + /// Message item. + Item(T), + + /// Signal from service to flush all messages and stop processing. + Close, + } + + pin_project! { + /// A future that reads frames from a [`Framed`] object and passes them to a [`Service`]. + pub struct Dispatcher + where + S: Service<::Item, Response = I>, + S::Error: 'static, + S::Future: 'static, + T: AsyncRead, + T: AsyncWrite, + U: Encoder, + U: Decoder, + I: 'static, + >::Error: fmt::Debug, + { + service: S, + state: State, + #[pin] + framed: Framed, + rx: mpsc::Receiver, S::Error>>, + tx: mpsc::Sender, S::Error>>, + } + } + + enum State + where + S: Service<::Item>, + U: Encoder + Decoder, + { + Processing, + Error(DispatcherError), + FramedError(DispatcherError), + FlushAndStop, + Stopping, + } + + impl State + where + S: Service<::Item>, + U: Encoder + Decoder, + { + fn take_error(&mut self) -> DispatcherError { + match mem::replace(self, State::Processing) { + State::Error(err) => err, + _ => panic!(), + } + } + + fn take_framed_error(&mut self) -> DispatcherError { + match mem::replace(self, State::Processing) { + State::FramedError(err) => err, + _ => panic!(), + } + } + } + + impl Dispatcher + where + S: Service<::Item, Response = I>, + S::Error: 'static, + S::Future: 'static, + T: AsyncRead + AsyncWrite, + U: Decoder + Encoder, + I: 'static, + ::Error: fmt::Debug, + >::Error: fmt::Debug, + { + /// Create new `Dispatcher`. + pub fn new(framed: Framed, service: F) -> Self + where + F: IntoService::Item>, + { + let (tx, rx) = mpsc::channel(); + Dispatcher { + framed, + rx, + tx, + service: service.into_service(), + state: State::Processing, + } + } + + /// Construct new `Dispatcher` instance with customer `mpsc::Receiver` + pub fn with_rx( + framed: Framed, + service: F, + rx: mpsc::Receiver, S::Error>>, + ) -> Self + where + F: IntoService::Item>, + { + let tx = rx.sender(); + Dispatcher { + framed, + rx, + tx, + service: service.into_service(), + state: State::Processing, + } + } + + /// Get sender handle. + pub fn tx(&self) -> mpsc::Sender, S::Error>> { + self.tx.clone() + } + + /// Get reference to a service wrapped by `Dispatcher` instance. + pub fn service(&self) -> &S { + &self.service + } + + /// Get mutable reference to a service wrapped by `Dispatcher` instance. + pub fn service_mut(&mut self) -> &mut S { + &mut self.service + } + + /// Get reference to a framed instance wrapped by `Dispatcher` instance. + pub fn framed(&self) -> &Framed { + &self.framed + } + + /// Get mutable reference to a framed instance wrapped by `Dispatcher` instance. + pub fn framed_mut(&mut self) -> &mut Framed { + &mut self.framed + } + + /// Read from framed object. + fn poll_read(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> bool + where + S: Service<::Item, Response = I>, + S::Error: 'static, + S::Future: 'static, + T: AsyncRead + AsyncWrite, + U: Decoder + Encoder, + I: 'static, + >::Error: fmt::Debug, + { + loop { + let this = self.as_mut().project(); + match this.service.poll_ready(cx) { + Poll::Ready(Ok(_)) => { + let item = match this.framed.next_item(cx) { + Poll::Ready(Some(Ok(el))) => el, + Poll::Ready(Some(Err(err))) => { + *this.state = + State::FramedError(DispatcherError::Decoder(err)); + return true; + } + Poll::Pending => return false, + Poll::Ready(None) => { + *this.state = State::Stopping; + return true; + } + }; + + let tx = this.tx.clone(); + let fut = this.service.call(item); + actix_rt::spawn(async move { + let item = fut.await; + let _ = tx.send(item.map(Message::Item)); + }); + } + Poll::Pending => return false, + Poll::Ready(Err(err)) => { + *this.state = State::Error(DispatcherError::Service(err)); + return true; + } + } + } + } + + /// Write to framed object. + fn poll_write(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> bool + where + S: Service<::Item, Response = I>, + S::Error: 'static, + S::Future: 'static, + T: AsyncRead + AsyncWrite, + U: Decoder + Encoder, + I: 'static, + >::Error: fmt::Debug, + { + loop { + let mut this = self.as_mut().project(); + while !this.framed.is_write_buf_full() { + match Pin::new(&mut this.rx).poll_next(cx) { + Poll::Ready(Some(Ok(Message::Item(msg)))) => { + if let Err(err) = this.framed.as_mut().write(msg) { + *this.state = + State::FramedError(DispatcherError::Encoder(err)); + return true; + } + } + Poll::Ready(Some(Ok(Message::Close))) => { + *this.state = State::FlushAndStop; + return true; + } + Poll::Ready(Some(Err(err))) => { + *this.state = State::Error(DispatcherError::Service(err)); + return true; + } + Poll::Ready(None) | Poll::Pending => break, + } + } + + if !this.framed.is_write_buf_empty() { + match this.framed.flush(cx) { + Poll::Pending => break, + Poll::Ready(Ok(_)) => {} + Poll::Ready(Err(err)) => { + debug!("Error sending data: {:?}", err); + *this.state = + State::FramedError(DispatcherError::Encoder(err)); + return true; + } + } + } else { + break; + } + } + + false + } + } + + impl Future for Dispatcher + where + S: Service<::Item, Response = I>, + S::Error: 'static, + S::Future: 'static, + T: AsyncRead + AsyncWrite, + U: Decoder + Encoder, + I: 'static, + >::Error: fmt::Debug, + ::Error: fmt::Debug, + { + type Output = Result<(), DispatcherError>; + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + loop { + let this = self.as_mut().project(); + + return match this.state { + State::Processing => { + if self.as_mut().poll_read(cx) || self.as_mut().poll_write(cx) { + continue; + } else { + Poll::Pending + } + } + State::Error(_) => { + // flush write buffer + if !this.framed.is_write_buf_empty() + && this.framed.flush(cx).is_pending() + { + return Poll::Pending; + } + Poll::Ready(Err(this.state.take_error())) + } + State::FlushAndStop => { + if !this.framed.is_write_buf_empty() { + this.framed.flush(cx).map(|res| { + if let Err(err) = res { + debug!("Error sending data: {:?}", err); + } + + Ok(()) + }) + } else { + Poll::Ready(Ok(())) + } + } + State::FramedError(_) => { + Poll::Ready(Err(this.state.take_framed_error())) + } + State::Stopping => Poll::Ready(Ok(())), + }; + } + } + } +} diff --git a/actix-http/tests/test_client.rs b/actix-http/tests/test_client.rs index 758e39745..b5f8d54b9 100644 --- a/actix-http/tests/test_client.rs +++ b/actix-http/tests/test_client.rs @@ -3,11 +3,9 @@ use actix_http::{ }; use actix_http_test::test_server; use actix_service::ServiceFactoryExt; +use actix_utils::future; use bytes::Bytes; -use futures_util::{ - future::{self, ok}, - StreamExt as _, -}; +use futures_util::StreamExt as _; const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ Hello World Hello World Hello World Hello World Hello World \ @@ -63,7 +61,7 @@ async fn test_h1_v2() { async fn test_connection_close() { let srv = test_server(move || { HttpService::build() - .finish(|_| ok::<_, ()>(Response::Ok().body(STR))) + .finish(|_| future::ok::<_, ()>(Response::Ok().body(STR))) .tcp() .map(|_| ()) }) @@ -79,9 +77,9 @@ async fn test_with_query_parameter() { HttpService::build() .finish(|req: Request| { if req.uri().query().unwrap().contains("qp=") { - ok::<_, ()>(Response::Ok().finish()) + future::ok::<_, ()>(Response::Ok().finish()) } else { - ok::<_, ()>(Response::BadRequest().finish()) + future::ok::<_, ()>(Response::BadRequest().finish()) } }) .tcp() diff --git a/actix-http/tests/test_openssl.rs b/actix-http/tests/test_openssl.rs index 49a68a60d..c9cfa7d18 100644 --- a/actix-http/tests/test_openssl.rs +++ b/actix-http/tests/test_openssl.rs @@ -11,12 +11,10 @@ use actix_http::HttpMessage; use actix_http::{body, Error, HttpService, Request, Response}; use actix_http_test::test_server; use actix_service::{fn_service, ServiceFactoryExt}; +use actix_utils::future::{err, ok, ready}; use bytes::{Bytes, BytesMut}; use futures_core::Stream; -use futures_util::{ - future::{err, ok, ready}, - stream::{once, StreamExt as _}, -}; +use futures_util::stream::{once, StreamExt as _}; use openssl::{ pkey::PKey, ssl::{SslAcceptor, SslMethod}, diff --git a/actix-http/tests/test_rustls.rs b/actix-http/tests/test_rustls.rs index 7a3cb1473..5b8ba6582 100644 --- a/actix-http/tests/test_rustls.rs +++ b/actix-http/tests/test_rustls.rs @@ -8,10 +8,10 @@ use actix_http::http::{Method, StatusCode, Version}; use actix_http::{body, error, Error, HttpService, Request, Response}; use actix_http_test::test_server; use actix_service::{fn_factory_with_config, fn_service}; +use actix_utils::future::{err, ok}; use bytes::{Bytes, BytesMut}; use futures_core::Stream; -use futures_util::future::{self, err, ok}; use futures_util::stream::{once, StreamExt as _}; use rustls::{ internal::pemfile::{certs, pkcs8_private_keys}, @@ -51,7 +51,7 @@ fn tls_config() -> RustlsServerConfig { async fn test_h1() -> io::Result<()> { let srv = test_server(move || { HttpService::build() - .h1(|_| future::ok::<_, Error>(Response::Ok().finish())) + .h1(|_| ok::<_, Error>(Response::Ok().finish())) .rustls(tls_config()) }) .await; @@ -65,7 +65,7 @@ async fn test_h1() -> io::Result<()> { async fn test_h2() -> io::Result<()> { let srv = test_server(move || { HttpService::build() - .h2(|_| future::ok::<_, Error>(Response::Ok().finish())) + .h2(|_| ok::<_, Error>(Response::Ok().finish())) .rustls(tls_config()) }) .await; @@ -82,7 +82,7 @@ async fn test_h1_1() -> io::Result<()> { .h1(|req: Request| { assert!(req.peer_addr().is_some()); assert_eq!(req.version(), Version::HTTP_11); - future::ok::<_, Error>(Response::Ok().finish()) + ok::<_, Error>(Response::Ok().finish()) }) .rustls(tls_config()) }) @@ -100,7 +100,7 @@ async fn test_h2_1() -> io::Result<()> { .finish(|req: Request| { assert!(req.peer_addr().is_some()); assert_eq!(req.version(), Version::HTTP_2); - future::ok::<_, Error>(Response::Ok().finish()) + ok::<_, Error>(Response::Ok().finish()) }) .rustls(tls_config()) }) @@ -144,7 +144,7 @@ async fn test_h2_content_length() { StatusCode::OK, StatusCode::NOT_FOUND, ]; - future::ok::<_, ()>(Response::new(statuses[indx])) + ok::<_, ()>(Response::new(statuses[indx])) }) .rustls(tls_config()) }) @@ -213,7 +213,7 @@ async fn test_h2_headers() { TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST ", )); } - future::ok::<_, ()>(config.body(data.clone())) + ok::<_, ()>(config.body(data.clone())) }) .rustls(tls_config()) }).await; @@ -252,7 +252,7 @@ const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ async fn test_h2_body2() { let mut srv = test_server(move || { HttpService::build() - .h2(|_| future::ok::<_, ()>(Response::Ok().body(STR))) + .h2(|_| ok::<_, ()>(Response::Ok().body(STR))) .rustls(tls_config()) }) .await; diff --git a/actix-http/tests/test_server.rs b/actix-http/tests/test_server.rs index 6d145400c..9084a597f 100644 --- a/actix-http/tests/test_server.rs +++ b/actix-http/tests/test_server.rs @@ -5,9 +5,10 @@ use std::{net, thread}; use actix_http_test::test_server; use actix_rt::time::sleep; use actix_service::fn_service; +use actix_utils::future::{err, ok, ready}; use bytes::Bytes; -use futures_util::future::{self, err, ok, ready, FutureExt}; use futures_util::stream::{once, StreamExt as _}; +use futures_util::FutureExt as _; use regex::Regex; use actix_http::HttpMessage; @@ -24,7 +25,7 @@ async fn test_h1() { .client_disconnect(1000) .h1(|req: Request| { assert!(req.peer_addr().is_some()); - future::ok::<_, ()>(Response::Ok().finish()) + ok::<_, ()>(Response::Ok().finish()) }) .tcp() }) @@ -44,7 +45,7 @@ async fn test_h1_2() { .finish(|req: Request| { assert!(req.peer_addr().is_some()); assert_eq!(req.version(), http::Version::HTTP_11); - future::ok::<_, ()>(Response::Ok().finish()) + ok::<_, ()>(Response::Ok().finish()) }) .tcp() }) @@ -65,7 +66,7 @@ async fn test_expect_continue() { err(error::ErrorPreconditionFailed("error")) } })) - .finish(|_| future::ok::<_, ()>(Response::Ok().finish())) + .finish(|_| ok::<_, ()>(Response::Ok().finish())) .tcp() }) .await; @@ -96,7 +97,7 @@ async fn test_expect_continue_h1() { } }) })) - .h1(fn_service(|_| future::ok::<_, ()>(Response::Ok().finish()))) + .h1(fn_service(|_| ok::<_, ()>(Response::Ok().finish()))) .tcp() }) .await; @@ -175,7 +176,7 @@ async fn test_slow_request() { let srv = test_server(|| { HttpService::build() .client_timeout(100) - .finish(|_| future::ok::<_, ()>(Response::Ok().finish())) + .finish(|_| ok::<_, ()>(Response::Ok().finish())) .tcp() }) .await; @@ -191,7 +192,7 @@ async fn test_slow_request() { async fn test_http1_malformed_request() { let srv = test_server(|| { HttpService::build() - .h1(|_| future::ok::<_, ()>(Response::Ok().finish())) + .h1(|_| ok::<_, ()>(Response::Ok().finish())) .tcp() }) .await; @@ -207,7 +208,7 @@ async fn test_http1_malformed_request() { async fn test_http1_keepalive() { let srv = test_server(|| { HttpService::build() - .h1(|_| future::ok::<_, ()>(Response::Ok().finish())) + .h1(|_| ok::<_, ()>(Response::Ok().finish())) .tcp() }) .await; @@ -229,7 +230,7 @@ async fn test_http1_keepalive_timeout() { let srv = test_server(|| { HttpService::build() .keep_alive(1) - .h1(|_| future::ok::<_, ()>(Response::Ok().finish())) + .h1(|_| ok::<_, ()>(Response::Ok().finish())) .tcp() }) .await; @@ -250,7 +251,7 @@ async fn test_http1_keepalive_timeout() { async fn test_http1_keepalive_close() { let srv = test_server(|| { HttpService::build() - .h1(|_| future::ok::<_, ()>(Response::Ok().finish())) + .h1(|_| ok::<_, ()>(Response::Ok().finish())) .tcp() }) .await; @@ -271,7 +272,7 @@ async fn test_http1_keepalive_close() { async fn test_http10_keepalive_default_close() { let srv = test_server(|| { HttpService::build() - .h1(|_| future::ok::<_, ()>(Response::Ok().finish())) + .h1(|_| ok::<_, ()>(Response::Ok().finish())) .tcp() }) .await; @@ -291,7 +292,7 @@ async fn test_http10_keepalive_default_close() { async fn test_http10_keepalive() { let srv = test_server(|| { HttpService::build() - .h1(|_| future::ok::<_, ()>(Response::Ok().finish())) + .h1(|_| ok::<_, ()>(Response::Ok().finish())) .tcp() }) .await; @@ -319,7 +320,7 @@ async fn test_http1_keepalive_disabled() { let srv = test_server(|| { HttpService::build() .keep_alive(KeepAlive::Disabled) - .h1(|_| future::ok::<_, ()>(Response::Ok().finish())) + .h1(|_| ok::<_, ()>(Response::Ok().finish())) .tcp() }) .await; @@ -354,7 +355,7 @@ async fn test_content_length() { StatusCode::OK, StatusCode::NOT_FOUND, ]; - future::ok::<_, ()>(Response::new(statuses[indx])) + ok::<_, ()>(Response::new(statuses[indx])) }) .tcp() }) @@ -409,7 +410,7 @@ async fn test_h1_headers() { TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST ", )); } - future::ok::<_, ()>(builder.body(data.clone())) + ok::<_, ()>(builder.body(data.clone())) }).tcp() }).await; @@ -645,7 +646,7 @@ async fn test_h1_response_http_error_handling() { async fn test_h1_service_error() { let mut srv = test_server(|| { HttpService::build() - .h1(|_| future::err::(error::ErrorBadRequest("error"))) + .h1(|_| err::(error::ErrorBadRequest("error"))) .tcp() }) .await; @@ -667,7 +668,7 @@ async fn test_h1_on_connect() { }) .h1(|req: Request| { assert!(req.extensions().contains::()); - future::ok::<_, ()>(Response::Ok().finish()) + ok::<_, ()>(Response::Ok().finish()) }) .tcp() }) diff --git a/actix-http/tests/test_ws.rs b/actix-http/tests/test_ws.rs index 51238215a..9a2e57711 100644 --- a/actix-http/tests/test_ws.rs +++ b/actix-http/tests/test_ws.rs @@ -9,11 +9,12 @@ use actix_codec::{AsyncRead, AsyncWrite, Framed}; use actix_http::{body, h1, ws, Error, HttpService, Request, Response}; use actix_http_test::test_server; use actix_service::{fn_factory, Service}; -use actix_utils::dispatcher::Dispatcher; +use actix_utils::future; use bytes::Bytes; -use futures_util::future; use futures_util::{SinkExt as _, StreamExt as _}; +use crate::ws::Dispatcher; + struct WsService(Arc, Cell)>>); impl WsService { @@ -58,7 +59,7 @@ where .await .unwrap(); - Dispatcher::new(framed.replace_codec(ws::Codec::new()), service) + Dispatcher::with(framed.replace_codec(ws::Codec::new()), service) .await .map_err(|_| panic!()) }; diff --git a/actix-multipart/Cargo.toml b/actix-multipart/Cargo.toml index 607e90849..cb00a0e74 100644 --- a/actix-multipart/Cargo.toml +++ b/actix-multipart/Cargo.toml @@ -17,12 +17,14 @@ path = "src/lib.rs" [dependencies] actix-web = { version = "4.0.0-beta.4", default-features = false } -actix-utils = "3.0.0-beta.2" +actix-utils = "3.0.0-beta.4" bytes = "1" derive_more = "0.99.5" -httparse = "1.3" +futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] } futures-util = { version = "0.3.7", default-features = false, features = ["alloc"] } +httparse = "1.3" +local-waker = "0.1" log = "0.4" mime = "0.3" twoway = "0.2" diff --git a/actix-multipart/src/extractor.rs b/actix-multipart/src/extractor.rs index bffbe8a1b..c87f8cc2d 100644 --- a/actix-multipart/src/extractor.rs +++ b/actix-multipart/src/extractor.rs @@ -1,21 +1,22 @@ //! Multipart payload support + +use actix_utils::future::{ready, Ready}; use actix_web::{dev::Payload, Error, FromRequest, HttpRequest}; -use futures_util::future::{ok, Ready}; use crate::server::Multipart; -/// Get request's payload as multipart stream +/// Get request's payload as multipart stream. /// /// Content-type: multipart/form-data; /// /// ## Server example /// /// ``` -/// use futures_util::stream::{Stream, StreamExt}; /// use actix_web::{web, HttpResponse, Error}; -/// use actix_multipart as mp; +/// use actix_multipart::Multipart; +/// use futures_util::stream::StreamExt as _; /// -/// async fn index(mut payload: mp::Multipart) -> Result { +/// async fn index(mut payload: Multipart) -> Result { /// // iterate over multipart stream /// while let Some(item) = payload.next().await { /// let mut field = item?; @@ -25,9 +26,9 @@ use crate::server::Multipart; /// println!("-- CHUNK: \n{:?}", std::str::from_utf8(&chunk?)); /// } /// } +/// /// Ok(HttpResponse::Ok().into()) /// } -/// # fn main() {} /// ``` impl FromRequest for Multipart { type Error = Error; @@ -36,9 +37,9 @@ impl FromRequest for Multipart { #[inline] fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future { - ok(match Multipart::boundary(req.headers()) { + ready(Ok(match Multipart::boundary(req.headers()) { Ok(boundary) => Multipart::from_boundary(boundary, payload.take()), Err(err) => Multipart::from_error(err), - }) + })) } } diff --git a/actix-multipart/src/server.rs b/actix-multipart/src/server.rs index d9ff3d574..b7d251537 100644 --- a/actix-multipart/src/server.rs +++ b/actix-multipart/src/server.rs @@ -1,4 +1,4 @@ -//! Multipart payload support +//! Multipart response payload support. use std::cell::{Cell, RefCell, RefMut}; use std::convert::TryFrom; @@ -8,12 +8,12 @@ use std::rc::Rc; use std::task::{Context, Poll}; use std::{cmp, fmt}; -use bytes::{Bytes, BytesMut}; -use futures_util::stream::{LocalBoxStream, Stream, StreamExt}; - -use actix_utils::task::LocalWaker; use actix_web::error::{ParseError, PayloadError}; use actix_web::http::header::{self, ContentDisposition, HeaderMap, HeaderName, HeaderValue}; +use bytes::{Bytes, BytesMut}; +use futures_core::stream::{LocalBoxStream, Stream}; +use futures_util::stream::StreamExt as _; +use local_waker::LocalWaker; use crate::error::MultipartError; diff --git a/actix-web-actors/tests/test_ws.rs b/actix-web-actors/tests/test_ws.rs index 7999beed9..4ffedb2ef 100644 --- a/actix-web-actors/tests/test_ws.rs +++ b/actix-web-actors/tests/test_ws.rs @@ -5,7 +5,7 @@ use actix_web::{ }; use actix_web_actors::*; use bytes::Bytes; -use futures_util::{SinkExt, StreamExt}; +use futures_util::{SinkExt as _, StreamExt as _}; struct Ws; diff --git a/actix-web-codegen/Cargo.toml b/actix-web-codegen/Cargo.toml index fdfb9f6ba..a513b820b 100644 --- a/actix-web-codegen/Cargo.toml +++ b/actix-web-codegen/Cargo.toml @@ -21,6 +21,7 @@ proc-macro2 = "1" [dev-dependencies] actix-rt = "2.2" actix-web = "4.0.0-beta.4" -futures-util = { version = "0.3.7", default-features = false } +actix-utils = "3.0.0-beta.4" +futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] } trybuild = "1" rustversion = "1" diff --git a/actix-web-codegen/tests/test_macro.rs b/actix-web-codegen/tests/test_macro.rs index 0cbb64ba5..4e06e15ed 100644 --- a/actix-web-codegen/tests/test_macro.rs +++ b/actix-web-codegen/tests/test_macro.rs @@ -1,11 +1,12 @@ use std::future::Future; use std::task::{Context, Poll}; +use actix_utils::future; use actix_web::dev::{Service, ServiceRequest, ServiceResponse, Transform}; use actix_web::http::header::{HeaderName, HeaderValue}; use actix_web::{http, test, web::Path, App, Error, HttpResponse, Responder}; use actix_web_codegen::{connect, delete, get, head, options, patch, post, put, route, trace}; -use futures_util::future::{self, LocalBoxFuture}; +use futures_core::future::LocalBoxFuture; // Make sure that we can name function as 'config' #[get("/config")] diff --git a/awc/Cargo.toml b/awc/Cargo.toml index cc6841606..d0e781e55 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -69,7 +69,7 @@ tls-rustls = { version = "0.19.0", package = "rustls", optional = true, features actix-web = { version = "4.0.0-beta.4", features = ["openssl"] } actix-http = { version = "3.0.0-beta.4", features = ["openssl"] } actix-http-test = { version = "3.0.0-beta.3", features = ["openssl"] } -actix-utils = "3.0.0-beta.1" +actix-utils = "3.0.0-beta.4" actix-server = "2.0.0-beta.3" actix-tls = { version = "3.0.0-beta.5", features = ["openssl", "rustls"] } diff --git a/awc/src/ws.rs b/awc/src/ws.rs index df25b7289..8458d3e31 100644 --- a/awc/src/ws.rs +++ b/awc/src/ws.rs @@ -6,7 +6,7 @@ //! //! ```no_run //! use awc::{Client, ws}; -//! use futures_util::{sink::SinkExt, stream::StreamExt}; +//! use futures_util::{sink::SinkExt as _, stream::StreamExt as _}; //! //! #[actix_rt::main] //! async fn main() { diff --git a/awc/tests/test_client.rs b/awc/tests/test_client.rs index 50d2b5eac..9682bc254 100644 --- a/awc/tests/test_client.rs +++ b/awc/tests/test_client.rs @@ -5,12 +5,13 @@ use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::Arc; use std::time::Duration; +use actix_utils::future::ok; use brotli2::write::BrotliEncoder; use bytes::Bytes; use flate2::read::GzDecoder; use flate2::write::GzEncoder; use flate2::Compression; -use futures_util::{future::ok, stream}; +use futures_util::stream; use rand::Rng; use actix_http::{ @@ -159,7 +160,7 @@ async fn test_timeout_override() { #[actix_rt::test] async fn test_response_timeout() { - use futures_util::stream::{once, StreamExt}; + use futures_util::stream::{once, StreamExt as _}; let srv = test::start(|| { App::new().service(web::resource("/").route(web::to(|| async { diff --git a/awc/tests/test_rustls_client.rs b/awc/tests/test_rustls_client.rs index 464edfe89..080eaf792 100644 --- a/awc/tests/test_rustls_client.rs +++ b/awc/tests/test_rustls_client.rs @@ -13,8 +13,8 @@ use std::{ use actix_http::HttpService; use actix_http_test::test_server; use actix_service::{map_config, pipeline_factory, ServiceFactoryExt}; +use actix_utils::future::ok; use actix_web::{dev::AppConfig, http::Version, web, App, HttpResponse}; -use futures_util::future::ok; use rustls::internal::pemfile::{certs, pkcs8_private_keys}; use rustls::{ClientConfig, NoClientAuth, ServerConfig}; diff --git a/awc/tests/test_ssl_client.rs b/awc/tests/test_ssl_client.rs index 3079aaf5e..502223401 100644 --- a/awc/tests/test_ssl_client.rs +++ b/awc/tests/test_ssl_client.rs @@ -8,9 +8,9 @@ use std::sync::Arc; use actix_http::HttpService; use actix_http_test::test_server; use actix_service::{map_config, pipeline_factory, ServiceFactoryExt}; +use actix_utils::future::ok; use actix_web::http::Version; use actix_web::{dev::AppConfig, web, App, HttpResponse}; -use futures_util::future::ok; use openssl::{ pkey::PKey, ssl::{SslAcceptor, SslConnector, SslMethod, SslVerifyMode}, diff --git a/awc/tests/test_ws.rs b/awc/tests/test_ws.rs index 1b3f780dc..3f19ac4e8 100644 --- a/awc/tests/test_ws.rs +++ b/awc/tests/test_ws.rs @@ -3,9 +3,9 @@ use std::io; use actix_codec::Framed; use actix_http::{body::BodySize, h1, ws, Error, HttpService, Request, Response}; use actix_http_test::test_server; +use actix_utils::future::ok; use bytes::Bytes; -use futures_util::future::ok; -use futures_util::{SinkExt, StreamExt}; +use futures_util::{SinkExt as _, StreamExt as _}; async fn ws_service(req: ws::Frame) -> Result { match req { diff --git a/benches/responder.rs b/benches/responder.rs index 8cfdbd3ea..0dfc8cd18 100644 --- a/benches/responder.rs +++ b/benches/responder.rs @@ -1,12 +1,12 @@ -use std::future::Future; -use std::time::Instant; +use std::{future::Future, time::Instant}; use actix_http::Response; +use actix_utils::future::{ready, Ready}; use actix_web::http::StatusCode; use actix_web::test::TestRequest; use actix_web::{error, Error, HttpRequest, HttpResponse, Responder}; use criterion::{criterion_group, criterion_main, Criterion}; -use futures_util::future::{ready, Either, Ready}; +use futures_util::future::{join_all, Either}; // responder simulate the old responder trait. trait FutureResponder { @@ -79,7 +79,7 @@ fn future_responder(c: &mut Criterion) { .await }); - let futs = futures_util::future::join_all(futs); + let futs = join_all(futs); let start = Instant::now(); diff --git a/docs/graphs/net-only.dot b/docs/graphs/net-only.dot index 84227cdb0..bee0185ab 100644 --- a/docs/graphs/net-only.dot +++ b/docs/graphs/net-only.dot @@ -1,19 +1,37 @@ digraph { + rankdir=TB + subgraph cluster_net { - label="actix/actix-net"; + label="actix-net" "actix-codec" "actix-macros" "actix-rt" "actix-server" "actix-service" "actix-tls" "actix-tracing" "actix-utils" "actix-router" - "local-channel" "local-waker" + } + + subgraph cluster_other { + label="other actix owned crates" + { rank=same; "local-channel" "local-waker" "bytestring" } } - "actix-codec" -> { "actix-rt" "actix-service" "local-channel" "tokio" } + subgraph cluster_tokio { + label="tokio" + "tokio" "tokio-util" + } + + "actix-codec" -> { "tokio" } + "actix-codec" -> { "tokio-util" }[color=red] "actix-utils" -> { "local-waker" } "actix-tracing" -> { "actix-service" } "actix-tls" -> { "actix-service" "actix-codec" "actix-utils" "actix-rt" } - "actix-server" -> { "actix-service" "actix-rt" "actix-codec" "actix-utils" "tokio" } + "actix-tls" -> { "tokio-util" }[color="#009900"] + "actix-server" -> { "actix-service" "actix-rt" "actix-utils" "tokio" } "actix-rt" -> { "actix-macros" "tokio" } + "actix-router" -> { "bytestring" } "local-channel" -> { "local-waker" } - "tokio" [fontcolor = darkgreen] + // invisible edges to force nicer layout + edge [style=invis] + "actix-macros" -> "tokio" + "actix-service" -> "bytestring" + "actix-macros" -> "bytestring" } diff --git a/src/app.rs b/src/app.rs index f2c6bce8a..357d45eeb 100644 --- a/src/app.rs +++ b/src/app.rs @@ -10,7 +10,7 @@ use actix_service::boxed::{self, BoxServiceFactory}; use actix_service::{ apply, apply_fn_factory, IntoServiceFactory, ServiceFactory, ServiceFactoryExt, Transform, }; -use futures_util::future::FutureExt; +use futures_util::future::FutureExt as _; use crate::app_service::{AppEntry, AppInit, AppRoutingFactory}; use crate::config::ServiceConfig; @@ -465,8 +465,8 @@ where #[cfg(test)] mod tests { use actix_service::Service; + use actix_utils::future::{err, ok}; use bytes::Bytes; - use futures_util::future::{err, ok}; use super::*; use crate::http::{header, HeaderValue, Method, StatusCode}; diff --git a/src/data.rs b/src/data.rs index 56ecdb8ae..3bc54a465 100644 --- a/src/data.rs +++ b/src/data.rs @@ -4,7 +4,8 @@ use std::sync::Arc; use actix_http::error::{Error, ErrorInternalServerError}; use actix_http::Extensions; -use futures_util::future::{err, ok, LocalBoxFuture, Ready}; +use actix_utils::future::{err, ok, Ready}; +use futures_core::future::LocalBoxFuture; use serde::Serialize; use crate::dev::Payload; @@ -147,10 +148,10 @@ impl DataFactory for Data { #[cfg(test)] mod tests { - use actix_service::Service; use std::sync::atomic::{AtomicUsize, Ordering}; use super::*; + use crate::dev::Service; use crate::http::StatusCode; use crate::test::{self, init_service, TestRequest}; use crate::{web, App, HttpResponse}; diff --git a/src/extract.rs b/src/extract.rs index 8851481e3..80f2384a0 100644 --- a/src/extract.rs +++ b/src/extract.rs @@ -6,10 +6,8 @@ use std::{ task::{Context, Poll}, }; -use futures_util::{ - future::{ready, Ready}, - ready, -}; +use actix_utils::future::{ready, Ready}; +use futures_core::ready; use crate::{dev::Payload, Error, HttpRequest}; diff --git a/src/handler.rs b/src/handler.rs index 7e3c5f47e..e005a96a6 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -5,8 +5,8 @@ use std::task::{Context, Poll}; use actix_http::{Error, Response}; use actix_service::{Service, ServiceFactory}; -use futures_util::future::{ready, Ready}; -use futures_util::ready; +use actix_utils::future::{ready, Ready}; +use futures_core::ready; use pin_project::pin_project; use crate::extract::FromRequest; diff --git a/src/middleware/compress.rs b/src/middleware/compress.rs index a397bccd6..6a56e6de0 100644 --- a/src/middleware/compress.rs +++ b/src/middleware/compress.rs @@ -16,8 +16,8 @@ use actix_http::{ Error, }; use actix_service::{Service, Transform}; +use actix_utils::future::{ok, Ready}; use futures_core::ready; -use futures_util::future::{ok, Ready}; use pin_project::pin_project; use crate::{ diff --git a/src/middleware/condition.rs b/src/middleware/condition.rs index dd599a0cb..d1ba7ee4d 100644 --- a/src/middleware/condition.rs +++ b/src/middleware/condition.rs @@ -3,7 +3,9 @@ use std::task::{Context, Poll}; use actix_service::{Service, Transform}; -use futures_util::future::{Either, FutureExt, LocalBoxFuture}; +use actix_utils::future::Either; +use futures_core::future::LocalBoxFuture; +use futures_util::future::FutureExt as _; /// Middleware for conditionally enabling other middleware. /// @@ -85,8 +87,8 @@ where fn call(&self, req: Req) -> Self::Future { match self { - ConditionMiddleware::Enable(service) => Either::Left(service.call(req)), - ConditionMiddleware::Disable(service) => Either::Right(service.call(req)), + ConditionMiddleware::Enable(service) => Either::left(service.call(req)), + ConditionMiddleware::Disable(service) => Either::right(service.call(req)), } } } @@ -94,7 +96,7 @@ where #[cfg(test)] mod tests { use actix_service::IntoService; - use futures_util::future::ok; + use actix_utils::future::ok; use super::*; use crate::{ diff --git a/src/middleware/default_headers.rs b/src/middleware/default_headers.rs index 12d70ab2c..d8a947aab 100644 --- a/src/middleware/default_headers.rs +++ b/src/middleware/default_headers.rs @@ -9,10 +9,8 @@ use std::{ task::{Context, Poll}, }; -use futures_util::{ - future::{ready, Ready}, - ready, -}; +use actix_utils::future::{ready, Ready}; +use futures_core::ready; use crate::{ dev::{Service, Transform}, @@ -188,7 +186,7 @@ where #[cfg(test)] mod tests { use actix_service::IntoService; - use futures_util::future::ok; + use actix_utils::future::ok; use super::*; use crate::{ diff --git a/src/middleware/err_handlers.rs b/src/middleware/err_handlers.rs index fddd87a99..88834f8ce 100644 --- a/src/middleware/err_handlers.rs +++ b/src/middleware/err_handlers.rs @@ -175,7 +175,8 @@ where #[cfg(test)] mod tests { use actix_service::IntoService; - use futures_util::future::{ok, FutureExt}; + use actix_utils::future::ok; + use futures_util::future::FutureExt as _; use super::*; use crate::http::{header::CONTENT_TYPE, HeaderValue, StatusCode}; diff --git a/src/middleware/logger.rs b/src/middleware/logger.rs index 8f5391757..3fd372117 100644 --- a/src/middleware/logger.rs +++ b/src/middleware/logger.rs @@ -13,8 +13,9 @@ use std::{ }; use actix_service::{Service, Transform}; +use actix_utils::future::{ok, Ready}; use bytes::Bytes; -use futures_util::future::{ok, Ready}; +use futures_core::ready; use log::{debug, warn}; use regex::{Regex, RegexSet}; use time::OffsetDateTime; @@ -269,7 +270,7 @@ where fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { let this = self.project(); - let res = match futures_util::ready!(this.fut.poll(cx)) { + let res = match ready!(this.fut.poll(cx)) { Ok(res) => res, Err(e) => return Poll::Ready(Err(e)), }; @@ -588,7 +589,7 @@ impl<'a> fmt::Display for FormatDisplay<'a> { #[cfg(test)] mod tests { use actix_service::{IntoService, Service, Transform}; - use futures_util::future::ok; + use actix_utils::future::ok; use super::*; use crate::http::{header, StatusCode}; diff --git a/src/middleware/normalize.rs b/src/middleware/normalize.rs index 2a97a047b..ec6c2a344 100644 --- a/src/middleware/normalize.rs +++ b/src/middleware/normalize.rs @@ -2,8 +2,8 @@ use actix_http::http::{PathAndQuery, Uri}; use actix_service::{Service, Transform}; +use actix_utils::future::{ready, Ready}; use bytes::Bytes; -use futures_util::future::{ready, Ready}; use regex::Regex; use crate::{ diff --git a/src/request.rs b/src/request.rs index 15c97345c..3fdbb13e1 100644 --- a/src/request.rs +++ b/src/request.rs @@ -5,7 +5,7 @@ use std::{fmt, net}; use actix_http::http::{HeaderMap, Method, Uri, Version}; use actix_http::{Error, Extensions, HttpMessage, Message, Payload, RequestHead}; use actix_router::{Path, Url}; -use futures_util::future::{ok, Ready}; +use actix_utils::future::{ok, Ready}; use smallvec::SmallVec; use crate::app_service::AppInitServiceState; diff --git a/src/request_data.rs b/src/request_data.rs index fc711d011..60471cbf9 100644 --- a/src/request_data.rs +++ b/src/request_data.rs @@ -1,7 +1,7 @@ use std::{any::type_name, ops::Deref}; use actix_http::error::{Error, ErrorInternalServerError}; -use futures_util::future; +use actix_utils::future::{err, ok, Ready}; use crate::{dev::Payload, FromRequest, HttpRequest}; @@ -67,11 +67,11 @@ impl Deref for ReqData { impl FromRequest for ReqData { type Config = (); type Error = Error; - type Future = future::Ready>; + type Future = Ready>; fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future { if let Some(st) = req.extensions().get::() { - future::ok(ReqData(st.clone())) + ok(ReqData(st.clone())) } else { log::debug!( "Failed to construct App-level ReqData extractor. \ @@ -79,7 +79,7 @@ impl FromRequest for ReqData { req.path(), type_name::(), ); - future::err(ErrorInternalServerError( + err(ErrorInternalServerError( "Missing expected request extension data", )) } diff --git a/src/resource.rs b/src/resource.rs index 8f356c76d..e868bb547 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -519,7 +519,7 @@ mod tests { use actix_rt::time::sleep; use actix_service::Service; - use futures_util::future::ok; + use actix_utils::future::ok; use crate::http::{header, HeaderValue, Method, StatusCode}; use crate::middleware::DefaultHeaders; diff --git a/src/scope.rs b/src/scope.rs index 693d6860f..3be6adb0c 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -575,8 +575,8 @@ impl ServiceFactory for ScopeEndpoint { #[cfg(test)] mod tests { use actix_service::Service; + use actix_utils::future::ok; use bytes::Bytes; - use futures_util::future::ok; use crate::dev::{Body, ResponseBody}; use crate::http::{header, HeaderValue, Method, StatusCode}; diff --git a/src/service.rs b/src/service.rs index 32f152f7d..570d88e7d 100644 --- a/src/service.rs +++ b/src/service.rs @@ -602,7 +602,7 @@ mod tests { use crate::test::{init_service, TestRequest}; use crate::{guard, http, web, App, HttpResponse}; use actix_service::Service; - use futures_util::future::ok; + use actix_utils::future::ok; #[actix_rt::test] async fn test_service() { diff --git a/src/test.rs b/src/test.rs index 18bf89cda..8ba019445 100644 --- a/src/test.rs +++ b/src/test.rs @@ -15,12 +15,12 @@ use actix_http::{ws, Extensions, HttpService, Request}; use actix_router::{Path, ResourceDef, Url}; use actix_rt::{time::sleep, System}; use actix_service::{map_config, IntoService, IntoServiceFactory, Service, ServiceFactory}; +use actix_utils::future::ok; use awc::error::PayloadError; use awc::{Client, ClientRequest, ClientResponse, Connector}; use bytes::{Bytes, BytesMut}; use futures_core::Stream; -use futures_util::future::ok; -use futures_util::StreamExt; +use futures_util::StreamExt as _; use serde::de::DeserializeOwned; use serde::Serialize; use socket2::{Domain, Protocol, Socket, Type}; diff --git a/src/types/either.rs b/src/types/either.rs index bbab48dec..210495e47 100644 --- a/src/types/either.rs +++ b/src/types/either.rs @@ -1,7 +1,8 @@ //! For either helper, see [`Either`]. use bytes::Bytes; -use futures_util::{future::LocalBoxFuture, FutureExt, TryFutureExt}; +use futures_core::future::LocalBoxFuture; +use futures_util::{FutureExt as _, TryFutureExt as _}; use crate::{ dev, diff --git a/src/types/form.rs b/src/types/form.rs index 57a742e38..0985bd945 100644 --- a/src/types/form.rs +++ b/src/types/form.rs @@ -12,10 +12,8 @@ use std::{ use actix_http::Payload; use bytes::BytesMut; use encoding_rs::{Encoding, UTF_8}; -use futures_util::{ - future::{FutureExt, LocalBoxFuture}, - StreamExt, -}; +use futures_core::future::LocalBoxFuture; +use futures_util::{FutureExt as _, StreamExt as _}; use serde::{de::DeserializeOwned, Serialize}; #[cfg(feature = "compress")] diff --git a/src/types/json.rs b/src/types/json.rs index d8ce3cb71..068dfeb2c 100644 --- a/src/types/json.rs +++ b/src/types/json.rs @@ -11,7 +11,7 @@ use std::{ }; use bytes::BytesMut; -use futures_util::{ready, stream::Stream}; +use futures_core::{ready, stream::Stream as _}; use serde::{de::DeserializeOwned, Serialize}; use actix_http::Payload; diff --git a/src/types/path.rs b/src/types/path.rs index 294df6cf2..90ee5296b 100644 --- a/src/types/path.rs +++ b/src/types/path.rs @@ -4,7 +4,7 @@ use std::{fmt, ops, sync::Arc}; use actix_http::error::{Error, ErrorNotFound}; use actix_router::PathDeserializer; -use futures_util::future::{ready, Ready}; +use actix_utils::future::{ready, Ready}; use serde::de; use crate::{dev::Payload, error::PathError, FromRequest, HttpRequest}; diff --git a/src/types/payload.rs b/src/types/payload.rs index 781347b84..f88800855 100644 --- a/src/types/payload.rs +++ b/src/types/payload.rs @@ -8,13 +8,10 @@ use std::{ }; use actix_http::error::{ErrorBadRequest, PayloadError}; +use actix_utils::future::{ready, Either, Ready}; use bytes::{Bytes, BytesMut}; use encoding_rs::{Encoding, UTF_8}; -use futures_core::stream::Stream; -use futures_util::{ - future::{ready, Either, ErrInto, Ready, TryFutureExt as _}, - ready, -}; +use futures_core::{ready, stream::Stream}; use mime::Mime; use crate::{dev, http::header, web, Error, FromRequest, HttpMessage, HttpRequest}; @@ -26,7 +23,7 @@ use crate::{dev, http::header, web, Error, FromRequest, HttpMessage, HttpRequest /// # Examples /// ``` /// use std::future::Future; -/// use futures_util::stream::{Stream, StreamExt}; +/// use futures_util::stream::StreamExt as _; /// use actix_web::{post, web}; /// /// // `body: web::Payload` parameter extracts raw payload stream from request @@ -91,7 +88,7 @@ impl FromRequest for Payload { impl FromRequest for Bytes { type Config = PayloadConfig; type Error = Error; - type Future = Either, Ready>>; + type Future = Either>>; #[inline] fn from_request(req: &HttpRequest, payload: &mut dev::Payload) -> Self::Future { @@ -99,12 +96,25 @@ impl FromRequest for Bytes { let cfg = PayloadConfig::from_req(req); if let Err(err) = cfg.check_mimetype(req) { - return Either::Right(ready(Err(err))); + return Either::right(ready(Err(err))); } - let limit = cfg.limit; - let fut = HttpMessageBody::new(req, payload).limit(limit); - Either::Left(fut.err_into()) + Either::left(BytesExtractFut { + body_fut: HttpMessageBody::new(req, payload).limit(cfg.limit), + }) + } +} + +/// Future for `Bytes` extractor. +pub struct BytesExtractFut { + body_fut: HttpMessageBody, +} + +impl<'a> Future for BytesExtractFut { + type Output = Result; + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + Pin::new(&mut self.body_fut).poll(cx).map_err(Into::into) } } @@ -135,21 +145,22 @@ impl FromRequest for String { // check content-type if let Err(err) = cfg.check_mimetype(req) { - return Either::Right(ready(Err(err))); + return Either::right(ready(Err(err))); } // check charset let encoding = match req.encoding() { Ok(enc) => enc, - Err(err) => return Either::Right(ready(Err(err.into()))), + Err(err) => return Either::right(ready(Err(err.into()))), }; let limit = cfg.limit; let body_fut = HttpMessageBody::new(req, payload).limit(limit); - Either::Left(StringExtractFut { body_fut, encoding }) + Either::left(StringExtractFut { body_fut, encoding }) } } +/// Future for `String` extractor. pub struct StringExtractFut { body_fut: HttpMessageBody, encoding: &'static Encoding, diff --git a/src/types/query.rs b/src/types/query.rs index 79af32581..b6c025ef3 100644 --- a/src/types/query.rs +++ b/src/types/query.rs @@ -2,7 +2,7 @@ use std::{fmt, ops, sync::Arc}; -use futures_util::future::{err, ok, Ready}; +use actix_utils::future::{err, ok, Ready}; use serde::de; use crate::{dev::Payload, error::QueryPayloadError, Error, FromRequest, HttpRequest}; diff --git a/src/types/readlines.rs b/src/types/readlines.rs index b8bdcc504..6c456e21c 100644 --- a/src/types/readlines.rs +++ b/src/types/readlines.rs @@ -177,7 +177,7 @@ where #[cfg(test)] mod tests { - use futures_util::stream::StreamExt; + use futures_util::stream::StreamExt as _; use super::*; use crate::test::TestRequest; diff --git a/tests/test_server.rs b/tests/test_server.rs index b35af657d..db9fe37db 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -20,7 +20,7 @@ use flate2::{ write::{GzEncoder, ZlibDecoder, ZlibEncoder}, Compression, }; -use futures_util::ready; +use futures_core::ready; #[cfg(feature = "openssl")] use openssl::{ pkey::PKey, From 50dc13f280573163d3c63b49fc1168004db0e870 Mon Sep 17 00:00:00 2001 From: Ibraheem Ahmed Date: Thu, 1 Apr 2021 11:42:18 -0400 Subject: [PATCH 32/79] move typed headers and implement FromRequest (#2094) Co-authored-by: Rob Ede --- CHANGES.md | 3 + Cargo.toml | 2 + actix-http/CHANGES.md | 2 + actix-http/src/header/mod.rs | 33 ------ .../{common => shared}/content_encoding.rs | 0 actix-http/src/header/shared/mod.rs | 6 +- actix-http/src/header/shared/quality_item.rs | 61 +++++++++- actix-http/src/header/utils.rs | 3 +- actix-http/src/response.rs | 16 +-- awc/CHANGES.md | 2 + awc/src/request.rs | 8 +- awc/src/response.rs | 24 ++-- awc/src/test.rs | 43 ++++--- .../common => src/http/header}/accept.rs | 28 ++--- .../http/header}/accept_charset.rs | 22 ++-- .../http/header}/accept_encoding.rs | 21 ++-- .../http/header}/accept_language.rs | 16 +-- .../common => src/http/header}/allow.rs | 18 +-- .../http/header}/cache_control.rs | 22 ++-- .../http/header}/content_disposition.rs | 11 +- .../http/header}/content_language.rs | 16 +-- .../http/header}/content_range.rs | 4 +- .../http/header}/content_type.rs | 16 +-- .../header/common => src/http/header}/date.rs | 10 +- .../shared => src/http/header}/encoding.rs | 0 .../shared => src/http/header}/entity.rs | 2 +- .../header/common => src/http/header}/etag.rs | 16 +-- .../common => src/http/header}/expires.rs | 10 +- .../common => src/http/header}/if_match.rs | 16 +-- .../http/header}/if_modified_since.rs | 10 +- .../http/header}/if_none_match.rs | 20 ++-- .../common => src/http/header}/if_range.rs | 17 +-- .../http/header}/if_unmodified_since.rs | 10 +- .../http/header}/last_modified.rs | 10 +- .../header/common => src/http/header}/mod.rs | 50 ++++++-- .../common => src/http/header}/range.rs | 4 +- src/http/mod.rs | 2 + src/lib.rs | 3 +- src/test.rs | 22 ++-- src/types/header.rs | 112 ++++++++++++++++++ src/types/mod.rs | 2 + 41 files changed, 445 insertions(+), 248 deletions(-) rename actix-http/src/header/{common => shared}/content_encoding.rs (100%) rename {actix-http/src/header/common => src/http/header}/accept.rs (93%) rename {actix-http/src/header/common => src/http/header}/accept_charset.rs (73%) rename {actix-http/src/header/common => src/http/header}/accept_encoding.rs (76%) rename {actix-http/src/header/common => src/http/header}/accept_language.rs (82%) rename {actix-http/src/header/common => src/http/header}/allow.rs (83%) rename {actix-http/src/header/common => src/http/header}/cache_control.rs (95%) rename {actix-http/src/header/common => src/http/header}/content_disposition.rs (99%) rename {actix-http/src/header/common => src/http/header}/content_language.rs (77%) rename {actix-http/src/header/common => src/http/header}/content_range.rs (99%) rename {actix-http/src/header/common => src/http/header}/content_type.rs (91%) rename {actix-http/src/header/common => src/http/header}/date.rs (83%) rename {actix-http/src/header/shared => src/http/header}/encoding.rs (100%) rename {actix-http/src/header/shared => src/http/header}/entity.rs (99%) rename {actix-http/src/header/common => src/http/header}/etag.rs (90%) rename {actix-http/src/header/common => src/http/header}/expires.rs (84%) rename {actix-http/src/header/common => src/http/header}/if_match.rs (86%) rename {actix-http/src/header/common => src/http/header}/if_modified_since.rs (84%) rename {actix-http/src/header/common => src/http/header}/if_none_match.rs (86%) rename {actix-http/src/header/common => src/http/header}/if_range.rs (89%) rename {actix-http/src/header/common => src/http/header}/if_unmodified_since.rs (85%) rename {actix-http/src/header/common => src/http/header}/last_modified.rs (84%) rename {actix-http/src/header/common => src/http/header}/mod.rs (92%) rename {actix-http/src/header/common => src/http/header}/range.rs (99%) create mode 100644 src/http/mod.rs create mode 100644 src/types/header.rs diff --git a/CHANGES.md b/CHANGES.md index 39743be2b..8e7b22f58 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -2,6 +2,7 @@ ## Unreleased - 2021-xx-xx ### Added +* `Header` extractor for extracting common HTTP headers in handlers. [#2094] * Added `TestServer::client_headers` method. [#2097] ### Fixed @@ -17,6 +18,8 @@ [#2067]: https://github.com/actix/actix-web/pull/2067 [#2093]: https://github.com/actix/actix-web/pull/2093 +[#2094]: https://github.com/actix/actix-web/pull/2094 +[#2097]: https://github.com/actix/actix-web/pull/2097 ## 4.0.0-beta.4 - 2021-03-09 diff --git a/Cargo.toml b/Cargo.toml index 15f9f5fe8..420e76405 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -97,6 +97,8 @@ either = "1.5.3" encoding_rs = "0.8" futures-core = { version = "0.3.7", default-features = false } futures-util = { version = "0.3.7", default-features = false } +language-tags = "0.2" +once_cell = "1.5" log = "0.4" mime = "0.3" pin-project = "1.0.0" diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 3a7b4f024..374c5f199 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -10,10 +10,12 @@ * `client::Connector` type now only have one generic type for `actix_service::Service`. [#2063] ### Removed +* Common HTTP headers were moved into actix-web. [2094] * `ResponseError` impl for `actix_utils::timeout::TimeoutError`. [#2127] [#2063]: https://github.com/actix/actix-web/pull/2063 [#2081]: https://github.com/actix/actix-web/pull/2081 +[#2094]: https://github.com/actix/actix-web/pull/2094 [#2127]: https://github.com/actix/actix-web/pull/2127 diff --git a/actix-http/src/header/mod.rs b/actix-http/src/header/mod.rs index 1100a959d..a6056ace4 100644 --- a/actix-http/src/header/mod.rs +++ b/actix-http/src/header/mod.rs @@ -1,9 +1,6 @@ //! Typed HTTP headers, pre-defined `HeaderName`s, traits for parsing and conversion, and other //! header utility methods. -use std::fmt; - -use bytes::{Bytes, BytesMut}; use percent_encoding::{AsciiSet, CONTROLS}; pub use http::header::*; @@ -16,11 +13,9 @@ mod into_pair; mod into_value; mod utils; -mod common; pub(crate) mod map; mod shared; -pub use self::common::*; #[doc(hidden)] pub use self::shared::*; @@ -41,34 +36,6 @@ pub trait Header: IntoHeaderValue { fn parse(msg: &T) -> Result; } -#[derive(Debug, Default)] -pub(crate) struct Writer { - buf: BytesMut, -} - -impl Writer { - fn new() -> Writer { - Writer::default() - } - - fn take(&mut self) -> Bytes { - self.buf.split().freeze() - } -} - -impl fmt::Write for Writer { - #[inline] - fn write_str(&mut self, s: &str) -> fmt::Result { - self.buf.extend_from_slice(s.as_bytes()); - Ok(()) - } - - #[inline] - fn write_fmt(&mut self, args: fmt::Arguments<'_>) -> fmt::Result { - fmt::write(self, args) - } -} - /// Convert `http::HeaderMap` to our `HeaderMap`. impl From for HeaderMap { fn from(mut map: http::HeaderMap) -> HeaderMap { diff --git a/actix-http/src/header/common/content_encoding.rs b/actix-http/src/header/shared/content_encoding.rs similarity index 100% rename from actix-http/src/header/common/content_encoding.rs rename to actix-http/src/header/shared/content_encoding.rs diff --git a/actix-http/src/header/shared/mod.rs b/actix-http/src/header/shared/mod.rs index 72161e46b..b8f9173f9 100644 --- a/actix-http/src/header/shared/mod.rs +++ b/actix-http/src/header/shared/mod.rs @@ -1,15 +1,13 @@ //! Originally taken from `hyper::header::shared`. mod charset; -mod encoding; -mod entity; +mod content_encoding; mod extended; mod httpdate; mod quality_item; pub use self::charset::Charset; -pub use self::encoding::Encoding; -pub use self::entity::EntityTag; +pub use self::content_encoding::ContentEncoding; pub use self::extended::{parse_extended_value, ExtendedValue}; pub use self::httpdate::HttpDate; pub use self::quality_item::{q, qitem, Quality, QualityItem}; diff --git a/actix-http/src/header/shared/quality_item.rs b/actix-http/src/header/shared/quality_item.rs index 01a3b988a..240a0afa2 100644 --- a/actix-http/src/header/shared/quality_item.rs +++ b/actix-http/src/header/shared/quality_item.rs @@ -193,21 +193,69 @@ where #[cfg(test)] mod tests { - use super::super::encoding::*; use super::*; + // copy of encoding from actix-web headers + #[derive(Clone, PartialEq, Debug)] + pub enum Encoding { + Chunked, + Brotli, + Gzip, + Deflate, + Compress, + Identity, + Trailers, + EncodingExt(String), + } + + impl fmt::Display for Encoding { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + use Encoding::*; + f.write_str(match *self { + Chunked => "chunked", + Brotli => "br", + Gzip => "gzip", + Deflate => "deflate", + Compress => "compress", + Identity => "identity", + Trailers => "trailers", + EncodingExt(ref s) => s.as_ref(), + }) + } + } + + impl str::FromStr for Encoding { + type Err = crate::error::ParseError; + fn from_str(s: &str) -> Result { + use Encoding::*; + match s { + "chunked" => Ok(Chunked), + "br" => Ok(Brotli), + "deflate" => Ok(Deflate), + "gzip" => Ok(Gzip), + "compress" => Ok(Compress), + "identity" => Ok(Identity), + "trailers" => Ok(Trailers), + _ => Ok(EncodingExt(s.to_owned())), + } + } + } + #[test] fn test_quality_item_fmt_q_1() { + use Encoding::*; let x = qitem(Chunked); assert_eq!(format!("{}", x), "chunked"); } #[test] fn test_quality_item_fmt_q_0001() { + use Encoding::*; let x = QualityItem::new(Chunked, Quality(1)); assert_eq!(format!("{}", x), "chunked; q=0.001"); } #[test] fn test_quality_item_fmt_q_05() { + use Encoding::*; // Custom value let x = QualityItem { item: EncodingExt("identity".to_owned()), @@ -218,6 +266,7 @@ mod tests { #[test] fn test_quality_item_fmt_q_0() { + use Encoding::*; // Custom value let x = QualityItem { item: EncodingExt("identity".to_owned()), @@ -228,6 +277,7 @@ mod tests { #[test] fn test_quality_item_from_str1() { + use Encoding::*; let x: Result, _> = "chunked".parse(); assert_eq!( x.unwrap(), @@ -237,8 +287,10 @@ mod tests { } ); } + #[test] fn test_quality_item_from_str2() { + use Encoding::*; let x: Result, _> = "chunked; q=1".parse(); assert_eq!( x.unwrap(), @@ -248,8 +300,10 @@ mod tests { } ); } + #[test] fn test_quality_item_from_str3() { + use Encoding::*; let x: Result, _> = "gzip; q=0.5".parse(); assert_eq!( x.unwrap(), @@ -259,8 +313,10 @@ mod tests { } ); } + #[test] fn test_quality_item_from_str4() { + use Encoding::*; let x: Result, _> = "gzip; q=0.273".parse(); assert_eq!( x.unwrap(), @@ -270,16 +326,19 @@ mod tests { } ); } + #[test] fn test_quality_item_from_str5() { let x: Result, _> = "gzip; q=0.2739999".parse(); assert!(x.is_err()); } + #[test] fn test_quality_item_from_str6() { let x: Result, _> = "gzip; q=2".parse(); assert!(x.is_err()); } + #[test] fn test_quality_item_ordering() { let x: QualityItem = "gzip; q=0.5".parse().ok().unwrap(); diff --git a/actix-http/src/header/utils.rs b/actix-http/src/header/utils.rs index e232d462f..5e9652380 100644 --- a/actix-http/src/header/utils.rs +++ b/actix-http/src/header/utils.rs @@ -1,7 +1,6 @@ use std::{fmt, str::FromStr}; -use http::HeaderValue; - +use super::HeaderValue; use crate::{error::ParseError, header::HTTP_VALUE}; /// Reads a comma-delimited raw header into a Vec. diff --git a/actix-http/src/response.rs b/actix-http/src/response.rs index 94f12bcc3..b27f477c9 100644 --- a/actix-http/src/response.rs +++ b/actix-http/src/response.rs @@ -359,10 +359,10 @@ impl ResponseBuilder { /// /// ``` /// # use actix_http::Response; - /// use actix_http::http::header::ContentType; + /// use actix_http::http::header; /// /// Response::Ok() - /// .insert_header(ContentType(mime::APPLICATION_JSON)) + /// .insert_header((header::CONTENT_TYPE, mime::APPLICATION_JSON)) /// .insert_header(("X-TEST", "value")) /// .finish(); /// ``` @@ -386,10 +386,10 @@ impl ResponseBuilder { /// /// ``` /// # use actix_http::Response; - /// use actix_http::http::header::ContentType; + /// use actix_http::http::header; /// /// Response::Ok() - /// .append_header(ContentType(mime::APPLICATION_JSON)) + /// .append_header((header::CONTENT_TYPE, mime::APPLICATION_JSON)) /// .append_header(("X-TEST", "value1")) /// .append_header(("X-TEST", "value2")) /// .finish(); @@ -682,7 +682,7 @@ impl ResponseBuilder { }; if !contains { - self.insert_header(header::ContentType(mime::APPLICATION_JSON)); + self.insert_header((header::CONTENT_TYPE, mime::APPLICATION_JSON)); } self.body(Body::from(body)) @@ -1133,7 +1133,7 @@ mod tests { #[test] fn response_builder_header_insert_typed() { let mut res = Response::Ok(); - res.insert_header(header::ContentType(mime::APPLICATION_OCTET_STREAM)); + res.insert_header((header::CONTENT_TYPE, mime::APPLICATION_OCTET_STREAM)); let res = res.finish(); assert_eq!( @@ -1158,8 +1158,8 @@ mod tests { #[test] fn response_builder_header_append_typed() { let mut res = Response::Ok(); - res.append_header(header::ContentType(mime::APPLICATION_OCTET_STREAM)); - res.append_header(header::ContentType(mime::APPLICATION_JSON)); + res.append_header((header::CONTENT_TYPE, mime::APPLICATION_OCTET_STREAM)); + res.append_header((header::CONTENT_TYPE, mime::APPLICATION_JSON)); let res = res.finish(); let headers: Vec<_> = res.headers().get_all("Content-Type").cloned().collect(); diff --git a/awc/CHANGES.md b/awc/CHANGES.md index b745e9868..c1031239a 100644 --- a/awc/CHANGES.md +++ b/awc/CHANGES.md @@ -7,8 +7,10 @@ ### Changed * `ConnectorService` type is renamed to `BoxConnectorService`. [#2081] * Fix http/https encoding when enabling `compress` feature. [#2116] +* Rename `TestResponse::header` to `append_header`, `set` to `insert_header`. `TestResponse` header methods now take `IntoHeaderPair` tuples. [#2094] [#2081]: https://github.com/actix/actix-web/pull/2081 +[#2094]: https://github.com/actix/actix-web/pull/2094 [#2114]: https://github.com/actix/actix-web/pull/2114 [#2116]: https://github.com/actix/actix-web/pull/2116 diff --git a/awc/src/request.rs b/awc/src/request.rs index 8b896a00d..6ecb64f81 100644 --- a/awc/src/request.rs +++ b/awc/src/request.rs @@ -189,12 +189,12 @@ impl ClientRequest { /// # #[actix_rt::main] /// # async fn main() { /// # use awc::Client; - /// use awc::http::header::ContentType; + /// use awc::http::header::CONTENT_TYPE; /// /// Client::new() /// .get("http://www.rust-lang.org") /// .insert_header(("X-TEST", "value")) - /// .insert_header(ContentType(mime::APPLICATION_JSON)); + /// .insert_header((CONTENT_TYPE, mime::APPLICATION_JSON)); /// # } /// ``` pub fn append_header(mut self, header: H) -> Self @@ -548,6 +548,8 @@ impl fmt::Debug for ClientRequest { mod tests { use std::time::SystemTime; + use actix_http::http::header::HttpDate; + use super::*; use crate::Client; @@ -564,7 +566,7 @@ mod tests { let req = Client::new() .put("/") .version(Version::HTTP_2) - .insert_header(header::Date(SystemTime::now().into())) + .insert_header((header::DATE, HttpDate::from(SystemTime::now()))) .content_type("plain/text") .append_header((header::SERVER, "awc")); diff --git a/awc/src/response.rs b/awc/src/response.rs index 994ddb761..27ba83af7 100644 --- a/awc/src/response.rs +++ b/awc/src/response.rs @@ -449,13 +449,13 @@ mod tests { #[actix_rt::test] async fn test_body() { - let mut req = TestResponse::with_header(header::CONTENT_LENGTH, "xxxx").finish(); + let mut req = TestResponse::with_header((header::CONTENT_LENGTH, "xxxx")).finish(); match req.body().await.err().unwrap() { PayloadError::UnknownLength => {} _ => unreachable!("error"), } - let mut req = TestResponse::with_header(header::CONTENT_LENGTH, "10000000").finish(); + let mut req = TestResponse::with_header((header::CONTENT_LENGTH, "10000000")).finish(); match req.body().await.err().unwrap() { PayloadError::Overflow => {} _ => unreachable!("error"), @@ -497,23 +497,23 @@ mod tests { assert!(json_eq(json.err().unwrap(), JsonPayloadError::ContentType)); let mut req = TestResponse::default() - .header( + .insert_header(( header::CONTENT_TYPE, header::HeaderValue::from_static("application/text"), - ) + )) .finish(); let json = JsonBody::<_, MyObject>::new(&mut req).await; assert!(json_eq(json.err().unwrap(), JsonPayloadError::ContentType)); let mut req = TestResponse::default() - .header( + .insert_header(( header::CONTENT_TYPE, header::HeaderValue::from_static("application/json"), - ) - .header( + )) + .insert_header(( header::CONTENT_LENGTH, header::HeaderValue::from_static("10000"), - ) + )) .finish(); let json = JsonBody::<_, MyObject>::new(&mut req).limit(100).await; @@ -523,14 +523,14 @@ mod tests { )); let mut req = TestResponse::default() - .header( + .insert_header(( header::CONTENT_TYPE, header::HeaderValue::from_static("application/json"), - ) - .header( + )) + .insert_header(( header::CONTENT_LENGTH, header::HeaderValue::from_static("16"), - ) + )) .set_payload(Bytes::from_static(b"{\"name\": \"test\"}")) .finish(); diff --git a/awc/src/test.rs b/awc/src/test.rs index 97bbb9c3d..8e95396b3 100644 --- a/awc/src/test.rs +++ b/awc/src/test.rs @@ -1,8 +1,6 @@ //! Test helpers for actix http client to use during testing. -use std::convert::TryFrom; - -use actix_http::http::header::{Header, IntoHeaderValue}; -use actix_http::http::{Error as HttpError, HeaderName, StatusCode, Version}; +use actix_http::http::header::IntoHeaderPair; +use actix_http::http::{StatusCode, Version}; #[cfg(feature = "cookies")] use actix_http::{ cookie::{Cookie, CookieJar}, @@ -34,13 +32,11 @@ impl Default for TestResponse { impl TestResponse { /// Create TestResponse and set header - pub fn with_header(key: K, value: V) -> Self + pub fn with_header(header: H) -> Self where - HeaderName: TryFrom, - >::Error: Into, - V: IntoHeaderValue, + H: IntoHeaderPair, { - Self::default().header(key, value) + Self::default().insert_header(header) } /// Set HTTP version of this response @@ -49,27 +45,26 @@ impl TestResponse { self } - /// Set a header - pub fn set(mut self, hdr: H) -> Self { - if let Ok(value) = hdr.try_into_value() { - self.head.headers.append(H::name(), value); + /// Insert a header + pub fn insert_header(mut self, header: H) -> Self + where + H: IntoHeaderPair, + { + if let Ok((key, value)) = header.try_into_header_pair() { + self.head.headers.insert(key, value); return self; } panic!("Can not set header"); } /// Append a header - pub fn header(mut self, key: K, value: V) -> Self + pub fn append_header(mut self, header: H) -> Self where - HeaderName: TryFrom, - >::Error: Into, - V: IntoHeaderValue, + H: IntoHeaderPair, { - if let Ok(key) = HeaderName::try_from(key) { - if let Ok(value) = value.try_into_value() { - self.head.headers.append(key, value); - return self; - } + if let Ok((key, value)) = header.try_into_header_pair() { + self.head.headers.append(key, value); + return self; } panic!("Can not create header"); } @@ -115,6 +110,8 @@ impl TestResponse { mod tests { use std::time::SystemTime; + use actix_http::http::header::HttpDate; + use super::*; use crate::{cookie, http::header}; @@ -122,7 +119,7 @@ mod tests { fn test_basics() { let res = TestResponse::default() .version(Version::HTTP_2) - .set(header::Date(SystemTime::now().into())) + .insert_header((header::DATE, HttpDate::from(SystemTime::now()))) .cookie(cookie::Cookie::build("name", "value").finish()) .finish(); assert!(res.headers().contains_key(header::SET_COOKIE)); diff --git a/actix-http/src/header/common/accept.rs b/src/http/header/accept.rs similarity index 93% rename from actix-http/src/header/common/accept.rs rename to src/http/header/accept.rs index 775da3394..1ec94e353 100644 --- a/actix-http/src/header/common/accept.rs +++ b/src/http/header/accept.rs @@ -2,10 +2,10 @@ use std::cmp::Ordering; use mime::Mime; -use crate::header::{qitem, QualityItem}; +use super::{qitem, QualityItem}; use crate::http::header; -header! { +crate::header! { /// `Accept` header, defined in [RFC7231](http://tools.ietf.org/html/rfc7231#section-5.3.2) /// /// The `Accept` header field can be used by user agents to specify @@ -33,10 +33,10 @@ header! { /// /// # Examples /// ``` - /// use actix_http::Response; - /// use actix_http::http::header::{Accept, qitem}; + /// use actix_web::HttpResponse; + /// use actix_web::http::header::{Accept, qitem}; /// - /// let mut builder = Response::Ok(); + /// let mut builder = HttpResponse::Ok(); /// builder.insert_header( /// Accept(vec![ /// qitem(mime::TEXT_HTML), @@ -45,10 +45,10 @@ header! { /// ``` /// /// ``` - /// use actix_http::Response; - /// use actix_http::http::header::{Accept, qitem}; + /// use actix_web::HttpResponse; + /// use actix_web::http::header::{Accept, qitem}; /// - /// let mut builder = Response::Ok(); + /// let mut builder = HttpResponse::Ok(); /// builder.insert_header( /// Accept(vec![ /// qitem(mime::APPLICATION_JSON), @@ -57,10 +57,10 @@ header! { /// ``` /// /// ``` - /// use actix_http::Response; - /// use actix_http::http::header::{Accept, QualityItem, q, qitem}; + /// use actix_web::HttpResponse; + /// use actix_web::http::header::{Accept, QualityItem, q, qitem}; /// - /// let mut builder = Response::Ok(); + /// let mut builder = HttpResponse::Ok(); /// builder.insert_header( /// Accept(vec![ /// qitem(mime::TEXT_HTML), @@ -116,8 +116,8 @@ header! { #[test] fn test_fuzzing1() { - use crate::test::TestRequest; - let req = TestRequest::default().insert_header((crate::header::ACCEPT, "chunk#;e")).finish(); + use actix_http::test::TestRequest; + let req = TestRequest::default().insert_header((crate::http::header::ACCEPT, "chunk#;e")).finish(); let header = Accept::parse(&req); assert!(header.is_ok()); } @@ -213,7 +213,7 @@ impl Accept { #[cfg(test)] mod tests { use super::*; - use crate::header::q; + use crate::http::header::q; #[test] fn test_mime_precedence() { diff --git a/actix-http/src/header/common/accept_charset.rs b/src/http/header/accept_charset.rs similarity index 73% rename from actix-http/src/header/common/accept_charset.rs rename to src/http/header/accept_charset.rs index db530a8bc..9932ac57a 100644 --- a/actix-http/src/header/common/accept_charset.rs +++ b/src/http/header/accept_charset.rs @@ -1,6 +1,6 @@ -use crate::header::{Charset, QualityItem, ACCEPT_CHARSET}; +use super::{Charset, QualityItem, ACCEPT_CHARSET}; -header! { +crate::header! { /// `Accept-Charset` header, defined in /// [RFC7231](http://tools.ietf.org/html/rfc7231#section-5.3.3) /// @@ -22,20 +22,20 @@ header! { /// /// # Examples /// ``` - /// use actix_http::Response; - /// use actix_http::http::header::{AcceptCharset, Charset, qitem}; + /// use actix_web::HttpResponse; + /// use actix_web::http::header::{AcceptCharset, Charset, qitem}; /// - /// let mut builder = Response::Ok(); + /// let mut builder = HttpResponse::Ok(); /// builder.insert_header( /// AcceptCharset(vec![qitem(Charset::Us_Ascii)]) /// ); /// ``` /// /// ``` - /// use actix_http::Response; - /// use actix_http::http::header::{AcceptCharset, Charset, q, QualityItem}; + /// use actix_web::HttpResponse; + /// use actix_web::http::header::{AcceptCharset, Charset, q, QualityItem}; /// - /// let mut builder = Response::Ok(); + /// let mut builder = HttpResponse::Ok(); /// builder.insert_header( /// AcceptCharset(vec![ /// QualityItem::new(Charset::Us_Ascii, q(900)), @@ -45,10 +45,10 @@ header! { /// ``` /// /// ``` - /// use actix_http::Response; - /// use actix_http::http::header::{AcceptCharset, Charset, qitem}; + /// use actix_web::HttpResponse; + /// use actix_web::http::header::{AcceptCharset, Charset, qitem}; /// - /// let mut builder = Response::Ok(); + /// let mut builder = HttpResponse::Ok(); /// builder.insert_header( /// AcceptCharset(vec![qitem(Charset::Ext("utf-8".to_owned()))]) /// ); diff --git a/actix-http/src/header/common/accept_encoding.rs b/src/http/header/accept_encoding.rs similarity index 76% rename from actix-http/src/header/common/accept_encoding.rs rename to src/http/header/accept_encoding.rs index c90f529bc..e59351708 100644 --- a/actix-http/src/header/common/accept_encoding.rs +++ b/src/http/header/accept_encoding.rs @@ -26,18 +26,20 @@ header! { /// /// # Examples /// ``` - /// use hyper::header::{Headers, AcceptEncoding, Encoding, qitem}; + /// use actix_web::HttpResponse; + /// use actix_web::http::header::{AcceptEncoding, Encoding, qitem}; /// - /// let mut headers = Headers::new(); - /// headers.set( + /// let mut builder = HttpResponse::new(); + /// builder.insert_header( /// AcceptEncoding(vec![qitem(Encoding::Chunked)]) /// ); /// ``` /// ``` - /// use hyper::header::{Headers, AcceptEncoding, Encoding, qitem}; + /// use actix_web::HttpResponse; + /// use actix_web::http::header::{AcceptEncoding, Encoding, qitem}; /// - /// let mut headers = Headers::new(); - /// headers.set( + /// let mut builder = HttpResponse::new(); + /// builder.insert_header( /// AcceptEncoding(vec![ /// qitem(Encoding::Chunked), /// qitem(Encoding::Gzip), @@ -46,10 +48,11 @@ header! { /// ); /// ``` /// ``` - /// use hyper::header::{Headers, AcceptEncoding, Encoding, QualityItem, q, qitem}; + /// use actix_web::HttpResponse; + /// use actix_web::http::header::{AcceptEncoding, Encoding, QualityItem, q, qitem}; /// - /// let mut headers = Headers::new(); - /// headers.set( + /// let mut builder = HttpResponse::new(); + /// builder.insert_header( /// AcceptEncoding(vec![ /// qitem(Encoding::Chunked), /// QualityItem::new(Encoding::Gzip, q(600)), diff --git a/actix-http/src/header/common/accept_language.rs b/src/http/header/accept_language.rs similarity index 82% rename from actix-http/src/header/common/accept_language.rs rename to src/http/header/accept_language.rs index a7ad00863..2963844af 100644 --- a/actix-http/src/header/common/accept_language.rs +++ b/src/http/header/accept_language.rs @@ -1,7 +1,7 @@ -use crate::header::{QualityItem, ACCEPT_LANGUAGE}; +use super::{QualityItem, ACCEPT_LANGUAGE}; use language_tags::LanguageTag; -header! { +crate::header! { /// `Accept-Language` header, defined in /// [RFC7231](http://tools.ietf.org/html/rfc7231#section-5.3.5) /// @@ -24,10 +24,10 @@ header! { /// /// ``` /// use language_tags::langtag; - /// use actix_http::Response; - /// use actix_http::http::header::{AcceptLanguage, LanguageTag, qitem}; + /// use actix_web::HttpResponse; + /// use actix_web::http::header::{AcceptLanguage, LanguageTag, qitem}; /// - /// let mut builder = Response::Ok(); + /// let mut builder = HttpResponse::Ok(); /// let mut langtag: LanguageTag = Default::default(); /// langtag.language = Some("en".to_owned()); /// langtag.region = Some("US".to_owned()); @@ -40,10 +40,10 @@ header! { /// /// ``` /// use language_tags::langtag; - /// use actix_http::Response; - /// use actix_http::http::header::{AcceptLanguage, QualityItem, q, qitem}; + /// use actix_web::HttpResponse; + /// use actix_web::http::header::{AcceptLanguage, QualityItem, q, qitem}; /// - /// let mut builder = Response::Ok(); + /// let mut builder = HttpResponse::Ok(); /// builder.insert_header( /// AcceptLanguage(vec![ /// qitem(langtag!(da)), diff --git a/actix-http/src/header/common/allow.rs b/src/http/header/allow.rs similarity index 83% rename from actix-http/src/header/common/allow.rs rename to src/http/header/allow.rs index 06b1efedc..e1f2bb4b6 100644 --- a/actix-http/src/header/common/allow.rs +++ b/src/http/header/allow.rs @@ -1,7 +1,7 @@ -use http::header; -use http::Method; +use actix_http::http::Method; +use crate::http::header; -header! { +crate::header! { /// `Allow` header, defined in [RFC7231](http://tools.ietf.org/html/rfc7231#section-7.4.1) /// /// The `Allow` header field lists the set of methods advertised as @@ -23,20 +23,20 @@ header! { /// # Examples /// /// ``` - /// use actix_http::Response; - /// use actix_http::http::{header::Allow, Method}; + /// use actix_web::HttpResponse; + /// use actix_web::http::{header::Allow, Method}; /// - /// let mut builder = Response::Ok(); + /// let mut builder = HttpResponse::Ok(); /// builder.insert_header( /// Allow(vec![Method::GET]) /// ); /// ``` /// /// ``` - /// use actix_http::Response; - /// use actix_http::http::{header::Allow, Method}; + /// use actix_web::HttpResponse; + /// use actix_web::http::{header::Allow, Method}; /// - /// let mut builder = Response::Ok(); + /// let mut builder = HttpResponse::Ok(); /// builder.insert_header( /// Allow(vec![ /// Method::GET, diff --git a/actix-http/src/header/common/cache_control.rs b/src/http/header/cache_control.rs similarity index 95% rename from actix-http/src/header/common/cache_control.rs rename to src/http/header/cache_control.rs index b19823d22..6f020a931 100644 --- a/actix-http/src/header/common/cache_control.rs +++ b/src/http/header/cache_control.rs @@ -1,12 +1,12 @@ use std::fmt::{self, Write}; use std::str::FromStr; -use http::header; - -use crate::header::{ +use super::{ fmt_comma_delimited, from_comma_delimited, Header, IntoHeaderValue, Writer, }; +use crate::http::header; + /// `Cache-Control` header, defined in [RFC7234](https://tools.ietf.org/html/rfc7234#section-5.2) /// /// The `Cache-Control` header field is used to specify directives for @@ -29,18 +29,18 @@ use crate::header::{ /// /// # Examples /// ``` -/// use actix_http::Response; -/// use actix_http::http::header::{CacheControl, CacheDirective}; +/// use actix_web::HttpResponse; +/// use actix_web::http::header::{CacheControl, CacheDirective}; /// -/// let mut builder = Response::Ok(); +/// let mut builder = HttpResponse::Ok(); /// builder.insert_header(CacheControl(vec![CacheDirective::MaxAge(86400u32)])); /// ``` /// /// ``` -/// use actix_http::Response; -/// use actix_http::http::header::{CacheControl, CacheDirective}; +/// use actix_web::HttpResponse; +/// use actix_web::http::header::{CacheControl, CacheDirective}; /// -/// let mut builder = Response::Ok(); +/// let mut builder = HttpResponse::Ok(); /// builder.insert_header(CacheControl(vec![ /// CacheDirective::NoCache, /// CacheDirective::Private, @@ -191,8 +191,8 @@ impl FromStr for CacheDirective { #[cfg(test)] mod tests { use super::*; - use crate::header::Header; - use crate::test::TestRequest; + use crate::http::header::Header; + use actix_http::test::TestRequest; #[test] fn test_parse_multiple_headers() { diff --git a/actix-http/src/header/common/content_disposition.rs b/src/http/header/content_disposition.rs similarity index 99% rename from actix-http/src/header/common/content_disposition.rs rename to src/http/header/content_disposition.rs index 6076d033c..3dea0997b 100644 --- a/actix-http/src/header/common/content_disposition.rs +++ b/src/http/header/content_disposition.rs @@ -10,7 +10,8 @@ use once_cell::sync::Lazy; use regex::Regex; use std::fmt::{self, Write}; -use crate::header::{self, ExtendedValue, Header, IntoHeaderValue, Writer}; +use crate::http::header; +use super::{ExtendedValue, Header, IntoHeaderValue, Writer}; /// Split at the index of the first `needle` if it exists or at the end. fn split_once(haystack: &str, needle: char) -> (&str, &str) { @@ -63,7 +64,7 @@ impl<'a> From<&'a str> for DispositionType { /// /// # Examples /// ``` -/// use actix_http::http::header::DispositionParam; +/// use actix_web::http::header::DispositionParam; /// /// let param = DispositionParam::Filename(String::from("sample.txt")); /// assert!(param.is_filename()); @@ -240,7 +241,7 @@ impl DispositionParam { /// # Example /// /// ``` -/// use actix_http::http::header::{ +/// use actix_web::http::header::{ /// Charset, ContentDisposition, DispositionParam, DispositionType, /// ExtendedValue, /// }; @@ -554,8 +555,8 @@ impl fmt::Display for ContentDisposition { #[cfg(test)] mod tests { use super::{ContentDisposition, DispositionParam, DispositionType}; - use crate::header::shared::Charset; - use crate::header::{ExtendedValue, HeaderValue}; + use crate::http::header::Charset; + use crate::http::header::{ExtendedValue, HeaderValue}; #[test] fn test_from_raw_basic() { diff --git a/actix-http/src/header/common/content_language.rs b/src/http/header/content_language.rs similarity index 77% rename from actix-http/src/header/common/content_language.rs rename to src/http/header/content_language.rs index e9be67a1b..5dd8f72a5 100644 --- a/actix-http/src/header/common/content_language.rs +++ b/src/http/header/content_language.rs @@ -1,7 +1,7 @@ -use crate::header::{QualityItem, CONTENT_LANGUAGE}; +use super::{QualityItem, CONTENT_LANGUAGE}; use language_tags::LanguageTag; -header! { +crate::header! { /// `Content-Language` header, defined in /// [RFC7231](https://tools.ietf.org/html/rfc7231#section-3.1.3.2) /// @@ -25,10 +25,10 @@ header! { /// /// ``` /// use language_tags::langtag; - /// use actix_http::Response; - /// use actix_http::http::header::{ContentLanguage, qitem}; + /// use actix_web::HttpResponse; + /// use actix_web::http::header::{ContentLanguage, qitem}; /// - /// let mut builder = Response::Ok(); + /// let mut builder = HttpResponse::Ok(); /// builder.insert_header( /// ContentLanguage(vec![ /// qitem(langtag!(en)), @@ -38,10 +38,10 @@ header! { /// /// ``` /// use language_tags::langtag; - /// use actix_http::Response; - /// use actix_http::http::header::{ContentLanguage, qitem}; + /// use actix_web::HttpResponse; + /// use actix_web::http::header::{ContentLanguage, qitem}; /// - /// let mut builder = Response::Ok(); + /// let mut builder = HttpResponse::Ok(); /// builder.insert_header( /// ContentLanguage(vec![ /// qitem(langtag!(da)), diff --git a/actix-http/src/header/common/content_range.rs b/src/http/header/content_range.rs similarity index 99% rename from actix-http/src/header/common/content_range.rs rename to src/http/header/content_range.rs index 8b7552377..dfcf24251 100644 --- a/actix-http/src/header/common/content_range.rs +++ b/src/http/header/content_range.rs @@ -2,11 +2,11 @@ use std::fmt::{self, Display, Write}; use std::str::FromStr; use crate::error::ParseError; -use crate::header::{ +use super::{ HeaderValue, IntoHeaderValue, InvalidHeaderValue, Writer, CONTENT_RANGE, }; -header! { +crate::header! { /// `Content-Range` header, defined in /// [RFC7233](http://tools.ietf.org/html/rfc7233#section-4.2) (ContentRange, CONTENT_RANGE) => [ContentRangeSpec] diff --git a/actix-http/src/header/common/content_type.rs b/src/http/header/content_type.rs similarity index 91% rename from actix-http/src/header/common/content_type.rs rename to src/http/header/content_type.rs index ac5c7e5b8..a85e64ba9 100644 --- a/actix-http/src/header/common/content_type.rs +++ b/src/http/header/content_type.rs @@ -1,7 +1,7 @@ -use crate::header::CONTENT_TYPE; +use super::CONTENT_TYPE; use mime::Mime; -header! { +crate::header! { /// `Content-Type` header, defined in /// [RFC7231](http://tools.ietf.org/html/rfc7231#section-3.1.1.5) /// @@ -31,20 +31,20 @@ header! { /// # Examples /// /// ``` - /// use actix_http::Response; - /// use actix_http::http::header::ContentType; + /// use actix_web::HttpResponse; + /// use actix_web::http::header::ContentType; /// - /// let mut builder = Response::Ok(); + /// let mut builder = HttpResponse::Ok(); /// builder.insert_header( /// ContentType::json() /// ); /// ``` /// /// ``` - /// use actix_http::Response; - /// use actix_http::http::header::ContentType; + /// use actix_web::HttpResponse; + /// use actix_web::http::header::ContentType; /// - /// let mut builder = Response::Ok(); + /// let mut builder = HttpResponse::Ok(); /// builder.insert_header( /// ContentType(mime::TEXT_HTML) /// ); diff --git a/actix-http/src/header/common/date.rs b/src/http/header/date.rs similarity index 83% rename from actix-http/src/header/common/date.rs rename to src/http/header/date.rs index e5ace95e6..faceefb4f 100644 --- a/actix-http/src/header/common/date.rs +++ b/src/http/header/date.rs @@ -1,7 +1,7 @@ -use crate::header::{HttpDate, DATE}; +use super::{HttpDate, DATE}; use std::time::SystemTime; -header! { +crate::header! { /// `Date` header, defined in [RFC7231](http://tools.ietf.org/html/rfc7231#section-7.1.1.2) /// /// The `Date` header field represents the date and time at which the @@ -21,10 +21,10 @@ header! { /// /// ``` /// use std::time::SystemTime; - /// use actix_http::Response; - /// use actix_http::http::header::Date; + /// use actix_web::HttpResponse; + /// use actix_web::http::header::Date; /// - /// let mut builder = Response::Ok(); + /// let mut builder = HttpResponse::Ok(); /// builder.insert_header( /// Date(SystemTime::now().into()) /// ); diff --git a/actix-http/src/header/shared/encoding.rs b/src/http/header/encoding.rs similarity index 100% rename from actix-http/src/header/shared/encoding.rs rename to src/http/header/encoding.rs diff --git a/actix-http/src/header/shared/entity.rs b/src/http/header/entity.rs similarity index 99% rename from actix-http/src/header/shared/entity.rs rename to src/http/header/entity.rs index 2505216f2..5073ed692 100644 --- a/actix-http/src/header/shared/entity.rs +++ b/src/http/header/entity.rs @@ -1,7 +1,7 @@ use std::fmt::{self, Display, Write}; use std::str::FromStr; -use crate::header::{HeaderValue, IntoHeaderValue, InvalidHeaderValue, Writer}; +use super::{HeaderValue, IntoHeaderValue, InvalidHeaderValue, Writer}; /// check that each char in the slice is either: /// 1. `%x21`, or diff --git a/actix-http/src/header/common/etag.rs b/src/http/header/etag.rs similarity index 90% rename from actix-http/src/header/common/etag.rs rename to src/http/header/etag.rs index 4c1e8d262..8972564d0 100644 --- a/actix-http/src/header/common/etag.rs +++ b/src/http/header/etag.rs @@ -1,6 +1,6 @@ -use crate::header::{EntityTag, ETAG}; +use super::{EntityTag, ETAG}; -header! { +crate::header! { /// `ETag` header, defined in [RFC7232](http://tools.ietf.org/html/rfc7232#section-2.3) /// /// The `ETag` header field in a response provides the current entity-tag @@ -28,20 +28,20 @@ header! { /// # Examples /// /// ``` - /// use actix_http::Response; - /// use actix_http::http::header::{ETag, EntityTag}; + /// use actix_web::HttpResponse; + /// use actix_web::http::header::{ETag, EntityTag}; /// - /// let mut builder = Response::Ok(); + /// let mut builder = HttpResponse::Ok(); /// builder.insert_header( /// ETag(EntityTag::new(false, "xyzzy".to_owned())) /// ); /// ``` /// /// ``` - /// use actix_http::Response; - /// use actix_http::http::header::{ETag, EntityTag}; + /// use actix_web::HttpResponse; + /// use actix_web::http::header::{ETag, EntityTag}; /// - /// let mut builder = Response::Ok(); + /// let mut builder = HttpResponse::Ok(); /// builder.insert_header( /// ETag(EntityTag::new(true, "xyzzy".to_owned())) /// ); diff --git a/actix-http/src/header/common/expires.rs b/src/http/header/expires.rs similarity index 84% rename from actix-http/src/header/common/expires.rs rename to src/http/header/expires.rs index 79563955d..1c306cae0 100644 --- a/actix-http/src/header/common/expires.rs +++ b/src/http/header/expires.rs @@ -1,6 +1,6 @@ -use crate::header::{HttpDate, EXPIRES}; +use super::{HttpDate, EXPIRES}; -header! { +crate::header! { /// `Expires` header, defined in [RFC7234](http://tools.ietf.org/html/rfc7234#section-5.3) /// /// The `Expires` header field gives the date/time after which the @@ -23,10 +23,10 @@ header! { /// /// ``` /// use std::time::{SystemTime, Duration}; - /// use actix_http::Response; - /// use actix_http::http::header::Expires; + /// use actix_web::HttpResponse; + /// use actix_web::http::header::Expires; /// - /// let mut builder = Response::Ok(); + /// let mut builder = HttpResponse::Ok(); /// let expiration = SystemTime::now() + Duration::from_secs(60 * 60 * 24); /// builder.insert_header( /// Expires(expiration.into()) diff --git a/actix-http/src/header/common/if_match.rs b/src/http/header/if_match.rs similarity index 86% rename from actix-http/src/header/common/if_match.rs rename to src/http/header/if_match.rs index db255e91a..80699e39c 100644 --- a/actix-http/src/header/common/if_match.rs +++ b/src/http/header/if_match.rs @@ -1,6 +1,6 @@ -use crate::header::{EntityTag, IF_MATCH}; +use super::{EntityTag, IF_MATCH}; -header! { +crate::header! { /// `If-Match` header, defined in /// [RFC7232](https://tools.ietf.org/html/rfc7232#section-3.1) /// @@ -30,18 +30,18 @@ header! { /// # Examples /// /// ``` - /// use actix_http::Response; - /// use actix_http::http::header::IfMatch; + /// use actix_web::HttpResponse; + /// use actix_web::http::header::IfMatch; /// - /// let mut builder = Response::Ok(); + /// let mut builder = HttpResponse::Ok(); /// builder.insert_header(IfMatch::Any); /// ``` /// /// ``` - /// use actix_http::Response; - /// use actix_http::http::header::{IfMatch, EntityTag}; + /// use actix_web::HttpResponse; + /// use actix_web::http::header::{IfMatch, EntityTag}; /// - /// let mut builder = Response::Ok(); + /// let mut builder = HttpResponse::Ok(); /// builder.insert_header( /// IfMatch::Items(vec![ /// EntityTag::new(false, "xyzzy".to_owned()), diff --git a/actix-http/src/header/common/if_modified_since.rs b/src/http/header/if_modified_since.rs similarity index 84% rename from actix-http/src/header/common/if_modified_since.rs rename to src/http/header/if_modified_since.rs index 99c7e441d..d777e0c5c 100644 --- a/actix-http/src/header/common/if_modified_since.rs +++ b/src/http/header/if_modified_since.rs @@ -1,6 +1,6 @@ -use crate::header::{HttpDate, IF_MODIFIED_SINCE}; +use super::{HttpDate, IF_MODIFIED_SINCE}; -header! { +crate::header! { /// `If-Modified-Since` header, defined in /// [RFC7232](http://tools.ietf.org/html/rfc7232#section-3.3) /// @@ -23,10 +23,10 @@ header! { /// /// ``` /// use std::time::{SystemTime, Duration}; - /// use actix_http::Response; - /// use actix_http::http::header::IfModifiedSince; + /// use actix_web::HttpResponse; + /// use actix_web::http::header::IfModifiedSince; /// - /// let mut builder = Response::Ok(); + /// let mut builder = HttpResponse::Ok(); /// let modified = SystemTime::now() - Duration::from_secs(60 * 60 * 24); /// builder.insert_header( /// IfModifiedSince(modified.into()) diff --git a/actix-http/src/header/common/if_none_match.rs b/src/http/header/if_none_match.rs similarity index 86% rename from actix-http/src/header/common/if_none_match.rs rename to src/http/header/if_none_match.rs index 464caf1ae..a5c06b374 100644 --- a/actix-http/src/header/common/if_none_match.rs +++ b/src/http/header/if_none_match.rs @@ -1,6 +1,6 @@ -use crate::header::{EntityTag, IF_NONE_MATCH}; +use super::{EntityTag, IF_NONE_MATCH}; -header! { +crate::header! { /// `If-None-Match` header, defined in /// [RFC7232](https://tools.ietf.org/html/rfc7232#section-3.2) /// @@ -32,18 +32,18 @@ header! { /// # Examples /// /// ``` - /// use actix_http::Response; - /// use actix_http::http::header::IfNoneMatch; + /// use actix_web::HttpResponse; + /// use actix_web::http::header::IfNoneMatch; /// - /// let mut builder = Response::Ok(); + /// let mut builder = HttpResponse::Ok(); /// builder.insert_header(IfNoneMatch::Any); /// ``` /// /// ``` - /// use actix_http::Response; - /// use actix_http::http::header::{IfNoneMatch, EntityTag}; + /// use actix_web::HttpResponse; + /// use actix_web::http::header::{IfNoneMatch, EntityTag}; /// - /// let mut builder = Response::Ok(); + /// let mut builder = HttpResponse::Ok(); /// builder.insert_header( /// IfNoneMatch::Items(vec![ /// EntityTag::new(false, "xyzzy".to_owned()), @@ -66,8 +66,8 @@ header! { #[cfg(test)] mod tests { use super::IfNoneMatch; - use crate::header::{EntityTag, Header, IF_NONE_MATCH}; - use crate::test::TestRequest; + use crate::http::header::{EntityTag, Header, IF_NONE_MATCH}; + use actix_http::test::TestRequest; #[test] fn test_if_none_match() { diff --git a/actix-http/src/header/common/if_range.rs b/src/http/header/if_range.rs similarity index 89% rename from actix-http/src/header/common/if_range.rs rename to src/http/header/if_range.rs index 0a5749505..f34332f22 100644 --- a/actix-http/src/header/common/if_range.rs +++ b/src/http/header/if_range.rs @@ -1,8 +1,9 @@ use std::fmt::{self, Display, Write}; +use crate::http::header; use crate::error::ParseError; -use crate::header::{ - self, from_one_raw_str, EntityTag, Header, HeaderName, HeaderValue, HttpDate, +use super::{ + from_one_raw_str, EntityTag, Header, HeaderName, HeaderValue, HttpDate, IntoHeaderValue, InvalidHeaderValue, Writer, }; use crate::HttpMessage; @@ -36,10 +37,10 @@ use crate::HttpMessage; /// # Examples /// /// ``` -/// use actix_http::Response; -/// use actix_http::http::header::{EntityTag, IfRange}; +/// use actix_web::HttpResponse; +/// use actix_web::http::header::{EntityTag, IfRange}; /// -/// let mut builder = Response::Ok(); +/// let mut builder = HttpResponse::Ok(); /// builder.insert_header( /// IfRange::EntityTag( /// EntityTag::new(false, "abc".to_owned()) @@ -49,9 +50,9 @@ use crate::HttpMessage; /// /// ``` /// use std::time::{Duration, SystemTime}; -/// use actix_http::{http::header::IfRange, Response}; +/// use actix_web::{http::header::IfRange, HttpResponse}; /// -/// let mut builder = Response::Ok(); +/// let mut builder = HttpResponse::Ok(); /// let fetched = SystemTime::now() - Duration::from_secs(60 * 60 * 24); /// builder.insert_header( /// IfRange::Date(fetched.into()) @@ -111,7 +112,7 @@ impl IntoHeaderValue for IfRange { #[cfg(test)] mod test_if_range { use super::IfRange as HeaderField; - use crate::header::*; + use crate::http::header::*; use std::str; test_header!(test1, vec![b"Sat, 29 Oct 1994 19:43:31 GMT"]); diff --git a/actix-http/src/header/common/if_unmodified_since.rs b/src/http/header/if_unmodified_since.rs similarity index 85% rename from actix-http/src/header/common/if_unmodified_since.rs rename to src/http/header/if_unmodified_since.rs index 1c2b4af78..8887982aa 100644 --- a/actix-http/src/header/common/if_unmodified_since.rs +++ b/src/http/header/if_unmodified_since.rs @@ -1,6 +1,6 @@ -use crate::header::{HttpDate, IF_UNMODIFIED_SINCE}; +use super::{HttpDate, IF_UNMODIFIED_SINCE}; -header! { +crate::header! { /// `If-Unmodified-Since` header, defined in /// [RFC7232](http://tools.ietf.org/html/rfc7232#section-3.4) /// @@ -24,10 +24,10 @@ header! { /// /// ``` /// use std::time::{SystemTime, Duration}; - /// use actix_http::Response; - /// use actix_http::http::header::IfUnmodifiedSince; + /// use actix_web::HttpResponse; + /// use actix_web::http::header::IfUnmodifiedSince; /// - /// let mut builder = Response::Ok(); + /// let mut builder = HttpResponse::Ok(); /// let modified = SystemTime::now() - Duration::from_secs(60 * 60 * 24); /// builder.insert_header( /// IfUnmodifiedSince(modified.into()) diff --git a/actix-http/src/header/common/last_modified.rs b/src/http/header/last_modified.rs similarity index 84% rename from actix-http/src/header/common/last_modified.rs rename to src/http/header/last_modified.rs index 65608d846..9ed6fcf69 100644 --- a/actix-http/src/header/common/last_modified.rs +++ b/src/http/header/last_modified.rs @@ -1,6 +1,6 @@ -use crate::header::{HttpDate, LAST_MODIFIED}; +use super::{HttpDate, LAST_MODIFIED}; -header! { +crate::header! { /// `Last-Modified` header, defined in /// [RFC7232](http://tools.ietf.org/html/rfc7232#section-2.2) /// @@ -23,10 +23,10 @@ header! { /// /// ``` /// use std::time::{SystemTime, Duration}; - /// use actix_http::Response; - /// use actix_http::http::header::LastModified; + /// use actix_web::HttpResponse; + /// use actix_web::http::header::LastModified; /// - /// let mut builder = Response::Ok(); + /// let mut builder = HttpResponse::Ok(); /// let modified = SystemTime::now() - Duration::from_secs(60 * 60 * 24); /// builder.insert_header( /// LastModified(modified.into()) diff --git a/actix-http/src/header/common/mod.rs b/src/http/header/mod.rs similarity index 92% rename from actix-http/src/header/common/mod.rs rename to src/http/header/mod.rs index 90e0a855e..a1c405344 100644 --- a/actix-http/src/header/common/mod.rs +++ b/src/http/header/mod.rs @@ -7,6 +7,10 @@ //! is used, such as `ContentType(pub Mime)`. #![cfg_attr(rustfmt, rustfmt_skip)] +use std::fmt; +use bytes::{BytesMut, Bytes}; + +pub use actix_http::http::header::*; pub use self::accept_charset::AcceptCharset; //pub use self::accept_encoding::AcceptEncoding; pub use self::accept::Accept; @@ -18,7 +22,6 @@ pub use self::content_disposition::{ }; pub use self::content_language::ContentLanguage; pub use self::content_range::{ContentRange, ContentRangeSpec}; -pub use self::content_encoding::{ContentEncoding}; pub use self::content_type::ContentType; pub use self::date::Date; pub use self::etag::ETag; @@ -29,7 +32,39 @@ pub use self::if_none_match::IfNoneMatch; pub use self::if_range::IfRange; pub use self::if_unmodified_since::IfUnmodifiedSince; pub use self::last_modified::LastModified; +pub use self::encoding::Encoding; +pub use self::entity::EntityTag; //pub use self::range::{Range, ByteRangeSpec}; +pub(crate) use actix_http::http::header::{fmt_comma_delimited, from_comma_delimited, from_one_raw_str}; + +#[derive(Debug, Default)] +struct Writer { + buf: BytesMut, +} + +impl Writer { + pub fn new() -> Writer { + Writer::default() + } + + pub fn take(&mut self) -> Bytes { + self.buf.split().freeze() + } +} + +impl fmt::Write for Writer { + #[inline] + fn write_str(&mut self, s: &str) -> fmt::Result { + self.buf.extend_from_slice(s.as_bytes()); + Ok(()) + } + + #[inline] + fn write_fmt(&mut self, args: fmt::Arguments<'_>) -> fmt::Result { + fmt::write(self, args) + } +} + #[doc(hidden)] #[macro_export] @@ -61,9 +96,9 @@ macro_rules! __hyper__tm { #[cfg(test)] mod $tm{ use std::str; - use http::Method; + use actix_http::http::Method; use mime::*; - use $crate::header::*; + use $crate::http::header::*; use super::$id as HeaderField; $($tf)* } @@ -77,8 +112,7 @@ macro_rules! test_header { ($id:ident, $raw:expr) => { #[test] fn $id() { - use super::*; - use $crate::test; + use actix_http::test; let raw = $raw; let a: Vec> = raw.iter().map(|x| x.to_vec()).collect(); @@ -106,7 +140,7 @@ macro_rules! test_header { ($id:ident, $raw:expr, $typed:expr) => { #[test] fn $id() { - use $crate::test; + use actix_http::test; let a: Vec> = $raw.iter().map(|x| x.to_vec()).collect(); let mut req = test::TestRequest::default(); @@ -134,6 +168,7 @@ macro_rules! test_header { }; } +#[doc(hidden)] #[macro_export] macro_rules! header { // $a:meta: Attributes associated with the header item (usually docs) @@ -341,7 +376,6 @@ mod allow; mod cache_control; mod content_disposition; mod content_language; -mod content_encoding; mod content_range; mod content_type; mod date; @@ -353,3 +387,5 @@ mod if_none_match; mod if_range; mod if_unmodified_since; mod last_modified; +mod encoding; +mod entity; diff --git a/actix-http/src/header/common/range.rs b/src/http/header/range.rs similarity index 99% rename from actix-http/src/header/common/range.rs rename to src/http/header/range.rs index f9e203bb2..a9b40b403 100644 --- a/actix-http/src/header/common/range.rs +++ b/src/http/header/range.rs @@ -1,8 +1,8 @@ use std::fmt::{self, Display}; use std::str::FromStr; -use header::parsing::from_one_raw_str; -use header::{Header, Raw}; +use super::parsing::from_one_raw_str; +use super::{Header, Raw}; /// `Range` header, defined in [RFC7233](https://tools.ietf.org/html/rfc7233#section-3.1) /// diff --git a/src/http/mod.rs b/src/http/mod.rs new file mode 100644 index 000000000..fa28a5fa9 --- /dev/null +++ b/src/http/mod.rs @@ -0,0 +1,2 @@ +pub mod header; +pub use actix_http::http::*; diff --git a/src/lib.rs b/src/lib.rs index 7a6498546..38d9d5734 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -84,6 +84,7 @@ pub mod error; mod extract; pub mod guard; mod handler; +pub mod http; mod info; pub mod middleware; mod request; @@ -102,7 +103,7 @@ pub mod web; #[cfg(feature = "cookies")] pub use actix_http::cookie; pub use actix_http::Response as HttpResponse; -pub use actix_http::{body, http, Error, HttpMessage, ResponseError, Result}; +pub use actix_http::{body, Error, HttpMessage, ResponseError, Result}; pub use actix_rt as rt; pub use actix_web_codegen::*; diff --git a/src/test.rs b/src/test.rs index 8ba019445..08c80df40 100644 --- a/src/test.rs +++ b/src/test.rs @@ -8,8 +8,7 @@ use std::{fmt, net, thread, time}; use actix_codec::{AsyncRead, AsyncWrite, Framed}; #[cfg(feature = "cookies")] use actix_http::cookie::Cookie; -use actix_http::http::header::{ContentType, HeaderMap, IntoHeaderPair}; -use actix_http::http::{Method, StatusCode, Uri, Version}; +use actix_http::http::{HeaderMap, Method, StatusCode, Uri, Version}; use actix_http::test::TestRequest as HttpTestRequest; use actix_http::{ws, Extensions, HttpService, Request}; use actix_router::{Path, ResourceDef, Url}; @@ -31,6 +30,7 @@ use crate::app_service::AppInitServiceState; use crate::config::AppConfig; use crate::data::Data; use crate::dev::{Body, MessageBody, Payload, Server}; +use crate::http::header::{ContentType, IntoHeaderPair}; use crate::rmap::ResourceMap; use crate::service::{ServiceRequest, ServiceResponse}; use crate::{Error, HttpRequest, HttpResponse}; @@ -162,7 +162,7 @@ where let mut resp = app .call(req) .await - .unwrap_or_else(|_| panic!("read_response failed at application call")); + .unwrap_or_else(|e| panic!("read_response failed at application call: {}", e)); let mut body = resp.take_body(); let mut bytes = BytesMut::new(); @@ -254,8 +254,12 @@ where { let body = read_body(res).await; - serde_json::from_slice(&body) - .unwrap_or_else(|e| panic!("read_response_json failed during deserialization: {}", e)) + serde_json::from_slice(&body).unwrap_or_else(|e| { + panic!( + "read_response_json failed during deserialization of body: {:?}, {}", + body, e + ) + }) } pub async fn load_stream(mut stream: S) -> Result @@ -311,8 +315,12 @@ where { let body = read_response(app, req).await; - serde_json::from_slice(&body) - .unwrap_or_else(|_| panic!("read_response_json failed during deserialization")) + serde_json::from_slice(&body).unwrap_or_else(|_| { + panic!( + "read_response_json failed during deserialization of body: {:?}", + body + ) + }) } /// Test `Request` builder. diff --git a/src/types/header.rs b/src/types/header.rs new file mode 100644 index 000000000..1f8be707a --- /dev/null +++ b/src/types/header.rs @@ -0,0 +1,112 @@ +//! For header extractor helper documentation, see [`Header`](crate::types::Header). + +use std::{fmt, ops}; + +use actix_utils::future::{err, ok, Ready}; + +use crate::{ + dev::Payload, error::ParseError, extract::FromRequest, http::header::Header as ParseHeader, + HttpRequest, +}; + +/// Extract typed headers from the request. +/// +/// To extract a header, the inner type `T` must implement the +/// [`Header`](crate::http::header::Header) trait. +/// +/// # Examples +/// ``` +/// use actix_web::{get, web, http::header}; +/// +/// #[get("/")] +/// async fn index(date: web::Header) -> String { +/// format!("Request was sent at {}", date.to_string()) +/// } +/// ``` +#[derive(Clone, PartialEq, Eq, PartialOrd, Ord)] +pub struct Header(pub T); + +impl Header { + /// Unwrap into the inner `T` value. + pub fn into_inner(self) -> T { + self.0 + } +} + +impl ops::Deref for Header { + type Target = T; + + fn deref(&self) -> &T { + &self.0 + } +} + +impl ops::DerefMut for Header { + fn deref_mut(&mut self) -> &mut T { + &mut self.0 + } +} + +impl fmt::Debug for Header +where + T: fmt::Debug, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "Header: {:?}", self.0) + } +} + +impl fmt::Display for Header +where + T: fmt::Display, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Display::fmt(&self.0, f) + } +} + +impl FromRequest for Header +where + T: ParseHeader, +{ + type Error = ParseError; + type Future = Ready>; + type Config = (); + + #[inline] + fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future { + match ParseHeader::parse(req) { + Ok(header) => ok(Header(header)), + Err(e) => err(e), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::http::{header, Method}; + use crate::test::TestRequest; + + #[actix_rt::test] + async fn test_header_extract() { + let (req, mut pl) = TestRequest::default() + .insert_header((header::CONTENT_TYPE, mime::APPLICATION_JSON)) + .insert_header((header::ALLOW, header::Allow(vec![Method::GET]))) + .to_http_parts(); + + let s = Header::::from_request(&req, &mut pl) + .await + .unwrap(); + assert_eq!(s.into_inner().0, mime::APPLICATION_JSON); + + let s = Header::::from_request(&req, &mut pl) + .await + .unwrap(); + assert_eq!(s.into_inner().0, vec![Method::GET]); + + assert!(Header::::from_request(&req, &mut pl) + .await + .is_err()); + } +} diff --git a/src/types/mod.rs b/src/types/mod.rs index a062c351e..461d771eb 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -3,6 +3,7 @@ // TODO: review visibility mod either; pub(crate) mod form; +mod header; pub(crate) mod json; mod path; pub(crate) mod payload; @@ -11,6 +12,7 @@ pub(crate) mod readlines; pub use self::either::{Either, EitherExtractError}; pub use self::form::{Form, FormConfig}; +pub use self::header::Header; pub use self::json::{Json, JsonConfig}; pub use self::path::{Path, PathConfig}; pub use self::payload::{Payload, PayloadConfig}; From c54a0713de99ce0c437b4577e0752d3cfe3e077c Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Fri, 2 Apr 2021 08:26:59 +0100 Subject: [PATCH 33/79] migrate integration testing to new crate (#2112) --- CHANGES.md | 3 + Cargo.toml | 28 +- actix-files/Cargo.toml | 1 + actix-files/src/lib.rs | 6 +- actix-http-test/src/lib.rs | 20 +- actix-http/Cargo.toml | 2 +- actix-http/src/service.rs | 18 +- actix-test/CHANGES.md | 6 + actix-test/Cargo.toml | 35 ++ actix-test/LICENSE-APACHE | 1 + actix-test/LICENSE-MIT | 1 + actix-test/src/lib.rs | 472 +++++++++++++++++ actix-web-actors/Cargo.toml | 2 + actix-web-actors/tests/test_ws.rs | 6 +- actix-web-codegen/Cargo.toml | 4 +- actix-web-codegen/tests/test_macro.rs | 10 +- .../tests/trybuild/docstring-ok.rs | 6 +- .../trybuild/route-duplicate-method-fail.rs | 4 +- .../route-duplicate-method-fail.stderr | 6 +- .../trybuild/route-missing-method-fail.rs | 4 +- .../trybuild/route-missing-method-fail.stderr | 6 +- actix-web-codegen/tests/trybuild/route-ok.rs | 4 +- .../trybuild/route-unexpected-method-fail.rs | 4 +- .../route-unexpected-method-fail.stderr | 6 +- actix-web-codegen/tests/trybuild/simple.rs | 4 +- awc/Cargo.toml | 5 + {examples => awc/examples}/client.rs | 0 awc/src/middleware/redirect.rs | 7 +- awc/tests/test_client.rs | 47 +- benches/server.rs | 2 +- codecov.yml | 1 - src/config.rs | 13 +- src/data.rs | 55 +- src/lib.rs | 5 - src/middleware/compat.rs | 4 +- src/request.rs | 12 + src/server.rs | 15 +- src/service.rs | 6 + src/test.rs | 481 ++---------------- tests/test_httpserver.rs | 6 +- tests/test_server.rs | 115 +++-- 41 files changed, 789 insertions(+), 644 deletions(-) create mode 100644 actix-test/CHANGES.md create mode 100644 actix-test/Cargo.toml create mode 120000 actix-test/LICENSE-APACHE create mode 120000 actix-test/LICENSE-MIT create mode 100644 actix-test/src/lib.rs rename {examples => awc/examples}/client.rs (100%) diff --git a/CHANGES.md b/CHANGES.md index 8e7b22f58..5442be2e0 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -15,11 +15,14 @@ ### Removed * The `client` mod was removed. Clients should now use `awc` directly. [871ca5e4](https://github.com/actix/actix-web/commit/871ca5e4ae2bdc22d1ea02701c2992fa8d04aed7) +* Integration testing was moved to new `actix-test` crate. Namely these items from the `test` + module: `TestServer`, `TestServerConfig`, `start`, `start_with`, and `unused_addr`. [#2112] [#2067]: https://github.com/actix/actix-web/pull/2067 [#2093]: https://github.com/actix/actix-web/pull/2093 [#2094]: https://github.com/actix/actix-web/pull/2094 [#2097]: https://github.com/actix/actix-web/pull/2097 +[#2112]: https://github.com/actix/actix-web/pull/2112 ## 4.0.0-beta.4 - 2021-03-09 diff --git a/Cargo.toml b/Cargo.toml index 420e76405..190b645d4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,25 +36,26 @@ members = [ "actix-web-actors", "actix-web-codegen", "actix-http-test", + "actix-test", ] [features] default = ["compress", "cookies"] # content-encoding support -compress = ["actix-http/compress", "awc/compress"] +compress = ["actix-http/compress"] # support for cookies -cookies = ["actix-http/cookies", "awc/cookies"] +cookies = ["actix-http/cookies"] # secure cookies feature secure-cookies = ["actix-http/secure-cookies"] # openssl -openssl = ["tls-openssl", "actix-tls/accept", "actix-tls/openssl", "awc/openssl"] +openssl = ["actix-http/openssl", "actix-tls/accept", "actix-tls/openssl"] # rustls -rustls = ["tls-rustls", "actix-tls/accept", "actix-tls/rustls", "awc/rustls"] +rustls = ["actix-http/rustls", "actix-tls/accept", "actix-tls/rustls"] [[example]] name = "basic" @@ -72,10 +73,6 @@ required-features = ["compress", "cookies"] name = "on_connect" required-features = [] -[[example]] -name = "client" -required-features = ["rustls"] - [dependencies] actix-codec = "0.4.0-beta.1" actix-macros = "0.2.0" @@ -88,7 +85,6 @@ actix-tls = { version = "3.0.0-beta.5", default-features = false, optional = tru actix-web-codegen = "0.5.0-beta.2" actix-http = "3.0.0-beta.4" -awc = { version = "3.0.0-beta.3", default-features = false } ahash = "0.7" bytes = "1" @@ -109,11 +105,12 @@ serde_urlencoded = "0.7" smallvec = "1.6" socket2 = "0.4.0" time = { version = "0.2.23", default-features = false, features = ["std"] } -tls-openssl = { package = "openssl", version = "0.10.9", optional = true } -tls-rustls = { package = "rustls", version = "0.19.0", optional = true } url = "2.1" [dev-dependencies] +actix-test = { version = "0.0.1", features = ["openssl", "rustls"] } +awc = { version = "3.0.0-beta.3", features = ["openssl"] } + brotli2 = "0.3.2" criterion = "0.3" env_logger = "0.8" @@ -121,6 +118,8 @@ flate2 = "1.0.13" rand = "0.8" rcgen = "0.8" serde_derive = "1.0" +tls-openssl = { package = "openssl", version = "0.10.9" } +tls-rustls = { package = "rustls", version = "0.19.0" } [profile.release] lto = true @@ -128,13 +127,14 @@ opt-level = 3 codegen-units = 1 [patch.crates-io] -actix-web = { path = "." } +actix-files = { path = "actix-files" } actix-http = { path = "actix-http" } actix-http-test = { path = "actix-http-test" } +actix-multipart = { path = "actix-multipart" } +actix-test = { path = "actix-test" } +actix-web = { path = "." } actix-web-actors = { path = "actix-web-actors" } actix-web-codegen = { path = "actix-web-codegen" } -actix-multipart = { path = "actix-multipart" } -actix-files = { path = "actix-files" } awc = { path = "awc" } [[bench]] diff --git a/actix-files/Cargo.toml b/actix-files/Cargo.toml index 0cc02c6bd..6a60397a6 100644 --- a/actix-files/Cargo.toml +++ b/actix-files/Cargo.toml @@ -35,3 +35,4 @@ percent-encoding = "2.1" [dev-dependencies] actix-rt = "2.2" actix-web = "4.0.0-beta.4" +actix-test = "0.0.1" diff --git a/actix-files/src/lib.rs b/actix-files/src/lib.rs index f8583febe..24b903c04 100644 --- a/actix-files/src/lib.rs +++ b/actix-files/src/lib.rs @@ -413,7 +413,7 @@ mod tests { #[actix_rt::test] async fn test_named_file_content_range_headers() { - let srv = test::start(|| App::new().service(Files::new("/", "."))); + let srv = actix_test::start(|| App::new().service(Files::new("/", "."))); // Valid range header let response = srv @@ -438,7 +438,7 @@ mod tests { #[actix_rt::test] async fn test_named_file_content_length_headers() { - let srv = test::start(|| App::new().service(Files::new("/", "."))); + let srv = actix_test::start(|| App::new().service(Files::new("/", "."))); // Valid range header let response = srv @@ -477,7 +477,7 @@ mod tests { #[actix_rt::test] async fn test_head_content_length_headers() { - let srv = test::start(|| App::new().service(Files::new("/", "."))); + let srv = actix_test::start(|| App::new().service(Files::new("/", "."))); let response = srv.head("/tests/test.binary").send().await.unwrap(); diff --git a/actix-http-test/src/lib.rs b/actix-http-test/src/lib.rs index 9a5069c49..0f126c99a 100644 --- a/actix-http-test/src/lib.rs +++ b/actix-http-test/src/lib.rs @@ -117,16 +117,6 @@ pub async fn test_server_with_addr>( } } -/// Get first available unused address -pub fn unused_addr() -> net::SocketAddr { - let addr: net::SocketAddr = "127.0.0.1:0".parse().unwrap(); - let socket = Socket::new(Domain::IPV4, Type::STREAM, Some(Protocol::TCP)).unwrap(); - socket.bind(&addr.into()).unwrap(); - socket.set_reuse_address(true).unwrap(); - let tcp = net::TcpListener::from(socket); - tcp.local_addr().unwrap() -} - /// Test server controller pub struct TestServer { addr: net::SocketAddr, @@ -279,3 +269,13 @@ impl Drop for TestServer { self.stop() } } + +/// Get a localhost socket address with random, unused port. +pub fn unused_addr() -> net::SocketAddr { + let addr: net::SocketAddr = "127.0.0.1:0".parse().unwrap(); + let socket = Socket::new(Domain::IPV4, Type::STREAM, Some(Protocol::TCP)).unwrap(); + socket.bind(&addr.into()).unwrap(); + socket.set_reuse_address(true).unwrap(); + let tcp = net::TcpListener::from(socket); + tcp.local_addr().unwrap() +} diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index d9af75aa5..4857f083d 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -48,7 +48,7 @@ actix-service = "2.0.0-beta.4" actix-codec = "0.4.0-beta.1" actix-utils = "3.0.0-beta.4" actix-rt = "2.2" -actix-tls = "3.0.0-beta.5" +actix-tls = { version = "3.0.0-beta.5", features = ["accept", "connect"] } ahash = "0.7" base64 = "0.13" diff --git a/actix-http/src/service.rs b/actix-http/src/service.rs index fd97fb5ef..67a3ec42e 100644 --- a/actix-http/src/service.rs +++ b/actix-http/src/service.rs @@ -153,11 +153,14 @@ where S::InitError: fmt::Debug, S::Response: Into> + 'static, >::Future: 'static, + B: MessageBody + 'static, + X: ServiceFactory, X::Future: 'static, X::Error: Into, X::InitError: fmt::Debug, + U: ServiceFactory< (Request, Framed), Config = (), @@ -187,11 +190,12 @@ where #[cfg(feature = "openssl")] mod openssl { - use super::*; use actix_service::ServiceFactoryExt; use actix_tls::accept::openssl::{Acceptor, SslAcceptor, SslError, TlsStream}; use actix_tls::accept::TlsError; + use super::*; + impl HttpService, S, B, X, U> where S: ServiceFactory, @@ -200,11 +204,14 @@ mod openssl { S::InitError: fmt::Debug, S::Response: Into> + 'static, >::Future: 'static, + B: MessageBody + 'static, + X: ServiceFactory, X::Future: 'static, X::Error: Into, X::InitError: fmt::Debug, + U: ServiceFactory< (Request, Framed, h1::Codec>), Config = (), @@ -266,11 +273,14 @@ mod rustls { S::InitError: fmt::Debug, S::Response: Into> + 'static, >::Future: 'static, + B: MessageBody + 'static, + X: ServiceFactory, X::Future: 'static, X::Error: Into, X::InitError: fmt::Debug, + U: ServiceFactory< (Request, Framed, h1::Codec>), Config = (), @@ -280,7 +290,7 @@ mod rustls { U::Error: fmt::Display + Into, U::InitError: fmt::Debug, { - /// Create openssl based service + /// Create rustls based service pub fn rustls( self, mut config: ServerConfig, @@ -321,17 +331,21 @@ impl ServiceFactory<(T, Protocol, Option)> for HttpService where T: AsyncRead + AsyncWrite + Unpin + 'static, + S: ServiceFactory, S::Future: 'static, S::Error: Into + 'static, S::InitError: fmt::Debug, S::Response: Into> + 'static, >::Future: 'static, + B: MessageBody + 'static, + X: ServiceFactory, X::Future: 'static, X::Error: Into, X::InitError: fmt::Debug, + U: ServiceFactory<(Request, Framed), Config = (), Response = ()>, U::Future: 'static, U::Error: fmt::Display + Into, diff --git a/actix-test/CHANGES.md b/actix-test/CHANGES.md new file mode 100644 index 000000000..318f08f55 --- /dev/null +++ b/actix-test/CHANGES.md @@ -0,0 +1,6 @@ +# Changes + +## Unreleased - 2021-xx-xx +* Move integration testing structs from `actix-web`. [#???] + +[#???]: https://github.com/actix/actix-web/pull/??? diff --git a/actix-test/Cargo.toml b/actix-test/Cargo.toml new file mode 100644 index 000000000..884ec229c --- /dev/null +++ b/actix-test/Cargo.toml @@ -0,0 +1,35 @@ +[package] +name = "actix-test" +version = "0.0.1" +authors = ["Rob Ede "] +edition = "2018" +description = "Integration testing tools for Actix Web applications" +license = "MIT OR Apache-2.0" + +[features] +default = [] + +# rustls +rustls = ["tls-rustls", "actix-http/rustls"] + +# openssl +openssl = ["tls-openssl", "actix-http/openssl"] + +[dependencies] +actix-codec = "0.4.0-beta.1" +actix-http = { version = "3.0.0-beta.4", features = ["cookies"] } +actix-http-test = { version = "3.0.0-beta.3", features = [] } +actix-service = "2.0.0-beta.4" +actix-utils = "3.0.0-beta.2" +actix-web = { version = "4.0.0-beta.4", default-features = false, features = ["cookies"] } +actix-rt = "2.1" +awc = { version = "3.0.0-beta.3", default-features = false, features = ["cookies"] } + +futures-core = { version = "0.3.7", default-features = false, features = ["std"] } +futures-util = { version = "0.3.7", default-features = false, features = [] } +log = "0.4" +serde = { version = "1", features = ["derive"] } +serde_json = "1" +serde_urlencoded = "0.7" +tls-openssl = { package = "openssl", version = "0.10.9", optional = true } +tls-rustls = { package = "rustls", version = "0.19.0", optional = true } diff --git a/actix-test/LICENSE-APACHE b/actix-test/LICENSE-APACHE new file mode 120000 index 000000000..965b606f3 --- /dev/null +++ b/actix-test/LICENSE-APACHE @@ -0,0 +1 @@ +../LICENSE-APACHE \ No newline at end of file diff --git a/actix-test/LICENSE-MIT b/actix-test/LICENSE-MIT new file mode 120000 index 000000000..76219eb72 --- /dev/null +++ b/actix-test/LICENSE-MIT @@ -0,0 +1 @@ +../LICENSE-MIT \ No newline at end of file diff --git a/actix-test/src/lib.rs b/actix-test/src/lib.rs new file mode 100644 index 000000000..bd86c27ad --- /dev/null +++ b/actix-test/src/lib.rs @@ -0,0 +1,472 @@ +//! Integration testing tools for Actix Web applications. +//! +//! The main integration testing tool is [`TestServer`]. It spawns a real HTTP server on an +//! unused port and provides methods that use a real HTTP client. Therefore, it is much closer to +//! real-world cases than using `init_service`, which skips HTTP encoding and decoding. +//! +//! # Examples +//! ``` +//! use actix_web::{get, web, test, App, HttpResponse, Error, Responder}; +//! +//! #[get("/")] +//! async fn my_handler() -> Result { +//! Ok(HttpResponse::Ok()) +//! } +//! +//! #[actix_rt::test] +//! async fn test_example() { +//! let srv = actix_test::start(|| +//! App::new().service(my_handler) +//! ); +//! +//! let req = srv.get("/"); +//! let res = req.send().await.unwrap(); +//! +//! assert!(res.status().is_success()); +//! } +//! ``` + +#[cfg(feature = "openssl")] +extern crate tls_openssl as openssl; +#[cfg(feature = "rustls")] +extern crate tls_rustls as rustls; + +use std::{fmt, net, sync::mpsc, thread, time}; + +use actix_codec::{AsyncRead, AsyncWrite, Framed}; +pub use actix_http::test::TestBuffer; +use actix_http::{ + http::{HeaderMap, Method}, + ws, HttpService, Request, +}; +use actix_service::{map_config, IntoServiceFactory, ServiceFactory}; +use actix_web::{ + dev::{AppConfig, MessageBody, Server, Service}, + rt, web, Error, HttpResponse, +}; +use awc::{error::PayloadError, Client, ClientRequest, ClientResponse, Connector}; +use futures_core::Stream; + +pub use actix_http_test::unused_addr; +pub use actix_web::test::{ + call_service, default_service, init_service, load_stream, ok_service, read_body, + read_body_json, read_response, read_response_json, TestRequest, +}; + +/// Start default [`TestServer`]. +/// +/// # Examples +/// ``` +/// use actix_web::{get, web, test, App, HttpResponse, Error, Responder}; +/// +/// #[get("/")] +/// async fn my_handler() -> Result { +/// Ok(HttpResponse::Ok()) +/// } +/// +/// #[actix_rt::test] +/// async fn test_example() { +/// let srv = actix_test::start(|| +/// App::new().service(my_handler) +/// ); +/// +/// let req = srv.get("/"); +/// let res = req.send().await.unwrap(); +/// +/// assert!(res.status().is_success()); +/// } +/// ``` +pub fn start(factory: F) -> TestServer +where + F: Fn() -> I + Send + Clone + 'static, + I: IntoServiceFactory, + S: ServiceFactory + 'static, + S::Error: Into + 'static, + S::InitError: fmt::Debug, + S::Response: Into> + 'static, + >::Future: 'static, + B: MessageBody + 'static, +{ + start_with(TestServerConfig::default(), factory) +} + +/// Start test server with custom configuration +/// +/// Check [`TestServerConfig`] docs for configuration options. +/// +/// # Examples +/// ``` +/// use actix_web::{get, web, test, App, HttpResponse, Error, Responder}; +/// +/// #[get("/")] +/// async fn my_handler() -> Result { +/// Ok(HttpResponse::Ok()) +/// } +/// +/// #[actix_rt::test] +/// async fn test_example() { +/// let srv = actix_test::start_with(actix_test::config().h1(), || +/// App::new().service(my_handler) +/// ); +/// +/// let req = srv.get("/"); +/// let res = req.send().await.unwrap(); +/// +/// assert!(res.status().is_success()); +/// } +/// ``` +pub fn start_with(cfg: TestServerConfig, factory: F) -> TestServer +where + F: Fn() -> I + Send + Clone + 'static, + I: IntoServiceFactory, + S: ServiceFactory + 'static, + S::Error: Into + 'static, + S::InitError: fmt::Debug, + S::Response: Into> + 'static, + >::Future: 'static, + B: MessageBody + 'static, +{ + let (tx, rx) = mpsc::channel(); + + let tls = match cfg.stream { + StreamType::Tcp => false, + #[cfg(feature = "openssl")] + StreamType::Openssl(_) => true, + #[cfg(feature = "rustls")] + StreamType::Rustls(_) => true, + }; + + // run server in separate thread + thread::spawn(move || { + let sys = rt::System::new(); + let tcp = net::TcpListener::bind("127.0.0.1:0").unwrap(); + let local_addr = tcp.local_addr().unwrap(); + let factory = factory.clone(); + let srv_cfg = cfg.clone(); + let timeout = cfg.client_timeout; + let builder = Server::build().workers(1).disable_signals(); + + let srv = match srv_cfg.stream { + StreamType::Tcp => match srv_cfg.tp { + HttpVer::Http1 => builder.listen("test", tcp, move || { + let app_cfg = + AppConfig::__priv_test_new(false, local_addr.to_string(), local_addr); + HttpService::build() + .client_timeout(timeout) + .h1(map_config(factory(), move |_| app_cfg.clone())) + .tcp() + }), + HttpVer::Http2 => builder.listen("test", tcp, move || { + let app_cfg = + AppConfig::__priv_test_new(false, local_addr.to_string(), local_addr); + HttpService::build() + .client_timeout(timeout) + .h2(map_config(factory(), move |_| app_cfg.clone())) + .tcp() + }), + HttpVer::Both => builder.listen("test", tcp, move || { + let app_cfg = + AppConfig::__priv_test_new(false, local_addr.to_string(), local_addr); + HttpService::build() + .client_timeout(timeout) + .finish(map_config(factory(), move |_| app_cfg.clone())) + .tcp() + }), + }, + #[cfg(feature = "openssl")] + StreamType::Openssl(acceptor) => match cfg.tp { + HttpVer::Http1 => builder.listen("test", tcp, move || { + let app_cfg = + AppConfig::__priv_test_new(false, local_addr.to_string(), local_addr); + HttpService::build() + .client_timeout(timeout) + .h1(map_config(factory(), move |_| app_cfg.clone())) + .openssl(acceptor.clone()) + }), + HttpVer::Http2 => builder.listen("test", tcp, move || { + let app_cfg = + AppConfig::__priv_test_new(false, local_addr.to_string(), local_addr); + HttpService::build() + .client_timeout(timeout) + .h2(map_config(factory(), move |_| app_cfg.clone())) + .openssl(acceptor.clone()) + }), + HttpVer::Both => builder.listen("test", tcp, move || { + let app_cfg = + AppConfig::__priv_test_new(false, local_addr.to_string(), local_addr); + HttpService::build() + .client_timeout(timeout) + .finish(map_config(factory(), move |_| app_cfg.clone())) + .openssl(acceptor.clone()) + }), + }, + #[cfg(feature = "rustls")] + StreamType::Rustls(config) => match cfg.tp { + HttpVer::Http1 => builder.listen("test", tcp, move || { + let app_cfg = + AppConfig::__priv_test_new(false, local_addr.to_string(), local_addr); + HttpService::build() + .client_timeout(timeout) + .h1(map_config(factory(), move |_| app_cfg.clone())) + .rustls(config.clone()) + }), + HttpVer::Http2 => builder.listen("test", tcp, move || { + let app_cfg = + AppConfig::__priv_test_new(false, local_addr.to_string(), local_addr); + HttpService::build() + .client_timeout(timeout) + .h2(map_config(factory(), move |_| app_cfg.clone())) + .rustls(config.clone()) + }), + HttpVer::Both => builder.listen("test", tcp, move || { + let app_cfg = + AppConfig::__priv_test_new(false, local_addr.to_string(), local_addr); + HttpService::build() + .client_timeout(timeout) + .finish(map_config(factory(), move |_| app_cfg.clone())) + .rustls(config.clone()) + }), + }, + } + .unwrap(); + + sys.block_on(async { + let srv = srv.run(); + tx.send((rt::System::current(), srv, local_addr)).unwrap(); + }); + + sys.run() + }); + + let (system, server, addr) = rx.recv().unwrap(); + + let client = { + let connector = { + #[cfg(feature = "openssl")] + { + use openssl::ssl::{SslConnector, SslMethod, SslVerifyMode}; + + let mut builder = SslConnector::builder(SslMethod::tls()).unwrap(); + builder.set_verify(SslVerifyMode::NONE); + let _ = builder + .set_alpn_protos(b"\x02h2\x08http/1.1") + .map_err(|e| log::error!("Can not set alpn protocol: {:?}", e)); + Connector::new() + .conn_lifetime(time::Duration::from_secs(0)) + .timeout(time::Duration::from_millis(30000)) + .ssl(builder.build()) + } + #[cfg(not(feature = "openssl"))] + { + Connector::new() + .conn_lifetime(time::Duration::from_secs(0)) + .timeout(time::Duration::from_millis(30000)) + } + }; + + Client::builder().connector(connector).finish() + }; + + TestServer { + addr, + client, + system, + tls, + server, + } +} + +#[derive(Debug, Clone)] +enum HttpVer { + Http1, + Http2, + Both, +} + +#[derive(Clone)] +enum StreamType { + Tcp, + #[cfg(feature = "openssl")] + Openssl(openssl::ssl::SslAcceptor), + #[cfg(feature = "rustls")] + Rustls(rustls::ServerConfig), +} + +/// Create default test server config. +pub fn config() -> TestServerConfig { + TestServerConfig::default() +} + +#[derive(Clone)] +pub struct TestServerConfig { + tp: HttpVer, + stream: StreamType, + client_timeout: u64, +} + +impl Default for TestServerConfig { + fn default() -> Self { + TestServerConfig::new() + } +} + +impl TestServerConfig { + /// Create default server configuration + pub(crate) fn new() -> TestServerConfig { + TestServerConfig { + tp: HttpVer::Both, + stream: StreamType::Tcp, + client_timeout: 5000, + } + } + + /// Accept HTTP/1.1 only. + pub fn h1(mut self) -> Self { + self.tp = HttpVer::Http1; + self + } + + /// Accept HTTP/2 only. + pub fn h2(mut self) -> Self { + self.tp = HttpVer::Http2; + self + } + + /// Accept secure connections via OpenSSL. + #[cfg(feature = "openssl")] + pub fn openssl(mut self, acceptor: openssl::ssl::SslAcceptor) -> Self { + self.stream = StreamType::Openssl(acceptor); + self + } + + /// Accept secure connections via Rustls. + #[cfg(feature = "rustls")] + pub fn rustls(mut self, config: rustls::ServerConfig) -> Self { + self.stream = StreamType::Rustls(config); + self + } + + /// Set client timeout in milliseconds for first request. + pub fn client_timeout(mut self, val: u64) -> Self { + self.client_timeout = val; + self + } +} + +/// A basic HTTP server controller that simplifies the process of writing integration tests for +/// Actix Web applications. +/// +/// See [`start`] for usage example. +pub struct TestServer { + addr: net::SocketAddr, + client: awc::Client, + system: rt::System, + tls: bool, + server: Server, +} + +impl TestServer { + /// Construct test server url + pub fn addr(&self) -> net::SocketAddr { + self.addr + } + + /// Construct test server url + pub fn url(&self, uri: &str) -> String { + let scheme = if self.tls { "https" } else { "http" }; + + if uri.starts_with('/') { + format!("{}://localhost:{}{}", scheme, self.addr.port(), uri) + } else { + format!("{}://localhost:{}/{}", scheme, self.addr.port(), uri) + } + } + + /// Create `GET` request. + pub fn get(&self, path: impl AsRef) -> ClientRequest { + self.client.get(self.url(path.as_ref()).as_str()) + } + + /// Create `POST` request. + pub fn post(&self, path: impl AsRef) -> ClientRequest { + self.client.post(self.url(path.as_ref()).as_str()) + } + + /// Create `HEAD` request. + pub fn head(&self, path: impl AsRef) -> ClientRequest { + self.client.head(self.url(path.as_ref()).as_str()) + } + + /// Create `PUT` request. + pub fn put(&self, path: impl AsRef) -> ClientRequest { + self.client.put(self.url(path.as_ref()).as_str()) + } + + /// Create `PATCH` request. + pub fn patch(&self, path: impl AsRef) -> ClientRequest { + self.client.patch(self.url(path.as_ref()).as_str()) + } + + /// Create `DELETE` request. + pub fn delete(&self, path: impl AsRef) -> ClientRequest { + self.client.delete(self.url(path.as_ref()).as_str()) + } + + /// Create `OPTIONS` request. + pub fn options(&self, path: impl AsRef) -> ClientRequest { + self.client.options(self.url(path.as_ref()).as_str()) + } + + /// Connect request with given method and path. + pub fn request(&self, method: Method, path: impl AsRef) -> ClientRequest { + self.client.request(method, path.as_ref()) + } + + pub async fn load_body( + &mut self, + mut response: ClientResponse, + ) -> Result + where + S: Stream> + Unpin + 'static, + { + response.body().limit(10_485_760).await + } + + /// Connect to WebSocket server at a given path. + pub async fn ws_at( + &mut self, + path: &str, + ) -> Result, awc::error::WsClientError> { + let url = self.url(path); + let connect = self.client.ws(url).connect(); + connect.await.map(|(_, framed)| framed) + } + + /// Connect to a WebSocket server. + pub async fn ws( + &mut self, + ) -> Result, awc::error::WsClientError> { + self.ws_at("/").await + } + + /// Get default HeaderMap of Client. + /// + /// Returns Some(&mut HeaderMap) when Client object is unique + /// (No other clone of client exists at the same time). + pub fn client_headers(&mut self) -> Option<&mut HeaderMap> { + self.client.headers() + } + + /// Gracefully stop HTTP server. + pub async fn stop(self) { + self.server.stop(true).await; + self.system.stop(); + rt::time::sleep(time::Duration::from_millis(100)).await; + } +} + +impl Drop for TestServer { + fn drop(&mut self) { + self.system.stop() + } +} diff --git a/actix-web-actors/Cargo.toml b/actix-web-actors/Cargo.toml index e60d4301c..4fd144195 100644 --- a/actix-web-actors/Cargo.toml +++ b/actix-web-actors/Cargo.toml @@ -29,6 +29,8 @@ tokio = { version = "1", features = ["sync"] } [dev-dependencies] actix-rt = "2.2" +actix-test = "0.0.1" + awc = { version = "3.0.0-beta.3", default-features = false } env_logger = "0.8" futures-util = { version = "0.3.7", default-features = false } diff --git a/actix-web-actors/tests/test_ws.rs b/actix-web-actors/tests/test_ws.rs index 4ffedb2ef..0a8e50b3e 100644 --- a/actix-web-actors/tests/test_ws.rs +++ b/actix-web-actors/tests/test_ws.rs @@ -1,7 +1,7 @@ use actix::prelude::*; use actix_web::{ http::{header, StatusCode}, - test, web, App, HttpRequest, HttpResponse, + web, App, HttpRequest, HttpResponse, }; use actix_web_actors::*; use bytes::Bytes; @@ -27,7 +27,7 @@ impl StreamHandler> for Ws { #[actix_rt::test] async fn test_simple() { - let mut srv = test::start(|| { + let mut srv = actix_test::start(|| { App::new().service(web::resource("/").to( |req: HttpRequest, stream: web::Payload| async move { ws::start(Ws, &req, stream) }, )) @@ -62,7 +62,7 @@ async fn test_simple() { #[actix_rt::test] async fn test_with_credentials() { - let mut srv = test::start(|| { + let mut srv = actix_test::start(|| { App::new().service(web::resource("/").to( |req: HttpRequest, stream: web::Payload| async move { if req.headers().contains_key("Authorization") { diff --git a/actix-web-codegen/Cargo.toml b/actix-web-codegen/Cargo.toml index a513b820b..daea993d0 100644 --- a/actix-web-codegen/Cargo.toml +++ b/actix-web-codegen/Cargo.toml @@ -20,8 +20,10 @@ proc-macro2 = "1" [dev-dependencies] actix-rt = "2.2" -actix-web = "4.0.0-beta.4" +actix-test = "0.0.1" actix-utils = "3.0.0-beta.4" +actix-web = "4.0.0-beta.4" + futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] } trybuild = "1" rustversion = "1" diff --git a/actix-web-codegen/tests/test_macro.rs b/actix-web-codegen/tests/test_macro.rs index 4e06e15ed..7b95edba2 100644 --- a/actix-web-codegen/tests/test_macro.rs +++ b/actix-web-codegen/tests/test_macro.rs @@ -4,7 +4,7 @@ use std::task::{Context, Poll}; use actix_utils::future; use actix_web::dev::{Service, ServiceRequest, ServiceResponse, Transform}; use actix_web::http::header::{HeaderName, HeaderValue}; -use actix_web::{http, test, web::Path, App, Error, HttpResponse, Responder}; +use actix_web::{http, web::Path, App, Error, HttpResponse, Responder}; use actix_web_codegen::{connect, delete, get, head, options, patch, post, put, route, trace}; use futures_core::future::LocalBoxFuture; @@ -149,7 +149,7 @@ async fn get_wrap(_: Path) -> impl Responder { #[actix_rt::test] async fn test_params() { - let srv = test::start(|| { + let srv = actix_test::start(|| { App::new() .service(get_param_test) .service(put_param_test) @@ -171,7 +171,7 @@ async fn test_params() { #[actix_rt::test] async fn test_body() { - let srv = test::start(|| { + let srv = actix_test::start(|| { App::new() .service(post_test) .service(put_test) @@ -245,7 +245,7 @@ async fn test_body() { #[actix_rt::test] async fn test_auto_async() { - let srv = test::start(|| App::new().service(auto_async)); + let srv = actix_test::start(|| App::new().service(auto_async)); let request = srv.request(http::Method::GET, srv.url("/test")); let response = request.send().await.unwrap(); @@ -254,7 +254,7 @@ async fn test_auto_async() { #[actix_rt::test] async fn test_wrap() { - let srv = test::start(|| App::new().service(get_wrap)); + let srv = actix_test::start(|| App::new().service(get_wrap)); let request = srv.request(http::Method::GET, srv.url("/test/wrap")); let response = request.send().await.unwrap(); diff --git a/actix-web-codegen/tests/trybuild/docstring-ok.rs b/actix-web-codegen/tests/trybuild/docstring-ok.rs index 2910976c7..4cf310be5 100644 --- a/actix-web-codegen/tests/trybuild/docstring-ok.rs +++ b/actix-web-codegen/tests/trybuild/docstring-ok.rs @@ -1,7 +1,7 @@ -use actix_web::{Responder, HttpResponse, App, test}; +use actix_web::{Responder, HttpResponse, App}; use actix_web_codegen::*; -/// Docstrings shouldn't break anything. +/// doc comments shouldn't break anything #[get("/")] async fn index() -> impl Responder { HttpResponse::Ok() @@ -9,7 +9,7 @@ async fn index() -> impl Responder { #[actix_web::main] async fn main() { - let srv = test::start(|| App::new().service(index)); + let srv = actix_test::start(|| App::new().service(index)); let request = srv.get("/"); let response = request.send().await.unwrap(); diff --git a/actix-web-codegen/tests/trybuild/route-duplicate-method-fail.rs b/actix-web-codegen/tests/trybuild/route-duplicate-method-fail.rs index 9a38050f7..9322b4895 100644 --- a/actix-web-codegen/tests/trybuild/route-duplicate-method-fail.rs +++ b/actix-web-codegen/tests/trybuild/route-duplicate-method-fail.rs @@ -7,9 +7,9 @@ async fn index() -> String { #[actix_web::main] async fn main() { - use actix_web::{App, test}; + use actix_web::App; - let srv = test::start(|| App::new().service(index)); + let srv = actix_test::start(|| App::new().service(index)); let request = srv.get("/"); let response = request.send().await.unwrap(); diff --git a/actix-web-codegen/tests/trybuild/route-duplicate-method-fail.stderr b/actix-web-codegen/tests/trybuild/route-duplicate-method-fail.stderr index f3eda68af..abdc895d7 100644 --- a/actix-web-codegen/tests/trybuild/route-duplicate-method-fail.stderr +++ b/actix-web-codegen/tests/trybuild/route-duplicate-method-fail.stderr @@ -5,7 +5,7 @@ error: HTTP method defined more than once: `GET` | ^^^^^ error[E0425]: cannot find value `index` in this scope - --> $DIR/route-duplicate-method-fail.rs:12:49 + --> $DIR/route-duplicate-method-fail.rs:12:55 | -12 | let srv = test::start(|| App::new().service(index)); - | ^^^^^ not found in this scope +12 | let srv = actix_test::start(|| App::new().service(index)); + | ^^^^^ not found in this scope diff --git a/actix-web-codegen/tests/trybuild/route-missing-method-fail.rs b/actix-web-codegen/tests/trybuild/route-missing-method-fail.rs index ce87a55a4..cd43c0669 100644 --- a/actix-web-codegen/tests/trybuild/route-missing-method-fail.rs +++ b/actix-web-codegen/tests/trybuild/route-missing-method-fail.rs @@ -7,9 +7,9 @@ async fn index() -> String { #[actix_web::main] async fn main() { - use actix_web::{App, test}; + use actix_web::App; - let srv = test::start(|| App::new().service(index)); + let srv = actix_test::start(|| App::new().service(index)); let request = srv.get("/"); let response = request.send().await.unwrap(); diff --git a/actix-web-codegen/tests/trybuild/route-missing-method-fail.stderr b/actix-web-codegen/tests/trybuild/route-missing-method-fail.stderr index 0518a61ed..0e16b5e27 100644 --- a/actix-web-codegen/tests/trybuild/route-missing-method-fail.stderr +++ b/actix-web-codegen/tests/trybuild/route-missing-method-fail.stderr @@ -7,7 +7,7 @@ error: The #[route(..)] macro requires at least one `method` attribute = note: this error originates in an attribute macro (in Nightly builds, run with -Z macro-backtrace for more info) error[E0425]: cannot find value `index` in this scope - --> $DIR/route-missing-method-fail.rs:12:49 + --> $DIR/route-missing-method-fail.rs:12:55 | -12 | let srv = test::start(|| App::new().service(index)); - | ^^^^^ not found in this scope +12 | let srv = actix_test::start(|| App::new().service(index)); + | ^^^^^ not found in this scope diff --git a/actix-web-codegen/tests/trybuild/route-ok.rs b/actix-web-codegen/tests/trybuild/route-ok.rs index c4f679604..e1082e88e 100644 --- a/actix-web-codegen/tests/trybuild/route-ok.rs +++ b/actix-web-codegen/tests/trybuild/route-ok.rs @@ -7,9 +7,9 @@ async fn index() -> String { #[actix_web::main] async fn main() { - use actix_web::{App, test}; + use actix_web::App; - let srv = test::start(|| App::new().service(index)); + let srv = actix_test::start(|| App::new().service(index)); let request = srv.get("/"); let response = request.send().await.unwrap(); diff --git a/actix-web-codegen/tests/trybuild/route-unexpected-method-fail.rs b/actix-web-codegen/tests/trybuild/route-unexpected-method-fail.rs index 28cd1344c..1a50e01bc 100644 --- a/actix-web-codegen/tests/trybuild/route-unexpected-method-fail.rs +++ b/actix-web-codegen/tests/trybuild/route-unexpected-method-fail.rs @@ -7,9 +7,9 @@ async fn index() -> String { #[actix_web::main] async fn main() { - use actix_web::{App, test}; + use actix_web::App; - let srv = test::start(|| App::new().service(index)); + let srv = actix_test::start(|| App::new().service(index)); let request = srv.get("/"); let response = request.send().await.unwrap(); diff --git a/actix-web-codegen/tests/trybuild/route-unexpected-method-fail.stderr b/actix-web-codegen/tests/trybuild/route-unexpected-method-fail.stderr index 9d87f310b..a638a96a6 100644 --- a/actix-web-codegen/tests/trybuild/route-unexpected-method-fail.stderr +++ b/actix-web-codegen/tests/trybuild/route-unexpected-method-fail.stderr @@ -5,7 +5,7 @@ error: Unexpected HTTP method: `UNEXPECTED` | ^^^^^^^^^^^^ error[E0425]: cannot find value `index` in this scope - --> $DIR/route-unexpected-method-fail.rs:12:49 + --> $DIR/route-unexpected-method-fail.rs:12:55 | -12 | let srv = test::start(|| App::new().service(index)); - | ^^^^^ not found in this scope +12 | let srv = actix_test::start(|| App::new().service(index)); + | ^^^^^ not found in this scope diff --git a/actix-web-codegen/tests/trybuild/simple.rs b/actix-web-codegen/tests/trybuild/simple.rs index 761b04905..8170edbfa 100644 --- a/actix-web-codegen/tests/trybuild/simple.rs +++ b/actix-web-codegen/tests/trybuild/simple.rs @@ -1,4 +1,4 @@ -use actix_web::{Responder, HttpResponse, App, test}; +use actix_web::{Responder, HttpResponse, App}; use actix_web_codegen::*; #[get("/config")] @@ -8,7 +8,7 @@ async fn config() -> impl Responder { #[actix_web::main] async fn main() { - let srv = test::start(|| App::new().service(config)); + let srv = actix_test::start(|| App::new().service(config)); let request = srv.get("/config"); let response = request.send().await.unwrap(); diff --git a/awc/Cargo.toml b/awc/Cargo.toml index d0e781e55..78110baed 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -72,6 +72,7 @@ actix-http-test = { version = "3.0.0-beta.3", features = ["openssl"] } actix-utils = "3.0.0-beta.4" actix-server = "2.0.0-beta.3" actix-tls = { version = "3.0.0-beta.5", features = ["openssl", "rustls"] } +actix-test = { version = "0.0.1", features = ["openssl", "rustls"] } brotli2 = "0.3.2" env_logger = "0.8" @@ -79,3 +80,7 @@ flate2 = "1.0.13" futures-util = { version = "0.3.7", default-features = false } rcgen = "0.8" webpki = "0.21" + +[[example]] +name = "client" +required-features = ["rustls"] diff --git a/examples/client.rs b/awc/examples/client.rs similarity index 100% rename from examples/client.rs rename to awc/examples/client.rs diff --git a/awc/src/middleware/redirect.rs b/awc/src/middleware/redirect.rs index 62ea1d0ac..ae09edf9c 100644 --- a/awc/src/middleware/redirect.rs +++ b/awc/src/middleware/redirect.rs @@ -283,10 +283,9 @@ fn rebuild_uri(res: &ClientResponse, org_uri: Uri) -> Result(HttpResponse::BadRequest()) @@ -323,7 +322,7 @@ mod tests { .connector(crate::Connector::new()) .finish(); - let srv = start(|| { + let srv = actix_test::start(|| { App::new() .service(web::resource("/").route(web::to(|| async { Ok::<_, Error>( diff --git a/awc/tests/test_client.rs b/awc/tests/test_client.rs index 9682bc254..a393a6415 100644 --- a/awc/tests/test_client.rs +++ b/awc/tests/test_client.rs @@ -24,7 +24,7 @@ use actix_web::{ dev::{AppConfig, BodyEncoding}, http::{header, Cookie}, middleware::Compress, - test, web, App, Error, HttpMessage, HttpRequest, HttpResponse, + web, App, Error, HttpMessage, HttpRequest, HttpResponse, }; use awc::error::{JsonPayloadError, PayloadError, SendRequestError}; @@ -52,7 +52,7 @@ const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ #[actix_rt::test] async fn test_simple() { - let srv = test::start(|| { + let srv = actix_test::start(|| { App::new().service(web::resource("/").route(web::to(|| HttpResponse::Ok().body(STR)))) }); @@ -78,7 +78,7 @@ async fn test_simple() { #[actix_rt::test] async fn test_json() { - let srv = test::start(|| { + let srv = actix_test::start(|| { App::new().service( web::resource("/").route(web::to(|_: web::Json| HttpResponse::Ok())), ) @@ -94,7 +94,7 @@ async fn test_json() { #[actix_rt::test] async fn test_form() { - let srv = test::start(|| { + let srv = actix_test::start(|| { App::new().service(web::resource("/").route(web::to( |_: web::Form>| HttpResponse::Ok(), ))) @@ -113,7 +113,7 @@ async fn test_form() { #[actix_rt::test] async fn test_timeout() { - let srv = test::start(|| { + let srv = actix_test::start(|| { App::new().service(web::resource("/").route(web::to(|| async { actix_rt::time::sleep(Duration::from_millis(200)).await; Ok::<_, Error>(HttpResponse::Ok().body(STR)) @@ -138,7 +138,7 @@ async fn test_timeout() { #[actix_rt::test] async fn test_timeout_override() { - let srv = test::start(|| { + let srv = actix_test::start(|| { App::new().service(web::resource("/").route(web::to(|| async { actix_rt::time::sleep(Duration::from_millis(200)).await; Ok::<_, Error>(HttpResponse::Ok().body(STR)) @@ -162,7 +162,7 @@ async fn test_timeout_override() { async fn test_response_timeout() { use futures_util::stream::{once, StreamExt as _}; - let srv = test::start(|| { + let srv = actix_test::start(|| { App::new().service(web::resource("/").route(web::to(|| async { Ok::<_, Error>( HttpResponse::Ok() @@ -444,7 +444,7 @@ async fn test_connection_wait_queue_force_close() { #[actix_rt::test] async fn test_with_query_parameter() { - let srv = test::start(|| { + let srv = actix_test::start(|| { App::new().service(web::resource("/").to(|req: HttpRequest| { if req.query_string().contains("qp") { HttpResponse::Ok() @@ -464,7 +464,7 @@ async fn test_with_query_parameter() { #[actix_rt::test] async fn test_no_decompress() { - let srv = test::start(|| { + let srv = actix_test::start(|| { App::new() .wrap(Compress::default()) .service(web::resource("/").route(web::to(|| { @@ -508,7 +508,7 @@ async fn test_no_decompress() { #[actix_rt::test] async fn test_client_gzip_encoding() { - let srv = test::start(|| { + let srv = actix_test::start(|| { App::new().service(web::resource("/").route(web::to(|| { let mut e = GzEncoder::new(Vec::new(), Compression::default()); e.write_all(STR.as_ref()).unwrap(); @@ -531,7 +531,7 @@ async fn test_client_gzip_encoding() { #[actix_rt::test] async fn test_client_gzip_encoding_large() { - let srv = test::start(|| { + let srv = actix_test::start(|| { App::new().service(web::resource("/").route(web::to(|| { let mut e = GzEncoder::new(Vec::new(), Compression::default()); e.write_all(STR.repeat(10).as_ref()).unwrap(); @@ -560,7 +560,7 @@ async fn test_client_gzip_encoding_large_random() { .map(char::from) .collect::(); - let srv = test::start(|| { + let srv = actix_test::start(|| { App::new().service(web::resource("/").route(web::to(|data: Bytes| { let mut e = GzEncoder::new(Vec::new(), Compression::default()); e.write_all(&data).unwrap(); @@ -582,7 +582,7 @@ async fn test_client_gzip_encoding_large_random() { #[actix_rt::test] async fn test_client_brotli_encoding() { - let srv = test::start(|| { + let srv = actix_test::start(|| { App::new().service(web::resource("/").route(web::to(|data: Bytes| { let mut e = BrotliEncoder::new(Vec::new(), 5); e.write_all(&data).unwrap(); @@ -610,7 +610,7 @@ async fn test_client_brotli_encoding_large_random() { .map(char::from) .collect::(); - let srv = test::start(|| { + let srv = actix_test::start(|| { App::new().service(web::resource("/").route(web::to(|data: Bytes| { let mut e = BrotliEncoder::new(Vec::new(), 5); e.write_all(&data).unwrap(); @@ -633,7 +633,7 @@ async fn test_client_brotli_encoding_large_random() { #[actix_rt::test] async fn test_client_deflate_encoding() { - let srv = test::start(|| { + let srv = actix_test::start(|| { App::new().default_service(web::to(|body: Bytes| { HttpResponse::Ok() .encoding(http::ContentEncoding::Br) @@ -658,7 +658,7 @@ async fn test_client_deflate_encoding_large_random() { .take(70_000) .collect::(); - let srv = test::start(|| { + let srv = actix_test::start(|| { App::new().default_service(web::to(|body: Bytes| { HttpResponse::Ok() .encoding(http::ContentEncoding::Br) @@ -677,7 +677,7 @@ async fn test_client_deflate_encoding_large_random() { #[actix_rt::test] async fn test_client_streaming_explicit() { - let srv = test::start(|| { + let srv = actix_test::start(|| { App::new().default_service(web::to(|body: web::Payload| { HttpResponse::Ok() .encoding(http::ContentEncoding::Identity) @@ -698,7 +698,7 @@ async fn test_client_streaming_explicit() { #[actix_rt::test] async fn test_body_streaming_implicit() { - let srv = test::start(|| { + let srv = actix_test::start(|| { App::new().default_service(web::to(|| { let body = stream::once(async { Ok::<_, actix_http::Error>(Bytes::from_static(STR.as_bytes())) @@ -734,7 +734,7 @@ async fn test_client_cookie_handling() { let cookie1b = cookie1.clone(); let cookie2b = cookie2.clone(); - let srv = test::start(move || { + let srv = actix_test::start(move || { let cookie1 = cookie1b.clone(); let cookie2 = cookie2b.clone(); @@ -790,8 +790,7 @@ async fn test_client_cookie_handling() { #[actix_rt::test] async fn client_unread_response() { - let addr = test::unused_addr(); - + let addr = actix_test::unused_addr(); let lst = std::net::TcpListener::bind(addr).unwrap(); std::thread::spawn(move || { @@ -820,7 +819,7 @@ async fn client_unread_response() { #[actix_rt::test] async fn client_basic_auth() { - let srv = test::start(|| { + let srv = actix_test::start(|| { App::new().route( "/", web::to(|req: HttpRequest| { @@ -848,7 +847,7 @@ async fn client_basic_auth() { #[actix_rt::test] async fn client_bearer_auth() { - let srv = test::start(|| { + let srv = actix_test::start(|| { App::new().route( "/", web::to(|req: HttpRequest| { @@ -878,7 +877,7 @@ async fn client_bearer_auth() { async fn test_local_address() { let ip = IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)); - let srv = test::start(move || { + let srv = actix_test::start(move || { App::new().service(web::resource("/").route(web::to( move |req: HttpRequest| async move { assert_eq!(req.peer_addr().unwrap().ip(), ip); diff --git a/benches/server.rs b/benches/server.rs index 9dd540a73..c6695817f 100644 --- a/benches/server.rs +++ b/benches/server.rs @@ -32,7 +32,7 @@ fn bench_async_burst(c: &mut Criterion) { let rt = actix_rt::System::new(); let srv = rt.block_on(async { - test::start(|| { + actix_test::start(|| { App::new() .service(web::resource("/").route(web::to(|| HttpResponse::Ok().body(STR)))) }) diff --git a/codecov.yml b/codecov.yml index e45672bfc..d80835c7f 100644 --- a/codecov.yml +++ b/codecov.yml @@ -11,6 +11,5 @@ coverage: ignore: # ignore code coverage on following paths - "**/tests" - - "test-server" - "**/benches" - "**/examples" diff --git a/src/config.rs b/src/config.rs index cd14eb4cc..4bd76f2b7 100644 --- a/src/config.rs +++ b/src/config.rs @@ -103,8 +103,8 @@ impl AppService { } } -/// Application connection config -#[derive(Clone)] +/// Application connection config. +#[derive(Debug, Clone)] pub struct AppConfig { secure: bool, host: String, @@ -112,10 +112,15 @@ pub struct AppConfig { } impl AppConfig { - pub(crate) fn new(secure: bool, addr: SocketAddr, host: String) -> Self { + pub(crate) fn new(secure: bool, host: String, addr: SocketAddr) -> Self { AppConfig { secure, host, addr } } + #[doc(hidden)] + pub fn __priv_test_new(secure: bool, host: String, addr: SocketAddr) -> Self { + AppConfig::new(secure, host, addr) + } + /// Server host name. /// /// Host name is used by application router as a hostname for url generation. @@ -142,8 +147,8 @@ impl Default for AppConfig { fn default() -> Self { AppConfig::new( false, - "127.0.0.1:8080".parse().unwrap(), "localhost:8080".to_owned(), + "127.0.0.1:8080".parse().unwrap(), ) } } diff --git a/src/data.rs b/src/data.rs index 3bc54a465..bd9b88301 100644 --- a/src/data.rs +++ b/src/data.rs @@ -148,13 +148,13 @@ impl DataFactory for Data { #[cfg(test)] mod tests { - use std::sync::atomic::{AtomicUsize, Ordering}; - use super::*; - use crate::dev::Service; - use crate::http::StatusCode; - use crate::test::{self, init_service, TestRequest}; - use crate::{web, App, HttpResponse}; + use crate::{ + dev::Service, + http::StatusCode, + test::{init_service, TestRequest}, + web, App, HttpResponse, + }; #[actix_rt::test] async fn test_data_extractor() { @@ -270,49 +270,6 @@ mod tests { assert_eq!(resp.status(), StatusCode::OK); } - #[actix_rt::test] - async fn test_data_drop() { - struct TestData(Arc); - - impl TestData { - fn new(inner: Arc) -> Self { - let _ = inner.fetch_add(1, Ordering::SeqCst); - Self(inner) - } - } - - impl Clone for TestData { - fn clone(&self) -> Self { - let inner = self.0.clone(); - let _ = inner.fetch_add(1, Ordering::SeqCst); - Self(inner) - } - } - - impl Drop for TestData { - fn drop(&mut self) { - let _ = self.0.fetch_sub(1, Ordering::SeqCst); - } - } - - let num = Arc::new(AtomicUsize::new(0)); - let data = TestData::new(num.clone()); - assert_eq!(num.load(Ordering::SeqCst), 1); - - let srv = test::start(move || { - let data = data.clone(); - - App::new() - .data(data) - .service(web::resource("/").to(|_data: Data| async { "ok" })) - }); - - assert!(srv.get("/").send().await.unwrap().status().is_success()); - srv.stop().await; - - assert_eq!(num.load(Ordering::SeqCst), 0); - } - #[actix_rt::test] async fn test_data_from_arc() { let data_new = Data::new(String::from("test-123")); diff --git a/src/lib.rs b/src/lib.rs index 38d9d5734..1a11921a9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -71,11 +71,6 @@ #![doc(html_logo_url = "https://actix.rs/img/logo.png")] #![doc(html_favicon_url = "https://actix.rs/favicon.ico")] -#[cfg(feature = "openssl")] -extern crate tls_openssl as openssl; -#[cfg(feature = "rustls")] -extern crate tls_rustls as rustls; - mod app; mod app_service; mod config; diff --git a/src/middleware/compat.rs b/src/middleware/compat.rs index 71193a5c5..0d197ba80 100644 --- a/src/middleware/compat.rs +++ b/src/middleware/compat.rs @@ -137,7 +137,7 @@ mod tests { use crate::{web, App, HttpResponse}; #[actix_rt::test] - #[cfg(feature = "cookies")] + #[cfg(all(feature = "cookies", feature = "compress"))] async fn test_scope_middleware() { use crate::middleware::Compress; @@ -160,7 +160,7 @@ mod tests { } #[actix_rt::test] - #[cfg(feature = "cookies")] + #[cfg(all(feature = "cookies", feature = "compress"))] async fn test_resource_scope_middleware() { use crate::middleware::Compress; diff --git a/src/request.rs b/src/request.rs index 3fdbb13e1..f3cbc07b8 100644 --- a/src/request.rs +++ b/src/request.rs @@ -52,6 +52,18 @@ impl HttpRequest { }), } } + + #[doc(hidden)] + pub fn __priv_test_new( + path: Path, + head: Message, + rmap: Rc, + config: AppConfig, + app_data: Rc, + ) -> HttpRequest { + let app_state = AppInitServiceState::new(rmap, config); + Self::new(path, head, app_state, app_data) + } } impl HttpRequest { diff --git a/src/server.rs b/src/server.rs index 97c4fdaa2..e3a9f6e01 100644 --- a/src/server.rs +++ b/src/server.rs @@ -71,12 +71,15 @@ impl HttpServer where F: Fn() -> I + Send + Clone + 'static, I: IntoServiceFactory, + S: ServiceFactory + 'static, + // S::Future: 'static, S::Error: Into + 'static, S::InitError: fmt::Debug, S::Response: Into> + 'static, >::Future: 'static, S::Service: 'static, + // S::Service: 'static, B: MessageBody + 'static, { /// Create new HTTP server with application factory @@ -288,7 +291,7 @@ where }; svc.finish(map_config(factory(), move |_| { - AppConfig::new(false, addr, host.clone()) + AppConfig::new(false, host.clone(), addr) })) .tcp() })?; @@ -343,10 +346,11 @@ where }; svc.finish(map_config(factory(), move |_| { - AppConfig::new(true, addr, host.clone()) + AppConfig::new(true, host.clone(), addr) })) .openssl(acceptor.clone()) })?; + Ok(self) } @@ -396,10 +400,11 @@ where }; svc.finish(map_config(factory(), move |_| { - AppConfig::new(true, addr, host.clone()) + AppConfig::new(true, host.clone(), addr) })) .rustls(config.clone()) })?; + Ok(self) } @@ -502,8 +507,8 @@ where let c = cfg.lock().unwrap(); let config = AppConfig::new( false, - socket_addr, c.host.clone().unwrap_or_else(|| format!("{}", socket_addr)), + socket_addr, ); pipeline_factory(|io: UnixStream| async { Ok((io, Protocol::Http1, None)) }) @@ -552,8 +557,8 @@ where let c = cfg.lock().unwrap(); let config = AppConfig::new( false, - socket_addr, c.host.clone().unwrap_or_else(|| format!("{}", socket_addr)), + socket_addr, ); pipeline_factory(|io: UnixStream| async { Ok((io, Protocol::Http1, None)) }) .and_then( diff --git a/src/service.rs b/src/service.rs index 570d88e7d..c2ecc0033 100644 --- a/src/service.rs +++ b/src/service.rs @@ -69,6 +69,12 @@ impl ServiceRequest { Self { req, payload } } + /// Construct service request. + #[doc(hidden)] + pub fn __priv_test_new(req: HttpRequest, payload: Payload) -> Self { + Self::new(req, payload) + } + /// Deconstruct request into parts #[inline] pub fn into_parts(self) -> (HttpRequest, Payload) { diff --git a/src/test.rs b/src/test.rs index 08c80df40..37fb96e0e 100644 --- a/src/test.rs +++ b/src/test.rs @@ -1,47 +1,41 @@ //! Various helpers for Actix applications to use during testing. -use std::net::SocketAddr; -use std::rc::Rc; -use std::sync::mpsc; -use std::{fmt, net, thread, time}; +use std::{net::SocketAddr, rc::Rc}; -use actix_codec::{AsyncRead, AsyncWrite, Framed}; #[cfg(feature = "cookies")] use actix_http::cookie::Cookie; -use actix_http::http::{HeaderMap, Method, StatusCode, Uri, Version}; -use actix_http::test::TestRequest as HttpTestRequest; -use actix_http::{ws, Extensions, HttpService, Request}; +pub use actix_http::test::TestBuffer; +use actix_http::{ + http::{header::IntoHeaderPair, Method, StatusCode, Uri, Version}, + test::TestRequest as HttpTestRequest, + Extensions, Request, +}; use actix_router::{Path, ResourceDef, Url}; -use actix_rt::{time::sleep, System}; -use actix_service::{map_config, IntoService, IntoServiceFactory, Service, ServiceFactory}; +use actix_service::{IntoService, IntoServiceFactory, Service, ServiceFactory}; use actix_utils::future::ok; -use awc::error::PayloadError; -use awc::{Client, ClientRequest, ClientResponse, Connector}; -use bytes::{Bytes, BytesMut}; use futures_core::Stream; use futures_util::StreamExt as _; -use serde::de::DeserializeOwned; -use serde::Serialize; -use socket2::{Domain, Protocol, Socket, Type}; +use serde::{de::DeserializeOwned, Serialize}; -pub use actix_http::test::TestBuffer; +use crate::{ + app_service::AppInitServiceState, + config::AppConfig, + data::Data, + dev::{Body, MessageBody, Payload}, + http::header::ContentType, + rmap::ResourceMap, + service::{ServiceRequest, ServiceResponse}, + web::{Bytes, BytesMut}, + Error, HttpRequest, HttpResponse, +}; -use crate::app_service::AppInitServiceState; -use crate::config::AppConfig; -use crate::data::Data; -use crate::dev::{Body, MessageBody, Payload, Server}; -use crate::http::header::{ContentType, IntoHeaderPair}; -use crate::rmap::ResourceMap; -use crate::service::{ServiceRequest, ServiceResponse}; -use crate::{Error, HttpRequest, HttpResponse}; - -/// Create service that always responds with `HttpResponse::Ok()` +/// Create service that always responds with `HttpResponse::Ok()` and no body. pub fn ok_service( ) -> impl Service, Error = Error> { default_service(StatusCode::OK) } -/// Create service that responds with response with specified status code +/// Create service that always responds with given status code and no body. pub fn default_service( status_code: StatusCode, ) -> impl Service, Error = Error> { @@ -51,8 +45,7 @@ pub fn default_service( .into_service() } -/// This method accepts application builder instance, and constructs -/// service. +/// Initialize service from application builder instance. /// /// ``` /// use actix_service::Service; @@ -83,10 +76,10 @@ where { try_init_service(app) .await - .expect("service initilization failed") + .expect("service initialization failed") } -/// Fallible version of init_service that allows testing data factory errors. +/// Fallible version of [`init_service`] that allows testing initialization errors. pub(crate) async fn try_init_service( app: R, ) -> Result, Error = E>, S::InitError> @@ -166,9 +159,11 @@ where let mut body = resp.take_body(); let mut bytes = BytesMut::new(); + while let Some(item) = body.next().await { bytes.extend_from_slice(&item.unwrap()); } + bytes.freeze() } @@ -573,430 +568,12 @@ impl TestRequest { } } -/// Start test server with default configuration -/// -/// Test server is very simple server that simplify process of writing -/// integration tests cases for actix web applications. -/// -/// # Examples -/// -/// ``` -/// use actix_web::{web, test, App, HttpResponse, Error}; -/// -/// async fn my_handler() -> Result { -/// Ok(HttpResponse::Ok().into()) -/// } -/// -/// #[actix_rt::test] -/// async fn test_example() { -/// let srv = test::start( -/// || App::new().service( -/// web::resource("/").to(my_handler)) -/// ); -/// -/// let req = srv.get("/"); -/// let response = req.send().await.unwrap(); -/// assert!(response.status().is_success()); -/// } -/// ``` -pub fn start(factory: F) -> TestServer -where - F: Fn() -> I + Send + Clone + 'static, - I: IntoServiceFactory, - S: ServiceFactory + 'static, - S::Error: Into + 'static, - S::InitError: fmt::Debug, - S::Response: Into> + 'static, - >::Future: 'static, - B: MessageBody + 'static, -{ - start_with(TestServerConfig::default(), factory) -} - -/// Start test server with custom configuration -/// -/// Test server could be configured in different ways, for details check -/// `TestServerConfig` docs. -/// -/// # Examples -/// -/// ``` -/// use actix_web::{web, test, App, HttpResponse, Error}; -/// -/// async fn my_handler() -> Result { -/// Ok(HttpResponse::Ok().into()) -/// } -/// -/// #[actix_rt::test] -/// async fn test_example() { -/// let srv = test::start_with(test::config().h1(), || -/// App::new().service(web::resource("/").to(my_handler)) -/// ); -/// -/// let req = srv.get("/"); -/// let response = req.send().await.unwrap(); -/// assert!(response.status().is_success()); -/// } -/// ``` -pub fn start_with(cfg: TestServerConfig, factory: F) -> TestServer -where - F: Fn() -> I + Send + Clone + 'static, - I: IntoServiceFactory, - S: ServiceFactory + 'static, - S::Error: Into + 'static, - S::InitError: fmt::Debug, - S::Response: Into> + 'static, - >::Future: 'static, - B: MessageBody + 'static, -{ - let (tx, rx) = mpsc::channel(); - - let ssl = match cfg.stream { - StreamType::Tcp => false, - #[cfg(feature = "openssl")] - StreamType::Openssl(_) => true, - #[cfg(feature = "rustls")] - StreamType::Rustls(_) => true, - }; - - // run server in separate thread - thread::spawn(move || { - let sys = System::new(); - let tcp = net::TcpListener::bind("127.0.0.1:0").unwrap(); - let local_addr = tcp.local_addr().unwrap(); - let factory = factory.clone(); - let cfg = cfg.clone(); - let ctimeout = cfg.client_timeout; - let builder = Server::build().workers(1).disable_signals(); - - let srv = match cfg.stream { - StreamType::Tcp => match cfg.tp { - HttpVer::Http1 => builder.listen("test", tcp, move || { - let cfg = AppConfig::new(false, local_addr, format!("{}", local_addr)); - HttpService::build() - .client_timeout(ctimeout) - .h1(map_config(factory(), move |_| cfg.clone())) - .tcp() - }), - HttpVer::Http2 => builder.listen("test", tcp, move || { - let cfg = AppConfig::new(false, local_addr, format!("{}", local_addr)); - HttpService::build() - .client_timeout(ctimeout) - .h2(map_config(factory(), move |_| cfg.clone())) - .tcp() - }), - HttpVer::Both => builder.listen("test", tcp, move || { - let cfg = AppConfig::new(false, local_addr, format!("{}", local_addr)); - HttpService::build() - .client_timeout(ctimeout) - .finish(map_config(factory(), move |_| cfg.clone())) - .tcp() - }), - }, - #[cfg(feature = "openssl")] - StreamType::Openssl(acceptor) => match cfg.tp { - HttpVer::Http1 => builder.listen("test", tcp, move || { - let cfg = AppConfig::new(true, local_addr, format!("{}", local_addr)); - HttpService::build() - .client_timeout(ctimeout) - .h1(map_config(factory(), move |_| cfg.clone())) - .openssl(acceptor.clone()) - }), - HttpVer::Http2 => builder.listen("test", tcp, move || { - let cfg = AppConfig::new(true, local_addr, format!("{}", local_addr)); - HttpService::build() - .client_timeout(ctimeout) - .h2(map_config(factory(), move |_| cfg.clone())) - .openssl(acceptor.clone()) - }), - HttpVer::Both => builder.listen("test", tcp, move || { - let cfg = AppConfig::new(true, local_addr, format!("{}", local_addr)); - HttpService::build() - .client_timeout(ctimeout) - .finish(map_config(factory(), move |_| cfg.clone())) - .openssl(acceptor.clone()) - }), - }, - #[cfg(feature = "rustls")] - StreamType::Rustls(config) => match cfg.tp { - HttpVer::Http1 => builder.listen("test", tcp, move || { - let cfg = AppConfig::new(true, local_addr, format!("{}", local_addr)); - HttpService::build() - .client_timeout(ctimeout) - .h1(map_config(factory(), move |_| cfg.clone())) - .rustls(config.clone()) - }), - HttpVer::Http2 => builder.listen("test", tcp, move || { - let cfg = AppConfig::new(true, local_addr, format!("{}", local_addr)); - HttpService::build() - .client_timeout(ctimeout) - .h2(map_config(factory(), move |_| cfg.clone())) - .rustls(config.clone()) - }), - HttpVer::Both => builder.listen("test", tcp, move || { - let cfg = AppConfig::new(true, local_addr, format!("{}", local_addr)); - HttpService::build() - .client_timeout(ctimeout) - .finish(map_config(factory(), move |_| cfg.clone())) - .rustls(config.clone()) - }), - }, - } - .unwrap(); - - sys.block_on(async { - let srv = srv.run(); - tx.send((System::current(), srv, local_addr)).unwrap(); - }); - - sys.run() - }); - - let (system, server, addr) = rx.recv().unwrap(); - - let client = { - let connector = { - #[cfg(feature = "openssl")] - { - use openssl::ssl::{SslConnector, SslMethod, SslVerifyMode}; - - let mut builder = SslConnector::builder(SslMethod::tls()).unwrap(); - builder.set_verify(SslVerifyMode::NONE); - let _ = builder - .set_alpn_protos(b"\x02h2\x08http/1.1") - .map_err(|e| log::error!("Can not set alpn protocol: {:?}", e)); - Connector::new() - .conn_lifetime(time::Duration::from_secs(0)) - .timeout(time::Duration::from_millis(30000)) - .ssl(builder.build()) - } - #[cfg(not(feature = "openssl"))] - { - Connector::new() - .conn_lifetime(time::Duration::from_secs(0)) - .timeout(time::Duration::from_millis(30000)) - } - }; - - Client::builder().connector(connector).finish() - }; - - TestServer { - addr, - client, - system, - ssl, - server, - } -} - -#[derive(Clone)] -pub struct TestServerConfig { - tp: HttpVer, - stream: StreamType, - client_timeout: u64, -} - -#[derive(Clone)] -enum HttpVer { - Http1, - Http2, - Both, -} - -#[derive(Clone)] -enum StreamType { - Tcp, - #[cfg(feature = "openssl")] - Openssl(openssl::ssl::SslAcceptor), - #[cfg(feature = "rustls")] - Rustls(rustls::ServerConfig), -} - -impl Default for TestServerConfig { - fn default() -> Self { - TestServerConfig::new() - } -} - -/// Create default test server config -pub fn config() -> TestServerConfig { - TestServerConfig::new() -} - -impl TestServerConfig { - /// Create default server configuration - pub(crate) fn new() -> TestServerConfig { - TestServerConfig { - tp: HttpVer::Both, - stream: StreamType::Tcp, - client_timeout: 5000, - } - } - - /// Start HTTP/1.1 server only - pub fn h1(mut self) -> Self { - self.tp = HttpVer::Http1; - self - } - - /// Start HTTP/2 server only - pub fn h2(mut self) -> Self { - self.tp = HttpVer::Http2; - self - } - - /// Start openssl server - #[cfg(feature = "openssl")] - pub fn openssl(mut self, acceptor: openssl::ssl::SslAcceptor) -> Self { - self.stream = StreamType::Openssl(acceptor); - self - } - - /// Start rustls server - #[cfg(feature = "rustls")] - pub fn rustls(mut self, config: rustls::ServerConfig) -> Self { - self.stream = StreamType::Rustls(config); - self - } - - /// Set server client timeout in milliseconds for first request. - pub fn client_timeout(mut self, val: u64) -> Self { - self.client_timeout = val; - self - } -} - -/// Get first available unused address -pub fn unused_addr() -> net::SocketAddr { - let addr: net::SocketAddr = "127.0.0.1:0".parse().unwrap(); - let socket = Socket::new(Domain::IPV4, Type::STREAM, Some(Protocol::TCP)).unwrap(); - socket.bind(&addr.into()).unwrap(); - socket.set_reuse_address(true).unwrap(); - let tcp = net::TcpListener::from(socket); - tcp.local_addr().unwrap() -} - -/// Test server controller -pub struct TestServer { - addr: net::SocketAddr, - client: awc::Client, - system: actix_rt::System, - ssl: bool, - server: Server, -} - -impl TestServer { - /// Construct test server url - pub fn addr(&self) -> net::SocketAddr { - self.addr - } - - /// Construct test server url - pub fn url(&self, uri: &str) -> String { - let scheme = if self.ssl { "https" } else { "http" }; - - if uri.starts_with('/') { - format!("{}://localhost:{}{}", scheme, self.addr.port(), uri) - } else { - format!("{}://localhost:{}/{}", scheme, self.addr.port(), uri) - } - } - - /// Create `GET` request - pub fn get>(&self, path: S) -> ClientRequest { - self.client.get(self.url(path.as_ref()).as_str()) - } - - /// Create `POST` request - pub fn post>(&self, path: S) -> ClientRequest { - self.client.post(self.url(path.as_ref()).as_str()) - } - - /// Create `HEAD` request - pub fn head>(&self, path: S) -> ClientRequest { - self.client.head(self.url(path.as_ref()).as_str()) - } - - /// Create `PUT` request - pub fn put>(&self, path: S) -> ClientRequest { - self.client.put(self.url(path.as_ref()).as_str()) - } - - /// Create `PATCH` request - pub fn patch>(&self, path: S) -> ClientRequest { - self.client.patch(self.url(path.as_ref()).as_str()) - } - - /// Create `DELETE` request - pub fn delete>(&self, path: S) -> ClientRequest { - self.client.delete(self.url(path.as_ref()).as_str()) - } - - /// Create `OPTIONS` request - pub fn options>(&self, path: S) -> ClientRequest { - self.client.options(self.url(path.as_ref()).as_str()) - } - - /// Connect to test HTTP server - pub fn request>(&self, method: Method, path: S) -> ClientRequest { - self.client.request(method, path.as_ref()) - } - - pub async fn load_body( - &mut self, - mut response: ClientResponse, - ) -> Result - where - S: Stream> + Unpin + 'static, - { - response.body().limit(10_485_760).await - } - - /// Connect to WebSocket server at a given path. - pub async fn ws_at( - &mut self, - path: &str, - ) -> Result, awc::error::WsClientError> { - let url = self.url(path); - let connect = self.client.ws(url).connect(); - connect.await.map(|(_, framed)| framed) - } - - /// Connect to a WebSocket server. - pub async fn ws( - &mut self, - ) -> Result, awc::error::WsClientError> { - self.ws_at("/").await - } - - /// Get default HeaderMap of Client. - /// - /// Returns Some(&mut HeaderMap) when Client object is unique - /// (No other clone of client exists at the same time). - pub fn client_headers(&mut self) -> Option<&mut HeaderMap> { - self.client.headers() - } - - /// Gracefully stop HTTP server - pub async fn stop(self) { - self.server.stop(true).await; - self.system.stop(); - sleep(time::Duration::from_millis(100)).await; - } -} - -impl Drop for TestServer { - fn drop(&mut self) { - self.system.stop() - } -} - #[cfg(test)] mod tests { + use std::time::SystemTime; + use actix_http::HttpMessage; use serde::{Deserialize, Serialize}; - use std::time::SystemTime; use super::*; use crate::{http::header, web, App, HttpResponse, Responder}; diff --git a/tests/test_httpserver.rs b/tests/test_httpserver.rs index 12225b7e5..881c6ce94 100644 --- a/tests/test_httpserver.rs +++ b/tests/test_httpserver.rs @@ -3,14 +3,14 @@ extern crate tls_openssl as openssl; #[cfg(any(unix, feature = "openssl"))] use { - actix_web::{test, web, App, HttpResponse, HttpServer}, + actix_web::{web, App, HttpResponse, HttpServer}, std::{sync::mpsc, thread, time::Duration}, }; #[cfg(unix)] #[actix_rt::test] async fn test_start() { - let addr = test::unused_addr(); + let addr = actix_test::unused_addr(); let (tx, rx) = mpsc::channel(); thread::spawn(move || { @@ -93,7 +93,7 @@ fn ssl_acceptor() -> openssl::ssl::SslAcceptorBuilder { async fn test_start_ssl() { use actix_web::HttpRequest; - let addr = test::unused_addr(); + let addr = actix_test::unused_addr(); let (tx, rx) = mpsc::channel(); thread::spawn(move || { diff --git a/tests/test_server.rs b/tests/test_server.rs index db9fe37db..d114b022d 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -31,7 +31,7 @@ use rand::{distributions::Alphanumeric, Rng}; use actix_web::dev::BodyEncoding; use actix_web::middleware::{Compress, NormalizePath, TrailingSlash}; -use actix_web::{dev, test, web, App, Error, HttpResponse}; +use actix_web::{dev, web, App, Error, HttpResponse}; const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ Hello World Hello World Hello World Hello World Hello World \ @@ -115,7 +115,7 @@ impl futures_core::stream::Stream for TestBody { #[actix_rt::test] async fn test_body() { - let srv = test::start(|| { + let srv = actix_test::start(|| { App::new().service(web::resource("/").route(web::to(|| HttpResponse::Ok().body(STR)))) }); @@ -129,7 +129,7 @@ async fn test_body() { #[actix_rt::test] async fn test_body_gzip() { - let srv = test::start_with(test::config().h1(), || { + let srv = actix_test::start_with(actix_test::config().h1(), || { App::new() .wrap(Compress::new(ContentEncoding::Gzip)) .service(web::resource("/").route(web::to(|| HttpResponse::Ok().body(STR)))) @@ -156,7 +156,7 @@ async fn test_body_gzip() { #[actix_rt::test] async fn test_body_gzip2() { - let srv = test::start_with(test::config().h1(), || { + let srv = actix_test::start_with(actix_test::config().h1(), || { App::new() .wrap(Compress::new(ContentEncoding::Gzip)) .service(web::resource("/").route(web::to(|| { @@ -185,7 +185,7 @@ async fn test_body_gzip2() { #[actix_rt::test] async fn test_body_encoding_override() { - let srv = test::start_with(test::config().h1(), || { + let srv = actix_test::start_with(actix_test::config().h1(), || { App::new() .wrap(Compress::new(ContentEncoding::Gzip)) .service(web::resource("/").route(web::to(|| { @@ -248,7 +248,7 @@ async fn test_body_gzip_large() { let data = STR.repeat(10); let srv_data = data.clone(); - let srv = test::start_with(test::config().h1(), move || { + let srv = actix_test::start_with(actix_test::config().h1(), move || { let data = srv_data.clone(); App::new() .wrap(Compress::new(ContentEncoding::Gzip)) @@ -286,7 +286,7 @@ async fn test_body_gzip_large_random() { .collect::(); let srv_data = data.clone(); - let srv = test::start_with(test::config().h1(), move || { + let srv = actix_test::start_with(actix_test::config().h1(), move || { let data = srv_data.clone(); App::new() .wrap(Compress::new(ContentEncoding::Gzip)) @@ -318,7 +318,7 @@ async fn test_body_gzip_large_random() { #[actix_rt::test] async fn test_body_chunked_implicit() { - let srv = test::start_with(test::config().h1(), || { + let srv = actix_test::start_with(actix_test::config().h1(), || { App::new() .wrap(Compress::new(ContentEncoding::Gzip)) .service(web::resource("/").route(web::get().to(move || { @@ -352,7 +352,7 @@ async fn test_body_chunked_implicit() { #[actix_rt::test] async fn test_body_br_streaming() { - let srv = test::start_with(test::config().h1(), || { + let srv = actix_test::start_with(actix_test::config().h1(), || { App::new() .wrap(Compress::new(ContentEncoding::Br)) .service(web::resource("/").route(web::to(move || { @@ -384,7 +384,7 @@ async fn test_body_br_streaming() { #[actix_rt::test] async fn test_head_binary() { - let srv = test::start_with(test::config().h1(), || { + let srv = actix_test::start_with(actix_test::config().h1(), || { App::new().service( web::resource("/").route(web::head().to(move || HttpResponse::Ok().body(STR))), ) @@ -405,7 +405,7 @@ async fn test_head_binary() { #[actix_rt::test] async fn test_no_chunking() { - let srv = test::start_with(test::config().h1(), || { + let srv = actix_test::start_with(actix_test::config().h1(), || { App::new().service(web::resource("/").route(web::to(move || { HttpResponse::Ok() .no_chunking(STR.len() as u64) @@ -424,7 +424,7 @@ async fn test_no_chunking() { #[actix_rt::test] async fn test_body_deflate() { - let srv = test::start_with(test::config().h1(), || { + let srv = actix_test::start_with(actix_test::config().h1(), || { App::new() .wrap(Compress::new(ContentEncoding::Deflate)) .service(web::resource("/").route(web::to(move || HttpResponse::Ok().body(STR)))) @@ -451,7 +451,7 @@ async fn test_body_deflate() { #[actix_rt::test] async fn test_body_brotli() { - let srv = test::start_with(test::config().h1(), || { + let srv = actix_test::start_with(actix_test::config().h1(), || { App::new() .wrap(Compress::new(ContentEncoding::Br)) .service(web::resource("/").route(web::to(move || HttpResponse::Ok().body(STR)))) @@ -479,7 +479,7 @@ async fn test_body_brotli() { #[actix_rt::test] async fn test_encoding() { - let srv = test::start_with(test::config().h1(), || { + let srv = actix_test::start_with(actix_test::config().h1(), || { App::new().wrap(Compress::default()).service( web::resource("/").route(web::to(move |body: Bytes| HttpResponse::Ok().body(body))), ) @@ -504,7 +504,7 @@ async fn test_encoding() { #[actix_rt::test] async fn test_gzip_encoding() { - let srv = test::start_with(test::config().h1(), || { + let srv = actix_test::start_with(actix_test::config().h1(), || { App::new().service( web::resource("/").route(web::to(move |body: Bytes| HttpResponse::Ok().body(body))), ) @@ -530,7 +530,7 @@ async fn test_gzip_encoding() { #[actix_rt::test] async fn test_gzip_encoding_large() { let data = STR.repeat(10); - let srv = test::start_with(test::config().h1(), || { + let srv = actix_test::start_with(actix_test::config().h1(), || { App::new().service( web::resource("/").route(web::to(move |body: Bytes| HttpResponse::Ok().body(body))), ) @@ -561,7 +561,7 @@ async fn test_reading_gzip_encoding_large_random() { .map(char::from) .collect::(); - let srv = test::start_with(test::config().h1(), || { + let srv = actix_test::start_with(actix_test::config().h1(), || { App::new().service( web::resource("/").route(web::to(move |body: Bytes| HttpResponse::Ok().body(body))), ) @@ -587,7 +587,7 @@ async fn test_reading_gzip_encoding_large_random() { #[actix_rt::test] async fn test_reading_deflate_encoding() { - let srv = test::start_with(test::config().h1(), || { + let srv = actix_test::start_with(actix_test::config().h1(), || { App::new().service( web::resource("/").route(web::to(move |body: Bytes| HttpResponse::Ok().body(body))), ) @@ -613,7 +613,7 @@ async fn test_reading_deflate_encoding() { #[actix_rt::test] async fn test_reading_deflate_encoding_large() { let data = STR.repeat(10); - let srv = test::start_with(test::config().h1(), || { + let srv = actix_test::start_with(actix_test::config().h1(), || { App::new().service( web::resource("/").route(web::to(move |body: Bytes| HttpResponse::Ok().body(body))), ) @@ -644,7 +644,7 @@ async fn test_reading_deflate_encoding_large_random() { .map(char::from) .collect::(); - let srv = test::start_with(test::config().h1(), || { + let srv = actix_test::start_with(actix_test::config().h1(), || { App::new().service( web::resource("/").route(web::to(move |body: Bytes| HttpResponse::Ok().body(body))), ) @@ -670,7 +670,7 @@ async fn test_reading_deflate_encoding_large_random() { #[actix_rt::test] async fn test_brotli_encoding() { - let srv = test::start_with(test::config().h1(), || { + let srv = actix_test::start_with(actix_test::config().h1(), || { App::new().service( web::resource("/").route(web::to(move |body: Bytes| HttpResponse::Ok().body(body))), ) @@ -701,7 +701,7 @@ async fn test_brotli_encoding_large() { .map(char::from) .collect::(); - let srv = test::start_with(test::config().h1(), || { + let srv = actix_test::start_with(actix_test::config().h1(), || { App::new().service( web::resource("/") .app_data(web::PayloadConfig::new(320_000)) @@ -732,13 +732,14 @@ async fn test_brotli_encoding_large() { #[actix_rt::test] async fn test_brotli_encoding_large_openssl() { let data = STR.repeat(10); - let srv = test::start_with(test::config().openssl(openssl_config()), move || { - App::new().service(web::resource("/").route(web::to(|bytes: Bytes| { - HttpResponse::Ok() - .encoding(actix_web::http::ContentEncoding::Identity) - .body(bytes) - }))) - }); + let srv = + actix_test::start_with(actix_test::config().openssl(openssl_config()), move || { + App::new().service(web::resource("/").route(web::to(|bytes: Bytes| { + HttpResponse::Ok() + .encoding(actix_web::http::ContentEncoding::Identity) + .body(bytes) + }))) + }); // body let mut e = BrotliEncoder::new(Vec::new(), 3); @@ -794,7 +795,7 @@ mod plus_rustls { .map(char::from) .collect::(); - let srv = test::start_with(test::config().rustls(rustls_config()), || { + let srv = actix_test::start_with(actix_test::config().rustls(rustls_config()), || { App::new().service(web::resource("/").route(web::to(|bytes: Bytes| { HttpResponse::Ok() .encoding(actix_web::http::ContentEncoding::Identity) @@ -827,7 +828,7 @@ mod plus_rustls { async fn test_server_cookies() { use actix_web::{http, HttpMessage}; - let srv = test::start(|| { + let srv = actix_test::start(|| { App::new().default_service(web::to(|| { HttpResponse::Ok() .cookie( @@ -880,7 +881,7 @@ async fn test_server_cookies() { async fn test_slow_request() { use std::net; - let srv = test::start_with(test::config().client_timeout(200), || { + let srv = actix_test::start_with(actix_test::config().client_timeout(200), || { App::new().service(web::resource("/").route(web::to(HttpResponse::Ok))) }); @@ -898,7 +899,7 @@ async fn test_slow_request() { #[actix_rt::test] async fn test_normalize() { - let srv = test::start_with(test::config().h1(), || { + let srv = actix_test::start_with(actix_test::config().h1(), || { App::new() .wrap(NormalizePath::new(TrailingSlash::Trim)) .service(web::resource("/one").route(web::to(|| HttpResponse::Ok().finish()))) @@ -907,3 +908,51 @@ async fn test_normalize() { let response = srv.get("/one/").send().await.unwrap(); assert!(response.status().is_success()); } + +#[actix_rt::test] +async fn test_data_drop() { + use std::sync::{ + atomic::{AtomicUsize, Ordering}, + Arc, + }; + + struct TestData(Arc); + + impl TestData { + fn new(inner: Arc) -> Self { + let _ = inner.fetch_add(1, Ordering::SeqCst); + Self(inner) + } + } + + impl Clone for TestData { + fn clone(&self) -> Self { + let inner = self.0.clone(); + let _ = inner.fetch_add(1, Ordering::SeqCst); + Self(inner) + } + } + + impl Drop for TestData { + fn drop(&mut self) { + self.0.fetch_sub(1, Ordering::SeqCst); + } + } + + let num = Arc::new(AtomicUsize::new(0)); + let data = TestData::new(num.clone()); + assert_eq!(num.load(Ordering::SeqCst), 1); + + let srv = actix_test::start(move || { + let data = data.clone(); + + App::new() + .data(data) + .service(web::resource("/").to(|_data: web::Data| async { "ok" })) + }); + + assert!(srv.get("/").send().await.unwrap().status().is_success()); + srv.stop().await; + + assert_eq!(num.load(Ordering::SeqCst), 0); +} From 6fb06a720a94f12af6cd32943e4b56cae584a05c Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Fri, 2 Apr 2021 09:27:11 +0100 Subject: [PATCH 34/79] prepare http release 3.0.0-beta.5 --- Cargo.toml | 2 +- actix-http-test/Cargo.toml | 2 +- actix-http/CHANGES.md | 7 +++++-- actix-http/Cargo.toml | 2 +- actix-http/README.md | 4 ++-- actix-multipart/Cargo.toml | 2 +- actix-test/Cargo.toml | 2 +- actix-web-actors/Cargo.toml | 2 +- awc/Cargo.toml | 4 ++-- docs/graphs/web-only.dot | 4 +++- 10 files changed, 18 insertions(+), 13 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 190b645d4..63b43918d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -84,7 +84,7 @@ actix-utils = "3.0.0-beta.4" actix-tls = { version = "3.0.0-beta.5", default-features = false, optional = true } actix-web-codegen = "0.5.0-beta.2" -actix-http = "3.0.0-beta.4" +actix-http = "3.0.0-beta.5" ahash = "0.7" bytes = "1" diff --git a/actix-http-test/Cargo.toml b/actix-http-test/Cargo.toml index 31a83eddb..d1ae3e609 100644 --- a/actix-http-test/Cargo.toml +++ b/actix-http-test/Cargo.toml @@ -52,4 +52,4 @@ tls-openssl = { version = "0.10.9", package = "openssl", optional = true } [dev-dependencies] actix-web = { version = "4.0.0-beta.4", default-features = false, features = ["cookies"] } -actix-http = "3.0.0-beta.4" +actix-http = "3.0.0-beta.5" diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 374c5f199..9765be3a6 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -1,8 +1,11 @@ # Changes ## Unreleased - 2021-xx-xx + + +## 3.0.0-beta.5 - 2021-04-02 ### Added -* `client::Connector::handshake_timeout` method for customize tls connection handshake timeout. [#2081] +* `client::Connector::handshake_timeout` method for customizing TLS connection handshake timeout. [#2081] * `client::ConnectorService` as `client::Connector::finish` method's return type [#2081] * `client::ConnectionIo` trait alias [#2081] @@ -10,7 +13,7 @@ * `client::Connector` type now only have one generic type for `actix_service::Service`. [#2063] ### Removed -* Common HTTP headers were moved into actix-web. [2094] +* Common typed HTTP headers were moved to actix-web. [2094] * `ResponseError` impl for `actix_utils::timeout::TimeoutError`. [#2127] [#2063]: https://github.com/actix/actix-web/pull/2063 diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 4857f083d..93ef4ec15 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-http" -version = "3.0.0-beta.4" +version = "3.0.0-beta.5" authors = ["Nikolay Kim "] description = "HTTP primitives for the Actix ecosystem" readme = "README.md" diff --git a/actix-http/README.md b/actix-http/README.md index 53fedd40e..50a17a02f 100644 --- a/actix-http/README.md +++ b/actix-http/README.md @@ -3,11 +3,11 @@ > HTTP primitives for the Actix ecosystem. [![crates.io](https://img.shields.io/crates/v/actix-http?label=latest)](https://crates.io/crates/actix-http) -[![Documentation](https://docs.rs/actix-http/badge.svg?version=3.0.0-beta.4)](https://docs.rs/actix-http/3.0.0-beta.4) +[![Documentation](https://docs.rs/actix-http/badge.svg?version=3.0.0-beta.5)](https://docs.rs/actix-http/3.0.0-beta.5) [![Version](https://img.shields.io/badge/rustc-1.46+-ab6000.svg)](https://blog.rust-lang.org/2020/03/12/Rust-1.46.html) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-http.svg)
-[![dependency status](https://deps.rs/crate/actix-http/3.0.0-beta.4/status.svg)](https://deps.rs/crate/actix-http/3.0.0-beta.4) +[![dependency status](https://deps.rs/crate/actix-http/3.0.0-beta.5/status.svg)](https://deps.rs/crate/actix-http/3.0.0-beta.5) [![Download](https://img.shields.io/crates/d/actix-http.svg)](https://crates.io/crates/actix-http) [![Join the chat at https://gitter.im/actix/actix](https://badges.gitter.im/actix/actix.svg)](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) diff --git a/actix-multipart/Cargo.toml b/actix-multipart/Cargo.toml index cb00a0e74..aa8660e40 100644 --- a/actix-multipart/Cargo.toml +++ b/actix-multipart/Cargo.toml @@ -31,6 +31,6 @@ twoway = "0.2" [dev-dependencies] actix-rt = "2.2" -actix-http = "3.0.0-beta.4" +actix-http = "3.0.0-beta.5" tokio = { version = "1", features = ["sync"] } tokio-stream = "0.1" diff --git a/actix-test/Cargo.toml b/actix-test/Cargo.toml index 884ec229c..6364c2757 100644 --- a/actix-test/Cargo.toml +++ b/actix-test/Cargo.toml @@ -17,7 +17,7 @@ openssl = ["tls-openssl", "actix-http/openssl"] [dependencies] actix-codec = "0.4.0-beta.1" -actix-http = { version = "3.0.0-beta.4", features = ["cookies"] } +actix-http = { version = "3.0.0-beta.5", features = ["cookies"] } actix-http-test = { version = "3.0.0-beta.3", features = [] } actix-service = "2.0.0-beta.4" actix-utils = "3.0.0-beta.2" diff --git a/actix-web-actors/Cargo.toml b/actix-web-actors/Cargo.toml index 4fd144195..cf596e25c 100644 --- a/actix-web-actors/Cargo.toml +++ b/actix-web-actors/Cargo.toml @@ -18,7 +18,7 @@ path = "src/lib.rs" [dependencies] actix = { version = "0.11.0-beta.3", default-features = false } actix-codec = "0.4.0-beta.1" -actix-http = "3.0.0-beta.4" +actix-http = "3.0.0-beta.5" actix-web = { version = "4.0.0-beta.4", default-features = false } bytes = "1" diff --git a/awc/Cargo.toml b/awc/Cargo.toml index 78110baed..19677e109 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -46,7 +46,7 @@ trust-dns = ["actix-http/trust-dns"] [dependencies] actix-codec = "0.4.0-beta.1" actix-service = "2.0.0-beta.4" -actix-http = "3.0.0-beta.4" +actix-http = "3.0.0-beta.5" actix-rt = { version = "2.1", default-features = false } base64 = "0.13" @@ -67,7 +67,7 @@ tls-rustls = { version = "0.19.0", package = "rustls", optional = true, features [dev-dependencies] actix-web = { version = "4.0.0-beta.4", features = ["openssl"] } -actix-http = { version = "3.0.0-beta.4", features = ["openssl"] } +actix-http = { version = "3.0.0-beta.5", features = ["openssl"] } actix-http-test = { version = "3.0.0-beta.3", features = ["openssl"] } actix-utils = "3.0.0-beta.4" actix-server = "2.0.0-beta.3" diff --git a/docs/graphs/web-only.dot b/docs/graphs/web-only.dot index 6f8292a3a..b0decd818 100644 --- a/docs/graphs/web-only.dot +++ b/docs/graphs/web-only.dot @@ -9,12 +9,14 @@ digraph { "actix-web-actors" "actix-web-codegen" "actix-http-test" + "actix-test" } - "actix-web" -> { "actix-web-codegen" "actix-http" "awc" } + "actix-web" -> { "actix-web-codegen" "actix-http" } "awc" -> { "actix-http" } "actix-web-actors" -> { "actix" "actix-web" "actix-http" } "actix-multipart" -> { "actix-web" } "actix-files" -> { "actix-web" } "actix-http-test" -> { "awc" } + "actix-test" -> { "actix-web" "awc" "actix-http-test" } } From 546e7c5da4201f0aa2212fdded1cd14ea8628f54 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Fri, 2 Apr 2021 09:37:51 +0100 Subject: [PATCH 35/79] prepare web release 4.0.0-beta.5 --- CHANGES.md | 7 +++++-- Cargo.toml | 2 +- README.md | 4 ++-- actix-files/Cargo.toml | 4 ++-- actix-http-test/Cargo.toml | 2 +- actix-multipart/Cargo.toml | 2 +- actix-test/Cargo.toml | 2 +- actix-web-actors/Cargo.toml | 2 +- actix-web-codegen/Cargo.toml | 2 +- awc/Cargo.toml | 2 +- 10 files changed, 16 insertions(+), 13 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 5442be2e0..a55005c7a 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,9 @@ # Changes ## Unreleased - 2021-xx-xx + + +## 4.0.0-beta.5 - 2021-04-02 ### Added * `Header` extractor for extracting common HTTP headers in handlers. [#2094] * Added `TestServer::client_headers` method. [#2097] @@ -9,8 +12,8 @@ * Double ampersand in Logger format is escaped correctly. [#2067] ### Changed -* `CustomResponder` would return error as `HttpResponse` when `CustomResponder::with_header` failed instead of skipping. - (Only the first error is kept when multiple error occur) [#2093] +* `CustomResponder` would return error as `HttpResponse` when `CustomResponder::with_header` failed + instead of skipping. (Only the first error is kept when multiple error occur) [#2093] ### Removed * The `client` mod was removed. Clients should now use `awc` directly. diff --git a/Cargo.toml b/Cargo.toml index 63b43918d..fd23aee73 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-web" -version = "4.0.0-beta.4" +version = "4.0.0-beta.5" authors = ["Nikolay Kim "] description = "Actix Web is a powerful, pragmatic, and extremely fast web framework for Rust" readme = "README.md" diff --git a/README.md b/README.md index 64fd7d08d..508736279 100644 --- a/README.md +++ b/README.md @@ -6,10 +6,10 @@

[![crates.io](https://img.shields.io/crates/v/actix-web?label=latest)](https://crates.io/crates/actix-web) -[![Documentation](https://docs.rs/actix-web/badge.svg?version=4.0.0-beta.4)](https://docs.rs/actix-web/4.0.0-beta.4) +[![Documentation](https://docs.rs/actix-web/badge.svg?version=4.0.0-beta.5)](https://docs.rs/actix-web/4.0.0-beta.5) [![Version](https://img.shields.io/badge/rustc-1.46+-ab6000.svg)](https://blog.rust-lang.org/2020/03/12/Rust-1.46.html) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-web.svg) -[![Dependency Status](https://deps.rs/crate/actix-web/4.0.0-beta.4/status.svg)](https://deps.rs/crate/actix-web/4.0.0-beta.4) +[![Dependency Status](https://deps.rs/crate/actix-web/4.0.0-beta.5/status.svg)](https://deps.rs/crate/actix-web/4.0.0-beta.5)
[![build status](https://github.com/actix/actix-web/workflows/CI%20%28Linux%29/badge.svg?branch=master&event=push)](https://github.com/actix/actix-web/actions) [![codecov](https://codecov.io/gh/actix/actix-web/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-web) diff --git a/actix-files/Cargo.toml b/actix-files/Cargo.toml index 6a60397a6..ff8331762 100644 --- a/actix-files/Cargo.toml +++ b/actix-files/Cargo.toml @@ -17,7 +17,7 @@ name = "actix_files" path = "src/lib.rs" [dependencies] -actix-web = { version = "4.0.0-beta.4", default-features = false } +actix-web = { version = "4.0.0-beta.5", default-features = false } actix-service = "2.0.0-beta.4" actix-utils = "3.0.0-beta.4" @@ -34,5 +34,5 @@ percent-encoding = "2.1" [dev-dependencies] actix-rt = "2.2" -actix-web = "4.0.0-beta.4" +actix-web = "4.0.0-beta.5" actix-test = "0.0.1" diff --git a/actix-http-test/Cargo.toml b/actix-http-test/Cargo.toml index d1ae3e609..906d64091 100644 --- a/actix-http-test/Cargo.toml +++ b/actix-http-test/Cargo.toml @@ -51,5 +51,5 @@ time = { version = "0.2.23", default-features = false, features = ["std"] } tls-openssl = { version = "0.10.9", package = "openssl", optional = true } [dev-dependencies] -actix-web = { version = "4.0.0-beta.4", default-features = false, features = ["cookies"] } +actix-web = { version = "4.0.0-beta.5", default-features = false, features = ["cookies"] } actix-http = "3.0.0-beta.5" diff --git a/actix-multipart/Cargo.toml b/actix-multipart/Cargo.toml index aa8660e40..e5a6bb7da 100644 --- a/actix-multipart/Cargo.toml +++ b/actix-multipart/Cargo.toml @@ -16,7 +16,7 @@ name = "actix_multipart" path = "src/lib.rs" [dependencies] -actix-web = { version = "4.0.0-beta.4", default-features = false } +actix-web = { version = "4.0.0-beta.5", default-features = false } actix-utils = "3.0.0-beta.4" bytes = "1" diff --git a/actix-test/Cargo.toml b/actix-test/Cargo.toml index 6364c2757..b5cafc7c4 100644 --- a/actix-test/Cargo.toml +++ b/actix-test/Cargo.toml @@ -21,7 +21,7 @@ actix-http = { version = "3.0.0-beta.5", features = ["cookies"] } actix-http-test = { version = "3.0.0-beta.3", features = [] } actix-service = "2.0.0-beta.4" actix-utils = "3.0.0-beta.2" -actix-web = { version = "4.0.0-beta.4", default-features = false, features = ["cookies"] } +actix-web = { version = "4.0.0-beta.5", default-features = false, features = ["cookies"] } actix-rt = "2.1" awc = { version = "3.0.0-beta.3", default-features = false, features = ["cookies"] } diff --git a/actix-web-actors/Cargo.toml b/actix-web-actors/Cargo.toml index cf596e25c..82cd19624 100644 --- a/actix-web-actors/Cargo.toml +++ b/actix-web-actors/Cargo.toml @@ -19,7 +19,7 @@ path = "src/lib.rs" actix = { version = "0.11.0-beta.3", default-features = false } actix-codec = "0.4.0-beta.1" actix-http = "3.0.0-beta.5" -actix-web = { version = "4.0.0-beta.4", default-features = false } +actix-web = { version = "4.0.0-beta.5", default-features = false } bytes = "1" bytestring = "1" diff --git a/actix-web-codegen/Cargo.toml b/actix-web-codegen/Cargo.toml index daea993d0..ec02a908e 100644 --- a/actix-web-codegen/Cargo.toml +++ b/actix-web-codegen/Cargo.toml @@ -22,7 +22,7 @@ proc-macro2 = "1" actix-rt = "2.2" actix-test = "0.0.1" actix-utils = "3.0.0-beta.4" -actix-web = "4.0.0-beta.4" +actix-web = "4.0.0-beta.5" futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] } trybuild = "1" diff --git a/awc/Cargo.toml b/awc/Cargo.toml index 19677e109..b3acbd5f1 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -66,7 +66,7 @@ tls-openssl = { version = "0.10.9", package = "openssl", optional = true } tls-rustls = { version = "0.19.0", package = "rustls", optional = true, features = ["dangerous_configuration"] } [dev-dependencies] -actix-web = { version = "4.0.0-beta.4", features = ["openssl"] } +actix-web = { version = "4.0.0-beta.5", features = ["openssl"] } actix-http = { version = "3.0.0-beta.5", features = ["openssl"] } actix-http-test = { version = "3.0.0-beta.3", features = ["openssl"] } actix-utils = "3.0.0-beta.4" From a32151525c51dcbdd9edab79fd1ec5c5761a7183 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Fri, 2 Apr 2021 09:40:36 +0100 Subject: [PATCH 36/79] prepare awc release 3.0.0-beta.4 --- Cargo.toml | 2 +- actix-http-test/Cargo.toml | 2 +- actix-test/Cargo.toml | 2 +- actix-web-actors/Cargo.toml | 2 +- awc/CHANGES.md | 6 +++++- awc/Cargo.toml | 7 +++++-- 6 files changed, 14 insertions(+), 7 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index fd23aee73..c63acebf3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -109,7 +109,7 @@ url = "2.1" [dev-dependencies] actix-test = { version = "0.0.1", features = ["openssl", "rustls"] } -awc = { version = "3.0.0-beta.3", features = ["openssl"] } +awc = { version = "3.0.0-beta.4", features = ["openssl"] } brotli2 = "0.3.2" criterion = "0.3" diff --git a/actix-http-test/Cargo.toml b/actix-http-test/Cargo.toml index 906d64091..f1827eb01 100644 --- a/actix-http-test/Cargo.toml +++ b/actix-http-test/Cargo.toml @@ -35,7 +35,7 @@ actix-tls = "3.0.0-beta.5" actix-utils = "3.0.0-beta.4" actix-rt = "2.2" actix-server = "2.0.0-beta.3" -awc = { version = "3.0.0-beta.3", default-features = false } +awc = { version = "3.0.0-beta.4", default-features = false } base64 = "0.13" bytes = "1" diff --git a/actix-test/Cargo.toml b/actix-test/Cargo.toml index b5cafc7c4..56d82e934 100644 --- a/actix-test/Cargo.toml +++ b/actix-test/Cargo.toml @@ -23,7 +23,7 @@ actix-service = "2.0.0-beta.4" actix-utils = "3.0.0-beta.2" actix-web = { version = "4.0.0-beta.5", default-features = false, features = ["cookies"] } actix-rt = "2.1" -awc = { version = "3.0.0-beta.3", default-features = false, features = ["cookies"] } +awc = { version = "3.0.0-beta.4", default-features = false, features = ["cookies"] } futures-core = { version = "0.3.7", default-features = false, features = ["std"] } futures-util = { version = "0.3.7", default-features = false, features = [] } diff --git a/actix-web-actors/Cargo.toml b/actix-web-actors/Cargo.toml index 82cd19624..1c8dcbd1b 100644 --- a/actix-web-actors/Cargo.toml +++ b/actix-web-actors/Cargo.toml @@ -31,6 +31,6 @@ tokio = { version = "1", features = ["sync"] } actix-rt = "2.2" actix-test = "0.0.1" -awc = { version = "3.0.0-beta.3", default-features = false } +awc = { version = "3.0.0-beta.4", default-features = false } env_logger = "0.8" futures-util = { version = "0.3.7", default-features = false } diff --git a/awc/CHANGES.md b/awc/CHANGES.md index c1031239a..eb008ff98 100644 --- a/awc/CHANGES.md +++ b/awc/CHANGES.md @@ -1,13 +1,17 @@ # Changes ## Unreleased - 2021-xx-xx + + +## 3.0.0-beta.4 - 2021-04-02 ### Added * Add `Client::headers` to get default mut reference of `HeaderMap` of client object. [#2114] ### Changed * `ConnectorService` type is renamed to `BoxConnectorService`. [#2081] * Fix http/https encoding when enabling `compress` feature. [#2116] -* Rename `TestResponse::header` to `append_header`, `set` to `insert_header`. `TestResponse` header methods now take `IntoHeaderPair` tuples. [#2094] +* Rename `TestResponse::header` to `append_header`, `set` to `insert_header`. `TestResponse` header + methods now take `IntoHeaderPair` tuples. [#2094] [#2081]: https://github.com/actix/actix-web/pull/2081 [#2094]: https://github.com/actix/actix-web/pull/2094 diff --git a/awc/Cargo.toml b/awc/Cargo.toml index b3acbd5f1..8f00913bb 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -1,7 +1,10 @@ [package] name = "awc" -version = "3.0.0-beta.3" -authors = ["Nikolay Kim "] +version = "3.0.0-beta.4" +authors = [ + "Nikolay Kim ", + "fakeshadow <24548779@qq.com>", +] description = "Async HTTP and WebSocket client library built on the Actix ecosystem" readme = "README.md" keywords = ["actix", "http", "framework", "async", "web"] From 8561263545d5df1004371a17ab5001acfe1de4e7 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Fri, 2 Apr 2021 09:43:51 +0100 Subject: [PATCH 37/79] prepare files release 0.6.0-beta.4 --- actix-files/CHANGES.md | 4 ++++ actix-files/Cargo.toml | 2 +- actix-files/README.md | 4 ++-- awc/README.md | 4 ++-- 4 files changed, 9 insertions(+), 5 deletions(-) diff --git a/actix-files/CHANGES.md b/actix-files/CHANGES.md index c035d5afe..eb66e0e07 100644 --- a/actix-files/CHANGES.md +++ b/actix-files/CHANGES.md @@ -3,6 +3,10 @@ ## Unreleased - 2021-xx-xx +## 0.6.0-beta.4 - 2021-04-02 +* No notable changes. + + ## 0.6.0-beta.3 - 2021-03-09 * No notable changes. diff --git a/actix-files/Cargo.toml b/actix-files/Cargo.toml index ff8331762..ce2a54667 100644 --- a/actix-files/Cargo.toml +++ b/actix-files/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-files" -version = "0.6.0-beta.3" +version = "0.6.0-beta.4" authors = ["Nikolay Kim "] description = "Static file serving for Actix Web" readme = "README.md" diff --git a/actix-files/README.md b/actix-files/README.md index c7b7424ec..895d5e687 100644 --- a/actix-files/README.md +++ b/actix-files/README.md @@ -3,11 +3,11 @@ > Static file serving for Actix Web [![crates.io](https://img.shields.io/crates/v/actix-files?label=latest)](https://crates.io/crates/actix-files) -[![Documentation](https://docs.rs/actix-files/badge.svg?version=0.6.0-beta.3)](https://docs.rs/actix-files/0.6.0-beta.3) +[![Documentation](https://docs.rs/actix-files/badge.svg?version=0.6.0-beta.4)](https://docs.rs/actix-files/0.6.0-beta.4) [![Version](https://img.shields.io/badge/rustc-1.46+-ab6000.svg)](https://blog.rust-lang.org/2020/03/12/Rust-1.46.html) ![License](https://img.shields.io/crates/l/actix-files.svg)
-[![dependency status](https://deps.rs/crate/actix-files/0.6.0-beta.3/status.svg)](https://deps.rs/crate/actix-files/0.6.0-beta.3) +[![dependency status](https://deps.rs/crate/actix-files/0.6.0-beta.4/status.svg)](https://deps.rs/crate/actix-files/0.6.0-beta.4) [![Download](https://img.shields.io/crates/d/actix-files.svg)](https://crates.io/crates/actix-files) [![Join the chat at https://gitter.im/actix/actix](https://badges.gitter.im/actix/actix.svg)](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) diff --git a/awc/README.md b/awc/README.md index 2e7dad320..a836a2497 100644 --- a/awc/README.md +++ b/awc/README.md @@ -3,9 +3,9 @@ > Async HTTP and WebSocket client library. [![crates.io](https://img.shields.io/crates/v/awc?label=latest)](https://crates.io/crates/awc) -[![Documentation](https://docs.rs/awc/badge.svg?version=3.0.0-beta.3)](https://docs.rs/awc/3.0.0-beta.3) +[![Documentation](https://docs.rs/awc/badge.svg?version=3.0.0-beta.4)](https://docs.rs/awc/3.0.0-beta.4) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/awc) -[![Dependency Status](https://deps.rs/crate/awc/3.0.0-beta.3/status.svg)](https://deps.rs/crate/awc/3.0.0-beta.3) +[![Dependency Status](https://deps.rs/crate/awc/3.0.0-beta.4/status.svg)](https://deps.rs/crate/awc/3.0.0-beta.4) [![Join the chat at https://gitter.im/actix/actix-web](https://badges.gitter.im/actix/actix-web.svg)](https://gitter.im/actix/actix-web?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) ## Documentation & Resources From 05c7505563b306f96e46c0cfb6fe15317a9c9b14 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Fri, 2 Apr 2021 09:45:31 +0100 Subject: [PATCH 38/79] prepare multipart release 0.4.0-beta.4 --- actix-multipart/CHANGES.md | 4 ++++ actix-multipart/Cargo.toml | 4 ++-- actix-multipart/README.md | 4 ++-- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/actix-multipart/CHANGES.md b/actix-multipart/CHANGES.md index ce5bd57d7..cd50305cb 100644 --- a/actix-multipart/CHANGES.md +++ b/actix-multipart/CHANGES.md @@ -3,6 +3,10 @@ ## Unreleased - 2021-xx-xx +## 0.4.0-beta.4 - 2021-04-02 +* No notable changes. + + ## 0.4.0-beta.3 - 2021-03-09 * No notable changes. diff --git a/actix-multipart/Cargo.toml b/actix-multipart/Cargo.toml index e5a6bb7da..77558fcc5 100644 --- a/actix-multipart/Cargo.toml +++ b/actix-multipart/Cargo.toml @@ -1,13 +1,13 @@ [package] name = "actix-multipart" -version = "0.4.0-beta.3" +version = "0.4.0-beta.4" authors = ["Nikolay Kim "] description = "Multipart form support for Actix Web" readme = "README.md" keywords = ["http", "web", "framework", "async", "futures"] homepage = "https://actix.rs" repository = "https://github.com/actix/actix-web.git" -documentation = "https://docs.rs/actix-multipart/" +documentation = "https://docs.rs/actix-multipart" license = "MIT OR Apache-2.0" edition = "2018" diff --git a/actix-multipart/README.md b/actix-multipart/README.md index e4a54ac47..8a4279a62 100644 --- a/actix-multipart/README.md +++ b/actix-multipart/README.md @@ -3,11 +3,11 @@ > Multipart form support for Actix Web. [![crates.io](https://img.shields.io/crates/v/actix-multipart?label=latest)](https://crates.io/crates/actix-multipart) -[![Documentation](https://docs.rs/actix-multipart/badge.svg?version=0.4.0-beta.3)](https://docs.rs/actix-multipart/0.4.0-beta.3) +[![Documentation](https://docs.rs/actix-multipart/badge.svg?version=0.4.0-beta.4)](https://docs.rs/actix-multipart/0.4.0-beta.4) [![Version](https://img.shields.io/badge/rustc-1.46+-ab6000.svg)](https://blog.rust-lang.org/2020/03/12/Rust-1.46.html) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-multipart.svg)
-[![dependency status](https://deps.rs/crate/actix-multipart/0.4.0-beta.3/status.svg)](https://deps.rs/crate/actix-multipart/0.4.0-beta.3) +[![dependency status](https://deps.rs/crate/actix-multipart/0.4.0-beta.4/status.svg)](https://deps.rs/crate/actix-multipart/0.4.0-beta.4) [![Download](https://img.shields.io/crates/d/actix-multipart.svg)](https://crates.io/crates/actix-multipart) ## Documentation & Resources From a9641e475adee39858ad7421e2c97d68f719c703 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Fri, 2 Apr 2021 09:54:35 +0100 Subject: [PATCH 39/79] prepare http-test release 3.0.0-beta.4 --- actix-http-test/CHANGES.md | 7 ++++++- actix-http-test/Cargo.toml | 2 +- actix-http-test/README.md | 4 ++-- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/actix-http-test/CHANGES.md b/actix-http-test/CHANGES.md index 0fac84a6c..1dbd9a15b 100644 --- a/actix-http-test/CHANGES.md +++ b/actix-http-test/CHANGES.md @@ -1,9 +1,14 @@ # Changes ## Unreleased - 2021-xx-xx -### Added + + +## 3.0.0-beta.4 - 2021-04-02 * Added `TestServer::client_headers` method. [#2097] +[#2097]: https://github.com/actix/actix-web/pull/2097 + + ## 3.0.0-beta.3 - 2021-03-09 * No notable changes. diff --git a/actix-http-test/Cargo.toml b/actix-http-test/Cargo.toml index f1827eb01..e1dd4c4e8 100644 --- a/actix-http-test/Cargo.toml +++ b/actix-http-test/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-http-test" -version = "3.0.0-beta.3" +version = "3.0.0-beta.4" authors = ["Nikolay Kim "] description = "Various helpers for Actix applications to use during testing" readme = "README.md" diff --git a/actix-http-test/README.md b/actix-http-test/README.md index 8cec94808..b8cf450d4 100644 --- a/actix-http-test/README.md +++ b/actix-http-test/README.md @@ -3,9 +3,9 @@ > Various helpers for Actix applications to use during testing. [![crates.io](https://img.shields.io/crates/v/actix-http-test?label=latest)](https://crates.io/crates/actix-http-test) -[![Documentation](https://docs.rs/actix-http-test/badge.svg?version=3.0.0-beta.3)](https://docs.rs/actix-http-test/3.0.0-beta.3) +[![Documentation](https://docs.rs/actix-http-test/badge.svg?version=3.0.0-beta.4)](https://docs.rs/actix-http-test/3.0.0-beta.4) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-http-test) -[![Dependency Status](https://deps.rs/crate/actix-http-test/3.0.0-beta.3/status.svg)](https://deps.rs/crate/actix-http-test/3.0.0-beta.3) +[![Dependency Status](https://deps.rs/crate/actix-http-test/3.0.0-beta.4/status.svg)](https://deps.rs/crate/actix-http-test/3.0.0-beta.4) [![Join the chat at https://gitter.im/actix/actix-web](https://badges.gitter.im/actix/actix-web.svg)](https://gitter.im/actix/actix-web?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) ## Documentation & Resources From e0ae8e59bf6ee0474d99d92e54309682f1e6a12a Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Fri, 2 Apr 2021 09:55:35 +0100 Subject: [PATCH 40/79] prepare actors release 4.0.0-beta.4 --- actix-web-actors/CHANGES.md | 4 ++++ actix-web-actors/Cargo.toml | 4 ++-- actix-web-actors/README.md | 4 ++-- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/actix-web-actors/CHANGES.md b/actix-web-actors/CHANGES.md index 80eef08c1..cd76b201e 100644 --- a/actix-web-actors/CHANGES.md +++ b/actix-web-actors/CHANGES.md @@ -3,6 +3,10 @@ ## Unreleased - 2021-xx-xx +## 4.0.0-beta.4 - 2021-04-02 +* No notable changes. + + ## 4.0.0-beta.3 - 2021-03-09 * No notable changes. diff --git a/actix-web-actors/Cargo.toml b/actix-web-actors/Cargo.toml index 1c8dcbd1b..a539227e4 100644 --- a/actix-web-actors/Cargo.toml +++ b/actix-web-actors/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-web-actors" -version = "4.0.0-beta.3" +version = "4.0.0-beta.4" authors = ["Nikolay Kim "] description = "Actix actors support for Actix Web" readme = "README.md" @@ -29,7 +29,7 @@ tokio = { version = "1", features = ["sync"] } [dev-dependencies] actix-rt = "2.2" -actix-test = "0.0.1" +actix-test = "0.1.0-beta.1" awc = { version = "3.0.0-beta.4", default-features = false } env_logger = "0.8" diff --git a/actix-web-actors/README.md b/actix-web-actors/README.md index 6d9d573d7..2dc779f10 100644 --- a/actix-web-actors/README.md +++ b/actix-web-actors/README.md @@ -3,11 +3,11 @@ > Actix actors support for Actix Web. [![crates.io](https://img.shields.io/crates/v/actix-web-actors?label=latest)](https://crates.io/crates/actix-web-actors) -[![Documentation](https://docs.rs/actix-web-actors/badge.svg?version=4.0.0-beta.3)](https://docs.rs/actix-web-actors/4.0.0-beta.3) +[![Documentation](https://docs.rs/actix-web-actors/badge.svg?version=4.0.0-beta.4)](https://docs.rs/actix-web-actors/4.0.0-beta.4) [![Version](https://img.shields.io/badge/rustc-1.46+-ab6000.svg)](https://blog.rust-lang.org/2020/03/12/Rust-1.46.html) ![License](https://img.shields.io/crates/l/actix-web-actors.svg)
-[![dependency status](https://deps.rs/crate/actix-web-actors/4.0.0-beta.3/status.svg)](https://deps.rs/crate/actix-web-actors/4.0.0-beta.3) +[![dependency status](https://deps.rs/crate/actix-web-actors/4.0.0-beta.4/status.svg)](https://deps.rs/crate/actix-web-actors/4.0.0-beta.4) [![Download](https://img.shields.io/crates/d/actix-web-actors.svg)](https://crates.io/crates/actix-web-actors) [![Join the chat at https://gitter.im/actix/actix](https://badges.gitter.im/actix/actix.svg)](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) From e0b2246c68fdcd30221b0d8a293fcb5cb7432851 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Fri, 2 Apr 2021 10:03:01 +0100 Subject: [PATCH 41/79] prepare test release 0.1.0-beta.1 --- Cargo.toml | 2 +- actix-files/Cargo.toml | 2 +- actix-http/Cargo.toml | 2 +- actix-test/CHANGES.md | 7 +++++-- actix-test/Cargo.toml | 9 ++++++--- actix-web-codegen/Cargo.toml | 2 +- awc/Cargo.toml | 4 ++-- 7 files changed, 17 insertions(+), 11 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index c63acebf3..52db6c335 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -108,7 +108,7 @@ time = { version = "0.2.23", default-features = false, features = ["std"] } url = "2.1" [dev-dependencies] -actix-test = { version = "0.0.1", features = ["openssl", "rustls"] } +actix-test = { version = "0.1.0-beta.1", features = ["openssl", "rustls"] } awc = { version = "3.0.0-beta.4", features = ["openssl"] } brotli2 = "0.3.2" diff --git a/actix-files/Cargo.toml b/actix-files/Cargo.toml index ce2a54667..84d8dd958 100644 --- a/actix-files/Cargo.toml +++ b/actix-files/Cargo.toml @@ -35,4 +35,4 @@ percent-encoding = "2.1" [dev-dependencies] actix-rt = "2.2" actix-web = "4.0.0-beta.5" -actix-test = "0.0.1" +actix-test = "0.1.0-beta.1" diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 93ef4ec15..e77d1139c 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -90,7 +90,7 @@ trust-dns-resolver = { version = "0.20.0", optional = true } [dev-dependencies] actix-server = "2.0.0-beta.3" -actix-http-test = { version = "3.0.0-beta.3", features = ["openssl"] } +actix-http-test = { version = "3.0.0-beta.4", features = ["openssl"] } actix-tls = { version = "3.0.0-beta.5", features = ["openssl"] } criterion = "0.3" env_logger = "0.8" diff --git a/actix-test/CHANGES.md b/actix-test/CHANGES.md index 318f08f55..70e643ba7 100644 --- a/actix-test/CHANGES.md +++ b/actix-test/CHANGES.md @@ -1,6 +1,9 @@ # Changes ## Unreleased - 2021-xx-xx -* Move integration testing structs from `actix-web`. [#???] -[#???]: https://github.com/actix/actix-web/pull/??? + +## 0.1.0-beta.1 - 2021-04-02 +* Move integration testing structs from `actix-web`. [#2112] + +[#2112]: https://github.com/actix/actix-web/pull/2112 diff --git a/actix-test/Cargo.toml b/actix-test/Cargo.toml index 56d82e934..aa82fd046 100644 --- a/actix-test/Cargo.toml +++ b/actix-test/Cargo.toml @@ -1,7 +1,10 @@ [package] name = "actix-test" -version = "0.0.1" -authors = ["Rob Ede "] +version = "0.1.0-beta.1" +authors = [ + "Nikolay Kim ", + "Rob Ede ", +] edition = "2018" description = "Integration testing tools for Actix Web applications" license = "MIT OR Apache-2.0" @@ -18,7 +21,7 @@ openssl = ["tls-openssl", "actix-http/openssl"] [dependencies] actix-codec = "0.4.0-beta.1" actix-http = { version = "3.0.0-beta.5", features = ["cookies"] } -actix-http-test = { version = "3.0.0-beta.3", features = [] } +actix-http-test = { version = "3.0.0-beta.4", features = [] } actix-service = "2.0.0-beta.4" actix-utils = "3.0.0-beta.2" actix-web = { version = "4.0.0-beta.5", default-features = false, features = ["cookies"] } diff --git a/actix-web-codegen/Cargo.toml b/actix-web-codegen/Cargo.toml index ec02a908e..04c988392 100644 --- a/actix-web-codegen/Cargo.toml +++ b/actix-web-codegen/Cargo.toml @@ -20,7 +20,7 @@ proc-macro2 = "1" [dev-dependencies] actix-rt = "2.2" -actix-test = "0.0.1" +actix-test = "0.1.0-beta.1" actix-utils = "3.0.0-beta.4" actix-web = "4.0.0-beta.5" diff --git a/awc/Cargo.toml b/awc/Cargo.toml index 8f00913bb..aebf73ef5 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -71,11 +71,11 @@ tls-rustls = { version = "0.19.0", package = "rustls", optional = true, features [dev-dependencies] actix-web = { version = "4.0.0-beta.5", features = ["openssl"] } actix-http = { version = "3.0.0-beta.5", features = ["openssl"] } -actix-http-test = { version = "3.0.0-beta.3", features = ["openssl"] } +actix-http-test = { version = "3.0.0-beta.4", features = ["openssl"] } actix-utils = "3.0.0-beta.4" actix-server = "2.0.0-beta.3" actix-tls = { version = "3.0.0-beta.5", features = ["openssl", "rustls"] } -actix-test = { version = "0.0.1", features = ["openssl", "rustls"] } +actix-test = { version = "0.1.0-beta.1", features = ["openssl", "rustls"] } brotli2 = "0.3.2" env_logger = "0.8" From 3f5a73793aee0ba6e2639f48af3513a90d48b73b Mon Sep 17 00:00:00 2001 From: Ibraheem Ahmed Date: Thu, 8 Apr 2021 15:51:16 -0400 Subject: [PATCH 42/79] make module/crate re-exports doc inline (#2141) --- src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib.rs b/src/lib.rs index 1a11921a9..1d5d5b83d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -99,6 +99,7 @@ pub mod web; pub use actix_http::cookie; pub use actix_http::Response as HttpResponse; pub use actix_http::{body, Error, HttpMessage, ResponseError, Result}; +#[doc(inline)] pub use actix_rt as rt; pub use actix_web_codegen::*; From 44a2d2214ce99279f7cf215cfd7c6787babed5f8 Mon Sep 17 00:00:00 2001 From: Ibraheem Ahmed Date: Thu, 8 Apr 2021 20:28:35 -0400 Subject: [PATCH 43/79] update year in MIT license (#2143) --- LICENSE-MIT | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE-MIT b/LICENSE-MIT index 95938ef15..d559b1cd1 100644 --- a/LICENSE-MIT +++ b/LICENSE-MIT @@ -1,4 +1,4 @@ -Copyright (c) 2017 Actix Team +Copyright (c) 2017-NOW Actix Team Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated From c72d77065d068f1861ab8628fb072452ec5f0e33 Mon Sep 17 00:00:00 2001 From: Ibraheem Ahmed Date: Thu, 8 Apr 2021 22:22:51 -0400 Subject: [PATCH 44/79] derive debug where possible (#2142) --- src/types/form.rs | 8 +------- src/types/header.rs | 11 +---------- src/types/json.rs | 10 +--------- src/types/path.rs | 10 ++-------- src/types/query.rs | 18 +++++++++--------- 5 files changed, 14 insertions(+), 43 deletions(-) diff --git a/src/types/form.rs b/src/types/form.rs index 0985bd945..14c1369ff 100644 --- a/src/types/form.rs +++ b/src/types/form.rs @@ -80,7 +80,7 @@ use crate::{ /// }) /// } /// ``` -#[derive(PartialEq, Eq, PartialOrd, Ord)] +#[derive(PartialEq, Eq, PartialOrd, Ord, Debug)] pub struct Form(pub T); impl Form { @@ -150,12 +150,6 @@ where } } -impl fmt::Debug for Form { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.0.fmt(f) - } -} - impl fmt::Display for Form { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.0.fmt(f) diff --git a/src/types/header.rs b/src/types/header.rs index 1f8be707a..9b64f445d 100644 --- a/src/types/header.rs +++ b/src/types/header.rs @@ -23,7 +23,7 @@ use crate::{ /// format!("Request was sent at {}", date.to_string()) /// } /// ``` -#[derive(Clone, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug)] pub struct Header(pub T); impl Header { @@ -47,15 +47,6 @@ impl ops::DerefMut for Header { } } -impl fmt::Debug for Header -where - T: fmt::Debug, -{ - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "Header: {:?}", self.0) - } -} - impl fmt::Display for Header where T: fmt::Display, diff --git a/src/types/json.rs b/src/types/json.rs index 068dfeb2c..97439d8fd 100644 --- a/src/types/json.rs +++ b/src/types/json.rs @@ -73,6 +73,7 @@ use crate::{ /// }) /// } /// ``` +#[derive(Debug)] pub struct Json(pub T); impl Json { @@ -96,15 +97,6 @@ impl ops::DerefMut for Json { } } -impl fmt::Debug for Json -where - T: fmt::Debug, -{ - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "Json: {:?}", self.0) - } -} - impl fmt::Display for Json where T: fmt::Display, diff --git a/src/types/path.rs b/src/types/path.rs index 90ee5296b..33ea70629 100644 --- a/src/types/path.rs +++ b/src/types/path.rs @@ -45,7 +45,7 @@ use crate::{dev::Payload, error::PathError, FromRequest, HttpRequest}; /// format!("Welcome {}!", info.name) /// } /// ``` -#[derive(PartialEq, Eq, PartialOrd, Ord)] +#[derive(PartialEq, Eq, PartialOrd, Ord, Debug)] pub struct Path(T); impl Path { @@ -81,12 +81,6 @@ impl From for Path { } } -impl fmt::Debug for Path { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.0.fmt(f) - } -} - impl fmt::Display for Path { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.0.fmt(f) @@ -261,7 +255,7 @@ mod tests { assert_eq!(s.value, "user2"); assert_eq!( format!("{}, {:?}", s, s), - "MyStruct(name, user2), MyStruct { key: \"name\", value: \"user2\" }" + "MyStruct(name, user2), Path(MyStruct { key: \"name\", value: \"user2\" })" ); let s = s.into_inner(); assert_eq!(s.value, "user2"); diff --git a/src/types/query.rs b/src/types/query.rs index b6c025ef3..4807335bc 100644 --- a/src/types/query.rs +++ b/src/types/query.rs @@ -57,7 +57,7 @@ use crate::{dev::Payload, error::QueryPayloadError, Error, FromRequest, HttpRequ /// "OK".to_string() /// } /// ``` -#[derive(Clone, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug)] pub struct Query(pub T); impl Query { @@ -100,12 +100,6 @@ impl ops::DerefMut for Query { } } -impl fmt::Debug for Query { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.0.fmt(f) - } -} - impl fmt::Display for Query { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.0.fmt(f) @@ -226,7 +220,10 @@ mod tests { let mut s = Query::::from_query(&req.query_string()).unwrap(); assert_eq!(s.id, "test"); - assert_eq!(format!("{}, {:?}", s, s), "test, Id { id: \"test\" }"); + assert_eq!( + format!("{}, {:?}", s, s), + "test, Query(Id { id: \"test\" })" + ); s.id = "test1".to_string(); let s = s.into_inner(); @@ -244,7 +241,10 @@ mod tests { let mut s = Query::::from_request(&req, &mut pl).await.unwrap(); assert_eq!(s.id, "test"); - assert_eq!(format!("{}, {:?}", s, s), "test, Id { id: \"test\" }"); + assert_eq!( + format!("{}, {:?}", s, s), + "test, Query(Id { id: \"test\" })" + ); s.id = "test1".to_string(); let s = s.into_inner(); From 44c55dd036eb1e4adae0853267685866257dc789 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Fri, 9 Apr 2021 18:07:10 +0100 Subject: [PATCH 45/79] remove cookie support from -http (#2065) --- .cargo/config.toml | 4 + CHANGES.md | 3 + Cargo.toml | 46 +- actix-files/src/error.rs | 6 +- actix-http/CHANGES.md | 7 + actix-http/Cargo.toml | 11 +- actix-http/examples/ws.rs | 6 +- actix-http/src/error.rs | 22 +- actix-http/src/h1/dispatcher.rs | 2 +- actix-http/src/http_message.rs | 40 -- actix-http/src/lib.rs | 7 - actix-http/src/message.rs | 4 +- actix-http/src/request.rs | 14 +- actix-http/src/response.rs | 294 +------- actix-http/src/test.rs | 32 - actix-http/src/ws/mod.rs | 6 +- actix-multipart/src/error.rs | 3 +- actix-test/Cargo.toml | 2 +- actix-test/src/lib.rs | 8 +- actix-web-actors/src/ws.rs | 2 +- actix-web-codegen/tests/test_macro.rs | 18 +- awc/Cargo.toml | 3 +- awc/src/lib.rs | 7 +- awc/src/request.rs | 8 +- awc/src/response.rs | 52 +- awc/src/test.rs | 9 +- awc/src/ws.rs | 6 +- awc/tests/test_client.rs | 5 +- src/error.rs | 26 +- src/handler.rs | 27 +- src/lib.rs | 32 +- src/request.rs | 66 +- src/responder.rs | 35 +- src/response.rs | 968 ++++++++++++++++++++++++++ src/service.rs | 40 +- src/test.rs | 48 +- src/types/json.rs | 4 +- src/types/path.rs | 11 +- src/types/query.rs | 4 +- tests/test_server.rs | 13 +- 40 files changed, 1335 insertions(+), 566 deletions(-) create mode 100644 src/response.rs diff --git a/.cargo/config.toml b/.cargo/config.toml index 40fe3e573..0bab205cd 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -1,3 +1,7 @@ [alias] chk = "hack check --workspace --all-features --tests --examples" lint = "hack --clean-per-run clippy --workspace --tests --examples" +ci-min = "hack check --workspace --no-default-features" +ci-min-test = "hack check --workspace --no-default-features --tests --examples" +ci-default = "hack check --workspace" +ci-full = "check --workspace --bins --examples --tests" diff --git a/CHANGES.md b/CHANGES.md index a55005c7a..64e0891e5 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,7 +1,10 @@ # Changes ## Unreleased - 2021-xx-xx +### Added +* `HttpResponse` and `HttpResponseBuilder` structs. [#2065] +[#2065]: https://github.com/actix/actix-web/pull/2065 ## 4.0.0-beta.5 - 2021-04-02 ### Added diff --git a/Cargo.toml b/Cargo.toml index 52db6c335..b8b18a84c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,11 +16,7 @@ edition = "2018" [package.metadata.docs.rs] # features that docs.rs will build with -features = ["openssl", "rustls", "compress", "secure-cookies"] - -[badges] -travis-ci = { repository = "actix/actix-web", branch = "master" } -codecov = { repository = "actix/actix-web", branch = "master", service = "github" } +features = ["openssl", "rustls", "compress", "cookies", "secure-cookies"] [lib] name = "actix_web" @@ -38,6 +34,8 @@ members = [ "actix-http-test", "actix-test", ] +# enable when MSRV is 1.51+ +# resolver = "2" [features] default = ["compress", "cookies"] @@ -46,10 +44,10 @@ default = ["compress", "cookies"] compress = ["actix-http/compress"] # support for cookies -cookies = ["actix-http/cookies"] +cookies = ["cookie"] # secure cookies feature -secure-cookies = ["actix-http/secure-cookies"] +secure-cookies = ["cookie/secure"] # openssl openssl = ["actix-http/openssl", "actix-tls/accept", "actix-tls/openssl"] @@ -57,22 +55,6 @@ openssl = ["actix-http/openssl", "actix-tls/accept", "actix-tls/openssl"] # rustls rustls = ["actix-http/rustls", "actix-tls/accept", "actix-tls/rustls"] -[[example]] -name = "basic" -required-features = ["compress"] - -[[example]] -name = "uds" -required-features = ["compress"] - -[[test]] -name = "test_server" -required-features = ["compress", "cookies"] - -[[example]] -name = "on_connect" -required-features = [] - [dependencies] actix-codec = "0.4.0-beta.1" actix-macros = "0.2.0" @@ -88,11 +70,13 @@ actix-http = "3.0.0-beta.5" ahash = "0.7" bytes = "1" +cookie = { version = "0.15", features = ["percent-encode"], optional = true } derive_more = "0.99.5" either = "1.5.3" encoding_rs = "0.8" futures-core = { version = "0.3.7", default-features = false } futures-util = { version = "0.3.7", default-features = false } +itoa = "0.4" language-tags = "0.2" once_cell = "1.5" log = "0.4" @@ -137,6 +121,22 @@ actix-web-actors = { path = "actix-web-actors" } actix-web-codegen = { path = "actix-web-codegen" } awc = { path = "awc" } +[[test]] +name = "test_server" +required-features = ["compress", "cookies"] + +[[example]] +name = "basic" +required-features = ["compress"] + +[[example]] +name = "uds" +required-features = ["compress"] + +[[example]] +name = "on_connect" +required-features = [] + [[bench]] name = "server" harness = false diff --git a/actix-files/src/error.rs b/actix-files/src/error.rs index 9b30cbaa2..e5f2d4779 100644 --- a/actix-files/src/error.rs +++ b/actix-files/src/error.rs @@ -1,4 +1,4 @@ -use actix_web::{http::StatusCode, HttpResponse, ResponseError}; +use actix_web::{http::StatusCode, ResponseError}; use derive_more::Display; /// Errors which can occur when serving static files. @@ -16,8 +16,8 @@ pub enum FilesError { /// Return `NotFound` for `FilesError` impl ResponseError for FilesError { - fn error_response(&self) -> HttpResponse { - HttpResponse::new(StatusCode::NOT_FOUND) + fn status_code(&self) -> StatusCode { + StatusCode::NOT_FOUND } } diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 9765be3a6..5e4258677 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -1,6 +1,13 @@ # Changes ## Unreleased - 2021-xx-xx +### Removed +* `cookies` feature flag. [#2065] +* Top-level `cookies` mod (re-export). [#2065] +* `HttpMessage` trait loses the `cookies` and `cookie` methods. [#2065] +* `impl ResponseError for CookieParseError`. [#2065] + +[#2065]: https://github.com/actix/actix-web/pull/2065 ## 3.0.0-beta.5 - 2021-04-02 diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index e77d1139c..573376b07 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -16,7 +16,7 @@ edition = "2018" [package.metadata.docs.rs] # features that docs.rs will build with -features = ["openssl", "rustls", "compress", "cookies", "secure-cookies"] +features = ["openssl", "rustls", "compress"] [lib] name = "actix_http" @@ -34,12 +34,6 @@ rustls = ["actix-tls/rustls"] # enable compression support compress = ["flate2", "brotli2"] -# support for cookies -cookies = ["cookie"] - -# support for secure cookies -secure-cookies = ["cookies", "cookie/secure"] - # trust-dns as client dns resolver trust-dns = ["trust-dns-resolver"] @@ -55,7 +49,6 @@ base64 = "0.13" bitflags = "1.2" bytes = "1" bytestring = "1" -cookie = { version = "0.14.1", features = ["percent-encode"], optional = true } derive_more = "0.99.5" encoding_rs = "0.8" futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] } @@ -95,7 +88,7 @@ actix-tls = { version = "3.0.0-beta.5", features = ["openssl"] } criterion = "0.3" env_logger = "0.8" rcgen = "0.8" -serde_derive = "1.0" +serde = { version = "1.0", features = ["derive"] } tls-openssl = { version = "0.10", package = "openssl" } tls-rustls = { version = "0.19", package = "rustls" } diff --git a/actix-http/examples/ws.rs b/actix-http/examples/ws.rs index 4e03aa8ab..af66f7d71 100644 --- a/actix-http/examples/ws.rs +++ b/actix-http/examples/ws.rs @@ -11,7 +11,7 @@ use std::{ }; use actix_codec::Encoder; -use actix_http::{error::Error, ws, HttpService, Request, Response}; +use actix_http::{body::BodyStream, error::Error, ws, HttpService, Request, Response}; use actix_rt::time::{interval, Interval}; use actix_server::Server; use bytes::{Bytes, BytesMut}; @@ -34,14 +34,14 @@ async fn main() -> io::Result<()> { .await } -async fn handler(req: Request) -> Result { +async fn handler(req: Request) -> Result>, Error> { log::info!("handshaking"); let mut res = ws::handshake(req.head())?; // handshake will always fail under HTTP/2 log::info!("responding"); - Ok(res.streaming(Heartbeat::new(ws::Codec::new()))) + Ok(res.message_body(BodyStream::new(Heartbeat::new(ws::Codec::new())))) } struct Heartbeat { diff --git a/actix-http/src/error.rs b/actix-http/src/error.rs index 0178be80c..03693ff2f 100644 --- a/actix-http/src/error.rs +++ b/actix-http/src/error.rs @@ -14,12 +14,7 @@ use serde::de::value::Error as DeError; use serde_json::error::Error as JsonError; use serde_urlencoded::ser::Error as FormError; -use crate::body::Body; -use crate::helpers::Writer; -use crate::response::{Response, ResponseBuilder}; - -#[cfg(feature = "cookies")] -pub use crate::cookie::ParseError as CookieParseError; +use crate::{body::Body, helpers::Writer, Response, ResponseBuilder}; /// A specialized [`std::result::Result`] /// for actix web operations @@ -378,14 +373,6 @@ impl ResponseError for PayloadError { } } -/// Return `BadRequest` for `cookie::ParseError` -#[cfg(feature = "cookies")] -impl ResponseError for crate::cookie::ParseError { - fn status_code(&self) -> StatusCode { - StatusCode::BAD_REQUEST - } -} - #[derive(Debug, Display, From)] /// A set of errors that can occur during dispatching HTTP requests pub enum DispatchError { @@ -959,13 +946,6 @@ mod tests { assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR); } - #[cfg(feature = "cookies")] - #[test] - fn test_cookie_parse() { - let resp: Response = CookieParseError::EmptyName.error_response(); - assert_eq!(resp.status(), StatusCode::BAD_REQUEST); - } - #[test] fn test_as_response() { let orig = io::Error::new(io::ErrorKind::Other, "other"); diff --git a/actix-http/src/h1/dispatcher.rs b/actix-http/src/h1/dispatcher.rs index bf0365693..bba79217a 100644 --- a/actix-http/src/h1/dispatcher.rs +++ b/actix-http/src/h1/dispatcher.rs @@ -346,7 +346,7 @@ where // send service call error as response Poll::Ready(Err(err)) => { - let res: Response = err.into().into(); + let res = Response::from_error(err.into()); let (res, body) = res.replace_body(()); self.as_mut().send_response(res, body.into_body())?; } diff --git a/actix-http/src/http_message.rs b/actix-http/src/http_message.rs index b1f04e50d..0f4e347e0 100644 --- a/actix-http/src/http_message.rs +++ b/actix-http/src/http_message.rs @@ -9,11 +9,6 @@ use crate::error::{ContentTypeError, ParseError}; use crate::extensions::Extensions; use crate::header::{Header, HeaderMap}; use crate::payload::Payload; -#[cfg(feature = "cookies")] -use crate::{cookie::Cookie, error::CookieParseError}; - -#[cfg(feature = "cookies")] -struct Cookies(Vec>); /// Trait that implements general purpose operations on HTTP messages. pub trait HttpMessage: Sized { @@ -104,41 +99,6 @@ pub trait HttpMessage: Sized { Ok(false) } } - - /// Load request cookies. - #[cfg(feature = "cookies")] - fn cookies(&self) -> Result>>, CookieParseError> { - if self.extensions().get::().is_none() { - let mut cookies = Vec::new(); - for hdr in self.headers().get_all(header::COOKIE) { - let s = - str::from_utf8(hdr.as_bytes()).map_err(CookieParseError::from)?; - for cookie_str in s.split(';').map(|s| s.trim()) { - if !cookie_str.is_empty() { - cookies.push(Cookie::parse_encoded(cookie_str)?.into_owned()); - } - } - } - self.extensions_mut().insert(Cookies(cookies)); - } - - Ok(Ref::map(self.extensions(), |ext| { - &ext.get::().unwrap().0 - })) - } - - /// Return request cookie. - #[cfg(feature = "cookies")] - fn cookie(&self, name: &str) -> Option> { - if let Ok(cookies) = self.cookies() { - for cookie in cookies.iter() { - if cookie.name() == name { - return Some(cookie.to_owned()); - } - } - } - None - } } impl<'a, T> HttpMessage for &'a mut T diff --git a/actix-http/src/lib.rs b/actix-http/src/lib.rs index 574d4ef68..5dd232491 100644 --- a/actix-http/src/lib.rs +++ b/actix-http/src/lib.rs @@ -6,13 +6,11 @@ //! | `openssl` | TLS support via [OpenSSL]. | //! | `rustls` | TLS support via [rustls]. | //! | `compress` | Payload compression support. (Deflate, Gzip & Brotli) | -//! | `cookies` | Support for cookies backed by the [cookie] crate. | //! | `secure-cookies` | Adds for secure cookies. Enables `cookies` feature. | //! | `trust-dns` | Use [trust-dns] as the client DNS resolver. | //! //! [OpenSSL]: https://crates.io/crates/openssl //! [rustls]: https://crates.io/crates/rustls -//! [cookie]: https://crates.io/crates/cookie //! [trust-dns]: https://crates.io/crates/trust-dns #![deny(rust_2018_idioms, nonstandard_style)] @@ -55,9 +53,6 @@ pub mod h2; pub mod test; pub mod ws; -#[cfg(feature = "cookies")] -pub use cookie; - pub use self::builder::HttpServiceBuilder; pub use self::config::{KeepAlive, ServiceConfig}; pub use self::error::{Error, ResponseError, Result}; @@ -78,8 +73,6 @@ pub mod http { pub use http::{uri, Error, Uri}; pub use http::{Method, StatusCode, Version}; - #[cfg(feature = "cookies")] - pub use crate::cookie::{Cookie, CookieBuilder}; pub use crate::header::HeaderMap; /// A collection of HTTP headers and helpers. diff --git a/actix-http/src/message.rs b/actix-http/src/message.rs index 6438ccba0..c89f5311a 100644 --- a/actix-http/src/message.rs +++ b/actix-http/src/message.rs @@ -345,8 +345,8 @@ impl ResponseHead { } pub struct Message { - // Rc here should not be cloned by anyone. - // It's used to reuse allocation of T and no shared ownership is allowed. + /// Rc here should not be cloned by anyone. + /// It's used to reuse allocation of T and no shared ownership is allowed. head: Rc, } diff --git a/actix-http/src/request.rs b/actix-http/src/request.rs index 197ec11c6..09c6dd296 100644 --- a/actix-http/src/request.rs +++ b/actix-http/src/request.rs @@ -2,16 +2,18 @@ use std::{ cell::{Ref, RefMut}, - fmt, net, + fmt, net, str, }; use http::{header, Method, Uri, Version}; -use crate::extensions::Extensions; -use crate::header::HeaderMap; -use crate::message::{Message, RequestHead}; -use crate::payload::{Payload, PayloadStream}; -use crate::HttpMessage; +use crate::{ + extensions::Extensions, + header::HeaderMap, + message::{Message, RequestHead}, + payload::{Payload, PayloadStream}, + HttpMessage, +}; /// Request pub struct Request

{ diff --git a/actix-http/src/response.rs b/actix-http/src/response.rs index b27f477c9..a96a13bd3 100644 --- a/actix-http/src/response.rs +++ b/actix-http/src/response.rs @@ -14,17 +14,16 @@ use bytes::{Bytes, BytesMut}; use futures_core::Stream; use serde::Serialize; -use crate::body::{Body, BodyStream, MessageBody, ResponseBody}; -use crate::error::Error; -use crate::extensions::Extensions; -use crate::header::{IntoHeaderPair, IntoHeaderValue}; -use crate::http::header::{self, HeaderName}; -use crate::http::{Error as HttpError, HeaderMap, StatusCode}; -use crate::message::{BoxedResponseHead, ConnectionType, ResponseHead}; -#[cfg(feature = "cookies")] use crate::{ - cookie::{Cookie, CookieJar}, - http::header::HeaderValue, + body::{Body, BodyStream, MessageBody, ResponseBody}, + error::Error, + extensions::Extensions, + header::{IntoHeaderPair, IntoHeaderValue}, + http::{ + header::{self, HeaderName}, + Error as HttpError, HeaderMap, StatusCode, + }, + message::{BoxedResponseHead, ConnectionType, ResponseHead}, }; /// An HTTP Response @@ -135,54 +134,6 @@ impl Response { &mut self.head.headers } - /// Get an iterator for the cookies set by this response - #[cfg(feature = "cookies")] - #[inline] - pub fn cookies(&self) -> CookieIter<'_> { - CookieIter { - iter: self.head.headers.get_all(header::SET_COOKIE), - } - } - - /// Add a cookie to this response - #[cfg(feature = "cookies")] - #[inline] - pub fn add_cookie(&mut self, cookie: &Cookie<'_>) -> Result<(), HttpError> { - let h = &mut self.head.headers; - HeaderValue::from_str(&cookie.to_string()) - .map(|c| { - h.append(header::SET_COOKIE, c); - }) - .map_err(|e| e.into()) - } - - /// Remove all cookies with the given name from this response. Returns - /// the number of cookies removed. - #[cfg(feature = "cookies")] - #[inline] - pub fn del_cookie(&mut self, name: &str) -> usize { - let h = &mut self.head.headers; - let vals: Vec = h - .get_all(header::SET_COOKIE) - .map(|v| v.to_owned()) - .collect(); - h.remove(header::SET_COOKIE); - - let mut count: usize = 0; - for v in vals { - if let Ok(s) = v.to_str() { - if let Ok(c) = Cookie::parse_encoded(s) { - if c.name() == name { - count += 1; - continue; - } - } - } - h.append(header::SET_COOKIE, v); - } - count - } - /// Connection upgrade status #[inline] pub fn upgrade(&self) -> bool { @@ -304,34 +255,12 @@ impl Future for Response { } } -#[cfg(feature = "cookies")] -pub struct CookieIter<'a> { - iter: header::GetAll<'a>, -} - -#[cfg(feature = "cookies")] -impl<'a> Iterator for CookieIter<'a> { - type Item = Cookie<'a>; - - #[inline] - fn next(&mut self) -> Option> { - for v in self.iter.by_ref() { - if let Ok(c) = Cookie::parse_encoded(v.to_str().ok()?) { - return Some(c); - } - } - None - } -} - /// An HTTP response builder. /// /// This type can be used to construct an instance of `Response` through a builder-like pattern. pub struct ResponseBuilder { head: Option, err: Option, - #[cfg(feature = "cookies")] - cookies: Option, } impl ResponseBuilder { @@ -341,8 +270,6 @@ impl ResponseBuilder { ResponseBuilder { head: Some(BoxedResponseHead::new(status)), err: None, - #[cfg(feature = "cookies")] - cookies: None, } } @@ -409,7 +336,10 @@ impl ResponseBuilder { } /// Replaced with [`Self::insert_header()`]. - #[deprecated = "Replaced with `insert_header((key, value))`."] + #[deprecated( + since = "4.0.0", + note = "Replaced with `insert_header((key, value))`. Will be removed in v5." + )] pub fn set_header(&mut self, key: K, value: V) -> &mut Self where K: TryInto, @@ -430,7 +360,10 @@ impl ResponseBuilder { } /// Replaced with [`Self::append_header()`]. - #[deprecated = "Replaced with `append_header((key, value))`."] + #[deprecated( + since = "4.0.0", + note = "Replaced with `append_header((key, value))`. Will be removed in v5." + )] pub fn header(&mut self, key: K, value: V) -> &mut Self where K: TryInto, @@ -523,63 +456,6 @@ impl ResponseBuilder { self } - /// Set a cookie - /// - /// ``` - /// use actix_http::{http, Request, Response}; - /// - /// fn index(req: Request) -> Response { - /// Response::Ok() - /// .cookie( - /// http::Cookie::build("name", "value") - /// .domain("www.rust-lang.org") - /// .path("/") - /// .secure(true) - /// .http_only(true) - /// .finish(), - /// ) - /// .finish() - /// } - /// ``` - #[cfg(feature = "cookies")] - pub fn cookie<'c>(&mut self, cookie: Cookie<'c>) -> &mut Self { - if self.cookies.is_none() { - let mut jar = CookieJar::new(); - jar.add(cookie.into_owned()); - self.cookies = Some(jar) - } else { - self.cookies.as_mut().unwrap().add(cookie.into_owned()); - } - self - } - - /// Remove cookie - /// - /// ``` - /// use actix_http::{http, Request, Response, HttpMessage}; - /// - /// fn index(req: Request) -> Response { - /// let mut builder = Response::Ok(); - /// - /// if let Some(ref cookie) = req.cookie("name") { - /// builder.del_cookie(cookie); - /// } - /// - /// builder.finish() - /// } - /// ``` - #[cfg(feature = "cookies")] - pub fn del_cookie<'a>(&mut self, cookie: &Cookie<'a>) -> &mut Self { - if self.cookies.is_none() { - self.cookies = Some(CookieJar::new()) - } - let jar = self.cookies.as_mut().unwrap(); - let cookie = cookie.clone().into_owned(); - jar.add_original(cookie.clone()); - jar.remove(cookie); - self - } - /// This method calls provided closure with builder reference if value is `true`. #[doc(hidden)] #[deprecated = "Use an if statement."] @@ -636,19 +512,7 @@ impl ResponseBuilder { return Response::from(Error::from(e)).into_body(); } - // allow unused mut when cookies feature is disabled - #[allow(unused_mut)] - let mut response = self.head.take().expect("cannot reuse response builder"); - - #[cfg(feature = "cookies")] - if let Some(ref jar) = self.cookies { - for cookie in jar.delta() { - match HeaderValue::from_str(&cookie.to_string()) { - Ok(val) => response.headers.append(header::SET_COOKIE, val), - Err(e) => return Response::from(Error::from(e)).into_body(), - }; - } - } + let response = self.head.take().expect("cannot reuse response builder"); Response { head: response, @@ -704,8 +568,6 @@ impl ResponseBuilder { ResponseBuilder { head: self.head.take(), err: self.err.take(), - #[cfg(feature = "cookies")] - cookies: self.cookies.take(), } } } @@ -724,29 +586,9 @@ fn parts<'a>( /// Convert `Response` to a `ResponseBuilder`. Body get dropped. impl From> for ResponseBuilder { fn from(res: Response) -> ResponseBuilder { - #[cfg(feature = "cookies")] - let jar = { - // If this response has cookies, load them into a jar - let mut jar: Option = None; - - for c in res.cookies() { - if let Some(ref mut j) = jar { - j.add_original(c.into_owned()); - } else { - let mut j = CookieJar::new(); - j.add_original(c.into_owned()); - jar = Some(j); - } - } - - jar - }; - ResponseBuilder { head: Some(res.head), err: None, - #[cfg(feature = "cookies")] - cookies: jar, } } } @@ -764,33 +606,9 @@ impl<'a> From<&'a ResponseHead> for ResponseBuilder { msg.no_chunking(!head.chunked()); - #[cfg(feature = "cookies")] - let jar = { - // If this response has cookies, load them into a jar - let mut jar: Option = None; - - let cookies = CookieIter { - iter: head.headers.get_all(header::SET_COOKIE), - }; - - for c in cookies { - if let Some(ref mut j) = jar { - j.add_original(c.into_owned()); - } else { - let mut j = CookieJar::new(); - j.add_original(c.into_owned()); - jar = Some(j); - } - } - - jar - }; - ResponseBuilder { head: Some(msg), err: None, - #[cfg(feature = "cookies")] - cookies: jar, } } } @@ -893,8 +711,6 @@ mod tests { use super::*; use crate::body::Body; use crate::http::header::{HeaderValue, CONTENT_TYPE, COOKIE}; - #[cfg(feature = "cookies")] - use crate::{http::header::SET_COOKIE, HttpMessage}; #[test] fn test_debug() { @@ -906,68 +722,6 @@ mod tests { assert!(dbg.contains("Response")); } - #[cfg(feature = "cookies")] - #[test] - fn test_response_cookies() { - let req = crate::test::TestRequest::default() - .append_header((COOKIE, "cookie1=value1")) - .append_header((COOKIE, "cookie2=value2")) - .finish(); - let cookies = req.cookies().unwrap(); - - let resp = Response::Ok() - .cookie( - crate::http::Cookie::build("name", "value") - .domain("www.rust-lang.org") - .path("/test") - .http_only(true) - .max_age(time::Duration::days(1)) - .finish(), - ) - .del_cookie(&cookies[0]) - .finish(); - - let mut val = resp - .headers() - .get_all(SET_COOKIE) - .map(|v| v.to_str().unwrap().to_owned()) - .collect::>(); - val.sort(); - - // the .del_cookie call - assert!(val[0].starts_with("cookie1=; Max-Age=0;")); - - // the .cookie call - assert_eq!( - val[1], - "name=value; HttpOnly; Path=/test; Domain=www.rust-lang.org; Max-Age=86400" - ); - } - - #[cfg(feature = "cookies")] - #[test] - fn test_update_response_cookies() { - let mut r = Response::Ok() - .cookie(crate::http::Cookie::new("original", "val100")) - .finish(); - - r.add_cookie(&crate::http::Cookie::new("cookie2", "val200")) - .unwrap(); - r.add_cookie(&crate::http::Cookie::new("cookie2", "val250")) - .unwrap(); - r.add_cookie(&crate::http::Cookie::new("cookie3", "val300")) - .unwrap(); - - assert_eq!(r.cookies().count(), 4); - r.del_cookie("cookie2"); - - let mut iter = r.cookies(); - let v = iter.next().unwrap(); - assert_eq!((v.name(), v.value()), ("original", "val100")); - let v = iter.next().unwrap(); - assert_eq!((v.name(), v.value()), ("cookie3", "val300")); - } - #[test] fn test_basic_builder() { let resp = Response::Ok().insert_header(("X-TEST", "value")).finish(); @@ -1026,6 +780,7 @@ mod tests { #[test] fn test_serde_json_in_body() { use serde_json::json; + let resp = Response::build(StatusCode::OK).body(json!({"test-key":"test-value"})); assert_eq!(resp.body().get_ref(), br#"{"test-key":"test-value"}"#); @@ -1101,21 +856,22 @@ mod tests { assert_eq!(resp.body().get_ref(), b"test"); } - #[cfg(feature = "cookies")] #[test] fn test_into_builder() { let mut resp: Response = "test".into(); assert_eq!(resp.status(), StatusCode::OK); - resp.add_cookie(&crate::http::Cookie::new("cookie1", "val100")) - .unwrap(); + resp.headers_mut().insert( + HeaderName::from_static("cookie"), + HeaderValue::from_static("cookie1=val100"), + ); let mut builder: ResponseBuilder = resp.into(); let resp = builder.status(StatusCode::BAD_REQUEST).finish(); assert_eq!(resp.status(), StatusCode::BAD_REQUEST); - let cookie = resp.cookies().next().unwrap(); - assert_eq!((cookie.name(), cookie.value()), ("cookie1", "val100")); + let cookie = resp.headers().get_all("Cookie").next().unwrap(); + assert_eq!(cookie.to_str().unwrap(), "cookie1=val100"); } #[test] diff --git a/actix-http/src/test.rs b/actix-http/src/test.rs index ad3dc74b2..ec781743d 100644 --- a/actix-http/src/test.rs +++ b/actix-http/src/test.rs @@ -13,11 +13,6 @@ use actix_codec::{AsyncRead, AsyncWrite, ReadBuf}; use bytes::{Bytes, BytesMut}; use http::{Method, Uri, Version}; -#[cfg(feature = "cookies")] -use crate::{ - cookie::{Cookie, CookieJar}, - header::{self, HeaderValue}, -}; use crate::{ header::{HeaderMap, IntoHeaderPair}, payload::Payload, @@ -54,8 +49,6 @@ struct Inner { method: Method, uri: Uri, headers: HeaderMap, - #[cfg(feature = "cookies")] - cookies: CookieJar, payload: Option, } @@ -66,8 +59,6 @@ impl Default for TestRequest { uri: Uri::from_str("/").unwrap(), version: Version::HTTP_11, headers: HeaderMap::new(), - #[cfg(feature = "cookies")] - cookies: CookieJar::new(), payload: None, })) } @@ -134,13 +125,6 @@ impl TestRequest { self } - /// Set cookie for this request. - #[cfg(feature = "cookies")] - pub fn cookie<'a>(&mut self, cookie: Cookie<'a>) -> &mut Self { - parts(&mut self.0).cookies.add(cookie.into_owned()); - self - } - /// Set request payload. pub fn set_payload>(&mut self, data: B) -> &mut Self { let mut payload = crate::h1::Payload::empty(); @@ -169,22 +153,6 @@ impl TestRequest { head.version = inner.version; head.headers = inner.headers; - #[cfg(feature = "cookies")] - { - let cookie: String = inner - .cookies - .delta() - // ensure only name=value is written to cookie header - .map(|c| Cookie::new(c.name(), c.value()).encoded().to_string()) - .collect::>() - .join("; "); - - if !cookie.is_empty() { - head.headers - .insert(header::COOKIE, HeaderValue::from_str(&cookie).unwrap()); - } - } - req } } diff --git a/actix-http/src/ws/mod.rs b/actix-http/src/ws/mod.rs index cec73db96..1a82ad839 100644 --- a/actix-http/src/ws/mod.rs +++ b/actix-http/src/ws/mod.rs @@ -9,10 +9,8 @@ use derive_more::{Display, Error, From}; use http::{header, Method, StatusCode}; use crate::{ - error::ResponseError, - header::HeaderValue, - message::RequestHead, - response::{Response, ResponseBuilder}, + error::ResponseError, header::HeaderValue, message::RequestHead, response::Response, + ResponseBuilder, }; mod codec; diff --git a/actix-multipart/src/error.rs b/actix-multipart/src/error.rs index cdbb5d395..5f91c60df 100644 --- a/actix-multipart/src/error.rs +++ b/actix-multipart/src/error.rs @@ -45,11 +45,10 @@ impl ResponseError for MultipartError { #[cfg(test)] mod tests { use super::*; - use actix_web::HttpResponse; #[test] fn test_multipart_error() { - let resp: HttpResponse = MultipartError::Boundary.error_response(); + let resp = MultipartError::Boundary.error_response(); assert_eq!(resp.status(), StatusCode::BAD_REQUEST); } } diff --git a/actix-test/Cargo.toml b/actix-test/Cargo.toml index aa82fd046..db7a50c5e 100644 --- a/actix-test/Cargo.toml +++ b/actix-test/Cargo.toml @@ -20,7 +20,7 @@ openssl = ["tls-openssl", "actix-http/openssl"] [dependencies] actix-codec = "0.4.0-beta.1" -actix-http = { version = "3.0.0-beta.5", features = ["cookies"] } +actix-http = "3.0.0-beta.5" actix-http-test = { version = "3.0.0-beta.4", features = [] } actix-service = "2.0.0-beta.4" actix-utils = "3.0.0-beta.2" diff --git a/actix-test/src/lib.rs b/actix-test/src/lib.rs index bd86c27ad..8fab33289 100644 --- a/actix-test/src/lib.rs +++ b/actix-test/src/lib.rs @@ -37,12 +37,12 @@ use actix_codec::{AsyncRead, AsyncWrite, Framed}; pub use actix_http::test::TestBuffer; use actix_http::{ http::{HeaderMap, Method}, - ws, HttpService, Request, + ws, HttpService, Request, Response, }; use actix_service::{map_config, IntoServiceFactory, ServiceFactory}; use actix_web::{ dev::{AppConfig, MessageBody, Server, Service}, - rt, web, Error, HttpResponse, + rt, web, Error, }; use awc::{error::PayloadError, Client, ClientRequest, ClientResponse, Connector}; use futures_core::Stream; @@ -83,7 +83,7 @@ where S: ServiceFactory + 'static, S::Error: Into + 'static, S::InitError: fmt::Debug, - S::Response: Into> + 'static, + S::Response: Into> + 'static, >::Future: 'static, B: MessageBody + 'static, { @@ -122,7 +122,7 @@ where S: ServiceFactory + 'static, S::Error: Into + 'static, S::InitError: fmt::Debug, - S::Response: Into> + 'static, + S::Response: Into> + 'static, >::Future: 'static, B: MessageBody + 'static, { diff --git a/actix-web-actors/src/ws.rs b/actix-web-actors/src/ws.rs index 77d7041f0..8c575206d 100644 --- a/actix-web-actors/src/ws.rs +++ b/actix-web-actors/src/ws.rs @@ -22,9 +22,9 @@ use actix_http::{ http::HeaderValue, ws::{hash_key, Codec}, }; -use actix_web::dev::HttpResponseBuilder; use actix_web::error::{Error, PayloadError}; use actix_web::http::{header, Method, StatusCode}; +use actix_web::HttpResponseBuilder; use actix_web::{HttpRequest, HttpResponse}; use bytes::{Bytes, BytesMut}; use bytestring::ByteString; diff --git a/actix-web-codegen/tests/test_macro.rs b/actix-web-codegen/tests/test_macro.rs index 7b95edba2..b983e6b1d 100644 --- a/actix-web-codegen/tests/test_macro.rs +++ b/actix-web-codegen/tests/test_macro.rs @@ -1,9 +1,10 @@ use std::future::Future; use std::task::{Context, Poll}; -use actix_utils::future; +use actix_utils::future::{ok, Ready}; use actix_web::dev::{Service, ServiceRequest, ServiceResponse, Transform}; use actix_web::http::header::{HeaderName, HeaderValue}; +use actix_web::http::StatusCode; use actix_web::{http, web::Path, App, Error, HttpResponse, Responder}; use actix_web_codegen::{connect, delete, get, head, options, patch, post, put, route, trace}; use futures_core::future::LocalBoxFuture; @@ -56,12 +57,12 @@ async fn trace_test() -> impl Responder { #[get("/test")] fn auto_async() -> impl Future> { - future::ok(HttpResponse::Ok().finish()) + ok(HttpResponse::Ok().finish()) } #[get("/test")] fn auto_sync() -> impl Future> { - future::ok(HttpResponse::Ok().finish()) + ok(HttpResponse::Ok().finish()) } #[put("/test/{param}")] @@ -103,10 +104,10 @@ where type Error = Error; type Transform = ChangeStatusCodeMiddleware; type InitError = (); - type Future = future::Ready>; + type Future = Ready>; fn new_transform(&self, service: S) -> Self::Future { - future::ok(ChangeStatusCodeMiddleware { service }) + ok(ChangeStatusCodeMiddleware { service }) } } @@ -144,6 +145,7 @@ where #[get("/test/wrap", wrap = "ChangeStatusCode")] async fn get_wrap(_: Path) -> impl Responder { + // panic!("actually never gets called because path failed to extract"); HttpResponse::Ok() } @@ -257,6 +259,10 @@ async fn test_wrap() { let srv = actix_test::start(|| App::new().service(get_wrap)); let request = srv.request(http::Method::GET, srv.url("/test/wrap")); - let response = request.send().await.unwrap(); + let mut response = request.send().await.unwrap(); + assert_eq!(response.status(), StatusCode::NOT_FOUND); assert!(response.headers().contains_key("custom-header")); + let body = response.body().await.unwrap(); + let body = String::from_utf8(body.to_vec()).unwrap(); + assert!(body.contains("wrong number of parameters")); } diff --git a/awc/Cargo.toml b/awc/Cargo.toml index aebf73ef5..27d8bdfbc 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -41,7 +41,7 @@ rustls = ["tls-rustls", "actix-http/rustls"] compress = ["actix-http/compress"] # cookie parsing and cookie jar -cookies = ["actix-http/cookies"] +cookies = ["cookie"] # trust-dns as dns resolver trust-dns = ["actix-http/trust-dns"] @@ -54,6 +54,7 @@ actix-rt = { version = "2.1", default-features = false } base64 = "0.13" bytes = "1" +cookie = { version = "0.15", features = ["percent-encode"], optional = true } derive_more = "0.99.5" futures-core = { version = "0.3.7", default-features = false } itoa = "0.4" diff --git a/awc/src/lib.rs b/awc/src/lib.rs index f1aecbd37..562d6ee7f 100644 --- a/awc/src/lib.rs +++ b/awc/src/lib.rs @@ -93,12 +93,11 @@ #![doc(html_logo_url = "https://actix.rs/img/logo.png")] #![doc(html_favicon_url = "https://actix.rs/favicon.ico")] -use std::convert::TryFrom; -use std::rc::Rc; -use std::time::Duration; +use std::{convert::TryFrom, rc::Rc, time::Duration}; #[cfg(feature = "cookies")] -pub use actix_http::cookie; +pub use cookie; + pub use actix_http::{client::Connector, http}; use actix_http::{ diff --git a/awc/src/request.rs b/awc/src/request.rs index 6ecb64f81..f5cb08f15 100644 --- a/awc/src/request.rs +++ b/awc/src/request.rs @@ -8,14 +8,14 @@ use futures_core::Stream; use serde::Serialize; use actix_http::body::Body; -#[cfg(feature = "cookies")] -use actix_http::cookie::{Cookie, CookieJar}; use actix_http::http::header::{self, IntoHeaderPair}; use actix_http::http::{ uri, ConnectionType, Error as HttpError, HeaderMap, HeaderValue, Method, Uri, Version, }; use actix_http::{Error, RequestHead}; +#[cfg(feature = "cookies")] +use crate::cookie::{Cookie, CookieJar}; use crate::error::{FreezeRequestError, InvalidUrl}; use crate::frozen::FrozenClientRequest; use crate::sender::{PrepForSendingError, RequestSender, SendClientRequest}; @@ -271,7 +271,7 @@ impl ClientRequest { /// async fn main() { /// let resp = awc::Client::new().get("https://www.rust-lang.org") /// .cookie( - /// awc::http::Cookie::build("name", "value") + /// awc::cookie::Cookie::build("name", "value") /// .domain("www.rust-lang.org") /// .path("/") /// .secure(true) @@ -494,7 +494,7 @@ impl ClientRequest { let cookie: String = jar .delta() // ensure only name=value is written to cookie header - .map(|c| Cookie::new(c.name(), c.value()).encoded().to_string()) + .map(|c| c.stripped().encoded().to_string()) .collect::>() .join("; "); diff --git a/awc/src/response.rs b/awc/src/response.rs index 27ba83af7..a966edd08 100644 --- a/awc/src/response.rs +++ b/awc/src/response.rs @@ -20,8 +20,7 @@ use futures_core::{ready, Stream}; use serde::de::DeserializeOwned; #[cfg(feature = "cookies")] -use actix_http::{cookie::Cookie, error::CookieParseError}; - +use crate::cookie::{Cookie, ParseError as CookieParseError}; use crate::error::JsonPayloadError; /// Client Response @@ -80,24 +79,6 @@ impl HttpMessage for ClientResponse { fn extensions_mut(&self) -> RefMut<'_, Extensions> { self.head.extensions_mut() } - - /// Load request cookies. - #[cfg(feature = "cookies")] - fn cookies(&self) -> Result>>, CookieParseError> { - struct Cookies(Vec>); - - if self.extensions().get::().is_none() { - let mut cookies = Vec::new(); - for hdr in self.headers().get_all(&header::SET_COOKIE) { - let s = std::str::from_utf8(hdr.as_bytes()).map_err(CookieParseError::from)?; - cookies.push(Cookie::parse_encoded(s)?.into_owned()); - } - self.extensions_mut().insert(Cookies(cookies)); - } - Ok(Ref::map(self.extensions(), |ext| { - &ext.get::().unwrap().0 - })) - } } impl ClientResponse { @@ -180,6 +161,37 @@ impl ClientResponse { self.timeout = ResponseTimeout::Disabled(timeout); self } + + /// Load request cookies. + #[cfg(feature = "cookies")] + pub fn cookies(&self) -> Result>>, CookieParseError> { + struct Cookies(Vec>); + + if self.extensions().get::().is_none() { + let mut cookies = Vec::new(); + for hdr in self.headers().get_all(&header::SET_COOKIE) { + let s = std::str::from_utf8(hdr.as_bytes()).map_err(CookieParseError::from)?; + cookies.push(Cookie::parse_encoded(s)?.into_owned()); + } + self.extensions_mut().insert(Cookies(cookies)); + } + Ok(Ref::map(self.extensions(), |ext| { + &ext.get::().unwrap().0 + })) + } + + /// Return request cookie. + #[cfg(feature = "cookies")] + pub fn cookie(&self, name: &str) -> Option> { + if let Ok(cookies) = self.cookies() { + for cookie in cookies.iter() { + if cookie.name() == name { + return Some(cookie.to_owned()); + } + } + } + None + } } impl ClientResponse diff --git a/awc/src/test.rs b/awc/src/test.rs index 8e95396b3..1abe78811 100644 --- a/awc/src/test.rs +++ b/awc/src/test.rs @@ -1,14 +1,11 @@ //! Test helpers for actix http client to use during testing. use actix_http::http::header::IntoHeaderPair; use actix_http::http::{StatusCode, Version}; -#[cfg(feature = "cookies")] -use actix_http::{ - cookie::{Cookie, CookieJar}, - http::header::{self, HeaderValue}, -}; use actix_http::{h1, Payload, ResponseHead}; use bytes::Bytes; +#[cfg(feature = "cookies")] +use crate::cookie::{Cookie, CookieJar}; use crate::ClientResponse; /// Test `ClientResponse` builder @@ -92,6 +89,8 @@ impl TestResponse { #[cfg(feature = "cookies")] for cookie in self.cookies.delta() { + use actix_http::http::header::{self, HeaderValue}; + head.headers.insert( header::SET_COOKIE, HeaderValue::from_str(&cookie.encoded().to_string()).unwrap(), diff --git a/awc/src/ws.rs b/awc/src/ws.rs index 8458d3e31..34b71f052 100644 --- a/awc/src/ws.rs +++ b/awc/src/ws.rs @@ -31,8 +31,6 @@ use std::net::SocketAddr; use std::{fmt, str}; use actix_codec::Framed; -#[cfg(feature = "cookies")] -use actix_http::cookie::{Cookie, CookieJar}; use actix_http::{ws, Payload, RequestHead}; use actix_rt::time::timeout; use actix_service::Service; @@ -40,6 +38,8 @@ use actix_service::Service; pub use actix_http::ws::{CloseCode, CloseReason, Codec, Frame, Message}; use crate::connect::{BoxedSocket, ConnectRequest}; +#[cfg(feature = "cookies")] +use crate::cookie::{Cookie, CookieJar}; use crate::error::{InvalidUrl, SendRequestError, WsClientError}; use crate::http::header::{self, HeaderName, HeaderValue, IntoHeaderValue, AUTHORIZATION}; use crate::http::{ConnectionType, Error as HttpError, Method, StatusCode, Uri, Version}; @@ -280,7 +280,7 @@ impl WebsocketsRequest { let cookie: String = jar .delta() // ensure only name=value is written to cookie header - .map(|c| Cookie::new(c.name(), c.value()).encoded().to_string()) + .map(|c| c.stripped().encoded().to_string()) .collect::>() .join("; "); diff --git a/awc/tests/test_client.rs b/awc/tests/test_client.rs index a393a6415..f1d29f0bc 100644 --- a/awc/tests/test_client.rs +++ b/awc/tests/test_client.rs @@ -8,6 +8,7 @@ use std::time::Duration; use actix_utils::future::ok; use brotli2::write::BrotliEncoder; use bytes::Bytes; +use cookie::Cookie; use flate2::read::GzDecoder; use flate2::write::GzEncoder; use flate2::Compression; @@ -22,9 +23,9 @@ use actix_http_test::test_server; use actix_service::{map_config, pipeline_factory}; use actix_web::{ dev::{AppConfig, BodyEncoding}, - http::{header, Cookie}, + http::header, middleware::Compress, - web, App, Error, HttpMessage, HttpRequest, HttpResponse, + web, App, Error, HttpRequest, HttpResponse, }; use awc::error::{JsonPayloadError, PayloadError, SendRequestError}; diff --git a/src/error.rs b/src/error.rs index 0865257d3..25cdc9feb 100644 --- a/src/error.rs +++ b/src/error.rs @@ -5,7 +5,7 @@ use derive_more::{Display, Error, From}; use serde_json::error::Error as JsonError; use url::ParseError as UrlParseError; -use crate::{http::StatusCode, HttpResponse}; +use crate::http::StatusCode; /// Errors which can occur when attempting to generate resource uri. #[derive(Debug, PartialEq, Display, From)] @@ -90,12 +90,11 @@ pub enum JsonPayloadError { impl std::error::Error for JsonPayloadError {} -/// Return `BadRequest` for `JsonPayloadError` impl ResponseError for JsonPayloadError { - fn error_response(&self) -> HttpResponse { + fn status_code(&self) -> StatusCode { match *self { - JsonPayloadError::Overflow => HttpResponse::new(StatusCode::PAYLOAD_TOO_LARGE), - _ => HttpResponse::new(StatusCode::BAD_REQUEST), + JsonPayloadError::Overflow => StatusCode::PAYLOAD_TOO_LARGE, + _ => StatusCode::BAD_REQUEST, } } } @@ -168,26 +167,25 @@ mod tests { #[test] fn test_urlencoded_error() { - let resp: HttpResponse = - UrlencodedError::Overflow { size: 0, limit: 0 }.error_response(); + let resp = UrlencodedError::Overflow { size: 0, limit: 0 }.error_response(); assert_eq!(resp.status(), StatusCode::PAYLOAD_TOO_LARGE); - let resp: HttpResponse = UrlencodedError::UnknownLength.error_response(); + let resp = UrlencodedError::UnknownLength.error_response(); assert_eq!(resp.status(), StatusCode::LENGTH_REQUIRED); - let resp: HttpResponse = UrlencodedError::ContentType.error_response(); + let resp = UrlencodedError::ContentType.error_response(); assert_eq!(resp.status(), StatusCode::BAD_REQUEST); } #[test] fn test_json_payload_error() { - let resp: HttpResponse = JsonPayloadError::Overflow.error_response(); + let resp = JsonPayloadError::Overflow.error_response(); assert_eq!(resp.status(), StatusCode::PAYLOAD_TOO_LARGE); - let resp: HttpResponse = JsonPayloadError::ContentType.error_response(); + let resp = JsonPayloadError::ContentType.error_response(); assert_eq!(resp.status(), StatusCode::BAD_REQUEST); } #[test] fn test_query_payload_error() { - let resp: HttpResponse = QueryPayloadError::Deserialize( + let resp = QueryPayloadError::Deserialize( serde_urlencoded::from_str::("bad query").unwrap_err(), ) .error_response(); @@ -196,9 +194,9 @@ mod tests { #[test] fn test_readlines_error() { - let resp: HttpResponse = ReadlinesError::LimitOverflow.error_response(); + let resp = ReadlinesError::LimitOverflow.error_response(); assert_eq!(resp.status(), StatusCode::PAYLOAD_TOO_LARGE); - let resp: HttpResponse = ReadlinesError::EncodingError.error_response(); + let resp = ReadlinesError::EncodingError.error_response(); assert_eq!(resp.status(), StatusCode::BAD_REQUEST); } } diff --git a/src/handler.rs b/src/handler.rs index e005a96a6..822dcafdd 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -3,20 +3,23 @@ use std::marker::PhantomData; use std::pin::Pin; use std::task::{Context, Poll}; -use actix_http::{Error, Response}; +use actix_http::Error; use actix_service::{Service, ServiceFactory}; use actix_utils::future::{ready, Ready}; use futures_core::ready; use pin_project::pin_project; -use crate::extract::FromRequest; -use crate::request::HttpRequest; -use crate::responder::Responder; -use crate::service::{ServiceRequest, ServiceResponse}; +use crate::{ + extract::FromRequest, + request::HttpRequest, + responder::Responder, + response::HttpResponse, + service::{ServiceRequest, ServiceResponse}, +}; -/// A request handler is an async function that accepts zero or more parameters that can be -/// extracted from a request (ie, [`impl FromRequest`](crate::FromRequest)) and returns a type that can be converted into -/// an [`HttpResponse`](crate::HttpResponse) (ie, [`impl Responder`](crate::Responder)). +/// A request handler is an async function that accepts zero or more parameters that can be +/// extracted from a request (i.e., [`impl FromRequest`](crate::FromRequest)) and returns a type +/// that can be converted into an [`HttpResponse`] (that is, it impls the [`Responder`] trait). /// /// If you got the error `the trait Handler<_, _, _> is not implemented`, then your function is not /// a valid handler. See [Request Handlers](https://actix.rs/docs/handlers/) for more information. @@ -102,9 +105,7 @@ where type Error = Error; type Future = HandlerServiceFuture; - fn poll_ready(&self, _: &mut Context<'_>) -> Poll> { - Poll::Ready(Ok(())) - } + actix_service::always_ready!(); fn call(&self, req: ServiceRequest) -> Self::Future { let (req, mut payload) = req.into_parts(); @@ -147,9 +148,9 @@ where let state = HandlerServiceFuture::Handle(fut, req.take()); self.as_mut().set(state); } - Err(e) => { - let res: Response = e.into().into(); + Err(err) => { let req = req.take().unwrap(); + let res = HttpResponse::from_error(err.into()); return Poll::Ready(Ok(ServiceResponse::new(req, res))); } }; diff --git a/src/lib.rs b/src/lib.rs index 1d5d5b83d..54db969df 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -86,6 +86,7 @@ mod request; mod request_data; mod resource; mod responder; +mod response; mod rmap; mod route; mod scope; @@ -95,19 +96,20 @@ pub mod test; pub(crate) mod types; pub mod web; -#[cfg(feature = "cookies")] -pub use actix_http::cookie; -pub use actix_http::Response as HttpResponse; +pub use actix_http::Response as BaseHttpResponse; pub use actix_http::{body, Error, HttpMessage, ResponseError, Result}; #[doc(inline)] pub use actix_rt as rt; pub use actix_web_codegen::*; +#[cfg(feature = "cookies")] +pub use cookie; pub use crate::app::App; pub use crate::extract::FromRequest; pub use crate::request::HttpRequest; pub use crate::resource::Resource; pub use crate::responder::Responder; +pub use crate::response::{HttpResponse, HttpResponseBuilder}; pub use crate::route::Route; pub use crate::scope::Scope; pub use crate::server::HttpServer; @@ -139,7 +141,7 @@ pub mod dev { pub use actix_http::body::{Body, BodySize, MessageBody, ResponseBody, SizedStream}; #[cfg(feature = "compress")] pub use actix_http::encoding::Decoder as Decompress; - pub use actix_http::ResponseBuilder as HttpResponseBuilder; + pub use actix_http::ResponseBuilder as BaseHttpResponseBuilder; pub use actix_http::{Extensions, Payload, PayloadStream, RequestHead, ResponseHead}; pub use actix_router::{Path, ResourceDef, ResourcePath, Url}; pub use actix_server::Server; @@ -189,4 +191,26 @@ pub mod dev { self } } + + impl BodyEncoding for crate::HttpResponseBuilder { + fn get_encoding(&self) -> Option { + self.extensions().get::().map(|enc| enc.0) + } + + fn encoding(&mut self, encoding: ContentEncoding) -> &mut Self { + self.extensions_mut().insert(Enc(encoding)); + self + } + } + + impl BodyEncoding for crate::HttpResponse { + fn get_encoding(&self) -> Option { + self.extensions().get::().map(|enc| enc.0) + } + + fn encoding(&mut self, encoding: ContentEncoding) -> &mut Self { + self.extensions_mut().insert(Enc(encoding)); + self + } + } } diff --git a/src/request.rs b/src/request.rs index f3cbc07b8..e3da991de 100644 --- a/src/request.rs +++ b/src/request.rs @@ -1,19 +1,27 @@ -use std::cell::{Ref, RefCell, RefMut}; -use std::rc::Rc; -use std::{fmt, net}; +use std::{ + cell::{Ref, RefCell, RefMut}, + fmt, net, + rc::Rc, + str, +}; -use actix_http::http::{HeaderMap, Method, Uri, Version}; -use actix_http::{Error, Extensions, HttpMessage, Message, Payload, RequestHead}; +use actix_http::{ + http::{HeaderMap, Method, Uri, Version}, + Error, Extensions, HttpMessage, Message, Payload, RequestHead, +}; use actix_router::{Path, Url}; use actix_utils::future::{ok, Ready}; +#[cfg(feature = "cookies")] +use cookie::{Cookie, ParseError as CookieParseError}; use smallvec::SmallVec; -use crate::app_service::AppInitServiceState; -use crate::config::AppConfig; -use crate::error::UrlGenerationError; -use crate::extract::FromRequest; -use crate::info::ConnectionInfo; -use crate::rmap::ResourceMap; +use crate::{ + app_service::AppInitServiceState, config::AppConfig, error::UrlGenerationError, + extract::FromRequest, info::ConnectionInfo, rmap::ResourceMap, +}; + +#[cfg(feature = "cookies")] +struct Cookies(Vec>); #[derive(Clone)] /// An HTTP Request @@ -260,6 +268,42 @@ impl HttpRequest { fn app_state(&self) -> &AppInitServiceState { &*self.inner.app_state } + + /// Load request cookies. + #[cfg(feature = "cookies")] + pub fn cookies(&self) -> Result>>, CookieParseError> { + use actix_http::http::header::COOKIE; + + if self.extensions().get::().is_none() { + let mut cookies = Vec::new(); + for hdr in self.headers().get_all(COOKIE) { + let s = str::from_utf8(hdr.as_bytes()).map_err(CookieParseError::from)?; + for cookie_str in s.split(';').map(|s| s.trim()) { + if !cookie_str.is_empty() { + cookies.push(Cookie::parse_encoded(cookie_str)?.into_owned()); + } + } + } + self.extensions_mut().insert(Cookies(cookies)); + } + + Ok(Ref::map(self.extensions(), |ext| { + &ext.get::().unwrap().0 + })) + } + + /// Return request cookie. + #[cfg(feature = "cookies")] + pub fn cookie(&self, name: &str) -> Option> { + if let Ok(cookies) = self.cookies() { + for cookie in cookies.iter() { + if cookie.name() == name { + return Some(cookie.to_owned()); + } + } + } + None + } } impl HttpMessage for HttpRequest { diff --git a/src/responder.rs b/src/responder.rs index b75c95083..66c93d257 100644 --- a/src/responder.rs +++ b/src/responder.rs @@ -3,11 +3,10 @@ use std::fmt; use actix_http::{ error::InternalError, http::{header::IntoHeaderPair, Error as HttpError, HeaderMap, StatusCode}, - ResponseBuilder, }; use bytes::{Bytes, BytesMut}; -use crate::{Error, HttpRequest, HttpResponse}; +use crate::{Error, HttpRequest, HttpResponse, HttpResponseBuilder}; /// Trait implemented by types that can be converted to an HTTP response. /// @@ -66,11 +65,32 @@ impl Responder for HttpResponse { } } +impl Responder for actix_http::Response { + #[inline] + fn respond_to(self, _: &HttpRequest) -> HttpResponse { + HttpResponse::from(self) + } +} + +impl Responder for HttpResponseBuilder { + #[inline] + fn respond_to(mut self, _: &HttpRequest) -> HttpResponse { + self.finish() + } +} + +impl Responder for actix_http::ResponseBuilder { + #[inline] + fn respond_to(mut self, _: &HttpRequest) -> HttpResponse { + HttpResponse::from(self.finish()) + } +} + impl Responder for Option { fn respond_to(self, req: &HttpRequest) -> HttpResponse { match self { - Some(t) => t.respond_to(req), - None => HttpResponse::build(StatusCode::NOT_FOUND).finish(), + Some(val) => val.respond_to(req), + None => HttpResponse::new(StatusCode::NOT_FOUND), } } } @@ -88,13 +108,6 @@ where } } -impl Responder for ResponseBuilder { - #[inline] - fn respond_to(mut self, _: &HttpRequest) -> HttpResponse { - self.finish() - } -} - impl Responder for (T, StatusCode) { fn respond_to(self, req: &HttpRequest) -> HttpResponse { let mut res = self.0.respond_to(req); diff --git a/src/response.rs b/src/response.rs new file mode 100644 index 000000000..1d017f080 --- /dev/null +++ b/src/response.rs @@ -0,0 +1,968 @@ +use std::{ + cell::{Ref, RefMut}, + convert::TryInto, + fmt, + future::Future, + pin::Pin, + task::{Context, Poll}, +}; + +use actix_http::{ + body::{Body, BodyStream, MessageBody, ResponseBody}, + http::{ + header::{self, HeaderMap, HeaderName, IntoHeaderPair, IntoHeaderValue}, + ConnectionType, Error as HttpError, StatusCode, + }, + Extensions, Response, ResponseHead, +}; +use bytes::Bytes; +use futures_core::Stream; +use serde::Serialize; + +#[cfg(feature = "cookies")] +use actix_http::http::header::HeaderValue; +#[cfg(feature = "cookies")] +use cookie::{Cookie, CookieJar}; + +use crate::error::Error; + +/// An HTTP Response +pub struct HttpResponse { + res: Response, + error: Option, +} + +impl HttpResponse { + /// Create HTTP response builder with specific status. + #[inline] + pub fn build(status: StatusCode) -> HttpResponseBuilder { + HttpResponseBuilder::new(status) + } + + /// Create HTTP response builder + #[inline] + pub fn build_from>(source: T) -> HttpResponseBuilder { + source.into() + } + + /// Create a response. + #[inline] + pub fn new(status: StatusCode) -> Self { + Self { + res: Response::new(status), + error: None, + } + } + + /// Create an error response. + #[inline] + pub fn from_error(error: Error) -> Self { + let res = error.as_response_error().error_response(); + + Self { + res, + error: Some(error), + } + } + + /// Convert response to response with body + pub fn into_body(self) -> HttpResponse { + HttpResponse { + res: self.res.into_body(), + error: self.error, + } + } +} + +impl HttpResponse { + /// Constructs a response with body + #[inline] + pub fn with_body(status: StatusCode, body: B) -> Self { + Self { + res: Response::with_body(status, body), + error: None, + } + } + + /// Returns a reference to response head. + #[inline] + pub fn head(&self) -> &ResponseHead { + self.res.head() + } + + /// Returns a mutable reference to response head. + #[inline] + pub fn head_mut(&mut self) -> &mut ResponseHead { + self.res.head_mut() + } + + /// The source `error` for this response + #[inline] + pub fn error(&self) -> Option<&Error> { + self.error.as_ref() + } + + /// Get the response status code + #[inline] + pub fn status(&self) -> StatusCode { + self.res.status() + } + + /// Set the `StatusCode` for this response + #[inline] + pub fn status_mut(&mut self) -> &mut StatusCode { + self.res.status_mut() + } + + /// Get the headers from the response + #[inline] + pub fn headers(&self) -> &HeaderMap { + self.res.headers() + } + + /// Get a mutable reference to the headers + #[inline] + pub fn headers_mut(&mut self) -> &mut HeaderMap { + self.res.headers_mut() + } + + /// Get an iterator for the cookies set by this response. + #[cfg(feature = "cookies")] + pub fn cookies(&self) -> CookieIter<'_> { + CookieIter { + iter: self.headers().get_all(header::SET_COOKIE), + } + } + + /// Add a cookie to this response + #[cfg(feature = "cookies")] + pub fn add_cookie(&mut self, cookie: &Cookie<'_>) -> Result<(), HttpError> { + HeaderValue::from_str(&cookie.to_string()) + .map(|c| { + self.headers_mut().append(header::SET_COOKIE, c); + }) + .map_err(|e| e.into()) + } + + /// Remove all cookies with the given name from this response. Returns + /// the number of cookies removed. + #[cfg(feature = "cookies")] + pub fn del_cookie(&mut self, name: &str) -> usize { + let headers = self.headers_mut(); + + let vals: Vec = headers + .get_all(header::SET_COOKIE) + .map(|v| v.to_owned()) + .collect(); + + headers.remove(header::SET_COOKIE); + + let mut count: usize = 0; + for v in vals { + if let Ok(s) = v.to_str() { + if let Ok(c) = Cookie::parse_encoded(s) { + if c.name() == name { + count += 1; + continue; + } + } + } + + // put set-cookie header head back if it does not validate + headers.append(header::SET_COOKIE, v); + } + + count + } + + /// Connection upgrade status + #[inline] + pub fn upgrade(&self) -> bool { + self.res.upgrade() + } + + /// Keep-alive status for this connection + pub fn keep_alive(&self) -> bool { + self.res.keep_alive() + } + + /// Responses extensions + #[inline] + pub fn extensions(&self) -> Ref<'_, Extensions> { + self.res.extensions() + } + + /// Mutable reference to a the response's extensions + #[inline] + pub fn extensions_mut(&mut self) -> RefMut<'_, Extensions> { + self.res.extensions_mut() + } + + /// Get body of this response + #[inline] + pub fn body(&self) -> &ResponseBody { + self.res.body() + } + + /// Set a body + pub fn set_body(self, body: B2) -> HttpResponse { + HttpResponse { + res: self.res.set_body(body), + error: None, + // error: self.error, ?? + } + } + + /// Split response and body + pub fn into_parts(self) -> (HttpResponse<()>, ResponseBody) { + let (head, body) = self.res.into_parts(); + + ( + HttpResponse { + res: head, + error: None, + }, + body, + ) + } + + /// Drop request's body + pub fn drop_body(self) -> HttpResponse<()> { + HttpResponse { + res: self.res.drop_body(), + error: None, + } + } + + /// Set a body and return previous body value + pub fn map_body(self, f: F) -> HttpResponse + where + F: FnOnce(&mut ResponseHead, ResponseBody) -> ResponseBody, + { + HttpResponse { + res: self.res.map_body(f), + error: self.error, + } + } + + /// Extract response body + pub fn take_body(&mut self) -> ResponseBody { + self.res.take_body() + } +} + +impl fmt::Debug for HttpResponse { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("HttpResponse") + .field("error", &self.error) + .field("res", &self.res) + .finish() + } +} + +impl From> for HttpResponse { + fn from(res: Response) -> Self { + HttpResponse { res, error: None } + } +} + +impl From for HttpResponse { + fn from(err: Error) -> Self { + HttpResponse::from_error(err) + } +} + +impl From> for Response { + fn from(res: HttpResponse) -> Self { + // this impl will always be called as part of dispatcher + + // TODO: expose cause somewhere? + // if let Some(err) = res.error { + // eprintln!("impl From> for Response let Some(err)"); + // return Response::from_error(err).into_body(); + // } + + res.res + } +} + +impl Future for HttpResponse { + type Output = Result; + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + if let Some(err) = self.error.take() { + eprintln!("httpresponse future error"); + return Poll::Ready(Ok(Response::from_error(err).into_body())); + } + + let res = &mut self.res; + actix_rt::pin!(res); + + res.poll(cx) + } +} + +/// An HTTP response builder. +/// +/// This type can be used to construct an instance of `Response` through a builder-like pattern. +pub struct HttpResponseBuilder { + head: Option, + err: Option, + #[cfg(feature = "cookies")] + cookies: Option, +} + +impl HttpResponseBuilder { + #[inline] + /// Create response builder + pub fn new(status: StatusCode) -> Self { + Self { + head: Some(ResponseHead::new(status)), + err: None, + #[cfg(feature = "cookies")] + cookies: None, + } + } + + /// Set HTTP status code of this response. + #[inline] + pub fn status(&mut self, status: StatusCode) -> &mut Self { + if let Some(parts) = self.inner() { + parts.status = status; + } + self + } + + /// Insert a header, replacing any that were set with an equivalent field name. + /// + /// ``` + /// use actix_web::{HttpResponse, http::header}; + /// + /// HttpResponse::Ok() + /// .insert_header(header::ContentType(mime::APPLICATION_JSON)) + /// .insert_header(("X-TEST", "value")) + /// .finish(); + /// ``` + pub fn insert_header(&mut self, header: H) -> &mut Self + where + H: IntoHeaderPair, + { + if let Some(parts) = self.inner() { + match header.try_into_header_pair() { + Ok((key, value)) => { + parts.headers.insert(key, value); + } + Err(e) => self.err = Some(e.into()), + }; + } + + self + } + + /// Append a header, keeping any that were set with an equivalent field name. + /// + /// ``` + /// use actix_web::{HttpResponse, http::header}; + /// + /// HttpResponse::Ok() + /// .append_header(header::ContentType(mime::APPLICATION_JSON)) + /// .append_header(("X-TEST", "value1")) + /// .append_header(("X-TEST", "value2")) + /// .finish(); + /// ``` + pub fn append_header(&mut self, header: H) -> &mut Self + where + H: IntoHeaderPair, + { + if let Some(parts) = self.inner() { + match header.try_into_header_pair() { + Ok((key, value)) => parts.headers.append(key, value), + Err(e) => self.err = Some(e.into()), + }; + } + + self + } + + /// Replaced with [`Self::insert_header()`]. + #[deprecated = "Replaced with `insert_header((key, value))`."] + pub fn set_header(&mut self, key: K, value: V) -> &mut Self + where + K: TryInto, + K::Error: Into, + V: IntoHeaderValue, + { + if self.err.is_some() { + return self; + } + + match (key.try_into(), value.try_into_value()) { + (Ok(name), Ok(value)) => return self.insert_header((name, value)), + (Err(err), _) => self.err = Some(err.into()), + (_, Err(err)) => self.err = Some(err.into()), + } + + self + } + + /// Replaced with [`Self::append_header()`]. + #[deprecated = "Replaced with `append_header((key, value))`."] + pub fn header(&mut self, key: K, value: V) -> &mut Self + where + K: TryInto, + K::Error: Into, + V: IntoHeaderValue, + { + if self.err.is_some() { + return self; + } + + match (key.try_into(), value.try_into_value()) { + (Ok(name), Ok(value)) => return self.append_header((name, value)), + (Err(err), _) => self.err = Some(err.into()), + (_, Err(err)) => self.err = Some(err.into()), + } + + self + } + + /// Set the custom reason for the response. + #[inline] + pub fn reason(&mut self, reason: &'static str) -> &mut Self { + if let Some(parts) = self.inner() { + parts.reason = Some(reason); + } + self + } + + /// Set connection type to KeepAlive + #[inline] + pub fn keep_alive(&mut self) -> &mut Self { + if let Some(parts) = self.inner() { + parts.set_connection_type(ConnectionType::KeepAlive); + } + self + } + + /// Set connection type to Upgrade + #[inline] + pub fn upgrade(&mut self, value: V) -> &mut Self + where + V: IntoHeaderValue, + { + if let Some(parts) = self.inner() { + parts.set_connection_type(ConnectionType::Upgrade); + } + + if let Ok(value) = value.try_into_value() { + self.insert_header((header::UPGRADE, value)); + } + + self + } + + /// Force close connection, even if it is marked as keep-alive + #[inline] + pub fn force_close(&mut self) -> &mut Self { + if let Some(parts) = self.inner() { + parts.set_connection_type(ConnectionType::Close); + } + self + } + + /// Disable chunked transfer encoding for HTTP/1.1 streaming responses. + #[inline] + pub fn no_chunking(&mut self, len: u64) -> &mut Self { + let mut buf = itoa::Buffer::new(); + self.insert_header((header::CONTENT_LENGTH, buf.format(len))); + + if let Some(parts) = self.inner() { + parts.no_chunking(true); + } + self + } + + /// Set response content type. + #[inline] + pub fn content_type(&mut self, value: V) -> &mut Self + where + V: IntoHeaderValue, + { + if let Some(parts) = self.inner() { + match value.try_into_value() { + Ok(value) => { + parts.headers.insert(header::CONTENT_TYPE, value); + } + Err(e) => self.err = Some(e.into()), + }; + } + self + } + + /// Set a cookie. + /// + /// ``` + /// use actix_web::{HttpResponse, cookie::Cookie}; + /// + /// HttpResponse::Ok() + /// .cookie( + /// Cookie::build("name", "value") + /// .domain("www.rust-lang.org") + /// .path("/") + /// .secure(true) + /// .http_only(true) + /// .finish(), + /// ) + /// .finish(); + /// ``` + #[cfg(feature = "cookies")] + pub fn cookie<'c>(&mut self, cookie: Cookie<'c>) -> &mut Self { + if self.cookies.is_none() { + let mut jar = CookieJar::new(); + jar.add(cookie.into_owned()); + self.cookies = Some(jar) + } else { + self.cookies.as_mut().unwrap().add(cookie.into_owned()); + } + self + } + + /// Remove cookie. + /// + /// A `Set-Cookie` header is added that will delete a cookie with the same name from the client. + /// + /// ``` + /// use actix_web::{HttpRequest, HttpResponse, Responder}; + /// + /// async fn handler(req: HttpRequest) -> impl Responder { + /// let mut builder = HttpResponse::Ok(); + /// + /// if let Some(ref cookie) = req.cookie("name") { + /// builder.del_cookie(cookie); + /// } + /// + /// builder.finish() + /// } + /// ``` + #[cfg(feature = "cookies")] + pub fn del_cookie(&mut self, cookie: &Cookie<'_>) -> &mut Self { + if self.cookies.is_none() { + self.cookies = Some(CookieJar::new()) + } + let jar = self.cookies.as_mut().unwrap(); + let cookie = cookie.clone().into_owned(); + jar.add_original(cookie.clone()); + jar.remove(cookie); + self + } + + /// Responses extensions + #[inline] + pub fn extensions(&self) -> Ref<'_, Extensions> { + let head = self.head.as_ref().expect("cannot reuse response builder"); + head.extensions() + } + + /// Mutable reference to a the response's extensions + #[inline] + pub fn extensions_mut(&mut self) -> RefMut<'_, Extensions> { + let head = self.head.as_ref().expect("cannot reuse response builder"); + head.extensions_mut() + } + + /// Set a body and generate `Response`. + /// + /// `ResponseBuilder` can not be used after this call. + #[inline] + pub fn body>(&mut self, body: B) -> HttpResponse { + self.message_body(body.into()) + } + + /// Set a body and generate `Response`. + /// + /// `ResponseBuilder` can not be used after this call. + pub fn message_body(&mut self, body: B) -> HttpResponse { + if let Some(err) = self.err.take() { + return HttpResponse::from_error(Error::from(err)).into_body(); + } + + // allow unused mut when cookies feature is disabled + #[allow(unused_mut)] + let mut head = self.head.take().expect("cannot reuse response builder"); + + let mut res = HttpResponse::with_body(StatusCode::OK, body); + *res.head_mut() = head; + + #[cfg(feature = "cookies")] + if let Some(ref jar) = self.cookies { + for cookie in jar.delta() { + match HeaderValue::from_str(&cookie.to_string()) { + Ok(val) => res.headers_mut().append(header::SET_COOKIE, val), + Err(err) => return HttpResponse::from_error(Error::from(err)).into_body(), + }; + } + } + + res + } + + /// Set a streaming body and generate `Response`. + /// + /// `ResponseBuilder` can not be used after this call. + #[inline] + pub fn streaming(&mut self, stream: S) -> HttpResponse + where + S: Stream> + Unpin + 'static, + E: Into + 'static, + { + self.body(Body::from_message(BodyStream::new(stream))) + } + + /// Set a json body and generate `Response` + /// + /// `ResponseBuilder` can not be used after this call. + pub fn json(&mut self, value: impl Serialize) -> HttpResponse { + match serde_json::to_string(&value) { + Ok(body) => { + let contains = if let Some(parts) = self.inner() { + parts.headers.contains_key(header::CONTENT_TYPE) + } else { + true + }; + + if !contains { + self.insert_header((header::CONTENT_TYPE, mime::APPLICATION_JSON)); + } + + self.body(Body::from(body)) + } + Err(e) => HttpResponse::from_error(Error::from(e)), + } + } + + /// Set an empty body and generate `Response` + /// + /// `ResponseBuilder` can not be used after this call. + #[inline] + pub fn finish(&mut self) -> HttpResponse { + self.body(Body::Empty) + } + + /// This method construct new `ResponseBuilder` + pub fn take(&mut self) -> Self { + Self { + head: self.head.take(), + err: self.err.take(), + #[cfg(feature = "cookies")] + cookies: self.cookies.take(), + } + } + + #[inline] + fn inner(&mut self) -> Option<&mut ResponseHead> { + if self.err.is_some() { + return None; + } + + self.head.as_mut() + } +} + +impl From for HttpResponse { + fn from(mut builder: HttpResponseBuilder) -> Self { + builder.finish() + } +} + +impl From for Response { + fn from(mut builder: HttpResponseBuilder) -> Self { + builder.finish().into() + } +} + +impl Future for HttpResponseBuilder { + type Output = Result; + + fn poll(mut self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll { + eprintln!("httpresponse future error"); + Poll::Ready(Ok(self.finish())) + } +} + +#[cfg(feature = "cookies")] +pub struct CookieIter<'a> { + iter: header::GetAll<'a>, +} + +#[cfg(feature = "cookies")] +impl<'a> Iterator for CookieIter<'a> { + type Item = Cookie<'a>; + + #[inline] + fn next(&mut self) -> Option> { + for v in self.iter.by_ref() { + if let Ok(c) = Cookie::parse_encoded(v.to_str().ok()?) { + return Some(c); + } + } + None + } +} + +mod http_codes { + //! Status code based HTTP response builders. + + use actix_http::http::StatusCode; + + use super::{HttpResponse, HttpResponseBuilder}; + + macro_rules! static_resp { + ($name:ident, $status:expr) => { + #[allow(non_snake_case, missing_docs)] + pub fn $name() -> HttpResponseBuilder { + HttpResponseBuilder::new($status) + } + }; + } + + impl HttpResponse { + static_resp!(Continue, StatusCode::CONTINUE); + static_resp!(SwitchingProtocols, StatusCode::SWITCHING_PROTOCOLS); + static_resp!(Processing, StatusCode::PROCESSING); + + static_resp!(Ok, StatusCode::OK); + static_resp!(Created, StatusCode::CREATED); + static_resp!(Accepted, StatusCode::ACCEPTED); + static_resp!( + NonAuthoritativeInformation, + StatusCode::NON_AUTHORITATIVE_INFORMATION + ); + + static_resp!(NoContent, StatusCode::NO_CONTENT); + static_resp!(ResetContent, StatusCode::RESET_CONTENT); + static_resp!(PartialContent, StatusCode::PARTIAL_CONTENT); + static_resp!(MultiStatus, StatusCode::MULTI_STATUS); + static_resp!(AlreadyReported, StatusCode::ALREADY_REPORTED); + + static_resp!(MultipleChoices, StatusCode::MULTIPLE_CHOICES); + static_resp!(MovedPermanently, StatusCode::MOVED_PERMANENTLY); + static_resp!(Found, StatusCode::FOUND); + static_resp!(SeeOther, StatusCode::SEE_OTHER); + static_resp!(NotModified, StatusCode::NOT_MODIFIED); + static_resp!(UseProxy, StatusCode::USE_PROXY); + static_resp!(TemporaryRedirect, StatusCode::TEMPORARY_REDIRECT); + static_resp!(PermanentRedirect, StatusCode::PERMANENT_REDIRECT); + + static_resp!(BadRequest, StatusCode::BAD_REQUEST); + static_resp!(NotFound, StatusCode::NOT_FOUND); + static_resp!(Unauthorized, StatusCode::UNAUTHORIZED); + static_resp!(PaymentRequired, StatusCode::PAYMENT_REQUIRED); + static_resp!(Forbidden, StatusCode::FORBIDDEN); + static_resp!(MethodNotAllowed, StatusCode::METHOD_NOT_ALLOWED); + static_resp!(NotAcceptable, StatusCode::NOT_ACCEPTABLE); + static_resp!( + ProxyAuthenticationRequired, + StatusCode::PROXY_AUTHENTICATION_REQUIRED + ); + static_resp!(RequestTimeout, StatusCode::REQUEST_TIMEOUT); + static_resp!(Conflict, StatusCode::CONFLICT); + static_resp!(Gone, StatusCode::GONE); + static_resp!(LengthRequired, StatusCode::LENGTH_REQUIRED); + static_resp!(PreconditionFailed, StatusCode::PRECONDITION_FAILED); + static_resp!(PreconditionRequired, StatusCode::PRECONDITION_REQUIRED); + static_resp!(PayloadTooLarge, StatusCode::PAYLOAD_TOO_LARGE); + static_resp!(UriTooLong, StatusCode::URI_TOO_LONG); + static_resp!(UnsupportedMediaType, StatusCode::UNSUPPORTED_MEDIA_TYPE); + static_resp!(RangeNotSatisfiable, StatusCode::RANGE_NOT_SATISFIABLE); + static_resp!(ExpectationFailed, StatusCode::EXPECTATION_FAILED); + static_resp!(UnprocessableEntity, StatusCode::UNPROCESSABLE_ENTITY); + static_resp!(TooManyRequests, StatusCode::TOO_MANY_REQUESTS); + static_resp!( + RequestHeaderFieldsTooLarge, + StatusCode::REQUEST_HEADER_FIELDS_TOO_LARGE + ); + static_resp!( + UnavailableForLegalReasons, + StatusCode::UNAVAILABLE_FOR_LEGAL_REASONS + ); + + static_resp!(InternalServerError, StatusCode::INTERNAL_SERVER_ERROR); + static_resp!(NotImplemented, StatusCode::NOT_IMPLEMENTED); + static_resp!(BadGateway, StatusCode::BAD_GATEWAY); + static_resp!(ServiceUnavailable, StatusCode::SERVICE_UNAVAILABLE); + static_resp!(GatewayTimeout, StatusCode::GATEWAY_TIMEOUT); + static_resp!(VersionNotSupported, StatusCode::HTTP_VERSION_NOT_SUPPORTED); + static_resp!(VariantAlsoNegotiates, StatusCode::VARIANT_ALSO_NEGOTIATES); + static_resp!(InsufficientStorage, StatusCode::INSUFFICIENT_STORAGE); + static_resp!(LoopDetected, StatusCode::LOOP_DETECTED); + } + + #[cfg(test)] + mod tests { + use crate::dev::Body; + use crate::http::StatusCode; + use crate::HttpResponse; + + #[test] + fn test_build() { + let resp = HttpResponse::Ok().body(Body::Empty); + assert_eq!(resp.status(), StatusCode::OK); + } + } +} + +#[cfg(test)] +mod tests { + use bytes::{Bytes, BytesMut}; + use serde_json::json; + + use super::{HttpResponse as Response, HttpResponseBuilder as ResponseBuilder}; + use crate::dev::{Body, MessageBody, ResponseBody}; + use crate::http::header::{self, HeaderValue, CONTENT_TYPE, COOKIE}; + use crate::http::StatusCode; + + #[test] + fn test_debug() { + let resp = Response::Ok() + .append_header((COOKIE, HeaderValue::from_static("cookie1=value1; "))) + .append_header((COOKIE, HeaderValue::from_static("cookie2=value2; "))) + .finish(); + let dbg = format!("{:?}", resp); + assert!(dbg.contains("Response")); + } + + #[test] + fn test_basic_builder() { + let resp = Response::Ok().insert_header(("X-TEST", "value")).finish(); + assert_eq!(resp.status(), StatusCode::OK); + } + + #[test] + fn test_upgrade() { + let resp = ResponseBuilder::new(StatusCode::OK) + .upgrade("websocket") + .finish(); + assert!(resp.upgrade()); + assert_eq!( + resp.headers().get(header::UPGRADE).unwrap(), + HeaderValue::from_static("websocket") + ); + } + + #[test] + fn test_force_close() { + let resp = ResponseBuilder::new(StatusCode::OK).force_close().finish(); + assert!(!resp.keep_alive()) + } + + #[test] + fn test_content_type() { + let resp = ResponseBuilder::new(StatusCode::OK) + .content_type("text/plain") + .body(Body::Empty); + assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "text/plain") + } + + pub async fn read_body(mut body: ResponseBody) -> Bytes + where + B: MessageBody + Unpin, + { + use futures_util::StreamExt as _; + + let mut bytes = BytesMut::new(); + while let Some(item) = body.next().await { + bytes.extend_from_slice(&item.unwrap()); + } + bytes.freeze() + } + + #[actix_rt::test] + async fn test_json() { + let mut resp = Response::Ok().json(vec!["v1", "v2", "v3"]); + let ct = resp.headers().get(CONTENT_TYPE).unwrap(); + assert_eq!(ct, HeaderValue::from_static("application/json")); + assert_eq!( + read_body(resp.take_body()).await.as_ref(), + br#"["v1","v2","v3"]"# + ); + + let mut resp = Response::Ok().json(&["v1", "v2", "v3"]); + let ct = resp.headers().get(CONTENT_TYPE).unwrap(); + assert_eq!(ct, HeaderValue::from_static("application/json")); + assert_eq!( + read_body(resp.take_body()).await.as_ref(), + br#"["v1","v2","v3"]"# + ); + + // content type override + let mut resp = Response::Ok() + .insert_header((CONTENT_TYPE, "text/json")) + .json(&vec!["v1", "v2", "v3"]); + let ct = resp.headers().get(CONTENT_TYPE).unwrap(); + assert_eq!(ct, HeaderValue::from_static("text/json")); + assert_eq!( + read_body(resp.take_body()).await.as_ref(), + br#"["v1","v2","v3"]"# + ); + } + + #[actix_rt::test] + async fn test_serde_json_in_body() { + use serde_json::json; + let mut resp = Response::Ok().body(json!({"test-key":"test-value"})); + assert_eq!( + read_body(resp.take_body()).await.as_ref(), + br#"{"test-key":"test-value"}"# + ); + } + + #[test] + fn response_builder_header_insert_kv() { + let mut res = Response::Ok(); + res.insert_header(("Content-Type", "application/octet-stream")); + let res = res.finish(); + + assert_eq!( + res.headers().get("Content-Type"), + Some(&HeaderValue::from_static("application/octet-stream")) + ); + } + + #[test] + fn response_builder_header_insert_typed() { + let mut res = Response::Ok(); + res.insert_header((header::CONTENT_TYPE, mime::APPLICATION_OCTET_STREAM)); + let res = res.finish(); + + assert_eq!( + res.headers().get("Content-Type"), + Some(&HeaderValue::from_static("application/octet-stream")) + ); + } + + #[test] + fn response_builder_header_append_kv() { + let mut res = Response::Ok(); + res.append_header(("Content-Type", "application/octet-stream")); + res.append_header(("Content-Type", "application/json")); + let res = res.finish(); + + let headers: Vec<_> = res.headers().get_all("Content-Type").cloned().collect(); + assert_eq!(headers.len(), 2); + assert!(headers.contains(&HeaderValue::from_static("application/octet-stream"))); + assert!(headers.contains(&HeaderValue::from_static("application/json"))); + } + + #[test] + fn response_builder_header_append_typed() { + let mut res = Response::Ok(); + res.append_header((header::CONTENT_TYPE, mime::APPLICATION_OCTET_STREAM)); + res.append_header((header::CONTENT_TYPE, mime::APPLICATION_JSON)); + let res = res.finish(); + + let headers: Vec<_> = res.headers().get_all("Content-Type").cloned().collect(); + assert_eq!(headers.len(), 2); + assert!(headers.contains(&HeaderValue::from_static("application/octet-stream"))); + assert!(headers.contains(&HeaderValue::from_static("application/json"))); + } +} diff --git a/src/service.rs b/src/service.rs index c2ecc0033..9765343c1 100644 --- a/src/service.rs +++ b/src/service.rs @@ -10,12 +10,15 @@ use actix_http::{ use actix_router::{IntoPattern, Path, Resource, ResourceDef, Url}; use actix_service::{IntoServiceFactory, ServiceFactory}; -use crate::config::{AppConfig, AppService}; use crate::dev::insert_slash; use crate::guard::Guard; use crate::info::ConnectionInfo; use crate::request::HttpRequest; use crate::rmap::ResourceMap; +use crate::{ + config::{AppConfig, AppService}, + HttpResponse, +}; pub trait HttpServiceFactory { fn register(self, config: &mut AppService); @@ -99,13 +102,14 @@ impl ServiceRequest { /// Create service response #[inline] pub fn into_response>>(self, res: R) -> ServiceResponse { - ServiceResponse::new(self.req, res.into()) + let res = HttpResponse::from(res.into()); + ServiceResponse::new(self.req, res) } /// Create service response for error #[inline] pub fn error_response>(self, err: E) -> ServiceResponse { - let res: Response = err.into().into(); + let res = HttpResponse::from_error(err.into()); ServiceResponse::new(self.req, res.into_body()) } @@ -315,23 +319,19 @@ impl fmt::Debug for ServiceRequest { pub struct ServiceResponse { request: HttpRequest, - response: Response, + response: HttpResponse, } impl ServiceResponse { /// Create service response instance - pub fn new(request: HttpRequest, response: Response) -> Self { + pub fn new(request: HttpRequest, response: HttpResponse) -> Self { ServiceResponse { request, response } } /// Create service response from the error pub fn from_err>(err: E, request: HttpRequest) -> Self { - let e: Error = err.into(); - let res: Response = e.into(); - ServiceResponse { - request, - response: res.into_body(), - } + let response = HttpResponse::from_error(err.into()).into_body(); + ServiceResponse { request, response } } /// Create service response for error @@ -342,7 +342,7 @@ impl ServiceResponse { /// Create service response #[inline] - pub fn into_response(self, response: Response) -> ServiceResponse { + pub fn into_response(self, response: HttpResponse) -> ServiceResponse { ServiceResponse::new(self.request, response) } @@ -354,13 +354,13 @@ impl ServiceResponse { /// Get reference to response #[inline] - pub fn response(&self) -> &Response { + pub fn response(&self) -> &HttpResponse { &self.response } /// Get mutable reference to response #[inline] - pub fn response_mut(&mut self) -> &mut Response { + pub fn response_mut(&mut self) -> &mut HttpResponse { &mut self.response } @@ -376,8 +376,8 @@ impl ServiceResponse { self.response.headers() } - #[inline] /// Returns mutable response's headers. + #[inline] pub fn headers_mut(&mut self) -> &mut HeaderMap { self.response.headers_mut() } @@ -391,7 +391,7 @@ impl ServiceResponse { match f(&mut self) { Ok(_) => self, Err(err) => { - let res: Response = err.into().into(); + let res = HttpResponse::from_error(err.into()); ServiceResponse::new(self.request, res.into_body()) } } @@ -418,9 +418,15 @@ impl ServiceResponse { } } +impl From> for HttpResponse { + fn from(res: ServiceResponse) -> HttpResponse { + res.response + } +} + impl From> for Response { fn from(res: ServiceResponse) -> Response { - res.response + res.response.into() } } diff --git a/src/test.rs b/src/test.rs index 37fb96e0e..c2e456e58 100644 --- a/src/test.rs +++ b/src/test.rs @@ -2,8 +2,6 @@ use std::{net::SocketAddr, rc::Rc}; -#[cfg(feature = "cookies")] -use actix_http::cookie::Cookie; pub use actix_http::test::TestBuffer; use actix_http::{ http::{header::IntoHeaderPair, Method, StatusCode, Uri, Version}, @@ -17,6 +15,8 @@ use futures_core::Stream; use futures_util::StreamExt as _; use serde::{de::DeserializeOwned, Serialize}; +#[cfg(feature = "cookies")] +use crate::cookie::{Cookie, CookieJar}; use crate::{ app_service::AppInitServiceState, config::AppConfig, @@ -26,7 +26,7 @@ use crate::{ rmap::ResourceMap, service::{ServiceRequest, ServiceResponse}, web::{Bytes, BytesMut}, - Error, HttpRequest, HttpResponse, + Error, HttpRequest, HttpResponse, HttpResponseBuilder, }; /// Create service that always responds with `HttpResponse::Ok()` and no body. @@ -40,7 +40,7 @@ pub fn default_service( status_code: StatusCode, ) -> impl Service, Error = Error> { (move |req: ServiceRequest| { - ok(req.into_response(HttpResponse::build(status_code).finish())) + ok(req.into_response(HttpResponseBuilder::new(status_code).finish())) }) .into_service() } @@ -359,6 +359,8 @@ pub struct TestRequest { path: Path, peer_addr: Option, app_data: Extensions, + #[cfg(feature = "cookies")] + cookies: CookieJar, } impl Default for TestRequest { @@ -370,6 +372,8 @@ impl Default for TestRequest { path: Path::new(Url::new(Uri::default())), peer_addr: None, app_data: Extensions::new(), + #[cfg(feature = "cookies")] + cookies: CookieJar::new(), } } } @@ -445,7 +449,7 @@ impl TestRequest { /// Set cookie for this request. #[cfg(feature = "cookies")] pub fn cookie(mut self, cookie: Cookie<'_>) -> Self { - self.req.cookie(cookie); + self.cookies.add(cookie.into_owned()); self } @@ -507,16 +511,42 @@ impl TestRequest { self } + fn finish(&mut self) -> Request { + // mut used when cookie feature is enabled + #[allow(unused_mut)] + let mut req = self.req.finish(); + + #[cfg(feature = "cookies")] + { + use actix_http::http::header::{HeaderValue, COOKIE}; + + let cookie: String = self + .cookies + .delta() + // ensure only name=value is written to cookie header + .map(|c| c.stripped().encoded().to_string()) + .collect::>() + .join("; "); + + if !cookie.is_empty() { + req.headers_mut() + .insert(COOKIE, HeaderValue::from_str(&cookie).unwrap()); + } + } + + req + } + /// Complete request creation and generate `Request` instance pub fn to_request(mut self) -> Request { - let mut req = self.req.finish(); + let mut req = self.finish(); req.head_mut().peer_addr = self.peer_addr; req } /// Complete request creation and generate `ServiceRequest` instance pub fn to_srv_request(mut self) -> ServiceRequest { - let (mut head, payload) = self.req.finish().into_parts(); + let (mut head, payload) = self.finish().into_parts(); head.peer_addr = self.peer_addr; self.path.get_mut().update(&head.uri); @@ -535,7 +565,7 @@ impl TestRequest { /// Complete request creation and generate `HttpRequest` instance pub fn to_http_request(mut self) -> HttpRequest { - let (mut head, _) = self.req.finish().into_parts(); + let (mut head, _) = self.finish().into_parts(); head.peer_addr = self.peer_addr; self.path.get_mut().update(&head.uri); @@ -546,7 +576,7 @@ impl TestRequest { /// Complete request creation and generate `HttpRequest` and `Payload` instances pub fn to_http_parts(mut self) -> (HttpRequest, Payload) { - let (mut head, payload) = self.req.finish().into_parts(); + let (mut head, payload) = self.finish().into_parts(); head.peer_addr = self.peer_addr; self.path.get_mut().update(&head.uri); diff --git a/src/types/json.rs b/src/types/json.rs index 97439d8fd..265beb56c 100644 --- a/src/types/json.rs +++ b/src/types/json.rs @@ -225,7 +225,7 @@ where /// .content_type(|mime| mime == mime::TEXT_PLAIN) /// // use custom error handler /// .error_handler(|err, req| { -/// error::InternalError::from_response(err, HttpResponse::Conflict().finish()).into() +/// error::InternalError::from_response(err, HttpResponse::Conflict().into()).into() /// }); /// /// App::new() @@ -486,7 +486,7 @@ mod tests { }; let resp = HttpResponse::BadRequest().body(serde_json::to_string(&msg).unwrap()); - InternalError::from_response(err, resp).into() + InternalError::from_response(err, resp.into()).into() })) .to_http_parts(); diff --git a/src/types/path.rs b/src/types/path.rs index 33ea70629..69a75e9cf 100644 --- a/src/types/path.rs +++ b/src/types/path.rs @@ -149,7 +149,7 @@ where /// .app_data(PathConfig::default().error_handler(|err, req| { /// error::InternalError::from_response( /// err, -/// HttpResponse::Conflict().finish(), +/// HttpResponse::Conflict().into(), /// ) /// .into() /// })) @@ -292,15 +292,18 @@ mod tests { async fn test_custom_err_handler() { let (req, mut pl) = TestRequest::with_uri("/name/user1/") .app_data(PathConfig::default().error_handler(|err, _| { - error::InternalError::from_response(err, HttpResponse::Conflict().finish()) - .into() + error::InternalError::from_response( + err, + HttpResponse::Conflict().finish().into(), + ) + .into() })) .to_http_parts(); let s = Path::<(usize,)>::from_request(&req, &mut pl) .await .unwrap_err(); - let res: HttpResponse = s.into(); + let res = HttpResponse::from_error(s.into()); assert_eq!(res.status(), http::StatusCode::CONFLICT); } diff --git a/src/types/query.rs b/src/types/query.rs index 4807335bc..978d00b5f 100644 --- a/src/types/query.rs +++ b/src/types/query.rs @@ -166,7 +166,7 @@ where /// let query_cfg = web::QueryConfig::default() /// // use custom error handler /// .error_handler(|err, req| { -/// error::InternalError::from_response(err, HttpResponse::Conflict().finish()).into() +/// error::InternalError::from_response(err, HttpResponse::Conflict().into()).into() /// }); /// /// App::new() @@ -267,7 +267,7 @@ mod tests { let req = TestRequest::with_uri("/name/user1/") .app_data(QueryConfig::default().error_handler(|e, _| { let resp = HttpResponse::UnprocessableEntity().finish(); - InternalError::from_response(e, resp).into() + InternalError::from_response(e, resp.into()).into() })) .to_srv_request(); diff --git a/tests/test_server.rs b/tests/test_server.rs index d114b022d..2760cc7fb 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -15,6 +15,7 @@ use actix_http::http::header::{ }; use brotli2::write::{BrotliDecoder, BrotliEncoder}; use bytes::Bytes; +use cookie::{Cookie, CookieBuilder}; use flate2::{ read::GzDecoder, write::{GzEncoder, ZlibDecoder, ZlibEncoder}, @@ -826,18 +827,18 @@ mod plus_rustls { #[actix_rt::test] async fn test_server_cookies() { - use actix_web::{http, HttpMessage}; + use actix_web::http; let srv = actix_test::start(|| { App::new().default_service(web::to(|| { HttpResponse::Ok() .cookie( - http::CookieBuilder::new("first", "first_value") + CookieBuilder::new("first", "first_value") .http_only(true) .finish(), ) - .cookie(http::Cookie::new("second", "first_value")) - .cookie(http::Cookie::new("second", "second_value")) + .cookie(Cookie::new("second", "first_value")) + .cookie(Cookie::new("second", "second_value")) .finish() })) }); @@ -846,10 +847,10 @@ async fn test_server_cookies() { let res = req.send().await.unwrap(); assert!(res.status().is_success()); - let first_cookie = http::CookieBuilder::new("first", "first_value") + let first_cookie = CookieBuilder::new("first", "first_value") .http_only(true) .finish(); - let second_cookie = http::Cookie::new("second", "second_value"); + let second_cookie = Cookie::new("second", "second_value"); let cookies = res.cookies().expect("To have cookies"); assert_eq!(cookies.len(), 2); From 981c54432ce3d92e3b9c7c35bbb83905486360a9 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Mon, 12 Apr 2021 10:30:28 +0100 Subject: [PATCH 46/79] remove json and url encoded form support from -http (#2148) --- CHANGES.md | 5 ++ actix-http/CHANGES.md | 5 ++ actix-http/Cargo.toml | 3 +- actix-http/src/body/body.rs | 6 -- actix-http/src/body/mod.rs | 8 +- actix-http/src/error.rs | 31 +++----- actix-http/src/response.rs | 145 ++---------------------------------- awc/CHANGES.md | 4 + awc/src/request.rs | 28 ------- awc/src/sender.rs | 8 +- src/error.rs | 64 +++++++++++----- src/response.rs | 65 +++++++++------- src/types/form.rs | 8 +- src/types/json.rs | 5 +- src/types/path.rs | 10 +-- 15 files changed, 137 insertions(+), 258 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 64e0891e5..bf8fc9424 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -4,7 +4,12 @@ ### Added * `HttpResponse` and `HttpResponseBuilder` structs. [#2065] +### Changed +* Most error types are now marked `#[non_exhaustive]`. [#2148] + [#2065]: https://github.com/actix/actix-web/pull/2065 +[#2148]: https://github.com/actix/actix-web/pull/2148 + ## 4.0.0-beta.5 - 2021-04-02 ### Added diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 5e4258677..e50259634 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -6,8 +6,13 @@ * Top-level `cookies` mod (re-export). [#2065] * `HttpMessage` trait loses the `cookies` and `cookie` methods. [#2065] * `impl ResponseError for CookieParseError`. [#2065] +* Deprecated methods on `ResponseBuilder`: `if_true`, `if_some`. [#2148] +* `ResponseBuilder::json`. [#2148] +* `ResponseBuilder::{set_header, header}`. [#2148] +* `impl From for Body`. [#2148] [#2065]: https://github.com/actix/actix-web/pull/2065 +[#2148]: https://github.com/actix/actix-web/pull/2148 ## 3.0.0-beta.5 - 2021-04-02 diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 573376b07..4bef9e37c 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -68,8 +68,6 @@ pin-project-lite = "0.2" rand = "0.8" regex = "1.3" serde = "1.0" -serde_json = "1.0" -serde_urlencoded = "0.7" sha-1 = "0.9" smallvec = "1.6" time = { version = "0.2.23", default-features = false, features = ["std"] } @@ -89,6 +87,7 @@ criterion = "0.3" env_logger = "0.8" rcgen = "0.8" serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" tls-openssl = { version = "0.10", package = "openssl" } tls-rustls = { version = "0.19", package = "rustls" } diff --git a/actix-http/src/body/body.rs b/actix-http/src/body/body.rs index a3fd7d41c..6814d54f7 100644 --- a/actix-http/src/body/body.rs +++ b/actix-http/src/body/body.rs @@ -132,12 +132,6 @@ impl From for Body { } } -impl From for Body { - fn from(v: serde_json::Value) -> Body { - Body::Bytes(v.to_string().into()) - } -} - impl From> for Body where S: Stream> + Unpin + 'static, diff --git a/actix-http/src/body/mod.rs b/actix-http/src/body/mod.rs index fa43e1b03..f5664e1dc 100644 --- a/actix-http/src/body/mod.rs +++ b/actix-http/src/body/mod.rs @@ -174,13 +174,15 @@ mod tests { #[actix_rt::test] async fn test_serde_json() { - use serde_json::json; + use serde_json::{json, Value}; assert_eq!( - Body::from(serde_json::Value::String("test".into())).size(), + Body::from(serde_json::to_vec(&Value::String("test".to_owned())).unwrap()) + .size(), BodySize::Sized(6) ); assert_eq!( - Body::from(json!({"test-key":"test-value"})).size(), + Body::from(serde_json::to_vec(&json!({"test-key":"test-value"})).unwrap()) + .size(), BodySize::Sized(25) ); } diff --git a/actix-http/src/error.rs b/actix-http/src/error.rs index 03693ff2f..60a870ecc 100644 --- a/actix-http/src/error.rs +++ b/actix-http/src/error.rs @@ -1,18 +1,18 @@ //! Error and Result module -use std::cell::RefCell; -use std::io::Write; -use std::str::Utf8Error; -use std::string::FromUtf8Error; -use std::{fmt, io, result}; +use std::{ + cell::RefCell, + fmt, + io::{self, Write as _}, + str::Utf8Error, + string::FromUtf8Error, +}; use bytes::BytesMut; -use derive_more::{Display, From}; +use derive_more::{Display, Error, From}; use http::uri::InvalidUri; use http::{header, Error as HttpError, StatusCode}; use serde::de::value::Error as DeError; -use serde_json::error::Error as JsonError; -use serde_urlencoded::ser::Error as FormError; use crate::{body::Body, helpers::Writer, Response, ResponseBuilder}; @@ -22,7 +22,7 @@ use crate::{body::Body, helpers::Writer, Response, ResponseBuilder}; /// This typedef is generally used to avoid writing out /// `actix_http::error::Error` directly and is otherwise a direct mapping to /// `Result`. -pub type Result = result::Result; +pub type Result = std::result::Result; /// General purpose actix web error. /// @@ -147,14 +147,8 @@ struct UnitError; /// Returns [`StatusCode::INTERNAL_SERVER_ERROR`] for [`UnitError`]. impl ResponseError for UnitError {} -/// Returns [`StatusCode::INTERNAL_SERVER_ERROR`] for [`JsonError`]. -impl ResponseError for JsonError {} - -/// Returns [`StatusCode::INTERNAL_SERVER_ERROR`] for [`FormError`]. -impl ResponseError for FormError {} - -#[cfg(feature = "openssl")] /// Returns [`StatusCode::INTERNAL_SERVER_ERROR`] for [`actix_tls::accept::openssl::SslError`]. +#[cfg(feature = "openssl")] impl ResponseError for actix_tls::accept::openssl::SslError {} /// Returns [`StatusCode::BAD_REQUEST`] for [`DeError`]. @@ -421,18 +415,17 @@ pub enum DispatchError { } /// A set of error that can occur during parsing content type -#[derive(PartialEq, Debug, Display)] +#[derive(Debug, PartialEq, Display, Error)] pub enum ContentTypeError { /// Can not parse content type #[display(fmt = "Can not parse content type")] ParseError, + /// Unknown content encoding #[display(fmt = "Unknown content encoding")] UnknownEncoding, } -impl std::error::Error for ContentTypeError {} - /// Return `BadRequest` for `ContentTypeError` impl ResponseError for ContentTypeError { fn status_code(&self) -> StatusCode { diff --git a/actix-http/src/response.rs b/actix-http/src/response.rs index a96a13bd3..046c4ca56 100644 --- a/actix-http/src/response.rs +++ b/actix-http/src/response.rs @@ -2,7 +2,6 @@ use std::{ cell::{Ref, RefMut}, - convert::TryInto, fmt, future::Future, pin::Pin, @@ -12,17 +11,13 @@ use std::{ use bytes::{Bytes, BytesMut}; use futures_core::Stream; -use serde::Serialize; use crate::{ body::{Body, BodyStream, MessageBody, ResponseBody}, error::Error, extensions::Extensions, header::{IntoHeaderPair, IntoHeaderValue}, - http::{ - header::{self, HeaderName}, - Error as HttpError, HeaderMap, StatusCode, - }, + http::{header, Error as HttpError, HeaderMap, StatusCode}, message::{BoxedResponseHead, ConnectionType, ResponseHead}, }; @@ -335,54 +330,6 @@ impl ResponseBuilder { self } - /// Replaced with [`Self::insert_header()`]. - #[deprecated( - since = "4.0.0", - note = "Replaced with `insert_header((key, value))`. Will be removed in v5." - )] - pub fn set_header(&mut self, key: K, value: V) -> &mut Self - where - K: TryInto, - K::Error: Into, - V: IntoHeaderValue, - { - if self.err.is_some() { - return self; - } - - match (key.try_into(), value.try_into_value()) { - (Ok(name), Ok(value)) => return self.insert_header((name, value)), - (Err(err), _) => self.err = Some(err.into()), - (_, Err(err)) => self.err = Some(err.into()), - } - - self - } - - /// Replaced with [`Self::append_header()`]. - #[deprecated( - since = "4.0.0", - note = "Replaced with `append_header((key, value))`. Will be removed in v5." - )] - pub fn header(&mut self, key: K, value: V) -> &mut Self - where - K: TryInto, - K::Error: Into, - V: IntoHeaderValue, - { - if self.err.is_some() { - return self; - } - - match (key.try_into(), value.try_into_value()) { - (Ok(name), Ok(value)) => return self.append_header((name, value)), - (Err(err), _) => self.err = Some(err.into()), - (_, Err(err)) => self.err = Some(err.into()), - } - - self - } - /// Set the custom reason for the response. #[inline] pub fn reason(&mut self, reason: &'static str) -> &mut Self { @@ -456,32 +403,6 @@ impl ResponseBuilder { self } - /// This method calls provided closure with builder reference if value is `true`. - #[doc(hidden)] - #[deprecated = "Use an if statement."] - pub fn if_true(&mut self, value: bool, f: F) -> &mut Self - where - F: FnOnce(&mut ResponseBuilder), - { - if value { - f(self); - } - self - } - - /// This method calls provided closure with builder reference if value is `Some`. - #[doc(hidden)] - #[deprecated = "Use an if-let construction."] - pub fn if_some(&mut self, value: Option, f: F) -> &mut Self - where - F: FnOnce(T, &mut ResponseBuilder), - { - if let Some(val) = value { - f(val, self); - } - self - } - /// Responses extensions #[inline] pub fn extensions(&self) -> Ref<'_, Extensions> { @@ -496,10 +417,10 @@ impl ResponseBuilder { head.extensions.borrow_mut() } - #[inline] /// Set a body and generate `Response`. /// /// `ResponseBuilder` can not be used after this call. + #[inline] pub fn body>(&mut self, body: B) -> Response { self.message_body(body.into()) } @@ -521,10 +442,10 @@ impl ResponseBuilder { } } - #[inline] /// Set a streaming body and generate `Response`. /// /// `ResponseBuilder` can not be used after this call. + #[inline] pub fn streaming(&mut self, stream: S) -> Response where S: Stream> + Unpin + 'static, @@ -533,32 +454,10 @@ impl ResponseBuilder { self.body(Body::from_message(BodyStream::new(stream))) } - /// Set a json body and generate `Response` - /// - /// `ResponseBuilder` can not be used after this call. - pub fn json(&mut self, value: impl Serialize) -> Response { - match serde_json::to_string(&value) { - Ok(body) => { - let contains = if let Some(parts) = parts(&mut self.head, &self.err) { - parts.headers.contains_key(header::CONTENT_TYPE) - } else { - true - }; - - if !contains { - self.insert_header((header::CONTENT_TYPE, mime::APPLICATION_JSON)); - } - - self.body(Body::from(body)) - } - Err(e) => Error::from(e).into(), - } - } - - #[inline] /// Set an empty body and generate `Response` /// /// `ResponseBuilder` can not be used after this call. + #[inline] pub fn finish(&mut self) -> Response { self.body(Body::Empty) } @@ -706,11 +605,9 @@ impl From for Response { #[cfg(test)] mod tests { - use serde_json::json; - use super::*; use crate::body::Body; - use crate::http::header::{HeaderValue, CONTENT_TYPE, COOKIE}; + use crate::http::header::{HeaderName, HeaderValue, CONTENT_TYPE, COOKIE}; #[test] fn test_debug() { @@ -754,38 +651,6 @@ mod tests { assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "text/plain") } - #[test] - fn test_json() { - let resp = Response::Ok().json(vec!["v1", "v2", "v3"]); - let ct = resp.headers().get(CONTENT_TYPE).unwrap(); - assert_eq!(ct, HeaderValue::from_static("application/json")); - assert_eq!(resp.body().get_ref(), b"[\"v1\",\"v2\",\"v3\"]"); - - let resp = Response::Ok().json(&["v1", "v2", "v3"]); - let ct = resp.headers().get(CONTENT_TYPE).unwrap(); - assert_eq!(ct, HeaderValue::from_static("application/json")); - assert_eq!(resp.body().get_ref(), b"[\"v1\",\"v2\",\"v3\"]"); - } - - #[test] - fn test_json_ct() { - let resp = Response::build(StatusCode::OK) - .insert_header((CONTENT_TYPE, "text/json")) - .json(&vec!["v1", "v2", "v3"]); - let ct = resp.headers().get(CONTENT_TYPE).unwrap(); - assert_eq!(ct, HeaderValue::from_static("text/json")); - assert_eq!(resp.body().get_ref(), b"[\"v1\",\"v2\",\"v3\"]"); - } - - #[test] - fn test_serde_json_in_body() { - use serde_json::json; - - let resp = - Response::build(StatusCode::OK).body(json!({"test-key":"test-value"})); - assert_eq!(resp.body().get_ref(), br#"{"test-key":"test-value"}"#); - } - #[test] fn test_into_response() { let resp: Response = "test".into(); diff --git a/awc/CHANGES.md b/awc/CHANGES.md index eb008ff98..a0bfcac86 100644 --- a/awc/CHANGES.md +++ b/awc/CHANGES.md @@ -1,6 +1,10 @@ # Changes ## Unreleased - 2021-xx-xx +### Removed +* Deprecated methods on `ClientRequest`: `if_true`, `if_some`. [#2148] + +[#2148]: https://github.com/actix/actix-web/pull/2148 ## 3.0.0-beta.4 - 2021-04-02 diff --git a/awc/src/request.rs b/awc/src/request.rs index f5cb08f15..483524102 100644 --- a/awc/src/request.rs +++ b/awc/src/request.rs @@ -311,34 +311,6 @@ impl ClientRequest { self } - /// This method calls provided closure with builder reference if value is `true`. - #[doc(hidden)] - #[deprecated = "Use an if statement."] - pub fn if_true(self, value: bool, f: F) -> Self - where - F: FnOnce(ClientRequest) -> ClientRequest, - { - if value { - f(self) - } else { - self - } - } - - /// This method calls provided closure with builder reference if value is `Some`. - #[doc(hidden)] - #[deprecated = "Use an if-let construction."] - pub fn if_some(self, value: Option, f: F) -> Self - where - F: FnOnce(T, ClientRequest) -> ClientRequest, - { - if let Some(val) = value { - f(val, self) - } else { - self - } - } - /// Sets the query part of the request pub fn query( mut self, diff --git a/awc/src/sender.rs b/awc/src/sender.rs index 1170c69a0..0e63be221 100644 --- a/awc/src/sender.rs +++ b/awc/src/sender.rs @@ -1,6 +1,6 @@ use std::{ future::Future, - net, + io, net, pin::Pin, rc::Rc, task::{Context, Poll}, @@ -209,7 +209,8 @@ impl RequestSender { ) -> SendClientRequest { let body = match serde_json::to_string(value) { Ok(body) => body, - Err(e) => return Error::from(e).into(), + // TODO: own error type + Err(e) => return Error::from(io::Error::new(io::ErrorKind::Other, e)).into(), }; if let Err(e) = self.set_header_if_none(header::CONTENT_TYPE, "application/json") { @@ -235,7 +236,8 @@ impl RequestSender { ) -> SendClientRequest { let body = match serde_urlencoded::to_string(value) { Ok(body) => body, - Err(e) => return Error::from(e).into(), + // TODO: own error type + Err(e) => return Error::from(io::Error::new(io::ErrorKind::Other, e)).into(), }; // set content-type diff --git a/src/error.rs b/src/error.rs index 25cdc9feb..cc1a055b8 100644 --- a/src/error.rs +++ b/src/error.rs @@ -3,12 +3,15 @@ pub use actix_http::error::*; use derive_more::{Display, Error, From}; use serde_json::error::Error as JsonError; +use serde_urlencoded::de::Error as FormDeError; +use serde_urlencoded::ser::Error as FormError; use url::ParseError as UrlParseError; use crate::http::StatusCode; /// Errors which can occur when attempting to generate resource uri. -#[derive(Debug, PartialEq, Display, From)] +#[derive(Debug, PartialEq, Display, Error, From)] +#[non_exhaustive] pub enum UrlGenerationError { /// Resource not found #[display(fmt = "Resource not found")] @@ -23,13 +26,12 @@ pub enum UrlGenerationError { ParseError(UrlParseError), } -impl std::error::Error for UrlGenerationError {} - /// `InternalServerError` for `UrlGeneratorError` impl ResponseError for UrlGenerationError {} /// A set of errors that can occur during parsing urlencoded payloads #[derive(Debug, Display, Error, From)] +#[non_exhaustive] pub enum UrlencodedError { /// Can not decode chunked transfer encoding. #[display(fmt = "Can not decode chunked transfer encoding.")] @@ -52,8 +54,16 @@ pub enum UrlencodedError { ContentType, /// Parse error. - #[display(fmt = "Parse error.")] - Parse, + #[display(fmt = "Parse error: {}.", _0)] + Parse(FormDeError), + + /// Encoding error. + #[display(fmt = "Encoding error.")] + Encoding, + + /// Serialize error. + #[display(fmt = "Serialize error: {}.", _0)] + Serialize(FormError), /// Payload error. #[display(fmt = "Error that occur during reading payload: {}.", _0)] @@ -63,52 +73,66 @@ pub enum UrlencodedError { /// Return `BadRequest` for `UrlencodedError` impl ResponseError for UrlencodedError { fn status_code(&self) -> StatusCode { - match *self { - UrlencodedError::Overflow { .. } => StatusCode::PAYLOAD_TOO_LARGE, - UrlencodedError::UnknownLength => StatusCode::LENGTH_REQUIRED, + match self { + Self::Overflow { .. } => StatusCode::PAYLOAD_TOO_LARGE, + Self::UnknownLength => StatusCode::LENGTH_REQUIRED, + Self::Payload(err) => err.status_code(), _ => StatusCode::BAD_REQUEST, } } } /// A set of errors that can occur during parsing json payloads -#[derive(Debug, Display, From)] +#[derive(Debug, Display, Error)] +#[non_exhaustive] pub enum JsonPayloadError { /// Payload size is bigger than allowed. (default: 32kB) #[display(fmt = "Json payload size is bigger than allowed")] Overflow, + /// Content type error #[display(fmt = "Content type error")] ContentType, + /// Deserialize error #[display(fmt = "Json deserialize error: {}", _0)] Deserialize(JsonError), + + /// Serialize error + #[display(fmt = "Json serialize error: {}", _0)] + Serialize(JsonError), + /// Payload error #[display(fmt = "Error that occur during reading payload: {}", _0)] Payload(PayloadError), } -impl std::error::Error for JsonPayloadError {} +impl From for JsonPayloadError { + fn from(err: PayloadError) -> Self { + Self::Payload(err) + } +} impl ResponseError for JsonPayloadError { fn status_code(&self) -> StatusCode { - match *self { - JsonPayloadError::Overflow => StatusCode::PAYLOAD_TOO_LARGE, + match self { + Self::Overflow => StatusCode::PAYLOAD_TOO_LARGE, + Self::Serialize(_) => StatusCode::INTERNAL_SERVER_ERROR, + Self::Payload(err) => err.status_code(), _ => StatusCode::BAD_REQUEST, } } } /// A set of errors that can occur during parsing request paths -#[derive(Debug, Display, From)] +#[derive(Debug, Display, Error)] +#[non_exhaustive] pub enum PathError { /// Deserialize error #[display(fmt = "Path deserialize error: {}", _0)] Deserialize(serde::de::value::Error), } -impl std::error::Error for PathError {} - /// Return `BadRequest` for `PathError` impl ResponseError for PathError { fn status_code(&self) -> StatusCode { @@ -118,6 +142,7 @@ impl ResponseError for PathError { /// A set of errors that can occur during parsing query strings. #[derive(Debug, Display, Error, From)] +#[non_exhaustive] pub enum QueryPayloadError { /// Query deserialize error. #[display(fmt = "Query deserialize error: {}", _0)] @@ -132,25 +157,26 @@ impl ResponseError for QueryPayloadError { } /// Error type returned when reading body as lines. -#[derive(From, Display, Debug)] +#[derive(Debug, Display, Error, From)] +#[non_exhaustive] pub enum ReadlinesError { - /// Error when decoding a line. #[display(fmt = "Encoding error")] /// Payload size is bigger than allowed. (default: 256kB) EncodingError, + /// Payload error. #[display(fmt = "Error that occur during reading payload: {}", _0)] Payload(PayloadError), + /// Line limit exceeded. #[display(fmt = "Line limit exceeded")] LimitOverflow, + /// ContentType error. #[display(fmt = "Content-type error")] ContentTypeError(ContentTypeError), } -impl std::error::Error for ReadlinesError {} - /// Return `BadRequest` for `ReadlinesError` impl ResponseError for ReadlinesError { fn status_code(&self) -> StatusCode { diff --git a/src/response.rs b/src/response.rs index 1d017f080..ce6739dc8 100644 --- a/src/response.rs +++ b/src/response.rs @@ -24,7 +24,7 @@ use actix_http::http::header::HeaderValue; #[cfg(feature = "cookies")] use cookie::{Cookie, CookieJar}; -use crate::error::Error; +use crate::error::{Error, JsonPayloadError}; /// An HTTP Response pub struct HttpResponse { @@ -385,7 +385,10 @@ impl HttpResponseBuilder { } /// Replaced with [`Self::insert_header()`]. - #[deprecated = "Replaced with `insert_header((key, value))`."] + #[deprecated( + since = "4.0.0", + note = "Replaced with `insert_header((key, value))`. Will be removed in v5." + )] pub fn set_header(&mut self, key: K, value: V) -> &mut Self where K: TryInto, @@ -406,7 +409,10 @@ impl HttpResponseBuilder { } /// Replaced with [`Self::append_header()`]. - #[deprecated = "Replaced with `append_header((key, value))`."] + #[deprecated( + since = "4.0.0", + note = "Replaced with `append_header((key, value))`. Will be removed in v5." + )] pub fn header(&mut self, key: K, value: V) -> &mut Self where K: TryInto, @@ -572,7 +578,7 @@ impl HttpResponseBuilder { /// Set a body and generate `Response`. /// - /// `ResponseBuilder` can not be used after this call. + /// `HttpResponseBuilder` can not be used after this call. #[inline] pub fn body>(&mut self, body: B) -> HttpResponse { self.message_body(body.into()) @@ -580,7 +586,7 @@ impl HttpResponseBuilder { /// Set a body and generate `Response`. /// - /// `ResponseBuilder` can not be used after this call. + /// `HttpResponseBuilder` can not be used after this call. pub fn message_body(&mut self, body: B) -> HttpResponse { if let Some(err) = self.err.take() { return HttpResponse::from_error(Error::from(err)).into_body(); @@ -608,7 +614,7 @@ impl HttpResponseBuilder { /// Set a streaming body and generate `Response`. /// - /// `ResponseBuilder` can not be used after this call. + /// `HttpResponseBuilder` can not be used after this call. #[inline] pub fn streaming(&mut self, stream: S) -> HttpResponse where @@ -620,7 +626,7 @@ impl HttpResponseBuilder { /// Set a json body and generate `Response` /// - /// `ResponseBuilder` can not be used after this call. + /// `HttpResponseBuilder` can not be used after this call. pub fn json(&mut self, value: impl Serialize) -> HttpResponse { match serde_json::to_string(&value) { Ok(body) => { @@ -636,19 +642,19 @@ impl HttpResponseBuilder { self.body(Body::from(body)) } - Err(e) => HttpResponse::from_error(Error::from(e)), + Err(err) => HttpResponse::from_error(JsonPayloadError::Serialize(err).into()), } } /// Set an empty body and generate `Response` /// - /// `ResponseBuilder` can not be used after this call. + /// `HttpResponseBuilder` can not be used after this call. #[inline] pub fn finish(&mut self) -> HttpResponse { self.body(Body::Empty) } - /// This method construct new `ResponseBuilder` + /// This method construct new `HttpResponseBuilder` pub fn take(&mut self) -> Self { Self { head: self.head.take(), @@ -814,32 +820,33 @@ mod http_codes { #[cfg(test)] mod tests { use bytes::{Bytes, BytesMut}; - use serde_json::json; - use super::{HttpResponse as Response, HttpResponseBuilder as ResponseBuilder}; + use super::{HttpResponse, HttpResponseBuilder}; use crate::dev::{Body, MessageBody, ResponseBody}; use crate::http::header::{self, HeaderValue, CONTENT_TYPE, COOKIE}; use crate::http::StatusCode; #[test] fn test_debug() { - let resp = Response::Ok() + let resp = HttpResponse::Ok() .append_header((COOKIE, HeaderValue::from_static("cookie1=value1; "))) .append_header((COOKIE, HeaderValue::from_static("cookie2=value2; "))) .finish(); let dbg = format!("{:?}", resp); - assert!(dbg.contains("Response")); + assert!(dbg.contains("HttpResponse")); } #[test] fn test_basic_builder() { - let resp = Response::Ok().insert_header(("X-TEST", "value")).finish(); + let resp = HttpResponse::Ok() + .insert_header(("X-TEST", "value")) + .finish(); assert_eq!(resp.status(), StatusCode::OK); } #[test] fn test_upgrade() { - let resp = ResponseBuilder::new(StatusCode::OK) + let resp = HttpResponseBuilder::new(StatusCode::OK) .upgrade("websocket") .finish(); assert!(resp.upgrade()); @@ -851,13 +858,15 @@ mod tests { #[test] fn test_force_close() { - let resp = ResponseBuilder::new(StatusCode::OK).force_close().finish(); + let resp = HttpResponseBuilder::new(StatusCode::OK) + .force_close() + .finish(); assert!(!resp.keep_alive()) } #[test] fn test_content_type() { - let resp = ResponseBuilder::new(StatusCode::OK) + let resp = HttpResponseBuilder::new(StatusCode::OK) .content_type("text/plain") .body(Body::Empty); assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "text/plain") @@ -878,7 +887,7 @@ mod tests { #[actix_rt::test] async fn test_json() { - let mut resp = Response::Ok().json(vec!["v1", "v2", "v3"]); + let mut resp = HttpResponse::Ok().json(vec!["v1", "v2", "v3"]); let ct = resp.headers().get(CONTENT_TYPE).unwrap(); assert_eq!(ct, HeaderValue::from_static("application/json")); assert_eq!( @@ -886,7 +895,7 @@ mod tests { br#"["v1","v2","v3"]"# ); - let mut resp = Response::Ok().json(&["v1", "v2", "v3"]); + let mut resp = HttpResponse::Ok().json(&["v1", "v2", "v3"]); let ct = resp.headers().get(CONTENT_TYPE).unwrap(); assert_eq!(ct, HeaderValue::from_static("application/json")); assert_eq!( @@ -895,7 +904,7 @@ mod tests { ); // content type override - let mut resp = Response::Ok() + let mut resp = HttpResponse::Ok() .insert_header((CONTENT_TYPE, "text/json")) .json(&vec!["v1", "v2", "v3"]); let ct = resp.headers().get(CONTENT_TYPE).unwrap(); @@ -908,8 +917,10 @@ mod tests { #[actix_rt::test] async fn test_serde_json_in_body() { - use serde_json::json; - let mut resp = Response::Ok().body(json!({"test-key":"test-value"})); + let mut resp = HttpResponse::Ok().body( + serde_json::to_vec(&serde_json::json!({ "test-key": "test-value" })).unwrap(), + ); + assert_eq!( read_body(resp.take_body()).await.as_ref(), br#"{"test-key":"test-value"}"# @@ -918,7 +929,7 @@ mod tests { #[test] fn response_builder_header_insert_kv() { - let mut res = Response::Ok(); + let mut res = HttpResponse::Ok(); res.insert_header(("Content-Type", "application/octet-stream")); let res = res.finish(); @@ -930,7 +941,7 @@ mod tests { #[test] fn response_builder_header_insert_typed() { - let mut res = Response::Ok(); + let mut res = HttpResponse::Ok(); res.insert_header((header::CONTENT_TYPE, mime::APPLICATION_OCTET_STREAM)); let res = res.finish(); @@ -942,7 +953,7 @@ mod tests { #[test] fn response_builder_header_append_kv() { - let mut res = Response::Ok(); + let mut res = HttpResponse::Ok(); res.append_header(("Content-Type", "application/octet-stream")); res.append_header(("Content-Type", "application/json")); let res = res.finish(); @@ -955,7 +966,7 @@ mod tests { #[test] fn response_builder_header_append_typed() { - let mut res = Response::Ok(); + let mut res = HttpResponse::Ok(); res.append_header((header::CONTENT_TYPE, mime::APPLICATION_OCTET_STREAM)); res.append_header((header::CONTENT_TYPE, mime::APPLICATION_JSON)); let res = res.finish(); diff --git a/src/types/form.rs b/src/types/form.rs index 14c1369ff..9d2311a45 100644 --- a/src/types/form.rs +++ b/src/types/form.rs @@ -163,7 +163,7 @@ impl Responder for Form { Ok(body) => HttpResponse::Ok() .content_type(mime::APPLICATION_WWW_FORM_URLENCODED) .body(body), - Err(err) => HttpResponse::from_error(err.into()), + Err(err) => HttpResponse::from_error(UrlencodedError::Serialize(err).into()), } } } @@ -346,14 +346,14 @@ where } if encoding == UTF_8 { - serde_urlencoded::from_bytes::(&body).map_err(|_| UrlencodedError::Parse) + serde_urlencoded::from_bytes::(&body).map_err(UrlencodedError::Parse) } else { let body = encoding .decode_without_bom_handling_and_without_replacement(&body) .map(|s| s.into_owned()) - .ok_or(UrlencodedError::Parse)?; + .ok_or(UrlencodedError::Encoding)?; - serde_urlencoded::from_str::(&body).map_err(|_| UrlencodedError::Parse) + serde_urlencoded::from_str::(&body).map_err(UrlencodedError::Parse) } } .boxed_local(), diff --git a/src/types/json.rs b/src/types/json.rs index 265beb56c..322e5cbf3 100644 --- a/src/types/json.rs +++ b/src/types/json.rs @@ -127,7 +127,7 @@ impl Responder for Json { Ok(body) => HttpResponse::Ok() .content_type(mime::APPLICATION_JSON) .body(body), - Err(err) => HttpResponse::from_error(err.into()), + Err(err) => HttpResponse::from_error(JsonPayloadError::Serialize(err).into()), } } } @@ -412,7 +412,8 @@ where } } None => { - let json = serde_json::from_slice::(&buf)?; + let json = serde_json::from_slice::(&buf) + .map_err(JsonPayloadError::Deserialize)?; return Poll::Ready(Ok(json)); } } diff --git a/src/types/path.rs b/src/types/path.rs index 69a75e9cf..50e2cb510 100644 --- a/src/types/path.rs +++ b/src/types/path.rs @@ -93,7 +93,7 @@ where T: de::DeserializeOwned, { type Error = Error; - type Future = Ready>; + type Future = Ready>; type Config = PathConfig; #[inline] @@ -106,17 +106,17 @@ where ready( de::Deserialize::deserialize(PathDeserializer::new(req.match_info())) .map(Path) - .map_err(move |e| { + .map_err(move |err| { log::debug!( "Failed during Path extractor deserialization. \ Request path: {:?}", req.path() ); if let Some(error_handler) = error_handler { - let e = PathError::Deserialize(e); + let e = PathError::Deserialize(err); (error_handler)(e, req) } else { - ErrorNotFound(e) + ErrorNotFound(err) } }), ) @@ -303,7 +303,7 @@ mod tests { let s = Path::<(usize,)>::from_request(&req, &mut pl) .await .unwrap_err(); - let res = HttpResponse::from_error(s.into()); + let res = HttpResponse::from_error(s); assert_eq!(res.status(), http::StatusCode::CONFLICT); } From ce50cc952385295675faf763d255d62a95de9fa1 Mon Sep 17 00:00:00 2001 From: Ali MJ Al-Nasrawy Date: Tue, 13 Apr 2021 07:28:30 +0300 Subject: [PATCH 47/79] files: Don't use canonical path when serving file (#2156) --- actix-files/CHANGES.md | 3 +++ actix-files/src/lib.rs | 15 +++++++++++++++ actix-files/src/service.rs | 8 ++++---- actix-files/tests/symlink-test.png | 1 + 4 files changed, 23 insertions(+), 4 deletions(-) create mode 120000 actix-files/tests/symlink-test.png diff --git a/actix-files/CHANGES.md b/actix-files/CHANGES.md index eb66e0e07..004479183 100644 --- a/actix-files/CHANGES.md +++ b/actix-files/CHANGES.md @@ -1,6 +1,9 @@ # Changes ## Unreleased - 2021-xx-xx +* For symbolic links, `Content-Disposition` header no longer shows the filename of the original file. [#2156] + +[#2156]: https://github.com/actix/actix-web/pull/2156 ## 0.6.0-beta.4 - 2021-04-02 diff --git a/actix-files/src/lib.rs b/actix-files/src/lib.rs index 24b903c04..e9b55e87e 100644 --- a/actix-files/src/lib.rs +++ b/actix-files/src/lib.rs @@ -754,4 +754,19 @@ mod tests { let res = test::call_service(&srv, req).await; assert_eq!(res.status(), StatusCode::OK); } + + #[actix_rt::test] + async fn test_symlinks() { + let srv = test::init_service(App::new().service(Files::new("test", "."))).await; + + let req = TestRequest::get() + .uri("/test/tests/symlink-test.png") + .to_request(); + let res = test::call_service(&srv, req).await; + assert_eq!(res.status(), StatusCode::OK); + assert_eq!( + res.headers().get(header::CONTENT_DISPOSITION).unwrap(), + "inline; filename=\"symlink-test.png\"" + ); + } } diff --git a/actix-files/src/service.rs b/actix-files/src/service.rs index d2db8503f..dc51ada18 100644 --- a/actix-files/src/service.rs +++ b/actix-files/src/service.rs @@ -83,10 +83,10 @@ impl Service for FilesService { }; // full file path - let path = match self.directory.join(&real_path).canonicalize() { - Ok(path) => path, - Err(err) => return Box::pin(self.handle_err(err, req)), - }; + let path = self.directory.join(&real_path); + if let Err(err) = path.canonicalize() { + return Box::pin(self.handle_err(err, req)); + } if path.is_dir() { if let Some(ref redir_index) = self.index { diff --git a/actix-files/tests/symlink-test.png b/actix-files/tests/symlink-test.png new file mode 120000 index 000000000..65c0dcfd6 --- /dev/null +++ b/actix-files/tests/symlink-test.png @@ -0,0 +1 @@ +test.png \ No newline at end of file From edd9f14752b23211583b74a40d08ade293bbc284 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Tue, 13 Apr 2021 11:16:12 +0100 Subject: [PATCH 48/79] remove unpin from body types (#2152) --- actix-http/CHANGES.md | 9 ++ actix-http/examples/echo2.rs | 4 +- actix-http/src/body/body.rs | 16 ++- actix-http/src/body/body_stream.rs | 22 ++-- actix-http/src/body/message_body.rs | 13 ++ actix-http/src/body/sized_stream.rs | 26 ++-- actix-http/src/encoding/encoder.rs | 6 +- actix-http/src/error.rs | 178 ++++++++++++++-------------- actix-http/src/h1/dispatcher.rs | 16 +-- actix-http/src/h2/dispatcher.rs | 4 +- actix-http/src/http_codes.rs | 7 +- actix-http/src/lib.rs | 1 - actix-http/src/response.rs | 50 ++++---- actix-http/src/ws/mod.rs | 18 +-- actix-http/tests/test_openssl.rs | 18 +-- actix-http/tests/test_rustls.rs | 19 +-- actix-http/tests/test_server.rs | 9 +- src/responder.rs | 3 +- src/response.rs | 16 +-- 19 files changed, 241 insertions(+), 194 deletions(-) diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index e50259634..2949c40c3 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -1,6 +1,14 @@ # Changes ## Unreleased - 2021-xx-xx +### Added +* `impl MessageBody for Pin>`. [#2152] + +### Changes +* The type parameter of `Response` no longer has a default. [#2152] +* The `Message` variant of `body::Body` is now `Pin>`. [#2152] +* `BodyStream` and `SizedStream` are no longer restricted to Unpin types. [#2152] + ### Removed * `cookies` feature flag. [#2065] * Top-level `cookies` mod (re-export). [#2065] @@ -13,6 +21,7 @@ [#2065]: https://github.com/actix/actix-web/pull/2065 [#2148]: https://github.com/actix/actix-web/pull/2148 +[#2152]: https://github.com/actix/actix-web/pull/2152 ## 3.0.0-beta.5 - 2021-04-02 diff --git a/actix-http/examples/echo2.rs b/actix-http/examples/echo2.rs index 408a40114..483a79aac 100644 --- a/actix-http/examples/echo2.rs +++ b/actix-http/examples/echo2.rs @@ -1,13 +1,13 @@ use std::{env, io}; -use actix_http::http::HeaderValue; +use actix_http::{body::Body, http::HeaderValue}; use actix_http::{Error, HttpService, Request, Response}; use actix_server::Server; use bytes::BytesMut; use futures_util::StreamExt as _; use log::info; -async fn handle_request(mut req: Request) -> Result { +async fn handle_request(mut req: Request) -> Result, Error> { let mut body = BytesMut::new(); while let Some(item) = req.payload().next().await { body.extend_from_slice(&item?) diff --git a/actix-http/src/body/body.rs b/actix-http/src/body/body.rs index 6814d54f7..5fc461d41 100644 --- a/actix-http/src/body/body.rs +++ b/actix-http/src/body/body.rs @@ -12,15 +12,19 @@ use crate::error::Error; use super::{BodySize, BodyStream, MessageBody, SizedStream}; /// Represents various types of HTTP message body. +// #[deprecated(since = "4.0.0", note = "Use body types directly.")] pub enum Body { /// Empty response. `Content-Length` header is not set. None, + /// Zero sized response body. `Content-Length` header is set to `0`. Empty, + /// Specific response body. Bytes(Bytes), + /// Generic message body. - Message(Box), + Message(Pin>), } impl Body { @@ -30,8 +34,8 @@ impl Body { } /// Create body from generic message body. - pub fn from_message(body: B) -> Body { - Body::Message(Box::new(body)) + pub fn from_message(body: B) -> Body { + Body::Message(Box::pin(body)) } } @@ -60,7 +64,7 @@ impl MessageBody for Body { Poll::Ready(Some(Ok(mem::take(bin)))) } } - Body::Message(body) => Pin::new(&mut **body).poll_next(cx), + Body::Message(body) => body.as_mut().poll_next(cx), } } } @@ -134,7 +138,7 @@ impl From for Body { impl From> for Body where - S: Stream> + Unpin + 'static, + S: Stream> + 'static, { fn from(s: SizedStream) -> Body { Body::from_message(s) @@ -143,7 +147,7 @@ where impl From> for Body where - S: Stream> + Unpin + 'static, + S: Stream> + 'static, E: Into + 'static, { fn from(s: BodyStream) -> Body { diff --git a/actix-http/src/body/body_stream.rs b/actix-http/src/body/body_stream.rs index 60e33b161..1157bc539 100644 --- a/actix-http/src/body/body_stream.rs +++ b/actix-http/src/body/body_stream.rs @@ -5,21 +5,25 @@ use std::{ use bytes::Bytes; use futures_core::{ready, Stream}; +use pin_project_lite::pin_project; use crate::error::Error; use super::{BodySize, MessageBody}; -/// Streaming response wrapper. -/// -/// Response does not contain `Content-Length` header and appropriate transfer encoding is used. -pub struct BodyStream { - stream: S, +pin_project! { + /// Streaming response wrapper. + /// + /// Response does not contain `Content-Length` header and appropriate transfer encoding is used. + pub struct BodyStream { + #[pin] + stream: S, + } } impl BodyStream where - S: Stream> + Unpin, + S: Stream>, E: Into, { pub fn new(stream: S) -> Self { @@ -29,7 +33,7 @@ where impl MessageBody for BodyStream where - S: Stream> + Unpin, + S: Stream>, E: Into, { fn size(&self) -> BodySize { @@ -46,9 +50,9 @@ where cx: &mut Context<'_>, ) -> Poll>> { loop { - let stream = &mut self.as_mut().stream; + let stream = self.as_mut().project().stream; - let chunk = match ready!(Pin::new(stream).poll_next(cx)) { + let chunk = match ready!(stream.poll_next(cx)) { Some(Ok(ref bytes)) if bytes.is_empty() => continue, opt => opt.map(|res| res.map_err(Into::into)), }; diff --git a/actix-http/src/body/message_body.rs b/actix-http/src/body/message_body.rs index 012329146..ea2cfd22d 100644 --- a/actix-http/src/body/message_body.rs +++ b/actix-http/src/body/message_body.rs @@ -52,6 +52,19 @@ impl MessageBody for Box { } } +impl MessageBody for Pin> { + fn size(&self) -> BodySize { + self.as_ref().size() + } + + fn poll_next( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + ) -> Poll>> { + self.as_mut().poll_next(cx) + } +} + impl MessageBody for Bytes { fn size(&self) -> BodySize { BodySize::Sized(self.len() as u64) diff --git a/actix-http/src/body/sized_stream.rs b/actix-http/src/body/sized_stream.rs index af995a0fb..f648f6f0b 100644 --- a/actix-http/src/body/sized_stream.rs +++ b/actix-http/src/body/sized_stream.rs @@ -5,23 +5,27 @@ use std::{ use bytes::Bytes; use futures_core::{ready, Stream}; +use pin_project_lite::pin_project; use crate::error::Error; use super::{BodySize, MessageBody}; -/// Known sized streaming response wrapper. -/// -/// This body implementation should be used if total size of stream is known. Data get sent as is -/// without using transfer encoding. -pub struct SizedStream { - size: u64, - stream: S, +pin_project! { + /// Known sized streaming response wrapper. + /// + /// This body implementation should be used if total size of stream is known. Data get sent as is + /// without using transfer encoding. + pub struct SizedStream { + size: u64, + #[pin] + stream: S, + } } impl SizedStream where - S: Stream> + Unpin, + S: Stream>, { pub fn new(size: u64, stream: S) -> Self { SizedStream { size, stream } @@ -30,7 +34,7 @@ where impl MessageBody for SizedStream where - S: Stream> + Unpin, + S: Stream>, { fn size(&self) -> BodySize { BodySize::Sized(self.size as u64) @@ -46,9 +50,9 @@ where cx: &mut Context<'_>, ) -> Poll>> { loop { - let stream = &mut self.as_mut().stream; + let stream = self.as_mut().project().stream; - let chunk = match ready!(Pin::new(stream).poll_next(cx)) { + let chunk = match ready!(stream.poll_next(cx)) { Some(Ok(ref bytes)) if bytes.is_empty() => continue, val => val, }; diff --git a/actix-http/src/encoding/encoder.rs b/actix-http/src/encoding/encoder.rs index ee0587fbd..add6ee980 100644 --- a/actix-http/src/encoding/encoder.rs +++ b/actix-http/src/encoding/encoder.rs @@ -92,7 +92,7 @@ impl Encoder { enum EncoderBody { Bytes(Bytes), Stream(#[pin] B), - BoxedStream(Box), + BoxedStream(Pin>), } impl MessageBody for EncoderBody { @@ -117,9 +117,7 @@ impl MessageBody for EncoderBody { } } EncoderBodyProj::Stream(b) => b.poll_next(cx), - EncoderBodyProj::BoxedStream(ref mut b) => { - Pin::new(b.as_mut()).poll_next(cx) - } + EncoderBodyProj::BoxedStream(ref mut b) => b.as_mut().poll_next(cx), } } } diff --git a/actix-http/src/error.rs b/actix-http/src/error.rs index 60a870ecc..01c4beeba 100644 --- a/actix-http/src/error.rs +++ b/actix-http/src/error.rs @@ -62,7 +62,7 @@ pub trait ResponseError: fmt::Debug + fmt::Display { /// Create response for error /// /// Internal server error is generated by default. - fn error_response(&self) -> Response { + fn error_response(&self) -> Response { let mut resp = Response::new(self.status_code()); let mut buf = BytesMut::new(); let _ = write!(Writer(&mut buf), "{}", self); @@ -111,7 +111,7 @@ impl From for Error { } /// Convert `Error` to a `Response` instance -impl From for Response { +impl From for Response { fn from(err: Error) -> Self { Response::from_error(err) } @@ -127,8 +127,8 @@ impl From for Error { } /// Convert Response to a Error -impl From for Error { - fn from(res: Response) -> Error { +impl From> for Error { + fn from(res: Response) -> Error { InternalError::from_response("", res).into() } } @@ -454,7 +454,7 @@ pub struct InternalError { enum InternalErrorType { Status(StatusCode), - Response(RefCell>), + Response(RefCell>>), } impl InternalError { @@ -467,7 +467,7 @@ impl InternalError { } /// Create `InternalError` with predefined `Response`. - pub fn from_response(cause: T, response: Response) -> Self { + pub fn from_response(cause: T, response: Response) -> Self { InternalError { cause, status: InternalErrorType::Response(RefCell::new(Some(response))), @@ -510,7 +510,7 @@ where } } - fn error_response(&self) -> Response { + fn error_response(&self) -> Response { match self.status { InternalErrorType::Status(st) => { let mut res = Response::new(st); @@ -931,11 +931,11 @@ mod tests { #[test] fn test_into_response() { - let resp: Response = ParseError::Incomplete.error_response(); + let resp: Response = ParseError::Incomplete.error_response(); assert_eq!(resp.status(), StatusCode::BAD_REQUEST); let err: HttpError = StatusCode::from_u16(10000).err().unwrap().into(); - let resp: Response = err.error_response(); + let resp: Response = err.error_response(); assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR); } @@ -966,7 +966,7 @@ mod tests { fn test_error_http_response() { let orig = io::Error::new(io::ErrorKind::Other, "other"); let e = Error::from(orig); - let resp: Response = e.into(); + let resp: Response = e.into(); assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR); } @@ -1023,7 +1023,7 @@ mod tests { fn test_internal_error() { let err = InternalError::from_response(ParseError::Method, Response::Ok().into()); - let resp: Response = err.error_response(); + let resp: Response = err.error_response(); assert_eq!(resp.status(), StatusCode::OK); } @@ -1039,121 +1039,121 @@ mod tests { #[test] fn test_error_helpers() { - let r: Response = ErrorBadRequest("err").into(); - assert_eq!(r.status(), StatusCode::BAD_REQUEST); + let res: Response = ErrorBadRequest("err").into(); + assert_eq!(res.status(), StatusCode::BAD_REQUEST); - let r: Response = ErrorUnauthorized("err").into(); - assert_eq!(r.status(), StatusCode::UNAUTHORIZED); + let res: Response = ErrorUnauthorized("err").into(); + assert_eq!(res.status(), StatusCode::UNAUTHORIZED); - let r: Response = ErrorPaymentRequired("err").into(); - assert_eq!(r.status(), StatusCode::PAYMENT_REQUIRED); + let res: Response = ErrorPaymentRequired("err").into(); + assert_eq!(res.status(), StatusCode::PAYMENT_REQUIRED); - let r: Response = ErrorForbidden("err").into(); - assert_eq!(r.status(), StatusCode::FORBIDDEN); + let res: Response = ErrorForbidden("err").into(); + assert_eq!(res.status(), StatusCode::FORBIDDEN); - let r: Response = ErrorNotFound("err").into(); - assert_eq!(r.status(), StatusCode::NOT_FOUND); + let res: Response = ErrorNotFound("err").into(); + assert_eq!(res.status(), StatusCode::NOT_FOUND); - let r: Response = ErrorMethodNotAllowed("err").into(); - assert_eq!(r.status(), StatusCode::METHOD_NOT_ALLOWED); + let res: Response = ErrorMethodNotAllowed("err").into(); + assert_eq!(res.status(), StatusCode::METHOD_NOT_ALLOWED); - let r: Response = ErrorNotAcceptable("err").into(); - assert_eq!(r.status(), StatusCode::NOT_ACCEPTABLE); + let res: Response = ErrorNotAcceptable("err").into(); + assert_eq!(res.status(), StatusCode::NOT_ACCEPTABLE); - let r: Response = ErrorProxyAuthenticationRequired("err").into(); - assert_eq!(r.status(), StatusCode::PROXY_AUTHENTICATION_REQUIRED); + let res: Response = ErrorProxyAuthenticationRequired("err").into(); + assert_eq!(res.status(), StatusCode::PROXY_AUTHENTICATION_REQUIRED); - let r: Response = ErrorRequestTimeout("err").into(); - assert_eq!(r.status(), StatusCode::REQUEST_TIMEOUT); + let res: Response = ErrorRequestTimeout("err").into(); + assert_eq!(res.status(), StatusCode::REQUEST_TIMEOUT); - let r: Response = ErrorConflict("err").into(); - assert_eq!(r.status(), StatusCode::CONFLICT); + let res: Response = ErrorConflict("err").into(); + assert_eq!(res.status(), StatusCode::CONFLICT); - let r: Response = ErrorGone("err").into(); - assert_eq!(r.status(), StatusCode::GONE); + let res: Response = ErrorGone("err").into(); + assert_eq!(res.status(), StatusCode::GONE); - let r: Response = ErrorLengthRequired("err").into(); - assert_eq!(r.status(), StatusCode::LENGTH_REQUIRED); + let res: Response = ErrorLengthRequired("err").into(); + assert_eq!(res.status(), StatusCode::LENGTH_REQUIRED); - let r: Response = ErrorPreconditionFailed("err").into(); - assert_eq!(r.status(), StatusCode::PRECONDITION_FAILED); + let res: Response = ErrorPreconditionFailed("err").into(); + assert_eq!(res.status(), StatusCode::PRECONDITION_FAILED); - let r: Response = ErrorPayloadTooLarge("err").into(); - assert_eq!(r.status(), StatusCode::PAYLOAD_TOO_LARGE); + let res: Response = ErrorPayloadTooLarge("err").into(); + assert_eq!(res.status(), StatusCode::PAYLOAD_TOO_LARGE); - let r: Response = ErrorUriTooLong("err").into(); - assert_eq!(r.status(), StatusCode::URI_TOO_LONG); + let res: Response = ErrorUriTooLong("err").into(); + assert_eq!(res.status(), StatusCode::URI_TOO_LONG); - let r: Response = ErrorUnsupportedMediaType("err").into(); - assert_eq!(r.status(), StatusCode::UNSUPPORTED_MEDIA_TYPE); + let res: Response = ErrorUnsupportedMediaType("err").into(); + assert_eq!(res.status(), StatusCode::UNSUPPORTED_MEDIA_TYPE); - let r: Response = ErrorRangeNotSatisfiable("err").into(); - assert_eq!(r.status(), StatusCode::RANGE_NOT_SATISFIABLE); + let res: Response = ErrorRangeNotSatisfiable("err").into(); + assert_eq!(res.status(), StatusCode::RANGE_NOT_SATISFIABLE); - let r: Response = ErrorExpectationFailed("err").into(); - assert_eq!(r.status(), StatusCode::EXPECTATION_FAILED); + let res: Response = ErrorExpectationFailed("err").into(); + assert_eq!(res.status(), StatusCode::EXPECTATION_FAILED); - let r: Response = ErrorImATeapot("err").into(); - assert_eq!(r.status(), StatusCode::IM_A_TEAPOT); + let res: Response = ErrorImATeapot("err").into(); + assert_eq!(res.status(), StatusCode::IM_A_TEAPOT); - let r: Response = ErrorMisdirectedRequest("err").into(); - assert_eq!(r.status(), StatusCode::MISDIRECTED_REQUEST); + let res: Response = ErrorMisdirectedRequest("err").into(); + assert_eq!(res.status(), StatusCode::MISDIRECTED_REQUEST); - let r: Response = ErrorUnprocessableEntity("err").into(); - assert_eq!(r.status(), StatusCode::UNPROCESSABLE_ENTITY); + let res: Response = ErrorUnprocessableEntity("err").into(); + assert_eq!(res.status(), StatusCode::UNPROCESSABLE_ENTITY); - let r: Response = ErrorLocked("err").into(); - assert_eq!(r.status(), StatusCode::LOCKED); + let res: Response = ErrorLocked("err").into(); + assert_eq!(res.status(), StatusCode::LOCKED); - let r: Response = ErrorFailedDependency("err").into(); - assert_eq!(r.status(), StatusCode::FAILED_DEPENDENCY); + let res: Response = ErrorFailedDependency("err").into(); + assert_eq!(res.status(), StatusCode::FAILED_DEPENDENCY); - let r: Response = ErrorUpgradeRequired("err").into(); - assert_eq!(r.status(), StatusCode::UPGRADE_REQUIRED); + let res: Response = ErrorUpgradeRequired("err").into(); + assert_eq!(res.status(), StatusCode::UPGRADE_REQUIRED); - let r: Response = ErrorPreconditionRequired("err").into(); - assert_eq!(r.status(), StatusCode::PRECONDITION_REQUIRED); + let res: Response = ErrorPreconditionRequired("err").into(); + assert_eq!(res.status(), StatusCode::PRECONDITION_REQUIRED); - let r: Response = ErrorTooManyRequests("err").into(); - assert_eq!(r.status(), StatusCode::TOO_MANY_REQUESTS); + let res: Response = ErrorTooManyRequests("err").into(); + assert_eq!(res.status(), StatusCode::TOO_MANY_REQUESTS); - let r: Response = ErrorRequestHeaderFieldsTooLarge("err").into(); - assert_eq!(r.status(), StatusCode::REQUEST_HEADER_FIELDS_TOO_LARGE); + let res: Response = ErrorRequestHeaderFieldsTooLarge("err").into(); + assert_eq!(res.status(), StatusCode::REQUEST_HEADER_FIELDS_TOO_LARGE); - let r: Response = ErrorUnavailableForLegalReasons("err").into(); - assert_eq!(r.status(), StatusCode::UNAVAILABLE_FOR_LEGAL_REASONS); + let res: Response = ErrorUnavailableForLegalReasons("err").into(); + assert_eq!(res.status(), StatusCode::UNAVAILABLE_FOR_LEGAL_REASONS); - let r: Response = ErrorInternalServerError("err").into(); - assert_eq!(r.status(), StatusCode::INTERNAL_SERVER_ERROR); + let res: Response = ErrorInternalServerError("err").into(); + assert_eq!(res.status(), StatusCode::INTERNAL_SERVER_ERROR); - let r: Response = ErrorNotImplemented("err").into(); - assert_eq!(r.status(), StatusCode::NOT_IMPLEMENTED); + let res: Response = ErrorNotImplemented("err").into(); + assert_eq!(res.status(), StatusCode::NOT_IMPLEMENTED); - let r: Response = ErrorBadGateway("err").into(); - assert_eq!(r.status(), StatusCode::BAD_GATEWAY); + let res: Response = ErrorBadGateway("err").into(); + assert_eq!(res.status(), StatusCode::BAD_GATEWAY); - let r: Response = ErrorServiceUnavailable("err").into(); - assert_eq!(r.status(), StatusCode::SERVICE_UNAVAILABLE); + let res: Response = ErrorServiceUnavailable("err").into(); + assert_eq!(res.status(), StatusCode::SERVICE_UNAVAILABLE); - let r: Response = ErrorGatewayTimeout("err").into(); - assert_eq!(r.status(), StatusCode::GATEWAY_TIMEOUT); + let res: Response = ErrorGatewayTimeout("err").into(); + assert_eq!(res.status(), StatusCode::GATEWAY_TIMEOUT); - let r: Response = ErrorHttpVersionNotSupported("err").into(); - assert_eq!(r.status(), StatusCode::HTTP_VERSION_NOT_SUPPORTED); + let res: Response = ErrorHttpVersionNotSupported("err").into(); + assert_eq!(res.status(), StatusCode::HTTP_VERSION_NOT_SUPPORTED); - let r: Response = ErrorVariantAlsoNegotiates("err").into(); - assert_eq!(r.status(), StatusCode::VARIANT_ALSO_NEGOTIATES); + let res: Response = ErrorVariantAlsoNegotiates("err").into(); + assert_eq!(res.status(), StatusCode::VARIANT_ALSO_NEGOTIATES); - let r: Response = ErrorInsufficientStorage("err").into(); - assert_eq!(r.status(), StatusCode::INSUFFICIENT_STORAGE); + let res: Response = ErrorInsufficientStorage("err").into(); + assert_eq!(res.status(), StatusCode::INSUFFICIENT_STORAGE); - let r: Response = ErrorLoopDetected("err").into(); - assert_eq!(r.status(), StatusCode::LOOP_DETECTED); + let res: Response = ErrorLoopDetected("err").into(); + assert_eq!(res.status(), StatusCode::LOOP_DETECTED); - let r: Response = ErrorNotExtended("err").into(); - assert_eq!(r.status(), StatusCode::NOT_EXTENDED); + let res: Response = ErrorNotExtended("err").into(); + assert_eq!(res.status(), StatusCode::NOT_EXTENDED); - let r: Response = ErrorNetworkAuthenticationRequired("err").into(); - assert_eq!(r.status(), StatusCode::NETWORK_AUTHENTICATION_REQUIRED); + let res: Response = ErrorNetworkAuthenticationRequired("err").into(); + assert_eq!(res.status(), StatusCode::NETWORK_AUTHENTICATION_REQUIRED); } } diff --git a/actix-http/src/h1/dispatcher.rs b/actix-http/src/h1/dispatcher.rs index bba79217a..2e66e0506 100644 --- a/actix-http/src/h1/dispatcher.rs +++ b/actix-http/src/h1/dispatcher.rs @@ -407,7 +407,7 @@ where } // send expect error as response Poll::Ready(Err(err)) => { - let res: Response = err.into().into(); + let res = Response::from_error(err.into()); let (res, body) = res.replace_body(()); self.as_mut().send_response(res, body.into_body())?; } @@ -456,8 +456,7 @@ where // to notify the dispatcher a new state is set and the outer loop // should be continue. Poll::Ready(Err(err)) => { - let err = err.into(); - let res: Response = err.into(); + let res = Response::from_error(err.into()); let (res, body) = res.replace_body(()); return self.send_response(res, body.into_body()); } @@ -477,7 +476,7 @@ where Poll::Pending => Ok(()), // see the comment on ExpectCall state branch's Ready(Err(err)). Poll::Ready(Err(err)) => { - let res: Response = err.into().into(); + let res = Response::from_error(err.into()); let (res, body) = res.replace_body(()); self.send_response(res, body.into_body()) } @@ -979,19 +978,20 @@ mod tests { } } - fn ok_service() -> impl Service { + fn ok_service() -> impl Service, Error = Error> { fn_service(|_req: Request| ready(Ok::<_, Error>(Response::Ok().finish()))) } - fn echo_path_service() -> impl Service { + fn echo_path_service( + ) -> impl Service, Error = Error> { fn_service(|req: Request| { let path = req.path().as_bytes(); ready(Ok::<_, Error>(Response::Ok().body(Body::from_slice(path)))) }) } - fn echo_payload_service() -> impl Service - { + fn echo_payload_service( + ) -> impl Service, Error = Error> { fn_service(|mut req: Request| { Box::pin(async move { use futures_util::stream::StreamExt as _; diff --git a/actix-http/src/h2/dispatcher.rs b/actix-http/src/h2/dispatcher.rs index 6e6cd5a2f..87dd66fe7 100644 --- a/actix-http/src/h2/dispatcher.rs +++ b/actix-http/src/h2/dispatcher.rs @@ -252,8 +252,8 @@ where } } - Err(e) => { - let res: Response = e.into().into(); + Err(err) => { + let res = Response::from_error(err.into()); let (res, body) = res.replace_body(()); let mut send = send.take().unwrap(); diff --git a/actix-http/src/http_codes.rs b/actix-http/src/http_codes.rs index 688a08be5..dc4f964de 100644 --- a/actix-http/src/http_codes.rs +++ b/actix-http/src/http_codes.rs @@ -4,7 +4,10 @@ use http::StatusCode; -use crate::response::{Response, ResponseBuilder}; +use crate::{ + body::Body, + response::{Response, ResponseBuilder}, +}; macro_rules! static_resp { ($name:ident, $status:expr) => { @@ -15,7 +18,7 @@ macro_rules! static_resp { }; } -impl Response { +impl Response { static_resp!(Continue, StatusCode::CONTINUE); static_resp!(SwitchingProtocols, StatusCode::SWITCHING_PROTOCOLS); static_resp!(Processing, StatusCode::PROCESSING); diff --git a/actix-http/src/lib.rs b/actix-http/src/lib.rs index 5dd232491..bba7af4c6 100644 --- a/actix-http/src/lib.rs +++ b/actix-http/src/lib.rs @@ -6,7 +6,6 @@ //! | `openssl` | TLS support via [OpenSSL]. | //! | `rustls` | TLS support via [rustls]. | //! | `compress` | Payload compression support. (Deflate, Gzip & Brotli) | -//! | `secure-cookies` | Adds for secure cookies. Enables `cookies` feature. | //! | `trust-dns` | Use [trust-dns] as the client DNS resolver. | //! //! [OpenSSL]: https://crates.io/crates/openssl diff --git a/actix-http/src/response.rs b/actix-http/src/response.rs index 046c4ca56..0c6272485 100644 --- a/actix-http/src/response.rs +++ b/actix-http/src/response.rs @@ -22,7 +22,7 @@ use crate::{ }; /// An HTTP Response -pub struct Response { +pub struct Response { head: BoxedResponseHead, body: ResponseBody, error: Option, @@ -43,7 +43,7 @@ impl Response { /// Constructs a response #[inline] - pub fn new(status: StatusCode) -> Response { + pub fn new(status: StatusCode) -> Response { Response { head: BoxedResponseHead::new(status), body: ResponseBody::Body(Body::Empty), @@ -53,7 +53,7 @@ impl Response { /// Constructs an error response #[inline] - pub fn from_error(error: Error) -> Response { + pub fn from_error(error: Error) -> Response { let mut resp = error.as_response_error().error_response(); if resp.head.status == StatusCode::INTERNAL_SERVER_ERROR { error!("Internal Server Error: {:?}", error); @@ -238,8 +238,8 @@ impl fmt::Debug for Response { } } -impl Future for Response { - type Output = Result; +impl Future for Response { + type Output = Result, Error>; fn poll(mut self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll { Poll::Ready(Ok(Response { @@ -421,7 +421,7 @@ impl ResponseBuilder { /// /// `ResponseBuilder` can not be used after this call. #[inline] - pub fn body>(&mut self, body: B) -> Response { + pub fn body>(&mut self, body: B) -> Response { self.message_body(body.into()) } @@ -446,7 +446,7 @@ impl ResponseBuilder { /// /// `ResponseBuilder` can not be used after this call. #[inline] - pub fn streaming(&mut self, stream: S) -> Response + pub fn streaming(&mut self, stream: S) -> Response where S: Stream> + Unpin + 'static, E: Into + 'static, @@ -458,7 +458,7 @@ impl ResponseBuilder { /// /// `ResponseBuilder` can not be used after this call. #[inline] - pub fn finish(&mut self) -> Response { + pub fn finish(&mut self) -> Response { self.body(Body::Empty) } @@ -513,7 +513,7 @@ impl<'a> From<&'a ResponseHead> for ResponseBuilder { } impl Future for ResponseBuilder { - type Output = Result; + type Output = Result, Error>; fn poll(mut self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll { Poll::Ready(Ok(self.finish())) @@ -540,7 +540,7 @@ impl fmt::Debug for ResponseBuilder { } /// Helper converters -impl, E: Into> From> for Response { +impl>, E: Into> From> for Response { fn from(res: Result) -> Self { match res { Ok(val) => val.into(), @@ -549,13 +549,13 @@ impl, E: Into> From> for Response { } } -impl From for Response { +impl From for Response { fn from(mut builder: ResponseBuilder) -> Self { builder.finish() } } -impl From<&'static str> for Response { +impl From<&'static str> for Response { fn from(val: &'static str) -> Self { Response::Ok() .content_type(mime::TEXT_PLAIN_UTF_8) @@ -563,7 +563,7 @@ impl From<&'static str> for Response { } } -impl From<&'static [u8]> for Response { +impl From<&'static [u8]> for Response { fn from(val: &'static [u8]) -> Self { Response::Ok() .content_type(mime::APPLICATION_OCTET_STREAM) @@ -571,7 +571,7 @@ impl From<&'static [u8]> for Response { } } -impl From for Response { +impl From for Response { fn from(val: String) -> Self { Response::Ok() .content_type(mime::TEXT_PLAIN_UTF_8) @@ -579,7 +579,7 @@ impl From for Response { } } -impl<'a> From<&'a String> for Response { +impl<'a> From<&'a String> for Response { fn from(val: &'a String) -> Self { Response::Ok() .content_type(mime::TEXT_PLAIN_UTF_8) @@ -587,7 +587,7 @@ impl<'a> From<&'a String> for Response { } } -impl From for Response { +impl From for Response { fn from(val: Bytes) -> Self { Response::Ok() .content_type(mime::APPLICATION_OCTET_STREAM) @@ -595,7 +595,7 @@ impl From for Response { } } -impl From for Response { +impl From for Response { fn from(val: BytesMut) -> Self { Response::Ok() .content_type(mime::APPLICATION_OCTET_STREAM) @@ -653,7 +653,7 @@ mod tests { #[test] fn test_into_response() { - let resp: Response = "test".into(); + let resp: Response = "test".into(); assert_eq!(resp.status(), StatusCode::OK); assert_eq!( resp.headers().get(CONTENT_TYPE).unwrap(), @@ -662,7 +662,7 @@ mod tests { assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.body().get_ref(), b"test"); - let resp: Response = b"test".as_ref().into(); + let resp: Response = b"test".as_ref().into(); assert_eq!(resp.status(), StatusCode::OK); assert_eq!( resp.headers().get(CONTENT_TYPE).unwrap(), @@ -671,7 +671,7 @@ mod tests { assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.body().get_ref(), b"test"); - let resp: Response = "test".to_owned().into(); + let resp: Response = "test".to_owned().into(); assert_eq!(resp.status(), StatusCode::OK); assert_eq!( resp.headers().get(CONTENT_TYPE).unwrap(), @@ -680,7 +680,7 @@ mod tests { assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.body().get_ref(), b"test"); - let resp: Response = (&"test".to_owned()).into(); + let resp: Response = (&"test".to_owned()).into(); assert_eq!(resp.status(), StatusCode::OK); assert_eq!( resp.headers().get(CONTENT_TYPE).unwrap(), @@ -690,7 +690,7 @@ mod tests { assert_eq!(resp.body().get_ref(), b"test"); let b = Bytes::from_static(b"test"); - let resp: Response = b.into(); + let resp: Response = b.into(); assert_eq!(resp.status(), StatusCode::OK); assert_eq!( resp.headers().get(CONTENT_TYPE).unwrap(), @@ -700,7 +700,7 @@ mod tests { assert_eq!(resp.body().get_ref(), b"test"); let b = Bytes::from_static(b"test"); - let resp: Response = b.into(); + let resp: Response = b.into(); assert_eq!(resp.status(), StatusCode::OK); assert_eq!( resp.headers().get(CONTENT_TYPE).unwrap(), @@ -710,7 +710,7 @@ mod tests { assert_eq!(resp.body().get_ref(), b"test"); let b = BytesMut::from("test"); - let resp: Response = b.into(); + let resp: Response = b.into(); assert_eq!(resp.status(), StatusCode::OK); assert_eq!( resp.headers().get(CONTENT_TYPE).unwrap(), @@ -723,7 +723,7 @@ mod tests { #[test] fn test_into_builder() { - let mut resp: Response = "test".into(); + let mut resp: Response = "test".into(); assert_eq!(resp.status(), StatusCode::OK); resp.headers_mut().insert( diff --git a/actix-http/src/ws/mod.rs b/actix-http/src/ws/mod.rs index 1a82ad839..5b18044b2 100644 --- a/actix-http/src/ws/mod.rs +++ b/actix-http/src/ws/mod.rs @@ -9,8 +9,8 @@ use derive_more::{Display, Error, From}; use http::{header, Method, StatusCode}; use crate::{ - error::ResponseError, header::HeaderValue, message::RequestHead, response::Response, - ResponseBuilder, + body::Body, error::ResponseError, header::HeaderValue, message::RequestHead, + response::Response, ResponseBuilder, }; mod codec; @@ -99,7 +99,7 @@ pub enum HandshakeError { } impl ResponseError for HandshakeError { - fn error_response(&self) -> Response { + fn error_response(&self) -> Response { match self { HandshakeError::GetMethodRequired => Response::MethodNotAllowed() .insert_header((header::ALLOW, "GET")) @@ -320,17 +320,17 @@ mod tests { #[test] fn test_wserror_http_response() { - let resp: Response = HandshakeError::GetMethodRequired.error_response(); + let resp = HandshakeError::GetMethodRequired.error_response(); assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED); - let resp: Response = HandshakeError::NoWebsocketUpgrade.error_response(); + let resp = HandshakeError::NoWebsocketUpgrade.error_response(); assert_eq!(resp.status(), StatusCode::BAD_REQUEST); - let resp: Response = HandshakeError::NoConnectionUpgrade.error_response(); + let resp = HandshakeError::NoConnectionUpgrade.error_response(); assert_eq!(resp.status(), StatusCode::BAD_REQUEST); - let resp: Response = HandshakeError::NoVersionHeader.error_response(); + let resp = HandshakeError::NoVersionHeader.error_response(); assert_eq!(resp.status(), StatusCode::BAD_REQUEST); - let resp: Response = HandshakeError::UnsupportedVersion.error_response(); + let resp = HandshakeError::UnsupportedVersion.error_response(); assert_eq!(resp.status(), StatusCode::BAD_REQUEST); - let resp: Response = HandshakeError::BadWebsocketKey.error_response(); + let resp = HandshakeError::BadWebsocketKey.error_response(); assert_eq!(resp.status(), StatusCode::BAD_REQUEST); } } diff --git a/actix-http/tests/test_openssl.rs b/actix-http/tests/test_openssl.rs index c9cfa7d18..dcf05e8d8 100644 --- a/actix-http/tests/test_openssl.rs +++ b/actix-http/tests/test_openssl.rs @@ -4,11 +4,15 @@ extern crate tls_openssl as openssl; use std::io; -use actix_http::error::{ErrorBadRequest, PayloadError}; -use actix_http::http::header::{self, HeaderName, HeaderValue}; -use actix_http::http::{Method, StatusCode, Version}; -use actix_http::HttpMessage; -use actix_http::{body, Error, HttpService, Request, Response}; +use actix_http::{ + body::{Body, SizedStream}, + error::{ErrorBadRequest, PayloadError}, + http::{ + header::{self, HeaderName, HeaderValue}, + Method, StatusCode, Version, + }, + Error, HttpMessage, HttpService, Request, Response, +}; use actix_http_test::test_server; use actix_service::{fn_service, ServiceFactoryExt}; use actix_utils::future::{err, ok, ready}; @@ -328,7 +332,7 @@ async fn test_h2_body_length() { .h2(|_| { let body = once(ok(Bytes::from_static(STR.as_ref()))); ok::<_, ()>( - Response::Ok().body(body::SizedStream::new(STR.len() as u64, body)), + Response::Ok().body(SizedStream::new(STR.len() as u64, body)), ) }) .openssl(tls_config()) @@ -401,7 +405,7 @@ async fn test_h2_response_http_error_handling() { async fn test_h2_service_error() { let mut srv = test_server(move || { HttpService::build() - .h2(|_| err::(ErrorBadRequest("error"))) + .h2(|_| err::, Error>(ErrorBadRequest("error"))) .openssl(tls_config()) .map_err(|_| ()) }) diff --git a/actix-http/tests/test_rustls.rs b/actix-http/tests/test_rustls.rs index 5b8ba6582..538a2b005 100644 --- a/actix-http/tests/test_rustls.rs +++ b/actix-http/tests/test_rustls.rs @@ -2,10 +2,15 @@ extern crate tls_rustls as rustls; -use actix_http::error::PayloadError; -use actix_http::http::header::{self, HeaderName, HeaderValue}; -use actix_http::http::{Method, StatusCode, Version}; -use actix_http::{body, error, Error, HttpService, Request, Response}; +use actix_http::{ + body::{Body, SizedStream}, + error::{self, PayloadError}, + http::{ + header::{self, HeaderName, HeaderValue}, + Method, StatusCode, Version, + }, + Error, HttpService, Request, Response, +}; use actix_http_test::test_server; use actix_service::{fn_factory_with_config, fn_service}; use actix_utils::future::{err, ok}; @@ -344,7 +349,7 @@ async fn test_h2_body_length() { .h2(|_| { let body = once(ok(Bytes::from_static(STR.as_ref()))); ok::<_, ()>( - Response::Ok().body(body::SizedStream::new(STR.len() as u64, body)), + Response::Ok().body(SizedStream::new(STR.len() as u64, body)), ) }) .rustls(tls_config()) @@ -416,7 +421,7 @@ async fn test_h2_response_http_error_handling() { async fn test_h2_service_error() { let mut srv = test_server(move || { HttpService::build() - .h2(|_| err::(error::ErrorBadRequest("error"))) + .h2(|_| err::, Error>(error::ErrorBadRequest("error"))) .rustls(tls_config()) }) .await; @@ -433,7 +438,7 @@ async fn test_h2_service_error() { async fn test_h1_service_error() { let mut srv = test_server(move || { HttpService::build() - .h1(|_| err::(error::ErrorBadRequest("error"))) + .h1(|_| err::, Error>(error::ErrorBadRequest("error"))) .rustls(tls_config()) }) .await; diff --git a/actix-http/tests/test_server.rs b/actix-http/tests/test_server.rs index 9084a597f..80ec0335b 100644 --- a/actix-http/tests/test_server.rs +++ b/actix-http/tests/test_server.rs @@ -13,7 +13,10 @@ use regex::Regex; use actix_http::HttpMessage; use actix_http::{ - body, error, http, http::header, Error, HttpService, KeepAlive, Request, Response, + body::{Body, SizedStream}, + error, http, + http::header, + Error, HttpService, KeepAlive, Request, Response, }; #[actix_rt::test] @@ -539,7 +542,7 @@ async fn test_h1_body_length() { .h1(|_| { let body = once(ok(Bytes::from_static(STR.as_ref()))); ok::<_, ()>( - Response::Ok().body(body::SizedStream::new(STR.len() as u64, body)), + Response::Ok().body(SizedStream::new(STR.len() as u64, body)), ) }) .tcp() @@ -646,7 +649,7 @@ async fn test_h1_response_http_error_handling() { async fn test_h1_service_error() { let mut srv = test_server(|| { HttpService::build() - .h1(|_| err::(error::ErrorBadRequest("error"))) + .h1(|_| err::, _>(error::ErrorBadRequest("error"))) .tcp() }) .await; diff --git a/src/responder.rs b/src/responder.rs index 66c93d257..2348e9276 100644 --- a/src/responder.rs +++ b/src/responder.rs @@ -1,6 +1,7 @@ use std::fmt; use actix_http::{ + body::Body, error::InternalError, http::{header::IntoHeaderPair, Error as HttpError, HeaderMap, StatusCode}, }; @@ -65,7 +66,7 @@ impl Responder for HttpResponse { } } -impl Responder for actix_http::Response { +impl Responder for actix_http::Response { #[inline] fn respond_to(self, _: &HttpRequest) -> HttpResponse { HttpResponse::from(self) diff --git a/src/response.rs b/src/response.rs index ce6739dc8..23244e6a5 100644 --- a/src/response.rs +++ b/src/response.rs @@ -3,6 +3,7 @@ use std::{ convert::TryInto, fmt, future::Future, + mem, pin::Pin, task::{Context, Poll}, }; @@ -287,18 +288,17 @@ impl From> for Response { } impl Future for HttpResponse { - type Output = Result; + type Output = Result, Error>; - fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + fn poll(mut self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll { if let Some(err) = self.error.take() { - eprintln!("httpresponse future error"); return Poll::Ready(Ok(Response::from_error(err).into_body())); } - let res = &mut self.res; - actix_rt::pin!(res); - - res.poll(cx) + Poll::Ready(Ok(mem::replace( + &mut self.res, + Response::new(StatusCode::default()), + ))) } } @@ -680,7 +680,7 @@ impl From for HttpResponse { } } -impl From for Response { +impl From for Response { fn from(mut builder: HttpResponseBuilder) -> Self { builder.finish().into() } From 4442535a45969fbd22639f368f976b265a0639fa Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Tue, 13 Apr 2021 12:44:38 +0100 Subject: [PATCH 49/79] clippy --- actix-http/src/h1/decoder.rs | 7 ++++--- actix-http/src/macros.rs | 1 + src/middleware/logger.rs | 6 +++--- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/actix-http/src/h1/decoder.rs b/actix-http/src/h1/decoder.rs index 93a4b13d2..8aba9f623 100644 --- a/actix-http/src/h1/decoder.rs +++ b/actix-http/src/h1/decoder.rs @@ -1213,8 +1213,9 @@ mod tests { #[test] fn test_parse_chunked_payload_chunk_extension() { let mut buf = BytesMut::from( - &"GET /test HTTP/1.1\r\n\ - transfer-encoding: chunked\r\n\r\n"[..], + "GET /test HTTP/1.1\r\n\ + transfer-encoding: chunked\r\n\ + \r\n", ); let mut reader = MessageDecoder::::default(); @@ -1233,7 +1234,7 @@ mod tests { #[test] fn test_response_http10_read_until_eof() { - let mut buf = BytesMut::from(&"HTTP/1.0 200 Ok\r\n\r\ntest data"[..]); + let mut buf = BytesMut::from("HTTP/1.0 200 Ok\r\n\r\ntest data"); let mut reader = MessageDecoder::::default(); let (_msg, pl) = reader.decode(&mut buf).unwrap().unwrap(); diff --git a/actix-http/src/macros.rs b/actix-http/src/macros.rs index 8973aa39b..714629b43 100644 --- a/actix-http/src/macros.rs +++ b/actix-http/src/macros.rs @@ -70,6 +70,7 @@ macro_rules! downcast { #[cfg(test)] mod tests { + #![allow(clippy::upper_case_acronyms)] trait MB { downcast_get_type_id!(); diff --git a/src/middleware/logger.rs b/src/middleware/logger.rs index 3fd372117..40ed9258f 100644 --- a/src/middleware/logger.rs +++ b/src/middleware/logger.rs @@ -380,7 +380,7 @@ impl Format { results.push(match cap.get(3).unwrap().as_str() { "a" => { if key.as_str() == "r" { - FormatText::RealIPRemoteAddr + FormatText::RealIpRemoteAddr } else { unreachable!() } @@ -434,7 +434,7 @@ enum FormatText { Time, TimeMillis, RemoteAddr, - RealIPRemoteAddr, + RealIpRemoteAddr, UrlPath, RequestHeader(HeaderName), ResponseHeader(HeaderName), @@ -554,7 +554,7 @@ impl FormatText { }; *self = s; } - FormatText::RealIPRemoteAddr => { + FormatText::RealIpRemoteAddr => { let s = if let Some(remote) = req.connection_info().realip_remote_addr() { FormatText::Str(remote.to_string()) } else { From 02ced426fd7ff3515f57917c889373299d15d9d5 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Tue, 13 Apr 2021 13:34:22 +0100 Subject: [PATCH 50/79] add body to_bytes helper (#2158) --- actix-http/CHANGES.md | 2 + actix-http/src/body/body_stream.rs | 46 +++++++++++ actix-http/src/body/mod.rs | 115 +++++++++++++++------------ actix-http/src/body/response_body.rs | 5 +- actix-http/src/body/sized_stream.rs | 46 +++++++++++ 5 files changed, 157 insertions(+), 57 deletions(-) diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 2949c40c3..f0f0a0255 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -3,6 +3,7 @@ ## Unreleased - 2021-xx-xx ### Added * `impl MessageBody for Pin>`. [#2152] +* Helper `body::to_bytes` for async collecting message body into Bytes. [#2158] ### Changes * The type parameter of `Response` no longer has a default. [#2152] @@ -22,6 +23,7 @@ [#2065]: https://github.com/actix/actix-web/pull/2065 [#2148]: https://github.com/actix/actix-web/pull/2148 [#2152]: https://github.com/actix/actix-web/pull/2152 +[#2158]: https://github.com/actix/actix-web/pull/2158 ## 3.0.0-beta.5 - 2021-04-02 diff --git a/actix-http/src/body/body_stream.rs b/actix-http/src/body/body_stream.rs index 1157bc539..b81aeb4c1 100644 --- a/actix-http/src/body/body_stream.rs +++ b/actix-http/src/body/body_stream.rs @@ -61,3 +61,49 @@ where } } } + +#[cfg(test)] +mod tests { + use actix_rt::pin; + use actix_utils::future::poll_fn; + use futures_util::stream; + + use super::*; + use crate::body::to_bytes; + + #[actix_rt::test] + async fn skips_empty_chunks() { + let body = BodyStream::new(stream::iter( + ["1", "", "2"] + .iter() + .map(|&v| Ok(Bytes::from(v)) as Result), + )); + pin!(body); + + assert_eq!( + poll_fn(|cx| body.as_mut().poll_next(cx)) + .await + .unwrap() + .ok(), + Some(Bytes::from("1")), + ); + assert_eq!( + poll_fn(|cx| body.as_mut().poll_next(cx)) + .await + .unwrap() + .ok(), + Some(Bytes::from("2")), + ); + } + + #[actix_rt::test] + async fn read_to_bytes() { + let body = BodyStream::new(stream::iter( + ["1", "", "2"] + .iter() + .map(|&v| Ok(Bytes::from(v)) as Result), + )); + + assert_eq!(to_bytes(body).await.ok(), Some(Bytes::from("12"))); + } +} diff --git a/actix-http/src/body/mod.rs b/actix-http/src/body/mod.rs index f5664e1dc..c298dda11 100644 --- a/actix-http/src/body/mod.rs +++ b/actix-http/src/body/mod.rs @@ -1,5 +1,12 @@ //! Traits and structures to aid consuming and writing HTTP payloads. +use std::task::Poll; + +use actix_rt::pin; +use actix_utils::future::poll_fn; +use bytes::{Bytes, BytesMut}; +use futures_core::ready; + #[allow(clippy::module_inception)] mod body; mod body_stream; @@ -15,6 +22,50 @@ pub use self::response_body::ResponseBody; pub use self::size::BodySize; pub use self::sized_stream::SizedStream; +/// Collects the body produced by a `MessageBody` implementation into `Bytes`. +/// +/// Any errors produced by the body stream are returned immediately. +/// +/// # Examples +/// ``` +/// use actix_http::body::{Body, to_bytes}; +/// use bytes::Bytes; +/// +/// # async fn test_to_bytes() { +/// let body = Body::Empty; +/// let bytes = to_bytes(body).await.unwrap(); +/// assert!(bytes.is_empty()); +/// +/// let body = Body::Bytes(Bytes::from_static(b"123")); +/// let bytes = to_bytes(body).await.unwrap(); +/// assert_eq!(bytes, b"123"[..]); +/// # } +/// ``` +pub async fn to_bytes(body: impl MessageBody) -> Result { + let cap = match body.size() { + BodySize::None | BodySize::Empty | BodySize::Sized(0) => return Ok(Bytes::new()), + BodySize::Sized(size) => size as usize, + BodySize::Stream => 32_768, + }; + + let mut buf = BytesMut::with_capacity(cap); + + pin!(body); + + poll_fn(|cx| loop { + let body = body.as_mut(); + + match ready!(body.poll_next(cx)) { + Some(Ok(bytes)) => buf.extend(bytes), + None => return Poll::Ready(Ok(())), + Some(Err(err)) => return Poll::Ready(Err(err)), + } + }) + .await?; + + Ok(buf.freeze()) +} + #[cfg(test)] mod tests { use std::pin::Pin; @@ -22,7 +73,6 @@ mod tests { use actix_rt::pin; use actix_utils::future::poll_fn; use bytes::{Bytes, BytesMut}; - use futures_util::stream; use super::*; @@ -187,58 +237,6 @@ mod tests { ); } - #[actix_rt::test] - async fn body_stream_skips_empty_chunks() { - let body = BodyStream::new(stream::iter( - ["1", "", "2"] - .iter() - .map(|&v| Ok(Bytes::from(v)) as Result), - )); - pin!(body); - - assert_eq!( - poll_fn(|cx| body.as_mut().poll_next(cx)) - .await - .unwrap() - .ok(), - Some(Bytes::from("1")), - ); - assert_eq!( - poll_fn(|cx| body.as_mut().poll_next(cx)) - .await - .unwrap() - .ok(), - Some(Bytes::from("2")), - ); - } - - mod sized_stream { - use super::*; - - #[actix_rt::test] - async fn skips_empty_chunks() { - let body = SizedStream::new( - 2, - stream::iter(["1", "", "2"].iter().map(|&v| Ok(Bytes::from(v)))), - ); - pin!(body); - assert_eq!( - poll_fn(|cx| body.as_mut().poll_next(cx)) - .await - .unwrap() - .ok(), - Some(Bytes::from("1")), - ); - assert_eq!( - poll_fn(|cx| body.as_mut().poll_next(cx)) - .await - .unwrap() - .ok(), - Some(Bytes::from("2")), - ); - } - } - #[actix_rt::test] async fn test_body_casting() { let mut body = String::from("hello cast"); @@ -252,4 +250,15 @@ mod tests { let not_body = resp_body.downcast_ref::<()>(); assert!(not_body.is_none()); } + + #[actix_rt::test] + async fn test_to_bytes() { + let body = Body::Empty; + let bytes = to_bytes(body).await.unwrap(); + assert!(bytes.is_empty()); + + let body = Body::Bytes(Bytes::from_static(b"123")); + let bytes = to_bytes(body).await.unwrap(); + assert_eq!(bytes, b"123"[..]); + } } diff --git a/actix-http/src/body/response_body.rs b/actix-http/src/body/response_body.rs index 97141e11e..b27112475 100644 --- a/actix-http/src/body/response_body.rs +++ b/actix-http/src/body/response_body.rs @@ -55,10 +55,7 @@ impl MessageBody for ResponseBody { self: Pin<&mut Self>, cx: &mut Context<'_>, ) -> Poll>> { - match self.project() { - ResponseBodyProj::Body(body) => body.poll_next(cx), - ResponseBodyProj::Other(body) => Pin::new(body).poll_next(cx), - } + Stream::poll_next(self, cx) } } diff --git a/actix-http/src/body/sized_stream.rs b/actix-http/src/body/sized_stream.rs index f648f6f0b..f0332fc8f 100644 --- a/actix-http/src/body/sized_stream.rs +++ b/actix-http/src/body/sized_stream.rs @@ -61,3 +61,49 @@ where } } } + +#[cfg(test)] +mod tests { + use actix_rt::pin; + use actix_utils::future::poll_fn; + use futures_util::stream; + + use super::*; + use crate::body::to_bytes; + + #[actix_rt::test] + async fn skips_empty_chunks() { + let body = SizedStream::new( + 2, + stream::iter(["1", "", "2"].iter().map(|&v| Ok(Bytes::from(v)))), + ); + + pin!(body); + + assert_eq!( + poll_fn(|cx| body.as_mut().poll_next(cx)) + .await + .unwrap() + .ok(), + Some(Bytes::from("1")), + ); + + assert_eq!( + poll_fn(|cx| body.as_mut().poll_next(cx)) + .await + .unwrap() + .ok(), + Some(Bytes::from("2")), + ); + } + + #[actix_rt::test] + async fn read_to_bytes() { + let body = SizedStream::new( + 2, + stream::iter(["1", "", "2"].iter().map(|&v| Ok(Bytes::from(v)))), + ); + + assert_eq!(to_bytes(body).await.ok(), Some(Bytes::from("12"))); + } +} From 23e0c9b6e0fe233442fdc94322afc1a8abea43cd Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Wed, 14 Apr 2021 02:00:14 +0100 Subject: [PATCH 51/79] remove http-codes builders from actix-http (#2159) --- actix-http/CHANGES.md | 4 + actix-http/examples/echo.rs | 4 +- actix-http/examples/echo2.rs | 4 +- actix-http/examples/hello-world.rs | 4 +- actix-http/src/error.rs | 3 +- actix-http/src/h1/dispatcher.rs | 24 +- actix-http/src/lib.rs | 1 - actix-http/src/response.rs | 81 ++-- actix-http/src/ws/mod.rs | 40 +- actix-http/tests/test_client.rs | 10 +- actix-http/tests/test_openssl.rs | 24 +- actix-http/tests/test_rustls.rs | 26 +- actix-http/tests/test_server.rs | 52 +- actix-http/tests/test_ws.rs | 2 +- awc/tests/test_ws.rs | 2 +- src/app_service.rs | 9 +- src/resource.rs | 6 +- src/{response.rs => response/builder.rs} | 453 +----------------- .../src => src/response}/http_codes.rs | 23 +- src/response/mod.rs | 10 + src/response/response.rs | 330 +++++++++++++ 21 files changed, 539 insertions(+), 573 deletions(-) rename src/{response.rs => response/builder.rs} (55%) rename {actix-http/src => src/response}/http_codes.rs (90%) create mode 100644 src/response/mod.rs create mode 100644 src/response/response.rs diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index f0f0a0255..17ca7340f 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -3,6 +3,7 @@ ## Unreleased - 2021-xx-xx ### Added * `impl MessageBody for Pin>`. [#2152] +* `Response::{ok, bad_request, not_found, internal_server_error}`. [#2159] * Helper `body::to_bytes` for async collecting message body into Bytes. [#2158] ### Changes @@ -19,10 +20,13 @@ * `ResponseBuilder::json`. [#2148] * `ResponseBuilder::{set_header, header}`. [#2148] * `impl From for Body`. [#2148] +* `Response::build_from`. [#2159] +* Most of the status code builders on `Response`. [#2159] [#2065]: https://github.com/actix/actix-web/pull/2065 [#2148]: https://github.com/actix/actix-web/pull/2148 [#2152]: https://github.com/actix/actix-web/pull/2152 +[#2159]: https://github.com/actix/actix-web/pull/2159 [#2158]: https://github.com/actix/actix-web/pull/2158 diff --git a/actix-http/examples/echo.rs b/actix-http/examples/echo.rs index 176ac5c2b..b2cdb0be1 100644 --- a/actix-http/examples/echo.rs +++ b/actix-http/examples/echo.rs @@ -1,6 +1,6 @@ use std::{env, io}; -use actix_http::{Error, HttpService, Request, Response}; +use actix_http::{http::StatusCode, Error, HttpService, Request, Response}; use actix_server::Server; use bytes::BytesMut; use futures_util::StreamExt as _; @@ -25,7 +25,7 @@ async fn main() -> io::Result<()> { info!("request body: {:?}", body); Ok::<_, Error>( - Response::Ok() + Response::build(StatusCode::OK) .insert_header(( "x-head", HeaderValue::from_static("dummy value!"), diff --git a/actix-http/examples/echo2.rs b/actix-http/examples/echo2.rs index 483a79aac..9acf4bbae 100644 --- a/actix-http/examples/echo2.rs +++ b/actix-http/examples/echo2.rs @@ -1,6 +1,6 @@ use std::{env, io}; -use actix_http::{body::Body, http::HeaderValue}; +use actix_http::{body::Body, http::HeaderValue, http::StatusCode}; use actix_http::{Error, HttpService, Request, Response}; use actix_server::Server; use bytes::BytesMut; @@ -14,7 +14,7 @@ async fn handle_request(mut req: Request) -> Result, Error> { } info!("request body: {:?}", body); - Ok(Response::Ok() + Ok(Response::build(StatusCode::OK) .insert_header(("x-head", HeaderValue::from_static("dummy value!"))) .body(body)) } diff --git a/actix-http/examples/hello-world.rs b/actix-http/examples/hello-world.rs index a99ddae46..85994556d 100644 --- a/actix-http/examples/hello-world.rs +++ b/actix-http/examples/hello-world.rs @@ -1,6 +1,6 @@ use std::{env, io}; -use actix_http::{HttpService, Response}; +use actix_http::{http::StatusCode, HttpService, Response}; use actix_server::Server; use actix_utils::future; use http::header::HeaderValue; @@ -18,7 +18,7 @@ async fn main() -> io::Result<()> { .client_disconnect(1000) .finish(|_req| { info!("{:?}", _req); - let mut res = Response::Ok(); + let mut res = Response::build(StatusCode::OK); res.insert_header(( "x-head", HeaderValue::from_static("dummy value!"), diff --git a/actix-http/src/error.rs b/actix-http/src/error.rs index 01c4beeba..705ae7f06 100644 --- a/actix-http/src/error.rs +++ b/actix-http/src/error.rs @@ -1021,8 +1021,7 @@ mod tests { #[test] fn test_internal_error() { - let err = - InternalError::from_response(ParseError::Method, Response::Ok().into()); + let err = InternalError::from_response(ParseError::Method, Response::ok()); let resp: Response = err.error_response(); assert_eq!(resp.status(), StatusCode::OK); } diff --git a/actix-http/src/h1/dispatcher.rs b/actix-http/src/h1/dispatcher.rs index 2e66e0506..e775846e9 100644 --- a/actix-http/src/h1/dispatcher.rs +++ b/actix-http/src/h1/dispatcher.rs @@ -21,6 +21,7 @@ use crate::body::{Body, BodySize, MessageBody, ResponseBody}; use crate::config::ServiceConfig; use crate::error::{DispatchError, Error}; use crate::error::{ParseError, PayloadError}; +use crate::http::StatusCode; use crate::request::Request; use crate::response::Response; use crate::service::HttpFlow; @@ -562,7 +563,7 @@ where ); this.flags.insert(Flags::READ_DISCONNECT); this.messages.push_back(DispatcherMessage::Error( - Response::InternalServerError().finish().drop_body(), + Response::internal_server_error().drop_body(), )); *this.error = Some(DispatchError::InternalError); break; @@ -575,7 +576,7 @@ where error!("Internal server error: unexpected eof"); this.flags.insert(Flags::READ_DISCONNECT); this.messages.push_back(DispatcherMessage::Error( - Response::InternalServerError().finish().drop_body(), + Response::internal_server_error().drop_body(), )); *this.error = Some(DispatchError::InternalError); break; @@ -598,7 +599,8 @@ where } // Requests overflow buffer size should be responded with 431 this.messages.push_back(DispatcherMessage::Error( - Response::RequestHeaderFieldsTooLarge().finish().drop_body(), + Response::new(StatusCode::REQUEST_HEADER_FIELDS_TOO_LARGE) + .drop_body(), )); this.flags.insert(Flags::READ_DISCONNECT); *this.error = Some(ParseError::TooLarge.into()); @@ -611,7 +613,7 @@ where // Malformed requests should be responded with 400 this.messages.push_back(DispatcherMessage::Error( - Response::BadRequest().finish().drop_body(), + Response::bad_request().drop_body(), )); this.flags.insert(Flags::READ_DISCONNECT); *this.error = Some(err.into()); @@ -684,7 +686,8 @@ where if !this.flags.contains(Flags::STARTED) { trace!("Slow request timeout"); let _ = self.as_mut().send_response( - Response::RequestTimeout().finish().drop_body(), + Response::new(StatusCode::REQUEST_TIMEOUT) + .drop_body(), ResponseBody::Other(Body::Empty), ); this = self.project(); @@ -951,6 +954,7 @@ mod tests { use actix_service::fn_service; use actix_utils::future::{ready, Ready}; + use bytes::Bytes; use futures_util::future::lazy; use super::*; @@ -979,19 +983,21 @@ mod tests { } fn ok_service() -> impl Service, Error = Error> { - fn_service(|_req: Request| ready(Ok::<_, Error>(Response::Ok().finish()))) + fn_service(|_req: Request| ready(Ok::<_, Error>(Response::ok()))) } fn echo_path_service( ) -> impl Service, Error = Error> { fn_service(|req: Request| { let path = req.path().as_bytes(); - ready(Ok::<_, Error>(Response::Ok().body(Body::from_slice(path)))) + ready(Ok::<_, Error>( + Response::ok().set_body(Body::from_slice(path)), + )) }) } fn echo_payload_service( - ) -> impl Service, Error = Error> { + ) -> impl Service, Error = Error> { fn_service(|mut req: Request| { Box::pin(async move { use futures_util::stream::StreamExt as _; @@ -1002,7 +1008,7 @@ mod tests { body.extend_from_slice(chunk.unwrap().chunk()) } - Ok::<_, Error>(Response::Ok().body(body)) + Ok::<_, Error>(Response::ok().set_body(body.freeze())) }) }) } diff --git a/actix-http/src/lib.rs b/actix-http/src/lib.rs index bba7af4c6..3125e3874 100644 --- a/actix-http/src/lib.rs +++ b/actix-http/src/lib.rs @@ -37,7 +37,6 @@ pub mod encoding; mod extensions; mod header; mod helpers; -mod http_codes; mod http_message; mod message; mod payload; diff --git a/actix-http/src/response.rs b/actix-http/src/response.rs index 0c6272485..7f73538fc 100644 --- a/actix-http/src/response.rs +++ b/actix-http/src/response.rs @@ -29,18 +29,6 @@ pub struct Response { } impl Response { - /// Create HTTP response builder with specific status. - #[inline] - pub fn build(status: StatusCode) -> ResponseBuilder { - ResponseBuilder::new(status) - } - - /// Create HTTP response builder - #[inline] - pub fn build_from>(source: T) -> ResponseBuilder { - source.into() - } - /// Constructs a response #[inline] pub fn new(status: StatusCode) -> Response { @@ -51,6 +39,41 @@ impl Response { } } + /// Create HTTP response builder with specific status. + #[inline] + pub fn build(status: StatusCode) -> ResponseBuilder { + ResponseBuilder::new(status) + } + + // just a couple frequently used shortcuts + // this list should not grow larger than a few + + /// Creates a new response with status 200 OK. + #[inline] + pub fn ok() -> Response { + Response::new(StatusCode::OK) + } + + /// Creates a new response with status 400 Bad Request. + #[inline] + pub fn bad_request() -> Response { + Response::new(StatusCode::BAD_REQUEST) + } + + /// Creates a new response with status 404 Not Found. + #[inline] + pub fn not_found() -> Response { + Response::new(StatusCode::NOT_FOUND) + } + + /// Creates a new response with status 500 Internal Server Error. + #[inline] + pub fn internal_server_error() -> Response { + Response::new(StatusCode::INTERNAL_SERVER_ERROR) + } + + // end shortcuts + /// Constructs an error response #[inline] pub fn from_error(error: Error) -> Response { @@ -281,9 +304,9 @@ impl ResponseBuilder { /// /// ``` /// # use actix_http::Response; - /// use actix_http::http::header; + /// use actix_http::http::{header, StatusCode}; /// - /// Response::Ok() + /// Response::build(StatusCode::OK) /// .insert_header((header::CONTENT_TYPE, mime::APPLICATION_JSON)) /// .insert_header(("X-TEST", "value")) /// .finish(); @@ -308,9 +331,9 @@ impl ResponseBuilder { /// /// ``` /// # use actix_http::Response; - /// use actix_http::http::header; + /// use actix_http::http::{header, StatusCode}; /// - /// Response::Ok() + /// Response::build(StatusCode::OK) /// .append_header((header::CONTENT_TYPE, mime::APPLICATION_JSON)) /// .append_header(("X-TEST", "value1")) /// .append_header(("X-TEST", "value2")) @@ -557,7 +580,7 @@ impl From for Response { impl From<&'static str> for Response { fn from(val: &'static str) -> Self { - Response::Ok() + Response::build(StatusCode::OK) .content_type(mime::TEXT_PLAIN_UTF_8) .body(val) } @@ -565,7 +588,7 @@ impl From<&'static str> for Response { impl From<&'static [u8]> for Response { fn from(val: &'static [u8]) -> Self { - Response::Ok() + Response::build(StatusCode::OK) .content_type(mime::APPLICATION_OCTET_STREAM) .body(val) } @@ -573,7 +596,7 @@ impl From<&'static [u8]> for Response { impl From for Response { fn from(val: String) -> Self { - Response::Ok() + Response::build(StatusCode::OK) .content_type(mime::TEXT_PLAIN_UTF_8) .body(val) } @@ -581,7 +604,7 @@ impl From for Response { impl<'a> From<&'a String> for Response { fn from(val: &'a String) -> Self { - Response::Ok() + Response::build(StatusCode::OK) .content_type(mime::TEXT_PLAIN_UTF_8) .body(val) } @@ -589,7 +612,7 @@ impl<'a> From<&'a String> for Response { impl From for Response { fn from(val: Bytes) -> Self { - Response::Ok() + Response::build(StatusCode::OK) .content_type(mime::APPLICATION_OCTET_STREAM) .body(val) } @@ -597,7 +620,7 @@ impl From for Response { impl From for Response { fn from(val: BytesMut) -> Self { - Response::Ok() + Response::build(StatusCode::OK) .content_type(mime::APPLICATION_OCTET_STREAM) .body(val) } @@ -611,7 +634,7 @@ mod tests { #[test] fn test_debug() { - let resp = Response::Ok() + let resp = Response::build(StatusCode::OK) .append_header((COOKIE, HeaderValue::from_static("cookie1=value1; "))) .append_header((COOKIE, HeaderValue::from_static("cookie2=value2; "))) .finish(); @@ -621,7 +644,9 @@ mod tests { #[test] fn test_basic_builder() { - let resp = Response::Ok().insert_header(("X-TEST", "value")).finish(); + let resp = Response::build(StatusCode::OK) + .insert_header(("X-TEST", "value")) + .finish(); assert_eq!(resp.status(), StatusCode::OK); } @@ -741,7 +766,7 @@ mod tests { #[test] fn response_builder_header_insert_kv() { - let mut res = Response::Ok(); + let mut res = Response::build(StatusCode::OK); res.insert_header(("Content-Type", "application/octet-stream")); let res = res.finish(); @@ -753,7 +778,7 @@ mod tests { #[test] fn response_builder_header_insert_typed() { - let mut res = Response::Ok(); + let mut res = Response::build(StatusCode::OK); res.insert_header((header::CONTENT_TYPE, mime::APPLICATION_OCTET_STREAM)); let res = res.finish(); @@ -765,7 +790,7 @@ mod tests { #[test] fn response_builder_header_append_kv() { - let mut res = Response::Ok(); + let mut res = Response::build(StatusCode::OK); res.append_header(("Content-Type", "application/octet-stream")); res.append_header(("Content-Type", "application/json")); let res = res.finish(); @@ -778,7 +803,7 @@ mod tests { #[test] fn response_builder_header_append_typed() { - let mut res = Response::Ok(); + let mut res = Response::build(StatusCode::OK); res.append_header((header::CONTENT_TYPE, mime::APPLICATION_OCTET_STREAM)); res.append_header((header::CONTENT_TYPE, mime::APPLICATION_JSON)); let res = res.finish(); diff --git a/actix-http/src/ws/mod.rs b/actix-http/src/ws/mod.rs index 5b18044b2..22df2b4ff 100644 --- a/actix-http/src/ws/mod.rs +++ b/actix-http/src/ws/mod.rs @@ -101,29 +101,37 @@ pub enum HandshakeError { impl ResponseError for HandshakeError { fn error_response(&self) -> Response { match self { - HandshakeError::GetMethodRequired => Response::MethodNotAllowed() - .insert_header((header::ALLOW, "GET")) - .finish(), + HandshakeError::GetMethodRequired => { + Response::build(StatusCode::METHOD_NOT_ALLOWED) + .insert_header((header::ALLOW, "GET")) + .finish() + } - HandshakeError::NoWebsocketUpgrade => Response::BadRequest() - .reason("No WebSocket Upgrade header found") - .finish(), + HandshakeError::NoWebsocketUpgrade => { + Response::build(StatusCode::BAD_REQUEST) + .reason("No WebSocket Upgrade header found") + .finish() + } - HandshakeError::NoConnectionUpgrade => Response::BadRequest() - .reason("No Connection upgrade") - .finish(), + HandshakeError::NoConnectionUpgrade => { + Response::build(StatusCode::BAD_REQUEST) + .reason("No Connection upgrade") + .finish() + } - HandshakeError::NoVersionHeader => Response::BadRequest() + HandshakeError::NoVersionHeader => Response::build(StatusCode::BAD_REQUEST) .reason("WebSocket version header is required") .finish(), - HandshakeError::UnsupportedVersion => Response::BadRequest() - .reason("Unsupported WebSocket version") - .finish(), - - HandshakeError::BadWebsocketKey => { - Response::BadRequest().reason("Handshake error").finish() + HandshakeError::UnsupportedVersion => { + Response::build(StatusCode::BAD_REQUEST) + .reason("Unsupported WebSocket version") + .finish() } + + HandshakeError::BadWebsocketKey => Response::build(StatusCode::BAD_REQUEST) + .reason("Handshake error") + .finish(), } } } diff --git a/actix-http/tests/test_client.rs b/actix-http/tests/test_client.rs index b5f8d54b9..0a06d90e5 100644 --- a/actix-http/tests/test_client.rs +++ b/actix-http/tests/test_client.rs @@ -33,7 +33,7 @@ const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ async fn test_h1_v2() { let srv = test_server(move || { HttpService::build() - .finish(|_| future::ok::<_, ()>(Response::Ok().body(STR))) + .finish(|_| future::ok::<_, ()>(Response::ok().set_body(STR))) .tcp() }) .await; @@ -61,7 +61,7 @@ async fn test_h1_v2() { async fn test_connection_close() { let srv = test_server(move || { HttpService::build() - .finish(|_| future::ok::<_, ()>(Response::Ok().body(STR))) + .finish(|_| future::ok::<_, ()>(Response::ok().set_body(STR))) .tcp() .map(|_| ()) }) @@ -77,9 +77,9 @@ async fn test_with_query_parameter() { HttpService::build() .finish(|req: Request| { if req.uri().query().unwrap().contains("qp=") { - future::ok::<_, ()>(Response::Ok().finish()) + future::ok::<_, ()>(Response::ok()) } else { - future::ok::<_, ()>(Response::BadRequest().finish()) + future::ok::<_, ()>(Response::bad_request()) } }) .tcp() @@ -112,7 +112,7 @@ async fn test_h1_expect() { let str = std::str::from_utf8(&buf).unwrap(); assert_eq!(str, "expect body"); - Ok::<_, ()>(Response::Ok().finish()) + Ok::<_, ()>(Response::ok()) }) .tcp() }) diff --git a/actix-http/tests/test_openssl.rs b/actix-http/tests/test_openssl.rs index dcf05e8d8..7cbd58518 100644 --- a/actix-http/tests/test_openssl.rs +++ b/actix-http/tests/test_openssl.rs @@ -71,7 +71,7 @@ fn tls_config() -> SslAcceptor { async fn test_h2() -> io::Result<()> { let srv = test_server(move || { HttpService::build() - .h2(|_| ok::<_, Error>(Response::Ok().finish())) + .h2(|_| ok::<_, Error>(Response::ok())) .openssl(tls_config()) .map_err(|_| ()) }) @@ -89,7 +89,7 @@ async fn test_h2_1() -> io::Result<()> { .finish(|req: Request| { assert!(req.peer_addr().is_some()); assert_eq!(req.version(), Version::HTTP_2); - ok::<_, Error>(Response::Ok().finish()) + ok::<_, Error>(Response::ok()) }) .openssl(tls_config()) .map_err(|_| ()) @@ -108,7 +108,7 @@ async fn test_h2_body() -> io::Result<()> { HttpService::build() .h2(|mut req: Request<_>| async move { let body = load_body(req.take_payload()).await?; - Ok::<_, Error>(Response::Ok().body(body)) + Ok::<_, Error>(Response::ok().set_body(body)) }) .openssl(tls_config()) .map_err(|_| ()) @@ -186,7 +186,7 @@ async fn test_h2_headers() { let mut srv = test_server(move || { let data = data.clone(); HttpService::build().h2(move |_| { - let mut builder = Response::Ok(); + let mut builder = Response::build(StatusCode::OK); for idx in 0..90 { builder.insert_header( (format!("X-TEST-{}", idx).as_str(), @@ -245,7 +245,7 @@ const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ async fn test_h2_body2() { let mut srv = test_server(move || { HttpService::build() - .h2(|_| ok::<_, ()>(Response::Ok().body(STR))) + .h2(|_| ok::<_, ()>(Response::ok().set_body(STR))) .openssl(tls_config()) .map_err(|_| ()) }) @@ -263,7 +263,7 @@ async fn test_h2_body2() { async fn test_h2_head_empty() { let mut srv = test_server(move || { HttpService::build() - .finish(|_| ok::<_, ()>(Response::Ok().body(STR))) + .finish(|_| ok::<_, ()>(Response::ok().set_body(STR))) .openssl(tls_config()) .map_err(|_| ()) }) @@ -287,7 +287,7 @@ async fn test_h2_head_empty() { async fn test_h2_head_binary() { let mut srv = test_server(move || { HttpService::build() - .h2(|_| ok::<_, ()>(Response::Ok().body(STR))) + .h2(|_| ok::<_, ()>(Response::ok().set_body(STR))) .openssl(tls_config()) .map_err(|_| ()) }) @@ -310,7 +310,7 @@ async fn test_h2_head_binary() { async fn test_h2_head_binary2() { let srv = test_server(move || { HttpService::build() - .h2(|_| ok::<_, ()>(Response::Ok().body(STR))) + .h2(|_| ok::<_, ()>(Response::ok().set_body(STR))) .openssl(tls_config()) .map_err(|_| ()) }) @@ -332,7 +332,7 @@ async fn test_h2_body_length() { .h2(|_| { let body = once(ok(Bytes::from_static(STR.as_ref()))); ok::<_, ()>( - Response::Ok().body(SizedStream::new(STR.len() as u64, body)), + Response::ok().set_body(SizedStream::new(STR.len() as u64, body)), ) }) .openssl(tls_config()) @@ -355,7 +355,7 @@ async fn test_h2_body_chunked_explicit() { .h2(|_| { let body = once(ok::<_, Error>(Bytes::from_static(STR.as_ref()))); ok::<_, ()>( - Response::Ok() + Response::build(StatusCode::OK) .insert_header((header::TRANSFER_ENCODING, "chunked")) .streaming(body), ) @@ -383,7 +383,7 @@ async fn test_h2_response_http_error_handling() { .h2(fn_service(|_| { let broken_header = Bytes::from_static(b"\0\0\0"); ok::<_, ()>( - Response::Ok() + Response::build(StatusCode::OK) .insert_header((header::CONTENT_TYPE, broken_header)) .body(STR), ) @@ -428,7 +428,7 @@ async fn test_h2_on_connect() { }) .h2(|req: Request| { assert!(req.extensions().contains::()); - ok::<_, ()>(Response::Ok().finish()) + ok::<_, ()>(Response::ok()) }) .openssl(tls_config()) .map_err(|_| ()) diff --git a/actix-http/tests/test_rustls.rs b/actix-http/tests/test_rustls.rs index 538a2b005..a122ab847 100644 --- a/actix-http/tests/test_rustls.rs +++ b/actix-http/tests/test_rustls.rs @@ -56,7 +56,7 @@ fn tls_config() -> RustlsServerConfig { async fn test_h1() -> io::Result<()> { let srv = test_server(move || { HttpService::build() - .h1(|_| ok::<_, Error>(Response::Ok().finish())) + .h1(|_| ok::<_, Error>(Response::ok())) .rustls(tls_config()) }) .await; @@ -70,7 +70,7 @@ async fn test_h1() -> io::Result<()> { async fn test_h2() -> io::Result<()> { let srv = test_server(move || { HttpService::build() - .h2(|_| ok::<_, Error>(Response::Ok().finish())) + .h2(|_| ok::<_, Error>(Response::ok())) .rustls(tls_config()) }) .await; @@ -87,7 +87,7 @@ async fn test_h1_1() -> io::Result<()> { .h1(|req: Request| { assert!(req.peer_addr().is_some()); assert_eq!(req.version(), Version::HTTP_11); - ok::<_, Error>(Response::Ok().finish()) + ok::<_, Error>(Response::ok()) }) .rustls(tls_config()) }) @@ -105,7 +105,7 @@ async fn test_h2_1() -> io::Result<()> { .finish(|req: Request| { assert!(req.peer_addr().is_some()); assert_eq!(req.version(), Version::HTTP_2); - ok::<_, Error>(Response::Ok().finish()) + ok::<_, Error>(Response::ok()) }) .rustls(tls_config()) }) @@ -123,7 +123,7 @@ async fn test_h2_body1() -> io::Result<()> { HttpService::build() .h2(|mut req: Request<_>| async move { let body = load_body(req.take_payload()).await?; - Ok::<_, Error>(Response::Ok().body(body)) + Ok::<_, Error>(Response::ok().set_body(body)) }) .rustls(tls_config()) }) @@ -199,7 +199,7 @@ async fn test_h2_headers() { let mut srv = test_server(move || { let data = data.clone(); HttpService::build().h2(move |_| { - let mut config = Response::Ok(); + let mut config = Response::build(StatusCode::OK); for idx in 0..90 { config.insert_header(( format!("X-TEST-{}", idx).as_str(), @@ -257,7 +257,7 @@ const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ async fn test_h2_body2() { let mut srv = test_server(move || { HttpService::build() - .h2(|_| ok::<_, ()>(Response::Ok().body(STR))) + .h2(|_| ok::<_, ()>(Response::ok().set_body(STR))) .rustls(tls_config()) }) .await; @@ -274,7 +274,7 @@ async fn test_h2_body2() { async fn test_h2_head_empty() { let mut srv = test_server(move || { HttpService::build() - .finish(|_| ok::<_, ()>(Response::Ok().body(STR))) + .finish(|_| ok::<_, ()>(Response::ok().set_body(STR))) .rustls(tls_config()) }) .await; @@ -300,7 +300,7 @@ async fn test_h2_head_empty() { async fn test_h2_head_binary() { let mut srv = test_server(move || { HttpService::build() - .h2(|_| ok::<_, ()>(Response::Ok().body(STR))) + .h2(|_| ok::<_, ()>(Response::ok().set_body(STR))) .rustls(tls_config()) }) .await; @@ -325,7 +325,7 @@ async fn test_h2_head_binary() { async fn test_h2_head_binary2() { let srv = test_server(move || { HttpService::build() - .h2(|_| ok::<_, ()>(Response::Ok().body(STR))) + .h2(|_| ok::<_, ()>(Response::ok().set_body(STR))) .rustls(tls_config()) }) .await; @@ -349,7 +349,7 @@ async fn test_h2_body_length() { .h2(|_| { let body = once(ok(Bytes::from_static(STR.as_ref()))); ok::<_, ()>( - Response::Ok().body(SizedStream::new(STR.len() as u64, body)), + Response::ok().set_body(SizedStream::new(STR.len() as u64, body)), ) }) .rustls(tls_config()) @@ -371,7 +371,7 @@ async fn test_h2_body_chunked_explicit() { .h2(|_| { let body = once(ok::<_, Error>(Bytes::from_static(STR.as_ref()))); ok::<_, ()>( - Response::Ok() + Response::build(StatusCode::OK) .insert_header((header::TRANSFER_ENCODING, "chunked")) .streaming(body), ) @@ -399,7 +399,7 @@ async fn test_h2_response_http_error_handling() { ok::<_, ()>(fn_service(|_| { let broken_header = Bytes::from_static(b"\0\0\0"); ok::<_, ()>( - Response::Ok() + Response::build(StatusCode::OK) .insert_header((http::header::CONTENT_TYPE, broken_header)) .body(STR), ) diff --git a/actix-http/tests/test_server.rs b/actix-http/tests/test_server.rs index 80ec0335b..9b8b039c3 100644 --- a/actix-http/tests/test_server.rs +++ b/actix-http/tests/test_server.rs @@ -14,8 +14,8 @@ use regex::Regex; use actix_http::HttpMessage; use actix_http::{ body::{Body, SizedStream}, - error, http, - http::header, + error, + http::{self, header, StatusCode}, Error, HttpService, KeepAlive, Request, Response, }; @@ -28,7 +28,7 @@ async fn test_h1() { .client_disconnect(1000) .h1(|req: Request| { assert!(req.peer_addr().is_some()); - ok::<_, ()>(Response::Ok().finish()) + ok::<_, ()>(Response::ok()) }) .tcp() }) @@ -48,7 +48,7 @@ async fn test_h1_2() { .finish(|req: Request| { assert!(req.peer_addr().is_some()); assert_eq!(req.version(), http::Version::HTTP_11); - ok::<_, ()>(Response::Ok().finish()) + ok::<_, ()>(Response::ok()) }) .tcp() }) @@ -69,7 +69,7 @@ async fn test_expect_continue() { err(error::ErrorPreconditionFailed("error")) } })) - .finish(|_| ok::<_, ()>(Response::Ok().finish())) + .finish(|_| ok::<_, ()>(Response::ok())) .tcp() }) .await; @@ -100,7 +100,7 @@ async fn test_expect_continue_h1() { } }) })) - .h1(fn_service(|_| ok::<_, ()>(Response::Ok().finish()))) + .h1(fn_service(|_| ok::<_, ()>(Response::ok()))) .tcp() }) .await; @@ -134,7 +134,9 @@ async fn test_chunked_payload() { }) .fold(0usize, |acc, chunk| ready(acc + chunk.len())) .map(|req_size| { - Ok::<_, Error>(Response::Ok().body(format!("size={}", req_size))) + Ok::<_, Error>( + Response::ok().set_body(format!("size={}", req_size)), + ) }) })) .tcp() @@ -179,7 +181,7 @@ async fn test_slow_request() { let srv = test_server(|| { HttpService::build() .client_timeout(100) - .finish(|_| ok::<_, ()>(Response::Ok().finish())) + .finish(|_| ok::<_, ()>(Response::ok())) .tcp() }) .await; @@ -195,7 +197,7 @@ async fn test_slow_request() { async fn test_http1_malformed_request() { let srv = test_server(|| { HttpService::build() - .h1(|_| ok::<_, ()>(Response::Ok().finish())) + .h1(|_| ok::<_, ()>(Response::ok())) .tcp() }) .await; @@ -211,7 +213,7 @@ async fn test_http1_malformed_request() { async fn test_http1_keepalive() { let srv = test_server(|| { HttpService::build() - .h1(|_| ok::<_, ()>(Response::Ok().finish())) + .h1(|_| ok::<_, ()>(Response::ok())) .tcp() }) .await; @@ -233,7 +235,7 @@ async fn test_http1_keepalive_timeout() { let srv = test_server(|| { HttpService::build() .keep_alive(1) - .h1(|_| ok::<_, ()>(Response::Ok().finish())) + .h1(|_| ok::<_, ()>(Response::ok())) .tcp() }) .await; @@ -254,7 +256,7 @@ async fn test_http1_keepalive_timeout() { async fn test_http1_keepalive_close() { let srv = test_server(|| { HttpService::build() - .h1(|_| ok::<_, ()>(Response::Ok().finish())) + .h1(|_| ok::<_, ()>(Response::ok())) .tcp() }) .await; @@ -275,7 +277,7 @@ async fn test_http1_keepalive_close() { async fn test_http10_keepalive_default_close() { let srv = test_server(|| { HttpService::build() - .h1(|_| ok::<_, ()>(Response::Ok().finish())) + .h1(|_| ok::<_, ()>(Response::ok())) .tcp() }) .await; @@ -295,7 +297,7 @@ async fn test_http10_keepalive_default_close() { async fn test_http10_keepalive() { let srv = test_server(|| { HttpService::build() - .h1(|_| ok::<_, ()>(Response::Ok().finish())) + .h1(|_| ok::<_, ()>(Response::ok())) .tcp() }) .await; @@ -323,7 +325,7 @@ async fn test_http1_keepalive_disabled() { let srv = test_server(|| { HttpService::build() .keep_alive(KeepAlive::Disabled) - .h1(|_| ok::<_, ()>(Response::Ok().finish())) + .h1(|_| ok::<_, ()>(Response::ok())) .tcp() }) .await; @@ -394,7 +396,7 @@ async fn test_h1_headers() { let mut srv = test_server(move || { let data = data.clone(); HttpService::build().h1(move |_| { - let mut builder = Response::Ok(); + let mut builder = Response::build(StatusCode::OK); for idx in 0..90 { builder.insert_header(( format!("X-TEST-{}", idx).as_str(), @@ -451,7 +453,7 @@ const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ async fn test_h1_body() { let mut srv = test_server(|| { HttpService::build() - .h1(|_| ok::<_, ()>(Response::Ok().body(STR))) + .h1(|_| ok::<_, ()>(Response::ok().set_body(STR))) .tcp() }) .await; @@ -468,7 +470,7 @@ async fn test_h1_body() { async fn test_h1_head_empty() { let mut srv = test_server(|| { HttpService::build() - .h1(|_| ok::<_, ()>(Response::Ok().body(STR))) + .h1(|_| ok::<_, ()>(Response::ok().set_body(STR))) .tcp() }) .await; @@ -493,7 +495,7 @@ async fn test_h1_head_empty() { async fn test_h1_head_binary() { let mut srv = test_server(|| { HttpService::build() - .h1(|_| ok::<_, ()>(Response::Ok().body(STR))) + .h1(|_| ok::<_, ()>(Response::ok().set_body(STR))) .tcp() }) .await; @@ -518,7 +520,7 @@ async fn test_h1_head_binary() { async fn test_h1_head_binary2() { let srv = test_server(|| { HttpService::build() - .h1(|_| ok::<_, ()>(Response::Ok().body(STR))) + .h1(|_| ok::<_, ()>(Response::ok().set_body(STR))) .tcp() }) .await; @@ -542,7 +544,7 @@ async fn test_h1_body_length() { .h1(|_| { let body = once(ok(Bytes::from_static(STR.as_ref()))); ok::<_, ()>( - Response::Ok().body(SizedStream::new(STR.len() as u64, body)), + Response::ok().set_body(SizedStream::new(STR.len() as u64, body)), ) }) .tcp() @@ -564,7 +566,7 @@ async fn test_h1_body_chunked_explicit() { .h1(|_| { let body = once(ok::<_, Error>(Bytes::from_static(STR.as_ref()))); ok::<_, ()>( - Response::Ok() + Response::build(StatusCode::OK) .insert_header((header::TRANSFER_ENCODING, "chunked")) .streaming(body), ) @@ -598,7 +600,7 @@ async fn test_h1_body_chunked_implicit() { HttpService::build() .h1(|_| { let body = once(ok::<_, Error>(Bytes::from_static(STR.as_ref()))); - ok::<_, ()>(Response::Ok().streaming(body)) + ok::<_, ()>(Response::build(StatusCode::OK).streaming(body)) }) .tcp() }) @@ -628,7 +630,7 @@ async fn test_h1_response_http_error_handling() { .h1(fn_service(|_| { let broken_header = Bytes::from_static(b"\0\0\0"); ok::<_, ()>( - Response::Ok() + Response::build(StatusCode::OK) .insert_header((http::header::CONTENT_TYPE, broken_header)) .body(STR), ) @@ -671,7 +673,7 @@ async fn test_h1_on_connect() { }) .h1(|req: Request| { assert!(req.extensions().contains::()); - ok::<_, ()>(Response::Ok().finish()) + ok::<_, ()>(Response::ok()) }) .tcp() }) diff --git a/actix-http/tests/test_ws.rs b/actix-http/tests/test_ws.rs index 9a2e57711..72870bab5 100644 --- a/actix-http/tests/test_ws.rs +++ b/actix-http/tests/test_ws.rs @@ -91,7 +91,7 @@ async fn test_simple() { let ws_service = ws_service.clone(); HttpService::build() .upgrade(fn_factory(move || future::ok::<_, ()>(ws_service.clone()))) - .finish(|_| future::ok::<_, ()>(Response::NotFound())) + .finish(|_| future::ok::<_, ()>(Response::not_found())) .tcp() } }) diff --git a/awc/tests/test_ws.rs b/awc/tests/test_ws.rs index 3f19ac4e8..bfc81afbc 100644 --- a/awc/tests/test_ws.rs +++ b/awc/tests/test_ws.rs @@ -36,7 +36,7 @@ async fn test_simple() { ws::Dispatcher::with(framed, ws_service).await } }) - .finish(|_| ok::<_, Error>(Response::NotFound())) + .finish(|_| ok::<_, Error>(Response::not_found())) .tcp() }) .await; diff --git a/src/app_service.rs b/src/app_service.rs index be4ccf22f..32c779a32 100644 --- a/src/app_service.rs +++ b/src/app_service.rs @@ -1,20 +1,23 @@ use std::cell::RefCell; use std::rc::Rc; -use actix_http::{Extensions, Request, Response}; +use actix_http::{Extensions, Request}; use actix_router::{Path, ResourceDef, Router, Url}; use actix_service::boxed::{self, BoxService, BoxServiceFactory}; use actix_service::{fn_service, Service, ServiceFactory}; use futures_core::future::LocalBoxFuture; use futures_util::future::join_all; -use crate::config::{AppConfig, AppService}; use crate::data::FnDataFactory; use crate::error::Error; use crate::guard::Guard; use crate::request::{HttpRequest, HttpRequestPool}; use crate::rmap::ResourceMap; use crate::service::{AppServiceFactory, ServiceRequest, ServiceResponse}; +use crate::{ + config::{AppConfig, AppService}, + HttpResponse, +}; type Guards = Vec>; type HttpService = BoxService; @@ -64,7 +67,7 @@ where // if no user defined default service exists. let default = self.default.clone().unwrap_or_else(|| { Rc::new(boxed::factory(fn_service(|req: ServiceRequest| async { - Ok(req.into_response(Response::NotFound().finish())) + Ok(req.into_response(HttpResponse::NotFound())) }))) }); diff --git a/src/resource.rs b/src/resource.rs index e868bb547..049e56291 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -3,7 +3,7 @@ use std::fmt; use std::future::Future; use std::rc::Rc; -use actix_http::{Error, Extensions, Response}; +use actix_http::{Error, Extensions}; use actix_router::IntoPattern; use actix_service::boxed::{self, BoxService, BoxServiceFactory}; use actix_service::{ @@ -13,7 +13,6 @@ use actix_service::{ use futures_core::future::LocalBoxFuture; use futures_util::future::join_all; -use crate::data::Data; use crate::dev::{insert_slash, AppService, HttpServiceFactory, ResourceDef}; use crate::extract::FromRequest; use crate::guard::Guard; @@ -21,6 +20,7 @@ use crate::handler::Handler; use crate::responder::Responder; use crate::route::{Route, RouteService}; use crate::service::{ServiceRequest, ServiceResponse}; +use crate::{data::Data, HttpResponse}; type HttpService = BoxService; type HttpNewService = BoxServiceFactory<(), ServiceRequest, ServiceResponse, Error, ()>; @@ -71,7 +71,7 @@ impl Resource { guards: Vec::new(), app_data: None, default: boxed::factory(fn_service(|req: ServiceRequest| async { - Ok(req.into_response(Response::MethodNotAllowed().finish())) + Ok(req.into_response(HttpResponse::MethodNotAllowed())) })), } } diff --git a/src/response.rs b/src/response/builder.rs similarity index 55% rename from src/response.rs rename to src/response/builder.rs index 23244e6a5..2c04f3f64 100644 --- a/src/response.rs +++ b/src/response/builder.rs @@ -1,17 +1,15 @@ use std::{ cell::{Ref, RefMut}, convert::TryInto, - fmt, future::Future, - mem, pin::Pin, task::{Context, Poll}, }; use actix_http::{ - body::{Body, BodyStream, MessageBody, ResponseBody}, + body::{Body, BodyStream}, http::{ - header::{self, HeaderMap, HeaderName, IntoHeaderPair, IntoHeaderValue}, + header::{self, HeaderName, IntoHeaderPair, IntoHeaderValue}, ConnectionType, Error as HttpError, StatusCode, }, Extensions, Response, ResponseHead, @@ -25,282 +23,10 @@ use actix_http::http::header::HeaderValue; #[cfg(feature = "cookies")] use cookie::{Cookie, CookieJar}; -use crate::error::{Error, JsonPayloadError}; - -/// An HTTP Response -pub struct HttpResponse { - res: Response, - error: Option, -} - -impl HttpResponse { - /// Create HTTP response builder with specific status. - #[inline] - pub fn build(status: StatusCode) -> HttpResponseBuilder { - HttpResponseBuilder::new(status) - } - - /// Create HTTP response builder - #[inline] - pub fn build_from>(source: T) -> HttpResponseBuilder { - source.into() - } - - /// Create a response. - #[inline] - pub fn new(status: StatusCode) -> Self { - Self { - res: Response::new(status), - error: None, - } - } - - /// Create an error response. - #[inline] - pub fn from_error(error: Error) -> Self { - let res = error.as_response_error().error_response(); - - Self { - res, - error: Some(error), - } - } - - /// Convert response to response with body - pub fn into_body(self) -> HttpResponse { - HttpResponse { - res: self.res.into_body(), - error: self.error, - } - } -} - -impl HttpResponse { - /// Constructs a response with body - #[inline] - pub fn with_body(status: StatusCode, body: B) -> Self { - Self { - res: Response::with_body(status, body), - error: None, - } - } - - /// Returns a reference to response head. - #[inline] - pub fn head(&self) -> &ResponseHead { - self.res.head() - } - - /// Returns a mutable reference to response head. - #[inline] - pub fn head_mut(&mut self) -> &mut ResponseHead { - self.res.head_mut() - } - - /// The source `error` for this response - #[inline] - pub fn error(&self) -> Option<&Error> { - self.error.as_ref() - } - - /// Get the response status code - #[inline] - pub fn status(&self) -> StatusCode { - self.res.status() - } - - /// Set the `StatusCode` for this response - #[inline] - pub fn status_mut(&mut self) -> &mut StatusCode { - self.res.status_mut() - } - - /// Get the headers from the response - #[inline] - pub fn headers(&self) -> &HeaderMap { - self.res.headers() - } - - /// Get a mutable reference to the headers - #[inline] - pub fn headers_mut(&mut self) -> &mut HeaderMap { - self.res.headers_mut() - } - - /// Get an iterator for the cookies set by this response. - #[cfg(feature = "cookies")] - pub fn cookies(&self) -> CookieIter<'_> { - CookieIter { - iter: self.headers().get_all(header::SET_COOKIE), - } - } - - /// Add a cookie to this response - #[cfg(feature = "cookies")] - pub fn add_cookie(&mut self, cookie: &Cookie<'_>) -> Result<(), HttpError> { - HeaderValue::from_str(&cookie.to_string()) - .map(|c| { - self.headers_mut().append(header::SET_COOKIE, c); - }) - .map_err(|e| e.into()) - } - - /// Remove all cookies with the given name from this response. Returns - /// the number of cookies removed. - #[cfg(feature = "cookies")] - pub fn del_cookie(&mut self, name: &str) -> usize { - let headers = self.headers_mut(); - - let vals: Vec = headers - .get_all(header::SET_COOKIE) - .map(|v| v.to_owned()) - .collect(); - - headers.remove(header::SET_COOKIE); - - let mut count: usize = 0; - for v in vals { - if let Ok(s) = v.to_str() { - if let Ok(c) = Cookie::parse_encoded(s) { - if c.name() == name { - count += 1; - continue; - } - } - } - - // put set-cookie header head back if it does not validate - headers.append(header::SET_COOKIE, v); - } - - count - } - - /// Connection upgrade status - #[inline] - pub fn upgrade(&self) -> bool { - self.res.upgrade() - } - - /// Keep-alive status for this connection - pub fn keep_alive(&self) -> bool { - self.res.keep_alive() - } - - /// Responses extensions - #[inline] - pub fn extensions(&self) -> Ref<'_, Extensions> { - self.res.extensions() - } - - /// Mutable reference to a the response's extensions - #[inline] - pub fn extensions_mut(&mut self) -> RefMut<'_, Extensions> { - self.res.extensions_mut() - } - - /// Get body of this response - #[inline] - pub fn body(&self) -> &ResponseBody { - self.res.body() - } - - /// Set a body - pub fn set_body(self, body: B2) -> HttpResponse { - HttpResponse { - res: self.res.set_body(body), - error: None, - // error: self.error, ?? - } - } - - /// Split response and body - pub fn into_parts(self) -> (HttpResponse<()>, ResponseBody) { - let (head, body) = self.res.into_parts(); - - ( - HttpResponse { - res: head, - error: None, - }, - body, - ) - } - - /// Drop request's body - pub fn drop_body(self) -> HttpResponse<()> { - HttpResponse { - res: self.res.drop_body(), - error: None, - } - } - - /// Set a body and return previous body value - pub fn map_body(self, f: F) -> HttpResponse - where - F: FnOnce(&mut ResponseHead, ResponseBody) -> ResponseBody, - { - HttpResponse { - res: self.res.map_body(f), - error: self.error, - } - } - - /// Extract response body - pub fn take_body(&mut self) -> ResponseBody { - self.res.take_body() - } -} - -impl fmt::Debug for HttpResponse { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("HttpResponse") - .field("error", &self.error) - .field("res", &self.res) - .finish() - } -} - -impl From> for HttpResponse { - fn from(res: Response) -> Self { - HttpResponse { res, error: None } - } -} - -impl From for HttpResponse { - fn from(err: Error) -> Self { - HttpResponse::from_error(err) - } -} - -impl From> for Response { - fn from(res: HttpResponse) -> Self { - // this impl will always be called as part of dispatcher - - // TODO: expose cause somewhere? - // if let Some(err) = res.error { - // eprintln!("impl From> for Response let Some(err)"); - // return Response::from_error(err).into_body(); - // } - - res.res - } -} - -impl Future for HttpResponse { - type Output = Result, Error>; - - fn poll(mut self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll { - if let Some(err) = self.error.take() { - return Poll::Ready(Ok(Response::from_error(err).into_body())); - } - - Poll::Ready(Ok(mem::replace( - &mut self.res, - Response::new(StatusCode::default()), - ))) - } -} +use crate::{ + error::{Error, JsonPayloadError}, + HttpResponse, +}; /// An HTTP response builder. /// @@ -695,146 +421,18 @@ impl Future for HttpResponseBuilder { } } -#[cfg(feature = "cookies")] -pub struct CookieIter<'a> { - iter: header::GetAll<'a>, -} - -#[cfg(feature = "cookies")] -impl<'a> Iterator for CookieIter<'a> { - type Item = Cookie<'a>; - - #[inline] - fn next(&mut self) -> Option> { - for v in self.iter.by_ref() { - if let Ok(c) = Cookie::parse_encoded(v.to_str().ok()?) { - return Some(c); - } - } - None - } -} - -mod http_codes { - //! Status code based HTTP response builders. - - use actix_http::http::StatusCode; - - use super::{HttpResponse, HttpResponseBuilder}; - - macro_rules! static_resp { - ($name:ident, $status:expr) => { - #[allow(non_snake_case, missing_docs)] - pub fn $name() -> HttpResponseBuilder { - HttpResponseBuilder::new($status) - } - }; - } - - impl HttpResponse { - static_resp!(Continue, StatusCode::CONTINUE); - static_resp!(SwitchingProtocols, StatusCode::SWITCHING_PROTOCOLS); - static_resp!(Processing, StatusCode::PROCESSING); - - static_resp!(Ok, StatusCode::OK); - static_resp!(Created, StatusCode::CREATED); - static_resp!(Accepted, StatusCode::ACCEPTED); - static_resp!( - NonAuthoritativeInformation, - StatusCode::NON_AUTHORITATIVE_INFORMATION - ); - - static_resp!(NoContent, StatusCode::NO_CONTENT); - static_resp!(ResetContent, StatusCode::RESET_CONTENT); - static_resp!(PartialContent, StatusCode::PARTIAL_CONTENT); - static_resp!(MultiStatus, StatusCode::MULTI_STATUS); - static_resp!(AlreadyReported, StatusCode::ALREADY_REPORTED); - - static_resp!(MultipleChoices, StatusCode::MULTIPLE_CHOICES); - static_resp!(MovedPermanently, StatusCode::MOVED_PERMANENTLY); - static_resp!(Found, StatusCode::FOUND); - static_resp!(SeeOther, StatusCode::SEE_OTHER); - static_resp!(NotModified, StatusCode::NOT_MODIFIED); - static_resp!(UseProxy, StatusCode::USE_PROXY); - static_resp!(TemporaryRedirect, StatusCode::TEMPORARY_REDIRECT); - static_resp!(PermanentRedirect, StatusCode::PERMANENT_REDIRECT); - - static_resp!(BadRequest, StatusCode::BAD_REQUEST); - static_resp!(NotFound, StatusCode::NOT_FOUND); - static_resp!(Unauthorized, StatusCode::UNAUTHORIZED); - static_resp!(PaymentRequired, StatusCode::PAYMENT_REQUIRED); - static_resp!(Forbidden, StatusCode::FORBIDDEN); - static_resp!(MethodNotAllowed, StatusCode::METHOD_NOT_ALLOWED); - static_resp!(NotAcceptable, StatusCode::NOT_ACCEPTABLE); - static_resp!( - ProxyAuthenticationRequired, - StatusCode::PROXY_AUTHENTICATION_REQUIRED - ); - static_resp!(RequestTimeout, StatusCode::REQUEST_TIMEOUT); - static_resp!(Conflict, StatusCode::CONFLICT); - static_resp!(Gone, StatusCode::GONE); - static_resp!(LengthRequired, StatusCode::LENGTH_REQUIRED); - static_resp!(PreconditionFailed, StatusCode::PRECONDITION_FAILED); - static_resp!(PreconditionRequired, StatusCode::PRECONDITION_REQUIRED); - static_resp!(PayloadTooLarge, StatusCode::PAYLOAD_TOO_LARGE); - static_resp!(UriTooLong, StatusCode::URI_TOO_LONG); - static_resp!(UnsupportedMediaType, StatusCode::UNSUPPORTED_MEDIA_TYPE); - static_resp!(RangeNotSatisfiable, StatusCode::RANGE_NOT_SATISFIABLE); - static_resp!(ExpectationFailed, StatusCode::EXPECTATION_FAILED); - static_resp!(UnprocessableEntity, StatusCode::UNPROCESSABLE_ENTITY); - static_resp!(TooManyRequests, StatusCode::TOO_MANY_REQUESTS); - static_resp!( - RequestHeaderFieldsTooLarge, - StatusCode::REQUEST_HEADER_FIELDS_TOO_LARGE - ); - static_resp!( - UnavailableForLegalReasons, - StatusCode::UNAVAILABLE_FOR_LEGAL_REASONS - ); - - static_resp!(InternalServerError, StatusCode::INTERNAL_SERVER_ERROR); - static_resp!(NotImplemented, StatusCode::NOT_IMPLEMENTED); - static_resp!(BadGateway, StatusCode::BAD_GATEWAY); - static_resp!(ServiceUnavailable, StatusCode::SERVICE_UNAVAILABLE); - static_resp!(GatewayTimeout, StatusCode::GATEWAY_TIMEOUT); - static_resp!(VersionNotSupported, StatusCode::HTTP_VERSION_NOT_SUPPORTED); - static_resp!(VariantAlsoNegotiates, StatusCode::VARIANT_ALSO_NEGOTIATES); - static_resp!(InsufficientStorage, StatusCode::INSUFFICIENT_STORAGE); - static_resp!(LoopDetected, StatusCode::LOOP_DETECTED); - } - - #[cfg(test)] - mod tests { - use crate::dev::Body; - use crate::http::StatusCode; - use crate::HttpResponse; - - #[test] - fn test_build() { - let resp = HttpResponse::Ok().body(Body::Empty); - assert_eq!(resp.status(), StatusCode::OK); - } - } -} - #[cfg(test)] mod tests { - use bytes::{Bytes, BytesMut}; + use actix_http::body; - use super::{HttpResponse, HttpResponseBuilder}; - use crate::dev::{Body, MessageBody, ResponseBody}; - use crate::http::header::{self, HeaderValue, CONTENT_TYPE, COOKIE}; - use crate::http::StatusCode; - - #[test] - fn test_debug() { - let resp = HttpResponse::Ok() - .append_header((COOKIE, HeaderValue::from_static("cookie1=value1; "))) - .append_header((COOKIE, HeaderValue::from_static("cookie2=value2; "))) - .finish(); - let dbg = format!("{:?}", resp); - assert!(dbg.contains("HttpResponse")); - } + use super::*; + use crate::{ + dev::Body, + http::{ + header::{self, HeaderValue, CONTENT_TYPE}, + StatusCode, + }, + }; #[test] fn test_basic_builder() { @@ -872,26 +470,13 @@ mod tests { assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "text/plain") } - pub async fn read_body(mut body: ResponseBody) -> Bytes - where - B: MessageBody + Unpin, - { - use futures_util::StreamExt as _; - - let mut bytes = BytesMut::new(); - while let Some(item) = body.next().await { - bytes.extend_from_slice(&item.unwrap()); - } - bytes.freeze() - } - #[actix_rt::test] async fn test_json() { let mut resp = HttpResponse::Ok().json(vec!["v1", "v2", "v3"]); let ct = resp.headers().get(CONTENT_TYPE).unwrap(); assert_eq!(ct, HeaderValue::from_static("application/json")); assert_eq!( - read_body(resp.take_body()).await.as_ref(), + body::to_bytes(resp.take_body()).await.unwrap().as_ref(), br#"["v1","v2","v3"]"# ); @@ -899,7 +484,7 @@ mod tests { let ct = resp.headers().get(CONTENT_TYPE).unwrap(); assert_eq!(ct, HeaderValue::from_static("application/json")); assert_eq!( - read_body(resp.take_body()).await.as_ref(), + body::to_bytes(resp.take_body()).await.unwrap().as_ref(), br#"["v1","v2","v3"]"# ); @@ -910,7 +495,7 @@ mod tests { let ct = resp.headers().get(CONTENT_TYPE).unwrap(); assert_eq!(ct, HeaderValue::from_static("text/json")); assert_eq!( - read_body(resp.take_body()).await.as_ref(), + body::to_bytes(resp.take_body()).await.unwrap().as_ref(), br#"["v1","v2","v3"]"# ); } @@ -922,7 +507,7 @@ mod tests { ); assert_eq!( - read_body(resp.take_body()).await.as_ref(), + body::to_bytes(resp.take_body()).await.unwrap().as_ref(), br#"{"test-key":"test-value"}"# ); } diff --git a/actix-http/src/http_codes.rs b/src/response/http_codes.rs similarity index 90% rename from actix-http/src/http_codes.rs rename to src/response/http_codes.rs index dc4f964de..d67ef3f92 100644 --- a/actix-http/src/http_codes.rs +++ b/src/response/http_codes.rs @@ -1,24 +1,19 @@ //! Status code based HTTP response builders. -#![allow(non_upper_case_globals)] +use actix_http::http::StatusCode; -use http::StatusCode; - -use crate::{ - body::Body, - response::{Response, ResponseBuilder}, -}; +use crate::{HttpResponse, HttpResponseBuilder}; macro_rules! static_resp { ($name:ident, $status:expr) => { #[allow(non_snake_case, missing_docs)] - pub fn $name() -> ResponseBuilder { - ResponseBuilder::new($status) + pub fn $name() -> HttpResponseBuilder { + HttpResponseBuilder::new($status) } }; } -impl Response { +impl HttpResponse { static_resp!(Continue, StatusCode::CONTINUE); static_resp!(SwitchingProtocols, StatusCode::SWITCHING_PROTOCOLS); static_resp!(Processing, StatusCode::PROCESSING); @@ -92,13 +87,13 @@ impl Response { #[cfg(test)] mod tests { - use crate::body::Body; - use crate::response::Response; - use http::StatusCode; + use crate::dev::Body; + use crate::http::StatusCode; + use crate::HttpResponse; #[test] fn test_build() { - let resp = Response::Ok().body(Body::Empty); + let resp = HttpResponse::Ok().body(Body::Empty); assert_eq!(resp.status(), StatusCode::OK); } } diff --git a/src/response/mod.rs b/src/response/mod.rs new file mode 100644 index 000000000..8401db9d2 --- /dev/null +++ b/src/response/mod.rs @@ -0,0 +1,10 @@ +mod builder; +mod http_codes; +#[allow(clippy::module_inception)] +mod response; + +pub use self::builder::HttpResponseBuilder; +pub use self::response::HttpResponse; + +#[cfg(feature = "cookies")] +pub use self::response::CookieIter; diff --git a/src/response/response.rs b/src/response/response.rs new file mode 100644 index 000000000..31868fe0b --- /dev/null +++ b/src/response/response.rs @@ -0,0 +1,330 @@ +use std::{ + cell::{Ref, RefMut}, + fmt, + future::Future, + mem, + pin::Pin, + task::{Context, Poll}, +}; + +use actix_http::{ + body::{Body, MessageBody, ResponseBody}, + http::{header::HeaderMap, StatusCode}, + Extensions, Response, ResponseHead, +}; + +#[cfg(feature = "cookies")] +use { + actix_http::http::{ + header::{self, HeaderValue}, + Error as HttpError, + }, + cookie::Cookie, +}; + +use crate::{error::Error, HttpResponseBuilder}; + +/// An HTTP Response +pub struct HttpResponse { + res: Response, + error: Option, +} + +impl HttpResponse { + /// Create HTTP response builder with specific status. + #[inline] + pub fn build(status: StatusCode) -> HttpResponseBuilder { + HttpResponseBuilder::new(status) + } + + /// Create a response. + #[inline] + pub fn new(status: StatusCode) -> Self { + Self { + res: Response::new(status), + error: None, + } + } + + /// Create an error response. + #[inline] + pub fn from_error(error: Error) -> Self { + let res = error.as_response_error().error_response(); + + Self { + res, + error: Some(error), + } + } + + /// Convert response to response with body + pub fn into_body(self) -> HttpResponse { + HttpResponse { + res: self.res.into_body(), + error: self.error, + } + } +} + +impl HttpResponse { + /// Constructs a response with body + #[inline] + pub fn with_body(status: StatusCode, body: B) -> Self { + Self { + res: Response::with_body(status, body), + error: None, + } + } + + /// Returns a reference to response head. + #[inline] + pub fn head(&self) -> &ResponseHead { + self.res.head() + } + + /// Returns a mutable reference to response head. + #[inline] + pub fn head_mut(&mut self) -> &mut ResponseHead { + self.res.head_mut() + } + + /// The source `error` for this response + #[inline] + pub fn error(&self) -> Option<&Error> { + self.error.as_ref() + } + + /// Get the response status code + #[inline] + pub fn status(&self) -> StatusCode { + self.res.status() + } + + /// Set the `StatusCode` for this response + #[inline] + pub fn status_mut(&mut self) -> &mut StatusCode { + self.res.status_mut() + } + + /// Get the headers from the response + #[inline] + pub fn headers(&self) -> &HeaderMap { + self.res.headers() + } + + /// Get a mutable reference to the headers + #[inline] + pub fn headers_mut(&mut self) -> &mut HeaderMap { + self.res.headers_mut() + } + + /// Get an iterator for the cookies set by this response. + #[cfg(feature = "cookies")] + pub fn cookies(&self) -> CookieIter<'_> { + CookieIter { + iter: self.headers().get_all(header::SET_COOKIE), + } + } + + /// Add a cookie to this response + #[cfg(feature = "cookies")] + pub fn add_cookie(&mut self, cookie: &Cookie<'_>) -> Result<(), HttpError> { + HeaderValue::from_str(&cookie.to_string()) + .map(|c| { + self.headers_mut().append(header::SET_COOKIE, c); + }) + .map_err(|e| e.into()) + } + + /// Remove all cookies with the given name from this response. Returns + /// the number of cookies removed. + #[cfg(feature = "cookies")] + pub fn del_cookie(&mut self, name: &str) -> usize { + let headers = self.headers_mut(); + + let vals: Vec = headers + .get_all(header::SET_COOKIE) + .map(|v| v.to_owned()) + .collect(); + + headers.remove(header::SET_COOKIE); + + let mut count: usize = 0; + for v in vals { + if let Ok(s) = v.to_str() { + if let Ok(c) = Cookie::parse_encoded(s) { + if c.name() == name { + count += 1; + continue; + } + } + } + + // put set-cookie header head back if it does not validate + headers.append(header::SET_COOKIE, v); + } + + count + } + + /// Connection upgrade status + #[inline] + pub fn upgrade(&self) -> bool { + self.res.upgrade() + } + + /// Keep-alive status for this connection + pub fn keep_alive(&self) -> bool { + self.res.keep_alive() + } + + /// Responses extensions + #[inline] + pub fn extensions(&self) -> Ref<'_, Extensions> { + self.res.extensions() + } + + /// Mutable reference to a the response's extensions + #[inline] + pub fn extensions_mut(&mut self) -> RefMut<'_, Extensions> { + self.res.extensions_mut() + } + + /// Get body of this response + #[inline] + pub fn body(&self) -> &ResponseBody { + self.res.body() + } + + /// Set a body + pub fn set_body(self, body: B2) -> HttpResponse { + HttpResponse { + res: self.res.set_body(body), + error: None, + // error: self.error, ?? + } + } + + /// Split response and body + pub fn into_parts(self) -> (HttpResponse<()>, ResponseBody) { + let (head, body) = self.res.into_parts(); + + ( + HttpResponse { + res: head, + error: None, + }, + body, + ) + } + + /// Drop request's body + pub fn drop_body(self) -> HttpResponse<()> { + HttpResponse { + res: self.res.drop_body(), + error: None, + } + } + + /// Set a body and return previous body value + pub fn map_body(self, f: F) -> HttpResponse + where + F: FnOnce(&mut ResponseHead, ResponseBody) -> ResponseBody, + { + HttpResponse { + res: self.res.map_body(f), + error: self.error, + } + } + + /// Extract response body + pub fn take_body(&mut self) -> ResponseBody { + self.res.take_body() + } +} + +impl fmt::Debug for HttpResponse { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("HttpResponse") + .field("error", &self.error) + .field("res", &self.res) + .finish() + } +} + +impl From> for HttpResponse { + fn from(res: Response) -> Self { + HttpResponse { res, error: None } + } +} + +impl From for HttpResponse { + fn from(err: Error) -> Self { + HttpResponse::from_error(err) + } +} + +impl From> for Response { + fn from(res: HttpResponse) -> Self { + // this impl will always be called as part of dispatcher + + // TODO: expose cause somewhere? + // if let Some(err) = res.error { + // eprintln!("impl From> for Response let Some(err)"); + // return Response::from_error(err).into_body(); + // } + + res.res + } +} + +impl Future for HttpResponse { + type Output = Result, Error>; + + fn poll(mut self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll { + if let Some(err) = self.error.take() { + return Poll::Ready(Ok(Response::from_error(err).into_body())); + } + + Poll::Ready(Ok(mem::replace( + &mut self.res, + Response::new(StatusCode::default()), + ))) + } +} + +#[cfg(feature = "cookies")] +pub struct CookieIter<'a> { + iter: header::GetAll<'a>, +} + +#[cfg(feature = "cookies")] +impl<'a> Iterator for CookieIter<'a> { + type Item = Cookie<'a>; + + #[inline] + fn next(&mut self) -> Option> { + for v in self.iter.by_ref() { + if let Ok(c) = Cookie::parse_encoded(v.to_str().ok()?) { + return Some(c); + } + } + None + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::http::header::{HeaderValue, COOKIE}; + + #[test] + fn test_debug() { + let resp = HttpResponse::Ok() + .append_header((COOKIE, HeaderValue::from_static("cookie1=value1; "))) + .append_header((COOKIE, HeaderValue::from_static("cookie2=value2; "))) + .finish(); + let dbg = format!("{:?}", resp); + assert!(dbg.contains("HttpResponse")); + } +} From 387c229f281271e7d701a0ff5d09e68a282e9988 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Wed, 14 Apr 2021 02:12:47 +0100 Subject: [PATCH 52/79] move response builder code to own file --- actix-http/src/body/mod.rs | 2 +- actix-http/src/lib.rs | 4 +- actix-http/src/response.rs | 412 +---------------------- actix-http/src/response_builder.rs | 503 +++++++++++++++++++++++++++++ 4 files changed, 517 insertions(+), 404 deletions(-) create mode 100644 actix-http/src/response_builder.rs diff --git a/actix-http/src/body/mod.rs b/actix-http/src/body/mod.rs index c298dda11..f26d6a8cf 100644 --- a/actix-http/src/body/mod.rs +++ b/actix-http/src/body/mod.rs @@ -56,7 +56,7 @@ pub async fn to_bytes(body: impl MessageBody) -> Result { let body = body.as_mut(); match ready!(body.poll_next(cx)) { - Some(Ok(bytes)) => buf.extend(bytes), + Some(Ok(bytes)) => buf.extend_from_slice(&*bytes), None => return Poll::Ready(Ok(())), Some(Err(err)) => return Poll::Ready(Err(err)), } diff --git a/actix-http/src/lib.rs b/actix-http/src/lib.rs index 3125e3874..8674834e0 100644 --- a/actix-http/src/lib.rs +++ b/actix-http/src/lib.rs @@ -42,6 +42,7 @@ mod message; mod payload; mod request; mod response; +mod response_builder; mod service; mod time_parser; @@ -59,7 +60,8 @@ pub use self::http_message::HttpMessage; pub use self::message::{Message, RequestHead, RequestHeadType, ResponseHead}; pub use self::payload::{Payload, PayloadStream}; pub use self::request::Request; -pub use self::response::{Response, ResponseBuilder}; +pub use self::response::{Response}; +pub use self::response_builder::{ResponseBuilder}; pub use self::service::HttpService; pub mod http { diff --git a/actix-http/src/response.rs b/actix-http/src/response.rs index 7f73538fc..a3ab1175c 100644 --- a/actix-http/src/response.rs +++ b/actix-http/src/response.rs @@ -1,4 +1,4 @@ -//! HTTP responses. +//! HTTP response. use std::{ cell::{Ref, RefMut}, @@ -10,22 +10,21 @@ use std::{ }; use bytes::{Bytes, BytesMut}; -use futures_core::Stream; use crate::{ - body::{Body, BodyStream, MessageBody, ResponseBody}, + body::{Body, MessageBody, ResponseBody}, error::Error, extensions::Extensions, - header::{IntoHeaderPair, IntoHeaderValue}, - http::{header, Error as HttpError, HeaderMap, StatusCode}, - message::{BoxedResponseHead, ConnectionType, ResponseHead}, + http::{HeaderMap, StatusCode}, + message::{BoxedResponseHead, ResponseHead}, + ResponseBuilder, }; -/// An HTTP Response +/// An HTTP response. pub struct Response { - head: BoxedResponseHead, - body: ResponseBody, - error: Option, + pub(crate) head: BoxedResponseHead, + pub(crate) body: ResponseBody, + pub(crate) error: Option, } impl Response { @@ -273,295 +272,6 @@ impl Future for Response { } } -/// An HTTP response builder. -/// -/// This type can be used to construct an instance of `Response` through a builder-like pattern. -pub struct ResponseBuilder { - head: Option, - err: Option, -} - -impl ResponseBuilder { - #[inline] - /// Create response builder - pub fn new(status: StatusCode) -> Self { - ResponseBuilder { - head: Some(BoxedResponseHead::new(status)), - err: None, - } - } - - /// Set HTTP status code of this response. - #[inline] - pub fn status(&mut self, status: StatusCode) -> &mut Self { - if let Some(parts) = parts(&mut self.head, &self.err) { - parts.status = status; - } - self - } - - /// Insert a header, replacing any that were set with an equivalent field name. - /// - /// ``` - /// # use actix_http::Response; - /// use actix_http::http::{header, StatusCode}; - /// - /// Response::build(StatusCode::OK) - /// .insert_header((header::CONTENT_TYPE, mime::APPLICATION_JSON)) - /// .insert_header(("X-TEST", "value")) - /// .finish(); - /// ``` - pub fn insert_header(&mut self, header: H) -> &mut Self - where - H: IntoHeaderPair, - { - if let Some(parts) = parts(&mut self.head, &self.err) { - match header.try_into_header_pair() { - Ok((key, value)) => { - parts.headers.insert(key, value); - } - Err(e) => self.err = Some(e.into()), - }; - } - - self - } - - /// Append a header, keeping any that were set with an equivalent field name. - /// - /// ``` - /// # use actix_http::Response; - /// use actix_http::http::{header, StatusCode}; - /// - /// Response::build(StatusCode::OK) - /// .append_header((header::CONTENT_TYPE, mime::APPLICATION_JSON)) - /// .append_header(("X-TEST", "value1")) - /// .append_header(("X-TEST", "value2")) - /// .finish(); - /// ``` - pub fn append_header(&mut self, header: H) -> &mut Self - where - H: IntoHeaderPair, - { - if let Some(parts) = parts(&mut self.head, &self.err) { - match header.try_into_header_pair() { - Ok((key, value)) => parts.headers.append(key, value), - Err(e) => self.err = Some(e.into()), - }; - } - - self - } - - /// Set the custom reason for the response. - #[inline] - pub fn reason(&mut self, reason: &'static str) -> &mut Self { - if let Some(parts) = parts(&mut self.head, &self.err) { - parts.reason = Some(reason); - } - self - } - - /// Set connection type to KeepAlive - #[inline] - pub fn keep_alive(&mut self) -> &mut Self { - if let Some(parts) = parts(&mut self.head, &self.err) { - parts.set_connection_type(ConnectionType::KeepAlive); - } - self - } - - /// Set connection type to Upgrade - #[inline] - pub fn upgrade(&mut self, value: V) -> &mut Self - where - V: IntoHeaderValue, - { - if let Some(parts) = parts(&mut self.head, &self.err) { - parts.set_connection_type(ConnectionType::Upgrade); - } - - if let Ok(value) = value.try_into_value() { - self.insert_header((header::UPGRADE, value)); - } - - self - } - - /// Force close connection, even if it is marked as keep-alive - #[inline] - pub fn force_close(&mut self) -> &mut Self { - if let Some(parts) = parts(&mut self.head, &self.err) { - parts.set_connection_type(ConnectionType::Close); - } - self - } - - /// Disable chunked transfer encoding for HTTP/1.1 streaming responses. - #[inline] - pub fn no_chunking(&mut self, len: u64) -> &mut Self { - let mut buf = itoa::Buffer::new(); - self.insert_header((header::CONTENT_LENGTH, buf.format(len))); - - if let Some(parts) = parts(&mut self.head, &self.err) { - parts.no_chunking(true); - } - self - } - - /// Set response content type. - #[inline] - pub fn content_type(&mut self, value: V) -> &mut Self - where - V: IntoHeaderValue, - { - if let Some(parts) = parts(&mut self.head, &self.err) { - match value.try_into_value() { - Ok(value) => { - parts.headers.insert(header::CONTENT_TYPE, value); - } - Err(e) => self.err = Some(e.into()), - }; - } - self - } - - /// Responses extensions - #[inline] - pub fn extensions(&self) -> Ref<'_, Extensions> { - let head = self.head.as_ref().expect("cannot reuse response builder"); - head.extensions.borrow() - } - - /// Mutable reference to a the response's extensions - #[inline] - pub fn extensions_mut(&mut self) -> RefMut<'_, Extensions> { - let head = self.head.as_ref().expect("cannot reuse response builder"); - head.extensions.borrow_mut() - } - - /// Set a body and generate `Response`. - /// - /// `ResponseBuilder` can not be used after this call. - #[inline] - pub fn body>(&mut self, body: B) -> Response { - self.message_body(body.into()) - } - - /// Set a body and generate `Response`. - /// - /// `ResponseBuilder` can not be used after this call. - pub fn message_body(&mut self, body: B) -> Response { - if let Some(e) = self.err.take() { - return Response::from(Error::from(e)).into_body(); - } - - let response = self.head.take().expect("cannot reuse response builder"); - - Response { - head: response, - body: ResponseBody::Body(body), - error: None, - } - } - - /// Set a streaming body and generate `Response`. - /// - /// `ResponseBuilder` can not be used after this call. - #[inline] - pub fn streaming(&mut self, stream: S) -> Response - where - S: Stream> + Unpin + 'static, - E: Into + 'static, - { - self.body(Body::from_message(BodyStream::new(stream))) - } - - /// Set an empty body and generate `Response` - /// - /// `ResponseBuilder` can not be used after this call. - #[inline] - pub fn finish(&mut self) -> Response { - self.body(Body::Empty) - } - - /// This method construct new `ResponseBuilder` - pub fn take(&mut self) -> ResponseBuilder { - ResponseBuilder { - head: self.head.take(), - err: self.err.take(), - } - } -} - -#[inline] -fn parts<'a>( - parts: &'a mut Option, - err: &Option, -) -> Option<&'a mut ResponseHead> { - if err.is_some() { - return None; - } - parts.as_mut().map(|r| &mut **r) -} - -/// Convert `Response` to a `ResponseBuilder`. Body get dropped. -impl From> for ResponseBuilder { - fn from(res: Response) -> ResponseBuilder { - ResponseBuilder { - head: Some(res.head), - err: None, - } - } -} - -/// Convert `ResponseHead` to a `ResponseBuilder` -impl<'a> From<&'a ResponseHead> for ResponseBuilder { - fn from(head: &'a ResponseHead) -> ResponseBuilder { - let mut msg = BoxedResponseHead::new(head.status); - msg.version = head.version; - msg.reason = head.reason; - - for (k, v) in head.headers.iter() { - msg.headers.append(k.clone(), v.clone()); - } - - msg.no_chunking(!head.chunked()); - - ResponseBuilder { - head: Some(msg), - err: None, - } - } -} - -impl Future for ResponseBuilder { - type Output = Result, Error>; - - fn poll(mut self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll { - Poll::Ready(Ok(self.finish())) - } -} - -impl fmt::Debug for ResponseBuilder { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let head = self.head.as_ref().unwrap(); - - let res = writeln!( - f, - "\nResponseBuilder {:?} {}{}", - head.version, - head.status, - head.reason.unwrap_or(""), - ); - let _ = writeln!(f, " headers:"); - for (key, val) in head.headers.iter() { - let _ = writeln!(f, " {:?}: {:?}", key, val); - } - res - } -} - /// Helper converters impl>, E: Into> From> for Response { fn from(res: Result) -> Self { @@ -630,7 +340,7 @@ impl From for Response { mod tests { use super::*; use crate::body::Body; - use crate::http::header::{HeaderName, HeaderValue, CONTENT_TYPE, COOKIE}; + use crate::http::header::{HeaderValue, CONTENT_TYPE, COOKIE}; #[test] fn test_debug() { @@ -642,40 +352,6 @@ mod tests { assert!(dbg.contains("Response")); } - #[test] - fn test_basic_builder() { - let resp = Response::build(StatusCode::OK) - .insert_header(("X-TEST", "value")) - .finish(); - assert_eq!(resp.status(), StatusCode::OK); - } - - #[test] - fn test_upgrade() { - let resp = Response::build(StatusCode::OK) - .upgrade("websocket") - .finish(); - assert!(resp.upgrade()); - assert_eq!( - resp.headers().get(header::UPGRADE).unwrap(), - HeaderValue::from_static("websocket") - ); - } - - #[test] - fn test_force_close() { - let resp = Response::build(StatusCode::OK).force_close().finish(); - assert!(!resp.keep_alive()) - } - - #[test] - fn test_content_type() { - let resp = Response::build(StatusCode::OK) - .content_type("text/plain") - .body(Body::Empty); - assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "text/plain") - } - #[test] fn test_into_response() { let resp: Response = "test".into(); @@ -745,72 +421,4 @@ mod tests { assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.body().get_ref(), b"test"); } - - #[test] - fn test_into_builder() { - let mut resp: Response = "test".into(); - assert_eq!(resp.status(), StatusCode::OK); - - resp.headers_mut().insert( - HeaderName::from_static("cookie"), - HeaderValue::from_static("cookie1=val100"), - ); - - let mut builder: ResponseBuilder = resp.into(); - let resp = builder.status(StatusCode::BAD_REQUEST).finish(); - assert_eq!(resp.status(), StatusCode::BAD_REQUEST); - - let cookie = resp.headers().get_all("Cookie").next().unwrap(); - assert_eq!(cookie.to_str().unwrap(), "cookie1=val100"); - } - - #[test] - fn response_builder_header_insert_kv() { - let mut res = Response::build(StatusCode::OK); - res.insert_header(("Content-Type", "application/octet-stream")); - let res = res.finish(); - - assert_eq!( - res.headers().get("Content-Type"), - Some(&HeaderValue::from_static("application/octet-stream")) - ); - } - - #[test] - fn response_builder_header_insert_typed() { - let mut res = Response::build(StatusCode::OK); - res.insert_header((header::CONTENT_TYPE, mime::APPLICATION_OCTET_STREAM)); - let res = res.finish(); - - assert_eq!( - res.headers().get("Content-Type"), - Some(&HeaderValue::from_static("application/octet-stream")) - ); - } - - #[test] - fn response_builder_header_append_kv() { - let mut res = Response::build(StatusCode::OK); - res.append_header(("Content-Type", "application/octet-stream")); - res.append_header(("Content-Type", "application/json")); - let res = res.finish(); - - let headers: Vec<_> = res.headers().get_all("Content-Type").cloned().collect(); - assert_eq!(headers.len(), 2); - assert!(headers.contains(&HeaderValue::from_static("application/octet-stream"))); - assert!(headers.contains(&HeaderValue::from_static("application/json"))); - } - - #[test] - fn response_builder_header_append_typed() { - let mut res = Response::build(StatusCode::OK); - res.append_header((header::CONTENT_TYPE, mime::APPLICATION_OCTET_STREAM)); - res.append_header((header::CONTENT_TYPE, mime::APPLICATION_JSON)); - let res = res.finish(); - - let headers: Vec<_> = res.headers().get_all("Content-Type").cloned().collect(); - assert_eq!(headers.len(), 2); - assert!(headers.contains(&HeaderValue::from_static("application/octet-stream"))); - assert!(headers.contains(&HeaderValue::from_static("application/json"))); - } } diff --git a/actix-http/src/response_builder.rs b/actix-http/src/response_builder.rs new file mode 100644 index 000000000..eab96b03d --- /dev/null +++ b/actix-http/src/response_builder.rs @@ -0,0 +1,503 @@ +//! HTTP response builder. + +use std::{ + cell::{Ref, RefMut}, + fmt, + future::Future, + pin::Pin, + str, + task::{Context, Poll}, +}; + +use bytes::Bytes; +use futures_core::Stream; + +use crate::{ + body::{Body, BodyStream, ResponseBody}, + error::Error, + extensions::Extensions, + header::{IntoHeaderPair, IntoHeaderValue}, + http::{header, Error as HttpError, StatusCode}, + message::{BoxedResponseHead, ConnectionType, ResponseHead}, + Response, +}; + +/// An HTTP response builder. +/// +/// This type can be used to construct an instance of `Response` using a builder pattern. +pub struct ResponseBuilder { + head: Option, + err: Option, +} + +impl ResponseBuilder { + #[inline] + /// Create response builder + pub fn new(status: StatusCode) -> Self { + ResponseBuilder { + head: Some(BoxedResponseHead::new(status)), + err: None, + } + } + + /// Set HTTP status code of this response. + #[inline] + pub fn status(&mut self, status: StatusCode) -> &mut Self { + if let Some(parts) = parts(&mut self.head, &self.err) { + parts.status = status; + } + self + } + + /// Insert a header, replacing any that were set with an equivalent field name. + /// + /// ``` + /// # use actix_http::Response; + /// use actix_http::http::{header, StatusCode}; + /// + /// Response::build(StatusCode::OK) + /// .insert_header((header::CONTENT_TYPE, mime::APPLICATION_JSON)) + /// .insert_header(("X-TEST", "value")) + /// .finish(); + /// ``` + pub fn insert_header(&mut self, header: H) -> &mut Self + where + H: IntoHeaderPair, + { + if let Some(parts) = parts(&mut self.head, &self.err) { + match header.try_into_header_pair() { + Ok((key, value)) => { + parts.headers.insert(key, value); + } + Err(e) => self.err = Some(e.into()), + }; + } + + self + } + + /// Append a header, keeping any that were set with an equivalent field name. + /// + /// ``` + /// # use actix_http::Response; + /// use actix_http::http::{header, StatusCode}; + /// + /// Response::build(StatusCode::OK) + /// .append_header((header::CONTENT_TYPE, mime::APPLICATION_JSON)) + /// .append_header(("X-TEST", "value1")) + /// .append_header(("X-TEST", "value2")) + /// .finish(); + /// ``` + pub fn append_header(&mut self, header: H) -> &mut Self + where + H: IntoHeaderPair, + { + if let Some(parts) = parts(&mut self.head, &self.err) { + match header.try_into_header_pair() { + Ok((key, value)) => parts.headers.append(key, value), + Err(e) => self.err = Some(e.into()), + }; + } + + self + } + + /// Set the custom reason for the response. + #[inline] + pub fn reason(&mut self, reason: &'static str) -> &mut Self { + if let Some(parts) = parts(&mut self.head, &self.err) { + parts.reason = Some(reason); + } + self + } + + /// Set connection type to KeepAlive + #[inline] + pub fn keep_alive(&mut self) -> &mut Self { + if let Some(parts) = parts(&mut self.head, &self.err) { + parts.set_connection_type(ConnectionType::KeepAlive); + } + self + } + + /// Set connection type to Upgrade + #[inline] + pub fn upgrade(&mut self, value: V) -> &mut Self + where + V: IntoHeaderValue, + { + if let Some(parts) = parts(&mut self.head, &self.err) { + parts.set_connection_type(ConnectionType::Upgrade); + } + + if let Ok(value) = value.try_into_value() { + self.insert_header((header::UPGRADE, value)); + } + + self + } + + /// Force close connection, even if it is marked as keep-alive + #[inline] + pub fn force_close(&mut self) -> &mut Self { + if let Some(parts) = parts(&mut self.head, &self.err) { + parts.set_connection_type(ConnectionType::Close); + } + self + } + + /// Disable chunked transfer encoding for HTTP/1.1 streaming responses. + #[inline] + pub fn no_chunking(&mut self, len: u64) -> &mut Self { + let mut buf = itoa::Buffer::new(); + self.insert_header((header::CONTENT_LENGTH, buf.format(len))); + + if let Some(parts) = parts(&mut self.head, &self.err) { + parts.no_chunking(true); + } + self + } + + /// Set response content type. + #[inline] + pub fn content_type(&mut self, value: V) -> &mut Self + where + V: IntoHeaderValue, + { + if let Some(parts) = parts(&mut self.head, &self.err) { + match value.try_into_value() { + Ok(value) => { + parts.headers.insert(header::CONTENT_TYPE, value); + } + Err(e) => self.err = Some(e.into()), + }; + } + self + } + + /// Responses extensions + #[inline] + pub fn extensions(&self) -> Ref<'_, Extensions> { + let head = self.head.as_ref().expect("cannot reuse response builder"); + head.extensions.borrow() + } + + /// Mutable reference to a the response's extensions + #[inline] + pub fn extensions_mut(&mut self) -> RefMut<'_, Extensions> { + let head = self.head.as_ref().expect("cannot reuse response builder"); + head.extensions.borrow_mut() + } + + /// Set a body and generate `Response`. + /// + /// `ResponseBuilder` can not be used after this call. + #[inline] + pub fn body>(&mut self, body: B) -> Response { + self.message_body(body.into()) + } + + /// Set a body and generate `Response`. + /// + /// `ResponseBuilder` can not be used after this call. + pub fn message_body(&mut self, body: B) -> Response { + if let Some(e) = self.err.take() { + return Response::from(Error::from(e)).into_body(); + } + + let response = self.head.take().expect("cannot reuse response builder"); + + Response { + head: response, + body: ResponseBody::Body(body), + error: None, + } + } + + /// Set a streaming body and generate `Response`. + /// + /// `ResponseBuilder` can not be used after this call. + #[inline] + pub fn streaming(&mut self, stream: S) -> Response + where + S: Stream> + Unpin + 'static, + E: Into + 'static, + { + self.body(Body::from_message(BodyStream::new(stream))) + } + + /// Set an empty body and generate `Response` + /// + /// `ResponseBuilder` can not be used after this call. + #[inline] + pub fn finish(&mut self) -> Response { + self.body(Body::Empty) + } + + /// This method construct new `ResponseBuilder` + pub fn take(&mut self) -> ResponseBuilder { + ResponseBuilder { + head: self.head.take(), + err: self.err.take(), + } + } +} + +#[inline] +fn parts<'a>( + parts: &'a mut Option, + err: &Option, +) -> Option<&'a mut ResponseHead> { + if err.is_some() { + return None; + } + parts.as_mut().map(|r| &mut **r) +} + +/// Convert `Response` to a `ResponseBuilder`. Body get dropped. +impl From> for ResponseBuilder { + fn from(res: Response) -> ResponseBuilder { + ResponseBuilder { + head: Some(res.head), + err: None, + } + } +} + +/// Convert `ResponseHead` to a `ResponseBuilder` +impl<'a> From<&'a ResponseHead> for ResponseBuilder { + fn from(head: &'a ResponseHead) -> ResponseBuilder { + let mut msg = BoxedResponseHead::new(head.status); + msg.version = head.version; + msg.reason = head.reason; + + for (k, v) in head.headers.iter() { + msg.headers.append(k.clone(), v.clone()); + } + + msg.no_chunking(!head.chunked()); + + ResponseBuilder { + head: Some(msg), + err: None, + } + } +} + +impl Future for ResponseBuilder { + type Output = Result, Error>; + + fn poll(mut self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll { + Poll::Ready(Ok(self.finish())) + } +} + +impl fmt::Debug for ResponseBuilder { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let head = self.head.as_ref().unwrap(); + + let res = writeln!( + f, + "\nResponseBuilder {:?} {}{}", + head.version, + head.status, + head.reason.unwrap_or(""), + ); + let _ = writeln!(f, " headers:"); + for (key, val) in head.headers.iter() { + let _ = writeln!(f, " {:?}: {:?}", key, val); + } + res + } +} + +#[cfg(test)] +mod tests { + use bytes::BytesMut; + + use super::*; + use crate::body::Body; + use crate::http::header::{HeaderName, HeaderValue, CONTENT_TYPE, COOKIE}; + + #[test] + fn test_debug() { + let resp = Response::build(StatusCode::OK) + .append_header((COOKIE, HeaderValue::from_static("cookie1=value1; "))) + .append_header((COOKIE, HeaderValue::from_static("cookie2=value2; "))) + .finish(); + let dbg = format!("{:?}", resp); + assert!(dbg.contains("Response")); + } + + #[test] + fn test_basic_builder() { + let resp = Response::build(StatusCode::OK) + .insert_header(("X-TEST", "value")) + .finish(); + assert_eq!(resp.status(), StatusCode::OK); + } + + #[test] + fn test_upgrade() { + let resp = Response::build(StatusCode::OK) + .upgrade("websocket") + .finish(); + assert!(resp.upgrade()); + assert_eq!( + resp.headers().get(header::UPGRADE).unwrap(), + HeaderValue::from_static("websocket") + ); + } + + #[test] + fn test_force_close() { + let resp = Response::build(StatusCode::OK).force_close().finish(); + assert!(!resp.keep_alive()) + } + + #[test] + fn test_content_type() { + let resp = Response::build(StatusCode::OK) + .content_type("text/plain") + .body(Body::Empty); + assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "text/plain") + } + + #[test] + fn test_into_response() { + let resp: Response = "test".into(); + assert_eq!(resp.status(), StatusCode::OK); + assert_eq!( + resp.headers().get(CONTENT_TYPE).unwrap(), + HeaderValue::from_static("text/plain; charset=utf-8") + ); + assert_eq!(resp.status(), StatusCode::OK); + assert_eq!(resp.body().get_ref(), b"test"); + + let resp: Response = b"test".as_ref().into(); + assert_eq!(resp.status(), StatusCode::OK); + assert_eq!( + resp.headers().get(CONTENT_TYPE).unwrap(), + HeaderValue::from_static("application/octet-stream") + ); + assert_eq!(resp.status(), StatusCode::OK); + assert_eq!(resp.body().get_ref(), b"test"); + + let resp: Response = "test".to_owned().into(); + assert_eq!(resp.status(), StatusCode::OK); + assert_eq!( + resp.headers().get(CONTENT_TYPE).unwrap(), + HeaderValue::from_static("text/plain; charset=utf-8") + ); + assert_eq!(resp.status(), StatusCode::OK); + assert_eq!(resp.body().get_ref(), b"test"); + + let resp: Response = (&"test".to_owned()).into(); + assert_eq!(resp.status(), StatusCode::OK); + assert_eq!( + resp.headers().get(CONTENT_TYPE).unwrap(), + HeaderValue::from_static("text/plain; charset=utf-8") + ); + assert_eq!(resp.status(), StatusCode::OK); + assert_eq!(resp.body().get_ref(), b"test"); + + let b = Bytes::from_static(b"test"); + let resp: Response = b.into(); + assert_eq!(resp.status(), StatusCode::OK); + assert_eq!( + resp.headers().get(CONTENT_TYPE).unwrap(), + HeaderValue::from_static("application/octet-stream") + ); + assert_eq!(resp.status(), StatusCode::OK); + assert_eq!(resp.body().get_ref(), b"test"); + + let b = Bytes::from_static(b"test"); + let resp: Response = b.into(); + assert_eq!(resp.status(), StatusCode::OK); + assert_eq!( + resp.headers().get(CONTENT_TYPE).unwrap(), + HeaderValue::from_static("application/octet-stream") + ); + assert_eq!(resp.status(), StatusCode::OK); + assert_eq!(resp.body().get_ref(), b"test"); + + let b = BytesMut::from("test"); + let resp: Response = b.into(); + assert_eq!(resp.status(), StatusCode::OK); + assert_eq!( + resp.headers().get(CONTENT_TYPE).unwrap(), + HeaderValue::from_static("application/octet-stream") + ); + + assert_eq!(resp.status(), StatusCode::OK); + assert_eq!(resp.body().get_ref(), b"test"); + } + + #[test] + fn test_into_builder() { + let mut resp: Response = "test".into(); + assert_eq!(resp.status(), StatusCode::OK); + + resp.headers_mut().insert( + HeaderName::from_static("cookie"), + HeaderValue::from_static("cookie1=val100"), + ); + + let mut builder: ResponseBuilder = resp.into(); + let resp = builder.status(StatusCode::BAD_REQUEST).finish(); + assert_eq!(resp.status(), StatusCode::BAD_REQUEST); + + let cookie = resp.headers().get_all("Cookie").next().unwrap(); + assert_eq!(cookie.to_str().unwrap(), "cookie1=val100"); + } + + #[test] + fn response_builder_header_insert_kv() { + let mut res = Response::build(StatusCode::OK); + res.insert_header(("Content-Type", "application/octet-stream")); + let res = res.finish(); + + assert_eq!( + res.headers().get("Content-Type"), + Some(&HeaderValue::from_static("application/octet-stream")) + ); + } + + #[test] + fn response_builder_header_insert_typed() { + let mut res = Response::build(StatusCode::OK); + res.insert_header((header::CONTENT_TYPE, mime::APPLICATION_OCTET_STREAM)); + let res = res.finish(); + + assert_eq!( + res.headers().get("Content-Type"), + Some(&HeaderValue::from_static("application/octet-stream")) + ); + } + + #[test] + fn response_builder_header_append_kv() { + let mut res = Response::build(StatusCode::OK); + res.append_header(("Content-Type", "application/octet-stream")); + res.append_header(("Content-Type", "application/json")); + let res = res.finish(); + + let headers: Vec<_> = res.headers().get_all("Content-Type").cloned().collect(); + assert_eq!(headers.len(), 2); + assert!(headers.contains(&HeaderValue::from_static("application/octet-stream"))); + assert!(headers.contains(&HeaderValue::from_static("application/json"))); + } + + #[test] + fn response_builder_header_append_typed() { + let mut res = Response::build(StatusCode::OK); + res.append_header((header::CONTENT_TYPE, mime::APPLICATION_OCTET_STREAM)); + res.append_header((header::CONTENT_TYPE, mime::APPLICATION_JSON)); + let res = res.finish(); + + let headers: Vec<_> = res.headers().get_all("Content-Type").cloned().collect(); + assert_eq!(headers.len(), 2); + assert!(headers.contains(&HeaderValue::from_static("application/octet-stream"))); + assert!(headers.contains(&HeaderValue::from_static("application/json"))); + } +} From 5202bf03c170023cf2c20a22e00d74f36cf73097 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Wed, 14 Apr 2021 02:45:58 +0100 Subject: [PATCH 53/79] add some doc examples to response builder --- actix-http/src/response_builder.rs | 149 +++++++++++------------------ 1 file changed, 58 insertions(+), 91 deletions(-) diff --git a/actix-http/src/response_builder.rs b/actix-http/src/response_builder.rs index eab96b03d..70870267e 100644 --- a/actix-http/src/response_builder.rs +++ b/actix-http/src/response_builder.rs @@ -24,15 +24,44 @@ use crate::{ /// An HTTP response builder. /// -/// This type can be used to construct an instance of `Response` using a builder pattern. +/// Used to construct an instance of `Response` using a builder pattern. Response builders are often +/// created using [`Response::build`]. +/// +/// # Examples +/// ``` +/// use actix_http::{Response, ResponseBuilder, body, http::StatusCode, http::header}; +/// +/// # actix_rt::System::new().block_on(async { +/// let mut res: Response<_> = Response::build(StatusCode::OK) +/// .content_type(mime::APPLICATION_JSON) +/// .insert_header((header::SERVER, "my-app/1.0")) +/// .append_header((header::SET_COOKIE, "a=1")) +/// .append_header((header::SET_COOKIE, "b=2")) +/// .body("1234"); +/// +/// assert_eq!(res.status(), StatusCode::OK); +/// assert_eq!(body::to_bytes(res.take_body()).await.unwrap(), &b"1234"[..]); +/// +/// assert!(res.headers().contains_key("server")); +/// assert_eq!(res.headers().get_all("set-cookie").count(), 2); +/// # }) +/// ``` pub struct ResponseBuilder { head: Option, err: Option, } impl ResponseBuilder { - #[inline] /// Create response builder + /// + /// # Examples + /// ``` + /// use actix_http::{Response, ResponseBuilder, http::StatusCode}; + /// + /// let res: Response<_> = ResponseBuilder::default().finish(); + /// assert_eq!(res.status(), StatusCode::OK); + /// ``` + #[inline] pub fn new(status: StatusCode) -> Self { ResponseBuilder { head: Some(BoxedResponseHead::new(status)), @@ -41,6 +70,14 @@ impl ResponseBuilder { } /// Set HTTP status code of this response. + /// + /// # Examples + /// ``` + /// use actix_http::{ResponseBuilder, http::StatusCode}; + /// + /// let res = ResponseBuilder::default().status(StatusCode::NOT_FOUND).finish(); + /// assert_eq!(res.status(), StatusCode::NOT_FOUND); + /// ``` #[inline] pub fn status(&mut self, status: StatusCode) -> &mut Self { if let Some(parts) = parts(&mut self.head, &self.err) { @@ -51,14 +88,17 @@ impl ResponseBuilder { /// Insert a header, replacing any that were set with an equivalent field name. /// + /// # Examples /// ``` - /// # use actix_http::Response; - /// use actix_http::http::{header, StatusCode}; + /// use actix_http::{ResponseBuilder, http::header}; /// - /// Response::build(StatusCode::OK) + /// let res = ResponseBuilder::default() /// .insert_header((header::CONTENT_TYPE, mime::APPLICATION_JSON)) /// .insert_header(("X-TEST", "value")) /// .finish(); + /// + /// assert!(res.headers().contains_key("content-type")); + /// assert!(res.headers().contains_key("x-test")); /// ``` pub fn insert_header(&mut self, header: H) -> &mut Self where @@ -78,15 +118,18 @@ impl ResponseBuilder { /// Append a header, keeping any that were set with an equivalent field name. /// + /// # Examples /// ``` - /// # use actix_http::Response; - /// use actix_http::http::{header, StatusCode}; + /// use actix_http::{ResponseBuilder, http::header}; /// - /// Response::build(StatusCode::OK) + /// let res = ResponseBuilder::default() /// .append_header((header::CONTENT_TYPE, mime::APPLICATION_JSON)) /// .append_header(("X-TEST", "value1")) /// .append_header(("X-TEST", "value2")) /// .finish(); + /// + /// assert_eq!(res.headers().get_all("content-type").count(), 1); + /// assert_eq!(res.headers().get_all("x-test").count(), 2); /// ``` pub fn append_header(&mut self, header: H) -> &mut Self where @@ -254,6 +297,12 @@ fn parts<'a>( parts.as_mut().map(|r| &mut **r) } +impl Default for ResponseBuilder { + fn default() -> Self { + Self::new(StatusCode::OK) + } +} + /// Convert `Response` to a `ResponseBuilder`. Body get dropped. impl From> for ResponseBuilder { fn from(res: Response) -> ResponseBuilder { @@ -313,21 +362,9 @@ impl fmt::Debug for ResponseBuilder { #[cfg(test)] mod tests { - use bytes::BytesMut; - use super::*; use crate::body::Body; - use crate::http::header::{HeaderName, HeaderValue, CONTENT_TYPE, COOKIE}; - - #[test] - fn test_debug() { - let resp = Response::build(StatusCode::OK) - .append_header((COOKIE, HeaderValue::from_static("cookie1=value1; "))) - .append_header((COOKIE, HeaderValue::from_static("cookie2=value2; "))) - .finish(); - let dbg = format!("{:?}", resp); - assert!(dbg.contains("Response")); - } + use crate::http::header::{HeaderName, HeaderValue, CONTENT_TYPE}; #[test] fn test_basic_builder() { @@ -363,76 +400,6 @@ mod tests { assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "text/plain") } - #[test] - fn test_into_response() { - let resp: Response = "test".into(); - assert_eq!(resp.status(), StatusCode::OK); - assert_eq!( - resp.headers().get(CONTENT_TYPE).unwrap(), - HeaderValue::from_static("text/plain; charset=utf-8") - ); - assert_eq!(resp.status(), StatusCode::OK); - assert_eq!(resp.body().get_ref(), b"test"); - - let resp: Response = b"test".as_ref().into(); - assert_eq!(resp.status(), StatusCode::OK); - assert_eq!( - resp.headers().get(CONTENT_TYPE).unwrap(), - HeaderValue::from_static("application/octet-stream") - ); - assert_eq!(resp.status(), StatusCode::OK); - assert_eq!(resp.body().get_ref(), b"test"); - - let resp: Response = "test".to_owned().into(); - assert_eq!(resp.status(), StatusCode::OK); - assert_eq!( - resp.headers().get(CONTENT_TYPE).unwrap(), - HeaderValue::from_static("text/plain; charset=utf-8") - ); - assert_eq!(resp.status(), StatusCode::OK); - assert_eq!(resp.body().get_ref(), b"test"); - - let resp: Response = (&"test".to_owned()).into(); - assert_eq!(resp.status(), StatusCode::OK); - assert_eq!( - resp.headers().get(CONTENT_TYPE).unwrap(), - HeaderValue::from_static("text/plain; charset=utf-8") - ); - assert_eq!(resp.status(), StatusCode::OK); - assert_eq!(resp.body().get_ref(), b"test"); - - let b = Bytes::from_static(b"test"); - let resp: Response = b.into(); - assert_eq!(resp.status(), StatusCode::OK); - assert_eq!( - resp.headers().get(CONTENT_TYPE).unwrap(), - HeaderValue::from_static("application/octet-stream") - ); - assert_eq!(resp.status(), StatusCode::OK); - assert_eq!(resp.body().get_ref(), b"test"); - - let b = Bytes::from_static(b"test"); - let resp: Response = b.into(); - assert_eq!(resp.status(), StatusCode::OK); - assert_eq!( - resp.headers().get(CONTENT_TYPE).unwrap(), - HeaderValue::from_static("application/octet-stream") - ); - assert_eq!(resp.status(), StatusCode::OK); - assert_eq!(resp.body().get_ref(), b"test"); - - let b = BytesMut::from("test"); - let resp: Response = b.into(); - assert_eq!(resp.status(), StatusCode::OK); - assert_eq!( - resp.headers().get(CONTENT_TYPE).unwrap(), - HeaderValue::from_static("application/octet-stream") - ); - - assert_eq!(resp.status(), StatusCode::OK); - assert_eq!(resp.body().get_ref(), b"test"); - } - #[test] fn test_into_builder() { let mut resp: Response = "test".into(); From 1bfdfd1f4174c097e6fd75fcff394484b2074dc2 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Wed, 14 Apr 2021 02:57:28 +0100 Subject: [PATCH 54/79] implement parts as assoc method --- actix-http/src/response_builder.rs | 52 ++++++++++++++---------------- 1 file changed, 25 insertions(+), 27 deletions(-) diff --git a/actix-http/src/response_builder.rs b/actix-http/src/response_builder.rs index 70870267e..4d8cb4429 100644 --- a/actix-http/src/response_builder.rs +++ b/actix-http/src/response_builder.rs @@ -80,7 +80,7 @@ impl ResponseBuilder { /// ``` #[inline] pub fn status(&mut self, status: StatusCode) -> &mut Self { - if let Some(parts) = parts(&mut self.head, &self.err) { + if let Some(parts) = self.inner() { parts.status = status; } self @@ -104,7 +104,7 @@ impl ResponseBuilder { where H: IntoHeaderPair, { - if let Some(parts) = parts(&mut self.head, &self.err) { + if let Some(parts) = self.inner() { match header.try_into_header_pair() { Ok((key, value)) => { parts.headers.insert(key, value); @@ -135,7 +135,7 @@ impl ResponseBuilder { where H: IntoHeaderPair, { - if let Some(parts) = parts(&mut self.head, &self.err) { + if let Some(parts) = self.inner() { match header.try_into_header_pair() { Ok((key, value)) => parts.headers.append(key, value), Err(e) => self.err = Some(e.into()), @@ -148,7 +148,7 @@ impl ResponseBuilder { /// Set the custom reason for the response. #[inline] pub fn reason(&mut self, reason: &'static str) -> &mut Self { - if let Some(parts) = parts(&mut self.head, &self.err) { + if let Some(parts) = self.inner() { parts.reason = Some(reason); } self @@ -157,7 +157,7 @@ impl ResponseBuilder { /// Set connection type to KeepAlive #[inline] pub fn keep_alive(&mut self) -> &mut Self { - if let Some(parts) = parts(&mut self.head, &self.err) { + if let Some(parts) = self.inner() { parts.set_connection_type(ConnectionType::KeepAlive); } self @@ -169,7 +169,7 @@ impl ResponseBuilder { where V: IntoHeaderValue, { - if let Some(parts) = parts(&mut self.head, &self.err) { + if let Some(parts) = self.inner() { parts.set_connection_type(ConnectionType::Upgrade); } @@ -183,7 +183,7 @@ impl ResponseBuilder { /// Force close connection, even if it is marked as keep-alive #[inline] pub fn force_close(&mut self) -> &mut Self { - if let Some(parts) = parts(&mut self.head, &self.err) { + if let Some(parts) = self.inner() { parts.set_connection_type(ConnectionType::Close); } self @@ -195,7 +195,7 @@ impl ResponseBuilder { let mut buf = itoa::Buffer::new(); self.insert_header((header::CONTENT_LENGTH, buf.format(len))); - if let Some(parts) = parts(&mut self.head, &self.err) { + if let Some(parts) = self.inner() { parts.no_chunking(true); } self @@ -207,7 +207,7 @@ impl ResponseBuilder { where V: IntoHeaderValue, { - if let Some(parts) = parts(&mut self.head, &self.err) { + if let Some(parts) = self.inner() { match value.try_into_value() { Ok(value) => { parts.headers.insert(header::CONTENT_TYPE, value); @@ -232,17 +232,17 @@ impl ResponseBuilder { head.extensions.borrow_mut() } - /// Set a body and generate `Response`. + /// Generate response with a wrapped body. /// - /// `ResponseBuilder` can not be used after this call. + /// This `ResponseBuilder` will be left in a useless state. #[inline] pub fn body>(&mut self, body: B) -> Response { self.message_body(body.into()) } - /// Set a body and generate `Response`. + /// Generate response with a body. /// - /// `ResponseBuilder` can not be used after this call. + /// This `ResponseBuilder` will be left in a useless state. pub fn message_body(&mut self, body: B) -> Response { if let Some(e) = self.err.take() { return Response::from(Error::from(e)).into_body(); @@ -257,9 +257,9 @@ impl ResponseBuilder { } } - /// Set a streaming body and generate `Response`. + /// Generate response with a streaming body. /// - /// `ResponseBuilder` can not be used after this call. + /// This `ResponseBuilder` will be left in a useless state. #[inline] pub fn streaming(&mut self, stream: S) -> Response where @@ -269,32 +269,30 @@ impl ResponseBuilder { self.body(Body::from_message(BodyStream::new(stream))) } - /// Set an empty body and generate `Response` + /// Generate response with an empty body. /// - /// `ResponseBuilder` can not be used after this call. + /// This `ResponseBuilder` will be left in a useless state. #[inline] pub fn finish(&mut self) -> Response { self.body(Body::Empty) } - /// This method construct new `ResponseBuilder` + /// Create an owned `ResponseBuilder`, leaving the original in a useless state. pub fn take(&mut self) -> ResponseBuilder { ResponseBuilder { head: self.head.take(), err: self.err.take(), } } -} -#[inline] -fn parts<'a>( - parts: &'a mut Option, - err: &Option, -) -> Option<&'a mut ResponseHead> { - if err.is_some() { - return None; + /// Get access to the inner response head if there has been no error. + fn inner(&mut self) -> Option<&mut ResponseHead> { + if self.err.is_some() { + return None; + } + + self.head.as_mut().map(|r| &mut **r) } - parts.as_mut().map(|r| &mut **r) } impl Default for ResponseBuilder { From 037ac80a32d8361eb66596b013d8df02bae6a920 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Wed, 14 Apr 2021 03:23:15 +0100 Subject: [PATCH 55/79] document messagebody trait items --- actix-http/src/body/message_body.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/actix-http/src/body/message_body.rs b/actix-http/src/body/message_body.rs index ea2cfd22d..894a5fa98 100644 --- a/actix-http/src/body/message_body.rs +++ b/actix-http/src/body/message_body.rs @@ -12,10 +12,12 @@ use crate::error::Error; use super::BodySize; -/// Type that implement this trait can be streamed to a peer. +/// An interface for response bodies. pub trait MessageBody { + /// Body size hint. fn size(&self) -> BodySize; + /// Attempt to pull out the next chunk of body bytes. fn poll_next( self: Pin<&mut Self>, cx: &mut Context<'_>, From a9f26286f9bb60d88b6899cf91d2f868bb6585f4 Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Tue, 13 Apr 2021 21:20:45 -0700 Subject: [PATCH 56/79] reduce branches in h1 dispatcher poll_keepalive (#2089) --- actix-http/src/h1/dispatcher.rs | 24 +++++++----------------- actix-http/src/lib.rs | 4 ++-- 2 files changed, 9 insertions(+), 19 deletions(-) diff --git a/actix-http/src/h1/dispatcher.rs b/actix-http/src/h1/dispatcher.rs index e775846e9..3b272f0fb 100644 --- a/actix-http/src/h1/dispatcher.rs +++ b/actix-http/src/h1/dispatcher.rs @@ -649,11 +649,6 @@ where // go into Some> branch this.ka_timer.set(Some(sleep_until(deadline))); return self.poll_keepalive(cx); - } else { - this.flags.insert(Flags::READ_DISCONNECT); - if let Some(mut payload) = this.payload.take() { - payload.set_error(PayloadError::Incomplete(None)); - } } } } @@ -683,19 +678,14 @@ where } } else { // timeout on first request (slow request) return 408 - if !this.flags.contains(Flags::STARTED) { - trace!("Slow request timeout"); - let _ = self.as_mut().send_response( - Response::new(StatusCode::REQUEST_TIMEOUT) - .drop_body(), - ResponseBody::Other(Body::Empty), - ); - this = self.project(); - } else { - trace!("Keep-alive connection timeout"); - } + trace!("Slow request timeout"); + let _ = self.as_mut().send_response( + Response::new(StatusCode::REQUEST_TIMEOUT) + .drop_body(), + ResponseBody::Other(Body::Empty), + ); + this = self.project(); this.flags.insert(Flags::STARTED | Flags::SHUTDOWN); - this.state.set(State::None); } // still have unfinished task. try to reset and register keep-alive. } else if let Some(deadline) = diff --git a/actix-http/src/lib.rs b/actix-http/src/lib.rs index 8674834e0..4547f3ef2 100644 --- a/actix-http/src/lib.rs +++ b/actix-http/src/lib.rs @@ -60,8 +60,8 @@ pub use self::http_message::HttpMessage; pub use self::message::{Message, RequestHead, RequestHeadType, ResponseHead}; pub use self::payload::{Payload, PayloadStream}; pub use self::request::Request; -pub use self::response::{Response}; -pub use self::response_builder::{ResponseBuilder}; +pub use self::response::Response; +pub use self::response_builder::ResponseBuilder; pub use self::service::HttpService; pub mod http { From ff65f1d006d771a7e91212460639193e3463b5dd Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Wed, 14 Apr 2021 06:07:59 +0100 Subject: [PATCH 57/79] non exhaustive http errors (#2161) --- actix-http/CHANGES.md | 2 + actix-http/Cargo.toml | 1 + actix-http/src/error.rs | 507 +++++++++------------------------------- 3 files changed, 108 insertions(+), 402 deletions(-) diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 17ca7340f..84d6617f7 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -10,6 +10,7 @@ * The type parameter of `Response` no longer has a default. [#2152] * The `Message` variant of `body::Body` is now `Pin>`. [#2152] * `BodyStream` and `SizedStream` are no longer restricted to Unpin types. [#2152] +* Error enum types are marked `#[non_exhaustive]`. [#2161] ### Removed * `cookies` feature flag. [#2065] @@ -28,6 +29,7 @@ [#2152]: https://github.com/actix/actix-web/pull/2152 [#2159]: https://github.com/actix/actix-web/pull/2159 [#2158]: https://github.com/actix/actix-web/pull/2158 +[#2161]: https://github.com/actix/actix-web/pull/2161 ## 3.0.0-beta.5 - 2021-04-02 diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 4bef9e37c..361cae62f 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -62,6 +62,7 @@ local-channel = "0.1" once_cell = "1.5" log = "0.4" mime = "0.3" +paste = "1" percent-encoding = "2.1" pin-project = "1.0.0" pin-project-lite = "0.2" diff --git a/actix-http/src/error.rs b/actix-http/src/error.rs index 705ae7f06..68ad709a1 100644 --- a/actix-http/src/error.rs +++ b/actix-http/src/error.rs @@ -140,8 +140,8 @@ impl From for Error { } } -#[derive(Debug, Display)] -#[display(fmt = "UnknownError")] +#[derive(Debug, Display, Error)] +#[display(fmt = "Unknown Error")] struct UnitError; /// Returns [`StatusCode::INTERNAL_SERVER_ERROR`] for [`UnitError`]. @@ -190,38 +190,47 @@ impl ResponseError for header::InvalidHeaderValue { } } -/// A set of errors that can occur during parsing HTTP streams -#[derive(Debug, Display)] +/// A set of errors that can occur during parsing HTTP streams. +#[derive(Debug, Display, Error)] +#[non_exhaustive] pub enum ParseError { /// An invalid `Method`, such as `GE.T`. #[display(fmt = "Invalid Method specified")] Method, + /// An invalid `Uri`, such as `exam ple.domain`. #[display(fmt = "Uri error: {}", _0)] Uri(InvalidUri), + /// An invalid `HttpVersion`, such as `HTP/1.1` #[display(fmt = "Invalid HTTP version specified")] Version, + /// An invalid `Header`. #[display(fmt = "Invalid Header provided")] Header, + /// A message head is too large to be reasonable. #[display(fmt = "Message head is too large")] TooLarge, + /// A message reached EOF, but is not complete. #[display(fmt = "Message is incomplete")] Incomplete, + /// An invalid `Status`, such as `1337 ELITE`. #[display(fmt = "Invalid Status provided")] Status, + /// A timeout occurred waiting for an IO event. #[allow(dead_code)] #[display(fmt = "Timeout")] Timeout, - /// An `io::Error` that occurred while trying to read or write to a network - /// stream. + + /// An `io::Error` that occurred while trying to read or write to a network stream. #[display(fmt = "IO error: {}", _0)] Io(io::Error), + /// Parsing a field as string failed #[display(fmt = "UTF8 error: {}", _0)] Utf8(Utf8Error), @@ -273,17 +282,16 @@ impl From for ParseError { } /// A set of errors that can occur running blocking tasks in thread pool. -#[derive(Debug, Display)] +#[derive(Debug, Display, Error)] #[display(fmt = "Blocking thread pool is gone")] pub struct BlockingError; -impl std::error::Error for BlockingError {} - /// `InternalServerError` for `BlockingError` impl ResponseError for BlockingError {} -#[derive(Display, Debug)] -/// A set of errors that can occur during payload parsing +/// A set of errors that can occur during payload parsing. +#[derive(Debug, Display)] +#[non_exhaustive] pub enum PayloadError { /// A payload reached EOF, but is not complete. #[display( @@ -367,8 +375,9 @@ impl ResponseError for PayloadError { } } -#[derive(Debug, Display, From)] -/// A set of errors that can occur during dispatching HTTP requests +/// A set of errors that can occur during dispatching HTTP requests. +#[derive(Debug, Display, Error, From)] +#[non_exhaustive] pub enum DispatchError { /// Service error Service(Error), @@ -414,8 +423,9 @@ pub enum DispatchError { Unknown, } -/// A set of error that can occur during parsing content type -#[derive(Debug, PartialEq, Display, Error)] +/// A set of error that can occur during parsing content type. +#[derive(Debug, Display, Error)] +#[non_exhaustive] pub enum ContentTypeError { /// Can not parse content type #[display(fmt = "Can not parse content type")] @@ -426,6 +436,22 @@ pub enum ContentTypeError { UnknownEncoding, } +#[cfg(test)] +mod content_type_test_impls { + use super::*; + + impl std::cmp::PartialEq for ContentTypeError { + fn eq(&self, other: &Self) -> bool { + match self { + Self::ParseError => matches!(other, ContentTypeError::ParseError), + Self::UnknownEncoding => { + matches!(other, ContentTypeError::UnknownEncoding) + } + } + } + } +} + /// Return `BadRequest` for `ContentTypeError` impl ResponseError for ContentTypeError { fn status_code(&self) -> StatusCode { @@ -533,395 +559,72 @@ where } } -/// Helper function that creates wrapper of any error and generate *BAD -/// REQUEST* response. -#[allow(non_snake_case)] -pub fn ErrorBadRequest(err: T) -> Error -where - T: fmt::Debug + fmt::Display + 'static, -{ - InternalError::new(err, StatusCode::BAD_REQUEST).into() +macro_rules! error_helper { + ($name:ident, $status:ident) => { + paste::paste! { + #[doc = "Helper function that wraps any error and generates a `" $status "` response."] + #[allow(non_snake_case)] + pub fn $name(err: T) -> Error + where + T: fmt::Debug + fmt::Display + 'static, + { + InternalError::new(err, StatusCode::$status).into() + } + } + } } -/// Helper function that creates wrapper of any error and generate -/// *UNAUTHORIZED* response. -#[allow(non_snake_case)] -pub fn ErrorUnauthorized(err: T) -> Error -where - T: fmt::Debug + fmt::Display + 'static, -{ - InternalError::new(err, StatusCode::UNAUTHORIZED).into() -} - -/// Helper function that creates wrapper of any error and generate -/// *PAYMENT_REQUIRED* response. -#[allow(non_snake_case)] -pub fn ErrorPaymentRequired(err: T) -> Error -where - T: fmt::Debug + fmt::Display + 'static, -{ - InternalError::new(err, StatusCode::PAYMENT_REQUIRED).into() -} - -/// Helper function that creates wrapper of any error and generate *FORBIDDEN* -/// response. -#[allow(non_snake_case)] -pub fn ErrorForbidden(err: T) -> Error -where - T: fmt::Debug + fmt::Display + 'static, -{ - InternalError::new(err, StatusCode::FORBIDDEN).into() -} - -/// Helper function that creates wrapper of any error and generate *NOT FOUND* -/// response. -#[allow(non_snake_case)] -pub fn ErrorNotFound(err: T) -> Error -where - T: fmt::Debug + fmt::Display + 'static, -{ - InternalError::new(err, StatusCode::NOT_FOUND).into() -} - -/// Helper function that creates wrapper of any error and generate *METHOD NOT -/// ALLOWED* response. -#[allow(non_snake_case)] -pub fn ErrorMethodNotAllowed(err: T) -> Error -where - T: fmt::Debug + fmt::Display + 'static, -{ - InternalError::new(err, StatusCode::METHOD_NOT_ALLOWED).into() -} - -/// Helper function that creates wrapper of any error and generate *NOT -/// ACCEPTABLE* response. -#[allow(non_snake_case)] -pub fn ErrorNotAcceptable(err: T) -> Error -where - T: fmt::Debug + fmt::Display + 'static, -{ - InternalError::new(err, StatusCode::NOT_ACCEPTABLE).into() -} - -/// Helper function that creates wrapper of any error and generate *PROXY -/// AUTHENTICATION REQUIRED* response. -#[allow(non_snake_case)] -pub fn ErrorProxyAuthenticationRequired(err: T) -> Error -where - T: fmt::Debug + fmt::Display + 'static, -{ - InternalError::new(err, StatusCode::PROXY_AUTHENTICATION_REQUIRED).into() -} - -/// Helper function that creates wrapper of any error and generate *REQUEST -/// TIMEOUT* response. -#[allow(non_snake_case)] -pub fn ErrorRequestTimeout(err: T) -> Error -where - T: fmt::Debug + fmt::Display + 'static, -{ - InternalError::new(err, StatusCode::REQUEST_TIMEOUT).into() -} - -/// Helper function that creates wrapper of any error and generate *CONFLICT* -/// response. -#[allow(non_snake_case)] -pub fn ErrorConflict(err: T) -> Error -where - T: fmt::Debug + fmt::Display + 'static, -{ - InternalError::new(err, StatusCode::CONFLICT).into() -} - -/// Helper function that creates wrapper of any error and generate *GONE* -/// response. -#[allow(non_snake_case)] -pub fn ErrorGone(err: T) -> Error -where - T: fmt::Debug + fmt::Display + 'static, -{ - InternalError::new(err, StatusCode::GONE).into() -} - -/// Helper function that creates wrapper of any error and generate *LENGTH -/// REQUIRED* response. -#[allow(non_snake_case)] -pub fn ErrorLengthRequired(err: T) -> Error -where - T: fmt::Debug + fmt::Display + 'static, -{ - InternalError::new(err, StatusCode::LENGTH_REQUIRED).into() -} - -/// Helper function that creates wrapper of any error and generate -/// *PAYLOAD TOO LARGE* response. -#[allow(non_snake_case)] -pub fn ErrorPayloadTooLarge(err: T) -> Error -where - T: fmt::Debug + fmt::Display + 'static, -{ - InternalError::new(err, StatusCode::PAYLOAD_TOO_LARGE).into() -} - -/// Helper function that creates wrapper of any error and generate -/// *URI TOO LONG* response. -#[allow(non_snake_case)] -pub fn ErrorUriTooLong(err: T) -> Error -where - T: fmt::Debug + fmt::Display + 'static, -{ - InternalError::new(err, StatusCode::URI_TOO_LONG).into() -} - -/// Helper function that creates wrapper of any error and generate -/// *UNSUPPORTED MEDIA TYPE* response. -#[allow(non_snake_case)] -pub fn ErrorUnsupportedMediaType(err: T) -> Error -where - T: fmt::Debug + fmt::Display + 'static, -{ - InternalError::new(err, StatusCode::UNSUPPORTED_MEDIA_TYPE).into() -} - -/// Helper function that creates wrapper of any error and generate -/// *RANGE NOT SATISFIABLE* response. -#[allow(non_snake_case)] -pub fn ErrorRangeNotSatisfiable(err: T) -> Error -where - T: fmt::Debug + fmt::Display + 'static, -{ - InternalError::new(err, StatusCode::RANGE_NOT_SATISFIABLE).into() -} - -/// Helper function that creates wrapper of any error and generate -/// *IM A TEAPOT* response. -#[allow(non_snake_case)] -pub fn ErrorImATeapot(err: T) -> Error -where - T: fmt::Debug + fmt::Display + 'static, -{ - InternalError::new(err, StatusCode::IM_A_TEAPOT).into() -} - -/// Helper function that creates wrapper of any error and generate -/// *MISDIRECTED REQUEST* response. -#[allow(non_snake_case)] -pub fn ErrorMisdirectedRequest(err: T) -> Error -where - T: fmt::Debug + fmt::Display + 'static, -{ - InternalError::new(err, StatusCode::MISDIRECTED_REQUEST).into() -} - -/// Helper function that creates wrapper of any error and generate -/// *UNPROCESSABLE ENTITY* response. -#[allow(non_snake_case)] -pub fn ErrorUnprocessableEntity(err: T) -> Error -where - T: fmt::Debug + fmt::Display + 'static, -{ - InternalError::new(err, StatusCode::UNPROCESSABLE_ENTITY).into() -} - -/// Helper function that creates wrapper of any error and generate -/// *LOCKED* response. -#[allow(non_snake_case)] -pub fn ErrorLocked(err: T) -> Error -where - T: fmt::Debug + fmt::Display + 'static, -{ - InternalError::new(err, StatusCode::LOCKED).into() -} - -/// Helper function that creates wrapper of any error and generate -/// *FAILED DEPENDENCY* response. -#[allow(non_snake_case)] -pub fn ErrorFailedDependency(err: T) -> Error -where - T: fmt::Debug + fmt::Display + 'static, -{ - InternalError::new(err, StatusCode::FAILED_DEPENDENCY).into() -} - -/// Helper function that creates wrapper of any error and generate -/// *UPGRADE REQUIRED* response. -#[allow(non_snake_case)] -pub fn ErrorUpgradeRequired(err: T) -> Error -where - T: fmt::Debug + fmt::Display + 'static, -{ - InternalError::new(err, StatusCode::UPGRADE_REQUIRED).into() -} - -/// Helper function that creates wrapper of any error and generate -/// *PRECONDITION FAILED* response. -#[allow(non_snake_case)] -pub fn ErrorPreconditionFailed(err: T) -> Error -where - T: fmt::Debug + fmt::Display + 'static, -{ - InternalError::new(err, StatusCode::PRECONDITION_FAILED).into() -} - -/// Helper function that creates wrapper of any error and generate -/// *PRECONDITION REQUIRED* response. -#[allow(non_snake_case)] -pub fn ErrorPreconditionRequired(err: T) -> Error -where - T: fmt::Debug + fmt::Display + 'static, -{ - InternalError::new(err, StatusCode::PRECONDITION_REQUIRED).into() -} - -/// Helper function that creates wrapper of any error and generate -/// *TOO MANY REQUESTS* response. -#[allow(non_snake_case)] -pub fn ErrorTooManyRequests(err: T) -> Error -where - T: fmt::Debug + fmt::Display + 'static, -{ - InternalError::new(err, StatusCode::TOO_MANY_REQUESTS).into() -} - -/// Helper function that creates wrapper of any error and generate -/// *REQUEST HEADER FIELDS TOO LARGE* response. -#[allow(non_snake_case)] -pub fn ErrorRequestHeaderFieldsTooLarge(err: T) -> Error -where - T: fmt::Debug + fmt::Display + 'static, -{ - InternalError::new(err, StatusCode::REQUEST_HEADER_FIELDS_TOO_LARGE).into() -} - -/// Helper function that creates wrapper of any error and generate -/// *UNAVAILABLE FOR LEGAL REASONS* response. -#[allow(non_snake_case)] -pub fn ErrorUnavailableForLegalReasons(err: T) -> Error -where - T: fmt::Debug + fmt::Display + 'static, -{ - InternalError::new(err, StatusCode::UNAVAILABLE_FOR_LEGAL_REASONS).into() -} - -/// Helper function that creates wrapper of any error and generate -/// *EXPECTATION FAILED* response. -#[allow(non_snake_case)] -pub fn ErrorExpectationFailed(err: T) -> Error -where - T: fmt::Debug + fmt::Display + 'static, -{ - InternalError::new(err, StatusCode::EXPECTATION_FAILED).into() -} - -/// Helper function that creates wrapper of any error and -/// generate *INTERNAL SERVER ERROR* response. -#[allow(non_snake_case)] -pub fn ErrorInternalServerError(err: T) -> Error -where - T: fmt::Debug + fmt::Display + 'static, -{ - InternalError::new(err, StatusCode::INTERNAL_SERVER_ERROR).into() -} - -/// Helper function that creates wrapper of any error and -/// generate *NOT IMPLEMENTED* response. -#[allow(non_snake_case)] -pub fn ErrorNotImplemented(err: T) -> Error -where - T: fmt::Debug + fmt::Display + 'static, -{ - InternalError::new(err, StatusCode::NOT_IMPLEMENTED).into() -} - -/// Helper function that creates wrapper of any error and -/// generate *BAD GATEWAY* response. -#[allow(non_snake_case)] -pub fn ErrorBadGateway(err: T) -> Error -where - T: fmt::Debug + fmt::Display + 'static, -{ - InternalError::new(err, StatusCode::BAD_GATEWAY).into() -} - -/// Helper function that creates wrapper of any error and -/// generate *SERVICE UNAVAILABLE* response. -#[allow(non_snake_case)] -pub fn ErrorServiceUnavailable(err: T) -> Error -where - T: fmt::Debug + fmt::Display + 'static, -{ - InternalError::new(err, StatusCode::SERVICE_UNAVAILABLE).into() -} - -/// Helper function that creates wrapper of any error and -/// generate *GATEWAY TIMEOUT* response. -#[allow(non_snake_case)] -pub fn ErrorGatewayTimeout(err: T) -> Error -where - T: fmt::Debug + fmt::Display + 'static, -{ - InternalError::new(err, StatusCode::GATEWAY_TIMEOUT).into() -} - -/// Helper function that creates wrapper of any error and -/// generate *HTTP VERSION NOT SUPPORTED* response. -#[allow(non_snake_case)] -pub fn ErrorHttpVersionNotSupported(err: T) -> Error -where - T: fmt::Debug + fmt::Display + 'static, -{ - InternalError::new(err, StatusCode::HTTP_VERSION_NOT_SUPPORTED).into() -} - -/// Helper function that creates wrapper of any error and -/// generate *VARIANT ALSO NEGOTIATES* response. -#[allow(non_snake_case)] -pub fn ErrorVariantAlsoNegotiates(err: T) -> Error -where - T: fmt::Debug + fmt::Display + 'static, -{ - InternalError::new(err, StatusCode::VARIANT_ALSO_NEGOTIATES).into() -} - -/// Helper function that creates wrapper of any error and -/// generate *INSUFFICIENT STORAGE* response. -#[allow(non_snake_case)] -pub fn ErrorInsufficientStorage(err: T) -> Error -where - T: fmt::Debug + fmt::Display + 'static, -{ - InternalError::new(err, StatusCode::INSUFFICIENT_STORAGE).into() -} - -/// Helper function that creates wrapper of any error and -/// generate *LOOP DETECTED* response. -#[allow(non_snake_case)] -pub fn ErrorLoopDetected(err: T) -> Error -where - T: fmt::Debug + fmt::Display + 'static, -{ - InternalError::new(err, StatusCode::LOOP_DETECTED).into() -} - -/// Helper function that creates wrapper of any error and -/// generate *NOT EXTENDED* response. -#[allow(non_snake_case)] -pub fn ErrorNotExtended(err: T) -> Error -where - T: fmt::Debug + fmt::Display + 'static, -{ - InternalError::new(err, StatusCode::NOT_EXTENDED).into() -} - -/// Helper function that creates wrapper of any error and -/// generate *NETWORK AUTHENTICATION REQUIRED* response. -#[allow(non_snake_case)] -pub fn ErrorNetworkAuthenticationRequired(err: T) -> Error -where - T: fmt::Debug + fmt::Display + 'static, -{ - InternalError::new(err, StatusCode::NETWORK_AUTHENTICATION_REQUIRED).into() -} +error_helper!(ErrorBadRequest, BAD_REQUEST); +error_helper!(ErrorUnauthorized, UNAUTHORIZED); +error_helper!(ErrorPaymentRequired, PAYMENT_REQUIRED); +error_helper!(ErrorForbidden, FORBIDDEN); +error_helper!(ErrorNotFound, NOT_FOUND); +error_helper!(ErrorMethodNotAllowed, METHOD_NOT_ALLOWED); +error_helper!(ErrorNotAcceptable, NOT_ACCEPTABLE); +error_helper!( + ErrorProxyAuthenticationRequired, + PROXY_AUTHENTICATION_REQUIRED +); +error_helper!(ErrorRequestTimeout, REQUEST_TIMEOUT); +error_helper!(ErrorConflict, CONFLICT); +error_helper!(ErrorGone, GONE); +error_helper!(ErrorLengthRequired, LENGTH_REQUIRED); +error_helper!(ErrorPayloadTooLarge, PAYLOAD_TOO_LARGE); +error_helper!(ErrorUriTooLong, URI_TOO_LONG); +error_helper!(ErrorUnsupportedMediaType, UNSUPPORTED_MEDIA_TYPE); +error_helper!(ErrorRangeNotSatisfiable, RANGE_NOT_SATISFIABLE); +error_helper!(ErrorImATeapot, IM_A_TEAPOT); +error_helper!(ErrorMisdirectedRequest, MISDIRECTED_REQUEST); +error_helper!(ErrorUnprocessableEntity, UNPROCESSABLE_ENTITY); +error_helper!(ErrorLocked, LOCKED); +error_helper!(ErrorFailedDependency, FAILED_DEPENDENCY); +error_helper!(ErrorUpgradeRequired, UPGRADE_REQUIRED); +error_helper!(ErrorPreconditionFailed, PRECONDITION_FAILED); +error_helper!(ErrorPreconditionRequired, PRECONDITION_REQUIRED); +error_helper!(ErrorTooManyRequests, TOO_MANY_REQUESTS); +error_helper!( + ErrorRequestHeaderFieldsTooLarge, + REQUEST_HEADER_FIELDS_TOO_LARGE +); +error_helper!( + ErrorUnavailableForLegalReasons, + UNAVAILABLE_FOR_LEGAL_REASONS +); +error_helper!(ErrorExpectationFailed, EXPECTATION_FAILED); +error_helper!(ErrorInternalServerError, INTERNAL_SERVER_ERROR); +error_helper!(ErrorNotImplemented, NOT_IMPLEMENTED); +error_helper!(ErrorBadGateway, BAD_GATEWAY); +error_helper!(ErrorServiceUnavailable, SERVICE_UNAVAILABLE); +error_helper!(ErrorGatewayTimeout, GATEWAY_TIMEOUT); +error_helper!(ErrorHttpVersionNotSupported, HTTP_VERSION_NOT_SUPPORTED); +error_helper!(ErrorVariantAlsoNegotiates, VARIANT_ALSO_NEGOTIATES); +error_helper!(ErrorInsufficientStorage, INSUFFICIENT_STORAGE); +error_helper!(ErrorLoopDetected, LOOP_DETECTED); +error_helper!(ErrorNotExtended, NOT_EXTENDED); +error_helper!( + ErrorNetworkAuthenticationRequired, + NETWORK_AUTHENTICATION_REQUIRED +); #[cfg(test)] mod tests { From 64bed506c25ffa7f45a9fe2f16f4286edb17fd62 Mon Sep 17 00:00:00 2001 From: "D.Loh" Date: Thu, 15 Apr 2021 11:11:30 -0700 Subject: [PATCH 58/79] chore: update benchmaks to round 20 (#2163) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 508736279..c85c0652f 100644 --- a/README.md +++ b/README.md @@ -90,7 +90,7 @@ You may consider checking out ## Benchmarks One of the fastest web frameworks available according to the -[TechEmpower Framework Benchmark](https://www.techempower.com/benchmarks/#section=data-r19). +[TechEmpower Framework Benchmark](https://www.techempower.com/benchmarks/#section=data-r20&test=composite). ## License From 845c02cb86f3ce5ff210a530855d86e1b5471c60 Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Thu, 15 Apr 2021 16:54:51 -0700 Subject: [PATCH 59/79] Add responder impl for Cow (#2164) --- actix-http/src/body/body.rs | 16 ++++++- src/responder.rs | 85 +++++++++++++++++++------------------ 2 files changed, 57 insertions(+), 44 deletions(-) diff --git a/actix-http/src/body/body.rs b/actix-http/src/body/body.rs index 5fc461d41..4fe18338a 100644 --- a/actix-http/src/body/body.rs +++ b/actix-http/src/body/body.rs @@ -1,4 +1,5 @@ use std::{ + borrow::Cow, fmt, mem, pin::Pin, task::{Context, Poll}, @@ -118,12 +119,23 @@ impl From for Body { } } -impl<'a> From<&'a String> for Body { - fn from(s: &'a String) -> Body { +impl From<&'_ String> for Body { + fn from(s: &String) -> Body { Body::Bytes(Bytes::copy_from_slice(AsRef::<[u8]>::as_ref(&s))) } } +impl From> for Body { + fn from(s: Cow<'_, str>) -> Body { + match s { + Cow::Owned(s) => Body::from(s), + Cow::Borrowed(s) => { + Body::Bytes(Bytes::copy_from_slice(AsRef::<[u8]>::as_ref(s))) + } + } + } +} + impl From for Body { fn from(s: Bytes) -> Body { Body::Bytes(s) diff --git a/src/responder.rs b/src/responder.rs index 2348e9276..7b8288ed8 100644 --- a/src/responder.rs +++ b/src/responder.rs @@ -1,4 +1,4 @@ -use std::fmt; +use std::{borrow::Cow, fmt}; use actix_http::{ body::Body, @@ -117,53 +117,29 @@ impl Responder for (T, StatusCode) { } } -impl Responder for &'static str { - fn respond_to(self, _: &HttpRequest) -> HttpResponse { - HttpResponse::Ok() - .content_type(mime::TEXT_PLAIN_UTF_8) - .body(self) - } +macro_rules! impl_responder { + ($res: ty, $ct: path) => { + impl Responder for $res { + fn respond_to(self, _: &HttpRequest) -> HttpResponse { + HttpResponse::Ok().content_type($ct).body(self) + } + } + }; } -impl Responder for &'static [u8] { - fn respond_to(self, _: &HttpRequest) -> HttpResponse { - HttpResponse::Ok() - .content_type(mime::APPLICATION_OCTET_STREAM) - .body(self) - } -} +impl_responder!(&'static str, mime::TEXT_PLAIN_UTF_8); -impl Responder for String { - fn respond_to(self, _: &HttpRequest) -> HttpResponse { - HttpResponse::Ok() - .content_type(mime::TEXT_PLAIN_UTF_8) - .body(self) - } -} +impl_responder!(String, mime::TEXT_PLAIN_UTF_8); -impl<'a> Responder for &'a String { - fn respond_to(self, _: &HttpRequest) -> HttpResponse { - HttpResponse::Ok() - .content_type(mime::TEXT_PLAIN_UTF_8) - .body(self) - } -} +impl_responder!(&'_ String, mime::TEXT_PLAIN_UTF_8); -impl Responder for Bytes { - fn respond_to(self, _: &HttpRequest) -> HttpResponse { - HttpResponse::Ok() - .content_type(mime::APPLICATION_OCTET_STREAM) - .body(self) - } -} +impl_responder!(Cow<'_, str>, mime::TEXT_PLAIN_UTF_8); -impl Responder for BytesMut { - fn respond_to(self, _: &HttpRequest) -> HttpResponse { - HttpResponse::Ok() - .content_type(mime::APPLICATION_OCTET_STREAM) - .body(self) - } -} +impl_responder!(&'static [u8], mime::APPLICATION_OCTET_STREAM); + +impl_responder!(Bytes, mime::APPLICATION_OCTET_STREAM); + +impl_responder!(BytesMut, mime::APPLICATION_OCTET_STREAM); /// Allows overriding status code and headers for a responder. pub struct CustomResponder { @@ -358,6 +334,31 @@ pub(crate) mod tests { HeaderValue::from_static("text/plain; charset=utf-8") ); + let s = String::from("test"); + let resp = Cow::Borrowed(s.as_str()).respond_to(&req); + assert_eq!(resp.status(), StatusCode::OK); + assert_eq!(resp.body().bin_ref(), b"test"); + assert_eq!( + resp.headers().get(CONTENT_TYPE).unwrap(), + HeaderValue::from_static("text/plain; charset=utf-8") + ); + + let resp = Cow::<'_, str>::Owned(s).respond_to(&req); + assert_eq!(resp.status(), StatusCode::OK); + assert_eq!(resp.body().bin_ref(), b"test"); + assert_eq!( + resp.headers().get(CONTENT_TYPE).unwrap(), + HeaderValue::from_static("text/plain; charset=utf-8") + ); + + let resp = Cow::Borrowed("test").respond_to(&req); + assert_eq!(resp.status(), StatusCode::OK); + assert_eq!(resp.body().bin_ref(), b"test"); + assert_eq!( + resp.headers().get(CONTENT_TYPE).unwrap(), + HeaderValue::from_static("text/plain; charset=utf-8") + ); + let resp = Bytes::from_static(b"test").respond_to(&req); assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.body().bin_ref(), b"test"); From 8d88a0a9af232f3d554002b20f9fa61e18e6aa6c Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Thu, 15 Apr 2021 22:05:06 +0100 Subject: [PATCH 60/79] rename header generator macros --- src/http/header/accept.rs | 10 +- src/http/header/accept_charset.rs | 4 +- src/http/header/accept_encoding.rs | 10 +- src/http/header/accept_language.rs | 9 +- src/http/header/allow.rs | 10 +- src/http/header/cache_control.rs | 6 +- src/http/header/content_language.rs | 6 +- src/http/header/content_range.rs | 24 +- src/http/header/content_type.rs | 4 +- src/http/header/date.rs | 4 +- src/http/header/etag.rs | 32 +-- src/http/header/expires.rs | 4 +- src/http/header/if_match.rs | 8 +- src/http/header/if_modified_since.rs | 4 +- src/http/header/if_none_match.rs | 12 +- src/http/header/if_range.rs | 6 +- src/http/header/if_unmodified_since.rs | 4 +- src/http/header/last_modified.rs | 5 +- src/http/header/macros.rs | 300 ++++++++++++++++++++++ src/http/header/mod.rs | 330 +------------------------ 20 files changed, 395 insertions(+), 397 deletions(-) create mode 100644 src/http/header/macros.rs diff --git a/src/http/header/accept.rs b/src/http/header/accept.rs index 1ec94e353..1b6a963da 100644 --- a/src/http/header/accept.rs +++ b/src/http/header/accept.rs @@ -5,7 +5,7 @@ use mime::Mime; use super::{qitem, QualityItem}; use crate::http::header; -crate::header! { +crate::__define_common_header! { /// `Accept` header, defined in [RFC7231](http://tools.ietf.org/html/rfc7231#section-5.3.2) /// /// The `Accept` header field can be used by user agents to specify @@ -81,14 +81,14 @@ crate::header! { test_accept { // Tests from the RFC - test_header!( + crate::__common_header_test!( test1, vec![b"audio/*; q=0.2, audio/basic"], Some(Accept(vec![ QualityItem::new("audio/*".parse().unwrap(), q(200)), qitem("audio/basic".parse().unwrap()), ]))); - test_header!( + crate::__common_header_test!( test2, vec![b"text/plain; q=0.5, text/html, text/x-dvi; q=0.8, text/x-c"], Some(Accept(vec![ @@ -100,13 +100,13 @@ crate::header! { qitem("text/x-c".parse().unwrap()), ]))); // Custom tests - test_header!( + crate::__common_header_test!( test3, vec![b"text/plain; charset=utf-8"], Some(Accept(vec![ qitem(mime::TEXT_PLAIN_UTF_8), ]))); - test_header!( + crate::__common_header_test!( test4, vec![b"text/plain; charset=utf-8; q=0.5"], Some(Accept(vec![ diff --git a/src/http/header/accept_charset.rs b/src/http/header/accept_charset.rs index 9932ac57a..2c6a0b9f6 100644 --- a/src/http/header/accept_charset.rs +++ b/src/http/header/accept_charset.rs @@ -1,6 +1,6 @@ use super::{Charset, QualityItem, ACCEPT_CHARSET}; -crate::header! { +crate::__define_common_header! { /// `Accept-Charset` header, defined in /// [RFC7231](http://tools.ietf.org/html/rfc7231#section-5.3.3) /// @@ -57,6 +57,6 @@ crate::header! { test_accept_charset { // Test case from RFC - test_header!(test1, vec![b"iso-8859-5, unicode-1-1;q=0.8"]); + crate::__common_header_test!(test1, vec![b"iso-8859-5, unicode-1-1;q=0.8"]); } } diff --git a/src/http/header/accept_encoding.rs b/src/http/header/accept_encoding.rs index e59351708..734a435b3 100644 --- a/src/http/header/accept_encoding.rs +++ b/src/http/header/accept_encoding.rs @@ -64,12 +64,12 @@ header! { test_accept_encoding { // From the RFC - test_header!(test1, vec![b"compress, gzip"]); - test_header!(test2, vec![b""], Some(AcceptEncoding(vec![]))); - test_header!(test3, vec![b"*"]); + crate::__common_header_test!(test1, vec![b"compress, gzip"]); + crate::__common_header_test!(test2, vec![b""], Some(AcceptEncoding(vec![]))); + crate::__common_header_test!(test3, vec![b"*"]); // Note: Removed quality 1 from gzip - test_header!(test4, vec![b"compress;q=0.5, gzip"]); + crate::__common_header_test!(test4, vec![b"compress;q=0.5, gzip"]); // Note: Removed quality 1 from gzip - test_header!(test5, vec![b"gzip, identity; q=0.5, *;q=0"]); + crate::__common_header_test!(test5, vec![b"gzip, identity; q=0.5, *;q=0"]); } } diff --git a/src/http/header/accept_language.rs b/src/http/header/accept_language.rs index 2963844af..5fab4f79c 100644 --- a/src/http/header/accept_language.rs +++ b/src/http/header/accept_language.rs @@ -1,7 +1,8 @@ -use super::{QualityItem, ACCEPT_LANGUAGE}; use language_tags::LanguageTag; -crate::header! { +use super::{QualityItem, ACCEPT_LANGUAGE}; + +crate::__define_common_header! { /// `Accept-Language` header, defined in /// [RFC7231](http://tools.ietf.org/html/rfc7231#section-5.3.5) /// @@ -56,9 +57,9 @@ crate::header! { test_accept_language { // From the RFC - test_header!(test1, vec![b"da, en-gb;q=0.8, en;q=0.7"]); + crate::__common_header_test!(test1, vec![b"da, en-gb;q=0.8, en;q=0.7"]); // Own test - test_header!( + crate::__common_header_test!( test2, vec![b"en-US, en; q=0.5, fr"], Some(AcceptLanguage(vec![ qitem("en-US".parse().unwrap()), diff --git a/src/http/header/allow.rs b/src/http/header/allow.rs index e1f2bb4b6..15a627b8f 100644 --- a/src/http/header/allow.rs +++ b/src/http/header/allow.rs @@ -1,7 +1,7 @@ -use actix_http::http::Method; use crate::http::header; +use actix_http::http::Method; -crate::header! { +crate::__define_common_header! { /// `Allow` header, defined in [RFC7231](http://tools.ietf.org/html/rfc7231#section-7.4.1) /// /// The `Allow` header field lists the set of methods advertised as @@ -49,12 +49,12 @@ crate::header! { test_allow { // From the RFC - test_header!( + crate::__common_header_test!( test1, vec![b"GET, HEAD, PUT"], Some(HeaderField(vec![Method::GET, Method::HEAD, Method::PUT]))); // Own tests - test_header!( + crate::__common_header_test!( test2, vec![b"OPTIONS, GET, PUT, POST, DELETE, HEAD, TRACE, CONNECT, PATCH"], Some(HeaderField(vec![ @@ -67,7 +67,7 @@ crate::header! { Method::TRACE, Method::CONNECT, Method::PATCH]))); - test_header!( + crate::__common_header_test!( test3, vec![b""], Some(HeaderField(Vec::::new()))); diff --git a/src/http/header/cache_control.rs b/src/http/header/cache_control.rs index 6f020a931..891ba7c79 100644 --- a/src/http/header/cache_control.rs +++ b/src/http/header/cache_control.rs @@ -51,9 +51,9 @@ use crate::http::header; #[derive(PartialEq, Clone, Debug)] pub struct CacheControl(pub Vec); -__hyper__deref!(CacheControl => Vec); +crate::__common_header_deref!(CacheControl => Vec); -//TODO: this could just be the header! macro +// TODO: this could just be the __define_common_header! macro impl Header for CacheControl { fn name() -> header::HeaderName { header::CACHE_CONTROL @@ -75,7 +75,7 @@ impl Header for CacheControl { impl fmt::Display for CacheControl { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt_comma_delimited(f, &self[..]) + fmt_comma_delimited(f, &self.0[..]) } } diff --git a/src/http/header/content_language.rs b/src/http/header/content_language.rs index 5dd8f72a5..41e6d9eff 100644 --- a/src/http/header/content_language.rs +++ b/src/http/header/content_language.rs @@ -1,7 +1,7 @@ use super::{QualityItem, CONTENT_LANGUAGE}; use language_tags::LanguageTag; -crate::header! { +crate::__define_common_header! { /// `Content-Language` header, defined in /// [RFC7231](https://tools.ietf.org/html/rfc7231#section-3.1.3.2) /// @@ -52,7 +52,7 @@ crate::header! { (ContentLanguage, CONTENT_LANGUAGE) => (QualityItem)+ test_content_language { - test_header!(test1, vec![b"da"]); - test_header!(test2, vec![b"mi, en"]); + crate::__common_header_test!(test1, vec![b"da"]); + crate::__common_header_test!(test2, vec![b"mi, en"]); } } diff --git a/src/http/header/content_range.rs b/src/http/header/content_range.rs index dfcf24251..e3a8450cb 100644 --- a/src/http/header/content_range.rs +++ b/src/http/header/content_range.rs @@ -6,65 +6,65 @@ use super::{ HeaderValue, IntoHeaderValue, InvalidHeaderValue, Writer, CONTENT_RANGE, }; -crate::header! { +crate::__define_common_header! { /// `Content-Range` header, defined in /// [RFC7233](http://tools.ietf.org/html/rfc7233#section-4.2) (ContentRange, CONTENT_RANGE) => [ContentRangeSpec] test_content_range { - test_header!(test_bytes, + crate::__common_header_test!(test_bytes, vec![b"bytes 0-499/500"], Some(ContentRange(ContentRangeSpec::Bytes { range: Some((0, 499)), instance_length: Some(500) }))); - test_header!(test_bytes_unknown_len, + crate::__common_header_test!(test_bytes_unknown_len, vec![b"bytes 0-499/*"], Some(ContentRange(ContentRangeSpec::Bytes { range: Some((0, 499)), instance_length: None }))); - test_header!(test_bytes_unknown_range, + crate::__common_header_test!(test_bytes_unknown_range, vec![b"bytes */500"], Some(ContentRange(ContentRangeSpec::Bytes { range: None, instance_length: Some(500) }))); - test_header!(test_unregistered, + crate::__common_header_test!(test_unregistered, vec![b"seconds 1-2"], Some(ContentRange(ContentRangeSpec::Unregistered { unit: "seconds".to_owned(), resp: "1-2".to_owned() }))); - test_header!(test_no_len, + crate::__common_header_test!(test_no_len, vec![b"bytes 0-499"], None::); - test_header!(test_only_unit, + crate::__common_header_test!(test_only_unit, vec![b"bytes"], None::); - test_header!(test_end_less_than_start, + crate::__common_header_test!(test_end_less_than_start, vec![b"bytes 499-0/500"], None::); - test_header!(test_blank, + crate::__common_header_test!(test_blank, vec![b""], None::); - test_header!(test_bytes_many_spaces, + crate::__common_header_test!(test_bytes_many_spaces, vec![b"bytes 1-2/500 3"], None::); - test_header!(test_bytes_many_slashes, + crate::__common_header_test!(test_bytes_many_slashes, vec![b"bytes 1-2/500/600"], None::); - test_header!(test_bytes_many_dashes, + crate::__common_header_test!(test_bytes_many_dashes, vec![b"bytes 1-2-3/500"], None::); diff --git a/src/http/header/content_type.rs b/src/http/header/content_type.rs index a85e64ba9..65cb2a986 100644 --- a/src/http/header/content_type.rs +++ b/src/http/header/content_type.rs @@ -1,7 +1,7 @@ use super::CONTENT_TYPE; use mime::Mime; -crate::header! { +crate::__define_common_header! { /// `Content-Type` header, defined in /// [RFC7231](http://tools.ietf.org/html/rfc7231#section-3.1.1.5) /// @@ -52,7 +52,7 @@ crate::header! { (ContentType, CONTENT_TYPE) => [Mime] test_content_type { - test_header!( + crate::__common_header_test!( test1, vec![b"text/html"], Some(HeaderField(mime::TEXT_HTML))); diff --git a/src/http/header/date.rs b/src/http/header/date.rs index faceefb4f..982a1455c 100644 --- a/src/http/header/date.rs +++ b/src/http/header/date.rs @@ -1,7 +1,7 @@ use super::{HttpDate, DATE}; use std::time::SystemTime; -crate::header! { +crate::__define_common_header! { /// `Date` header, defined in [RFC7231](http://tools.ietf.org/html/rfc7231#section-7.1.1.2) /// /// The `Date` header field represents the date and time at which the @@ -32,7 +32,7 @@ crate::header! { (Date, DATE) => [HttpDate] test_date { - test_header!(test1, vec![b"Tue, 15 Nov 1994 08:12:31 GMT"]); + crate::__common_header_test!(test1, vec![b"Tue, 15 Nov 1994 08:12:31 GMT"]); } } diff --git a/src/http/header/etag.rs b/src/http/header/etag.rs index 8972564d0..b121fe26f 100644 --- a/src/http/header/etag.rs +++ b/src/http/header/etag.rs @@ -1,6 +1,6 @@ use super::{EntityTag, ETAG}; -crate::header! { +crate::__define_common_header! { /// `ETag` header, defined in [RFC7232](http://tools.ietf.org/html/rfc7232#section-2.3) /// /// The `ETag` header field in a response provides the current entity-tag @@ -50,50 +50,50 @@ crate::header! { test_etag { // From the RFC - test_header!(test1, + crate::__common_header_test!(test1, vec![b"\"xyzzy\""], Some(ETag(EntityTag::new(false, "xyzzy".to_owned())))); - test_header!(test2, + crate::__common_header_test!(test2, vec![b"W/\"xyzzy\""], Some(ETag(EntityTag::new(true, "xyzzy".to_owned())))); - test_header!(test3, + crate::__common_header_test!(test3, vec![b"\"\""], Some(ETag(EntityTag::new(false, "".to_owned())))); // Own tests - test_header!(test4, + crate::__common_header_test!(test4, vec![b"\"foobar\""], Some(ETag(EntityTag::new(false, "foobar".to_owned())))); - test_header!(test5, + crate::__common_header_test!(test5, vec![b"\"\""], Some(ETag(EntityTag::new(false, "".to_owned())))); - test_header!(test6, + crate::__common_header_test!(test6, vec![b"W/\"weak-etag\""], Some(ETag(EntityTag::new(true, "weak-etag".to_owned())))); - test_header!(test7, + crate::__common_header_test!(test7, vec![b"W/\"\x65\x62\""], Some(ETag(EntityTag::new(true, "\u{0065}\u{0062}".to_owned())))); - test_header!(test8, + crate::__common_header_test!(test8, vec![b"W/\"\""], Some(ETag(EntityTag::new(true, "".to_owned())))); - test_header!(test9, + crate::__common_header_test!(test9, vec![b"no-dquotes"], None::); - test_header!(test10, + crate::__common_header_test!(test10, vec![b"w/\"the-first-w-is-case-sensitive\""], None::); - test_header!(test11, + crate::__common_header_test!(test11, vec![b""], None::); - test_header!(test12, + crate::__common_header_test!(test12, vec![b"\"unmatched-dquotes1"], None::); - test_header!(test13, + crate::__common_header_test!(test13, vec![b"unmatched-dquotes2\""], None::); - test_header!(test14, + crate::__common_header_test!(test14, vec![b"matched-\"dquotes\""], None::); - test_header!(test15, + crate::__common_header_test!(test15, vec![b"\""], None::); } diff --git a/src/http/header/expires.rs b/src/http/header/expires.rs index 1c306cae0..759e7d280 100644 --- a/src/http/header/expires.rs +++ b/src/http/header/expires.rs @@ -1,6 +1,6 @@ use super::{HttpDate, EXPIRES}; -crate::header! { +crate::__define_common_header! { /// `Expires` header, defined in [RFC7234](http://tools.ietf.org/html/rfc7234#section-5.3) /// /// The `Expires` header field gives the date/time after which the @@ -36,6 +36,6 @@ crate::header! { test_expires { // Test case from RFC - test_header!(test1, vec![b"Thu, 01 Dec 1994 16:00:00 GMT"]); + crate::__common_header_test!(test1, vec![b"Thu, 01 Dec 1994 16:00:00 GMT"]); } } diff --git a/src/http/header/if_match.rs b/src/http/header/if_match.rs index 80699e39c..d4402715d 100644 --- a/src/http/header/if_match.rs +++ b/src/http/header/if_match.rs @@ -1,6 +1,6 @@ use super::{EntityTag, IF_MATCH}; -crate::header! { +crate::__define_common_header! { /// `If-Match` header, defined in /// [RFC7232](https://tools.ietf.org/html/rfc7232#section-3.1) /// @@ -53,18 +53,18 @@ crate::header! { (IfMatch, IF_MATCH) => {Any / (EntityTag)+} test_if_match { - test_header!( + crate::__common_header_test!( test1, vec![b"\"xyzzy\""], Some(HeaderField::Items( vec![EntityTag::new(false, "xyzzy".to_owned())]))); - test_header!( + crate::__common_header_test!( test2, vec![b"\"xyzzy\", \"r2d2xxxx\", \"c3piozzzz\""], Some(HeaderField::Items( vec![EntityTag::new(false, "xyzzy".to_owned()), EntityTag::new(false, "r2d2xxxx".to_owned()), EntityTag::new(false, "c3piozzzz".to_owned())]))); - test_header!(test3, vec![b"*"], Some(IfMatch::Any)); + crate::__common_header_test!(test3, vec![b"*"], Some(IfMatch::Any)); } } diff --git a/src/http/header/if_modified_since.rs b/src/http/header/if_modified_since.rs index d777e0c5c..ba393032d 100644 --- a/src/http/header/if_modified_since.rs +++ b/src/http/header/if_modified_since.rs @@ -1,6 +1,6 @@ use super::{HttpDate, IF_MODIFIED_SINCE}; -crate::header! { +crate::__define_common_header! { /// `If-Modified-Since` header, defined in /// [RFC7232](http://tools.ietf.org/html/rfc7232#section-3.3) /// @@ -36,6 +36,6 @@ crate::header! { test_if_modified_since { // Test case from RFC - test_header!(test1, vec![b"Sat, 29 Oct 1994 19:43:31 GMT"]); + crate::__common_header_test!(test1, vec![b"Sat, 29 Oct 1994 19:43:31 GMT"]); } } diff --git a/src/http/header/if_none_match.rs b/src/http/header/if_none_match.rs index a5c06b374..f16b196cc 100644 --- a/src/http/header/if_none_match.rs +++ b/src/http/header/if_none_match.rs @@ -1,6 +1,6 @@ use super::{EntityTag, IF_NONE_MATCH}; -crate::header! { +crate::__define_common_header! { /// `If-None-Match` header, defined in /// [RFC7232](https://tools.ietf.org/html/rfc7232#section-3.2) /// @@ -55,11 +55,11 @@ crate::header! { (IfNoneMatch, IF_NONE_MATCH) => {Any / (EntityTag)+} test_if_none_match { - test_header!(test1, vec![b"\"xyzzy\""]); - test_header!(test2, vec![b"W/\"xyzzy\""]); - test_header!(test3, vec![b"\"xyzzy\", \"r2d2xxxx\", \"c3piozzzz\""]); - test_header!(test4, vec![b"W/\"xyzzy\", W/\"r2d2xxxx\", W/\"c3piozzzz\""]); - test_header!(test5, vec![b"*"]); + crate::__common_header_test!(test1, vec![b"\"xyzzy\""]); + crate::__common_header_test!(test2, vec![b"W/\"xyzzy\""]); + crate::__common_header_test!(test3, vec![b"\"xyzzy\", \"r2d2xxxx\", \"c3piozzzz\""]); + crate::__common_header_test!(test4, vec![b"W/\"xyzzy\", W/\"r2d2xxxx\", W/\"c3piozzzz\""]); + crate::__common_header_test!(test5, vec![b"*"]); } } diff --git a/src/http/header/if_range.rs b/src/http/header/if_range.rs index f34332f22..80e0642bc 100644 --- a/src/http/header/if_range.rs +++ b/src/http/header/if_range.rs @@ -115,7 +115,7 @@ mod test_if_range { use crate::http::header::*; use std::str; - test_header!(test1, vec![b"Sat, 29 Oct 1994 19:43:31 GMT"]); - test_header!(test2, vec![b"\"abc\""]); - test_header!(test3, vec![b"this-is-invalid"], None::); + crate::__common_header_test!(test1, vec![b"Sat, 29 Oct 1994 19:43:31 GMT"]); + crate::__common_header_test!(test2, vec![b"\"abc\""]); + crate::__common_header_test!(test3, vec![b"this-is-invalid"], None::); } diff --git a/src/http/header/if_unmodified_since.rs b/src/http/header/if_unmodified_since.rs index 8887982aa..26b16b513 100644 --- a/src/http/header/if_unmodified_since.rs +++ b/src/http/header/if_unmodified_since.rs @@ -1,6 +1,6 @@ use super::{HttpDate, IF_UNMODIFIED_SINCE}; -crate::header! { +crate::__define_common_header! { /// `If-Unmodified-Since` header, defined in /// [RFC7232](http://tools.ietf.org/html/rfc7232#section-3.4) /// @@ -37,6 +37,6 @@ crate::header! { test_if_unmodified_since { // Test case from RFC - test_header!(test1, vec![b"Sat, 29 Oct 1994 19:43:31 GMT"]); + crate::__common_header_test!(test1, vec![b"Sat, 29 Oct 1994 19:43:31 GMT"]); } } diff --git a/src/http/header/last_modified.rs b/src/http/header/last_modified.rs index 9ed6fcf69..0de2fc06b 100644 --- a/src/http/header/last_modified.rs +++ b/src/http/header/last_modified.rs @@ -1,6 +1,6 @@ use super::{HttpDate, LAST_MODIFIED}; -crate::header! { +crate::__define_common_header! { /// `Last-Modified` header, defined in /// [RFC7232](http://tools.ietf.org/html/rfc7232#section-2.2) /// @@ -36,5 +36,6 @@ crate::header! { test_last_modified { // Test case from RFC - test_header!(test1, vec![b"Sat, 29 Oct 1994 19:43:31 GMT"]);} + crate::__common_header_test!(test1, vec![b"Sat, 29 Oct 1994 19:43:31 GMT"]); + } } diff --git a/src/http/header/macros.rs b/src/http/header/macros.rs new file mode 100644 index 000000000..1718a8663 --- /dev/null +++ b/src/http/header/macros.rs @@ -0,0 +1,300 @@ +#[doc(hidden)] +#[macro_export] +macro_rules! __common_header_deref { + ($from:ty => $to:ty) => { + impl ::std::ops::Deref for $from { + type Target = $to; + + #[inline] + fn deref(&self) -> &$to { + &self.0 + } + } + + impl ::std::ops::DerefMut for $from { + #[inline] + fn deref_mut(&mut self) -> &mut $to { + &mut self.0 + } + } + }; +} + +#[doc(hidden)] +#[macro_export] +macro_rules! __common_header_test_module { + ($id:ident, $tm:ident{$($tf:item)*}) => { + #[allow(unused_imports)] + #[cfg(test)] + mod $tm { + use std::str; + use actix_http::http::Method; + use mime::*; + use $crate::http::header::*; + use super::$id as HeaderField; + $($tf)* + } + } +} + +#[doc(hidden)] +#[macro_export] +macro_rules! __common_header_test { + ($id:ident, $raw:expr) => { + #[test] + fn $id() { + use actix_http::test; + + let raw = $raw; + let a: Vec> = raw.iter().map(|x| x.to_vec()).collect(); + let mut req = test::TestRequest::default(); + for item in a { + req = req.insert_header((HeaderField::name(), item)).take(); + } + let req = req.finish(); + let value = HeaderField::parse(&req); + let result = format!("{}", value.unwrap()); + let expected = String::from_utf8(raw[0].to_vec()).unwrap(); + let result_cmp: Vec = result + .to_ascii_lowercase() + .split(' ') + .map(|x| x.to_owned()) + .collect(); + let expected_cmp: Vec = expected + .to_ascii_lowercase() + .split(' ') + .map(|x| x.to_owned()) + .collect(); + assert_eq!(result_cmp.concat(), expected_cmp.concat()); + } + }; + ($id:ident, $raw:expr, $typed:expr) => { + #[test] + fn $id() { + use actix_http::test; + + let a: Vec> = $raw.iter().map(|x| x.to_vec()).collect(); + let mut req = test::TestRequest::default(); + for item in a { + req.insert_header((HeaderField::name(), item)); + } + let req = req.finish(); + let val = HeaderField::parse(&req); + let typed: Option = $typed; + // Test parsing + assert_eq!(val.ok(), typed); + // Test formatting + if typed.is_some() { + let raw = &($raw)[..]; + let mut iter = raw.iter().map(|b| str::from_utf8(&b[..]).unwrap()); + let mut joined = String::new(); + joined.push_str(iter.next().unwrap()); + for s in iter { + joined.push_str(", "); + joined.push_str(s); + } + assert_eq!(format!("{}", typed.unwrap()), joined); + } + } + }; +} + +#[doc(hidden)] +#[macro_export] +macro_rules! __define_common_header { + // $a:meta: Attributes associated with the header item (usually docs) + // $id:ident: Identifier of the header + // $n:expr: Lowercase name of the header + // $nn:expr: Nice name of the header + + // List header, zero or more items + ($(#[$a:meta])*($id:ident, $name:expr) => ($item:ty)*) => { + $(#[$a])* + #[derive(Clone, Debug, PartialEq)] + pub struct $id(pub Vec<$item>); + crate::__common_header_deref!($id => Vec<$item>); + impl $crate::http::header::Header for $id { + #[inline] + fn name() -> $crate::http::header::HeaderName { + $name + } + #[inline] + fn parse(msg: &T) -> Result + where T: $crate::HttpMessage + { + $crate::http::header::from_comma_delimited( + msg.headers().get_all(Self::name())).map($id) + } + } + impl std::fmt::Display for $id { + #[inline] + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> ::std::fmt::Result { + $crate::http::header::fmt_comma_delimited(f, &self.0[..]) + } + } + impl $crate::http::header::IntoHeaderValue for $id { + type Error = $crate::http::header::InvalidHeaderValue; + + fn try_into_value(self) -> Result<$crate::http::header::HeaderValue, Self::Error> { + use std::fmt::Write; + let mut writer = $crate::http::header::Writer::new(); + let _ = write!(&mut writer, "{}", self); + $crate::http::header::HeaderValue::from_maybe_shared(writer.take()) + } + } + }; + // List header, one or more items + ($(#[$a:meta])*($id:ident, $name:expr) => ($item:ty)+) => { + $(#[$a])* + #[derive(Clone, Debug, PartialEq)] + pub struct $id(pub Vec<$item>); + crate::__common_header_deref!($id => Vec<$item>); + impl $crate::http::header::Header for $id { + #[inline] + fn name() -> $crate::http::header::HeaderName { + $name + } + #[inline] + fn parse(msg: &T) -> Result + where T: $crate::HttpMessage + { + $crate::http::header::from_comma_delimited( + msg.headers().get_all(Self::name())).map($id) + } + } + impl std::fmt::Display for $id { + #[inline] + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + $crate::http::header::fmt_comma_delimited(f, &self.0[..]) + } + } + impl $crate::http::header::IntoHeaderValue for $id { + type Error = $crate::http::header::InvalidHeaderValue; + + fn try_into_value(self) -> Result<$crate::http::header::HeaderValue, Self::Error> { + use std::fmt::Write; + let mut writer = $crate::http::header::Writer::new(); + let _ = write!(&mut writer, "{}", self); + $crate::http::header::HeaderValue::from_maybe_shared(writer.take()) + } + } + }; + // Single value header + ($(#[$a:meta])*($id:ident, $name:expr) => [$value:ty]) => { + $(#[$a])* + #[derive(Clone, Debug, PartialEq)] + pub struct $id(pub $value); + crate::__common_header_deref!($id => $value); + impl $crate::http::header::Header for $id { + #[inline] + fn name() -> $crate::http::header::HeaderName { + $name + } + #[inline] + fn parse(msg: &T) -> Result + where T: $crate::HttpMessage + { + $crate::http::header::from_one_raw_str( + msg.headers().get(Self::name())).map($id) + } + } + impl std::fmt::Display for $id { + #[inline] + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Display::fmt(&self.0, f) + } + } + impl $crate::http::header::IntoHeaderValue for $id { + type Error = $crate::http::header::InvalidHeaderValue; + + fn try_into_value(self) -> Result<$crate::http::header::HeaderValue, Self::Error> { + self.0.try_into_value() + } + } + }; + // List header, one or more items with "*" option + ($(#[$a:meta])*($id:ident, $name:expr) => {Any / ($item:ty)+}) => { + $(#[$a])* + #[derive(Clone, Debug, PartialEq)] + pub enum $id { + /// Any value is a match + Any, + /// Only the listed items are a match + Items(Vec<$item>), + } + impl $crate::http::header::Header for $id { + #[inline] + fn name() -> $crate::http::header::HeaderName { + $name + } + #[inline] + fn parse(msg: &T) -> Result + where T: $crate::HttpMessage + { + let any = msg.headers().get(Self::name()).and_then(|hdr| { + hdr.to_str().ok().and_then(|hdr| Some(hdr.trim() == "*"))}); + + if let Some(true) = any { + Ok($id::Any) + } else { + Ok($id::Items( + $crate::http::header::from_comma_delimited( + msg.headers().get_all(Self::name()))?)) + } + } + } + impl std::fmt::Display for $id { + #[inline] + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match *self { + $id::Any => f.write_str("*"), + $id::Items(ref fields) => $crate::http::header::fmt_comma_delimited( + f, &fields[..]) + } + } + } + impl $crate::http::header::IntoHeaderValue for $id { + type Error = $crate::http::header::InvalidHeaderValue; + + fn try_into_value(self) -> Result<$crate::http::header::HeaderValue, Self::Error> { + use std::fmt::Write; + let mut writer = $crate::http::header::Writer::new(); + let _ = write!(&mut writer, "{}", self); + $crate::http::header::HeaderValue::from_maybe_shared(writer.take()) + } + } + }; + + // optional test module + ($(#[$a:meta])*($id:ident, $name:expr) => ($item:ty)* $tm:ident{$($tf:item)*}) => { + crate::__define_common_header! { + $(#[$a])* + ($id, $name) => ($item)* + } + + crate::__common_header_test_module! { $id, $tm { $($tf)* }} + }; + ($(#[$a:meta])*($id:ident, $n:expr) => ($item:ty)+ $tm:ident{$($tf:item)*}) => { + crate::__define_common_header! { + $(#[$a])* + ($id, $n) => ($item)+ + } + + crate::__common_header_test_module! { $id, $tm { $($tf)* }} + }; + ($(#[$a:meta])*($id:ident, $name:expr) => [$item:ty] $tm:ident{$($tf:item)*}) => { + crate::__define_common_header! { + $(#[$a])* ($id, $name) => [$item] + } + + crate::__common_header_test_module! { $id, $tm { $($tf)* }} + }; + ($(#[$a:meta])*($id:ident, $name:expr) => {Any / ($item:ty)+} $tm:ident{$($tf:item)*}) => { + crate::__define_common_header! { + $(#[$a])* + ($id, $name) => {Any / ($item)+} + } + + crate::__common_header_test_module! { $id, $tm { $($tf)* }} + }; +} diff --git a/src/http/header/mod.rs b/src/http/header/mod.rs index a1c405344..0e5651a77 100644 --- a/src/http/header/mod.rs +++ b/src/http/header/mod.rs @@ -2,28 +2,26 @@ //! //! ## Mime //! -//! Several header fields use MIME values for their contents. Keeping with the -//! strongly-typed theme, the [mime] crate -//! is used, such as `ContentType(pub Mime)`. -#![cfg_attr(rustfmt, rustfmt_skip)] +//! Several header fields use MIME values for their contents. Keeping with the strongly-typed theme, +//! the [mime] crate is used in such headers as [`ContentType`] and [`Accept`]. +use bytes::{Bytes, BytesMut}; use std::fmt; -use bytes::{BytesMut, Bytes}; -pub use actix_http::http::header::*; pub use self::accept_charset::AcceptCharset; +pub use actix_http::http::header::*; //pub use self::accept_encoding::AcceptEncoding; pub use self::accept::Accept; pub use self::accept_language::AcceptLanguage; pub use self::allow::Allow; pub use self::cache_control::{CacheControl, CacheDirective}; -pub use self::content_disposition::{ - ContentDisposition, DispositionParam, DispositionType, -}; +pub use self::content_disposition::{ContentDisposition, DispositionParam, DispositionType}; pub use self::content_language::ContentLanguage; pub use self::content_range::{ContentRange, ContentRangeSpec}; pub use self::content_type::ContentType; pub use self::date::Date; +pub use self::encoding::Encoding; +pub use self::entity::EntityTag; pub use self::etag::ETag; pub use self::expires::Expires; pub use self::if_match::IfMatch; @@ -32,10 +30,10 @@ pub use self::if_none_match::IfNoneMatch; pub use self::if_range::IfRange; pub use self::if_unmodified_since::IfUnmodifiedSince; pub use self::last_modified::LastModified; -pub use self::encoding::Encoding; -pub use self::entity::EntityTag; //pub use self::range::{Range, ByteRangeSpec}; -pub(crate) use actix_http::http::header::{fmt_comma_delimited, from_comma_delimited, from_one_raw_str}; +pub(crate) use actix_http::http::header::{ + fmt_comma_delimited, from_comma_delimited, from_one_raw_str, +}; #[derive(Debug, Default)] struct Writer { @@ -65,309 +63,6 @@ impl fmt::Write for Writer { } } - -#[doc(hidden)] -#[macro_export] -macro_rules! __hyper__deref { - ($from:ty => $to:ty) => { - impl ::std::ops::Deref for $from { - type Target = $to; - - #[inline] - fn deref(&self) -> &$to { - &self.0 - } - } - - impl ::std::ops::DerefMut for $from { - #[inline] - fn deref_mut(&mut self) -> &mut $to { - &mut self.0 - } - } - }; -} - -#[doc(hidden)] -#[macro_export] -macro_rules! __hyper__tm { - ($id:ident, $tm:ident{$($tf:item)*}) => { - #[allow(unused_imports)] - #[cfg(test)] - mod $tm{ - use std::str; - use actix_http::http::Method; - use mime::*; - use $crate::http::header::*; - use super::$id as HeaderField; - $($tf)* - } - - } -} - -#[doc(hidden)] -#[macro_export] -macro_rules! test_header { - ($id:ident, $raw:expr) => { - #[test] - fn $id() { - use actix_http::test; - - let raw = $raw; - let a: Vec> = raw.iter().map(|x| x.to_vec()).collect(); - let mut req = test::TestRequest::default(); - for item in a { - req = req.insert_header((HeaderField::name(), item)).take(); - } - let req = req.finish(); - let value = HeaderField::parse(&req); - let result = format!("{}", value.unwrap()); - let expected = String::from_utf8(raw[0].to_vec()).unwrap(); - let result_cmp: Vec = result - .to_ascii_lowercase() - .split(' ') - .map(|x| x.to_owned()) - .collect(); - let expected_cmp: Vec = expected - .to_ascii_lowercase() - .split(' ') - .map(|x| x.to_owned()) - .collect(); - assert_eq!(result_cmp.concat(), expected_cmp.concat()); - } - }; - ($id:ident, $raw:expr, $typed:expr) => { - #[test] - fn $id() { - use actix_http::test; - - let a: Vec> = $raw.iter().map(|x| x.to_vec()).collect(); - let mut req = test::TestRequest::default(); - for item in a { - req.insert_header((HeaderField::name(), item)); - } - let req = req.finish(); - let val = HeaderField::parse(&req); - let typed: Option = $typed; - // Test parsing - assert_eq!(val.ok(), typed); - // Test formatting - if typed.is_some() { - let raw = &($raw)[..]; - let mut iter = raw.iter().map(|b| str::from_utf8(&b[..]).unwrap()); - let mut joined = String::new(); - joined.push_str(iter.next().unwrap()); - for s in iter { - joined.push_str(", "); - joined.push_str(s); - } - assert_eq!(format!("{}", typed.unwrap()), joined); - } - } - }; -} - -#[doc(hidden)] -#[macro_export] -macro_rules! header { - // $a:meta: Attributes associated with the header item (usually docs) - // $id:ident: Identifier of the header - // $n:expr: Lowercase name of the header - // $nn:expr: Nice name of the header - - // List header, zero or more items - ($(#[$a:meta])*($id:ident, $name:expr) => ($item:ty)*) => { - $(#[$a])* - #[derive(Clone, Debug, PartialEq)] - pub struct $id(pub Vec<$item>); - __hyper__deref!($id => Vec<$item>); - impl $crate::http::header::Header for $id { - #[inline] - fn name() -> $crate::http::header::HeaderName { - $name - } - #[inline] - fn parse(msg: &T) -> Result - where T: $crate::HttpMessage - { - $crate::http::header::from_comma_delimited( - msg.headers().get_all(Self::name())).map($id) - } - } - impl std::fmt::Display for $id { - #[inline] - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> ::std::fmt::Result { - $crate::http::header::fmt_comma_delimited(f, &self.0[..]) - } - } - impl $crate::http::header::IntoHeaderValue for $id { - type Error = $crate::http::header::InvalidHeaderValue; - - fn try_into_value(self) -> Result<$crate::http::header::HeaderValue, Self::Error> { - use std::fmt::Write; - let mut writer = $crate::http::header::Writer::new(); - let _ = write!(&mut writer, "{}", self); - $crate::http::header::HeaderValue::from_maybe_shared(writer.take()) - } - } - }; - // List header, one or more items - ($(#[$a:meta])*($id:ident, $name:expr) => ($item:ty)+) => { - $(#[$a])* - #[derive(Clone, Debug, PartialEq)] - pub struct $id(pub Vec<$item>); - __hyper__deref!($id => Vec<$item>); - impl $crate::http::header::Header for $id { - #[inline] - fn name() -> $crate::http::header::HeaderName { - $name - } - #[inline] - fn parse(msg: &T) -> Result - where T: $crate::HttpMessage - { - $crate::http::header::from_comma_delimited( - msg.headers().get_all(Self::name())).map($id) - } - } - impl std::fmt::Display for $id { - #[inline] - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - $crate::http::header::fmt_comma_delimited(f, &self.0[..]) - } - } - impl $crate::http::header::IntoHeaderValue for $id { - type Error = $crate::http::header::InvalidHeaderValue; - - fn try_into_value(self) -> Result<$crate::http::header::HeaderValue, Self::Error> { - use std::fmt::Write; - let mut writer = $crate::http::header::Writer::new(); - let _ = write!(&mut writer, "{}", self); - $crate::http::header::HeaderValue::from_maybe_shared(writer.take()) - } - } - }; - // Single value header - ($(#[$a:meta])*($id:ident, $name:expr) => [$value:ty]) => { - $(#[$a])* - #[derive(Clone, Debug, PartialEq)] - pub struct $id(pub $value); - __hyper__deref!($id => $value); - impl $crate::http::header::Header for $id { - #[inline] - fn name() -> $crate::http::header::HeaderName { - $name - } - #[inline] - fn parse(msg: &T) -> Result - where T: $crate::HttpMessage - { - $crate::http::header::from_one_raw_str( - msg.headers().get(Self::name())).map($id) - } - } - impl std::fmt::Display for $id { - #[inline] - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - std::fmt::Display::fmt(&self.0, f) - } - } - impl $crate::http::header::IntoHeaderValue for $id { - type Error = $crate::http::header::InvalidHeaderValue; - - fn try_into_value(self) -> Result<$crate::http::header::HeaderValue, Self::Error> { - self.0.try_into_value() - } - } - }; - // List header, one or more items with "*" option - ($(#[$a:meta])*($id:ident, $name:expr) => {Any / ($item:ty)+}) => { - $(#[$a])* - #[derive(Clone, Debug, PartialEq)] - pub enum $id { - /// Any value is a match - Any, - /// Only the listed items are a match - Items(Vec<$item>), - } - impl $crate::http::header::Header for $id { - #[inline] - fn name() -> $crate::http::header::HeaderName { - $name - } - #[inline] - fn parse(msg: &T) -> Result - where T: $crate::HttpMessage - { - let any = msg.headers().get(Self::name()).and_then(|hdr| { - hdr.to_str().ok().and_then(|hdr| Some(hdr.trim() == "*"))}); - - if let Some(true) = any { - Ok($id::Any) - } else { - Ok($id::Items( - $crate::http::header::from_comma_delimited( - msg.headers().get_all(Self::name()))?)) - } - } - } - impl std::fmt::Display for $id { - #[inline] - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match *self { - $id::Any => f.write_str("*"), - $id::Items(ref fields) => $crate::http::header::fmt_comma_delimited( - f, &fields[..]) - } - } - } - impl $crate::http::header::IntoHeaderValue for $id { - type Error = $crate::http::header::InvalidHeaderValue; - - fn try_into_value(self) -> Result<$crate::http::header::HeaderValue, Self::Error> { - use std::fmt::Write; - let mut writer = $crate::http::header::Writer::new(); - let _ = write!(&mut writer, "{}", self); - $crate::http::header::HeaderValue::from_maybe_shared(writer.take()) - } - } - }; - - // optional test module - ($(#[$a:meta])*($id:ident, $name:expr) => ($item:ty)* $tm:ident{$($tf:item)*}) => { - header! { - $(#[$a])* - ($id, $name) => ($item)* - } - - __hyper__tm! { $id, $tm { $($tf)* }} - }; - ($(#[$a:meta])*($id:ident, $n:expr) => ($item:ty)+ $tm:ident{$($tf:item)*}) => { - header! { - $(#[$a])* - ($id, $n) => ($item)+ - } - - __hyper__tm! { $id, $tm { $($tf)* }} - }; - ($(#[$a:meta])*($id:ident, $name:expr) => [$item:ty] $tm:ident{$($tf:item)*}) => { - header! { - $(#[$a])* ($id, $name) => [$item] - } - - __hyper__tm! { $id, $tm { $($tf)* }} - }; - ($(#[$a:meta])*($id:ident, $name:expr) => {Any / ($item:ty)+} $tm:ident{$($tf:item)*}) => { - header! { - $(#[$a])* - ($id, $name) => {Any / ($item)+} - } - - __hyper__tm! { $id, $tm { $($tf)* }} - }; -} - mod accept_charset; // mod accept_encoding; mod accept; @@ -379,6 +74,8 @@ mod content_language; mod content_range; mod content_type; mod date; +mod encoding; +mod entity; mod etag; mod expires; mod if_match; @@ -387,5 +84,4 @@ mod if_none_match; mod if_range; mod if_unmodified_since; mod last_modified; -mod encoding; -mod entity; +mod macros; From d8f56eee3ece2595032512e294c5ead2a7ad359c Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Fri, 16 Apr 2021 20:28:21 +0100 Subject: [PATCH 61/79] bump service to stable v2 --- Cargo.toml | 2 +- actix-files/Cargo.toml | 2 +- actix-http-test/Cargo.toml | 2 +- actix-http/Cargo.toml | 2 +- actix-http/src/h1/service.rs | 42 ++++++++------- actix-http/src/h2/service.rs | 60 ++++++++++------------ actix-http/src/service.rs | 71 +++++++++++++------------- actix-test/Cargo.toml | 2 +- awc/Cargo.toml | 2 +- awc/tests/test_client.rs | 12 ++--- awc/tests/test_ssl_client.rs | 4 +- src/http/header/cache_control.rs | 8 +-- src/http/header/content_disposition.rs | 46 +++++++---------- src/http/header/content_range.rs | 7 +-- src/http/header/if_range.rs | 14 +++-- src/server.rs | 42 +++++++-------- 16 files changed, 144 insertions(+), 174 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index b8b18a84c..c01111020 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -61,7 +61,7 @@ actix-macros = "0.2.0" actix-router = "0.2.7" actix-rt = "2.2" actix-server = "2.0.0-beta.3" -actix-service = "2.0.0-beta.4" +actix-service = "2.0.0" actix-utils = "3.0.0-beta.4" actix-tls = { version = "3.0.0-beta.5", default-features = false, optional = true } diff --git a/actix-files/Cargo.toml b/actix-files/Cargo.toml index 84d8dd958..3b25806ad 100644 --- a/actix-files/Cargo.toml +++ b/actix-files/Cargo.toml @@ -18,7 +18,7 @@ path = "src/lib.rs" [dependencies] actix-web = { version = "4.0.0-beta.5", default-features = false } -actix-service = "2.0.0-beta.4" +actix-service = "2.0.0" actix-utils = "3.0.0-beta.4" askama_escape = "0.10" diff --git a/actix-http-test/Cargo.toml b/actix-http-test/Cargo.toml index e1dd4c4e8..836510dce 100644 --- a/actix-http-test/Cargo.toml +++ b/actix-http-test/Cargo.toml @@ -29,7 +29,7 @@ default = [] openssl = ["tls-openssl", "awc/openssl"] [dependencies] -actix-service = "2.0.0-beta.4" +actix-service = "2.0.0" actix-codec = "0.4.0-beta.1" actix-tls = "3.0.0-beta.5" actix-utils = "3.0.0-beta.4" diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 361cae62f..13557c6a9 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -38,7 +38,7 @@ compress = ["flate2", "brotli2"] trust-dns = ["trust-dns-resolver"] [dependencies] -actix-service = "2.0.0-beta.4" +actix-service = "2.0.0" actix-codec = "0.4.0-beta.1" actix-utils = "3.0.0-beta.4" actix-rt = "2.2" diff --git a/actix-http/src/h1/service.rs b/actix-http/src/h1/service.rs index a98f6bd0a..916643a18 100644 --- a/actix-http/src/h1/service.rs +++ b/actix-http/src/h1/service.rs @@ -5,7 +5,9 @@ use std::{fmt, net}; use actix_codec::{AsyncRead, AsyncWrite, Framed}; use actix_rt::net::TcpStream; -use actix_service::{pipeline_factory, IntoServiceFactory, Service, ServiceFactory}; +use actix_service::{ + fn_service, IntoServiceFactory, Service, ServiceFactory, ServiceFactoryExt as _, +}; use actix_utils::future::ready; use futures_core::future::LocalBoxFuture; @@ -82,7 +84,7 @@ where Error = DispatchError, InitError = (), > { - pipeline_factory(|io: TcpStream| { + fn_service(|io: TcpStream| { let peer_addr = io.peer_addr().ok(); ready(Ok((io, peer_addr))) }) @@ -132,16 +134,14 @@ mod openssl { Error = TlsError, InitError = (), > { - pipeline_factory( - Acceptor::new(acceptor) - .map_err(TlsError::Tls) - .map_init_err(|_| panic!()), - ) - .and_then(|io: TlsStream| { - let peer_addr = io.get_ref().peer_addr().ok(); - ready(Ok((io, peer_addr))) - }) - .and_then(self.map_err(TlsError::Service)) + Acceptor::new(acceptor) + .map_err(TlsError::Tls) + .map_init_err(|_| panic!()) + .and_then(|io: TlsStream| { + let peer_addr = io.get_ref().peer_addr().ok(); + ready(Ok((io, peer_addr))) + }) + .and_then(self.map_err(TlsError::Service)) } } } @@ -190,16 +190,14 @@ mod rustls { Error = TlsError, InitError = (), > { - pipeline_factory( - Acceptor::new(config) - .map_err(TlsError::Tls) - .map_init_err(|_| panic!()), - ) - .and_then(|io: TlsStream| { - let peer_addr = io.get_ref().0.peer_addr().ok(); - ready(Ok((io, peer_addr))) - }) - .and_then(self.map_err(TlsError::Service)) + Acceptor::new(config) + .map_err(TlsError::Tls) + .map_init_err(|_| panic!()) + .and_then(|io: TlsStream| { + let peer_addr = io.get_ref().0.peer_addr().ok(); + ready(Ok((io, peer_addr))) + }) + .and_then(self.map_err(TlsError::Service)) } } } diff --git a/actix-http/src/h2/service.rs b/actix-http/src/h2/service.rs index 8f202e752..1a0b8c7f5 100644 --- a/actix-http/src/h2/service.rs +++ b/actix-http/src/h2/service.rs @@ -7,8 +7,8 @@ use std::{net, rc::Rc}; use actix_codec::{AsyncRead, AsyncWrite}; use actix_rt::net::TcpStream; use actix_service::{ - fn_factory, fn_service, pipeline_factory, IntoServiceFactory, Service, - ServiceFactory, + fn_factory, fn_service, IntoServiceFactory, Service, ServiceFactory, + ServiceFactoryExt as _, }; use actix_utils::future::ready; use bytes::Bytes; @@ -81,12 +81,12 @@ where Error = DispatchError, InitError = S::InitError, > { - pipeline_factory(fn_factory(|| { + fn_factory(|| { ready(Ok::<_, S::InitError>(fn_service(|io: TcpStream| { let peer_addr = io.peer_addr().ok(); ready(Ok::<_, DispatchError>((io, peer_addr))) }))) - })) + }) .and_then(self) } } @@ -119,20 +119,18 @@ mod openssl { Error = TlsError, InitError = S::InitError, > { - pipeline_factory( - Acceptor::new(acceptor) - .map_err(TlsError::Tls) - .map_init_err(|_| panic!()), - ) - .and_then(fn_factory(|| { - ready(Ok::<_, S::InitError>(fn_service( - |io: TlsStream| { - let peer_addr = io.get_ref().peer_addr().ok(); - ready(Ok((io, peer_addr))) - }, - ))) - })) - .and_then(self.map_err(TlsError::Service)) + Acceptor::new(acceptor) + .map_err(TlsError::Tls) + .map_init_err(|_| panic!()) + .and_then(fn_factory(|| { + ready(Ok::<_, S::InitError>(fn_service( + |io: TlsStream| { + let peer_addr = io.get_ref().peer_addr().ok(); + ready(Ok((io, peer_addr))) + }, + ))) + })) + .and_then(self.map_err(TlsError::Service)) } } } @@ -168,20 +166,18 @@ mod rustls { let protos = vec!["h2".to_string().into()]; config.set_protocols(&protos); - pipeline_factory( - Acceptor::new(config) - .map_err(TlsError::Tls) - .map_init_err(|_| panic!()), - ) - .and_then(fn_factory(|| { - ready(Ok::<_, S::InitError>(fn_service( - |io: TlsStream| { - let peer_addr = io.get_ref().0.peer_addr().ok(); - ready(Ok((io, peer_addr))) - }, - ))) - })) - .and_then(self.map_err(TlsError::Service)) + Acceptor::new(config) + .map_err(TlsError::Tls) + .map_init_err(|_| panic!()) + .and_then(fn_factory(|| { + ready(Ok::<_, S::InitError>(fn_service( + |io: TlsStream| { + let peer_addr = io.get_ref().0.peer_addr().ok(); + ready(Ok((io, peer_addr))) + }, + ))) + })) + .and_then(self.map_err(TlsError::Service)) } } } diff --git a/actix-http/src/service.rs b/actix-http/src/service.rs index 67a3ec42e..ff4b49f1d 100644 --- a/actix-http/src/service.rs +++ b/actix-http/src/service.rs @@ -10,7 +10,9 @@ use std::{ use actix_codec::{AsyncRead, AsyncWrite, Framed}; use actix_rt::net::TcpStream; -use actix_service::{pipeline_factory, IntoServiceFactory, Service, ServiceFactory}; +use actix_service::{ + fn_service, IntoServiceFactory, Service, ServiceFactory, ServiceFactoryExt as _, +}; use bytes::Bytes; use futures_core::{future::LocalBoxFuture, ready}; use h2::server::{handshake, Handshake}; @@ -180,7 +182,7 @@ where Error = DispatchError, InitError = (), > { - pipeline_factory(|io: TcpStream| async { + fn_service(|io: TcpStream| async { let peer_addr = io.peer_addr().ok(); Ok((io, Protocol::Http1, peer_addr)) }) @@ -232,25 +234,23 @@ mod openssl { Error = TlsError, InitError = (), > { - pipeline_factory( - Acceptor::new(acceptor) - .map_err(TlsError::Tls) - .map_init_err(|_| panic!()), - ) - .and_then(|io: TlsStream| async { - let proto = if let Some(protos) = io.ssl().selected_alpn_protocol() { - if protos.windows(2).any(|window| window == b"h2") { - Protocol::Http2 + Acceptor::new(acceptor) + .map_err(TlsError::Tls) + .map_init_err(|_| panic!()) + .and_then(|io: TlsStream| async { + let proto = if let Some(protos) = io.ssl().selected_alpn_protocol() { + if protos.windows(2).any(|window| window == b"h2") { + Protocol::Http2 + } else { + Protocol::Http1 + } } else { Protocol::Http1 - } - } else { - Protocol::Http1 - }; - let peer_addr = io.get_ref().peer_addr().ok(); - Ok((io, proto, peer_addr)) - }) - .and_then(self.map_err(TlsError::Service)) + }; + let peer_addr = io.get_ref().peer_addr().ok(); + Ok((io, proto, peer_addr)) + }) + .and_then(self.map_err(TlsError::Service)) } } } @@ -304,25 +304,24 @@ mod rustls { let protos = vec!["h2".to_string().into(), "http/1.1".to_string().into()]; config.set_protocols(&protos); - pipeline_factory( - Acceptor::new(config) - .map_err(TlsError::Tls) - .map_init_err(|_| panic!()), - ) - .and_then(|io: TlsStream| async { - let proto = if let Some(protos) = io.get_ref().1.get_alpn_protocol() { - if protos.windows(2).any(|window| window == b"h2") { - Protocol::Http2 + Acceptor::new(config) + .map_err(TlsError::Tls) + .map_init_err(|_| panic!()) + .and_then(|io: TlsStream| async { + let proto = if let Some(protos) = io.get_ref().1.get_alpn_protocol() + { + if protos.windows(2).any(|window| window == b"h2") { + Protocol::Http2 + } else { + Protocol::Http1 + } } else { Protocol::Http1 - } - } else { - Protocol::Http1 - }; - let peer_addr = io.get_ref().0.peer_addr().ok(); - Ok((io, proto, peer_addr)) - }) - .and_then(self.map_err(TlsError::Service)) + }; + let peer_addr = io.get_ref().0.peer_addr().ok(); + Ok((io, proto, peer_addr)) + }) + .and_then(self.map_err(TlsError::Service)) } } } diff --git a/actix-test/Cargo.toml b/actix-test/Cargo.toml index db7a50c5e..730fb7bed 100644 --- a/actix-test/Cargo.toml +++ b/actix-test/Cargo.toml @@ -22,7 +22,7 @@ openssl = ["tls-openssl", "actix-http/openssl"] actix-codec = "0.4.0-beta.1" actix-http = "3.0.0-beta.5" actix-http-test = { version = "3.0.0-beta.4", features = [] } -actix-service = "2.0.0-beta.4" +actix-service = "2.0.0" actix-utils = "3.0.0-beta.2" actix-web = { version = "4.0.0-beta.5", default-features = false, features = ["cookies"] } actix-rt = "2.1" diff --git a/awc/Cargo.toml b/awc/Cargo.toml index 27d8bdfbc..28d93ad36 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -48,7 +48,7 @@ trust-dns = ["actix-http/trust-dns"] [dependencies] actix-codec = "0.4.0-beta.1" -actix-service = "2.0.0-beta.4" +actix-service = "2.0.0" actix-http = "3.0.0-beta.5" actix-rt = { version = "2.1", default-features = false } diff --git a/awc/tests/test_client.rs b/awc/tests/test_client.rs index f1d29f0bc..615789fb3 100644 --- a/awc/tests/test_client.rs +++ b/awc/tests/test_client.rs @@ -20,7 +20,7 @@ use actix_http::{ HttpService, }; use actix_http_test::test_server; -use actix_service::{map_config, pipeline_factory}; +use actix_service::{fn_service, map_config, ServiceFactoryExt as _}; use actix_web::{ dev::{AppConfig, BodyEncoding}, http::header, @@ -239,7 +239,7 @@ async fn test_connection_reuse() { let srv = test_server(move || { let num2 = num2.clone(); - pipeline_factory(move |io| { + fn_service(move |io| { num2.fetch_add(1, Ordering::Relaxed); ok(io) }) @@ -276,7 +276,7 @@ async fn test_connection_force_close() { let srv = test_server(move || { let num2 = num2.clone(); - pipeline_factory(move |io| { + fn_service(move |io| { num2.fetch_add(1, Ordering::Relaxed); ok(io) }) @@ -313,7 +313,7 @@ async fn test_connection_server_close() { let srv = test_server(move || { let num2 = num2.clone(); - pipeline_factory(move |io| { + fn_service(move |io| { num2.fetch_add(1, Ordering::Relaxed); ok(io) }) @@ -353,7 +353,7 @@ async fn test_connection_wait_queue() { let srv = test_server(move || { let num2 = num2.clone(); - pipeline_factory(move |io| { + fn_service(move |io| { num2.fetch_add(1, Ordering::Relaxed); ok(io) }) @@ -401,7 +401,7 @@ async fn test_connection_wait_queue_force_close() { let srv = test_server(move || { let num2 = num2.clone(); - pipeline_factory(move |io| { + fn_service(move |io| { num2.fetch_add(1, Ordering::Relaxed); ok(io) }) diff --git a/awc/tests/test_ssl_client.rs b/awc/tests/test_ssl_client.rs index 502223401..57305e49a 100644 --- a/awc/tests/test_ssl_client.rs +++ b/awc/tests/test_ssl_client.rs @@ -7,7 +7,7 @@ use std::sync::Arc; use actix_http::HttpService; use actix_http_test::test_server; -use actix_service::{map_config, pipeline_factory, ServiceFactoryExt}; +use actix_service::{map_config, fn_service, ServiceFactoryExt}; use actix_utils::future::ok; use actix_web::http::Version; use actix_web::{dev::AppConfig, web, App, HttpResponse}; @@ -48,7 +48,7 @@ async fn test_connection_reuse_h2() { let srv = test_server(move || { let num2 = num2.clone(); - pipeline_factory(move |io| { + fn_service(move |io| { num2.fetch_add(1, Ordering::Relaxed); ok(io) }) diff --git a/src/http/header/cache_control.rs b/src/http/header/cache_control.rs index 891ba7c79..620c576ae 100644 --- a/src/http/header/cache_control.rs +++ b/src/http/header/cache_control.rs @@ -1,9 +1,7 @@ use std::fmt::{self, Write}; use std::str::FromStr; -use super::{ - fmt_comma_delimited, from_comma_delimited, Header, IntoHeaderValue, Writer, -}; +use super::{fmt_comma_delimited, from_comma_delimited, Header, IntoHeaderValue, Writer}; use crate::http::header; @@ -176,9 +174,7 @@ impl FromStr for CacheDirective { ("max-stale", secs) => secs.parse().map(MaxStale).map_err(Some), ("min-fresh", secs) => secs.parse().map(MinFresh).map_err(Some), ("s-maxage", secs) => secs.parse().map(SMaxAge).map_err(Some), - (left, right) => { - Ok(Extension(left.to_owned(), Some(right.to_owned()))) - } + (left, right) => Ok(Extension(left.to_owned(), Some(right.to_owned()))), } } Some(_) => Err(None), diff --git a/src/http/header/content_disposition.rs b/src/http/header/content_disposition.rs index 3dea0997b..509d68968 100644 --- a/src/http/header/content_disposition.rs +++ b/src/http/header/content_disposition.rs @@ -10,8 +10,8 @@ use once_cell::sync::Lazy; use regex::Regex; use std::fmt::{self, Write}; -use crate::http::header; use super::{ExtendedValue, Header, IntoHeaderValue, Writer}; +use crate::http::header; /// Split at the index of the first `needle` if it exists or at the end. fn split_once(haystack: &str, needle: char) -> (&str, &str) { @@ -403,11 +403,7 @@ impl ContentDisposition { /// Returns `true` if it is [`Ext`](DispositionType::Ext) and the `disp_type` matches. pub fn is_ext>(&self, disp_type: T) -> bool { match self.disposition { - DispositionType::Ext(ref t) - if t.eq_ignore_ascii_case(disp_type.as_ref()) => - { - true - } + DispositionType::Ext(ref t) if t.eq_ignore_ascii_case(disp_type.as_ref()) => true, _ => false, } } @@ -521,7 +517,8 @@ impl fmt::Display for DispositionParam { // // // See also comments in test_from_raw_unnecessary_percent_decode. - static RE: Lazy = Lazy::new(|| Regex::new("[\x00-\x08\x10-\x1F\x7F\"\\\\]").unwrap()); + static RE: Lazy = + Lazy::new(|| Regex::new("[\x00-\x08\x10-\x1F\x7F\"\\\\]").unwrap()); match self { DispositionParam::Name(ref value) => write!(f, "name={}", value), DispositionParam::Filename(ref value) => { @@ -618,8 +615,8 @@ mod tests { charset: Charset::Ext(String::from("UTF-8")), language_tag: None, value: vec![ - 0xc2, 0xa3, 0x20, b'a', b'n', b'd', 0x20, 0xe2, 0x82, 0xac, 0x20, - b'r', b'a', b't', b'e', b's', + 0xc2, 0xa3, 0x20, b'a', b'n', b'd', 0x20, 0xe2, 0x82, 0xac, 0x20, b'r', + b'a', b't', b'e', b's', ], })], }; @@ -635,8 +632,8 @@ mod tests { charset: Charset::Ext(String::from("UTF-8")), language_tag: None, value: vec![ - 0xc2, 0xa3, 0x20, b'a', b'n', b'd', 0x20, 0xe2, 0x82, 0xac, 0x20, - b'r', b'a', b't', b'e', b's', + 0xc2, 0xa3, 0x20, b'a', b'n', b'd', 0x20, 0xe2, 0x82, 0xac, 0x20, b'r', + b'a', b't', b'e', b's', ], })], }; @@ -698,26 +695,22 @@ mod tests { #[test] fn test_from_raw_only_disp() { - let a = ContentDisposition::from_raw(&HeaderValue::from_static("attachment")) - .unwrap(); + let a = ContentDisposition::from_raw(&HeaderValue::from_static("attachment")).unwrap(); let b = ContentDisposition { disposition: DispositionType::Attachment, parameters: vec![], }; assert_eq!(a, b); - let a = - ContentDisposition::from_raw(&HeaderValue::from_static("inline ;")).unwrap(); + let a = ContentDisposition::from_raw(&HeaderValue::from_static("inline ;")).unwrap(); let b = ContentDisposition { disposition: DispositionType::Inline, parameters: vec![], }; assert_eq!(a, b); - let a = ContentDisposition::from_raw(&HeaderValue::from_static( - "unknown-disp-param", - )) - .unwrap(); + let a = ContentDisposition::from_raw(&HeaderValue::from_static("unknown-disp-param")) + .unwrap(); let b = ContentDisposition { disposition: DispositionType::Ext(String::from("unknown-disp-param")), parameters: vec![], @@ -756,8 +749,8 @@ mod tests { Mainstream browsers like Firefox (gecko) and Chrome use UTF-8 directly as above. (And now, only UTF-8 is handled by this implementation.) */ - let a = HeaderValue::from_str("form-data; name=upload; filename=\"文件.webp\"") - .unwrap(); + let a = + HeaderValue::from_str("form-data; name=upload; filename=\"文件.webp\"").unwrap(); let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); let b = ContentDisposition { disposition: DispositionType::FormData, @@ -808,8 +801,7 @@ mod tests { #[test] fn test_from_raw_semicolon() { - let a = - HeaderValue::from_static("form-data; filename=\"A semicolon here;.pdf\""); + let a = HeaderValue::from_static("form-data; filename=\"A semicolon here;.pdf\""); let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); let b = ContentDisposition { disposition: DispositionType::FormData, @@ -845,9 +837,8 @@ mod tests { }; assert_eq!(a, b); - let a = HeaderValue::from_static( - "form-data; name=photo; filename=\"%74%65%73%74.png\"", - ); + let a = + HeaderValue::from_static("form-data; name=photo; filename=\"%74%65%73%74.png\""); let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); let b = ContentDisposition { disposition: DispositionType::FormData, @@ -892,8 +883,7 @@ mod tests { #[test] fn test_display_extended() { - let as_string = - "attachment; filename*=UTF-8'en'%C2%A3%20and%20%E2%82%AC%20rates"; + let as_string = "attachment; filename*=UTF-8'en'%C2%A3%20and%20%E2%82%AC%20rates"; let a = HeaderValue::from_static(as_string); let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); let display_rendered = format!("{}", a); diff --git a/src/http/header/content_range.rs b/src/http/header/content_range.rs index e3a8450cb..ba0d51742 100644 --- a/src/http/header/content_range.rs +++ b/src/http/header/content_range.rs @@ -1,10 +1,8 @@ use std::fmt::{self, Display, Write}; use std::str::FromStr; +use super::{HeaderValue, IntoHeaderValue, InvalidHeaderValue, Writer, CONTENT_RANGE}; use crate::error::ParseError; -use super::{ - HeaderValue, IntoHeaderValue, InvalidHeaderValue, Writer, CONTENT_RANGE, -}; crate::__define_common_header! { /// `Content-Range` header, defined in @@ -141,8 +139,7 @@ impl FromStr for ContentRangeSpec { } else { let (first_byte, last_byte) = split_in_two(range, '-').ok_or(ParseError::Header)?; - let first_byte = - first_byte.parse().map_err(|_| ParseError::Header)?; + let first_byte = first_byte.parse().map_err(|_| ParseError::Header)?; let last_byte = last_byte.parse().map_err(|_| ParseError::Header)?; if last_byte < first_byte { return Err(ParseError::Header); diff --git a/src/http/header/if_range.rs b/src/http/header/if_range.rs index 80e0642bc..9612405e8 100644 --- a/src/http/header/if_range.rs +++ b/src/http/header/if_range.rs @@ -1,11 +1,11 @@ use std::fmt::{self, Display, Write}; -use crate::http::header; -use crate::error::ParseError; use super::{ - from_one_raw_str, EntityTag, Header, HeaderName, HeaderValue, HttpDate, - IntoHeaderValue, InvalidHeaderValue, Writer, + from_one_raw_str, EntityTag, Header, HeaderName, HeaderValue, HttpDate, IntoHeaderValue, + InvalidHeaderValue, Writer, }; +use crate::error::ParseError; +use crate::http::header; use crate::HttpMessage; /// `If-Range` header, defined in [RFC7233](http://tools.ietf.org/html/rfc7233#section-3.2) @@ -76,13 +76,11 @@ impl Header for IfRange { where T: HttpMessage, { - let etag: Result = - from_one_raw_str(msg.headers().get(&header::IF_RANGE)); + let etag: Result = from_one_raw_str(msg.headers().get(&header::IF_RANGE)); if let Ok(etag) = etag { return Ok(IfRange::EntityTag(etag)); } - let date: Result = - from_one_raw_str(msg.headers().get(&header::IF_RANGE)); + let date: Result = from_one_raw_str(msg.headers().get(&header::IF_RANGE)); if let Ok(date) = date { return Ok(IfRange::Date(date)); } diff --git a/src/server.rs b/src/server.rs index e3a9f6e01..6577f4d1f 100644 --- a/src/server.rs +++ b/src/server.rs @@ -489,7 +489,7 @@ where pub fn listen_uds(mut self, lst: std::os::unix::net::UnixListener) -> io::Result { use actix_http::Protocol; use actix_rt::net::UnixStream; - use actix_service::pipeline_factory; + use actix_service::{fn_service, ServiceFactoryExt as _}; let cfg = self.config.clone(); let factory = self.factory.clone(); @@ -511,22 +511,19 @@ where socket_addr, ); - pipeline_factory(|io: UnixStream| async { Ok((io, Protocol::Http1, None)) }) - .and_then({ - let svc = HttpService::build() - .keep_alive(c.keep_alive) - .client_timeout(c.client_timeout); + fn_service(|io: UnixStream| async { Ok((io, Protocol::Http1, None)) }).and_then({ + let svc = HttpService::build() + .keep_alive(c.keep_alive) + .client_timeout(c.client_timeout); - let svc = if let Some(handler) = on_connect_fn.clone() { - svc.on_connect_ext(move |io: &_, ext: _| { - (&*handler)(io as &dyn Any, ext) - }) - } else { - svc - }; + let svc = if let Some(handler) = on_connect_fn.clone() { + svc.on_connect_ext(move |io: &_, ext: _| (&*handler)(io as &dyn Any, ext)) + } else { + svc + }; - svc.finish(map_config(factory(), move |_| config.clone())) - }) + svc.finish(map_config(factory(), move |_| config.clone())) + }) })?; Ok(self) } @@ -539,7 +536,7 @@ where { use actix_http::Protocol; use actix_rt::net::UnixStream; - use actix_service::pipeline_factory; + use actix_service::{fn_service, ServiceFactoryExt as _}; let cfg = self.config.clone(); let factory = self.factory.clone(); @@ -560,13 +557,12 @@ where c.host.clone().unwrap_or_else(|| format!("{}", socket_addr)), socket_addr, ); - pipeline_factory(|io: UnixStream| async { Ok((io, Protocol::Http1, None)) }) - .and_then( - HttpService::build() - .keep_alive(c.keep_alive) - .client_timeout(c.client_timeout) - .finish(map_config(factory(), move |_| config.clone())), - ) + fn_service(|io: UnixStream| async { Ok((io, Protocol::Http1, None)) }).and_then( + HttpService::build() + .keep_alive(c.keep_alive) + .client_timeout(c.client_timeout) + .finish(map_config(factory(), move |_| config.clone())), + ) }, )?; Ok(self) From 2449f2555cec8fc90c91a4ac1b160f96436b11ce Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Fri, 16 Apr 2021 20:48:37 +0100 Subject: [PATCH 62/79] missed one pipeline_factory --- awc/tests/test_rustls_client.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/awc/tests/test_rustls_client.rs b/awc/tests/test_rustls_client.rs index 080eaf792..bc811c046 100644 --- a/awc/tests/test_rustls_client.rs +++ b/awc/tests/test_rustls_client.rs @@ -12,7 +12,7 @@ use std::{ use actix_http::HttpService; use actix_http_test::test_server; -use actix_service::{map_config, pipeline_factory, ServiceFactoryExt}; +use actix_service::{fn_service, map_config, ServiceFactoryExt}; use actix_utils::future::ok; use actix_web::{dev::AppConfig, http::Version, web, App, HttpResponse}; use rustls::internal::pemfile::{certs, pkcs8_private_keys}; @@ -57,7 +57,7 @@ async fn test_connection_reuse_h2() { let srv = test_server(move || { let num2 = num2.clone(); - pipeline_factory(move |io| { + fn_service(move |io| { num2.fetch_add(1, Ordering::Relaxed); ok(io) }) From 879a4cbcd8fc2d65e3961738904c35f41f0e4c55 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Fri, 16 Apr 2021 23:21:02 +0100 Subject: [PATCH 63/79] re-export ready boilerplate macros in dev --- actix-web-codegen/tests/test_macro.rs | 26 ++++++++++++++------------ src/lib.rs | 2 +- src/middleware/compat.rs | 4 +--- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/actix-web-codegen/tests/test_macro.rs b/actix-web-codegen/tests/test_macro.rs index b983e6b1d..6b08c409c 100644 --- a/actix-web-codegen/tests/test_macro.rs +++ b/actix-web-codegen/tests/test_macro.rs @@ -1,11 +1,15 @@ use std::future::Future; -use std::task::{Context, Poll}; use actix_utils::future::{ok, Ready}; -use actix_web::dev::{Service, ServiceRequest, ServiceResponse, Transform}; -use actix_web::http::header::{HeaderName, HeaderValue}; -use actix_web::http::StatusCode; -use actix_web::{http, web::Path, App, Error, HttpResponse, Responder}; +use actix_web::{ + dev::{Service, ServiceRequest, ServiceResponse, Transform}, + http::{ + self, + header::{HeaderName, HeaderValue}, + StatusCode, + }, + web, App, Error, HttpResponse, Responder, +}; use actix_web_codegen::{connect, delete, get, head, options, patch, post, put, route, trace}; use futures_core::future::LocalBoxFuture; @@ -66,17 +70,17 @@ fn auto_sync() -> impl Future> { } #[put("/test/{param}")] -async fn put_param_test(_: Path) -> impl Responder { +async fn put_param_test(_: web::Path) -> impl Responder { HttpResponse::Created() } #[delete("/test/{param}")] -async fn delete_param_test(_: Path) -> impl Responder { +async fn delete_param_test(_: web::Path) -> impl Responder { HttpResponse::NoContent() } #[get("/test/{param}")] -async fn get_param_test(_: Path) -> impl Responder { +async fn get_param_test(_: web::Path) -> impl Responder { HttpResponse::Ok() } @@ -125,9 +129,7 @@ where type Error = Error; type Future = LocalBoxFuture<'static, Result>; - fn poll_ready(&self, cx: &mut Context<'_>) -> Poll> { - self.service.poll_ready(cx) - } + actix_web::dev::forward_ready!(service); fn call(&self, req: ServiceRequest) -> Self::Future { let fut = self.service.call(req); @@ -144,7 +146,7 @@ where } #[get("/test/wrap", wrap = "ChangeStatusCode")] -async fn get_wrap(_: Path) -> impl Responder { +async fn get_wrap(_: web::Path) -> impl Responder { // panic!("actually never gets called because path failed to extract"); HttpResponse::Ok() } diff --git a/src/lib.rs b/src/lib.rs index 54db969df..cf1bfa590 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -145,7 +145,7 @@ pub mod dev { pub use actix_http::{Extensions, Payload, PayloadStream, RequestHead, ResponseHead}; pub use actix_router::{Path, ResourceDef, ResourcePath, Url}; pub use actix_server::Server; - pub use actix_service::{Service, Transform}; + pub use actix_service::{always_ready, forward_ready, Service, Transform}; pub(crate) fn insert_slash(mut patterns: Vec) -> Vec { for path in &mut patterns { diff --git a/src/middleware/compat.rs b/src/middleware/compat.rs index 0d197ba80..0e3a4f2b7 100644 --- a/src/middleware/compat.rs +++ b/src/middleware/compat.rs @@ -80,9 +80,7 @@ where type Error = Error; type Future = CompatMiddlewareFuture; - fn poll_ready(&self, cx: &mut Context<'_>) -> Poll> { - self.service.poll_ready(cx).map_err(From::from) - } + actix_service::forward_ready!(service); fn call(&self, req: Req) -> Self::Future { let fut = self.service.call(req); From 5747f8473689b46bb46d6ad9c314263cc59680fa Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sat, 17 Apr 2021 02:07:33 +0100 Subject: [PATCH 64/79] bump utils to stable v3 --- Cargo.toml | 2 +- actix-files/Cargo.toml | 2 +- actix-http-test/Cargo.toml | 2 +- actix-http/Cargo.toml | 2 +- actix-multipart/Cargo.toml | 2 +- actix-test/Cargo.toml | 2 +- actix-web-codegen/Cargo.toml | 2 +- awc/Cargo.toml | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index c01111020..447a0b197 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -62,7 +62,7 @@ actix-router = "0.2.7" actix-rt = "2.2" actix-server = "2.0.0-beta.3" actix-service = "2.0.0" -actix-utils = "3.0.0-beta.4" +actix-utils = "3.0.0" actix-tls = { version = "3.0.0-beta.5", default-features = false, optional = true } actix-web-codegen = "0.5.0-beta.2" diff --git a/actix-files/Cargo.toml b/actix-files/Cargo.toml index 3b25806ad..b28c0c62d 100644 --- a/actix-files/Cargo.toml +++ b/actix-files/Cargo.toml @@ -19,7 +19,7 @@ path = "src/lib.rs" [dependencies] actix-web = { version = "4.0.0-beta.5", default-features = false } actix-service = "2.0.0" -actix-utils = "3.0.0-beta.4" +actix-utils = "3.0.0" askama_escape = "0.10" bitflags = "1" diff --git a/actix-http-test/Cargo.toml b/actix-http-test/Cargo.toml index 836510dce..2374984a2 100644 --- a/actix-http-test/Cargo.toml +++ b/actix-http-test/Cargo.toml @@ -32,7 +32,7 @@ openssl = ["tls-openssl", "awc/openssl"] actix-service = "2.0.0" actix-codec = "0.4.0-beta.1" actix-tls = "3.0.0-beta.5" -actix-utils = "3.0.0-beta.4" +actix-utils = "3.0.0" actix-rt = "2.2" actix-server = "2.0.0-beta.3" awc = { version = "3.0.0-beta.4", default-features = false } diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 13557c6a9..6189a1ae6 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -40,7 +40,7 @@ trust-dns = ["trust-dns-resolver"] [dependencies] actix-service = "2.0.0" actix-codec = "0.4.0-beta.1" -actix-utils = "3.0.0-beta.4" +actix-utils = "3.0.0" actix-rt = "2.2" actix-tls = { version = "3.0.0-beta.5", features = ["accept", "connect"] } diff --git a/actix-multipart/Cargo.toml b/actix-multipart/Cargo.toml index 77558fcc5..c4cb42450 100644 --- a/actix-multipart/Cargo.toml +++ b/actix-multipart/Cargo.toml @@ -17,7 +17,7 @@ path = "src/lib.rs" [dependencies] actix-web = { version = "4.0.0-beta.5", default-features = false } -actix-utils = "3.0.0-beta.4" +actix-utils = "3.0.0" bytes = "1" derive_more = "0.99.5" diff --git a/actix-test/Cargo.toml b/actix-test/Cargo.toml index 730fb7bed..e280516c3 100644 --- a/actix-test/Cargo.toml +++ b/actix-test/Cargo.toml @@ -23,7 +23,7 @@ actix-codec = "0.4.0-beta.1" actix-http = "3.0.0-beta.5" actix-http-test = { version = "3.0.0-beta.4", features = [] } actix-service = "2.0.0" -actix-utils = "3.0.0-beta.2" +actix-utils = "3.0.0" actix-web = { version = "4.0.0-beta.5", default-features = false, features = ["cookies"] } actix-rt = "2.1" awc = { version = "3.0.0-beta.4", default-features = false, features = ["cookies"] } diff --git a/actix-web-codegen/Cargo.toml b/actix-web-codegen/Cargo.toml index 04c988392..653ce038a 100644 --- a/actix-web-codegen/Cargo.toml +++ b/actix-web-codegen/Cargo.toml @@ -21,7 +21,7 @@ proc-macro2 = "1" [dev-dependencies] actix-rt = "2.2" actix-test = "0.1.0-beta.1" -actix-utils = "3.0.0-beta.4" +actix-utils = "3.0.0" actix-web = "4.0.0-beta.5" futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] } diff --git a/awc/Cargo.toml b/awc/Cargo.toml index 28d93ad36..e1d2699df 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -73,7 +73,7 @@ tls-rustls = { version = "0.19.0", package = "rustls", optional = true, features actix-web = { version = "4.0.0-beta.5", features = ["openssl"] } actix-http = { version = "3.0.0-beta.5", features = ["openssl"] } actix-http-test = { version = "3.0.0-beta.4", features = ["openssl"] } -actix-utils = "3.0.0-beta.4" +actix-utils = "3.0.0" actix-server = "2.0.0-beta.3" actix-tls = { version = "3.0.0-beta.5", features = ["openssl", "rustls"] } actix-test = { version = "0.1.0-beta.1", features = ["openssl", "rustls"] } From f743e885a33720adbc2bdca4808184d0a5868911 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sat, 17 Apr 2021 15:24:18 +0100 Subject: [PATCH 65/79] prepare http release 3.0.0-beta.6 --- Cargo.toml | 2 +- actix-http-test/Cargo.toml | 2 +- actix-http/CHANGES.md | 3 +++ actix-http/Cargo.toml | 2 +- actix-http/README.md | 4 ++-- actix-multipart/Cargo.toml | 2 +- actix-test/Cargo.toml | 2 +- actix-web-actors/Cargo.toml | 2 +- awc/Cargo.toml | 4 ++-- 9 files changed, 13 insertions(+), 10 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 447a0b197..a17de52c0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -66,7 +66,7 @@ actix-utils = "3.0.0" actix-tls = { version = "3.0.0-beta.5", default-features = false, optional = true } actix-web-codegen = "0.5.0-beta.2" -actix-http = "3.0.0-beta.5" +actix-http = "3.0.0-beta.6" ahash = "0.7" bytes = "1" diff --git a/actix-http-test/Cargo.toml b/actix-http-test/Cargo.toml index 2374984a2..bfce32540 100644 --- a/actix-http-test/Cargo.toml +++ b/actix-http-test/Cargo.toml @@ -52,4 +52,4 @@ tls-openssl = { version = "0.10.9", package = "openssl", optional = true } [dev-dependencies] actix-web = { version = "4.0.0-beta.5", default-features = false, features = ["cookies"] } -actix-http = "3.0.0-beta.5" +actix-http = "3.0.0-beta.6" diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 84d6617f7..6cf9ff276 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -1,6 +1,9 @@ # Changes ## Unreleased - 2021-xx-xx + + +## 3.0.0-beta.6 - 2021-04-17 ### Added * `impl MessageBody for Pin>`. [#2152] * `Response::{ok, bad_request, not_found, internal_server_error}`. [#2159] diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 6189a1ae6..61f60b79e 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-http" -version = "3.0.0-beta.5" +version = "3.0.0-beta.6" authors = ["Nikolay Kim "] description = "HTTP primitives for the Actix ecosystem" readme = "README.md" diff --git a/actix-http/README.md b/actix-http/README.md index 50a17a02f..87eb38e5d 100644 --- a/actix-http/README.md +++ b/actix-http/README.md @@ -3,11 +3,11 @@ > HTTP primitives for the Actix ecosystem. [![crates.io](https://img.shields.io/crates/v/actix-http?label=latest)](https://crates.io/crates/actix-http) -[![Documentation](https://docs.rs/actix-http/badge.svg?version=3.0.0-beta.5)](https://docs.rs/actix-http/3.0.0-beta.5) +[![Documentation](https://docs.rs/actix-http/badge.svg?version=3.0.0-beta.6)](https://docs.rs/actix-http/3.0.0-beta.6) [![Version](https://img.shields.io/badge/rustc-1.46+-ab6000.svg)](https://blog.rust-lang.org/2020/03/12/Rust-1.46.html) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-http.svg)
-[![dependency status](https://deps.rs/crate/actix-http/3.0.0-beta.5/status.svg)](https://deps.rs/crate/actix-http/3.0.0-beta.5) +[![dependency status](https://deps.rs/crate/actix-http/3.0.0-beta.6/status.svg)](https://deps.rs/crate/actix-http/3.0.0-beta.6) [![Download](https://img.shields.io/crates/d/actix-http.svg)](https://crates.io/crates/actix-http) [![Join the chat at https://gitter.im/actix/actix](https://badges.gitter.im/actix/actix.svg)](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) diff --git a/actix-multipart/Cargo.toml b/actix-multipart/Cargo.toml index c4cb42450..449e3121f 100644 --- a/actix-multipart/Cargo.toml +++ b/actix-multipart/Cargo.toml @@ -31,6 +31,6 @@ twoway = "0.2" [dev-dependencies] actix-rt = "2.2" -actix-http = "3.0.0-beta.5" +actix-http = "3.0.0-beta.6" tokio = { version = "1", features = ["sync"] } tokio-stream = "0.1" diff --git a/actix-test/Cargo.toml b/actix-test/Cargo.toml index e280516c3..b021db00e 100644 --- a/actix-test/Cargo.toml +++ b/actix-test/Cargo.toml @@ -20,7 +20,7 @@ openssl = ["tls-openssl", "actix-http/openssl"] [dependencies] actix-codec = "0.4.0-beta.1" -actix-http = "3.0.0-beta.5" +actix-http = "3.0.0-beta.6" actix-http-test = { version = "3.0.0-beta.4", features = [] } actix-service = "2.0.0" actix-utils = "3.0.0" diff --git a/actix-web-actors/Cargo.toml b/actix-web-actors/Cargo.toml index a539227e4..4871b470d 100644 --- a/actix-web-actors/Cargo.toml +++ b/actix-web-actors/Cargo.toml @@ -18,7 +18,7 @@ path = "src/lib.rs" [dependencies] actix = { version = "0.11.0-beta.3", default-features = false } actix-codec = "0.4.0-beta.1" -actix-http = "3.0.0-beta.5" +actix-http = "3.0.0-beta.6" actix-web = { version = "4.0.0-beta.5", default-features = false } bytes = "1" diff --git a/awc/Cargo.toml b/awc/Cargo.toml index e1d2699df..29f76371c 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -49,7 +49,7 @@ trust-dns = ["actix-http/trust-dns"] [dependencies] actix-codec = "0.4.0-beta.1" actix-service = "2.0.0" -actix-http = "3.0.0-beta.5" +actix-http = "3.0.0-beta.6" actix-rt = { version = "2.1", default-features = false } base64 = "0.13" @@ -71,7 +71,7 @@ tls-rustls = { version = "0.19.0", package = "rustls", optional = true, features [dev-dependencies] actix-web = { version = "4.0.0-beta.5", features = ["openssl"] } -actix-http = { version = "3.0.0-beta.5", features = ["openssl"] } +actix-http = { version = "3.0.0-beta.6", features = ["openssl"] } actix-http-test = { version = "3.0.0-beta.4", features = ["openssl"] } actix-utils = "3.0.0" actix-server = "2.0.0-beta.3" From b2d6b6a70c815689945256b508109b57bdff1d2a Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sat, 17 Apr 2021 15:28:13 +0100 Subject: [PATCH 66/79] prepare web release 4.0.0-beta.6 --- CHANGES.md | 3 +++ Cargo.toml | 11 ++++------- actix-files/Cargo.toml | 4 ++-- actix-http-test/Cargo.toml | 2 +- actix-multipart/Cargo.toml | 2 +- actix-test/Cargo.toml | 2 +- actix-web-actors/Cargo.toml | 2 +- actix-web-codegen/Cargo.toml | 2 +- awc/Cargo.toml | 2 +- 9 files changed, 15 insertions(+), 15 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index bf8fc9424..eb5130b99 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,9 @@ # Changes ## Unreleased - 2021-xx-xx + + +## 4.0.0-beta.6 - 2021-04-17 ### Added * `HttpResponse` and `HttpResponseBuilder` structs. [#2065] diff --git a/Cargo.toml b/Cargo.toml index a17de52c0..24d8f54fb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,16 +1,13 @@ [package] name = "actix-web" -version = "4.0.0-beta.5" +version = "4.0.0-beta.6" authors = ["Nikolay Kim "] description = "Actix Web is a powerful, pragmatic, and extremely fast web framework for Rust" -readme = "README.md" keywords = ["actix", "http", "web", "framework", "async"] -homepage = "https://actix.rs" -repository = "https://github.com/actix/actix-web.git" -documentation = "https://docs.rs/actix-web/" categories = ["network-programming", "asynchronous", - "web-programming::http-server", - "web-programming::websocket"] + "web-programming::http-server", "web-programming::websocket"] +homepage = "https://actix.rs" +repository = "https://github.com/actix/actix-web" license = "MIT OR Apache-2.0" edition = "2018" diff --git a/actix-files/Cargo.toml b/actix-files/Cargo.toml index b28c0c62d..9d87b839b 100644 --- a/actix-files/Cargo.toml +++ b/actix-files/Cargo.toml @@ -17,7 +17,7 @@ name = "actix_files" path = "src/lib.rs" [dependencies] -actix-web = { version = "4.0.0-beta.5", default-features = false } +actix-web = { version = "4.0.0-beta.6", default-features = false } actix-service = "2.0.0" actix-utils = "3.0.0" @@ -34,5 +34,5 @@ percent-encoding = "2.1" [dev-dependencies] actix-rt = "2.2" -actix-web = "4.0.0-beta.5" +actix-web = "4.0.0-beta.6" actix-test = "0.1.0-beta.1" diff --git a/actix-http-test/Cargo.toml b/actix-http-test/Cargo.toml index bfce32540..38e5c4639 100644 --- a/actix-http-test/Cargo.toml +++ b/actix-http-test/Cargo.toml @@ -51,5 +51,5 @@ time = { version = "0.2.23", default-features = false, features = ["std"] } tls-openssl = { version = "0.10.9", package = "openssl", optional = true } [dev-dependencies] -actix-web = { version = "4.0.0-beta.5", default-features = false, features = ["cookies"] } +actix-web = { version = "4.0.0-beta.6", default-features = false, features = ["cookies"] } actix-http = "3.0.0-beta.6" diff --git a/actix-multipart/Cargo.toml b/actix-multipart/Cargo.toml index 449e3121f..fd9a8d529 100644 --- a/actix-multipart/Cargo.toml +++ b/actix-multipart/Cargo.toml @@ -16,7 +16,7 @@ name = "actix_multipart" path = "src/lib.rs" [dependencies] -actix-web = { version = "4.0.0-beta.5", default-features = false } +actix-web = { version = "4.0.0-beta.6", default-features = false } actix-utils = "3.0.0" bytes = "1" diff --git a/actix-test/Cargo.toml b/actix-test/Cargo.toml index b021db00e..4c4c7cf5b 100644 --- a/actix-test/Cargo.toml +++ b/actix-test/Cargo.toml @@ -24,7 +24,7 @@ actix-http = "3.0.0-beta.6" actix-http-test = { version = "3.0.0-beta.4", features = [] } actix-service = "2.0.0" actix-utils = "3.0.0" -actix-web = { version = "4.0.0-beta.5", default-features = false, features = ["cookies"] } +actix-web = { version = "4.0.0-beta.6", default-features = false, features = ["cookies"] } actix-rt = "2.1" awc = { version = "3.0.0-beta.4", default-features = false, features = ["cookies"] } diff --git a/actix-web-actors/Cargo.toml b/actix-web-actors/Cargo.toml index 4871b470d..f6514ffb1 100644 --- a/actix-web-actors/Cargo.toml +++ b/actix-web-actors/Cargo.toml @@ -19,7 +19,7 @@ path = "src/lib.rs" actix = { version = "0.11.0-beta.3", default-features = false } actix-codec = "0.4.0-beta.1" actix-http = "3.0.0-beta.6" -actix-web = { version = "4.0.0-beta.5", default-features = false } +actix-web = { version = "4.0.0-beta.6", default-features = false } bytes = "1" bytestring = "1" diff --git a/actix-web-codegen/Cargo.toml b/actix-web-codegen/Cargo.toml index 653ce038a..7eb1b28fc 100644 --- a/actix-web-codegen/Cargo.toml +++ b/actix-web-codegen/Cargo.toml @@ -22,7 +22,7 @@ proc-macro2 = "1" actix-rt = "2.2" actix-test = "0.1.0-beta.1" actix-utils = "3.0.0" -actix-web = "4.0.0-beta.5" +actix-web = "4.0.0-beta.6" futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] } trybuild = "1" diff --git a/awc/Cargo.toml b/awc/Cargo.toml index 29f76371c..efc63053a 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -70,7 +70,7 @@ tls-openssl = { version = "0.10.9", package = "openssl", optional = true } tls-rustls = { version = "0.19.0", package = "rustls", optional = true, features = ["dangerous_configuration"] } [dev-dependencies] -actix-web = { version = "4.0.0-beta.5", features = ["openssl"] } +actix-web = { version = "4.0.0-beta.6", features = ["openssl"] } actix-http = { version = "3.0.0-beta.6", features = ["openssl"] } actix-http-test = { version = "3.0.0-beta.4", features = ["openssl"] } actix-utils = "3.0.0" From 5a162932f3ce79ad81381eccdab38dd4cef68f0c Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sat, 17 Apr 2021 15:30:31 +0100 Subject: [PATCH 67/79] prepare awc release 3.0.0-beta.5 --- Cargo.toml | 28 ++++++++++++++++------------ actix-http-test/Cargo.toml | 2 +- actix-test/Cargo.toml | 2 +- actix-web-actors/Cargo.toml | 2 +- awc/Cargo.toml | 8 +++----- 5 files changed, 22 insertions(+), 20 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 24d8f54fb..2f80fc398 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,8 +4,12 @@ version = "4.0.0-beta.6" authors = ["Nikolay Kim "] description = "Actix Web is a powerful, pragmatic, and extremely fast web framework for Rust" keywords = ["actix", "http", "web", "framework", "async"] -categories = ["network-programming", "asynchronous", - "web-programming::http-server", "web-programming::websocket"] +categories = [ + "network-programming", + "asynchronous", + "web-programming::http-server", + "web-programming::websocket" +] homepage = "https://actix.rs" repository = "https://github.com/actix/actix-web" license = "MIT OR Apache-2.0" @@ -21,15 +25,15 @@ path = "src/lib.rs" [workspace] members = [ - ".", - "awc", - "actix-http", - "actix-files", - "actix-multipart", - "actix-web-actors", - "actix-web-codegen", - "actix-http-test", - "actix-test", + ".", + "awc", + "actix-http", + "actix-files", + "actix-multipart", + "actix-web-actors", + "actix-web-codegen", + "actix-http-test", + "actix-test", ] # enable when MSRV is 1.51+ # resolver = "2" @@ -90,7 +94,7 @@ url = "2.1" [dev-dependencies] actix-test = { version = "0.1.0-beta.1", features = ["openssl", "rustls"] } -awc = { version = "3.0.0-beta.4", features = ["openssl"] } +awc = { version = "3.0.0-beta.5", features = ["openssl"] } brotli2 = "0.3.2" criterion = "0.3" diff --git a/actix-http-test/Cargo.toml b/actix-http-test/Cargo.toml index 38e5c4639..05a0e022a 100644 --- a/actix-http-test/Cargo.toml +++ b/actix-http-test/Cargo.toml @@ -35,7 +35,7 @@ actix-tls = "3.0.0-beta.5" actix-utils = "3.0.0" actix-rt = "2.2" actix-server = "2.0.0-beta.3" -awc = { version = "3.0.0-beta.4", default-features = false } +awc = { version = "3.0.0-beta.5", default-features = false } base64 = "0.13" bytes = "1" diff --git a/actix-test/Cargo.toml b/actix-test/Cargo.toml index 4c4c7cf5b..86d06dbd2 100644 --- a/actix-test/Cargo.toml +++ b/actix-test/Cargo.toml @@ -26,7 +26,7 @@ actix-service = "2.0.0" actix-utils = "3.0.0" actix-web = { version = "4.0.0-beta.6", default-features = false, features = ["cookies"] } actix-rt = "2.1" -awc = { version = "3.0.0-beta.4", default-features = false, features = ["cookies"] } +awc = { version = "3.0.0-beta.5", default-features = false, features = ["cookies"] } futures-core = { version = "0.3.7", default-features = false, features = ["std"] } futures-util = { version = "0.3.7", default-features = false, features = [] } diff --git a/actix-web-actors/Cargo.toml b/actix-web-actors/Cargo.toml index f6514ffb1..192cb3a11 100644 --- a/actix-web-actors/Cargo.toml +++ b/actix-web-actors/Cargo.toml @@ -31,6 +31,6 @@ tokio = { version = "1", features = ["sync"] } actix-rt = "2.2" actix-test = "0.1.0-beta.1" -awc = { version = "3.0.0-beta.4", default-features = false } +awc = { version = "3.0.0-beta.5", default-features = false } env_logger = "0.8" futures-util = { version = "0.3.7", default-features = false } diff --git a/awc/Cargo.toml b/awc/Cargo.toml index efc63053a..40ec4e448 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -1,22 +1,20 @@ [package] name = "awc" -version = "3.0.0-beta.4" +version = "3.0.0-beta.5" authors = [ "Nikolay Kim ", "fakeshadow <24548779@qq.com>", ] description = "Async HTTP and WebSocket client library built on the Actix ecosystem" -readme = "README.md" keywords = ["actix", "http", "framework", "async", "web"] -homepage = "https://actix.rs" -repository = "https://github.com/actix/actix-web.git" -documentation = "https://docs.rs/awc/" categories = [ "network-programming", "asynchronous", "web-programming::http-client", "web-programming::websocket", ] +homepage = "https://actix.rs" +repository = "https://github.com/actix/actix-web" license = "MIT OR Apache-2.0" edition = "2018" From f462aaa7b6129b633fa53935d0ee54dbc01b70ea Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sat, 17 Apr 2021 15:53:54 +0100 Subject: [PATCH 68/79] prepare actix-test release 0.1.0-beta.2 --- Cargo.toml | 2 +- actix-files/Cargo.toml | 2 +- actix-test/CHANGES.md | 4 ++++ actix-test/Cargo.toml | 2 +- actix-web-actors/Cargo.toml | 2 +- actix-web-codegen/Cargo.toml | 2 +- awc/CHANGES.md | 3 +++ awc/Cargo.toml | 2 +- 8 files changed, 13 insertions(+), 6 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 2f80fc398..e482dec27 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -93,7 +93,7 @@ time = { version = "0.2.23", default-features = false, features = ["std"] } url = "2.1" [dev-dependencies] -actix-test = { version = "0.1.0-beta.1", features = ["openssl", "rustls"] } +actix-test = { version = "0.1.0-beta.2", features = ["openssl", "rustls"] } awc = { version = "3.0.0-beta.5", features = ["openssl"] } brotli2 = "0.3.2" diff --git a/actix-files/Cargo.toml b/actix-files/Cargo.toml index 9d87b839b..b97badd3e 100644 --- a/actix-files/Cargo.toml +++ b/actix-files/Cargo.toml @@ -35,4 +35,4 @@ percent-encoding = "2.1" [dev-dependencies] actix-rt = "2.2" actix-web = "4.0.0-beta.6" -actix-test = "0.1.0-beta.1" +actix-test = "0.1.0-beta.2" diff --git a/actix-test/CHANGES.md b/actix-test/CHANGES.md index 70e643ba7..d57f8bf4a 100644 --- a/actix-test/CHANGES.md +++ b/actix-test/CHANGES.md @@ -3,6 +3,10 @@ ## Unreleased - 2021-xx-xx +## 0.1.0-beta.2 - 2021-04-17 +* * No significant changes from `0.1.0-beta.1`. + + ## 0.1.0-beta.1 - 2021-04-02 * Move integration testing structs from `actix-web`. [#2112] diff --git a/actix-test/Cargo.toml b/actix-test/Cargo.toml index 86d06dbd2..4c7e89f31 100644 --- a/actix-test/Cargo.toml +++ b/actix-test/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-test" -version = "0.1.0-beta.1" +version = "0.1.0-beta.2" authors = [ "Nikolay Kim ", "Rob Ede ", diff --git a/actix-web-actors/Cargo.toml b/actix-web-actors/Cargo.toml index 192cb3a11..ade2795cb 100644 --- a/actix-web-actors/Cargo.toml +++ b/actix-web-actors/Cargo.toml @@ -29,7 +29,7 @@ tokio = { version = "1", features = ["sync"] } [dev-dependencies] actix-rt = "2.2" -actix-test = "0.1.0-beta.1" +actix-test = "0.1.0-beta.2" awc = { version = "3.0.0-beta.5", default-features = false } env_logger = "0.8" diff --git a/actix-web-codegen/Cargo.toml b/actix-web-codegen/Cargo.toml index 7eb1b28fc..db4f8430c 100644 --- a/actix-web-codegen/Cargo.toml +++ b/actix-web-codegen/Cargo.toml @@ -20,7 +20,7 @@ proc-macro2 = "1" [dev-dependencies] actix-rt = "2.2" -actix-test = "0.1.0-beta.1" +actix-test = "0.1.0-beta.2" actix-utils = "3.0.0" actix-web = "4.0.0-beta.6" diff --git a/awc/CHANGES.md b/awc/CHANGES.md index a0bfcac86..b2e0ff78d 100644 --- a/awc/CHANGES.md +++ b/awc/CHANGES.md @@ -1,6 +1,9 @@ # Changes ## Unreleased - 2021-xx-xx + + +## 3.0.0-beta.5 - 2021-04-17 ### Removed * Deprecated methods on `ClientRequest`: `if_true`, `if_some`. [#2148] diff --git a/awc/Cargo.toml b/awc/Cargo.toml index 40ec4e448..5591048bc 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -74,7 +74,7 @@ actix-http-test = { version = "3.0.0-beta.4", features = ["openssl"] } actix-utils = "3.0.0" actix-server = "2.0.0-beta.3" actix-tls = { version = "3.0.0-beta.5", features = ["openssl", "rustls"] } -actix-test = { version = "0.1.0-beta.1", features = ["openssl", "rustls"] } +actix-test = { version = "0.1.0-beta.2", features = ["openssl", "rustls"] } brotli2 = "0.3.2" env_logger = "0.8" From 26e9c806264447d15bb71ad75da1b78a254f9aef Mon Sep 17 00:00:00 2001 From: Ibraheem Ahmed Date: Sun, 18 Apr 2021 18:34:51 -0400 Subject: [PATCH 69/79] Named file service (#2135) --- actix-files/src/files.rs | 12 ++++++ actix-files/src/lib.rs | 74 +++++++++++++++++++++++++++++++++ actix-files/src/named.rs | 80 +++++++++++++++++++++++++++++++++++- awc/tests/test_ssl_client.rs | 2 +- 4 files changed, 165 insertions(+), 3 deletions(-) diff --git a/actix-files/src/files.rs b/actix-files/src/files.rs index ff4241340..9b24c4b21 100644 --- a/actix-files/src/files.rs +++ b/actix-files/src/files.rs @@ -199,6 +199,18 @@ impl Files { } /// Sets default handler which is used when no matched file could be found. + /// + /// For example, you could set a fall back static file handler: + /// ```rust + /// use actix_files::{Files, NamedFile}; + /// + /// # fn run() -> Result<(), actix_web::Error> { + /// let files = Files::new("/", "./static") + /// .index_file("index.html") + /// .default_handler(NamedFile::open("./static/404.html")?); + /// # Ok(()) + /// # } + /// ``` pub fn default_handler(mut self, f: F) -> Self where F: IntoServiceFactory, diff --git a/actix-files/src/lib.rs b/actix-files/src/lib.rs index e9b55e87e..34b02581e 100644 --- a/actix-files/src/lib.rs +++ b/actix-files/src/lib.rs @@ -755,6 +755,80 @@ mod tests { assert_eq!(res.status(), StatusCode::OK); } + #[actix_rt::test] + async fn test_serve_named_file() { + let srv = + test::init_service(App::new().service(NamedFile::open("Cargo.toml").unwrap())) + .await; + + let req = TestRequest::get().uri("/Cargo.toml").to_request(); + let res = test::call_service(&srv, req).await; + assert_eq!(res.status(), StatusCode::OK); + + let bytes = test::read_body(res).await; + let data = Bytes::from(fs::read("Cargo.toml").unwrap()); + assert_eq!(bytes, data); + + let req = TestRequest::get().uri("/test/unknown").to_request(); + let res = test::call_service(&srv, req).await; + assert_eq!(res.status(), StatusCode::NOT_FOUND); + } + + #[actix_rt::test] + async fn test_serve_named_file_prefix() { + let srv = test::init_service( + App::new() + .service(web::scope("/test").service(NamedFile::open("Cargo.toml").unwrap())), + ) + .await; + + let req = TestRequest::get().uri("/test/Cargo.toml").to_request(); + let res = test::call_service(&srv, req).await; + assert_eq!(res.status(), StatusCode::OK); + + let bytes = test::read_body(res).await; + let data = Bytes::from(fs::read("Cargo.toml").unwrap()); + assert_eq!(bytes, data); + + let req = TestRequest::get().uri("/Cargo.toml").to_request(); + let res = test::call_service(&srv, req).await; + assert_eq!(res.status(), StatusCode::NOT_FOUND); + } + + #[actix_rt::test] + async fn test_named_file_default_service() { + let srv = test::init_service( + App::new().default_service(NamedFile::open("Cargo.toml").unwrap()), + ) + .await; + + for route in ["/foobar", "/baz", "/"].iter() { + let req = TestRequest::get().uri(route).to_request(); + let res = test::call_service(&srv, req).await; + assert_eq!(res.status(), StatusCode::OK); + + let bytes = test::read_body(res).await; + let data = Bytes::from(fs::read("Cargo.toml").unwrap()); + assert_eq!(bytes, data); + } + } + + #[actix_rt::test] + async fn test_default_handler_named_file() { + let st = Files::new("/", ".") + .default_handler(NamedFile::open("Cargo.toml").unwrap()) + .new_service(()) + .await + .unwrap(); + let req = TestRequest::with_uri("/missing").to_srv_request(); + let resp = test::call_service(&st, req).await; + + assert_eq!(resp.status(), StatusCode::OK); + let bytes = test::read_body(resp).await; + let data = Bytes::from(fs::read("Cargo.toml").unwrap()); + assert_eq!(bytes, data); + } + #[actix_rt::test] async fn test_symlinks() { let srv = test::init_service(App::new().service(Files::new("test", "."))).await; diff --git a/actix-files/src/named.rs b/actix-files/src/named.rs index 2846646a2..519234f0d 100644 --- a/actix-files/src/named.rs +++ b/actix-files/src/named.rs @@ -1,3 +1,6 @@ +use actix_service::{Service, ServiceFactory}; +use actix_utils::future::{ok, ready, Ready}; +use actix_web::dev::{AppService, HttpServiceFactory, ResourceDef}; use std::fs::{File, Metadata}; use std::io; use std::ops::{Deref, DerefMut}; @@ -8,14 +11,14 @@ use std::time::{SystemTime, UNIX_EPOCH}; use std::os::unix::fs::MetadataExt; use actix_web::{ - dev::{BodyEncoding, SizedStream}, + dev::{BodyEncoding, ServiceRequest, ServiceResponse, SizedStream}, http::{ header::{ self, Charset, ContentDisposition, DispositionParam, DispositionType, ExtendedValue, }, ContentEncoding, StatusCode, }, - HttpMessage, HttpRequest, HttpResponse, Responder, + Error, HttpMessage, HttpRequest, HttpResponse, Responder, }; use bitflags::bitflags; use mime_guess::from_path; @@ -39,6 +42,29 @@ impl Default for Flags { } /// A file with an associated name. +/// +/// `NamedFile` can be registered as services: +/// ``` +/// use actix_web::App; +/// use actix_files::NamedFile; +/// +/// # fn run() -> Result<(), Box> { +/// let app = App::new() +/// .service(NamedFile::open("./static/index.html")?); +/// # Ok(()) +/// # } +/// ``` +/// +/// They can also be returned from handlers: +/// ``` +/// use actix_web::{Responder, get}; +/// use actix_files::NamedFile; +/// +/// #[get("/")] +/// async fn index() -> impl Responder { +/// NamedFile::open("./static/index.html") +/// } +/// ``` #[derive(Debug)] pub struct NamedFile { path: PathBuf, @@ -480,3 +506,53 @@ impl Responder for NamedFile { self.into_response(req) } } + +impl ServiceFactory for NamedFile { + type Response = ServiceResponse; + type Error = Error; + type Config = (); + type InitError = (); + type Service = NamedFileService; + type Future = Ready>; + + fn new_service(&self, _: ()) -> Self::Future { + ok(NamedFileService { + path: self.path.clone(), + }) + } +} + +#[doc(hidden)] +#[derive(Debug)] +pub struct NamedFileService { + path: PathBuf, +} + +impl Service for NamedFileService { + type Response = ServiceResponse; + type Error = Error; + type Future = Ready>; + + actix_service::always_ready!(); + + fn call(&self, req: ServiceRequest) -> Self::Future { + let (req, _) = req.into_parts(); + ready( + NamedFile::open(&self.path) + .map_err(|e| e.into()) + .map(|f| f.into_response(&req)) + .map(|res| ServiceResponse::new(req, res)), + ) + } +} + +impl HttpServiceFactory for NamedFile { + fn register(self, config: &mut AppService) { + config.register_service( + ResourceDef::root_prefix(self.path.to_string_lossy().as_ref()), + None, + self, + None, + ) + } +} diff --git a/awc/tests/test_ssl_client.rs b/awc/tests/test_ssl_client.rs index 57305e49a..811efd4bc 100644 --- a/awc/tests/test_ssl_client.rs +++ b/awc/tests/test_ssl_client.rs @@ -7,7 +7,7 @@ use std::sync::Arc; use actix_http::HttpService; use actix_http_test::test_server; -use actix_service::{map_config, fn_service, ServiceFactoryExt}; +use actix_service::{fn_service, map_config, ServiceFactoryExt}; use actix_utils::future::ok; use actix_web::http::Version; use actix_web::{dev::AppConfig, web, App, HttpResponse}; From 8ffb1f201199e914196ff7e4080eedd7b5606571 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Mon, 19 Apr 2021 02:11:07 +0100 Subject: [PATCH 70/79] update files changelog --- actix-files/CHANGES.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/actix-files/CHANGES.md b/actix-files/CHANGES.md index 004479183..6c28e42d0 100644 --- a/actix-files/CHANGES.md +++ b/actix-files/CHANGES.md @@ -1,8 +1,10 @@ # Changes ## Unreleased - 2021-xx-xx +* `NamedFile` now implements `ServiceFactory` and `HttpServiceFactory` making it much more useful in routing. For example, it can be used directly as a default service. [#2135] * For symbolic links, `Content-Disposition` header no longer shows the filename of the original file. [#2156] +[#2135]: https://github.com/actix/actix-web/pull/2135 [#2156]: https://github.com/actix/actix-web/pull/2156 From 35f818841012012855c3644a75cf641bd6c96182 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Mon, 19 Apr 2021 02:24:20 +0100 Subject: [PATCH 71/79] restore cookie methods on ServiceRequest --- actix-http/src/http_message.rs | 16 ++++++++++------ src/service.rs | 12 ++++++++++++ 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/actix-http/src/http_message.rs b/actix-http/src/http_message.rs index 0f4e347e0..ccaa320fa 100644 --- a/actix-http/src/http_message.rs +++ b/actix-http/src/http_message.rs @@ -1,14 +1,18 @@ -use std::cell::{Ref, RefMut}; -use std::str; +use std::{ + cell::{Ref, RefMut}, + str, +}; use encoding_rs::{Encoding, UTF_8}; use http::header; use mime::Mime; -use crate::error::{ContentTypeError, ParseError}; -use crate::extensions::Extensions; -use crate::header::{Header, HeaderMap}; -use crate::payload::Payload; +use crate::{ + error::{ContentTypeError, ParseError}, + header::{Header, HeaderMap}, + payload::Payload, + Extensions, +}; /// Trait that implements general purpose operations on HTTP messages. pub trait HttpMessage: Sized { diff --git a/src/service.rs b/src/service.rs index 9765343c1..dd3597efb 100644 --- a/src/service.rs +++ b/src/service.rs @@ -9,6 +9,7 @@ use actix_http::{ }; use actix_router::{IntoPattern, Path, Resource, ResourceDef, Url}; use actix_service::{IntoServiceFactory, ServiceFactory}; +use cookie::{Cookie, ParseError as CookieParseError}; use crate::dev::insert_slash; use crate::guard::Guard; @@ -244,6 +245,17 @@ impl ServiceRequest { None } + #[cfg(feature = "cookies")] + pub fn cookies(&self) -> Result>>, CookieParseError> { + self.req.cookies() + } + + /// Return request cookie. + #[cfg(feature = "cookies")] + pub fn cookie(&self, name: &str) -> Option> { + self.req.cookie(name) + } + /// Set request payload. pub fn set_payload(&mut self, payload: Payload) { self.payload = payload; From b9dbc58e20a0b28ae8d543a581509b607c26a510 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Mon, 19 Apr 2021 02:31:11 +0100 Subject: [PATCH 72/79] content disposition methods take impl AsRef --- CHANGES.md | 1 + src/http/header/content_disposition.rs | 19 +++++++++---------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index eb5130b99..bea9ab935 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -9,6 +9,7 @@ ### Changed * Most error types are now marked `#[non_exhaustive]`. [#2148] +* Methods on `ContentDisposition` that took `T: AsRef` now take `impl AsRef`. [#2065]: https://github.com/actix/actix-web/pull/2065 [#2148]: https://github.com/actix/actix-web/pull/2148 diff --git a/src/http/header/content_disposition.rs b/src/http/header/content_disposition.rs index 509d68968..bff4e49a8 100644 --- a/src/http/header/content_disposition.rs +++ b/src/http/header/content_disposition.rs @@ -401,11 +401,11 @@ impl ContentDisposition { } /// Returns `true` if it is [`Ext`](DispositionType::Ext) and the `disp_type` matches. - pub fn is_ext>(&self, disp_type: T) -> bool { - match self.disposition { - DispositionType::Ext(ref t) if t.eq_ignore_ascii_case(disp_type.as_ref()) => true, - _ => false, - } + pub fn is_ext(&self, disp_type: impl AsRef) -> bool { + matches!( + self.disposition, + DispositionType::Ext(ref t) if t.eq_ignore_ascii_case(disp_type.as_ref()) + ) } /// Return the value of *name* if exists. @@ -430,7 +430,7 @@ impl ContentDisposition { } /// Return the value of the parameter which the `name` matches. - pub fn get_unknown>(&self, name: T) -> Option<&str> { + pub fn get_unknown(&self, name: impl AsRef) -> Option<&str> { let name = name.as_ref(); self.parameters .iter() @@ -439,7 +439,7 @@ impl ContentDisposition { } /// Return the value of the extended parameter which the `name` matches. - pub fn get_unknown_ext>(&self, name: T) -> Option<&ExtendedValue> { + pub fn get_unknown_ext(&self, name: impl AsRef) -> Option<&ExtendedValue> { let name = name.as_ref(); self.parameters .iter() @@ -552,9 +552,8 @@ impl fmt::Display for ContentDisposition { #[cfg(test)] mod tests { use super::{ContentDisposition, DispositionParam, DispositionType}; - use crate::http::header::Charset; - use crate::http::header::{ExtendedValue, HeaderValue}; - + use crate::http::header::{Charset, ExtendedValue, HeaderValue}; + #[test] fn test_from_raw_basic() { assert!(ContentDisposition::from_raw(&HeaderValue::from_static("")).is_err()); From db97974dc1caf2810bdbf235b2fe1a7e7ce74619 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Mon, 19 Apr 2021 03:29:38 +0100 Subject: [PATCH 73/79] make some http re-exports more accessible (#2171) --- actix-http/CHANGES.md | 13 +++++++++++ actix-http/src/error.rs | 5 +++-- actix-http/src/header/mod.rs | 30 +++++++++++++++++++++++--- actix-http/src/lib.rs | 8 ++++++- actix-http/src/message.rs | 15 +++++++------ actix-http/src/response_builder.rs | 10 ++++----- actix-test/CHANGES.md | 2 +- src/http/header/content_disposition.rs | 2 +- src/service.rs | 1 + 9 files changed, 66 insertions(+), 20 deletions(-) diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 6cf9ff276..e89208748 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -1,6 +1,19 @@ # Changes ## Unreleased - 2021-xx-xx +### Added +* Re-export `http` crate's `Error` type as `error::HttpError`. [#2171] +* Re-export `StatusCode`, `Method`, `Version` and `Uri` at the crate root. [#2171] +* Re-export `ContentEncoding` and `ConnectionType` at the crate root. [#2171] + +### Changed +* `header` mod is now public. [#2171] +* `uri` mod is now public. [#2171] + +### Removed +* Stop re-exporting `http` crate's `HeaderMap` types in addition to ours. [#2171] + +[#2171]: https://github.com/actix/actix-web/pull/2171 ## 3.0.0-beta.6 - 2021-04-17 diff --git a/actix-http/src/error.rs b/actix-http/src/error.rs index 68ad709a1..cd2917c93 100644 --- a/actix-http/src/error.rs +++ b/actix-http/src/error.rs @@ -10,12 +10,13 @@ use std::{ use bytes::BytesMut; use derive_more::{Display, Error, From}; -use http::uri::InvalidUri; -use http::{header, Error as HttpError, StatusCode}; +use http::{header, uri::InvalidUri, StatusCode}; use serde::de::value::Error as DeError; use crate::{body::Body, helpers::Writer, Response, ResponseBuilder}; +pub use http::Error as HttpError; + /// A specialized [`std::result::Result`] /// for actix web operations /// diff --git a/actix-http/src/header/mod.rs b/actix-http/src/header/mod.rs index a6056ace4..18494f555 100644 --- a/actix-http/src/header/mod.rs +++ b/actix-http/src/header/mod.rs @@ -1,9 +1,33 @@ -//! Typed HTTP headers, pre-defined `HeaderName`s, traits for parsing and conversion, and other -//! header utility methods. +//! Pre-defined `HeaderName`s, traits for parsing and conversion, and other header utility methods. use percent_encoding::{AsciiSet, CONTROLS}; -pub use http::header::*; +// re-export from http except header map related items +pub use http::header::{ + HeaderName, HeaderValue, InvalidHeaderName, InvalidHeaderValue, ToStrError, +}; + +// re-export const header names +pub use http::header::{ + ACCEPT, ACCEPT_CHARSET, ACCEPT_ENCODING, ACCEPT_LANGUAGE, ACCEPT_RANGES, + ACCESS_CONTROL_ALLOW_CREDENTIALS, ACCESS_CONTROL_ALLOW_HEADERS, + ACCESS_CONTROL_ALLOW_METHODS, ACCESS_CONTROL_ALLOW_ORIGIN, + ACCESS_CONTROL_EXPOSE_HEADERS, ACCESS_CONTROL_MAX_AGE, + ACCESS_CONTROL_REQUEST_HEADERS, ACCESS_CONTROL_REQUEST_METHOD, AGE, ALLOW, ALT_SVC, + AUTHORIZATION, CACHE_CONTROL, CONNECTION, CONTENT_DISPOSITION, CONTENT_ENCODING, + CONTENT_LANGUAGE, CONTENT_LENGTH, CONTENT_LOCATION, CONTENT_RANGE, + CONTENT_SECURITY_POLICY, CONTENT_SECURITY_POLICY_REPORT_ONLY, CONTENT_TYPE, COOKIE, + DATE, DNT, ETAG, EXPECT, EXPIRES, FORWARDED, FROM, HOST, IF_MATCH, + IF_MODIFIED_SINCE, IF_NONE_MATCH, IF_RANGE, IF_UNMODIFIED_SINCE, LAST_MODIFIED, + LINK, LOCATION, MAX_FORWARDS, ORIGIN, PRAGMA, PROXY_AUTHENTICATE, + PROXY_AUTHORIZATION, PUBLIC_KEY_PINS, PUBLIC_KEY_PINS_REPORT_ONLY, RANGE, REFERER, + REFERRER_POLICY, REFRESH, RETRY_AFTER, SEC_WEBSOCKET_ACCEPT, + SEC_WEBSOCKET_EXTENSIONS, SEC_WEBSOCKET_KEY, SEC_WEBSOCKET_PROTOCOL, + SEC_WEBSOCKET_VERSION, SERVER, SET_COOKIE, STRICT_TRANSPORT_SECURITY, TE, TRAILER, + TRANSFER_ENCODING, UPGRADE, UPGRADE_INSECURE_REQUESTS, USER_AGENT, VARY, VIA, + WARNING, WWW_AUTHENTICATE, X_CONTENT_TYPE_OPTIONS, X_DNS_PREFETCH_CONTROL, + X_FRAME_OPTIONS, X_XSS_PROTECTION, +}; use crate::error::ParseError; use crate::HttpMessage; diff --git a/actix-http/src/lib.rs b/actix-http/src/lib.rs index 4547f3ef2..82d0415c2 100644 --- a/actix-http/src/lib.rs +++ b/actix-http/src/lib.rs @@ -35,7 +35,7 @@ mod config; #[cfg(feature = "compress")] pub mod encoding; mod extensions; -mod header; +pub mod header; mod helpers; mod http_message; mod message; @@ -56,7 +56,9 @@ pub use self::builder::HttpServiceBuilder; pub use self::config::{KeepAlive, ServiceConfig}; pub use self::error::{Error, ResponseError, Result}; pub use self::extensions::Extensions; +pub use self::header::ContentEncoding; pub use self::http_message::HttpMessage; +pub use self::message::ConnectionType; pub use self::message::{Message, RequestHead, RequestHeadType, ResponseHead}; pub use self::payload::{Payload, PayloadStream}; pub use self::request::Request; @@ -64,6 +66,10 @@ pub use self::response::Response; pub use self::response_builder::ResponseBuilder; pub use self::service::HttpService; +pub use ::http::{uri, uri::Uri}; +pub use ::http::{Method, StatusCode, Version}; + +// TODO: deprecate this mish-mash of random items pub mod http { //! Various HTTP related types. diff --git a/actix-http/src/message.rs b/actix-http/src/message.rs index c89f5311a..8cb99d43a 100644 --- a/actix-http/src/message.rs +++ b/actix-http/src/message.rs @@ -1,12 +1,15 @@ -use std::cell::{Ref, RefCell, RefMut}; -use std::net; -use std::rc::Rc; +use std::{ + cell::{Ref, RefCell, RefMut}, + net, + rc::Rc, +}; use bitflags::bitflags; -use crate::extensions::Extensions; -use crate::header::HeaderMap; -use crate::http::{header, Method, StatusCode, Uri, Version}; +use crate::{ + header::{self, HeaderMap}, + Extensions, Method, StatusCode, Uri, Version, +}; /// Represents various types of connection #[derive(Copy, Clone, PartialEq, Debug)] diff --git a/actix-http/src/response_builder.rs b/actix-http/src/response_builder.rs index 4d8cb4429..0105f70cf 100644 --- a/actix-http/src/response_builder.rs +++ b/actix-http/src/response_builder.rs @@ -14,12 +14,10 @@ use futures_core::Stream; use crate::{ body::{Body, BodyStream, ResponseBody}, - error::Error, - extensions::Extensions, - header::{IntoHeaderPair, IntoHeaderValue}, - http::{header, Error as HttpError, StatusCode}, + error::{Error, HttpError}, + header::{self, IntoHeaderPair, IntoHeaderValue}, message::{BoxedResponseHead, ConnectionType, ResponseHead}, - Response, + Extensions, Response, StatusCode, }; /// An HTTP response builder. @@ -291,7 +289,7 @@ impl ResponseBuilder { return None; } - self.head.as_mut().map(|r| &mut **r) + self.head.as_deref_mut() } } diff --git a/actix-test/CHANGES.md b/actix-test/CHANGES.md index d57f8bf4a..2276fe745 100644 --- a/actix-test/CHANGES.md +++ b/actix-test/CHANGES.md @@ -4,7 +4,7 @@ ## 0.1.0-beta.2 - 2021-04-17 -* * No significant changes from `0.1.0-beta.1`. +* No significant changes from `0.1.0-beta.1`. ## 0.1.0-beta.1 - 2021-04-02 diff --git a/src/http/header/content_disposition.rs b/src/http/header/content_disposition.rs index bff4e49a8..71c610157 100644 --- a/src/http/header/content_disposition.rs +++ b/src/http/header/content_disposition.rs @@ -553,7 +553,7 @@ impl fmt::Display for ContentDisposition { mod tests { use super::{ContentDisposition, DispositionParam, DispositionType}; use crate::http::header::{Charset, ExtendedValue, HeaderValue}; - + #[test] fn test_from_raw_basic() { assert!(ContentDisposition::from_raw(&HeaderValue::from_static("")).is_err()); diff --git a/src/service.rs b/src/service.rs index dd3597efb..f6d1f9ebf 100644 --- a/src/service.rs +++ b/src/service.rs @@ -9,6 +9,7 @@ use actix_http::{ }; use actix_router::{IntoPattern, Path, Resource, ResourceDef, Url}; use actix_service::{IntoServiceFactory, ServiceFactory}; +#[cfg(feature = "cookies")] use cookie::{Cookie, ParseError as CookieParseError}; use crate::dev::insert_slash; From 52bb2b5daf30cc409386e7aaeee07b9c90dd6b4e Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Mon, 19 Apr 2021 03:42:53 +0100 Subject: [PATCH 74/79] hide downcast macros --- actix-http/src/macros.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/actix-http/src/macros.rs b/actix-http/src/macros.rs index 714629b43..7cf0e288b 100644 --- a/actix-http/src/macros.rs +++ b/actix-http/src/macros.rs @@ -1,4 +1,5 @@ #[macro_export] +#[doc(hidden)] macro_rules! downcast_get_type_id { () => { /// A helper method to get the type ID of the type @@ -25,6 +26,7 @@ macro_rules! downcast_get_type_id { } //Generate implementation for dyn $name +#[doc(hidden)] #[macro_export] macro_rules! downcast { ($name:ident) => { From 2aa674c1fdb121cc6330f65b2bb18b4b4fa139e8 Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Mon, 19 Apr 2021 15:15:57 -0700 Subject: [PATCH 75/79] Fix perf drop in HttpResponseBuilder (#2174) --- src/response/builder.rs | 32 +++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/src/response/builder.rs b/src/response/builder.rs index 2c04f3f64..8b3c0f10d 100644 --- a/src/response/builder.rs +++ b/src/response/builder.rs @@ -32,7 +32,7 @@ use crate::{ /// /// This type can be used to construct an instance of `Response` through a builder-like pattern. pub struct HttpResponseBuilder { - head: Option, + res: Option>, err: Option, #[cfg(feature = "cookies")] cookies: Option, @@ -43,7 +43,7 @@ impl HttpResponseBuilder { /// Create response builder pub fn new(status: StatusCode) -> Self { Self { - head: Some(ResponseHead::new(status)), + res: Some(Response::new(status)), err: None, #[cfg(feature = "cookies")] cookies: None, @@ -291,15 +291,19 @@ impl HttpResponseBuilder { /// Responses extensions #[inline] pub fn extensions(&self) -> Ref<'_, Extensions> { - let head = self.head.as_ref().expect("cannot reuse response builder"); - head.extensions() + self.res + .as_ref() + .expect("cannot reuse response builder") + .extensions() } /// Mutable reference to a the response's extensions #[inline] pub fn extensions_mut(&mut self) -> RefMut<'_, Extensions> { - let head = self.head.as_ref().expect("cannot reuse response builder"); - head.extensions_mut() + self.res + .as_mut() + .expect("cannot reuse response builder") + .extensions_mut() } /// Set a body and generate `Response`. @@ -318,12 +322,14 @@ impl HttpResponseBuilder { return HttpResponse::from_error(Error::from(err)).into_body(); } - // allow unused mut when cookies feature is disabled - #[allow(unused_mut)] - let mut head = self.head.take().expect("cannot reuse response builder"); + let res = self + .res + .take() + .expect("cannot reuse response builder") + .set_body(body); - let mut res = HttpResponse::with_body(StatusCode::OK, body); - *res.head_mut() = head; + #[allow(unused_mut)] + let mut res = HttpResponse::from(res); #[cfg(feature = "cookies")] if let Some(ref jar) = self.cookies { @@ -383,7 +389,7 @@ impl HttpResponseBuilder { /// This method construct new `HttpResponseBuilder` pub fn take(&mut self) -> Self { Self { - head: self.head.take(), + res: self.res.take(), err: self.err.take(), #[cfg(feature = "cookies")] cookies: self.cookies.take(), @@ -396,7 +402,7 @@ impl HttpResponseBuilder { return None; } - self.head.as_mut() + self.res.as_mut().map(|res| res.head_mut()) } } From 427fe6bd82e70921e9353636bb5096c549ec59cc Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Mon, 19 Apr 2021 21:12:52 +0100 Subject: [PATCH 76/79] improve responseerror trait docs --- actix-http/src/error.rs | 20 ++++++++++---------- actix-http/src/helpers.rs | 1 + 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/actix-http/src/error.rs b/actix-http/src/error.rs index cd2917c93..39ffa29e7 100644 --- a/actix-http/src/error.rs +++ b/actix-http/src/error.rs @@ -17,12 +17,10 @@ use crate::{body::Body, helpers::Writer, Response, ResponseBuilder}; pub use http::Error as HttpError; -/// A specialized [`std::result::Result`] -/// for actix web operations +/// A specialized [`std::result::Result`] for Actix Web operations. /// -/// This typedef is generally used to avoid writing out -/// `actix_http::error::Error` directly and is otherwise a direct mapping to -/// `Result`. +/// This typedef is generally used to avoid writing out `actix_http::error::Error` directly and is +/// otherwise a direct mapping to `Result`. pub type Result = std::result::Result; /// General purpose actix web error. @@ -51,18 +49,20 @@ impl Error { } } -/// Error that can be converted to `Response` +/// Errors that can generate responses. pub trait ResponseError: fmt::Debug + fmt::Display { - /// Response's status code + /// Returns appropriate status code for error. /// - /// Internal server error is generated by default. + /// A 500 Internal Server Error is used by default. If [error_response](Self::error_response) is + /// also implemented and does not call `self.status_code()`, then this will not be used. fn status_code(&self) -> StatusCode { StatusCode::INTERNAL_SERVER_ERROR } - /// Create response for error + /// Creates full response for error. /// - /// Internal server error is generated by default. + /// By default, the generated response uses a 500 Internal Server Error status code, a + /// `Content-Type` of `text/plain`, and the body is set to `Self`'s `Display` impl. fn error_response(&self) -> Response { let mut resp = Response::new(self.status_code()); let mut buf = BytesMut::new(); diff --git a/actix-http/src/helpers.rs b/actix-http/src/helpers.rs index 74188717d..b00ca307e 100644 --- a/actix-http/src/helpers.rs +++ b/actix-http/src/helpers.rs @@ -41,6 +41,7 @@ pub fn write_content_length(n: u64, buf: &mut B) { buf.put_slice(b"\r\n"); } +// TODO: bench why this is needed pub(crate) struct Writer<'a, B>(pub &'a mut B); impl<'a, B> io::Write for Writer<'a, B> From 6a9c4f102657f35b7aba86ea8eb3ff4e3dee009a Mon Sep 17 00:00:00 2001 From: Ibraheem Ahmed Date: Tue, 20 Apr 2021 14:57:27 -0400 Subject: [PATCH 77/79] update awc docs link, formatting (#2180) --- README.md | 2 +- src/lib.rs | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index c85c0652f..60ec57c60 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ * Static assets * SSL support using OpenSSL or Rustls * Middlewares ([Logger, Session, CORS, etc](https://actix.rs/docs/middleware/)) -* Includes an async [HTTP client](https://docs.rs/actix-web/latest/actix_web/client/index.html) +* Includes an async [HTTP client](https://docs.rs/awc/) * Runs on stable Rust 1.46+ ## Documentation diff --git a/src/lib.rs b/src/lib.rs index cf1bfa590..4d0ad26ed 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -30,16 +30,16 @@ //! //! To get started navigating the API docs, you may consider looking at the following pages first: //! -//! * [App]: This struct represents an Actix Web application and is used to +//! * [`App`]: This struct represents an Actix Web application and is used to //! configure routes and other common application settings. //! -//! * [HttpServer]: This struct represents an HTTP server instance and is +//! * [`HttpServer`]: This struct represents an HTTP server instance and is //! used to instantiate and configure servers. //! -//! * [web]: This module provides essential types for route registration as well as +//! * [`web`]: This module provides essential types for route registration as well as //! common utilities for request handlers. //! -//! * [HttpRequest] and [HttpResponse]: These +//! * [`HttpRequest`] and [`HttpResponse`]: These //! structs represent HTTP requests and responses and expose methods for creating, inspecting, //! and otherwise utilizing them. //! @@ -55,7 +55,7 @@ //! * Static assets //! * SSL support using OpenSSL or Rustls //! * Middlewares ([Logger, Session, CORS, etc](https://actix.rs/docs/middleware/)) -//! * Includes an async [HTTP client](https://actix.rs/actix-web/actix_web/client/index.html) +//! * Includes an async [HTTP client](https://docs.rs/awc/) //! * Runs on stable Rust 1.46+ //! //! ## Crate Features From a7cd4e85cf75e2cdf6fc54d91ef3c60967e64288 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Wed, 21 Apr 2021 11:14:22 +0100 Subject: [PATCH 78/79] use stable codec 0.4.0 --- Cargo.toml | 2 +- actix-http-test/Cargo.toml | 2 +- actix-http/Cargo.toml | 2 +- actix-test/Cargo.toml | 2 +- actix-web-actors/Cargo.toml | 2 +- awc/Cargo.toml | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e482dec27..714da13a0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -57,7 +57,7 @@ openssl = ["actix-http/openssl", "actix-tls/accept", "actix-tls/openssl"] rustls = ["actix-http/rustls", "actix-tls/accept", "actix-tls/rustls"] [dependencies] -actix-codec = "0.4.0-beta.1" +actix-codec = "0.4.0" actix-macros = "0.2.0" actix-router = "0.2.7" actix-rt = "2.2" diff --git a/actix-http-test/Cargo.toml b/actix-http-test/Cargo.toml index 05a0e022a..88b4bd04a 100644 --- a/actix-http-test/Cargo.toml +++ b/actix-http-test/Cargo.toml @@ -30,7 +30,7 @@ openssl = ["tls-openssl", "awc/openssl"] [dependencies] actix-service = "2.0.0" -actix-codec = "0.4.0-beta.1" +actix-codec = "0.4.0" actix-tls = "3.0.0-beta.5" actix-utils = "3.0.0" actix-rt = "2.2" diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 61f60b79e..55dc6d05e 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -39,7 +39,7 @@ trust-dns = ["trust-dns-resolver"] [dependencies] actix-service = "2.0.0" -actix-codec = "0.4.0-beta.1" +actix-codec = "0.4.0" actix-utils = "3.0.0" actix-rt = "2.2" actix-tls = { version = "3.0.0-beta.5", features = ["accept", "connect"] } diff --git a/actix-test/Cargo.toml b/actix-test/Cargo.toml index 4c7e89f31..23f3650cd 100644 --- a/actix-test/Cargo.toml +++ b/actix-test/Cargo.toml @@ -19,7 +19,7 @@ rustls = ["tls-rustls", "actix-http/rustls"] openssl = ["tls-openssl", "actix-http/openssl"] [dependencies] -actix-codec = "0.4.0-beta.1" +actix-codec = "0.4.0" actix-http = "3.0.0-beta.6" actix-http-test = { version = "3.0.0-beta.4", features = [] } actix-service = "2.0.0" diff --git a/actix-web-actors/Cargo.toml b/actix-web-actors/Cargo.toml index ade2795cb..b653dd92a 100644 --- a/actix-web-actors/Cargo.toml +++ b/actix-web-actors/Cargo.toml @@ -17,7 +17,7 @@ path = "src/lib.rs" [dependencies] actix = { version = "0.11.0-beta.3", default-features = false } -actix-codec = "0.4.0-beta.1" +actix-codec = "0.4.0" actix-http = "3.0.0-beta.6" actix-web = { version = "4.0.0-beta.6", default-features = false } diff --git a/awc/Cargo.toml b/awc/Cargo.toml index 5591048bc..c8a184513 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -45,7 +45,7 @@ cookies = ["cookie"] trust-dns = ["actix-http/trust-dns"] [dependencies] -actix-codec = "0.4.0-beta.1" +actix-codec = "0.4.0" actix-service = "2.0.0" actix-http = "3.0.0-beta.6" actix-rt = { version = "2.1", default-features = false } From 07036b56407c4abdd3034ee5594a43a0ea57cd0e Mon Sep 17 00:00:00 2001 From: Ibraheem Ahmed Date: Thu, 22 Apr 2021 08:54:29 -0400 Subject: [PATCH 79/79] static form extract future (#2181) --- src/types/form.rs | 53 ++++++++++++++++++++++++++++++++++------------- 1 file changed, 39 insertions(+), 14 deletions(-) diff --git a/src/types/form.rs b/src/types/form.rs index 9d2311a45..d1deac937 100644 --- a/src/types/form.rs +++ b/src/types/form.rs @@ -12,7 +12,7 @@ use std::{ use actix_http::Payload; use bytes::BytesMut; use encoding_rs::{Encoding, UTF_8}; -use futures_core::future::LocalBoxFuture; +use futures_core::{future::LocalBoxFuture, ready}; use futures_util::{FutureExt as _, StreamExt as _}; use serde::{de::DeserializeOwned, Serialize}; @@ -123,11 +123,10 @@ where { type Config = FormConfig; type Error = Error; - type Future = LocalBoxFuture<'static, Result>; + type Future = FormExtractFut; #[inline] fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future { - let req2 = req.clone(); let (limit, err_handler) = req .app_data::() .or_else(|| { @@ -137,16 +136,42 @@ where .map(|c| (c.limit, c.err_handler.clone())) .unwrap_or((16384, None)); - UrlEncoded::new(req, payload) - .limit(limit) - .map(move |res| match res { - Err(err) => match err_handler { - Some(err_handler) => Err((err_handler)(err, &req2)), - None => Err(err.into()), - }, - Ok(item) => Ok(Form(item)), - }) - .boxed_local() + FormExtractFut { + fut: UrlEncoded::new(req, payload).limit(limit), + req: req.clone(), + err_handler, + } + } +} + +type FormErrHandler = Option Error>>; + +pub struct FormExtractFut { + fut: UrlEncoded, + err_handler: FormErrHandler, + req: HttpRequest, +} + +impl Future for FormExtractFut +where + T: DeserializeOwned + 'static, +{ + type Output = Result, Error>; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let this = self.get_mut(); + + let res = ready!(Pin::new(&mut this.fut).poll(cx)); + + let res = match res { + Err(err) => match &this.err_handler { + Some(err_handler) => Err((err_handler)(err, &this.req)), + None => Err(err.into()), + }, + Ok(item) => Ok(Form(item)), + }; + + Poll::Ready(res) } } @@ -193,7 +218,7 @@ impl Responder for Form { #[derive(Clone)] pub struct FormConfig { limit: usize, - err_handler: Option Error>>, + err_handler: FormErrHandler, } impl FormConfig {