From edd9f14752b23211583b74a40d08ade293bbc284 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Tue, 13 Apr 2021 11:16:12 +0100 Subject: [PATCH 01/31] 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 02/31] 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 03/31] 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 04/31] 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 05/31] 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 06/31] 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 07/31] 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 08/31] 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 09/31] 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 10/31] 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 11/31] 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 12/31] 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 13/31] 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 14/31] 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 15/31] 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 16/31] 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 17/31] 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 18/31] 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 19/31] 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 20/31] 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 21/31] 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 22/31] 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 23/31] 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 24/31] 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 25/31] 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 26/31] 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 27/31] 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 28/31] 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 29/31] 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 30/31] 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 31/31] 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 }