mirror of
https://github.com/fafhrd91/actix-web
synced 2025-07-01 16:55:08 +02:00
split request and response modules (#2530)
This commit is contained in:
441
actix-http/src/responses/builder.rs
Normal file
441
actix-http/src/responses/builder.rs
Normal file
@ -0,0 +1,441 @@
|
||||
//! HTTP response builder.
|
||||
|
||||
use std::{
|
||||
cell::{Ref, RefMut},
|
||||
fmt, str,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
body::{EitherBody, MessageBody},
|
||||
error::{Error, HttpError},
|
||||
header::{self, TryIntoHeaderPair, TryIntoHeaderValue},
|
||||
responses::{BoxedResponseHead, ResponseHead},
|
||||
ConnectionType, Extensions, Response, StatusCode,
|
||||
};
|
||||
|
||||
/// An HTTP response builder.
|
||||
///
|
||||
/// Used to construct an instance of `Response` using a builder pattern. Response builders are often
|
||||
/// created using [`Response::build`].
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use actix_http::{Response, ResponseBuilder, StatusCode, body, header};
|
||||
///
|
||||
/// # actix_rt::System::new().block_on(async {
|
||||
/// let mut res: Response<_> = Response::build(StatusCode::OK)
|
||||
/// .content_type(mime::APPLICATION_JSON)
|
||||
/// .insert_header((header::SERVER, "my-app/1.0"))
|
||||
/// .append_header((header::SET_COOKIE, "a=1"))
|
||||
/// .append_header((header::SET_COOKIE, "b=2"))
|
||||
/// .body("1234");
|
||||
///
|
||||
/// assert_eq!(res.status(), StatusCode::OK);
|
||||
///
|
||||
/// assert!(res.headers().contains_key("server"));
|
||||
/// assert_eq!(res.headers().get_all("set-cookie").count(), 2);
|
||||
///
|
||||
/// assert_eq!(body::to_bytes(res.into_body()).await.unwrap(), &b"1234"[..]);
|
||||
/// # })
|
||||
/// ```
|
||||
pub struct ResponseBuilder {
|
||||
head: Option<BoxedResponseHead>,
|
||||
err: Option<HttpError>,
|
||||
}
|
||||
|
||||
impl ResponseBuilder {
|
||||
/// Create response builder
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use actix_http::{Response, ResponseBuilder, StatusCode};
|
||||
/// let res: Response<_> = ResponseBuilder::default().finish();
|
||||
/// assert_eq!(res.status(), StatusCode::OK);
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn new(status: StatusCode) -> Self {
|
||||
ResponseBuilder {
|
||||
head: Some(BoxedResponseHead::new(status)),
|
||||
err: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Set HTTP status code of this response.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use actix_http::{ResponseBuilder, StatusCode};
|
||||
/// let res = ResponseBuilder::default().status(StatusCode::NOT_FOUND).finish();
|
||||
/// assert_eq!(res.status(), StatusCode::NOT_FOUND);
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn status(&mut self, status: StatusCode) -> &mut Self {
|
||||
if let Some(parts) = self.inner() {
|
||||
parts.status = status;
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
/// Insert a header, replacing any that were set with an equivalent field name.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use actix_http::{ResponseBuilder, header};
|
||||
///
|
||||
/// let res = ResponseBuilder::default()
|
||||
/// .insert_header((header::CONTENT_TYPE, mime::APPLICATION_JSON))
|
||||
/// .insert_header(("X-TEST", "value"))
|
||||
/// .finish();
|
||||
///
|
||||
/// assert!(res.headers().contains_key("content-type"));
|
||||
/// assert!(res.headers().contains_key("x-test"));
|
||||
/// ```
|
||||
pub fn insert_header(&mut self, header: impl TryIntoHeaderPair) -> &mut Self {
|
||||
if let Some(parts) = self.inner() {
|
||||
match header.try_into_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.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use actix_http::{ResponseBuilder, header};
|
||||
///
|
||||
/// let res = ResponseBuilder::default()
|
||||
/// .append_header((header::CONTENT_TYPE, mime::APPLICATION_JSON))
|
||||
/// .append_header(("X-TEST", "value1"))
|
||||
/// .append_header(("X-TEST", "value2"))
|
||||
/// .finish();
|
||||
///
|
||||
/// assert_eq!(res.headers().get_all("content-type").count(), 1);
|
||||
/// assert_eq!(res.headers().get_all("x-test").count(), 2);
|
||||
/// ```
|
||||
pub fn append_header(&mut self, header: impl TryIntoHeaderPair) -> &mut Self {
|
||||
if let Some(parts) = self.inner() {
|
||||
match header.try_into_pair() {
|
||||
Ok((key, value)) => parts.headers.append(key, value),
|
||||
Err(e) => self.err = Some(e.into()),
|
||||
};
|
||||
}
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the custom reason for the response.
|
||||
#[inline]
|
||||
pub fn reason(&mut self, reason: &'static str) -> &mut Self {
|
||||
if let Some(parts) = self.inner() {
|
||||
parts.reason = Some(reason);
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
/// Set connection type to KeepAlive
|
||||
#[inline]
|
||||
pub fn keep_alive(&mut self) -> &mut Self {
|
||||
if let Some(parts) = self.inner() {
|
||||
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: TryIntoHeaderValue,
|
||||
{
|
||||
if let Some(parts) = self.inner() {
|
||||
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.inner() {
|
||||
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.inner() {
|
||||
parts.no_chunking(true);
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
/// Set response content type.
|
||||
#[inline]
|
||||
pub fn content_type<V>(&mut self, value: V) -> &mut Self
|
||||
where
|
||||
V: TryIntoHeaderValue,
|
||||
{
|
||||
if let Some(parts) = self.inner() {
|
||||
match value.try_into_value() {
|
||||
Ok(value) => {
|
||||
parts.headers.insert(header::CONTENT_TYPE, value);
|
||||
}
|
||||
Err(e) => self.err = Some(e.into()),
|
||||
};
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
/// Responses extensions
|
||||
#[inline]
|
||||
pub fn extensions(&self) -> Ref<'_, Extensions> {
|
||||
let head = self.head.as_ref().expect("cannot reuse response builder");
|
||||
head.extensions.borrow()
|
||||
}
|
||||
|
||||
/// Mutable reference to a the response's extensions
|
||||
#[inline]
|
||||
pub fn extensions_mut(&mut self) -> RefMut<'_, Extensions> {
|
||||
let head = self.head.as_ref().expect("cannot reuse response builder");
|
||||
head.extensions.borrow_mut()
|
||||
}
|
||||
|
||||
/// Generate response with a wrapped body.
|
||||
///
|
||||
/// This `ResponseBuilder` will be left in a useless state.
|
||||
pub fn body<B>(&mut self, body: B) -> Response<EitherBody<B>>
|
||||
where
|
||||
B: MessageBody + 'static,
|
||||
{
|
||||
match self.message_body(body) {
|
||||
Ok(res) => res.map_body(|_, body| EitherBody::left(body)),
|
||||
Err(err) => Response::from(err).map_body(|_, body| EitherBody::right(body)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate response with a body.
|
||||
///
|
||||
/// This `ResponseBuilder` will be left in a useless state.
|
||||
pub fn message_body<B>(&mut self, body: B) -> Result<Response<B>, Error> {
|
||||
if let Some(err) = self.err.take() {
|
||||
return Err(Error::new_http().with_cause(err));
|
||||
}
|
||||
|
||||
let head = self.head.take().expect("cannot reuse response builder");
|
||||
Ok(Response { head, body })
|
||||
}
|
||||
|
||||
/// Generate response with an empty body.
|
||||
///
|
||||
/// This `ResponseBuilder` will be left in a useless state.
|
||||
#[inline]
|
||||
pub fn finish(&mut self) -> Response<EitherBody<()>> {
|
||||
self.body(())
|
||||
}
|
||||
|
||||
/// Create an owned `ResponseBuilder`, leaving the original in a useless state.
|
||||
pub fn take(&mut self) -> ResponseBuilder {
|
||||
ResponseBuilder {
|
||||
head: self.head.take(),
|
||||
err: self.err.take(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get access to the inner response head if there has been no error.
|
||||
fn inner(&mut self) -> Option<&mut ResponseHead> {
|
||||
if self.err.is_some() {
|
||||
return None;
|
||||
}
|
||||
|
||||
self.head.as_deref_mut()
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for ResponseBuilder {
|
||||
fn default() -> Self {
|
||||
Self::new(StatusCode::OK)
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert `Response` to a `ResponseBuilder`. Body get dropped.
|
||||
impl<B> From<Response<B>> for ResponseBuilder {
|
||||
fn from(res: Response<B>) -> ResponseBuilder {
|
||||
ResponseBuilder {
|
||||
head: Some(res.head),
|
||||
err: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert `ResponseHead` to a `ResponseBuilder`
|
||||
impl<'a> From<&'a ResponseHead> for ResponseBuilder {
|
||||
fn from(head: &'a ResponseHead) -> ResponseBuilder {
|
||||
let mut msg = BoxedResponseHead::new(head.status);
|
||||
msg.version = head.version;
|
||||
msg.reason = head.reason;
|
||||
|
||||
for (k, v) in head.headers.iter() {
|
||||
msg.headers.append(k.clone(), v.clone());
|
||||
}
|
||||
|
||||
msg.no_chunking(!head.chunked());
|
||||
|
||||
ResponseBuilder {
|
||||
head: Some(msg),
|
||||
err: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for ResponseBuilder {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let head = self.head.as_ref().unwrap();
|
||||
|
||||
let res = writeln!(
|
||||
f,
|
||||
"\nResponseBuilder {:?} {}{}",
|
||||
head.version,
|
||||
head.status,
|
||||
head.reason.unwrap_or(""),
|
||||
);
|
||||
let _ = writeln!(f, " headers:");
|
||||
for (key, val) in head.headers.iter() {
|
||||
let _ = writeln!(f, " {:?}: {:?}", key, val);
|
||||
}
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use bytes::Bytes;
|
||||
|
||||
use super::*;
|
||||
use crate::header::{HeaderName, HeaderValue, CONTENT_TYPE};
|
||||
|
||||
#[test]
|
||||
fn test_basic_builder() {
|
||||
let resp = Response::build(StatusCode::OK)
|
||||
.insert_header(("X-TEST", "value"))
|
||||
.finish();
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_upgrade() {
|
||||
let resp = Response::build(StatusCode::OK)
|
||||
.upgrade("websocket")
|
||||
.finish();
|
||||
assert!(resp.upgrade());
|
||||
assert_eq!(
|
||||
resp.headers().get(header::UPGRADE).unwrap(),
|
||||
HeaderValue::from_static("websocket")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_force_close() {
|
||||
let resp = Response::build(StatusCode::OK).force_close().finish();
|
||||
assert!(!resp.keep_alive());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_content_type() {
|
||||
let resp = Response::build(StatusCode::OK)
|
||||
.content_type("text/plain")
|
||||
.body(Bytes::new());
|
||||
assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "text/plain");
|
||||
|
||||
let resp = Response::build(StatusCode::OK)
|
||||
.content_type(mime::APPLICATION_JAVASCRIPT_UTF_8)
|
||||
.body(Bytes::new());
|
||||
assert_eq!(
|
||||
resp.headers().get(CONTENT_TYPE).unwrap(),
|
||||
"application/javascript; charset=utf-8"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_into_builder() {
|
||||
let mut resp: Response<_> = "test".into();
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
|
||||
resp.headers_mut().insert(
|
||||
HeaderName::from_static("cookie"),
|
||||
HeaderValue::from_static("cookie1=val100"),
|
||||
);
|
||||
|
||||
let mut builder: ResponseBuilder = resp.into();
|
||||
let resp = builder.status(StatusCode::BAD_REQUEST).finish();
|
||||
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
|
||||
|
||||
let cookie = resp.headers().get_all("Cookie").next().unwrap();
|
||||
assert_eq!(cookie.to_str().unwrap(), "cookie1=val100");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn response_builder_header_insert_kv() {
|
||||
let mut res = Response::build(StatusCode::OK);
|
||||
res.insert_header(("Content-Type", "application/octet-stream"));
|
||||
let res = res.finish();
|
||||
|
||||
assert_eq!(
|
||||
res.headers().get("Content-Type"),
|
||||
Some(&HeaderValue::from_static("application/octet-stream"))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn response_builder_header_insert_typed() {
|
||||
let mut res = Response::build(StatusCode::OK);
|
||||
res.insert_header((header::CONTENT_TYPE, mime::APPLICATION_OCTET_STREAM));
|
||||
let res = res.finish();
|
||||
|
||||
assert_eq!(
|
||||
res.headers().get("Content-Type"),
|
||||
Some(&HeaderValue::from_static("application/octet-stream"))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn response_builder_header_append_kv() {
|
||||
let mut res = Response::build(StatusCode::OK);
|
||||
res.append_header(("Content-Type", "application/octet-stream"));
|
||||
res.append_header(("Content-Type", "application/json"));
|
||||
let res = res.finish();
|
||||
|
||||
let headers: Vec<_> = res.headers().get_all("Content-Type").cloned().collect();
|
||||
assert_eq!(headers.len(), 2);
|
||||
assert!(headers.contains(&HeaderValue::from_static("application/octet-stream")));
|
||||
assert!(headers.contains(&HeaderValue::from_static("application/json")));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn response_builder_header_append_typed() {
|
||||
let mut res = Response::build(StatusCode::OK);
|
||||
res.append_header((header::CONTENT_TYPE, mime::APPLICATION_OCTET_STREAM));
|
||||
res.append_header((header::CONTENT_TYPE, mime::APPLICATION_JSON));
|
||||
let res = res.finish();
|
||||
|
||||
let headers: Vec<_> = res.headers().get_all("Content-Type").cloned().collect();
|
||||
assert_eq!(headers.len(), 2);
|
||||
assert!(headers.contains(&HeaderValue::from_static("application/octet-stream")));
|
||||
assert!(headers.contains(&HeaderValue::from_static("application/json")));
|
||||
}
|
||||
}
|
208
actix-http/src/responses/head.rs
Normal file
208
actix-http/src/responses/head.rs
Normal file
@ -0,0 +1,208 @@
|
||||
//! Response head type and caching pool.
|
||||
|
||||
use std::{
|
||||
cell::{Ref, RefCell, RefMut},
|
||||
ops,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
header::HeaderMap, message::Flags, ConnectionType, Extensions, StatusCode, Version,
|
||||
};
|
||||
|
||||
thread_local! {
|
||||
static RESPONSE_POOL: BoxedResponsePool = BoxedResponsePool::create();
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ResponseHead {
|
||||
pub version: Version,
|
||||
pub status: StatusCode,
|
||||
pub headers: HeaderMap,
|
||||
pub reason: Option<&'static str>,
|
||||
pub(crate) extensions: RefCell<Extensions>,
|
||||
flags: Flags,
|
||||
}
|
||||
|
||||
impl ResponseHead {
|
||||
/// Create new instance of `ResponseHead` type
|
||||
#[inline]
|
||||
pub fn new(status: StatusCode) -> ResponseHead {
|
||||
ResponseHead {
|
||||
status,
|
||||
version: Version::HTTP_11,
|
||||
headers: HeaderMap::with_capacity(12),
|
||||
reason: None,
|
||||
flags: Flags::empty(),
|
||||
extensions: RefCell::new(Extensions::new()),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
/// Read the message headers.
|
||||
pub fn headers(&self) -> &HeaderMap {
|
||||
&self.headers
|
||||
}
|
||||
|
||||
#[inline]
|
||||
/// Mutable reference to the message headers.
|
||||
pub fn headers_mut(&mut self) -> &mut HeaderMap {
|
||||
&mut self.headers
|
||||
}
|
||||
|
||||
/// Message extensions
|
||||
#[inline]
|
||||
pub fn extensions(&self) -> Ref<'_, Extensions> {
|
||||
self.extensions.borrow()
|
||||
}
|
||||
|
||||
/// Mutable reference to a the message's extensions
|
||||
#[inline]
|
||||
pub fn extensions_mut(&self) -> RefMut<'_, Extensions> {
|
||||
self.extensions.borrow_mut()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
/// Set connection type of the message
|
||||
pub fn set_connection_type(&mut self, ctype: ConnectionType) {
|
||||
match ctype {
|
||||
ConnectionType::Close => self.flags.insert(Flags::CLOSE),
|
||||
ConnectionType::KeepAlive => self.flags.insert(Flags::KEEP_ALIVE),
|
||||
ConnectionType::Upgrade => self.flags.insert(Flags::UPGRADE),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn connection_type(&self) -> ConnectionType {
|
||||
if self.flags.contains(Flags::CLOSE) {
|
||||
ConnectionType::Close
|
||||
} else if self.flags.contains(Flags::KEEP_ALIVE) {
|
||||
ConnectionType::KeepAlive
|
||||
} else if self.flags.contains(Flags::UPGRADE) {
|
||||
ConnectionType::Upgrade
|
||||
} else if self.version < Version::HTTP_11 {
|
||||
ConnectionType::Close
|
||||
} else {
|
||||
ConnectionType::KeepAlive
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if keep-alive is enabled
|
||||
#[inline]
|
||||
pub fn keep_alive(&self) -> bool {
|
||||
self.connection_type() == ConnectionType::KeepAlive
|
||||
}
|
||||
|
||||
/// Check upgrade status of this message
|
||||
#[inline]
|
||||
pub fn upgrade(&self) -> bool {
|
||||
self.connection_type() == ConnectionType::Upgrade
|
||||
}
|
||||
|
||||
/// Get custom reason for the response
|
||||
#[inline]
|
||||
pub fn reason(&self) -> &str {
|
||||
self.reason.unwrap_or_else(|| {
|
||||
self.status
|
||||
.canonical_reason()
|
||||
.unwrap_or("<unknown status code>")
|
||||
})
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn conn_type(&self) -> Option<ConnectionType> {
|
||||
if self.flags.contains(Flags::CLOSE) {
|
||||
Some(ConnectionType::Close)
|
||||
} else if self.flags.contains(Flags::KEEP_ALIVE) {
|
||||
Some(ConnectionType::KeepAlive)
|
||||
} else if self.flags.contains(Flags::UPGRADE) {
|
||||
Some(ConnectionType::Upgrade)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
/// Get response body chunking state
|
||||
pub fn chunked(&self) -> bool {
|
||||
!self.flags.contains(Flags::NO_CHUNKING)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
/// Set no chunking for payload
|
||||
pub fn no_chunking(&mut self, val: bool) {
|
||||
if val {
|
||||
self.flags.insert(Flags::NO_CHUNKING);
|
||||
} else {
|
||||
self.flags.remove(Flags::NO_CHUNKING);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct BoxedResponseHead {
|
||||
head: Option<Box<ResponseHead>>,
|
||||
}
|
||||
|
||||
impl BoxedResponseHead {
|
||||
/// Get new message from the pool of objects
|
||||
pub fn new(status: StatusCode) -> Self {
|
||||
RESPONSE_POOL.with(|p| p.get_message(status))
|
||||
}
|
||||
}
|
||||
|
||||
impl ops::Deref for BoxedResponseHead {
|
||||
type Target = ResponseHead;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
self.head.as_ref().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl ops::DerefMut for BoxedResponseHead {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
self.head.as_mut().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for BoxedResponseHead {
|
||||
fn drop(&mut self) {
|
||||
if let Some(head) = self.head.take() {
|
||||
RESPONSE_POOL.with(move |p| p.release(head))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Request's objects pool
|
||||
#[doc(hidden)]
|
||||
pub struct BoxedResponsePool(#[allow(clippy::vec_box)] RefCell<Vec<Box<ResponseHead>>>);
|
||||
|
||||
impl BoxedResponsePool {
|
||||
fn create() -> BoxedResponsePool {
|
||||
BoxedResponsePool(RefCell::new(Vec::with_capacity(128)))
|
||||
}
|
||||
|
||||
/// Get message from the pool
|
||||
#[inline]
|
||||
fn get_message(&self, status: StatusCode) -> BoxedResponseHead {
|
||||
if let Some(mut head) = self.0.borrow_mut().pop() {
|
||||
head.reason = None;
|
||||
head.status = status;
|
||||
head.headers.clear();
|
||||
head.flags = Flags::empty();
|
||||
BoxedResponseHead { head: Some(head) }
|
||||
} else {
|
||||
BoxedResponseHead {
|
||||
head: Some(Box::new(ResponseHead::new(status))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Release request instance
|
||||
#[inline]
|
||||
fn release(&self, mut msg: Box<ResponseHead>) {
|
||||
let pool = &mut self.0.borrow_mut();
|
||||
if pool.len() < 128 {
|
||||
msg.extensions.get_mut().clear();
|
||||
pool.push(msg);
|
||||
}
|
||||
}
|
||||
}
|
11
actix-http/src/responses/mod.rs
Normal file
11
actix-http/src/responses/mod.rs
Normal file
@ -0,0 +1,11 @@
|
||||
//! HTTP response.
|
||||
|
||||
mod builder;
|
||||
mod head;
|
||||
#[allow(clippy::module_inception)]
|
||||
mod response;
|
||||
|
||||
pub use self::builder::ResponseBuilder;
|
||||
pub(crate) use self::head::BoxedResponseHead;
|
||||
pub use self::head::ResponseHead;
|
||||
pub use self::response::Response;
|
405
actix-http/src/responses/response.rs
Normal file
405
actix-http/src/responses/response.rs
Normal file
@ -0,0 +1,405 @@
|
||||
//! HTTP response.
|
||||
|
||||
use std::{
|
||||
cell::{Ref, RefMut},
|
||||
fmt, str,
|
||||
};
|
||||
|
||||
use bytes::{Bytes, BytesMut};
|
||||
use bytestring::ByteString;
|
||||
|
||||
use crate::{
|
||||
body::{BoxBody, MessageBody},
|
||||
extensions::Extensions,
|
||||
header::{self, HeaderMap, TryIntoHeaderValue},
|
||||
responses::{BoxedResponseHead, ResponseBuilder, ResponseHead},
|
||||
Error, StatusCode,
|
||||
};
|
||||
|
||||
/// An HTTP response.
|
||||
pub struct Response<B> {
|
||||
pub(crate) head: BoxedResponseHead,
|
||||
pub(crate) body: B,
|
||||
}
|
||||
|
||||
impl Response<BoxBody> {
|
||||
/// Constructs a new response with default body.
|
||||
#[inline]
|
||||
pub fn new(status: StatusCode) -> Self {
|
||||
Response {
|
||||
head: BoxedResponseHead::new(status),
|
||||
body: BoxBody::new(()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Constructs a new response builder.
|
||||
#[inline]
|
||||
pub fn build(status: StatusCode) -> ResponseBuilder {
|
||||
ResponseBuilder::new(status)
|
||||
}
|
||||
|
||||
// just a couple frequently used shortcuts
|
||||
// this list should not grow larger than a few
|
||||
|
||||
/// Constructs a new response with status 200 OK.
|
||||
#[inline]
|
||||
pub fn ok() -> Self {
|
||||
Response::new(StatusCode::OK)
|
||||
}
|
||||
|
||||
/// Constructs a new response with status 400 Bad Request.
|
||||
#[inline]
|
||||
pub fn bad_request() -> Self {
|
||||
Response::new(StatusCode::BAD_REQUEST)
|
||||
}
|
||||
|
||||
/// Constructs a new response with status 404 Not Found.
|
||||
#[inline]
|
||||
pub fn not_found() -> Self {
|
||||
Response::new(StatusCode::NOT_FOUND)
|
||||
}
|
||||
|
||||
/// Constructs a new response with status 500 Internal Server Error.
|
||||
#[inline]
|
||||
pub fn internal_server_error() -> Self {
|
||||
Response::new(StatusCode::INTERNAL_SERVER_ERROR)
|
||||
}
|
||||
|
||||
// end shortcuts
|
||||
}
|
||||
|
||||
impl<B> Response<B> {
|
||||
/// Constructs a new response with given body.
|
||||
#[inline]
|
||||
pub fn with_body(status: StatusCode, body: B) -> Response<B> {
|
||||
Response {
|
||||
head: BoxedResponseHead::new(status),
|
||||
body,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a reference to the head of this response.
|
||||
#[inline]
|
||||
pub fn head(&self) -> &ResponseHead {
|
||||
&*self.head
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to the head of this response.
|
||||
#[inline]
|
||||
pub fn head_mut(&mut self) -> &mut ResponseHead {
|
||||
&mut *self.head
|
||||
}
|
||||
|
||||
/// Returns the status code of this response.
|
||||
#[inline]
|
||||
pub fn status(&self) -> StatusCode {
|
||||
self.head.status
|
||||
}
|
||||
|
||||
/// Returns a mutable reference the status code of this response.
|
||||
#[inline]
|
||||
pub fn status_mut(&mut self) -> &mut StatusCode {
|
||||
&mut self.head.status
|
||||
}
|
||||
|
||||
/// Returns a reference to response headers.
|
||||
#[inline]
|
||||
pub fn headers(&self) -> &HeaderMap {
|
||||
&self.head.headers
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to response headers.
|
||||
#[inline]
|
||||
pub fn headers_mut(&mut self) -> &mut HeaderMap {
|
||||
&mut self.head.headers
|
||||
}
|
||||
|
||||
/// Returns true if connection upgrade is enabled.
|
||||
#[inline]
|
||||
pub fn upgrade(&self) -> bool {
|
||||
self.head.upgrade()
|
||||
}
|
||||
|
||||
/// Returns true if keep-alive is enabled.
|
||||
pub fn keep_alive(&self) -> bool {
|
||||
self.head.keep_alive()
|
||||
}
|
||||
|
||||
/// Returns a reference to the extensions of this response.
|
||||
#[inline]
|
||||
pub fn extensions(&self) -> Ref<'_, Extensions> {
|
||||
self.head.extensions.borrow()
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to the extensions of this response.
|
||||
#[inline]
|
||||
pub fn extensions_mut(&mut self) -> RefMut<'_, Extensions> {
|
||||
self.head.extensions.borrow_mut()
|
||||
}
|
||||
|
||||
/// Returns a reference to the body of this response.
|
||||
#[inline]
|
||||
pub fn body(&self) -> &B {
|
||||
&self.body
|
||||
}
|
||||
|
||||
/// Sets new body.
|
||||
pub fn set_body<B2>(self, body: B2) -> Response<B2> {
|
||||
Response {
|
||||
head: self.head,
|
||||
body,
|
||||
}
|
||||
}
|
||||
|
||||
/// Drops body and returns new response.
|
||||
pub fn drop_body(self) -> Response<()> {
|
||||
self.set_body(())
|
||||
}
|
||||
|
||||
/// Sets new body, returning new response and previous body value.
|
||||
pub(crate) fn replace_body<B2>(self, body: B2) -> (Response<B2>, B) {
|
||||
(
|
||||
Response {
|
||||
head: self.head,
|
||||
body,
|
||||
},
|
||||
self.body,
|
||||
)
|
||||
}
|
||||
|
||||
/// Returns split head and body.
|
||||
///
|
||||
/// # Implementation Notes
|
||||
/// Due to internal performance optimizations, the first element of the returned tuple is a
|
||||
/// `Response` as well but only contains the head of the response this was called on.
|
||||
pub fn into_parts(self) -> (Response<()>, B) {
|
||||
self.replace_body(())
|
||||
}
|
||||
|
||||
/// Returns new response with mapped body.
|
||||
pub fn map_body<F, B2>(mut self, f: F) -> Response<B2>
|
||||
where
|
||||
F: FnOnce(&mut ResponseHead, B) -> B2,
|
||||
{
|
||||
let body = f(&mut self.head, self.body);
|
||||
|
||||
Response {
|
||||
head: self.head,
|
||||
body,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn map_into_boxed_body(self) -> Response<BoxBody>
|
||||
where
|
||||
B: MessageBody + 'static,
|
||||
{
|
||||
self.map_body(|_, body| body.boxed())
|
||||
}
|
||||
|
||||
/// Returns body, consuming this response.
|
||||
pub fn into_body(self) -> B {
|
||||
self.body
|
||||
}
|
||||
}
|
||||
|
||||
impl<B> fmt::Debug for Response<B>
|
||||
where
|
||||
B: MessageBody,
|
||||
{
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let res = writeln!(
|
||||
f,
|
||||
"\nResponse {:?} {}{}",
|
||||
self.head.version,
|
||||
self.head.status,
|
||||
self.head.reason.unwrap_or(""),
|
||||
);
|
||||
let _ = writeln!(f, " headers:");
|
||||
for (key, val) in self.head.headers.iter() {
|
||||
let _ = writeln!(f, " {:?}: {:?}", key, val);
|
||||
}
|
||||
let _ = writeln!(f, " body: {:?}", self.body.size());
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
impl<B: Default> Default for Response<B> {
|
||||
#[inline]
|
||||
fn default() -> Response<B> {
|
||||
Response::with_body(StatusCode::default(), B::default())
|
||||
}
|
||||
}
|
||||
|
||||
impl<I: Into<Response<BoxBody>>, E: Into<Error>> From<Result<I, E>> for Response<BoxBody> {
|
||||
fn from(res: Result<I, E>) -> Self {
|
||||
match res {
|
||||
Ok(val) => val.into(),
|
||||
Err(err) => Response::from(err.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ResponseBuilder> for Response<BoxBody> {
|
||||
fn from(mut builder: ResponseBuilder) -> Self {
|
||||
builder.finish().map_into_boxed_body()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<std::convert::Infallible> for Response<BoxBody> {
|
||||
fn from(val: std::convert::Infallible) -> Self {
|
||||
match val {}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&'static str> for Response<&'static str> {
|
||||
fn from(val: &'static str) -> Self {
|
||||
let mut res = Response::with_body(StatusCode::OK, val);
|
||||
let mime = mime::TEXT_PLAIN_UTF_8.try_into_value().unwrap();
|
||||
res.headers_mut().insert(header::CONTENT_TYPE, mime);
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&'static [u8]> for Response<&'static [u8]> {
|
||||
fn from(val: &'static [u8]) -> Self {
|
||||
let mut res = Response::with_body(StatusCode::OK, val);
|
||||
let mime = mime::APPLICATION_OCTET_STREAM.try_into_value().unwrap();
|
||||
res.headers_mut().insert(header::CONTENT_TYPE, mime);
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for Response<String> {
|
||||
fn from(val: String) -> Self {
|
||||
let mut res = Response::with_body(StatusCode::OK, val);
|
||||
let mime = mime::TEXT_PLAIN_UTF_8.try_into_value().unwrap();
|
||||
res.headers_mut().insert(header::CONTENT_TYPE, mime);
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&String> for Response<String> {
|
||||
fn from(val: &String) -> Self {
|
||||
let mut res = Response::with_body(StatusCode::OK, val.clone());
|
||||
let mime = mime::TEXT_PLAIN_UTF_8.try_into_value().unwrap();
|
||||
res.headers_mut().insert(header::CONTENT_TYPE, mime);
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Bytes> for Response<Bytes> {
|
||||
fn from(val: Bytes) -> Self {
|
||||
let mut res = Response::with_body(StatusCode::OK, val);
|
||||
let mime = mime::APPLICATION_OCTET_STREAM.try_into_value().unwrap();
|
||||
res.headers_mut().insert(header::CONTENT_TYPE, mime);
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
impl From<BytesMut> for Response<BytesMut> {
|
||||
fn from(val: BytesMut) -> Self {
|
||||
let mut res = Response::with_body(StatusCode::OK, val);
|
||||
let mime = mime::APPLICATION_OCTET_STREAM.try_into_value().unwrap();
|
||||
res.headers_mut().insert(header::CONTENT_TYPE, mime);
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ByteString> for Response<ByteString> {
|
||||
fn from(val: ByteString) -> Self {
|
||||
let mut res = Response::with_body(StatusCode::OK, val);
|
||||
let mime = mime::TEXT_PLAIN_UTF_8.try_into_value().unwrap();
|
||||
res.headers_mut().insert(header::CONTENT_TYPE, mime);
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{
|
||||
body::to_bytes,
|
||||
header::{HeaderValue, CONTENT_TYPE, COOKIE},
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn test_debug() {
|
||||
let resp = Response::build(StatusCode::OK)
|
||||
.append_header((COOKIE, HeaderValue::from_static("cookie1=value1; ")))
|
||||
.append_header((COOKIE, HeaderValue::from_static("cookie2=value2; ")))
|
||||
.finish();
|
||||
let dbg = format!("{:?}", resp);
|
||||
assert!(dbg.contains("Response"));
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn test_into_response() {
|
||||
let res = Response::from("test");
|
||||
assert_eq!(res.status(), StatusCode::OK);
|
||||
assert_eq!(
|
||||
res.headers().get(CONTENT_TYPE).unwrap(),
|
||||
HeaderValue::from_static("text/plain; charset=utf-8")
|
||||
);
|
||||
assert_eq!(res.status(), StatusCode::OK);
|
||||
assert_eq!(to_bytes(res.into_body()).await.unwrap(), &b"test"[..]);
|
||||
|
||||
let res = Response::from(b"test".as_ref());
|
||||
assert_eq!(res.status(), StatusCode::OK);
|
||||
assert_eq!(
|
||||
res.headers().get(CONTENT_TYPE).unwrap(),
|
||||
HeaderValue::from_static("application/octet-stream")
|
||||
);
|
||||
assert_eq!(res.status(), StatusCode::OK);
|
||||
assert_eq!(to_bytes(res.into_body()).await.unwrap(), &b"test"[..]);
|
||||
|
||||
let res = Response::from("test".to_owned());
|
||||
assert_eq!(res.status(), StatusCode::OK);
|
||||
assert_eq!(
|
||||
res.headers().get(CONTENT_TYPE).unwrap(),
|
||||
HeaderValue::from_static("text/plain; charset=utf-8")
|
||||
);
|
||||
assert_eq!(res.status(), StatusCode::OK);
|
||||
assert_eq!(to_bytes(res.into_body()).await.unwrap(), &b"test"[..]);
|
||||
|
||||
let res = Response::from("test".to_owned());
|
||||
assert_eq!(res.status(), StatusCode::OK);
|
||||
assert_eq!(
|
||||
res.headers().get(CONTENT_TYPE).unwrap(),
|
||||
HeaderValue::from_static("text/plain; charset=utf-8")
|
||||
);
|
||||
assert_eq!(res.status(), StatusCode::OK);
|
||||
assert_eq!(to_bytes(res.into_body()).await.unwrap(), &b"test"[..]);
|
||||
|
||||
let b = Bytes::from_static(b"test");
|
||||
let res = Response::from(b);
|
||||
assert_eq!(res.status(), StatusCode::OK);
|
||||
assert_eq!(
|
||||
res.headers().get(CONTENT_TYPE).unwrap(),
|
||||
HeaderValue::from_static("application/octet-stream")
|
||||
);
|
||||
assert_eq!(res.status(), StatusCode::OK);
|
||||
assert_eq!(to_bytes(res.into_body()).await.unwrap(), &b"test"[..]);
|
||||
|
||||
let b = Bytes::from_static(b"test");
|
||||
let res = Response::from(b);
|
||||
assert_eq!(res.status(), StatusCode::OK);
|
||||
assert_eq!(
|
||||
res.headers().get(CONTENT_TYPE).unwrap(),
|
||||
HeaderValue::from_static("application/octet-stream")
|
||||
);
|
||||
assert_eq!(res.status(), StatusCode::OK);
|
||||
assert_eq!(to_bytes(res.into_body()).await.unwrap(), &b"test"[..]);
|
||||
|
||||
let b = BytesMut::from("test");
|
||||
let res = Response::from(b);
|
||||
assert_eq!(res.status(), StatusCode::OK);
|
||||
assert_eq!(
|
||||
res.headers().get(CONTENT_TYPE).unwrap(),
|
||||
HeaderValue::from_static("application/octet-stream")
|
||||
);
|
||||
|
||||
assert_eq!(res.status(), StatusCode::OK);
|
||||
assert_eq!(to_bytes(res.into_body()).await.unwrap(), &b"test"[..]);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user