1
0
mirror of https://github.com/fafhrd91/actix-web synced 2025-08-31 17:07:01 +02:00

align remaining header map terminology (#2510)

This commit is contained in:
Rob Ede
2021-12-13 16:08:08 +00:00
committed by GitHub
parent 551a0d973c
commit 11ee8ec3ab
47 changed files with 608 additions and 503 deletions

View File

@@ -602,7 +602,7 @@ mod tests {
App::new()
.wrap(
DefaultHeaders::new()
.header(header::CONTENT_TYPE, HeaderValue::from_static("0001")),
.add((header::CONTENT_TYPE, HeaderValue::from_static("0001"))),
)
.route("/test", web::get().to(HttpResponse::Ok)),
)
@@ -623,7 +623,7 @@ mod tests {
.route("/test", web::get().to(HttpResponse::Ok))
.wrap(
DefaultHeaders::new()
.header(header::CONTENT_TYPE, HeaderValue::from_static("0001")),
.add((header::CONTENT_TYPE, HeaderValue::from_static("0001"))),
),
)
.await;

View File

@@ -2,7 +2,7 @@ use std::{cell::RefCell, fmt, io::Write as _};
use actix_http::{
body::BoxBody,
header::{self, IntoHeaderValue as _},
header::{self, TryIntoHeaderValue as _},
StatusCode,
};
use bytes::{BufMut as _, BytesMut};

View File

@@ -8,7 +8,7 @@ use std::{
use actix_http::{
body::BoxBody,
header::{self, IntoHeaderValue},
header::{self, TryIntoHeaderValue},
Response, StatusCode,
};
use bytes::BytesMut;

View File

@@ -14,7 +14,7 @@ use once_cell::sync::Lazy;
use regex::Regex;
use std::fmt::{self, Write};
use super::{ExtendedValue, Header, IntoHeaderValue, Writer};
use super::{ExtendedValue, Header, TryIntoHeaderValue, Writer};
use crate::http::header;
/// Split at the index of the first `needle` if it exists or at the end.
@@ -454,7 +454,7 @@ impl ContentDisposition {
}
}
impl IntoHeaderValue for ContentDisposition {
impl TryIntoHeaderValue for ContentDisposition {
type Error = header::InvalidHeaderValue;
fn try_into_value(self) -> Result<header::HeaderValue, Self::Error> {

View File

@@ -3,7 +3,7 @@ use std::{
str::FromStr,
};
use super::{HeaderValue, IntoHeaderValue, InvalidHeaderValue, Writer, CONTENT_RANGE};
use super::{HeaderValue, InvalidHeaderValue, TryIntoHeaderValue, Writer, CONTENT_RANGE};
use crate::error::ParseError;
crate::http::header::common_header! {
@@ -196,7 +196,7 @@ impl Display for ContentRangeSpec {
}
}
impl IntoHeaderValue for ContentRangeSpec {
impl TryIntoHeaderValue for ContentRangeSpec {
type Error = InvalidHeaderValue;
fn try_into_value(self) -> Result<HeaderValue, Self::Error> {

View File

@@ -3,7 +3,7 @@ use std::{
str::FromStr,
};
use super::{HeaderValue, IntoHeaderValue, InvalidHeaderValue, Writer};
use super::{HeaderValue, InvalidHeaderValue, TryIntoHeaderValue, Writer};
/// check that each char in the slice is either:
/// 1. `%x21`, or
@@ -159,7 +159,7 @@ impl FromStr for EntityTag {
}
}
impl IntoHeaderValue for EntityTag {
impl TryIntoHeaderValue for EntityTag {
type Error = InvalidHeaderValue;
fn try_into_value(self) -> Result<HeaderValue, Self::Error> {

View File

@@ -1,8 +1,8 @@
use std::fmt::{self, Display, Write};
use super::{
from_one_raw_str, EntityTag, Header, HeaderName, HeaderValue, HttpDate, IntoHeaderValue,
InvalidHeaderValue, Writer,
from_one_raw_str, EntityTag, Header, HeaderName, HeaderValue, HttpDate, InvalidHeaderValue,
TryIntoHeaderValue, Writer,
};
use crate::error::ParseError;
use crate::http::header;
@@ -96,7 +96,7 @@ impl Display for IfRange {
}
}
impl IntoHeaderValue for IfRange {
impl TryIntoHeaderValue for IfRange {
type Error = InvalidHeaderValue;
fn try_into_value(self) -> Result<HeaderValue, Self::Error> {

View File

@@ -125,7 +125,7 @@ macro_rules! common_header {
}
}
impl $crate::http::header::IntoHeaderValue for $id {
impl $crate::http::header::TryIntoHeaderValue for $id {
type Error = $crate::http::header::InvalidHeaderValue;
#[inline]
@@ -172,7 +172,7 @@ macro_rules! common_header {
}
}
impl $crate::http::header::IntoHeaderValue for $id {
impl $crate::http::header::TryIntoHeaderValue for $id {
type Error = $crate::http::header::InvalidHeaderValue;
#[inline]
@@ -211,7 +211,7 @@ macro_rules! common_header {
}
}
impl $crate::http::header::IntoHeaderValue for $id {
impl $crate::http::header::TryIntoHeaderValue for $id {
type Error = $crate::http::header::InvalidHeaderValue;
#[inline]
@@ -266,7 +266,7 @@ macro_rules! common_header {
}
}
impl $crate::http::header::IntoHeaderValue for $id {
impl $crate::http::header::TryIntoHeaderValue for $id {
type Error = $crate::http::header::InvalidHeaderValue;
#[inline]

View File

@@ -6,7 +6,7 @@ use std::{
use actix_http::{error::ParseError, header, HttpMessage};
use super::{Header, HeaderName, HeaderValue, IntoHeaderValue, InvalidHeaderValue, Writer};
use super::{Header, HeaderName, HeaderValue, InvalidHeaderValue, TryIntoHeaderValue, Writer};
/// `Range` header, defined
/// in [RFC 7233 §3.1](https://datatracker.ietf.org/doc/html/rfc7233#section-3.1)
@@ -274,7 +274,7 @@ impl Header for Range {
}
}
impl IntoHeaderValue for Range {
impl TryIntoHeaderValue for Range {
type Error = InvalidHeaderValue;
fn try_into_value(self) -> Result<HeaderValue, Self::Error> {

View File

@@ -86,7 +86,6 @@ pub mod middleware;
mod request;
mod request_data;
mod resource;
mod responder;
mod response;
mod rmap;
mod route;
@@ -109,12 +108,10 @@ pub use crate::error::{Error, ResponseError, Result};
pub use crate::extract::FromRequest;
pub use crate::request::HttpRequest;
pub use crate::resource::Resource;
pub use crate::responder::Responder;
pub use crate::response::{HttpResponse, HttpResponseBuilder};
pub use crate::response::{CustomizeResponder, HttpResponse, HttpResponseBuilder, Responder};
pub use crate::route::Route;
pub use crate::scope::Scope;
pub use crate::server::HttpServer;
// TODO: is exposing the error directly really needed
pub use crate::types::{Either, EitherExtractError};
pub use crate::types::Either;
pub(crate) type BoxError = Box<dyn std::error::Error>;

View File

@@ -16,7 +16,7 @@ use pin_project_lite::pin_project;
use crate::{
dev::{Service, Transform},
http::header::{HeaderMap, HeaderName, HeaderValue, CONTENT_TYPE},
http::header::{HeaderMap, HeaderName, HeaderValue, TryIntoHeaderPair, CONTENT_TYPE},
service::{ServiceRequest, ServiceResponse},
Error,
};
@@ -29,79 +29,81 @@ use crate::{
/// ```
/// use actix_web::{web, http, middleware, App, HttpResponse};
///
/// fn main() {
/// let app = App::new()
/// .wrap(middleware::DefaultHeaders::new().header("X-Version", "0.2"))
/// .service(
/// web::resource("/test")
/// .route(web::get().to(|| HttpResponse::Ok()))
/// .route(web::method(http::Method::HEAD).to(|| HttpResponse::MethodNotAllowed()))
/// );
/// }
/// let app = App::new()
/// .wrap(middleware::DefaultHeaders::new().add(("X-Version", "0.2")))
/// .service(
/// web::resource("/test")
/// .route(web::get().to(|| HttpResponse::Ok()))
/// .route(web::method(http::Method::HEAD).to(|| HttpResponse::MethodNotAllowed()))
/// );
/// ```
#[derive(Clone)]
#[derive(Debug, Clone, Default)]
pub struct DefaultHeaders {
inner: Rc<Inner>,
}
#[derive(Debug, Default)]
struct Inner {
headers: HeaderMap,
}
impl Default for DefaultHeaders {
fn default() -> Self {
DefaultHeaders {
inner: Rc::new(Inner {
headers: HeaderMap::new(),
}),
}
}
}
impl DefaultHeaders {
/// Constructs an empty `DefaultHeaders` middleware.
#[inline]
pub fn new() -> DefaultHeaders {
DefaultHeaders::default()
}
/// Adds a header to the default set.
#[inline]
pub fn header<K, V>(mut self, key: K, value: V) -> Self
///
/// # Panics
/// Panics when resolved header name or value is invalid.
#[allow(clippy::should_implement_trait)]
pub fn add(mut self, header: impl TryIntoHeaderPair) -> Self {
// standard header terminology `insert` or `append` for this method would make the behavior
// of this middleware less obvious since it only adds the headers if they are not present
match header.try_into_pair() {
Ok((key, value)) => Rc::get_mut(&mut self.inner)
.expect("All default headers must be added before cloning.")
.headers
.append(key, value),
Err(err) => panic!("Invalid header: {}", err.into()),
}
self
}
#[doc(hidden)]
#[deprecated(
since = "4.0.0",
note = "Prefer `.add((key, value))`. Will be removed in v5."
)]
pub fn header<K, V>(self, key: K, value: V) -> Self
where
HeaderName: TryFrom<K>,
<HeaderName as TryFrom<K>>::Error: Into<HttpError>,
HeaderValue: TryFrom<V>,
<HeaderValue as TryFrom<V>>::Error: Into<HttpError>,
{
#[allow(clippy::match_wild_err_arm)]
match HeaderName::try_from(key) {
Ok(key) => match HeaderValue::try_from(value) {
Ok(value) => {
Rc::get_mut(&mut self.inner)
.expect("Multiple copies exist")
.headers
.append(key, value);
}
Err(_) => panic!("Can not create header value"),
},
Err(_) => panic!("Can not create header name"),
}
self
self.add((
HeaderName::try_from(key)
.map_err(Into::into)
.expect("Invalid header name"),
HeaderValue::try_from(value)
.map_err(Into::into)
.expect("Invalid header value"),
))
}
/// Adds a default *Content-Type* header if response does not contain one.
///
/// Default is `application/octet-stream`.
pub fn add_content_type(mut self) -> Self {
Rc::get_mut(&mut self.inner)
.expect("Multiple `Inner` copies exist.")
.headers
.insert(
CONTENT_TYPE,
HeaderValue::from_static("application/octet-stream"),
);
self
pub fn add_content_type(self) -> Self {
self.add((
CONTENT_TYPE,
HeaderValue::from_static("application/octet-stream"),
))
}
}
@@ -119,7 +121,7 @@ where
fn new_transform(&self, service: S) -> Self::Future {
ready(Ok(DefaultHeadersMiddleware {
service,
inner: self.inner.clone(),
inner: Rc::clone(&self.inner),
}))
}
}
@@ -197,17 +199,22 @@ mod tests {
};
#[actix_rt::test]
async fn test_default_headers() {
async fn adding_default_headers() {
let mw = DefaultHeaders::new()
.header(CONTENT_TYPE, "0001")
.add(("X-TEST", "0001"))
.add(("X-TEST-TWO", HeaderValue::from_static("123")))
.new_transform(ok_service())
.await
.unwrap();
let req = TestRequest::default().to_srv_request();
let resp = mw.call(req).await.unwrap();
assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0001");
let res = mw.call(req).await.unwrap();
assert_eq!(res.headers().get("x-test").unwrap(), "0001");
assert_eq!(res.headers().get("x-test-two").unwrap(), "123");
}
#[actix_rt::test]
async fn no_override_existing() {
let req = TestRequest::default().to_srv_request();
let srv = |req: ServiceRequest| {
ok(req.into_response(
@@ -217,7 +224,7 @@ mod tests {
))
};
let mw = DefaultHeaders::new()
.header(CONTENT_TYPE, "0001")
.add((CONTENT_TYPE, "0001"))
.new_transform(srv.into_service())
.await
.unwrap();
@@ -226,7 +233,7 @@ mod tests {
}
#[actix_rt::test]
async fn test_content_type() {
async fn adding_content_type() {
let srv = |req: ServiceRequest| ok(req.into_response(HttpResponse::Ok().finish()));
let mw = DefaultHeaders::new()
.add_content_type()
@@ -241,4 +248,16 @@ mod tests {
"application/octet-stream"
);
}
#[test]
#[should_panic]
fn invalid_header_name() {
DefaultHeaders::new().add((":", "hello"));
}
#[test]
#[should_panic]
fn invalid_header_value() {
DefaultHeaders::new().add(("x-test", "\n"));
}
}

View File

@@ -33,7 +33,7 @@ mod tests {
let _ = App::new()
.wrap(Compat::new(Logger::default()))
.wrap(Condition::new(true, DefaultHeaders::new()))
.wrap(DefaultHeaders::new().header("X-Test2", "X-Value2"))
.wrap(DefaultHeaders::new().add(("X-Test2", "X-Value2")))
.wrap(ErrorHandlers::new().handler(StatusCode::FORBIDDEN, |res| {
Ok(ErrorHandlerResponse::Response(res))
}))
@@ -46,7 +46,7 @@ mod tests {
.wrap(ErrorHandlers::new().handler(StatusCode::FORBIDDEN, |res| {
Ok(ErrorHandlerResponse::Response(res))
}))
.wrap(DefaultHeaders::new().header("X-Test2", "X-Value2"))
.wrap(DefaultHeaders::new().add(("X-Test2", "X-Value2")))
.wrap(Condition::new(true, DefaultHeaders::new()))
.wrap(Compat::new(Logger::default()));

View File

@@ -17,7 +17,7 @@ use crate::{dev::Payload, error::ErrorInternalServerError, Error, FromRequest, H
/// # Mutating Request Data
/// Note that since extractors must output owned data, only types that `impl Clone` can use this
/// extractor. A clone is taken of the required request data and can, therefore, not be directly
/// mutated in-place. To mutate request data, continue to use [`HttpRequest::extensions_mut`] or
/// mutated in-place. To mutate request data, continue to use [`HttpRequest::req_data_mut`] or
/// re-insert the cloned data back into the extensions map. A `DerefMut` impl is intentionally not
/// provided to make this potential foot-gun more obvious.
///

View File

@@ -15,13 +15,12 @@ use crate::{
dev::{ensure_leading_slash, AppService, ResourceDef},
guard::Guard,
handler::Handler,
responder::Responder,
route::{Route, RouteService},
service::{
BoxedHttpService, BoxedHttpServiceFactory, HttpServiceFactory, ServiceRequest,
ServiceResponse,
},
BoxError, Error, FromRequest, HttpResponse,
BoxError, Error, FromRequest, HttpResponse, Responder,
};
/// *Resource* is an entry in resources table which corresponds to requested URL.
@@ -526,7 +525,7 @@ mod tests {
.name("test")
.wrap(
DefaultHeaders::new()
.header(header::CONTENT_TYPE, HeaderValue::from_static("0001")),
.add((header::CONTENT_TYPE, HeaderValue::from_static("0001"))),
)
.route(web::get().to(HttpResponse::Ok)),
),

View File

@@ -9,7 +9,7 @@ use std::{
use actix_http::{
body::{BodyStream, BoxBody, MessageBody},
error::HttpError,
header::{self, HeaderName, IntoHeaderPair, IntoHeaderValue},
header::{self, HeaderName, TryIntoHeaderPair, TryIntoHeaderValue},
ConnectionType, Extensions, Response, ResponseHead, StatusCode,
};
use bytes::Bytes;
@@ -67,12 +67,9 @@ impl HttpResponseBuilder {
/// .insert_header(("X-TEST", "value"))
/// .finish();
/// ```
pub fn insert_header<H>(&mut self, header: H) -> &mut Self
where
H: IntoHeaderPair,
{
pub fn insert_header(&mut self, header: impl TryIntoHeaderPair) -> &mut Self {
if let Some(parts) = self.inner() {
match header.try_into_header_pair() {
match header.try_into_pair() {
Ok((key, value)) => {
parts.headers.insert(key, value);
}
@@ -94,12 +91,9 @@ impl HttpResponseBuilder {
/// .append_header(("X-TEST", "value2"))
/// .finish();
/// ```
pub fn append_header<H>(&mut self, header: H) -> &mut Self
where
H: IntoHeaderPair,
{
pub fn append_header(&mut self, header: impl TryIntoHeaderPair) -> &mut Self {
if let Some(parts) = self.inner() {
match header.try_into_header_pair() {
match header.try_into_pair() {
Ok((key, value)) => parts.headers.append(key, value),
Err(e) => self.err = Some(e.into()),
};
@@ -118,7 +112,7 @@ impl HttpResponseBuilder {
where
K: TryInto<HeaderName>,
K::Error: Into<HttpError>,
V: IntoHeaderValue,
V: TryIntoHeaderValue,
{
if self.err.is_some() {
return self;
@@ -143,7 +137,7 @@ impl HttpResponseBuilder {
where
K: TryInto<HeaderName>,
K::Error: Into<HttpError>,
V: IntoHeaderValue,
V: TryIntoHeaderValue,
{
if self.err.is_some() {
return self;
@@ -180,7 +174,7 @@ impl HttpResponseBuilder {
#[inline]
pub fn upgrade<V>(&mut self, value: V) -> &mut Self
where
V: IntoHeaderValue,
V: TryIntoHeaderValue,
{
if let Some(parts) = self.inner() {
parts.set_connection_type(ConnectionType::Upgrade);
@@ -218,7 +212,7 @@ impl HttpResponseBuilder {
#[inline]
pub fn content_type<V>(&mut self, value: V) -> &mut Self
where
V: IntoHeaderValue,
V: TryIntoHeaderValue,
{
if let Some(parts) = self.inner() {
match value.try_into_value() {

View File

@@ -0,0 +1,245 @@
use actix_http::{
body::{EitherBody, MessageBody},
error::HttpError,
header::HeaderMap,
header::TryIntoHeaderPair,
StatusCode,
};
use crate::{BoxError, HttpRequest, HttpResponse, Responder};
/// Allows overriding status code and headers for a [`Responder`].
///
/// Created by the [`Responder::customize`] method.
pub struct CustomizeResponder<R> {
inner: CustomizeResponderInner<R>,
error: Option<HttpError>,
}
struct CustomizeResponderInner<R> {
responder: R,
status: Option<StatusCode>,
override_headers: HeaderMap,
append_headers: HeaderMap,
}
impl<R: Responder> CustomizeResponder<R> {
pub(crate) fn new(responder: R) -> Self {
CustomizeResponder {
inner: CustomizeResponderInner {
responder,
status: None,
override_headers: HeaderMap::new(),
append_headers: HeaderMap::new(),
},
error: None,
}
}
/// Override a status code for the Responder's response.
///
/// # Examples
/// ```
/// use actix_web::{Responder, http::StatusCode, test::TestRequest};
///
/// let responder = "Welcome!".customize().with_status(StatusCode::ACCEPTED);
///
/// let request = TestRequest::default().to_http_request();
/// let response = responder.respond_to(&request);
/// assert_eq!(response.status(), StatusCode::ACCEPTED);
/// ```
pub fn with_status(mut self, status: StatusCode) -> Self {
if let Some(inner) = self.inner() {
inner.status = Some(status);
}
self
}
/// Insert (override) header in the final response.
///
/// Overrides other headers with the same name.
/// See [`HeaderMap::insert`](crate::http::header::HeaderMap::insert).
///
/// Headers added with this method will be inserted before those added
/// with [`append_header`](Self::append_header). As such, header(s) can be overridden with more
/// than one new header by first calling `insert_header` followed by `append_header`.
///
/// # Examples
/// ```
/// use actix_web::{Responder, test::TestRequest};
///
/// let responder = "Hello world!"
/// .customize()
/// .insert_header(("x-version", "1.2.3"));
///
/// let request = TestRequest::default().to_http_request();
/// let response = responder.respond_to(&request);
/// assert_eq!(response.headers().get("x-version").unwrap(), "1.2.3");
/// ```
pub fn insert_header(mut self, header: impl TryIntoHeaderPair) -> Self {
if let Some(inner) = self.inner() {
match header.try_into_pair() {
Ok((key, value)) => {
inner.override_headers.insert(key, value);
}
Err(err) => self.error = Some(err.into()),
};
}
self
}
/// Append header to the final response.
///
/// Unlike [`insert_header`](Self::insert_header), this will not override existing headers.
/// See [`HeaderMap::append`](crate::http::header::HeaderMap::append).
///
/// Headers added here are appended _after_ additions/overrides from `insert_header`.
///
/// # Examples
/// ```
/// use actix_web::{Responder, test::TestRequest};
///
/// let responder = "Hello world!"
/// .customize()
/// .append_header(("x-version", "1.2.3"));
///
/// let request = TestRequest::default().to_http_request();
/// let response = responder.respond_to(&request);
/// assert_eq!(response.headers().get("x-version").unwrap(), "1.2.3");
/// ```
pub fn append_header(mut self, header: impl TryIntoHeaderPair) -> Self {
if let Some(inner) = self.inner() {
match header.try_into_pair() {
Ok((key, value)) => {
inner.append_headers.append(key, value);
}
Err(err) => self.error = Some(err.into()),
};
}
self
}
#[doc(hidden)]
#[deprecated(since = "4.0.0", note = "Renamed to `insert_header`.")]
pub fn with_header(self, header: impl TryIntoHeaderPair) -> Self
where
Self: Sized,
{
self.insert_header(header)
}
fn inner(&mut self) -> Option<&mut CustomizeResponderInner<R>> {
if self.error.is_some() {
None
} else {
Some(&mut self.inner)
}
}
}
impl<T> Responder for CustomizeResponder<T>
where
T: Responder,
<T::Body as MessageBody>::Error: Into<BoxError>,
{
type Body = EitherBody<T::Body>;
fn respond_to(self, req: &HttpRequest) -> HttpResponse<Self::Body> {
if let Some(err) = self.error {
return HttpResponse::from_error(err).map_into_right_body();
}
let mut res = self.inner.responder.respond_to(req);
if let Some(status) = self.inner.status {
*res.status_mut() = status;
}
for (k, v) in self.inner.override_headers {
res.headers_mut().insert(k, v);
}
for (k, v) in self.inner.append_headers {
res.headers_mut().append(k, v);
}
res.map_into_left_body()
}
}
#[cfg(test)]
mod tests {
use bytes::Bytes;
use actix_http::body::to_bytes;
use super::*;
use crate::{
http::{
header::{HeaderValue, CONTENT_TYPE},
StatusCode,
},
test::TestRequest,
};
#[actix_rt::test]
async fn customize_responder() {
let req = TestRequest::default().to_http_request();
let res = "test"
.to_string()
.customize()
.with_status(StatusCode::BAD_REQUEST)
.respond_to(&req);
assert_eq!(res.status(), StatusCode::BAD_REQUEST);
assert_eq!(
to_bytes(res.into_body()).await.unwrap(),
Bytes::from_static(b"test"),
);
let res = "test"
.to_string()
.customize()
.insert_header(("content-type", "json"))
.respond_to(&req);
assert_eq!(res.status(), StatusCode::OK);
assert_eq!(
res.headers().get(CONTENT_TYPE).unwrap(),
HeaderValue::from_static("json")
);
assert_eq!(
to_bytes(res.into_body()).await.unwrap(),
Bytes::from_static(b"test"),
);
}
#[actix_rt::test]
async fn tuple_responder_with_status_code() {
let req = TestRequest::default().to_http_request();
let res = ("test".to_string(), StatusCode::BAD_REQUEST).respond_to(&req);
assert_eq!(res.status(), StatusCode::BAD_REQUEST);
assert_eq!(
to_bytes(res.into_body()).await.unwrap(),
Bytes::from_static(b"test"),
);
let req = TestRequest::default().to_http_request();
let res = ("test".to_string(), StatusCode::OK)
.customize()
.insert_header((CONTENT_TYPE, mime::APPLICATION_JSON))
.respond_to(&req);
assert_eq!(res.status(), StatusCode::OK);
assert_eq!(
res.headers().get(CONTENT_TYPE).unwrap(),
HeaderValue::from_static("application/json")
);
assert_eq!(
to_bytes(res.into_body()).await.unwrap(),
Bytes::from_static(b"test"),
);
}
}

View File

@@ -1,9 +1,13 @@
mod builder;
mod customize_responder;
mod http_codes;
mod responder;
#[allow(clippy::module_inception)]
mod response;
pub use self::builder::HttpResponseBuilder;
pub use self::customize_responder::CustomizeResponder;
pub use self::responder::Responder;
pub use self::response::HttpResponse;
#[cfg(feature = "cookies")]

View File

@@ -2,64 +2,58 @@ use std::borrow::Cow;
use actix_http::{
body::{BoxBody, EitherBody, MessageBody},
error::HttpError,
header::HeaderMap,
header::IntoHeaderPair,
header::TryIntoHeaderPair,
StatusCode,
};
use bytes::{Bytes, BytesMut};
use crate::{BoxError, Error, HttpRequest, HttpResponse, HttpResponseBuilder};
use super::CustomizeResponder;
/// Trait implemented by types that can be converted to an HTTP response.
///
/// Any types that implement this trait can be used in the return type of a handler.
// # TODO: more about implementation notes and foreign impls
pub trait Responder {
type Body: MessageBody + 'static;
/// Convert self to `HttpResponse`.
fn respond_to(self, req: &HttpRequest) -> HttpResponse<Self::Body>;
/// Override a status code for a Responder.
/// Wraps responder to allow alteration of its response.
///
/// ```
/// use actix_web::{http::StatusCode, HttpRequest, Responder};
/// See [`CustomizeResponder`] docs for its capabilities.
///
/// fn index(req: HttpRequest) -> impl Responder {
/// "Welcome!".with_status(StatusCode::OK)
/// }
/// # Examples
/// ```
fn with_status(self, status: StatusCode) -> CustomResponder<Self>
/// use actix_web::{Responder, http::StatusCode, test::TestRequest};
///
/// let responder = "Hello world!"
/// .customize()
/// .with_status(StatusCode::BAD_REQUEST)
/// .insert_header(("x-hello", "world"));
///
/// let request = TestRequest::default().to_http_request();
/// let response = responder.respond_to(&request);
/// assert_eq!(response.status(), StatusCode::BAD_REQUEST);
/// assert_eq!(response.headers().get("x-hello").unwrap(), "world");
/// ```
#[inline]
fn customize(self) -> CustomizeResponder<Self>
where
Self: Sized,
{
CustomResponder::new(self).with_status(status)
CustomizeResponder::new(self)
}
/// Insert header to the final response.
///
/// Overrides other headers with the same name.
///
/// ```
/// use actix_web::{web, HttpRequest, Responder};
/// use serde::Serialize;
///
/// #[derive(Serialize)]
/// struct MyObj {
/// name: String,
/// }
///
/// fn index(req: HttpRequest) -> impl Responder {
/// web::Json(MyObj { name: "Name".to_owned() })
/// .with_header(("x-version", "1.2.3"))
/// }
/// ```
fn with_header<H>(self, header: H) -> CustomResponder<Self>
#[doc(hidden)]
#[deprecated(since = "4.0.0", note = "Prefer `.customize().insert_header(header)`.")]
fn with_header(self, header: impl TryIntoHeaderPair) -> CustomizeResponder<Self>
where
Self: Sized,
H: IntoHeaderPair,
{
CustomResponder::new(self).with_header(header)
self.customize().insert_header(header)
}
}
@@ -181,98 +175,6 @@ macro_rules! impl_into_string_responder {
impl_into_string_responder!(&'_ String);
impl_into_string_responder!(Cow<'_, str>);
/// Allows overriding status code and headers for a responder.
pub struct CustomResponder<T> {
responder: T,
status: Option<StatusCode>,
headers: Result<HeaderMap, HttpError>,
}
impl<T: Responder> CustomResponder<T> {
fn new(responder: T) -> Self {
CustomResponder {
responder,
status: None,
headers: Ok(HeaderMap::new()),
}
}
/// Override a status code for the Responder's response.
///
/// ```
/// use actix_web::{HttpRequest, Responder, http::StatusCode};
///
/// fn index(req: HttpRequest) -> impl Responder {
/// "Welcome!".with_status(StatusCode::OK)
/// }
/// ```
pub fn with_status(mut self, status: StatusCode) -> Self {
self.status = Some(status);
self
}
/// Insert header to the final response.
///
/// Overrides other headers with the same name.
///
/// ```
/// use actix_web::{web, HttpRequest, Responder};
/// use serde::Serialize;
///
/// #[derive(Serialize)]
/// struct MyObj {
/// name: String,
/// }
///
/// fn index(req: HttpRequest) -> impl Responder {
/// web::Json(MyObj { name: "Name".to_string() })
/// .with_header(("x-version", "1.2.3"))
/// .with_header(("x-version", "1.2.3"))
/// }
/// ```
pub fn with_header<H>(mut self, header: H) -> Self
where
H: IntoHeaderPair,
{
if let Ok(ref mut headers) = self.headers {
match header.try_into_header_pair() {
Ok((key, value)) => headers.append(key, value),
Err(e) => self.headers = Err(e.into()),
};
}
self
}
}
impl<T> Responder for CustomResponder<T>
where
T: Responder,
<T::Body as MessageBody>::Error: Into<BoxError>,
{
type Body = EitherBody<T::Body>;
fn respond_to(self, req: &HttpRequest) -> HttpResponse<Self::Body> {
let headers = match self.headers {
Ok(headers) => headers,
Err(err) => return HttpResponse::from_error(err).map_into_right_body(),
};
let mut res = self.responder.respond_to(req);
if let Some(status) = self.status {
*res.status_mut() = status;
}
for (k, v) in headers {
// TODO: before v4, decide if this should be append instead
res.headers_mut().insert(k, v);
}
res.map_into_left_body()
}
}
#[cfg(test)]
pub(crate) mod tests {
use actix_service::Service;
@@ -440,59 +342,4 @@ pub(crate) mod tests {
assert_eq!(res.status(), StatusCode::BAD_REQUEST);
}
#[actix_rt::test]
async fn test_custom_responder() {
let req = TestRequest::default().to_http_request();
let res = "test"
.to_string()
.with_status(StatusCode::BAD_REQUEST)
.respond_to(&req);
assert_eq!(res.status(), StatusCode::BAD_REQUEST);
assert_eq!(
to_bytes(res.into_body()).await.unwrap(),
Bytes::from_static(b"test"),
);
let res = "test"
.to_string()
.with_header(("content-type", "json"))
.respond_to(&req);
assert_eq!(res.status(), StatusCode::OK);
assert_eq!(
res.headers().get(CONTENT_TYPE).unwrap(),
HeaderValue::from_static("json")
);
assert_eq!(
to_bytes(res.into_body()).await.unwrap(),
Bytes::from_static(b"test"),
);
}
#[actix_rt::test]
async fn test_tuple_responder_with_status_code() {
let req = TestRequest::default().to_http_request();
let res = ("test".to_string(), StatusCode::BAD_REQUEST).respond_to(&req);
assert_eq!(res.status(), StatusCode::BAD_REQUEST);
assert_eq!(
to_bytes(res.into_body()).await.unwrap(),
Bytes::from_static(b"test"),
);
let req = TestRequest::default().to_http_request();
let res = ("test".to_string(), StatusCode::OK)
.with_header((CONTENT_TYPE, mime::APPLICATION_JSON))
.respond_to(&req);
assert_eq!(res.status(), StatusCode::OK);
assert_eq!(
res.headers().get(CONTENT_TYPE).unwrap(),
HeaderValue::from_static("application/json")
);
assert_eq!(
to_bytes(res.into_body()).await.unwrap(),
Bytes::from_static(b"test"),
);
}
}

View File

@@ -935,7 +935,7 @@ mod tests {
web::scope("app")
.wrap(
DefaultHeaders::new()
.header(header::CONTENT_TYPE, HeaderValue::from_static("0001")),
.add((header::CONTENT_TYPE, HeaderValue::from_static("0001"))),
)
.service(web::resource("/test").route(web::get().to(HttpResponse::Ok))),
),

View File

@@ -4,8 +4,8 @@ use std::{borrow::Cow, net::SocketAddr, rc::Rc};
pub use actix_http::test::TestBuffer;
use actix_http::{
header::IntoHeaderPair, test::TestRequest as HttpTestRequest, Extensions, Method, Request,
StatusCode, Uri, Version,
header::TryIntoHeaderPair, test::TestRequest as HttpTestRequest, Extensions, Method,
Request, StatusCode, Uri, Version,
};
use actix_router::{Path, ResourceDef, Url};
use actix_service::{IntoService, IntoServiceFactory, Service, ServiceFactory};
@@ -445,19 +445,13 @@ impl TestRequest {
}
/// Insert a header, replacing any that were set with an equivalent field name.
pub fn insert_header<H>(mut self, header: H) -> Self
where
H: IntoHeaderPair,
{
pub fn insert_header(mut self, header: impl TryIntoHeaderPair) -> Self {
self.req.insert_header(header);
self
}
/// Append a header, keeping any that were set with an equivalent field name.
pub fn append_header<H>(mut self, header: H) -> Self
where
H: IntoHeaderPair,
{
pub fn append_header(mut self, header: impl TryIntoHeaderPair) -> Self {
self.req.append_header(header);
self
}

View File

@@ -8,7 +8,7 @@ pub use bytes::{Buf, BufMut, Bytes, BytesMut};
use crate::{
body::MessageBody, error::BlockingError, extract::FromRequest, handler::Handler,
resource::Resource, responder::Responder, route::Route, scope::Scope, service::WebService,
resource::Resource, route::Route, scope::Scope, service::WebService, Responder,
};
pub use crate::config::ServiceConfig;