mirror of https://github.com/fafhrd91/actix-web
add web http response builder
This commit is contained in:
parent
0c6ec535e0
commit
bc48190401
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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>>);
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
// }
|
||||
// }
|
||||
// }
|
42
src/test.rs
42
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<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);
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in New Issue