diff --git a/.cargo/config.toml b/.cargo/config.toml index 40fe3e573..0bab205cd 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -1,3 +1,7 @@ [alias] chk = "hack check --workspace --all-features --tests --examples" lint = "hack --clean-per-run clippy --workspace --tests --examples" +ci-min = "hack check --workspace --no-default-features" +ci-min-test = "hack check --workspace --no-default-features --tests --examples" +ci-default = "hack check --workspace" +ci-full = "check --workspace --bins --examples --tests" diff --git a/Cargo.toml b/Cargo.toml index d40e68e1a..87d8a49e9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,7 +16,7 @@ edition = "2018" [package.metadata.docs.rs] # features that docs.rs will build with -features = ["openssl", "rustls", "compress", "secure-cookies"] +features = ["openssl", "rustls", "compress", "cookies", "secure-cookies"] [lib] name = "actix_web" @@ -34,6 +34,7 @@ members = [ "actix-http-test", "actix-test", ] +resolver = "2" [features] default = ["compress", "cookies"] @@ -53,22 +54,6 @@ openssl = ["actix-http/openssl", "actix-tls/accept", "actix-tls/openssl"] # rustls rustls = ["actix-http/rustls", "actix-tls/accept", "actix-tls/rustls"] -[[example]] -name = "basic" -required-features = ["compress"] - -[[example]] -name = "uds" -required-features = ["compress"] - -[[test]] -name = "test_server" -required-features = ["compress", "cookies"] - -[[example]] -name = "on_connect" -required-features = [] - [dependencies] actix-codec = "0.4.0-beta.1" actix-macros = "0.2.0" @@ -135,6 +120,22 @@ actix-web-actors = { path = "actix-web-actors" } actix-web-codegen = { path = "actix-web-codegen" } awc = { path = "awc" } +[[test]] +name = "test_server" +required-features = ["compress", "cookies"] + +[[example]] +name = "basic" +required-features = ["compress"] + +[[example]] +name = "uds" +required-features = ["compress"] + +[[example]] +name = "on_connect" +required-features = [] + [[bench]] name = "server" harness = false diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 81ba68424..5e4258677 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -2,8 +2,13 @@ ## Unreleased - 2021-xx-xx ### Removed -* `HttpMessage::cookies` and `HttpMessage::cookie`. [#????] -* `impl ResponseError for CookieParseError`. [#????] +* `cookies` feature flag. [#2065] +* Top-level `cookies` mod (re-export). [#2065] +* `HttpMessage` trait loses the `cookies` and `cookie` methods. [#2065] +* `impl ResponseError for CookieParseError`. [#2065] + +[#2065]: https://github.com/actix/actix-web/pull/2065 + ## 3.0.0-beta.5 - 2021-04-02 ### Added diff --git a/actix-http/examples/ws.rs b/actix-http/examples/ws.rs index 2374c2d96..af66f7d71 100644 --- a/actix-http/examples/ws.rs +++ b/actix-http/examples/ws.rs @@ -36,12 +36,12 @@ async fn main() -> io::Result<()> { async fn handler(req: Request) -> Result>, Error> { log::info!("handshaking"); - let res = ws::handshake(req.head())?; + let mut res = ws::handshake(req.head())?; // handshake will always fail under HTTP/2 log::info!("responding"); - Ok(res.set_body(BodyStream::new(Heartbeat::new(ws::Codec::new())))) + Ok(res.message_body(BodyStream::new(Heartbeat::new(ws::Codec::new())))) } struct Heartbeat { diff --git a/actix-http/src/error.rs b/actix-http/src/error.rs index e315501bc..03693ff2f 100644 --- a/actix-http/src/error.rs +++ b/actix-http/src/error.rs @@ -14,9 +14,7 @@ use serde::de::value::Error as DeError; use serde_json::error::Error as JsonError; use serde_urlencoded::ser::Error as FormError; -use crate::body::Body; -use crate::helpers::Writer; -use crate::response::Response; +use crate::{body::Body, helpers::Writer, Response, ResponseBuilder}; /// A specialized [`std::result::Result`] /// for actix web operations @@ -135,12 +133,12 @@ impl From for Error { } } -// /// Convert ResponseBuilder to a Error -// impl From for Error { -// fn from(mut res: ResponseBuilder) -> Error { -// InternalError::from_response("", res.finish()).into() -// } -// } +/// Convert ResponseBuilder to a Error +impl From for Error { + fn from(mut res: ResponseBuilder) -> Error { + InternalError::from_response("", res.finish()).into() + } +} #[derive(Debug, Display)] #[display(fmt = "UnknownError")] diff --git a/actix-http/src/h1/dispatcher.rs b/actix-http/src/h1/dispatcher.rs index 4fc200b43..bba79217a 100644 --- a/actix-http/src/h1/dispatcher.rs +++ b/actix-http/src/h1/dispatcher.rs @@ -340,14 +340,12 @@ where StateProj::ServiceCall(fut) => match fut.poll(cx) { // service call resolved. send response. Poll::Ready(Ok(res)) => { - eprintln!("dispatcher ok!"); let (res, body) = res.into().replace_body(()); self.as_mut().send_response(res, body)?; } // send service call error as response Poll::Ready(Err(err)) => { - eprintln!("dispatcher err"); let res = Response::from_error(err.into()); let (res, body) = res.replace_body(()); self.as_mut().send_response(res, body.into_body())?; diff --git a/actix-http/src/response.rs b/actix-http/src/response.rs index 288928c88..a96a13bd3 100644 --- a/actix-http/src/response.rs +++ b/actix-http/src/response.rs @@ -34,6 +34,18 @@ 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 { @@ -444,6 +456,32 @@ impl ResponseBuilder { self } + /// This method calls provided closure with builder reference if value is `true`. + #[doc(hidden)] + #[deprecated = "Use an if statement."] + pub fn if_true(&mut self, value: bool, f: F) -> &mut Self + where + F: FnOnce(&mut ResponseBuilder), + { + if value { + f(self); + } + self + } + + /// This method calls provided closure with builder reference if value is `Some`. + #[doc(hidden)] + #[deprecated = "Use an if-let construction."] + pub fn if_some(&mut self, value: Option, f: F) -> &mut Self + where + F: FnOnce(T, &mut ResponseBuilder), + { + if let Some(val) = value { + f(val, self); + } + self + } + /// Responses extensions #[inline] pub fn extensions(&self) -> Ref<'_, Extensions> { @@ -692,7 +730,7 @@ mod tests { #[test] fn test_upgrade() { - let resp = ResponseBuilder::new(StatusCode::OK) + let resp = Response::build(StatusCode::OK) .upgrade("websocket") .finish(); assert!(resp.upgrade()); @@ -704,13 +742,13 @@ mod tests { #[test] fn test_force_close() { - let resp = ResponseBuilder::new(StatusCode::OK).force_close().finish(); + let resp = Response::build(StatusCode::OK).force_close().finish(); assert!(!resp.keep_alive()) } #[test] fn test_content_type() { - let resp = ResponseBuilder::new(StatusCode::OK) + let resp = Response::build(StatusCode::OK) .content_type("text/plain") .body(Body::Empty); assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "text/plain") @@ -731,7 +769,7 @@ mod tests { #[test] fn test_json_ct() { - let resp = ResponseBuilder::new(StatusCode::OK) + let resp = Response::build(StatusCode::OK) .insert_header((CONTENT_TYPE, "text/json")) .json(&vec!["v1", "v2", "v3"]); let ct = resp.headers().get(CONTENT_TYPE).unwrap(); @@ -744,7 +782,7 @@ mod tests { use serde_json::json; let resp = - ResponseBuilder::new(StatusCode::OK).body(json!({"test-key":"test-value"})); + Response::build(StatusCode::OK).body(json!({"test-key":"test-value"})); assert_eq!(resp.body().get_ref(), br#"{"test-key":"test-value"}"#); } @@ -818,6 +856,24 @@ mod tests { 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::Ok(); diff --git a/actix-http/src/ws/mod.rs b/actix-http/src/ws/mod.rs index 01d8655b6..1a82ad839 100644 --- a/actix-http/src/ws/mod.rs +++ b/actix-http/src/ws/mod.rs @@ -9,10 +9,8 @@ use derive_more::{Display, Error, From}; use http::{header, Method, StatusCode}; use crate::{ - error::ResponseError, - header::HeaderValue, - message::{ConnectionType, RequestHead}, - response::Response, + error::ResponseError, header::HeaderValue, message::RequestHead, response::Response, + ResponseBuilder, }; mod codec; @@ -131,7 +129,7 @@ impl ResponseError for HandshakeError { } /// Verify WebSocket handshake request and create handshake response. -pub fn handshake(req: &RequestHead) -> Result { +pub fn handshake(req: &RequestHead) -> Result { verify_handshake(req)?; Ok(handshake_response(req)) } @@ -187,39 +185,21 @@ pub fn verify_handshake(req: &RequestHead) -> Result<(), HandshakeError> { /// Create WebSocket handshake response. /// /// This function returns handshake `Response`, ready to send to peer. -pub fn handshake_response(req: &RequestHead) -> Response { +pub fn handshake_response(req: &RequestHead) -> ResponseBuilder { let key = { let key = req.headers().get(header::SEC_WEBSOCKET_KEY).unwrap(); proto::hash_key(key.as_ref()) }; - let mut res = Response::new(StatusCode::SWITCHING_PROTOCOLS); - - res.head_mut().set_connection_type(ConnectionType::Upgrade); - let headers = res.headers_mut(); - - headers.insert(header::UPGRADE, HeaderValue::from_static("websocket")); - - headers.insert( - header::TRANSFER_ENCODING, - HeaderValue::from_static("chunked"), - ); - headers.insert( - header::SEC_WEBSOCKET_ACCEPT, - // key is known to be header value safe ascii - HeaderValue::from_bytes(&key).unwrap(), - ); - - res - - // Response::build(StatusCode::SWITCHING_PROTOCOLS) - // .upgrade("websocket") - // .insert_header((header::TRANSFER_ENCODING, "chunked")) - // .insert_header(( - // header::SEC_WEBSOCKET_ACCEPT, - // // key is known to be header value safe ascii - // HeaderValue::from_bytes(&key).unwrap(), - // )) + Response::build(StatusCode::SWITCHING_PROTOCOLS) + .upgrade("websocket") + .insert_header((header::TRANSFER_ENCODING, "chunked")) + .insert_header(( + header::SEC_WEBSOCKET_ACCEPT, + // key is known to be header value safe ascii + HeaderValue::from_bytes(&key).unwrap(), + )) + .take() } #[cfg(test)] @@ -334,7 +314,7 @@ mod tests { .finish(); assert_eq!( StatusCode::SWITCHING_PROTOCOLS, - handshake_response(req.head()).status() + handshake_response(req.head()).finish().status() ); } diff --git a/actix-http/tests/test_ws.rs b/actix-http/tests/test_ws.rs index 041c7b467..9a2e57711 100644 --- a/actix-http/tests/test_ws.rs +++ b/actix-http/tests/test_ws.rs @@ -52,7 +52,7 @@ where fn call(&self, (req, mut framed): (Request, Framed)) -> Self::Future { let fut = async move { - let res = ws::handshake(req.head()).unwrap().set_body(()); + let res = ws::handshake(req.head()).unwrap().message_body(()); framed .send((res, body::BodySize::None).into()) diff --git a/actix-web-actors/src/ws.rs b/actix-web-actors/src/ws.rs index f729871c1..8c575206d 100644 --- a/actix-web-actors/src/ws.rs +++ b/actix-web-actors/src/ws.rs @@ -163,7 +163,7 @@ pub fn handshake_with_protocols( .find(|req_p| protocols.iter().any(|p| p == req_p)) }); - let mut response = HttpResponseBuilder::new(StatusCode::SWITCHING_PROTOCOLS) + let mut response = HttpResponse::build(StatusCode::SWITCHING_PROTOCOLS) .upgrade("websocket") .insert_header(( header::SEC_WEBSOCKET_ACCEPT, diff --git a/awc/src/request.rs b/awc/src/request.rs index 3f783445b..1d7d3a145 100644 --- a/awc/src/request.rs +++ b/awc/src/request.rs @@ -8,13 +8,13 @@ use futures_core::Stream; use serde::Serialize; use actix_http::body::Body; -#[cfg(feature = "cookies")] use actix_http::http::header::{self, IntoHeaderPair}; use actix_http::http::{ uri, ConnectionType, Error as HttpError, HeaderMap, HeaderValue, Method, Uri, Version, }; use actix_http::{Error, RequestHead}; +#[cfg(feature = "cookies")] use crate::cookie::{Cookie, CookieJar}; use crate::error::{FreezeRequestError, InvalidUrl}; use crate::frozen::FrozenClientRequest; diff --git a/awc/src/test.rs b/awc/src/test.rs index 4bdefda97..1abe78811 100644 --- a/awc/src/test.rs +++ b/awc/src/test.rs @@ -1,6 +1,5 @@ //! Test helpers for actix http client to use during testing. use actix_http::http::header::IntoHeaderPair; -use actix_http::http::header::{self, HeaderValue}; use actix_http::http::{StatusCode, Version}; use actix_http::{h1, Payload, ResponseHead}; use bytes::Bytes; @@ -90,6 +89,8 @@ impl TestResponse { #[cfg(feature = "cookies")] for cookie in self.cookies.delta() { + use actix_http::http::header::{self, HeaderValue}; + head.headers.insert( header::SET_COOKIE, HeaderValue::from_str(&cookie.encoded().to_string()).unwrap(), diff --git a/awc/tests/test_ws.rs b/awc/tests/test_ws.rs index f07b04836..3f19ac4e8 100644 --- a/awc/tests/test_ws.rs +++ b/awc/tests/test_ws.rs @@ -25,7 +25,7 @@ async fn test_simple() { HttpService::build() .upgrade(|(req, mut framed): (Request, Framed<_, _>)| { async move { - let res = ws::handshake_response(req.head()); + let res = ws::handshake_response(req.head()).finish(); // send handshake response framed .send(h1::Message::Item((res.drop_body(), BodySize::None))) diff --git a/src/request.rs b/src/request.rs index 5c7b47bea..e5e4465eb 100644 --- a/src/request.rs +++ b/src/request.rs @@ -17,7 +17,7 @@ use smallvec::SmallVec; use crate::{ app_service::AppInitServiceState, config::AppConfig, error::UrlGenerationError, - extract::FromRequest, http::header, info::ConnectionInfo, rmap::ResourceMap, + extract::FromRequest, info::ConnectionInfo, rmap::ResourceMap, }; #[cfg(feature = "cookies")] @@ -272,9 +272,11 @@ impl HttpRequest { /// Load request cookies. #[cfg(feature = "cookies")] pub fn cookies(&self) -> Result>>, CookieParseError> { + use actix_http::http::header::COOKIE; + if self.extensions().get::().is_none() { let mut cookies = Vec::new(); - for hdr in self.headers().get_all(header::COOKIE) { + for hdr in self.headers().get_all(COOKIE) { let s = str::from_utf8(hdr.as_bytes()).map_err(CookieParseError::from)?; for cookie_str in s.split(';').map(|s| s.trim()) { if !cookie_str.is_empty() { diff --git a/src/responder.rs b/src/responder.rs index 7b5803f1c..66c93d257 100644 --- a/src/responder.rs +++ b/src/responder.rs @@ -3,7 +3,6 @@ use std::fmt; use actix_http::{ error::InternalError, http::{header::IntoHeaderPair, Error as HttpError, HeaderMap, StatusCode}, - ResponseBuilder, }; use bytes::{Bytes, BytesMut}; @@ -73,11 +72,25 @@ impl Responder for actix_http::Response { } } +impl Responder for HttpResponseBuilder { + #[inline] + fn respond_to(mut self, _: &HttpRequest) -> HttpResponse { + self.finish() + } +} + +impl Responder for actix_http::ResponseBuilder { + #[inline] + fn respond_to(mut self, _: &HttpRequest) -> HttpResponse { + HttpResponse::from(self.finish()) + } +} + impl Responder for Option { fn respond_to(self, req: &HttpRequest) -> HttpResponse { match self { - Some(t) => t.respond_to(req), - None => HttpResponseBuilder::new(StatusCode::NOT_FOUND).finish(), + Some(val) => val.respond_to(req), + None => HttpResponse::new(StatusCode::NOT_FOUND), } } } @@ -95,20 +108,6 @@ where } } -impl Responder for HttpResponseBuilder { - #[inline] - fn respond_to(mut self, _: &HttpRequest) -> HttpResponse { - self.finish() - } -} - -impl Responder for ResponseBuilder { - #[inline] - fn respond_to(mut self, _: &HttpRequest) -> HttpResponse { - HttpResponse::from(self.finish()) - } -} - impl Responder for (T, StatusCode) { fn respond_to(self, req: &HttpRequest) -> HttpResponse { let mut res = self.0.respond_to(req); diff --git a/src/test.rs b/src/test.rs index cf0ef8085..c2e456e58 100644 --- a/src/test.rs +++ b/src/test.rs @@ -4,21 +4,19 @@ use std::{net::SocketAddr, rc::Rc}; pub use actix_http::test::TestBuffer; use actix_http::{ - http::{ - header::{self, HeaderValue, IntoHeaderPair}, - Method, StatusCode, Uri, Version, - }, + http::{header::IntoHeaderPair, Method, StatusCode, Uri, Version}, test::TestRequest as HttpTestRequest, Extensions, Request, }; use actix_router::{Path, ResourceDef, Url}; use actix_service::{IntoService, IntoServiceFactory, Service, ServiceFactory}; use actix_utils::future::ok; -use cookie::{Cookie, CookieJar}; use futures_core::Stream; use futures_util::StreamExt as _; use serde::{de::DeserializeOwned, Serialize}; +#[cfg(feature = "cookies")] +use crate::cookie::{Cookie, CookieJar}; use crate::{ app_service::AppInitServiceState, config::AppConfig, @@ -361,6 +359,7 @@ pub struct TestRequest { path: Path, peer_addr: Option, app_data: Extensions, + #[cfg(feature = "cookies")] cookies: CookieJar, } @@ -373,6 +372,7 @@ impl Default for TestRequest { path: Path::new(Url::new(Uri::default())), peer_addr: None, app_data: Extensions::new(), + #[cfg(feature = "cookies")] cookies: CookieJar::new(), } } @@ -512,10 +512,14 @@ impl TestRequest { } fn finish(&mut self) -> Request { + // mut used when cookie feature is enabled + #[allow(unused_mut)] let mut req = self.req.finish(); #[cfg(feature = "cookies")] { + use actix_http::http::header::{HeaderValue, COOKIE}; + let cookie: String = self .cookies .delta() @@ -526,7 +530,7 @@ impl TestRequest { if !cookie.is_empty() { req.headers_mut() - .insert(header::COOKIE, HeaderValue::from_str(&cookie).unwrap()); + .insert(COOKIE, HeaderValue::from_str(&cookie).unwrap()); } }