add web http response builder

This commit is contained in:
Rob Ede 2021-04-06 22:58:12 +01:00
parent 0c6ec535e0
commit bc48190401
No known key found for this signature in database
GPG Key ID: 97C636207D3EF933
12 changed files with 530 additions and 203 deletions

View File

@ -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"

View File

@ -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

View File

@ -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");

View File

@ -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<Cookie<'static>>);

View File

@ -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<B> From<Response<B>> for ResponseBuilder {
fn from(res: Response<B>) -> ResponseBuilder {
#[cfg(feature = "cookies")]
let jar = {
// If this response has cookies, load them into a jar
let mut jar: Option<CookieJar> = 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<CookieJar> = 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::<Vec<_>>();
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();

View File

@ -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<Payload>,
}
@ -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<B: Into<Bytes>>(&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::<Vec<_>>()
.join("; ");
if !cookie.is_empty() {
head.headers
.insert(header::COOKIE, HeaderValue::from_str(&cookie).unwrap());
}
}
req
}
}

View File

@ -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,

View File

@ -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,
};

View File

@ -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;

456
src/response.rs Normal file
View File

@ -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<B = dev::Body>(dev::BaseHttpResponse<B>);
// 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<B> From<HttpResponse<B>> for dev::BaseHttpResponse<B> {
// fn from(res: HttpResponse<B>) -> 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<ResponseHead>,
err: Option<HttpError>,
}
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<H>(&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<H>(&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<K, V>(&mut self, key: K, value: V) -> &mut Self
where
K: TryInto<HeaderName>,
K::Error: Into<HttpError>,
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<K, V>(&mut self, key: K, value: V) -> &mut Self
where
K: TryInto<HeaderName>,
K::Error: Into<HttpError>,
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<V>(&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<V>(&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<B: Into<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<B>(&mut self, body: B) -> Response<B> {
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<S, E>(&mut self, stream: S) -> Response
where
S: Stream<Item = Result<Bytes, E>> + Unpin + 'static,
E: Into<Error> + '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);
// }
// }
// }

View File

@ -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<Url>,
peer_addr: Option<SocketAddr>,
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::<Vec<_>>()
.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);

View File

@ -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);