mirror of https://github.com/fafhrd91/actix-web
align header map terminology
To prevent conflicts when migrating, this adds a new method to the Responder trait that wraps in a CustomizeResponder instead of renaming with_header to insert_header, like that which exists on HttpResponse. This is overall a nicer, more explicit API. Also alters the methods on DefaultHeaders middleware.
This commit is contained in:
parent
b5831cc23b
commit
b1dfec2e87
|
@ -1,6 +1,13 @@
|
||||||
# Changes
|
# Changes
|
||||||
|
|
||||||
## Unreleased - 2021-xx-xx
|
## Unreleased - 2021-xx-xx
|
||||||
|
### Added
|
||||||
|
* Method on `Responder` trait (`customize`) for customizing responders and `CustomizeResponder` struct. [#????]
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
* Align `DefaultHeader` method terminology, deprecating previous methods. [#????]
|
||||||
|
|
||||||
|
[#????]: https://github.com/actix/actix-web/pull/????
|
||||||
|
|
||||||
|
|
||||||
## 4.0.0-beta.14 - 2021-12-11
|
## 4.0.0-beta.14 - 2021-12-11
|
||||||
|
|
|
@ -1,6 +1,10 @@
|
||||||
# Changes
|
# Changes
|
||||||
|
|
||||||
## Unreleased - 2021-xx-xx
|
## Unreleased - 2021-xx-xx
|
||||||
|
### Changed
|
||||||
|
* Rename trait `IntoHeaderPair => TryIntoHeaderPair`. [#????]
|
||||||
|
|
||||||
|
[#????]: https://github.com/actix/actix-web/pull/????
|
||||||
|
|
||||||
|
|
||||||
## 3.0.0-beta.15 - 2021-12-11
|
## 3.0.0-beta.15 - 2021-12-11
|
||||||
|
@ -260,7 +264,7 @@
|
||||||
|
|
||||||
## 3.0.0-beta.2 - 2021-02-10
|
## 3.0.0-beta.2 - 2021-02-10
|
||||||
### Added
|
### Added
|
||||||
* `IntoHeaderPair` trait that allows using typed and untyped headers in the same methods. [#1869]
|
* `TryIntoHeaderPair` trait that allows using typed and untyped headers in the same methods. [#1869]
|
||||||
* `ResponseBuilder::insert_header` method which allows using typed headers. [#1869]
|
* `ResponseBuilder::insert_header` method which allows using typed headers. [#1869]
|
||||||
* `ResponseBuilder::append_header` method which allows using typed headers. [#1869]
|
* `ResponseBuilder::append_header` method which allows using typed headers. [#1869]
|
||||||
* `TestRequest::insert_header` method which allows using typed headers. [#1869]
|
* `TestRequest::insert_header` method which allows using typed headers. [#1869]
|
||||||
|
|
|
@ -270,10 +270,10 @@ where
|
||||||
type Future = H2ServiceHandlerResponse<T, S, B>;
|
type Future = H2ServiceHandlerResponse<T, S, B>;
|
||||||
|
|
||||||
fn poll_ready(&self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
fn poll_ready(&self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||||
self.flow.service.poll_ready(cx).map_err(|e| {
|
self.flow.service.poll_ready(cx).map_err(|err| {
|
||||||
let e = e.into();
|
let err = err.into();
|
||||||
error!("Service readiness error: {:?}", e);
|
error!("Service readiness error: {:?}", err);
|
||||||
DispatchError::Service(e)
|
DispatchError::Service(err)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -297,7 +297,6 @@ where
|
||||||
T: AsyncRead + AsyncWrite + Unpin,
|
T: AsyncRead + AsyncWrite + Unpin,
|
||||||
S::Future: 'static,
|
S::Future: 'static,
|
||||||
{
|
{
|
||||||
Incoming(Dispatcher<T, S, B, (), ()>),
|
|
||||||
Handshake(
|
Handshake(
|
||||||
Option<Rc<HttpFlow<S, (), ()>>>,
|
Option<Rc<HttpFlow<S, (), ()>>>,
|
||||||
Option<ServiceConfig>,
|
Option<ServiceConfig>,
|
||||||
|
@ -305,6 +304,7 @@ where
|
||||||
OnConnectData,
|
OnConnectData,
|
||||||
HandshakeWithTimeout<T>,
|
HandshakeWithTimeout<T>,
|
||||||
),
|
),
|
||||||
|
Established(Dispatcher<T, S, B, (), ()>),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct H2ServiceHandlerResponse<T, S, B>
|
pub struct H2ServiceHandlerResponse<T, S, B>
|
||||||
|
@ -332,7 +332,6 @@ where
|
||||||
|
|
||||||
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||||
match self.state {
|
match self.state {
|
||||||
State::Incoming(ref mut disp) => Pin::new(disp).poll(cx),
|
|
||||||
State::Handshake(
|
State::Handshake(
|
||||||
ref mut srv,
|
ref mut srv,
|
||||||
ref mut config,
|
ref mut config,
|
||||||
|
@ -343,7 +342,7 @@ where
|
||||||
Ok((conn, timer)) => {
|
Ok((conn, timer)) => {
|
||||||
let on_connect_data = mem::take(conn_data);
|
let on_connect_data = mem::take(conn_data);
|
||||||
|
|
||||||
self.state = State::Incoming(Dispatcher::new(
|
self.state = State::Established(Dispatcher::new(
|
||||||
conn,
|
conn,
|
||||||
srv.take().unwrap(),
|
srv.take().unwrap(),
|
||||||
config.take().unwrap(),
|
config.take().unwrap(),
|
||||||
|
@ -360,6 +359,8 @@ where
|
||||||
Poll::Ready(Err(err))
|
Poll::Ready(Err(err))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
State::Established(ref mut disp) => Pin::new(disp).poll(cx),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,19 +1,17 @@
|
||||||
//! [`IntoHeaderPair`] trait and implementations.
|
//! [`TryIntoHeaderPair`] trait and implementations.
|
||||||
|
|
||||||
use std::convert::TryFrom as _;
|
use std::convert::TryFrom as _;
|
||||||
|
|
||||||
use http::{
|
use super::{
|
||||||
header::{HeaderName, InvalidHeaderName, InvalidHeaderValue},
|
Header, HeaderName, HeaderValue, IntoHeaderValue, InvalidHeaderName, InvalidHeaderValue,
|
||||||
Error as HttpError, HeaderValue,
|
|
||||||
};
|
};
|
||||||
|
use crate::error::HttpError;
|
||||||
|
|
||||||
use super::{Header, IntoHeaderValue};
|
/// An interface for types that can be converted into a [`HeaderName`] + [`HeaderValue`] pair for
|
||||||
|
|
||||||
/// An interface for types that can be converted into a [`HeaderName`]/[`HeaderValue`] pair for
|
|
||||||
/// insertion into a [`HeaderMap`].
|
/// insertion into a [`HeaderMap`].
|
||||||
///
|
///
|
||||||
/// [`HeaderMap`]: super::HeaderMap
|
/// [`HeaderMap`]: super::HeaderMap
|
||||||
pub trait IntoHeaderPair: Sized {
|
pub trait TryIntoHeaderPair: Sized {
|
||||||
type Error: Into<HttpError>;
|
type Error: Into<HttpError>;
|
||||||
|
|
||||||
fn try_into_header_pair(self) -> Result<(HeaderName, HeaderValue), Self::Error>;
|
fn try_into_header_pair(self) -> Result<(HeaderName, HeaderValue), Self::Error>;
|
||||||
|
@ -34,7 +32,7 @@ impl From<InvalidHeaderPart> for HttpError {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<V> IntoHeaderPair for (HeaderName, V)
|
impl<V> TryIntoHeaderPair for (HeaderName, V)
|
||||||
where
|
where
|
||||||
V: IntoHeaderValue,
|
V: IntoHeaderValue,
|
||||||
V::Error: Into<InvalidHeaderValue>,
|
V::Error: Into<InvalidHeaderValue>,
|
||||||
|
@ -50,7 +48,7 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<V> IntoHeaderPair for (&HeaderName, V)
|
impl<V> TryIntoHeaderPair for (&HeaderName, V)
|
||||||
where
|
where
|
||||||
V: IntoHeaderValue,
|
V: IntoHeaderValue,
|
||||||
V::Error: Into<InvalidHeaderValue>,
|
V::Error: Into<InvalidHeaderValue>,
|
||||||
|
@ -66,7 +64,7 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<V> IntoHeaderPair for (&[u8], V)
|
impl<V> TryIntoHeaderPair for (&[u8], V)
|
||||||
where
|
where
|
||||||
V: IntoHeaderValue,
|
V: IntoHeaderValue,
|
||||||
V::Error: Into<InvalidHeaderValue>,
|
V::Error: Into<InvalidHeaderValue>,
|
||||||
|
@ -83,7 +81,7 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<V> IntoHeaderPair for (&str, V)
|
impl<V> TryIntoHeaderPair for (&str, V)
|
||||||
where
|
where
|
||||||
V: IntoHeaderValue,
|
V: IntoHeaderValue,
|
||||||
V::Error: Into<InvalidHeaderValue>,
|
V::Error: Into<InvalidHeaderValue>,
|
||||||
|
@ -100,22 +98,24 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<V> IntoHeaderPair for (String, V)
|
impl<V> TryIntoHeaderPair for (String, V)
|
||||||
where
|
where
|
||||||
V: IntoHeaderValue,
|
V: IntoHeaderValue,
|
||||||
V::Error: Into<InvalidHeaderValue>,
|
V::Error: Into<InvalidHeaderValue>,
|
||||||
{
|
{
|
||||||
type Error = InvalidHeaderPart;
|
type Error = InvalidHeaderPart;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn try_into_header_pair(self) -> Result<(HeaderName, HeaderValue), Self::Error> {
|
fn try_into_header_pair(self) -> Result<(HeaderName, HeaderValue), Self::Error> {
|
||||||
let (name, value) = self;
|
let (name, value) = self;
|
||||||
(name.as_str(), value).try_into_header_pair()
|
(name.as_str(), value).try_into_header_pair()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Header> IntoHeaderPair for T {
|
impl<T: Header> TryIntoHeaderPair for T {
|
||||||
type Error = <T as IntoHeaderValue>::Error;
|
type Error = <T as IntoHeaderValue>::Error;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn try_into_header_pair(self) -> Result<(HeaderName, HeaderValue), Self::Error> {
|
fn try_into_header_pair(self) -> Result<(HeaderName, HeaderValue), Self::Error> {
|
||||||
Ok((T::name(), self.try_into_value()?))
|
Ok((T::name(), self.try_into_value()?))
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,7 +37,7 @@ mod shared;
|
||||||
mod utils;
|
mod utils;
|
||||||
|
|
||||||
pub use self::as_name::AsHeaderName;
|
pub use self::as_name::AsHeaderName;
|
||||||
pub use self::into_pair::IntoHeaderPair;
|
pub use self::into_pair::TryIntoHeaderPair;
|
||||||
pub use self::into_value::IntoHeaderValue;
|
pub use self::into_value::IntoHeaderValue;
|
||||||
pub use self::map::HeaderMap;
|
pub use self::map::HeaderMap;
|
||||||
pub use self::shared::{
|
pub use self::shared::{
|
||||||
|
|
|
@ -8,7 +8,7 @@ use std::{
|
||||||
use crate::{
|
use crate::{
|
||||||
body::{EitherBody, MessageBody},
|
body::{EitherBody, MessageBody},
|
||||||
error::{Error, HttpError},
|
error::{Error, HttpError},
|
||||||
header::{self, IntoHeaderPair, IntoHeaderValue},
|
header::{self, IntoHeaderValue, TryIntoHeaderPair},
|
||||||
message::{BoxedResponseHead, ConnectionType, ResponseHead},
|
message::{BoxedResponseHead, ConnectionType, ResponseHead},
|
||||||
Extensions, Response, StatusCode,
|
Extensions, Response, StatusCode,
|
||||||
};
|
};
|
||||||
|
@ -90,10 +90,7 @@ impl ResponseBuilder {
|
||||||
/// assert!(res.headers().contains_key("content-type"));
|
/// assert!(res.headers().contains_key("content-type"));
|
||||||
/// assert!(res.headers().contains_key("x-test"));
|
/// assert!(res.headers().contains_key("x-test"));
|
||||||
/// ```
|
/// ```
|
||||||
pub fn insert_header<H>(&mut self, header: H) -> &mut Self
|
pub fn insert_header(&mut self, header: impl TryIntoHeaderPair) -> &mut Self {
|
||||||
where
|
|
||||||
H: IntoHeaderPair,
|
|
||||||
{
|
|
||||||
if let Some(parts) = self.inner() {
|
if let Some(parts) = self.inner() {
|
||||||
match header.try_into_header_pair() {
|
match header.try_into_header_pair() {
|
||||||
Ok((key, value)) => {
|
Ok((key, value)) => {
|
||||||
|
@ -121,10 +118,7 @@ impl ResponseBuilder {
|
||||||
/// assert_eq!(res.headers().get_all("content-type").count(), 1);
|
/// assert_eq!(res.headers().get_all("content-type").count(), 1);
|
||||||
/// assert_eq!(res.headers().get_all("x-test").count(), 2);
|
/// assert_eq!(res.headers().get_all("x-test").count(), 2);
|
||||||
/// ```
|
/// ```
|
||||||
pub fn append_header<H>(&mut self, header: H) -> &mut Self
|
pub fn append_header(&mut self, header: impl TryIntoHeaderPair) -> &mut Self {
|
||||||
where
|
|
||||||
H: IntoHeaderPair,
|
|
||||||
{
|
|
||||||
if let Some(parts) = self.inner() {
|
if let Some(parts) = self.inner() {
|
||||||
match header.try_into_header_pair() {
|
match header.try_into_header_pair() {
|
||||||
Ok((key, value)) => parts.headers.append(key, value),
|
Ok((key, value)) => parts.headers.append(key, value),
|
||||||
|
|
|
@ -14,7 +14,7 @@ use bytes::{Bytes, BytesMut};
|
||||||
use http::{Method, Uri, Version};
|
use http::{Method, Uri, Version};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
header::{HeaderMap, IntoHeaderPair},
|
header::{HeaderMap, TryIntoHeaderPair},
|
||||||
payload::Payload,
|
payload::Payload,
|
||||||
Request,
|
Request,
|
||||||
};
|
};
|
||||||
|
@ -92,10 +92,7 @@ impl TestRequest {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Insert a header, replacing any that were set with an equivalent field name.
|
/// Insert a header, replacing any that were set with an equivalent field name.
|
||||||
pub fn insert_header<H>(&mut self, header: H) -> &mut Self
|
pub fn insert_header(&mut self, header: impl TryIntoHeaderPair) -> &mut Self {
|
||||||
where
|
|
||||||
H: IntoHeaderPair,
|
|
||||||
{
|
|
||||||
match header.try_into_header_pair() {
|
match header.try_into_header_pair() {
|
||||||
Ok((key, value)) => {
|
Ok((key, value)) => {
|
||||||
parts(&mut self.0).headers.insert(key, value);
|
parts(&mut self.0).headers.insert(key, value);
|
||||||
|
@ -109,10 +106,7 @@ impl TestRequest {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Append a header, keeping any that were set with an equivalent field name.
|
/// Append a header, keeping any that were set with an equivalent field name.
|
||||||
pub fn append_header<H>(&mut self, header: H) -> &mut Self
|
pub fn append_header(&mut self, header: impl TryIntoHeaderPair) -> &mut Self {
|
||||||
where
|
|
||||||
H: IntoHeaderPair,
|
|
||||||
{
|
|
||||||
match header.try_into_header_pair() {
|
match header.try_into_header_pair() {
|
||||||
Ok((key, value)) => {
|
Ok((key, value)) => {
|
||||||
parts(&mut self.0).headers.append(key, value);
|
parts(&mut self.0).headers.append(key, value);
|
||||||
|
|
|
@ -60,7 +60,7 @@
|
||||||
* `ConnectorService` type is renamed to `BoxConnectorService`. [#2081]
|
* `ConnectorService` type is renamed to `BoxConnectorService`. [#2081]
|
||||||
* Fix http/https encoding when enabling `compress` feature. [#2116]
|
* Fix http/https encoding when enabling `compress` feature. [#2116]
|
||||||
* Rename `TestResponse::header` to `append_header`, `set` to `insert_header`. `TestResponse` header
|
* Rename `TestResponse::header` to `append_header`, `set` to `insert_header`. `TestResponse` header
|
||||||
methods now take `IntoHeaderPair` tuples. [#2094]
|
methods now take `TryIntoHeaderPair` tuples. [#2094]
|
||||||
|
|
||||||
[#2081]: https://github.com/actix/actix-web/pull/2081
|
[#2081]: https://github.com/actix/actix-web/pull/2081
|
||||||
[#2094]: https://github.com/actix/actix-web/pull/2094
|
[#2094]: https://github.com/actix/actix-web/pull/2094
|
||||||
|
|
|
@ -6,7 +6,7 @@ use serde::Serialize;
|
||||||
|
|
||||||
use actix_http::{
|
use actix_http::{
|
||||||
error::HttpError,
|
error::HttpError,
|
||||||
header::{self, HeaderMap, HeaderValue, IntoHeaderPair},
|
header::{self, HeaderMap, HeaderValue, TryIntoHeaderPair},
|
||||||
ConnectionType, Method, RequestHead, Uri, Version,
|
ConnectionType, Method, RequestHead, Uri, Version,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -147,10 +147,7 @@ impl ClientRequest {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Insert a header, replacing any that were set with an equivalent field name.
|
/// Insert a header, replacing any that were set with an equivalent field name.
|
||||||
pub fn insert_header<H>(mut self, header: H) -> Self
|
pub fn insert_header(mut self, header: impl TryIntoHeaderPair) -> Self {
|
||||||
where
|
|
||||||
H: IntoHeaderPair,
|
|
||||||
{
|
|
||||||
match header.try_into_header_pair() {
|
match header.try_into_header_pair() {
|
||||||
Ok((key, value)) => {
|
Ok((key, value)) => {
|
||||||
self.head.headers.insert(key, value);
|
self.head.headers.insert(key, value);
|
||||||
|
@ -162,10 +159,7 @@ impl ClientRequest {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Insert a header only if it is not yet set.
|
/// Insert a header only if it is not yet set.
|
||||||
pub fn insert_header_if_none<H>(mut self, header: H) -> Self
|
pub fn insert_header_if_none(mut self, header: impl TryIntoHeaderPair) -> Self {
|
||||||
where
|
|
||||||
H: IntoHeaderPair,
|
|
||||||
{
|
|
||||||
match header.try_into_header_pair() {
|
match header.try_into_header_pair() {
|
||||||
Ok((key, value)) => {
|
Ok((key, value)) => {
|
||||||
if !self.head.headers.contains_key(&key) {
|
if !self.head.headers.contains_key(&key) {
|
||||||
|
@ -192,10 +186,7 @@ impl ClientRequest {
|
||||||
/// .insert_header((CONTENT_TYPE, mime::APPLICATION_JSON));
|
/// .insert_header((CONTENT_TYPE, mime::APPLICATION_JSON));
|
||||||
/// # }
|
/// # }
|
||||||
/// ```
|
/// ```
|
||||||
pub fn append_header<H>(mut self, header: H) -> Self
|
pub fn append_header(mut self, header: impl TryIntoHeaderPair) -> Self {
|
||||||
where
|
|
||||||
H: IntoHeaderPair,
|
|
||||||
{
|
|
||||||
match header.try_into_header_pair() {
|
match header.try_into_header_pair() {
|
||||||
Ok((key, value)) => self.head.headers.append(key, value),
|
Ok((key, value)) => self.head.headers.append(key, value),
|
||||||
Err(e) => self.err = Some(e.into()),
|
Err(e) => self.err = Some(e.into()),
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
//! Test helpers for actix http client to use during testing.
|
//! Test helpers for actix http client to use during testing.
|
||||||
|
|
||||||
use actix_http::{h1, header::IntoHeaderPair, Payload, ResponseHead, StatusCode, Version};
|
use actix_http::{h1, header::TryIntoHeaderPair, Payload, ResponseHead, StatusCode, Version};
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
|
|
||||||
#[cfg(feature = "cookies")]
|
#[cfg(feature = "cookies")]
|
||||||
|
@ -28,10 +28,7 @@ impl Default for TestResponse {
|
||||||
|
|
||||||
impl TestResponse {
|
impl TestResponse {
|
||||||
/// Create TestResponse and set header
|
/// Create TestResponse and set header
|
||||||
pub fn with_header<H>(header: H) -> Self
|
pub fn with_header(header: impl TryIntoHeaderPair) -> Self {
|
||||||
where
|
|
||||||
H: IntoHeaderPair,
|
|
||||||
{
|
|
||||||
Self::default().insert_header(header)
|
Self::default().insert_header(header)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -42,10 +39,7 @@ impl TestResponse {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Insert a header
|
/// Insert a header
|
||||||
pub fn insert_header<H>(mut self, header: H) -> Self
|
pub fn insert_header(mut self, header: impl TryIntoHeaderPair) -> Self {
|
||||||
where
|
|
||||||
H: IntoHeaderPair,
|
|
||||||
{
|
|
||||||
if let Ok((key, value)) = header.try_into_header_pair() {
|
if let Ok((key, value)) = header.try_into_header_pair() {
|
||||||
self.head.headers.insert(key, value);
|
self.head.headers.insert(key, value);
|
||||||
return self;
|
return self;
|
||||||
|
@ -54,10 +48,7 @@ impl TestResponse {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Append a header
|
/// Append a header
|
||||||
pub fn append_header<H>(mut self, header: H) -> Self
|
pub fn append_header(mut self, header: impl TryIntoHeaderPair) -> Self {
|
||||||
where
|
|
||||||
H: IntoHeaderPair,
|
|
||||||
{
|
|
||||||
if let Ok((key, value)) = header.try_into_header_pair() {
|
if let Ok((key, value)) = header.try_into_header_pair() {
|
||||||
self.head.headers.append(key, value);
|
self.head.headers.append(key, value);
|
||||||
return self;
|
return self;
|
||||||
|
|
|
@ -22,14 +22,14 @@ async fn main() -> std::io::Result<()> {
|
||||||
|
|
||||||
HttpServer::new(|| {
|
HttpServer::new(|| {
|
||||||
App::new()
|
App::new()
|
||||||
.wrap(middleware::DefaultHeaders::new().header("X-Version", "0.2"))
|
.wrap(middleware::DefaultHeaders::new().insert(("X-Version", "0.2")))
|
||||||
.wrap(middleware::Compress::default())
|
.wrap(middleware::Compress::default())
|
||||||
.wrap(middleware::Logger::default())
|
.wrap(middleware::Logger::default())
|
||||||
.service(index)
|
.service(index)
|
||||||
.service(no_params)
|
.service(no_params)
|
||||||
.service(
|
.service(
|
||||||
web::resource("/resource2/index.html")
|
web::resource("/resource2/index.html")
|
||||||
.wrap(middleware::DefaultHeaders::new().header("X-Version-R2", "0.3"))
|
.wrap(middleware::DefaultHeaders::new().insert(("X-Version-R2", "0.3")))
|
||||||
.default_service(web::route().to(HttpResponse::MethodNotAllowed))
|
.default_service(web::route().to(HttpResponse::MethodNotAllowed))
|
||||||
.route(web::get().to(index_async)),
|
.route(web::get().to(index_async)),
|
||||||
)
|
)
|
||||||
|
|
|
@ -26,14 +26,14 @@ async fn main() -> std::io::Result<()> {
|
||||||
|
|
||||||
HttpServer::new(|| {
|
HttpServer::new(|| {
|
||||||
App::new()
|
App::new()
|
||||||
.wrap(middleware::DefaultHeaders::new().header("X-Version", "0.2"))
|
.wrap(middleware::DefaultHeaders::new().insert(("X-Version", "0.2")))
|
||||||
.wrap(middleware::Compress::default())
|
.wrap(middleware::Compress::default())
|
||||||
.wrap(middleware::Logger::default())
|
.wrap(middleware::Logger::default())
|
||||||
.service(index)
|
.service(index)
|
||||||
.service(no_params)
|
.service(no_params)
|
||||||
.service(
|
.service(
|
||||||
web::resource("/resource2/index.html")
|
web::resource("/resource2/index.html")
|
||||||
.wrap(middleware::DefaultHeaders::new().header("X-Version-R2", "0.3"))
|
.wrap(middleware::DefaultHeaders::new().insert(("X-Version-R2", "0.3")))
|
||||||
.default_service(web::route().to(HttpResponse::MethodNotAllowed))
|
.default_service(web::route().to(HttpResponse::MethodNotAllowed))
|
||||||
.route(web::get().to(index_async)),
|
.route(web::get().to(index_async)),
|
||||||
)
|
)
|
||||||
|
|
|
@ -602,7 +602,7 @@ mod tests {
|
||||||
App::new()
|
App::new()
|
||||||
.wrap(
|
.wrap(
|
||||||
DefaultHeaders::new()
|
DefaultHeaders::new()
|
||||||
.header(header::CONTENT_TYPE, HeaderValue::from_static("0001")),
|
.insert((header::CONTENT_TYPE, HeaderValue::from_static("0001"))),
|
||||||
)
|
)
|
||||||
.route("/test", web::get().to(HttpResponse::Ok)),
|
.route("/test", web::get().to(HttpResponse::Ok)),
|
||||||
)
|
)
|
||||||
|
@ -623,7 +623,7 @@ mod tests {
|
||||||
.route("/test", web::get().to(HttpResponse::Ok))
|
.route("/test", web::get().to(HttpResponse::Ok))
|
||||||
.wrap(
|
.wrap(
|
||||||
DefaultHeaders::new()
|
DefaultHeaders::new()
|
||||||
.header(header::CONTENT_TYPE, HeaderValue::from_static("0001")),
|
.insert((header::CONTENT_TYPE, HeaderValue::from_static("0001"))),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|
|
@ -86,7 +86,6 @@ pub mod middleware;
|
||||||
mod request;
|
mod request;
|
||||||
mod request_data;
|
mod request_data;
|
||||||
mod resource;
|
mod resource;
|
||||||
mod responder;
|
|
||||||
mod response;
|
mod response;
|
||||||
mod rmap;
|
mod rmap;
|
||||||
mod route;
|
mod route;
|
||||||
|
@ -109,12 +108,10 @@ pub use crate::error::{Error, ResponseError, Result};
|
||||||
pub use crate::extract::FromRequest;
|
pub use crate::extract::FromRequest;
|
||||||
pub use crate::request::HttpRequest;
|
pub use crate::request::HttpRequest;
|
||||||
pub use crate::resource::Resource;
|
pub use crate::resource::Resource;
|
||||||
pub use crate::responder::Responder;
|
pub use crate::response::{CustomizeResponder, HttpResponse, HttpResponseBuilder, Responder};
|
||||||
pub use crate::response::{HttpResponse, HttpResponseBuilder};
|
|
||||||
pub use crate::route::Route;
|
pub use crate::route::Route;
|
||||||
pub use crate::scope::Scope;
|
pub use crate::scope::Scope;
|
||||||
pub use crate::server::HttpServer;
|
pub use crate::server::HttpServer;
|
||||||
// TODO: is exposing the error directly really needed
|
pub use crate::types::Either;
|
||||||
pub use crate::types::{Either, EitherExtractError};
|
|
||||||
|
|
||||||
pub(crate) type BoxError = Box<dyn std::error::Error>;
|
pub(crate) type BoxError = Box<dyn std::error::Error>;
|
||||||
|
|
|
@ -16,7 +16,7 @@ use pin_project_lite::pin_project;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
dev::{Service, Transform},
|
dev::{Service, Transform},
|
||||||
http::header::{HeaderMap, HeaderName, HeaderValue, CONTENT_TYPE},
|
http::header::{HeaderMap, HeaderName, HeaderValue, TryIntoHeaderPair, CONTENT_TYPE},
|
||||||
service::{ServiceRequest, ServiceResponse},
|
service::{ServiceRequest, ServiceResponse},
|
||||||
Error,
|
Error,
|
||||||
};
|
};
|
||||||
|
@ -29,79 +29,82 @@ use crate::{
|
||||||
/// ```
|
/// ```
|
||||||
/// use actix_web::{web, http, middleware, App, HttpResponse};
|
/// use actix_web::{web, http, middleware, App, HttpResponse};
|
||||||
///
|
///
|
||||||
/// fn main() {
|
/// let app = App::new()
|
||||||
/// let app = App::new()
|
/// .wrap(middleware::DefaultHeaders::new().insert("X-Version", "0.2"))
|
||||||
/// .wrap(middleware::DefaultHeaders::new().header("X-Version", "0.2"))
|
/// .service(
|
||||||
/// .service(
|
/// web::resource("/test")
|
||||||
/// web::resource("/test")
|
/// .route(web::get().to(|| HttpResponse::Ok()))
|
||||||
/// .route(web::get().to(|| HttpResponse::Ok()))
|
/// .route(web::method(http::Method::HEAD).to(|| HttpResponse::MethodNotAllowed()))
|
||||||
/// .route(web::method(http::Method::HEAD).to(|| HttpResponse::MethodNotAllowed()))
|
/// );
|
||||||
/// );
|
|
||||||
/// }
|
|
||||||
/// ```
|
/// ```
|
||||||
#[derive(Clone)]
|
#[derive(Debug, Clone, Default)]
|
||||||
pub struct DefaultHeaders {
|
pub struct DefaultHeaders {
|
||||||
inner: Rc<Inner>,
|
inner: Rc<Inner>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
struct Inner {
|
struct Inner {
|
||||||
headers: HeaderMap,
|
headers: HeaderMap,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for DefaultHeaders {
|
|
||||||
fn default() -> Self {
|
|
||||||
DefaultHeaders {
|
|
||||||
inner: Rc::new(Inner {
|
|
||||||
headers: HeaderMap::new(),
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DefaultHeaders {
|
impl DefaultHeaders {
|
||||||
/// Constructs an empty `DefaultHeaders` middleware.
|
/// Constructs an empty `DefaultHeaders` middleware.
|
||||||
|
#[inline]
|
||||||
pub fn new() -> DefaultHeaders {
|
pub fn new() -> DefaultHeaders {
|
||||||
DefaultHeaders::default()
|
DefaultHeaders::default()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Adds a header to the default set.
|
/// 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.
|
||||||
|
pub fn insert(mut self, header: impl TryIntoHeaderPair) -> Self {
|
||||||
|
match header.try_into_header_pair() {
|
||||||
|
Ok((key, value)) => Rc::get_mut(&mut self.inner)
|
||||||
|
.expect(
|
||||||
|
"DefaultHeaders has been cloned. Add all default headers before cloning.",
|
||||||
|
)
|
||||||
|
.headers
|
||||||
|
.append(key, value),
|
||||||
|
Err(err) => panic!("Invalid header: {}", err.into()),
|
||||||
|
}
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
#[deprecated(since = "4.0.0", note = "Prefer `insert`.")]
|
||||||
|
pub fn header<K, V>(self, key: K, value: V) -> Self
|
||||||
where
|
where
|
||||||
HeaderName: TryFrom<K>,
|
HeaderName: TryFrom<K>,
|
||||||
<HeaderName as TryFrom<K>>::Error: Into<HttpError>,
|
<HeaderName as TryFrom<K>>::Error: Into<HttpError>,
|
||||||
HeaderValue: TryFrom<V>,
|
HeaderValue: TryFrom<V>,
|
||||||
<HeaderValue as TryFrom<V>>::Error: Into<HttpError>,
|
<HeaderValue as TryFrom<V>>::Error: Into<HttpError>,
|
||||||
{
|
{
|
||||||
#[allow(clippy::match_wild_err_arm)]
|
self.insert((
|
||||||
match HeaderName::try_from(key) {
|
HeaderName::try_from(key)
|
||||||
Ok(key) => match HeaderValue::try_from(value) {
|
.map_err(Into::into)
|
||||||
Ok(value) => {
|
.expect("Invalid header name"),
|
||||||
Rc::get_mut(&mut self.inner)
|
HeaderValue::try_from(value)
|
||||||
.expect("Multiple copies exist")
|
.map_err(Into::into)
|
||||||
.headers
|
.expect("Invalid header value"),
|
||||||
.append(key, value);
|
))
|
||||||
}
|
|
||||||
Err(_) => panic!("Can not create header value"),
|
|
||||||
},
|
|
||||||
Err(_) => panic!("Can not create header name"),
|
|
||||||
}
|
|
||||||
self
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Adds a default *Content-Type* header if response does not contain one.
|
/// Adds a default *Content-Type* header if response does not contain one.
|
||||||
///
|
///
|
||||||
/// Default is `application/octet-stream`.
|
/// Default is `application/octet-stream`.
|
||||||
pub fn add_content_type(mut self) -> Self {
|
pub fn insert_content_type(self) -> Self {
|
||||||
Rc::get_mut(&mut self.inner)
|
self.insert((
|
||||||
.expect("Multiple `Inner` copies exist.")
|
CONTENT_TYPE,
|
||||||
.headers
|
HeaderValue::from_static("application/octet-stream"),
|
||||||
.insert(
|
))
|
||||||
CONTENT_TYPE,
|
}
|
||||||
HeaderValue::from_static("application/octet-stream"),
|
|
||||||
);
|
|
||||||
|
|
||||||
self
|
#[doc(hidden)]
|
||||||
|
#[deprecated(since = "4.0.0", note = "Renamed to `insert_content_type`.")]
|
||||||
|
pub fn add_content_type(self) -> Self {
|
||||||
|
self.insert_content_type()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -119,7 +122,7 @@ where
|
||||||
fn new_transform(&self, service: S) -> Self::Future {
|
fn new_transform(&self, service: S) -> Self::Future {
|
||||||
ready(Ok(DefaultHeadersMiddleware {
|
ready(Ok(DefaultHeadersMiddleware {
|
||||||
service,
|
service,
|
||||||
inner: self.inner.clone(),
|
inner: Rc::clone(&self.inner),
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -197,17 +200,22 @@ mod tests {
|
||||||
};
|
};
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_default_headers() {
|
async fn adding_default_headers() {
|
||||||
let mw = DefaultHeaders::new()
|
let mw = DefaultHeaders::new()
|
||||||
.header(CONTENT_TYPE, "0001")
|
.insert(("X-TEST", "0001"))
|
||||||
|
.insert(("X-TEST-TWO", HeaderValue::from_static("123")))
|
||||||
.new_transform(ok_service())
|
.new_transform(ok_service())
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let req = TestRequest::default().to_srv_request();
|
let req = TestRequest::default().to_srv_request();
|
||||||
let resp = mw.call(req).await.unwrap();
|
let res = mw.call(req).await.unwrap();
|
||||||
assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0001");
|
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 req = TestRequest::default().to_srv_request();
|
||||||
let srv = |req: ServiceRequest| {
|
let srv = |req: ServiceRequest| {
|
||||||
ok(req.into_response(
|
ok(req.into_response(
|
||||||
|
@ -217,7 +225,7 @@ mod tests {
|
||||||
))
|
))
|
||||||
};
|
};
|
||||||
let mw = DefaultHeaders::new()
|
let mw = DefaultHeaders::new()
|
||||||
.header(CONTENT_TYPE, "0001")
|
.insert((CONTENT_TYPE, "0001"))
|
||||||
.new_transform(srv.into_service())
|
.new_transform(srv.into_service())
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
@ -226,10 +234,10 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[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 srv = |req: ServiceRequest| ok(req.into_response(HttpResponse::Ok().finish()));
|
||||||
let mw = DefaultHeaders::new()
|
let mw = DefaultHeaders::new()
|
||||||
.add_content_type()
|
.insert_content_type()
|
||||||
.new_transform(srv.into_service())
|
.new_transform(srv.into_service())
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
@ -241,4 +249,16 @@ mod tests {
|
||||||
"application/octet-stream"
|
"application/octet-stream"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[should_panic]
|
||||||
|
fn invalid_header_name() {
|
||||||
|
DefaultHeaders::new().insert((":", "hello"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[should_panic]
|
||||||
|
fn invalid_header_value() {
|
||||||
|
DefaultHeaders::new().insert(("x-test", "\n"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,7 +33,7 @@ mod tests {
|
||||||
let _ = App::new()
|
let _ = App::new()
|
||||||
.wrap(Compat::new(Logger::default()))
|
.wrap(Compat::new(Logger::default()))
|
||||||
.wrap(Condition::new(true, DefaultHeaders::new()))
|
.wrap(Condition::new(true, DefaultHeaders::new()))
|
||||||
.wrap(DefaultHeaders::new().header("X-Test2", "X-Value2"))
|
.wrap(DefaultHeaders::new().insert(("X-Test2", "X-Value2")))
|
||||||
.wrap(ErrorHandlers::new().handler(StatusCode::FORBIDDEN, |res| {
|
.wrap(ErrorHandlers::new().handler(StatusCode::FORBIDDEN, |res| {
|
||||||
Ok(ErrorHandlerResponse::Response(res))
|
Ok(ErrorHandlerResponse::Response(res))
|
||||||
}))
|
}))
|
||||||
|
@ -46,7 +46,7 @@ mod tests {
|
||||||
.wrap(ErrorHandlers::new().handler(StatusCode::FORBIDDEN, |res| {
|
.wrap(ErrorHandlers::new().handler(StatusCode::FORBIDDEN, |res| {
|
||||||
Ok(ErrorHandlerResponse::Response(res))
|
Ok(ErrorHandlerResponse::Response(res))
|
||||||
}))
|
}))
|
||||||
.wrap(DefaultHeaders::new().header("X-Test2", "X-Value2"))
|
.wrap(DefaultHeaders::new().insert(("X-Test2", "X-Value2")))
|
||||||
.wrap(Condition::new(true, DefaultHeaders::new()))
|
.wrap(Condition::new(true, DefaultHeaders::new()))
|
||||||
.wrap(Compat::new(Logger::default()));
|
.wrap(Compat::new(Logger::default()));
|
||||||
|
|
||||||
|
|
|
@ -15,13 +15,12 @@ use crate::{
|
||||||
dev::{ensure_leading_slash, AppService, ResourceDef},
|
dev::{ensure_leading_slash, AppService, ResourceDef},
|
||||||
guard::Guard,
|
guard::Guard,
|
||||||
handler::Handler,
|
handler::Handler,
|
||||||
responder::Responder,
|
|
||||||
route::{Route, RouteService},
|
route::{Route, RouteService},
|
||||||
service::{
|
service::{
|
||||||
BoxedHttpService, BoxedHttpServiceFactory, HttpServiceFactory, ServiceRequest,
|
BoxedHttpService, BoxedHttpServiceFactory, HttpServiceFactory, ServiceRequest,
|
||||||
ServiceResponse,
|
ServiceResponse,
|
||||||
},
|
},
|
||||||
BoxError, Error, FromRequest, HttpResponse,
|
BoxError, Error, FromRequest, HttpResponse, Responder,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// *Resource* is an entry in resources table which corresponds to requested URL.
|
/// *Resource* is an entry in resources table which corresponds to requested URL.
|
||||||
|
@ -526,7 +525,7 @@ mod tests {
|
||||||
.name("test")
|
.name("test")
|
||||||
.wrap(
|
.wrap(
|
||||||
DefaultHeaders::new()
|
DefaultHeaders::new()
|
||||||
.header(header::CONTENT_TYPE, HeaderValue::from_static("0001")),
|
.insert((header::CONTENT_TYPE, HeaderValue::from_static("0001"))),
|
||||||
)
|
)
|
||||||
.route(web::get().to(HttpResponse::Ok)),
|
.route(web::get().to(HttpResponse::Ok)),
|
||||||
),
|
),
|
||||||
|
|
|
@ -9,7 +9,7 @@ use std::{
|
||||||
use actix_http::{
|
use actix_http::{
|
||||||
body::{BodyStream, BoxBody, MessageBody},
|
body::{BodyStream, BoxBody, MessageBody},
|
||||||
error::HttpError,
|
error::HttpError,
|
||||||
header::{self, HeaderName, IntoHeaderPair, IntoHeaderValue},
|
header::{self, HeaderName, IntoHeaderValue, TryIntoHeaderPair},
|
||||||
ConnectionType, Extensions, Response, ResponseHead, StatusCode,
|
ConnectionType, Extensions, Response, ResponseHead, StatusCode,
|
||||||
};
|
};
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
|
@ -67,10 +67,7 @@ impl HttpResponseBuilder {
|
||||||
/// .insert_header(("X-TEST", "value"))
|
/// .insert_header(("X-TEST", "value"))
|
||||||
/// .finish();
|
/// .finish();
|
||||||
/// ```
|
/// ```
|
||||||
pub fn insert_header<H>(&mut self, header: H) -> &mut Self
|
pub fn insert_header(&mut self, header: impl TryIntoHeaderPair) -> &mut Self {
|
||||||
where
|
|
||||||
H: IntoHeaderPair,
|
|
||||||
{
|
|
||||||
if let Some(parts) = self.inner() {
|
if let Some(parts) = self.inner() {
|
||||||
match header.try_into_header_pair() {
|
match header.try_into_header_pair() {
|
||||||
Ok((key, value)) => {
|
Ok((key, value)) => {
|
||||||
|
@ -94,10 +91,7 @@ impl HttpResponseBuilder {
|
||||||
/// .append_header(("X-TEST", "value2"))
|
/// .append_header(("X-TEST", "value2"))
|
||||||
/// .finish();
|
/// .finish();
|
||||||
/// ```
|
/// ```
|
||||||
pub fn append_header<H>(&mut self, header: H) -> &mut Self
|
pub fn append_header(&mut self, header: impl TryIntoHeaderPair) -> &mut Self {
|
||||||
where
|
|
||||||
H: IntoHeaderPair,
|
|
||||||
{
|
|
||||||
if let Some(parts) = self.inner() {
|
if let Some(parts) = self.inner() {
|
||||||
match header.try_into_header_pair() {
|
match header.try_into_header_pair() {
|
||||||
Ok((key, value)) => parts.headers.append(key, value),
|
Ok((key, value)) => parts.headers.append(key, value),
|
||||||
|
|
|
@ -0,0 +1,219 @@
|
||||||
|
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.
|
||||||
|
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.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// 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 {
|
||||||
|
if let Some(inner) = self.inner() {
|
||||||
|
inner.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 insert_header(mut self, header: impl TryIntoHeaderPair) -> Self {
|
||||||
|
if let Some(inner) = self.inner() {
|
||||||
|
match header.try_into_header_pair() {
|
||||||
|
Ok((key, value)) => {
|
||||||
|
inner.override_headers.insert(key, value);
|
||||||
|
}
|
||||||
|
Err(err) => self.error = Some(err.into()),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn append_header(mut self, header: impl TryIntoHeaderPair) -> Self {
|
||||||
|
if let Some(inner) = self.inner() {
|
||||||
|
match header.try_into_header_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"),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,9 +1,13 @@
|
||||||
mod builder;
|
mod builder;
|
||||||
|
mod customize_responder;
|
||||||
mod http_codes;
|
mod http_codes;
|
||||||
|
mod responder;
|
||||||
#[allow(clippy::module_inception)]
|
#[allow(clippy::module_inception)]
|
||||||
mod response;
|
mod response;
|
||||||
|
|
||||||
pub use self::builder::HttpResponseBuilder;
|
pub use self::builder::HttpResponseBuilder;
|
||||||
|
pub use self::customize_responder::CustomizeResponder;
|
||||||
|
pub use self::responder::Responder;
|
||||||
pub use self::response::HttpResponse;
|
pub use self::response::HttpResponse;
|
||||||
|
|
||||||
#[cfg(feature = "cookies")]
|
#[cfg(feature = "cookies")]
|
||||||
|
|
|
@ -2,64 +2,59 @@ use std::borrow::Cow;
|
||||||
|
|
||||||
use actix_http::{
|
use actix_http::{
|
||||||
body::{BoxBody, EitherBody, MessageBody},
|
body::{BoxBody, EitherBody, MessageBody},
|
||||||
error::HttpError,
|
header::TryIntoHeaderPair,
|
||||||
header::HeaderMap,
|
|
||||||
header::IntoHeaderPair,
|
|
||||||
StatusCode,
|
StatusCode,
|
||||||
};
|
};
|
||||||
use bytes::{Bytes, BytesMut};
|
use bytes::{Bytes, BytesMut};
|
||||||
|
|
||||||
use crate::{BoxError, Error, HttpRequest, HttpResponse, HttpResponseBuilder};
|
use crate::{BoxError, Error, HttpRequest, HttpResponse, HttpResponseBuilder};
|
||||||
|
|
||||||
|
use super::CustomizeResponder;
|
||||||
|
|
||||||
/// Trait implemented by types that can be converted to an HTTP response.
|
/// 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.
|
/// 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 {
|
pub trait Responder {
|
||||||
type Body: MessageBody + 'static;
|
type Body: MessageBody + 'static;
|
||||||
|
|
||||||
/// Convert self to `HttpResponse`.
|
/// Convert self to `HttpResponse`.
|
||||||
fn respond_to(self, req: &HttpRequest) -> HttpResponse<Self::Body>;
|
fn respond_to(self, req: &HttpRequest) -> HttpResponse<Self::Body>;
|
||||||
|
|
||||||
/// Override a status code for a Responder.
|
/// Wraps responder in [`CustomizeResponder`] that allows modification of response details.
|
||||||
///
|
///
|
||||||
/// ```
|
/// See [`CustomizeResponder`] docs for more.
|
||||||
/// use actix_web::{http::StatusCode, HttpRequest, Responder};
|
|
||||||
///
|
///
|
||||||
/// fn index(req: HttpRequest) -> impl Responder {
|
/// # Examples
|
||||||
/// "Welcome!".with_status(StatusCode::OK)
|
|
||||||
/// }
|
|
||||||
/// ```
|
/// ```
|
||||||
fn with_status(self, status: StatusCode) -> CustomResponder<Self>
|
/// use actix_web::{Responder, test};
|
||||||
|
///
|
||||||
|
/// let request = test::TestRequest::default().to_http_request();
|
||||||
|
///
|
||||||
|
/// let responder = "Hello world!"
|
||||||
|
/// .customize()
|
||||||
|
/// .with_status(400)
|
||||||
|
/// .insert_header(("x-hello", "world"))
|
||||||
|
///
|
||||||
|
/// let response = res.respond_to(&req);
|
||||||
|
/// assert_eq!(res.status(), 400);
|
||||||
|
/// assert_eq!(res.headers().get("x-hello").unwrap(), "world");
|
||||||
|
/// ```
|
||||||
|
#[inline]
|
||||||
|
fn customize(self) -> CustomizeResponder<Self>
|
||||||
where
|
where
|
||||||
Self: Sized,
|
Self: Sized,
|
||||||
{
|
{
|
||||||
CustomResponder::new(self).with_status(status)
|
CustomizeResponder::new(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Insert header to the final response.
|
#[doc(hidden)]
|
||||||
///
|
#[deprecated(since = "4.0.0", note = "Prefer `.customize().insert_header()`.")]
|
||||||
/// Overrides other headers with the same name.
|
fn with_header(self, header: impl TryIntoHeaderPair) -> CustomizeResponder<Self>
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// 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>
|
|
||||||
where
|
where
|
||||||
Self: Sized,
|
Self: Sized,
|
||||||
H: IntoHeaderPair,
|
|
||||||
{
|
{
|
||||||
CustomResponder::new(self).with_header(header)
|
self.customize().insert_header(header)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -181,98 +176,6 @@ macro_rules! impl_into_string_responder {
|
||||||
impl_into_string_responder!(&'_ String);
|
impl_into_string_responder!(&'_ String);
|
||||||
impl_into_string_responder!(Cow<'_, str>);
|
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)]
|
#[cfg(test)]
|
||||||
pub(crate) mod tests {
|
pub(crate) mod tests {
|
||||||
use actix_service::Service;
|
use actix_service::Service;
|
||||||
|
@ -440,59 +343,4 @@ pub(crate) mod tests {
|
||||||
|
|
||||||
assert_eq!(res.status(), StatusCode::BAD_REQUEST);
|
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"),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -935,7 +935,7 @@ mod tests {
|
||||||
web::scope("app")
|
web::scope("app")
|
||||||
.wrap(
|
.wrap(
|
||||||
DefaultHeaders::new()
|
DefaultHeaders::new()
|
||||||
.header(header::CONTENT_TYPE, HeaderValue::from_static("0001")),
|
.insert((header::CONTENT_TYPE, HeaderValue::from_static("0001"))),
|
||||||
)
|
)
|
||||||
.service(web::resource("/test").route(web::get().to(HttpResponse::Ok))),
|
.service(web::resource("/test").route(web::get().to(HttpResponse::Ok))),
|
||||||
),
|
),
|
||||||
|
|
14
src/test.rs
14
src/test.rs
|
@ -4,8 +4,8 @@ use std::{borrow::Cow, net::SocketAddr, rc::Rc};
|
||||||
|
|
||||||
pub use actix_http::test::TestBuffer;
|
pub use actix_http::test::TestBuffer;
|
||||||
use actix_http::{
|
use actix_http::{
|
||||||
header::IntoHeaderPair, test::TestRequest as HttpTestRequest, Extensions, Method, Request,
|
header::TryIntoHeaderPair, test::TestRequest as HttpTestRequest, Extensions, Method,
|
||||||
StatusCode, Uri, Version,
|
Request, StatusCode, Uri, Version,
|
||||||
};
|
};
|
||||||
use actix_router::{Path, ResourceDef, Url};
|
use actix_router::{Path, ResourceDef, Url};
|
||||||
use actix_service::{IntoService, IntoServiceFactory, Service, ServiceFactory};
|
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.
|
/// Insert a header, replacing any that were set with an equivalent field name.
|
||||||
pub fn insert_header<H>(mut self, header: H) -> Self
|
pub fn insert_header(mut self, header: impl TryIntoHeaderPair) -> Self {
|
||||||
where
|
|
||||||
H: IntoHeaderPair,
|
|
||||||
{
|
|
||||||
self.req.insert_header(header);
|
self.req.insert_header(header);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Append a header, keeping any that were set with an equivalent field name.
|
/// Append a header, keeping any that were set with an equivalent field name.
|
||||||
pub fn append_header<H>(mut self, header: H) -> Self
|
pub fn append_header(mut self, header: impl TryIntoHeaderPair) -> Self {
|
||||||
where
|
|
||||||
H: IntoHeaderPair,
|
|
||||||
{
|
|
||||||
self.req.append_header(header);
|
self.req.append_header(header);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,7 @@ pub use bytes::{Buf, BufMut, Bytes, BytesMut};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
body::MessageBody, error::BlockingError, extract::FromRequest, handler::Handler,
|
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;
|
pub use crate::config::ServiceConfig;
|
||||||
|
|
Loading…
Reference in New Issue