diff --git a/Cargo.toml b/Cargo.toml index 7ee100c1a..b47ba4627 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -94,6 +94,7 @@ either = "1.5.3" encoding_rs = "0.8" futures-core = { version = "0.3.7", default-features = false } futures-util = { version = "0.3.7", default-features = false } +itoa = "0.4" language-tags = "0.2" once_cell = "1.5" log = "0.4" diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 9765be3a6..81ba68424 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -1,7 +1,9 @@ # Changes ## Unreleased - 2021-xx-xx - +### Removed +* `HttpMessage::cookies` and `HttpMessage::cookie`. [#????] +* `impl ResponseError for CookieParseError`. [#????] ## 3.0.0-beta.5 - 2021-04-02 ### Added diff --git a/actix-http/src/error.rs b/actix-http/src/error.rs index 0178be80c..bd3c73a4c 100644 --- a/actix-http/src/error.rs +++ b/actix-http/src/error.rs @@ -18,9 +18,6 @@ use crate::body::Body; use crate::helpers::Writer; use crate::response::{Response, ResponseBuilder}; -#[cfg(feature = "cookies")] -pub use crate::cookie::ParseError as CookieParseError; - /// A specialized [`std::result::Result`] /// for actix web operations /// @@ -378,14 +375,6 @@ impl ResponseError for PayloadError { } } -/// Return `BadRequest` for `cookie::ParseError` -#[cfg(feature = "cookies")] -impl ResponseError for crate::cookie::ParseError { - fn status_code(&self) -> StatusCode { - StatusCode::BAD_REQUEST - } -} - #[derive(Debug, Display, From)] /// A set of errors that can occur during dispatching HTTP requests pub enum DispatchError { @@ -959,13 +948,6 @@ mod tests { assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR); } - #[cfg(feature = "cookies")] - #[test] - fn test_cookie_parse() { - let resp: Response = CookieParseError::EmptyName.error_response(); - assert_eq!(resp.status(), StatusCode::BAD_REQUEST); - } - #[test] fn test_as_response() { let orig = io::Error::new(io::ErrorKind::Other, "other"); diff --git a/actix-http/src/request.rs b/actix-http/src/request.rs index 890f55381..2632fc2ba 100644 --- a/actix-http/src/request.rs +++ b/actix-http/src/request.rs @@ -9,11 +9,14 @@ use std::{ use cookie::{Cookie, ParseError as CookieParseError}; use http::{header, Method, Uri, Version}; -use crate::extensions::Extensions; -use crate::header::HeaderMap; -use crate::message::{Message, RequestHead}; -use crate::payload::{Payload, PayloadStream}; -use crate::HttpMessage; + +use crate::{ + extensions::Extensions, + header::HeaderMap, + message::{Message, RequestHead}, + payload::{Payload, PayloadStream}, + HttpMessage, +}; #[cfg(feature = "cookies")] struct Cookies(Vec>); diff --git a/actix-http/src/response.rs b/actix-http/src/response.rs index a18090242..d13e52689 100644 --- a/actix-http/src/response.rs +++ b/actix-http/src/response.rs @@ -14,13 +14,17 @@ use bytes::{Bytes, BytesMut}; use futures_core::Stream; use serde::Serialize; -use crate::body::{Body, BodyStream, MessageBody, ResponseBody}; -use crate::error::Error; -use crate::extensions::Extensions; -use crate::header::{IntoHeaderPair, IntoHeaderValue}; -use crate::http::header::{self, HeaderName}; -use crate::http::{Error as HttpError, HeaderMap, StatusCode}; -use crate::message::{BoxedResponseHead, ConnectionType, ResponseHead}; +use crate::{ + body::{Body, BodyStream, MessageBody, ResponseBody}, + error::Error, + extensions::Extensions, + header::{IntoHeaderPair, IntoHeaderValue}, + http::{ + header::{self, HeaderName}, + Error as HttpError, HeaderMap, StatusCode, + }, + message::{BoxedResponseHead, ConnectionType, ResponseHead}, +}; #[cfg(feature = "cookies")] use crate::{ cookie::{Cookie, CookieJar}, @@ -724,29 +728,11 @@ fn parts<'a>( /// Convert `Response` to a `ResponseBuilder`. Body get dropped. impl From> for ResponseBuilder { fn from(res: Response) -> ResponseBuilder { - #[cfg(feature = "cookies")] - let jar = { - // If this response has cookies, load them into a jar - let mut jar: Option = None; - - for c in res.cookies() { - if let Some(ref mut j) = jar { - j.add_original(c.into_owned()); - } else { - let mut j = CookieJar::new(); - j.add_original(c.into_owned()); - jar = Some(j); - } - } - - jar - }; - ResponseBuilder { head: Some(res.head), err: None, #[cfg(feature = "cookies")] - cookies: jar, + cookies: None } } } @@ -764,33 +750,11 @@ impl<'a> From<&'a ResponseHead> for ResponseBuilder { msg.no_chunking(!head.chunked()); - #[cfg(feature = "cookies")] - let jar = { - // If this response has cookies, load them into a jar - let mut jar: Option = None; - - let cookies = CookieIter { - iter: head.headers.get_all(header::SET_COOKIE), - }; - - for c in cookies { - if let Some(ref mut j) = jar { - j.add_original(c.into_owned()); - } else { - let mut j = CookieJar::new(); - j.add_original(c.into_owned()); - jar = Some(j); - } - } - - jar - }; - ResponseBuilder { head: Some(msg), err: None, #[cfg(feature = "cookies")] - cookies: jar, + cookies: None, } } } @@ -892,8 +856,6 @@ mod tests { use super::*; use crate::body::Body; - #[cfg(feature = "cookies")] - use crate::http::header::SET_COOKIE; use crate::http::header::{HeaderValue, CONTENT_TYPE, COOKIE}; #[test] @@ -906,68 +868,6 @@ mod tests { assert!(dbg.contains("Response")); } - #[cfg(feature = "cookies")] - #[test] - fn test_response_cookies() { - let req = crate::test::TestRequest::default() - .append_header((COOKIE, "cookie1=value1")) - .append_header((COOKIE, "cookie2=value2")) - .finish(); - let cookies = req.cookies().unwrap(); - - let resp = Response::Ok() - .cookie( - crate::http::Cookie::build("name", "value") - .domain("www.rust-lang.org") - .path("/test") - .http_only(true) - .max_age(time::Duration::days(1)) - .finish(), - ) - .del_cookie(&cookies[0]) - .finish(); - - let mut val = resp - .headers() - .get_all(SET_COOKIE) - .map(|v| v.to_str().unwrap().to_owned()) - .collect::>(); - val.sort(); - - // the .del_cookie call - assert!(val[0].starts_with("cookie1=; Max-Age=0;")); - - // the .cookie call - assert_eq!( - val[1], - "name=value; HttpOnly; Path=/test; Domain=www.rust-lang.org; Max-Age=86400" - ); - } - - #[cfg(feature = "cookies")] - #[test] - fn test_update_response_cookies() { - let mut r = Response::Ok() - .cookie(crate::http::Cookie::new("original", "val100")) - .finish(); - - r.add_cookie(&crate::http::Cookie::new("cookie2", "val200")) - .unwrap(); - r.add_cookie(&crate::http::Cookie::new("cookie2", "val250")) - .unwrap(); - r.add_cookie(&crate::http::Cookie::new("cookie3", "val300")) - .unwrap(); - - assert_eq!(r.cookies().count(), 4); - r.del_cookie("cookie2"); - - let mut iter = r.cookies(); - let v = iter.next().unwrap(); - assert_eq!((v.name(), v.value()), ("original", "val100")); - let v = iter.next().unwrap(); - assert_eq!((v.name(), v.value()), ("cookie3", "val300")); - } - #[test] fn test_basic_builder() { let resp = Response::Ok().insert_header(("X-TEST", "value")).finish(); @@ -1101,23 +1001,6 @@ mod tests { assert_eq!(resp.body().get_ref(), b"test"); } - #[cfg(feature = "cookies")] - #[test] - fn test_into_builder() { - let mut resp: Response = "test".into(); - assert_eq!(resp.status(), StatusCode::OK); - - resp.add_cookie(&crate::http::Cookie::new("cookie1", "val100")) - .unwrap(); - - let mut builder: ResponseBuilder = resp.into(); - let resp = builder.status(StatusCode::BAD_REQUEST).finish(); - assert_eq!(resp.status(), StatusCode::BAD_REQUEST); - - let cookie = resp.cookies().next().unwrap(); - assert_eq!((cookie.name(), cookie.value()), ("cookie1", "val100")); - } - #[test] fn response_builder_header_insert_kv() { let mut res = Response::Ok(); diff --git a/actix-http/src/test.rs b/actix-http/src/test.rs index ad3dc74b2..ec781743d 100644 --- a/actix-http/src/test.rs +++ b/actix-http/src/test.rs @@ -13,11 +13,6 @@ use actix_codec::{AsyncRead, AsyncWrite, ReadBuf}; use bytes::{Bytes, BytesMut}; use http::{Method, Uri, Version}; -#[cfg(feature = "cookies")] -use crate::{ - cookie::{Cookie, CookieJar}, - header::{self, HeaderValue}, -}; use crate::{ header::{HeaderMap, IntoHeaderPair}, payload::Payload, @@ -54,8 +49,6 @@ struct Inner { method: Method, uri: Uri, headers: HeaderMap, - #[cfg(feature = "cookies")] - cookies: CookieJar, payload: Option, } @@ -66,8 +59,6 @@ impl Default for TestRequest { uri: Uri::from_str("/").unwrap(), version: Version::HTTP_11, headers: HeaderMap::new(), - #[cfg(feature = "cookies")] - cookies: CookieJar::new(), payload: None, })) } @@ -134,13 +125,6 @@ impl TestRequest { self } - /// Set cookie for this request. - #[cfg(feature = "cookies")] - pub fn cookie<'a>(&mut self, cookie: Cookie<'a>) -> &mut Self { - parts(&mut self.0).cookies.add(cookie.into_owned()); - self - } - /// Set request payload. pub fn set_payload>(&mut self, data: B) -> &mut Self { let mut payload = crate::h1::Payload::empty(); @@ -169,22 +153,6 @@ impl TestRequest { head.version = inner.version; head.headers = inner.headers; - #[cfg(feature = "cookies")] - { - let cookie: String = inner - .cookies - .delta() - // ensure only name=value is written to cookie header - .map(|c| Cookie::new(c.name(), c.value()).encoded().to_string()) - .collect::>() - .join("; "); - - if !cookie.is_empty() { - head.headers - .insert(header::COOKIE, HeaderValue::from_str(&cookie).unwrap()); - } - } - req } } diff --git a/actix-web-actors/src/ws.rs b/actix-web-actors/src/ws.rs index 77d7041f0..c849ebdf1 100644 --- a/actix-web-actors/src/ws.rs +++ b/actix-web-actors/src/ws.rs @@ -22,7 +22,7 @@ use actix_http::{ http::HeaderValue, ws::{hash_key, Codec}, }; -use actix_web::dev::HttpResponseBuilder; +use actix_web::HttpResponseBuilder; use actix_web::error::{Error, PayloadError}; use actix_web::http::{header, Method, StatusCode}; use actix_web::{HttpRequest, HttpResponse}; @@ -163,7 +163,7 @@ pub fn handshake_with_protocols( .find(|req_p| protocols.iter().any(|p| p == req_p)) }); - let mut response = HttpResponse::build(StatusCode::SWITCHING_PROTOCOLS) + let mut response = HttpResponseBuilder::new(StatusCode::SWITCHING_PROTOCOLS) .upgrade("websocket") .insert_header(( header::SEC_WEBSOCKET_ACCEPT, diff --git a/awc/tests/test_client.rs b/awc/tests/test_client.rs index e2a8fdbff..f1d29f0bc 100644 --- a/awc/tests/test_client.rs +++ b/awc/tests/test_client.rs @@ -8,6 +8,7 @@ use std::time::Duration; use actix_utils::future::ok; use brotli2::write::BrotliEncoder; use bytes::Bytes; +use cookie::Cookie; use flate2::read::GzDecoder; use flate2::write::GzEncoder; use flate2::Compression; @@ -22,7 +23,7 @@ use actix_http_test::test_server; use actix_service::{map_config, pipeline_factory}; use actix_web::{ dev::{AppConfig, BodyEncoding}, - http::{header, Cookie}, + http::header, middleware::Compress, web, App, Error, HttpRequest, HttpResponse, }; diff --git a/src/lib.rs b/src/lib.rs index 57b782ac8..04735e228 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -86,6 +86,7 @@ mod request; mod request_data; mod resource; mod responder; +mod response; mod rmap; mod route; mod scope; @@ -95,10 +96,10 @@ pub mod test; pub(crate) mod types; pub mod web; -pub use actix_http::Response as HttpResponse; pub use actix_http::{body, Error, HttpMessage, ResponseError, Result}; #[doc(inline)] pub use actix_rt as rt; +pub use actix_http::Response as HttpResponse; pub use actix_web_codegen::*; #[cfg(feature = "cookies")] pub use cookie; @@ -108,6 +109,7 @@ pub use crate::extract::FromRequest; pub use crate::request::HttpRequest; pub use crate::resource::Resource; pub use crate::responder::Responder; +pub use crate::response::HttpResponseBuilder; pub use crate::route::Route; pub use crate::scope::Scope; pub use crate::server::HttpServer; @@ -139,7 +141,7 @@ pub mod dev { pub use actix_http::body::{Body, BodySize, MessageBody, ResponseBody, SizedStream}; #[cfg(feature = "compress")] pub use actix_http::encoding::Decoder as Decompress; - pub use actix_http::ResponseBuilder as HttpResponseBuilder; + pub use actix_http::ResponseBuilder as BaseHttpResponseBuilder; pub use actix_http::{Extensions, Payload, PayloadStream, RequestHead, ResponseHead}; pub use actix_router::{Path, ResourceDef, ResourcePath, Url}; pub use actix_server::Server; diff --git a/src/response.rs b/src/response.rs new file mode 100644 index 000000000..1795c841d --- /dev/null +++ b/src/response.rs @@ -0,0 +1,456 @@ +use std::{ + cell::{Ref, RefMut}, + convert::TryInto, +}; + +use actix_http::{ + body::{Body, BodyStream}, + http::{ + header::{self, HeaderName, IntoHeaderPair, IntoHeaderValue}, + ConnectionType, Error as HttpError, StatusCode, + }, + Extensions, Response, ResponseHead, +}; +use bytes::Bytes; +use futures_core::Stream; +use serde::Serialize; + +use crate::error::Error; + +// pub struct HttpResponse(dev::BaseHttpResponse); + +// impl HttpResponse { +// /// Create HTTP response builder with specific status. +// #[inline] +// pub fn build(status: http::StatusCode) -> HttpResponseBuilder { +// HttpResponseBuilder(dev::BaseHttpResponse::build(status)) +// } + +// /// Constructs a response +// #[inline] +// pub fn new(status: http::StatusCode) -> HttpResponse { +// HttpResponse(dev::BaseHttpResponse::new(status)) +// } + +// /// Constructs an error response +// #[inline] +// pub fn from_error(error: Error) -> HttpResponse { +// HttpResponse(dev::BaseHttpResponse::from_error(error)) +// } +// } + +// impl ops::Deref for HttpResponse { +// type Target = dev::BaseHttpResponse; + +// fn deref(&self) -> &Self::Target { +// &self.0 +// } +// } + +// impl ops::DerefMut for HttpResponse { +// fn deref_mut(&mut self) -> &mut Self::Target { +// &mut self.0 +// } +// } + +// impl From> for dev::BaseHttpResponse { +// fn from(res: HttpResponse) -> Self { +// res.0 +// } +// } + +/// An HTTP response builder. +/// +/// This type can be used to construct an instance of `Response` through a builder-like pattern. +pub struct HttpResponseBuilder { + head: Option, + err: Option, +} + +impl HttpResponseBuilder { + #[inline] + /// Create response builder + pub fn new(status: StatusCode) -> Self { + Self { + head: Some(ResponseHead::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) = self.parts() { + 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; + /// + /// Response::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) = self.parts() { + 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; + /// + /// Response::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) = self.parts() { + match header.try_into_header_pair() { + Ok((key, value)) => parts.headers.append(key, value), + Err(e) => self.err = Some(e.into()), + }; + } + + self + } + + /// Replaced with [`Self::insert_header()`]. + #[deprecated = "Replaced with `insert_header((key, value))`."] + pub fn set_header(&mut self, key: K, value: V) -> &mut Self + where + K: TryInto, + K::Error: Into, + V: IntoHeaderValue, + { + if self.err.is_some() { + return self; + } + + match (key.try_into(), value.try_into_value()) { + (Ok(name), Ok(value)) => return self.insert_header((name, value)), + (Err(err), _) => self.err = Some(err.into()), + (_, Err(err)) => self.err = Some(err.into()), + } + + self + } + + /// Replaced with [`Self::append_header()`]. + #[deprecated = "Replaced with `append_header((key, value))`."] + pub fn header(&mut self, key: K, value: V) -> &mut Self + where + K: TryInto, + K::Error: Into, + V: IntoHeaderValue, + { + if self.err.is_some() { + return self; + } + + match (key.try_into(), value.try_into_value()) { + (Ok(name), Ok(value)) => return self.append_header((name, value)), + (Err(err), _) => self.err = Some(err.into()), + (_, Err(err)) => self.err = Some(err.into()), + } + + self + } + + /// Set the custom reason for the response. + #[inline] + pub fn reason(&mut self, reason: &'static str) -> &mut Self { + if let Some(parts) = self.parts() { + parts.reason = Some(reason); + } + self + } + + /// Set connection type to KeepAlive + #[inline] + pub fn keep_alive(&mut self) -> &mut Self { + if let Some(parts) = self.parts() { + parts.set_connection_type(ConnectionType::KeepAlive); + } + self + } + + /// Set connection type to Upgrade + #[inline] + pub fn upgrade(&mut self, value: V) -> &mut Self + where + V: IntoHeaderValue, + { + if let Some(parts) = self.parts() { + parts.set_connection_type(ConnectionType::Upgrade); + } + + if let Ok(value) = value.try_into_value() { + self.insert_header((header::UPGRADE, value)); + } + + self + } + + /// Force close connection, even if it is marked as keep-alive + #[inline] + pub fn force_close(&mut self) -> &mut Self { + if let Some(parts) = self.parts() { + parts.set_connection_type(ConnectionType::Close); + } + self + } + + /// Disable chunked transfer encoding for HTTP/1.1 streaming responses. + #[inline] + pub fn no_chunking(&mut self, len: u64) -> &mut Self { + let mut buf = itoa::Buffer::new(); + self.insert_header((header::CONTENT_LENGTH, buf.format(len))); + + if let Some(parts) = self.parts() { + parts.no_chunking(true); + } + self + } + + /// Set response content type. + #[inline] + pub fn content_type(&mut self, value: V) -> &mut Self + where + V: IntoHeaderValue, + { + if let Some(parts) = self.parts() { + 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() + } + + /// Mutable reference to a the response's extensions + #[inline] + pub fn extensions_mut(&mut self) -> RefMut<'_, Extensions> { + let head = self.head.as_ref().expect("cannot reuse response builder"); + head.extensions_mut() + } + + /// Set a body and generate `Response`. + /// + /// `ResponseBuilder` can not be used after this call. + #[inline] + pub fn body>(&mut self, body: B) -> 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(); + } + + // allow unused mut when cookies feature is disabled + #[allow(unused_mut)] + let mut head = self.head.take().expect("cannot reuse response builder"); + + let mut res = Response::with_body(StatusCode::OK, body); + *res.head_mut() = head; + res + } + + /// 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 a json body and generate `Response` + /// + /// `ResponseBuilder` can not be used after this call. + pub fn json(&mut self, value: impl Serialize) -> Response { + match serde_json::to_string(&value) { + Ok(body) => { + let contains = if let Some(parts) = self.parts() { + parts.headers.contains_key(header::CONTENT_TYPE) + } else { + true + }; + + if !contains { + self.insert_header((header::CONTENT_TYPE, mime::APPLICATION_JSON)); + } + + self.body(Body::from(body)) + } + Err(e) => Error::from(e).into(), + } + } + + /// 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) -> Self { + Self { + head: self.head.take(), + err: self.err.take(), + } + } + + #[inline] + fn parts(&mut self) -> Option<&mut ResponseHead> { + if self.err.is_some() { + return None; + } + + self.head.as_mut() + } +} + +// 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::HttpRespone; + +// #[test] +// fn test_build() { +// let resp = HttpResponse::Ok().body(Body::Empty); +// assert_eq!(resp.status(), StatusCode::OK); +// } +// } +// } diff --git a/src/test.rs b/src/test.rs index 73d7ad56f..f5e19258f 100644 --- a/src/test.rs +++ b/src/test.rs @@ -4,13 +4,17 @@ use std::{net::SocketAddr, rc::Rc}; pub use actix_http::test::TestBuffer; use actix_http::{ - http::{header::IntoHeaderPair, Method, StatusCode, Uri, Version}, + http::{ + header::{self, HeaderValue, 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}; @@ -357,6 +361,7 @@ pub struct TestRequest { path: Path, peer_addr: Option, app_data: Extensions, + cookies: CookieJar, } impl Default for TestRequest { @@ -368,6 +373,7 @@ impl Default for TestRequest { path: Path::new(Url::new(Uri::default())), peer_addr: None, app_data: Extensions::new(), + cookies: CookieJar::new(), } } } @@ -442,8 +448,8 @@ impl TestRequest { /// Set cookie for this request. #[cfg(feature = "cookies")] - pub fn cookie(mut self, cookie: crate::cookie::Cookie<'_>) -> Self { - self.req.cookie(cookie); + pub fn cookie(mut self, cookie: Cookie<'_>) -> Self { + self.cookies.add(cookie.into_owned()); self } @@ -505,16 +511,38 @@ impl TestRequest { self } + fn finish(&mut self) -> Request { + let mut req = self.req.finish(); + + #[cfg(feature = "cookies")] + { + let cookie: String = self + .cookies + .delta() + // ensure only name=value is written to cookie header + .map(|c| Cookie::new(c.name(), c.value()).encoded().to_string()) + .collect::>() + .join("; "); + + if !cookie.is_empty() { + req.headers_mut() + .insert(header::COOKIE, HeaderValue::from_str(&cookie).unwrap()); + } + } + + req + } + /// Complete request creation and generate `Request` instance pub fn to_request(mut self) -> Request { - let mut req = self.req.finish(); + let mut req = self.finish(); req.head_mut().peer_addr = self.peer_addr; req } /// Complete request creation and generate `ServiceRequest` instance pub fn to_srv_request(mut self) -> ServiceRequest { - let (mut head, payload) = self.req.finish().into_parts(); + let (mut head, payload) = self.finish().into_parts(); head.peer_addr = self.peer_addr; self.path.get_mut().update(&head.uri); @@ -533,7 +561,7 @@ impl TestRequest { /// Complete request creation and generate `HttpRequest` instance pub fn to_http_request(mut self) -> HttpRequest { - let (mut head, _) = self.req.finish().into_parts(); + let (mut head, _) = self.finish().into_parts(); head.peer_addr = self.peer_addr; self.path.get_mut().update(&head.uri); @@ -544,7 +572,7 @@ impl TestRequest { /// Complete request creation and generate `HttpRequest` and `Payload` instances pub fn to_http_parts(mut self) -> (HttpRequest, Payload) { - let (mut head, payload) = self.req.finish().into_parts(); + let (mut head, payload) = self.finish().into_parts(); head.peer_addr = self.peer_addr; self.path.get_mut().update(&head.uri); diff --git a/tests/test_server.rs b/tests/test_server.rs index 072560748..2760cc7fb 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -15,6 +15,7 @@ use actix_http::http::header::{ }; use brotli2::write::{BrotliDecoder, BrotliEncoder}; use bytes::Bytes; +use cookie::{Cookie, CookieBuilder}; use flate2::{ read::GzDecoder, write::{GzEncoder, ZlibDecoder, ZlibEncoder}, @@ -832,12 +833,12 @@ async fn test_server_cookies() { App::new().default_service(web::to(|| { HttpResponse::Ok() .cookie( - http::CookieBuilder::new("first", "first_value") + CookieBuilder::new("first", "first_value") .http_only(true) .finish(), ) - .cookie(http::Cookie::new("second", "first_value")) - .cookie(http::Cookie::new("second", "second_value")) + .cookie(Cookie::new("second", "first_value")) + .cookie(Cookie::new("second", "second_value")) .finish() })) }); @@ -846,10 +847,10 @@ async fn test_server_cookies() { let res = req.send().await.unwrap(); assert!(res.status().is_success()); - let first_cookie = http::CookieBuilder::new("first", "first_value") + let first_cookie = CookieBuilder::new("first", "first_value") .http_only(true) .finish(); - let second_cookie = http::Cookie::new("second", "second_value"); + let second_cookie = Cookie::new("second", "second_value"); let cookies = res.cookies().expect("To have cookies"); assert_eq!(cookies.len(), 2);