mirror of
https://github.com/actix/actix-web.git
synced 2024-11-26 03:21:08 +00:00
align remaining header map terminology (#2510)
This commit is contained in:
parent
551a0d973c
commit
11ee8ec3ab
47 changed files with 608 additions and 503 deletions
11
CHANGES.md
11
CHANGES.md
|
@ -1,6 +1,17 @@
|
||||||
# Changes
|
# Changes
|
||||||
|
|
||||||
## Unreleased - 2021-xx-xx
|
## Unreleased - 2021-xx-xx
|
||||||
|
### Added
|
||||||
|
* Method on `Responder` trait (`customize`) for customizing responders and `CustomizeResponder` struct. [#2510]
|
||||||
|
* Implement `Debug` for `DefaultHeaders`. [#2510]
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
* Align `DefaultHeader` method terminology, deprecating previous methods. [#2510]
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
* Top-level `EitherExtractError` export. [#2510]
|
||||||
|
|
||||||
|
[#2510]: https://github.com/actix/actix-web/pull/2510
|
||||||
|
|
||||||
|
|
||||||
## 4.0.0-beta.14 - 2021-12-11
|
## 4.0.0-beta.14 - 2021-12-11
|
||||||
|
|
|
@ -2,14 +2,10 @@ use std::{
|
||||||
fmt,
|
fmt,
|
||||||
fs::Metadata,
|
fs::Metadata,
|
||||||
io,
|
io,
|
||||||
ops::{Deref, DerefMut},
|
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
time::{SystemTime, UNIX_EPOCH},
|
time::{SystemTime, UNIX_EPOCH},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(unix)]
|
|
||||||
use std::os::unix::fs::MetadataExt;
|
|
||||||
|
|
||||||
use actix_service::{Service, ServiceFactory};
|
use actix_service::{Service, ServiceFactory};
|
||||||
use actix_web::{
|
use actix_web::{
|
||||||
body::{self, BoxBody, SizedStream},
|
body::{self, BoxBody, SizedStream},
|
||||||
|
@ -27,6 +23,7 @@ use actix_web::{
|
||||||
Error, HttpMessage, HttpRequest, HttpResponse, Responder,
|
Error, HttpMessage, HttpRequest, HttpResponse, Responder,
|
||||||
};
|
};
|
||||||
use bitflags::bitflags;
|
use bitflags::bitflags;
|
||||||
|
use derive_more::{Deref, DerefMut};
|
||||||
use futures_core::future::LocalBoxFuture;
|
use futures_core::future::LocalBoxFuture;
|
||||||
use mime_guess::from_path;
|
use mime_guess::from_path;
|
||||||
|
|
||||||
|
@ -71,8 +68,11 @@ impl Default for Flags {
|
||||||
/// NamedFile::open_async("./static/index.html").await
|
/// NamedFile::open_async("./static/index.html").await
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
|
#[derive(Deref, DerefMut)]
|
||||||
pub struct NamedFile {
|
pub struct NamedFile {
|
||||||
path: PathBuf,
|
path: PathBuf,
|
||||||
|
#[deref]
|
||||||
|
#[deref_mut]
|
||||||
file: File,
|
file: File,
|
||||||
modified: Option<SystemTime>,
|
modified: Option<SystemTime>,
|
||||||
pub(crate) md: Metadata,
|
pub(crate) md: Metadata,
|
||||||
|
@ -364,14 +364,18 @@ impl NamedFile {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Creates a etag in a format is similar to Apache's.
|
||||||
pub(crate) fn etag(&self) -> Option<header::EntityTag> {
|
pub(crate) fn etag(&self) -> Option<header::EntityTag> {
|
||||||
// This etag format is similar to Apache's.
|
|
||||||
self.modified.as_ref().map(|mtime| {
|
self.modified.as_ref().map(|mtime| {
|
||||||
let ino = {
|
let ino = {
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
{
|
{
|
||||||
|
#[cfg(unix)]
|
||||||
|
use std::os::unix::fs::MetadataExt as _;
|
||||||
|
|
||||||
self.md.ino()
|
self.md.ino()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(unix))]
|
#[cfg(not(unix))]
|
||||||
{
|
{
|
||||||
0
|
0
|
||||||
|
@ -472,17 +476,17 @@ impl NamedFile {
|
||||||
false
|
false
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut resp = HttpResponse::build(self.status_code);
|
let mut res = HttpResponse::build(self.status_code);
|
||||||
|
|
||||||
if self.flags.contains(Flags::PREFER_UTF8) {
|
if self.flags.contains(Flags::PREFER_UTF8) {
|
||||||
let ct = equiv_utf8_text(self.content_type.clone());
|
let ct = equiv_utf8_text(self.content_type.clone());
|
||||||
resp.insert_header((header::CONTENT_TYPE, ct.to_string()));
|
res.insert_header((header::CONTENT_TYPE, ct.to_string()));
|
||||||
} else {
|
} else {
|
||||||
resp.insert_header((header::CONTENT_TYPE, self.content_type.to_string()));
|
res.insert_header((header::CONTENT_TYPE, self.content_type.to_string()));
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.flags.contains(Flags::CONTENT_DISPOSITION) {
|
if self.flags.contains(Flags::CONTENT_DISPOSITION) {
|
||||||
resp.insert_header((
|
res.insert_header((
|
||||||
header::CONTENT_DISPOSITION,
|
header::CONTENT_DISPOSITION,
|
||||||
self.content_disposition.to_string(),
|
self.content_disposition.to_string(),
|
||||||
));
|
));
|
||||||
|
@ -490,18 +494,18 @@ impl NamedFile {
|
||||||
|
|
||||||
// default compressing
|
// default compressing
|
||||||
if let Some(current_encoding) = self.encoding {
|
if let Some(current_encoding) = self.encoding {
|
||||||
resp.encoding(current_encoding);
|
res.encoding(current_encoding);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(lm) = last_modified {
|
if let Some(lm) = last_modified {
|
||||||
resp.insert_header((header::LAST_MODIFIED, lm.to_string()));
|
res.insert_header((header::LAST_MODIFIED, lm.to_string()));
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(etag) = etag {
|
if let Some(etag) = etag {
|
||||||
resp.insert_header((header::ETAG, etag.to_string()));
|
res.insert_header((header::ETAG, etag.to_string()));
|
||||||
}
|
}
|
||||||
|
|
||||||
resp.insert_header((header::ACCEPT_RANGES, "bytes"));
|
res.insert_header((header::ACCEPT_RANGES, "bytes"));
|
||||||
|
|
||||||
let mut length = self.md.len();
|
let mut length = self.md.len();
|
||||||
let mut offset = 0;
|
let mut offset = 0;
|
||||||
|
@ -513,24 +517,24 @@ impl NamedFile {
|
||||||
length = ranges[0].length;
|
length = ranges[0].length;
|
||||||
offset = ranges[0].start;
|
offset = ranges[0].start;
|
||||||
|
|
||||||
resp.encoding(ContentEncoding::Identity);
|
res.encoding(ContentEncoding::Identity);
|
||||||
resp.insert_header((
|
res.insert_header((
|
||||||
header::CONTENT_RANGE,
|
header::CONTENT_RANGE,
|
||||||
format!("bytes {}-{}/{}", offset, offset + length - 1, self.md.len()),
|
format!("bytes {}-{}/{}", offset, offset + length - 1, self.md.len()),
|
||||||
));
|
));
|
||||||
} else {
|
} else {
|
||||||
resp.insert_header((header::CONTENT_RANGE, format!("bytes */{}", length)));
|
res.insert_header((header::CONTENT_RANGE, format!("bytes */{}", length)));
|
||||||
return resp.status(StatusCode::RANGE_NOT_SATISFIABLE).finish();
|
return res.status(StatusCode::RANGE_NOT_SATISFIABLE).finish();
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
return resp.status(StatusCode::BAD_REQUEST).finish();
|
return res.status(StatusCode::BAD_REQUEST).finish();
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
if precondition_failed {
|
if precondition_failed {
|
||||||
return resp.status(StatusCode::PRECONDITION_FAILED).finish();
|
return res.status(StatusCode::PRECONDITION_FAILED).finish();
|
||||||
} else if not_modified {
|
} else if not_modified {
|
||||||
return resp
|
return res
|
||||||
.status(StatusCode::NOT_MODIFIED)
|
.status(StatusCode::NOT_MODIFIED)
|
||||||
.body(body::None::new())
|
.body(body::None::new())
|
||||||
.map_into_boxed_body();
|
.map_into_boxed_body();
|
||||||
|
@ -539,10 +543,10 @@ impl NamedFile {
|
||||||
let reader = chunked::new_chunked_read(length, offset, self.file);
|
let reader = chunked::new_chunked_read(length, offset, self.file);
|
||||||
|
|
||||||
if offset != 0 || length != self.md.len() {
|
if offset != 0 || length != self.md.len() {
|
||||||
resp.status(StatusCode::PARTIAL_CONTENT);
|
res.status(StatusCode::PARTIAL_CONTENT);
|
||||||
}
|
}
|
||||||
|
|
||||||
resp.body(SizedStream::new(length, reader))
|
res.body(SizedStream::new(length, reader))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -586,20 +590,6 @@ fn none_match(etag: Option<&header::EntityTag>, req: &HttpRequest) -> bool {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Deref for NamedFile {
|
|
||||||
type Target = File;
|
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
|
||||||
&self.file
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DerefMut for NamedFile {
|
|
||||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
|
||||||
&mut self.file
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Responder for NamedFile {
|
impl Responder for NamedFile {
|
||||||
type Body = BoxBody;
|
type Body = BoxBody;
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,12 @@
|
||||||
# Changes
|
# Changes
|
||||||
|
|
||||||
## Unreleased - 2021-xx-xx
|
## Unreleased - 2021-xx-xx
|
||||||
|
### Changed
|
||||||
|
* Rename trait `IntoHeaderPair => TryIntoHeaderPair`. [#2510]
|
||||||
|
* Rename `TryIntoHeaderPair::{try_into_header_pair => try_into_pair}`. [#2510]
|
||||||
|
* Rename trait `IntoHeaderValue => TryIntoHeaderValue`. [#2510]
|
||||||
|
|
||||||
|
[#2510]: https://github.com/actix/actix-web/pull/2510
|
||||||
|
|
||||||
|
|
||||||
## 3.0.0-beta.15 - 2021-12-11
|
## 3.0.0-beta.15 - 2021-12-11
|
||||||
|
@ -260,7 +266,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]
|
||||||
|
@ -271,9 +277,9 @@
|
||||||
* `trust-dns` optional feature to enable `trust-dns-resolver` as client dns resolver. [#1969]
|
* `trust-dns` optional feature to enable `trust-dns-resolver` as client dns resolver. [#1969]
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
* `ResponseBuilder::content_type` now takes an `impl IntoHeaderValue` to support using typed
|
* `ResponseBuilder::content_type` now takes an `impl TryIntoHeaderValue` to support using typed
|
||||||
`mime` types. [#1894]
|
`mime` types. [#1894]
|
||||||
* Renamed `IntoHeaderValue::{try_into => try_into_value}` to avoid ambiguity with std
|
* Renamed `TryIntoHeaderValue::{try_into => try_into_value}` to avoid ambiguity with std
|
||||||
`TryInto` trait. [#1894]
|
`TryInto` trait. [#1894]
|
||||||
* `Extensions::insert` returns Option of replaced item. [#1904]
|
* `Extensions::insert` returns Option of replaced item. [#1904]
|
||||||
* Remove `HttpResponseBuilder::json2()`. [#1903]
|
* Remove `HttpResponseBuilder::json2()`. [#1903]
|
||||||
|
|
|
@ -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),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,7 @@ pub trait Sealed {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Sealed for HeaderName {
|
impl Sealed for HeaderName {
|
||||||
|
#[inline]
|
||||||
fn try_as_name(&self, _: Seal) -> Result<Cow<'_, HeaderName>, InvalidHeaderName> {
|
fn try_as_name(&self, _: Seal) -> Result<Cow<'_, HeaderName>, InvalidHeaderName> {
|
||||||
Ok(Cow::Borrowed(self))
|
Ok(Cow::Borrowed(self))
|
||||||
}
|
}
|
||||||
|
@ -23,6 +24,7 @@ impl Sealed for HeaderName {
|
||||||
impl AsHeaderName for HeaderName {}
|
impl AsHeaderName for HeaderName {}
|
||||||
|
|
||||||
impl Sealed for &HeaderName {
|
impl Sealed for &HeaderName {
|
||||||
|
#[inline]
|
||||||
fn try_as_name(&self, _: Seal) -> Result<Cow<'_, HeaderName>, InvalidHeaderName> {
|
fn try_as_name(&self, _: Seal) -> Result<Cow<'_, HeaderName>, InvalidHeaderName> {
|
||||||
Ok(Cow::Borrowed(*self))
|
Ok(Cow::Borrowed(*self))
|
||||||
}
|
}
|
||||||
|
@ -30,6 +32,7 @@ impl Sealed for &HeaderName {
|
||||||
impl AsHeaderName for &HeaderName {}
|
impl AsHeaderName for &HeaderName {}
|
||||||
|
|
||||||
impl Sealed for &str {
|
impl Sealed for &str {
|
||||||
|
#[inline]
|
||||||
fn try_as_name(&self, _: Seal) -> Result<Cow<'_, HeaderName>, InvalidHeaderName> {
|
fn try_as_name(&self, _: Seal) -> Result<Cow<'_, HeaderName>, InvalidHeaderName> {
|
||||||
HeaderName::from_str(self).map(Cow::Owned)
|
HeaderName::from_str(self).map(Cow::Owned)
|
||||||
}
|
}
|
||||||
|
@ -37,6 +40,7 @@ impl Sealed for &str {
|
||||||
impl AsHeaderName for &str {}
|
impl AsHeaderName for &str {}
|
||||||
|
|
||||||
impl Sealed for String {
|
impl Sealed for String {
|
||||||
|
#[inline]
|
||||||
fn try_as_name(&self, _: Seal) -> Result<Cow<'_, HeaderName>, InvalidHeaderName> {
|
fn try_as_name(&self, _: Seal) -> Result<Cow<'_, HeaderName>, InvalidHeaderName> {
|
||||||
HeaderName::from_str(self).map(Cow::Owned)
|
HeaderName::from_str(self).map(Cow::Owned)
|
||||||
}
|
}
|
||||||
|
@ -44,6 +48,7 @@ impl Sealed for String {
|
||||||
impl AsHeaderName for String {}
|
impl AsHeaderName for String {}
|
||||||
|
|
||||||
impl Sealed for &String {
|
impl Sealed for &String {
|
||||||
|
#[inline]
|
||||||
fn try_as_name(&self, _: Seal) -> Result<Cow<'_, HeaderName>, InvalidHeaderName> {
|
fn try_as_name(&self, _: Seal) -> Result<Cow<'_, HeaderName>, InvalidHeaderName> {
|
||||||
HeaderName::from_str(self).map(Cow::Owned)
|
HeaderName::from_str(self).map(Cow::Owned)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,22 +1,20 @@
|
||||||
//! [`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, InvalidHeaderName, InvalidHeaderValue, TryIntoHeaderValue,
|
||||||
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_pair(self) -> Result<(HeaderName, HeaderValue), Self::Error>;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -34,14 +32,14 @@ impl From<InvalidHeaderPart> for HttpError {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<V> IntoHeaderPair for (HeaderName, V)
|
impl<V> TryIntoHeaderPair for (HeaderName, V)
|
||||||
where
|
where
|
||||||
V: IntoHeaderValue,
|
V: TryIntoHeaderValue,
|
||||||
V::Error: Into<InvalidHeaderValue>,
|
V::Error: Into<InvalidHeaderValue>,
|
||||||
{
|
{
|
||||||
type Error = InvalidHeaderPart;
|
type Error = InvalidHeaderPart;
|
||||||
|
|
||||||
fn try_into_header_pair(self) -> Result<(HeaderName, HeaderValue), Self::Error> {
|
fn try_into_pair(self) -> Result<(HeaderName, HeaderValue), Self::Error> {
|
||||||
let (name, value) = self;
|
let (name, value) = self;
|
||||||
let value = value
|
let value = value
|
||||||
.try_into_value()
|
.try_into_value()
|
||||||
|
@ -50,14 +48,14 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<V> IntoHeaderPair for (&HeaderName, V)
|
impl<V> TryIntoHeaderPair for (&HeaderName, V)
|
||||||
where
|
where
|
||||||
V: IntoHeaderValue,
|
V: TryIntoHeaderValue,
|
||||||
V::Error: Into<InvalidHeaderValue>,
|
V::Error: Into<InvalidHeaderValue>,
|
||||||
{
|
{
|
||||||
type Error = InvalidHeaderPart;
|
type Error = InvalidHeaderPart;
|
||||||
|
|
||||||
fn try_into_header_pair(self) -> Result<(HeaderName, HeaderValue), Self::Error> {
|
fn try_into_pair(self) -> Result<(HeaderName, HeaderValue), Self::Error> {
|
||||||
let (name, value) = self;
|
let (name, value) = self;
|
||||||
let value = value
|
let value = value
|
||||||
.try_into_value()
|
.try_into_value()
|
||||||
|
@ -66,14 +64,14 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<V> IntoHeaderPair for (&[u8], V)
|
impl<V> TryIntoHeaderPair for (&[u8], V)
|
||||||
where
|
where
|
||||||
V: IntoHeaderValue,
|
V: TryIntoHeaderValue,
|
||||||
V::Error: Into<InvalidHeaderValue>,
|
V::Error: Into<InvalidHeaderValue>,
|
||||||
{
|
{
|
||||||
type Error = InvalidHeaderPart;
|
type Error = InvalidHeaderPart;
|
||||||
|
|
||||||
fn try_into_header_pair(self) -> Result<(HeaderName, HeaderValue), Self::Error> {
|
fn try_into_pair(self) -> Result<(HeaderName, HeaderValue), Self::Error> {
|
||||||
let (name, value) = self;
|
let (name, value) = self;
|
||||||
let name = HeaderName::try_from(name).map_err(InvalidHeaderPart::Name)?;
|
let name = HeaderName::try_from(name).map_err(InvalidHeaderPart::Name)?;
|
||||||
let value = value
|
let value = value
|
||||||
|
@ -83,14 +81,14 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<V> IntoHeaderPair for (&str, V)
|
impl<V> TryIntoHeaderPair for (&str, V)
|
||||||
where
|
where
|
||||||
V: IntoHeaderValue,
|
V: TryIntoHeaderValue,
|
||||||
V::Error: Into<InvalidHeaderValue>,
|
V::Error: Into<InvalidHeaderValue>,
|
||||||
{
|
{
|
||||||
type Error = InvalidHeaderPart;
|
type Error = InvalidHeaderPart;
|
||||||
|
|
||||||
fn try_into_header_pair(self) -> Result<(HeaderName, HeaderValue), Self::Error> {
|
fn try_into_pair(self) -> Result<(HeaderName, HeaderValue), Self::Error> {
|
||||||
let (name, value) = self;
|
let (name, value) = self;
|
||||||
let name = HeaderName::try_from(name).map_err(InvalidHeaderPart::Name)?;
|
let name = HeaderName::try_from(name).map_err(InvalidHeaderPart::Name)?;
|
||||||
let value = value
|
let value = value
|
||||||
|
@ -100,23 +98,25 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<V> IntoHeaderPair for (String, V)
|
impl<V> TryIntoHeaderPair for (String, V)
|
||||||
where
|
where
|
||||||
V: IntoHeaderValue,
|
V: TryIntoHeaderValue,
|
||||||
V::Error: Into<InvalidHeaderValue>,
|
V::Error: Into<InvalidHeaderValue>,
|
||||||
{
|
{
|
||||||
type Error = InvalidHeaderPart;
|
type Error = InvalidHeaderPart;
|
||||||
|
|
||||||
fn try_into_header_pair(self) -> Result<(HeaderName, HeaderValue), Self::Error> {
|
#[inline]
|
||||||
|
fn try_into_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_pair()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Header> IntoHeaderPair for T {
|
impl<T: Header> TryIntoHeaderPair for T {
|
||||||
type Error = <T as IntoHeaderValue>::Error;
|
type Error = <T as TryIntoHeaderValue>::Error;
|
||||||
|
|
||||||
fn try_into_header_pair(self) -> Result<(HeaderName, HeaderValue), Self::Error> {
|
#[inline]
|
||||||
|
fn try_into_pair(self) -> Result<(HeaderName, HeaderValue), Self::Error> {
|
||||||
Ok((T::name(), self.try_into_value()?))
|
Ok((T::name(), self.try_into_value()?))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
//! [`IntoHeaderValue`] trait and implementations.
|
//! [`TryIntoHeaderValue`] trait and implementations.
|
||||||
|
|
||||||
use std::convert::TryFrom as _;
|
use std::convert::TryFrom as _;
|
||||||
|
|
||||||
|
@ -7,7 +7,7 @@ use http::{header::InvalidHeaderValue, Error as HttpError, HeaderValue};
|
||||||
use mime::Mime;
|
use mime::Mime;
|
||||||
|
|
||||||
/// An interface for types that can be converted into a [`HeaderValue`].
|
/// An interface for types that can be converted into a [`HeaderValue`].
|
||||||
pub trait IntoHeaderValue: Sized {
|
pub trait TryIntoHeaderValue: Sized {
|
||||||
/// The type returned in the event of a conversion error.
|
/// The type returned in the event of a conversion error.
|
||||||
type Error: Into<HttpError>;
|
type Error: Into<HttpError>;
|
||||||
|
|
||||||
|
@ -15,7 +15,7 @@ pub trait IntoHeaderValue: Sized {
|
||||||
fn try_into_value(self) -> Result<HeaderValue, Self::Error>;
|
fn try_into_value(self) -> Result<HeaderValue, Self::Error>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IntoHeaderValue for HeaderValue {
|
impl TryIntoHeaderValue for HeaderValue {
|
||||||
type Error = InvalidHeaderValue;
|
type Error = InvalidHeaderValue;
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
@ -24,7 +24,7 @@ impl IntoHeaderValue for HeaderValue {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IntoHeaderValue for &HeaderValue {
|
impl TryIntoHeaderValue for &HeaderValue {
|
||||||
type Error = InvalidHeaderValue;
|
type Error = InvalidHeaderValue;
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
@ -33,7 +33,7 @@ impl IntoHeaderValue for &HeaderValue {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IntoHeaderValue for &str {
|
impl TryIntoHeaderValue for &str {
|
||||||
type Error = InvalidHeaderValue;
|
type Error = InvalidHeaderValue;
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
@ -42,7 +42,7 @@ impl IntoHeaderValue for &str {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IntoHeaderValue for &[u8] {
|
impl TryIntoHeaderValue for &[u8] {
|
||||||
type Error = InvalidHeaderValue;
|
type Error = InvalidHeaderValue;
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
@ -51,7 +51,7 @@ impl IntoHeaderValue for &[u8] {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IntoHeaderValue for Bytes {
|
impl TryIntoHeaderValue for Bytes {
|
||||||
type Error = InvalidHeaderValue;
|
type Error = InvalidHeaderValue;
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
@ -60,7 +60,7 @@ impl IntoHeaderValue for Bytes {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IntoHeaderValue for Vec<u8> {
|
impl TryIntoHeaderValue for Vec<u8> {
|
||||||
type Error = InvalidHeaderValue;
|
type Error = InvalidHeaderValue;
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
@ -69,7 +69,7 @@ impl IntoHeaderValue for Vec<u8> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IntoHeaderValue for String {
|
impl TryIntoHeaderValue for String {
|
||||||
type Error = InvalidHeaderValue;
|
type Error = InvalidHeaderValue;
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
@ -78,7 +78,7 @@ impl IntoHeaderValue for String {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IntoHeaderValue for usize {
|
impl TryIntoHeaderValue for usize {
|
||||||
type Error = InvalidHeaderValue;
|
type Error = InvalidHeaderValue;
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
@ -87,7 +87,7 @@ impl IntoHeaderValue for usize {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IntoHeaderValue for i64 {
|
impl TryIntoHeaderValue for i64 {
|
||||||
type Error = InvalidHeaderValue;
|
type Error = InvalidHeaderValue;
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
@ -96,7 +96,7 @@ impl IntoHeaderValue for i64 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IntoHeaderValue for u64 {
|
impl TryIntoHeaderValue for u64 {
|
||||||
type Error = InvalidHeaderValue;
|
type Error = InvalidHeaderValue;
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
@ -105,7 +105,7 @@ impl IntoHeaderValue for u64 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IntoHeaderValue for i32 {
|
impl TryIntoHeaderValue for i32 {
|
||||||
type Error = InvalidHeaderValue;
|
type Error = InvalidHeaderValue;
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
@ -114,7 +114,7 @@ impl IntoHeaderValue for i32 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IntoHeaderValue for u32 {
|
impl TryIntoHeaderValue for u32 {
|
||||||
type Error = InvalidHeaderValue;
|
type Error = InvalidHeaderValue;
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
@ -123,7 +123,7 @@ impl IntoHeaderValue for u32 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IntoHeaderValue for Mime {
|
impl TryIntoHeaderValue for Mime {
|
||||||
type Error = InvalidHeaderValue;
|
type Error = InvalidHeaderValue;
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
|
|
@ -333,7 +333,7 @@ impl HeaderMap {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Inserts a name-value pair into the map.
|
/// Inserts (overrides) a name-value pair in the map.
|
||||||
///
|
///
|
||||||
/// If the map already contained this key, the new value is associated with the key and all
|
/// If the map already contained this key, the new value is associated with the key and all
|
||||||
/// previous values are removed and returned as a `Removed` iterator. The key is not updated;
|
/// previous values are removed and returned as a `Removed` iterator. The key is not updated;
|
||||||
|
@ -372,7 +372,7 @@ impl HeaderMap {
|
||||||
Removed::new(value)
|
Removed::new(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Inserts a name-value pair into the map.
|
/// Appends a name-value pair to the map.
|
||||||
///
|
///
|
||||||
/// If the map already contained this key, the new value is added to the list of values
|
/// If the map already contained this key, the new value is added to the list of values
|
||||||
/// currently associated with the key. The key is not updated; this matters for types that can
|
/// currently associated with the key. The key is not updated; this matters for types that can
|
||||||
|
|
|
@ -37,8 +37,8 @@ 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::TryIntoHeaderValue;
|
||||||
pub use self::map::HeaderMap;
|
pub use self::map::HeaderMap;
|
||||||
pub use self::shared::{
|
pub use self::shared::{
|
||||||
parse_extended_value, q, Charset, ContentEncoding, ExtendedValue, HttpDate, LanguageTag,
|
parse_extended_value, q, Charset, ContentEncoding, ExtendedValue, HttpDate, LanguageTag,
|
||||||
|
@ -49,7 +49,7 @@ pub use self::utils::{
|
||||||
};
|
};
|
||||||
|
|
||||||
/// An interface for types that already represent a valid header.
|
/// An interface for types that already represent a valid header.
|
||||||
pub trait Header: IntoHeaderValue {
|
pub trait Header: TryIntoHeaderValue {
|
||||||
/// Returns the name of the header field
|
/// Returns the name of the header field
|
||||||
fn name() -> HeaderName;
|
fn name() -> HeaderName;
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@ use http::header::InvalidHeaderValue;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
error::ParseError,
|
error::ParseError,
|
||||||
header::{self, from_one_raw_str, Header, HeaderName, HeaderValue, IntoHeaderValue},
|
header::{self, from_one_raw_str, Header, HeaderName, HeaderValue, TryIntoHeaderValue},
|
||||||
HttpMessage,
|
HttpMessage,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -96,7 +96,7 @@ impl TryFrom<&str> for ContentEncoding {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IntoHeaderValue for ContentEncoding {
|
impl TryIntoHeaderValue for ContentEncoding {
|
||||||
type Error = InvalidHeaderValue;
|
type Error = InvalidHeaderValue;
|
||||||
|
|
||||||
fn try_into_value(self) -> Result<http::HeaderValue, Self::Error> {
|
fn try_into_value(self) -> Result<http::HeaderValue, Self::Error> {
|
||||||
|
|
|
@ -4,7 +4,8 @@ use bytes::BytesMut;
|
||||||
use http::header::{HeaderValue, InvalidHeaderValue};
|
use http::header::{HeaderValue, InvalidHeaderValue};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
config::DATE_VALUE_LENGTH, error::ParseError, header::IntoHeaderValue, helpers::MutWriter,
|
config::DATE_VALUE_LENGTH, error::ParseError, header::TryIntoHeaderValue,
|
||||||
|
helpers::MutWriter,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// A timestamp with HTTP-style formatting and parsing.
|
/// A timestamp with HTTP-style formatting and parsing.
|
||||||
|
@ -29,7 +30,7 @@ impl fmt::Display for HttpDate {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IntoHeaderValue for HttpDate {
|
impl TryIntoHeaderValue for HttpDate {
|
||||||
type Error = InvalidHeaderValue;
|
type Error = InvalidHeaderValue;
|
||||||
|
|
||||||
fn try_into_value(self) -> Result<HeaderValue, Self::Error> {
|
fn try_into_value(self) -> Result<HeaderValue, Self::Error> {
|
||||||
|
|
|
@ -11,7 +11,7 @@ use bytestring::ByteString;
|
||||||
use crate::{
|
use crate::{
|
||||||
body::{BoxBody, MessageBody},
|
body::{BoxBody, MessageBody},
|
||||||
extensions::Extensions,
|
extensions::Extensions,
|
||||||
header::{self, HeaderMap, IntoHeaderValue},
|
header::{self, HeaderMap, TryIntoHeaderValue},
|
||||||
message::{BoxedResponseHead, ResponseHead},
|
message::{BoxedResponseHead, ResponseHead},
|
||||||
Error, ResponseBuilder, StatusCode,
|
Error, ResponseBuilder, StatusCode,
|
||||||
};
|
};
|
||||||
|
|
|
@ -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, TryIntoHeaderPair, TryIntoHeaderValue},
|
||||||
message::{BoxedResponseHead, ConnectionType, ResponseHead},
|
message::{BoxedResponseHead, ConnectionType, ResponseHead},
|
||||||
Extensions, Response, StatusCode,
|
Extensions, Response, StatusCode,
|
||||||
};
|
};
|
||||||
|
@ -90,12 +90,9 @@ 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_pair() {
|
||||||
Ok((key, value)) => {
|
Ok((key, value)) => {
|
||||||
parts.headers.insert(key, value);
|
parts.headers.insert(key, value);
|
||||||
}
|
}
|
||||||
|
@ -121,12 +118,9 @@ 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_pair() {
|
||||||
Ok((key, value)) => parts.headers.append(key, value),
|
Ok((key, value)) => parts.headers.append(key, value),
|
||||||
Err(e) => self.err = Some(e.into()),
|
Err(e) => self.err = Some(e.into()),
|
||||||
};
|
};
|
||||||
|
@ -157,7 +151,7 @@ impl ResponseBuilder {
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn upgrade<V>(&mut self, value: V) -> &mut Self
|
pub fn upgrade<V>(&mut self, value: V) -> &mut Self
|
||||||
where
|
where
|
||||||
V: IntoHeaderValue,
|
V: TryIntoHeaderValue,
|
||||||
{
|
{
|
||||||
if let Some(parts) = self.inner() {
|
if let Some(parts) = self.inner() {
|
||||||
parts.set_connection_type(ConnectionType::Upgrade);
|
parts.set_connection_type(ConnectionType::Upgrade);
|
||||||
|
@ -195,7 +189,7 @@ impl ResponseBuilder {
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn content_type<V>(&mut self, value: V) -> &mut Self
|
pub fn content_type<V>(&mut self, value: V) -> &mut Self
|
||||||
where
|
where
|
||||||
V: IntoHeaderValue,
|
V: TryIntoHeaderValue,
|
||||||
{
|
{
|
||||||
if let Some(parts) = self.inner() {
|
if let Some(parts) = self.inner() {
|
||||||
match value.try_into_value() {
|
match value.try_into_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,11 +92,8 @@ 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
|
match header.try_into_pair() {
|
||||||
H: IntoHeaderPair,
|
|
||||||
{
|
|
||||||
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,11 +106,8 @@ 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
|
match header.try_into_pair() {
|
||||||
H: IntoHeaderPair,
|
|
||||||
{
|
|
||||||
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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
# Changes
|
# Changes
|
||||||
|
|
||||||
## Unreleased - 2021-xx-xx
|
## Unreleased - 2021-xx-xx
|
||||||
|
* Add `ClientBuilder::add_default_header` and deprecate `ClientBuilder::header`. [#2510]
|
||||||
|
|
||||||
|
[#2510]: https://github.com/actix/actix-web/pull/2510
|
||||||
|
|
||||||
|
|
||||||
## 3.0.0-beta.13 - 2021-12-11
|
## 3.0.0-beta.13 - 2021-12-11
|
||||||
|
@ -60,7 +63,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
|
||||||
|
|
|
@ -2,7 +2,7 @@ use std::{convert::TryFrom, fmt, net::IpAddr, rc::Rc, time::Duration};
|
||||||
|
|
||||||
use actix_http::{
|
use actix_http::{
|
||||||
error::HttpError,
|
error::HttpError,
|
||||||
header::{self, HeaderMap, HeaderName},
|
header::{self, HeaderMap, HeaderName, TryIntoHeaderPair},
|
||||||
Uri,
|
Uri,
|
||||||
};
|
};
|
||||||
use actix_rt::net::{ActixStream, TcpStream};
|
use actix_rt::net::{ActixStream, TcpStream};
|
||||||
|
@ -21,11 +21,11 @@ use crate::{
|
||||||
/// This type can be used to construct an instance of `Client` through a
|
/// This type can be used to construct an instance of `Client` through a
|
||||||
/// builder-like pattern.
|
/// builder-like pattern.
|
||||||
pub struct ClientBuilder<S = (), M = ()> {
|
pub struct ClientBuilder<S = (), M = ()> {
|
||||||
default_headers: bool,
|
|
||||||
max_http_version: Option<http::Version>,
|
max_http_version: Option<http::Version>,
|
||||||
stream_window_size: Option<u32>,
|
stream_window_size: Option<u32>,
|
||||||
conn_window_size: Option<u32>,
|
conn_window_size: Option<u32>,
|
||||||
headers: HeaderMap,
|
fundamental_headers: bool,
|
||||||
|
default_headers: HeaderMap,
|
||||||
timeout: Option<Duration>,
|
timeout: Option<Duration>,
|
||||||
connector: Connector<S>,
|
connector: Connector<S>,
|
||||||
middleware: M,
|
middleware: M,
|
||||||
|
@ -44,15 +44,15 @@ impl ClientBuilder {
|
||||||
(),
|
(),
|
||||||
> {
|
> {
|
||||||
ClientBuilder {
|
ClientBuilder {
|
||||||
middleware: (),
|
|
||||||
default_headers: true,
|
|
||||||
headers: HeaderMap::new(),
|
|
||||||
timeout: Some(Duration::from_secs(5)),
|
|
||||||
local_address: None,
|
|
||||||
connector: Connector::new(),
|
|
||||||
max_http_version: None,
|
max_http_version: None,
|
||||||
stream_window_size: None,
|
stream_window_size: None,
|
||||||
conn_window_size: None,
|
conn_window_size: None,
|
||||||
|
fundamental_headers: true,
|
||||||
|
default_headers: HeaderMap::new(),
|
||||||
|
timeout: Some(Duration::from_secs(5)),
|
||||||
|
connector: Connector::new(),
|
||||||
|
middleware: (),
|
||||||
|
local_address: None,
|
||||||
max_redirects: 10,
|
max_redirects: 10,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -78,8 +78,8 @@ where
|
||||||
{
|
{
|
||||||
ClientBuilder {
|
ClientBuilder {
|
||||||
middleware: self.middleware,
|
middleware: self.middleware,
|
||||||
|
fundamental_headers: self.fundamental_headers,
|
||||||
default_headers: self.default_headers,
|
default_headers: self.default_headers,
|
||||||
headers: self.headers,
|
|
||||||
timeout: self.timeout,
|
timeout: self.timeout,
|
||||||
local_address: self.local_address,
|
local_address: self.local_address,
|
||||||
connector,
|
connector,
|
||||||
|
@ -153,30 +153,46 @@ where
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Do not add default request headers.
|
/// Do not add fundamental default request headers.
|
||||||
|
///
|
||||||
/// By default `Date` and `User-Agent` headers are set.
|
/// By default `Date` and `User-Agent` headers are set.
|
||||||
pub fn no_default_headers(mut self) -> Self {
|
pub fn no_default_headers(mut self) -> Self {
|
||||||
self.default_headers = false;
|
self.fundamental_headers = false;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add default header. Headers added by this method
|
/// Add default header.
|
||||||
/// get added to every request.
|
///
|
||||||
|
/// Headers added by this method get added to every request unless overriden by .
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
/// Panics if header name or value is invalid.
|
||||||
|
pub fn add_default_header(mut self, header: impl TryIntoHeaderPair) -> Self {
|
||||||
|
match header.try_into_pair() {
|
||||||
|
Ok((key, value)) => self.default_headers.append(key, value),
|
||||||
|
Err(err) => panic!("Header error: {:?}", err.into()),
|
||||||
|
}
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
#[deprecated(since = "3.0.0", note = "Prefer `add_default_header((key, value))`.")]
|
||||||
pub fn header<K, V>(mut self, key: K, value: V) -> Self
|
pub fn header<K, V>(mut self, key: K, value: V) -> Self
|
||||||
where
|
where
|
||||||
HeaderName: TryFrom<K>,
|
HeaderName: TryFrom<K>,
|
||||||
<HeaderName as TryFrom<K>>::Error: fmt::Debug + Into<HttpError>,
|
<HeaderName as TryFrom<K>>::Error: fmt::Debug + Into<HttpError>,
|
||||||
V: header::IntoHeaderValue,
|
V: header::TryIntoHeaderValue,
|
||||||
V::Error: fmt::Debug,
|
V::Error: fmt::Debug,
|
||||||
{
|
{
|
||||||
match HeaderName::try_from(key) {
|
match HeaderName::try_from(key) {
|
||||||
Ok(key) => match value.try_into_value() {
|
Ok(key) => match value.try_into_value() {
|
||||||
Ok(value) => {
|
Ok(value) => {
|
||||||
self.headers.append(key, value);
|
self.default_headers.append(key, value);
|
||||||
}
|
}
|
||||||
Err(e) => log::error!("Header value error: {:?}", e),
|
Err(err) => log::error!("Header value error: {:?}", err),
|
||||||
},
|
},
|
||||||
Err(e) => log::error!("Header name error: {:?}", e),
|
Err(err) => log::error!("Header name error: {:?}", err),
|
||||||
}
|
}
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
@ -190,10 +206,10 @@ where
|
||||||
Some(password) => format!("{}:{}", username, password),
|
Some(password) => format!("{}:{}", username, password),
|
||||||
None => format!("{}:", username),
|
None => format!("{}:", username),
|
||||||
};
|
};
|
||||||
self.header(
|
self.add_default_header((
|
||||||
header::AUTHORIZATION,
|
header::AUTHORIZATION,
|
||||||
format!("Basic {}", base64::encode(&auth)),
|
format!("Basic {}", base64::encode(&auth)),
|
||||||
)
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set client wide HTTP bearer authentication header
|
/// Set client wide HTTP bearer authentication header
|
||||||
|
@ -201,13 +217,12 @@ where
|
||||||
where
|
where
|
||||||
T: fmt::Display,
|
T: fmt::Display,
|
||||||
{
|
{
|
||||||
self.header(header::AUTHORIZATION, format!("Bearer {}", token))
|
self.add_default_header((header::AUTHORIZATION, format!("Bearer {}", token)))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Registers middleware, in the form of a middleware component (type),
|
/// Registers middleware, in the form of a middleware component (type), that runs during inbound
|
||||||
/// that runs during inbound and/or outbound processing in the request
|
/// and/or outbound processing in the request life-cycle (request -> response),
|
||||||
/// life-cycle (request -> response), modifying request/response as
|
/// modifying request/response as necessary, across all requests managed by the `Client`.
|
||||||
/// necessary, across all requests managed by the Client.
|
|
||||||
pub fn wrap<S1, M1>(
|
pub fn wrap<S1, M1>(
|
||||||
self,
|
self,
|
||||||
mw: M1,
|
mw: M1,
|
||||||
|
@ -218,11 +233,11 @@ where
|
||||||
{
|
{
|
||||||
ClientBuilder {
|
ClientBuilder {
|
||||||
middleware: NestTransform::new(self.middleware, mw),
|
middleware: NestTransform::new(self.middleware, mw),
|
||||||
default_headers: self.default_headers,
|
fundamental_headers: self.fundamental_headers,
|
||||||
max_http_version: self.max_http_version,
|
max_http_version: self.max_http_version,
|
||||||
stream_window_size: self.stream_window_size,
|
stream_window_size: self.stream_window_size,
|
||||||
conn_window_size: self.conn_window_size,
|
conn_window_size: self.conn_window_size,
|
||||||
headers: self.headers,
|
default_headers: self.default_headers,
|
||||||
timeout: self.timeout,
|
timeout: self.timeout,
|
||||||
connector: self.connector,
|
connector: self.connector,
|
||||||
local_address: self.local_address,
|
local_address: self.local_address,
|
||||||
|
@ -237,10 +252,10 @@ where
|
||||||
M::Transform:
|
M::Transform:
|
||||||
Service<ConnectRequest, Response = ConnectResponse, Error = SendRequestError>,
|
Service<ConnectRequest, Response = ConnectResponse, Error = SendRequestError>,
|
||||||
{
|
{
|
||||||
let redirect_time = self.max_redirects;
|
let max_redirects = self.max_redirects;
|
||||||
|
|
||||||
if redirect_time > 0 {
|
if max_redirects > 0 {
|
||||||
self.wrap(Redirect::new().max_redirect_times(redirect_time))
|
self.wrap(Redirect::new().max_redirect_times(max_redirects))
|
||||||
._finish()
|
._finish()
|
||||||
} else {
|
} else {
|
||||||
self._finish()
|
self._finish()
|
||||||
|
@ -272,7 +287,7 @@ where
|
||||||
let connector = boxed::rc_service(self.middleware.new_transform(connector));
|
let connector = boxed::rc_service(self.middleware.new_transform(connector));
|
||||||
|
|
||||||
Client(ClientConfig {
|
Client(ClientConfig {
|
||||||
headers: Rc::new(self.headers),
|
default_headers: Rc::new(self.default_headers),
|
||||||
timeout: self.timeout,
|
timeout: self.timeout,
|
||||||
connector,
|
connector,
|
||||||
})
|
})
|
||||||
|
@ -288,7 +303,7 @@ mod tests {
|
||||||
let client = ClientBuilder::new().basic_auth("username", Some("password"));
|
let client = ClientBuilder::new().basic_auth("username", Some("password"));
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
client
|
client
|
||||||
.headers
|
.default_headers
|
||||||
.get(header::AUTHORIZATION)
|
.get(header::AUTHORIZATION)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.to_str()
|
.to_str()
|
||||||
|
@ -299,7 +314,7 @@ mod tests {
|
||||||
let client = ClientBuilder::new().basic_auth("username", None);
|
let client = ClientBuilder::new().basic_auth("username", None);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
client
|
client
|
||||||
.headers
|
.default_headers
|
||||||
.get(header::AUTHORIZATION)
|
.get(header::AUTHORIZATION)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.to_str()
|
.to_str()
|
||||||
|
@ -313,7 +328,7 @@ mod tests {
|
||||||
let client = ClientBuilder::new().bearer_auth("someS3cr3tAutht0k3n");
|
let client = ClientBuilder::new().bearer_auth("someS3cr3tAutht0k3n");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
client
|
client
|
||||||
.headers
|
.default_headers
|
||||||
.get(header::AUTHORIZATION)
|
.get(header::AUTHORIZATION)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.to_str()
|
.to_str()
|
||||||
|
|
|
@ -9,7 +9,7 @@ use actix_http::{
|
||||||
body::{BodySize, MessageBody},
|
body::{BodySize, MessageBody},
|
||||||
error::PayloadError,
|
error::PayloadError,
|
||||||
h1,
|
h1,
|
||||||
header::{HeaderMap, IntoHeaderValue, EXPECT, HOST},
|
header::{HeaderMap, TryIntoHeaderValue, EXPECT, HOST},
|
||||||
Payload, RequestHeadType, ResponseHead, StatusCode,
|
Payload, RequestHeadType, ResponseHead, StatusCode,
|
||||||
};
|
};
|
||||||
use actix_utils::future::poll_fn;
|
use actix_utils::future::poll_fn;
|
||||||
|
|
|
@ -6,7 +6,7 @@ use serde::Serialize;
|
||||||
|
|
||||||
use actix_http::{
|
use actix_http::{
|
||||||
error::HttpError,
|
error::HttpError,
|
||||||
header::{HeaderMap, HeaderName, IntoHeaderValue},
|
header::{HeaderMap, HeaderName, TryIntoHeaderValue},
|
||||||
Method, RequestHead, Uri,
|
Method, RequestHead, Uri,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -114,7 +114,7 @@ impl FrozenClientRequest {
|
||||||
where
|
where
|
||||||
HeaderName: TryFrom<K>,
|
HeaderName: TryFrom<K>,
|
||||||
<HeaderName as TryFrom<K>>::Error: Into<HttpError>,
|
<HeaderName as TryFrom<K>>::Error: Into<HttpError>,
|
||||||
V: IntoHeaderValue,
|
V: TryIntoHeaderValue,
|
||||||
{
|
{
|
||||||
self.extra_headers(HeaderMap::new())
|
self.extra_headers(HeaderMap::new())
|
||||||
.extra_header(key, value)
|
.extra_header(key, value)
|
||||||
|
@ -142,7 +142,7 @@ impl FrozenSendBuilder {
|
||||||
where
|
where
|
||||||
HeaderName: TryFrom<K>,
|
HeaderName: TryFrom<K>,
|
||||||
<HeaderName as TryFrom<K>>::Error: Into<HttpError>,
|
<HeaderName as TryFrom<K>>::Error: Into<HttpError>,
|
||||||
V: IntoHeaderValue,
|
V: TryIntoHeaderValue,
|
||||||
{
|
{
|
||||||
match HeaderName::try_from(key) {
|
match HeaderName::try_from(key) {
|
||||||
Ok(key) => match value.try_into_value() {
|
Ok(key) => match value.try_into_value() {
|
||||||
|
|
|
@ -168,7 +168,7 @@ pub struct Client(ClientConfig);
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub(crate) struct ClientConfig {
|
pub(crate) struct ClientConfig {
|
||||||
pub(crate) connector: BoxConnectorService,
|
pub(crate) connector: BoxConnectorService,
|
||||||
pub(crate) headers: Rc<HeaderMap>,
|
pub(crate) default_headers: Rc<HeaderMap>,
|
||||||
pub(crate) timeout: Option<Duration>,
|
pub(crate) timeout: Option<Duration>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -204,7 +204,9 @@ impl Client {
|
||||||
{
|
{
|
||||||
let mut req = ClientRequest::new(method, url, self.0.clone());
|
let mut req = ClientRequest::new(method, url, self.0.clone());
|
||||||
|
|
||||||
for header in self.0.headers.iter() {
|
for header in self.0.default_headers.iter() {
|
||||||
|
// header map is empty
|
||||||
|
// TODO: probably append instead
|
||||||
req = req.insert_header_if_none(header);
|
req = req.insert_header_if_none(header);
|
||||||
}
|
}
|
||||||
req
|
req
|
||||||
|
@ -297,7 +299,7 @@ impl Client {
|
||||||
<Uri as TryFrom<U>>::Error: Into<HttpError>,
|
<Uri as TryFrom<U>>::Error: Into<HttpError>,
|
||||||
{
|
{
|
||||||
let mut req = ws::WebsocketsRequest::new(url, self.0.clone());
|
let mut req = ws::WebsocketsRequest::new(url, self.0.clone());
|
||||||
for (key, value) in self.0.headers.iter() {
|
for (key, value) in self.0.default_headers.iter() {
|
||||||
req.head.headers.insert(key.clone(), value.clone());
|
req.head.headers.insert(key.clone(), value.clone());
|
||||||
}
|
}
|
||||||
req
|
req
|
||||||
|
@ -308,6 +310,6 @@ impl Client {
|
||||||
/// Returns Some(&mut HeaderMap) when Client object is unique
|
/// Returns Some(&mut HeaderMap) when Client object is unique
|
||||||
/// (No other clone of client exists at the same time).
|
/// (No other clone of client exists at the same time).
|
||||||
pub fn headers(&mut self) -> Option<&mut HeaderMap> {
|
pub fn headers(&mut self) -> Option<&mut HeaderMap> {
|
||||||
Rc::get_mut(&mut self.0.headers)
|
Rc::get_mut(&mut self.0.default_headers)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -442,13 +442,15 @@ mod tests {
|
||||||
});
|
});
|
||||||
|
|
||||||
let client = ClientBuilder::new()
|
let client = ClientBuilder::new()
|
||||||
.header("custom", "value")
|
.add_default_header(("custom", "value"))
|
||||||
.disable_redirects()
|
.disable_redirects()
|
||||||
.finish();
|
.finish();
|
||||||
let res = client.get(srv.url("/")).send().await.unwrap();
|
let res = client.get(srv.url("/")).send().await.unwrap();
|
||||||
assert_eq!(res.status().as_u16(), 302);
|
assert_eq!(res.status().as_u16(), 302);
|
||||||
|
|
||||||
let client = ClientBuilder::new().header("custom", "value").finish();
|
let client = ClientBuilder::new()
|
||||||
|
.add_default_header(("custom", "value"))
|
||||||
|
.finish();
|
||||||
let res = client.get(srv.url("/")).send().await.unwrap();
|
let res = client.get(srv.url("/")).send().await.unwrap();
|
||||||
assert_eq!(res.status().as_u16(), 200);
|
assert_eq!(res.status().as_u16(), 200);
|
||||||
|
|
||||||
|
@ -520,7 +522,7 @@ mod tests {
|
||||||
|
|
||||||
// send a request to different origins, http://srv1/ then http://srv2/. So it should remove the header
|
// send a request to different origins, http://srv1/ then http://srv2/. So it should remove the header
|
||||||
let client = ClientBuilder::new()
|
let client = ClientBuilder::new()
|
||||||
.header(header::AUTHORIZATION, "auth_key_value")
|
.add_default_header((header::AUTHORIZATION, "auth_key_value"))
|
||||||
.finish();
|
.finish();
|
||||||
let res = client.get(srv1.url("/")).send().await.unwrap();
|
let res = client.get(srv1.url("/")).send().await.unwrap();
|
||||||
assert_eq!(res.status().as_u16(), 200);
|
assert_eq!(res.status().as_u16(), 200);
|
||||||
|
|
|
@ -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,11 +147,8 @@ 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
|
match header.try_into_pair() {
|
||||||
H: IntoHeaderPair,
|
|
||||||
{
|
|
||||||
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,11 +159,8 @@ 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
|
match header.try_into_pair() {
|
||||||
H: IntoHeaderPair,
|
|
||||||
{
|
|
||||||
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) {
|
||||||
self.head.headers.insert(key, value);
|
self.head.headers.insert(key, value);
|
||||||
|
@ -192,11 +186,8 @@ 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
|
match header.try_into_pair() {
|
||||||
H: IntoHeaderPair,
|
|
||||||
{
|
|
||||||
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()),
|
||||||
};
|
};
|
||||||
|
@ -588,7 +579,7 @@ mod tests {
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_client_header() {
|
async fn test_client_header() {
|
||||||
let req = Client::builder()
|
let req = Client::builder()
|
||||||
.header(header::CONTENT_TYPE, "111")
|
.add_default_header((header::CONTENT_TYPE, "111"))
|
||||||
.finish()
|
.finish()
|
||||||
.get("/");
|
.get("/");
|
||||||
|
|
||||||
|
@ -606,7 +597,7 @@ mod tests {
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_client_header_override() {
|
async fn test_client_header_override() {
|
||||||
let req = Client::builder()
|
let req = Client::builder()
|
||||||
.header(header::CONTENT_TYPE, "111")
|
.add_default_header((header::CONTENT_TYPE, "111"))
|
||||||
.finish()
|
.finish()
|
||||||
.get("/")
|
.get("/")
|
||||||
.insert_header((header::CONTENT_TYPE, "222"));
|
.insert_header((header::CONTENT_TYPE, "222"));
|
||||||
|
|
|
@ -10,7 +10,7 @@ use std::{
|
||||||
use actix_http::{
|
use actix_http::{
|
||||||
body::BodyStream,
|
body::BodyStream,
|
||||||
error::HttpError,
|
error::HttpError,
|
||||||
header::{self, HeaderMap, HeaderName, IntoHeaderValue},
|
header::{self, HeaderMap, HeaderName, TryIntoHeaderValue},
|
||||||
RequestHead, RequestHeadType,
|
RequestHead, RequestHeadType,
|
||||||
};
|
};
|
||||||
use actix_rt::time::{sleep, Sleep};
|
use actix_rt::time::{sleep, Sleep};
|
||||||
|
@ -298,7 +298,7 @@ impl RequestSender {
|
||||||
|
|
||||||
fn set_header_if_none<V>(&mut self, key: HeaderName, value: V) -> Result<(), HttpError>
|
fn set_header_if_none<V>(&mut self, key: HeaderName, value: V) -> Result<(), HttpError>
|
||||||
where
|
where
|
||||||
V: IntoHeaderValue,
|
V: TryIntoHeaderValue,
|
||||||
{
|
{
|
||||||
match self {
|
match self {
|
||||||
RequestSender::Owned(head) => {
|
RequestSender::Owned(head) => {
|
||||||
|
|
|
@ -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,11 +39,8 @@ 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
|
if let Ok((key, value)) = header.try_into_pair() {
|
||||||
H: IntoHeaderPair,
|
|
||||||
{
|
|
||||||
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,11 +48,8 @@ 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
|
if let Ok((key, value)) = header.try_into_pair() {
|
||||||
H: IntoHeaderPair,
|
|
||||||
{
|
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,7 +39,7 @@ use crate::{
|
||||||
connect::{BoxedSocket, ConnectRequest},
|
connect::{BoxedSocket, ConnectRequest},
|
||||||
error::{HttpError, InvalidUrl, SendRequestError, WsClientError},
|
error::{HttpError, InvalidUrl, SendRequestError, WsClientError},
|
||||||
http::{
|
http::{
|
||||||
header::{self, HeaderName, HeaderValue, IntoHeaderValue, AUTHORIZATION},
|
header::{self, HeaderName, HeaderValue, TryIntoHeaderValue, AUTHORIZATION},
|
||||||
ConnectionType, Method, StatusCode, Uri, Version,
|
ConnectionType, Method, StatusCode, Uri, Version,
|
||||||
},
|
},
|
||||||
response::ClientResponse,
|
response::ClientResponse,
|
||||||
|
@ -171,7 +171,7 @@ impl WebsocketsRequest {
|
||||||
where
|
where
|
||||||
HeaderName: TryFrom<K>,
|
HeaderName: TryFrom<K>,
|
||||||
<HeaderName as TryFrom<K>>::Error: Into<HttpError>,
|
<HeaderName as TryFrom<K>>::Error: Into<HttpError>,
|
||||||
V: IntoHeaderValue,
|
V: TryIntoHeaderValue,
|
||||||
{
|
{
|
||||||
match HeaderName::try_from(key) {
|
match HeaderName::try_from(key) {
|
||||||
Ok(key) => match value.try_into_value() {
|
Ok(key) => match value.try_into_value() {
|
||||||
|
@ -190,7 +190,7 @@ impl WebsocketsRequest {
|
||||||
where
|
where
|
||||||
HeaderName: TryFrom<K>,
|
HeaderName: TryFrom<K>,
|
||||||
<HeaderName as TryFrom<K>>::Error: Into<HttpError>,
|
<HeaderName as TryFrom<K>>::Error: Into<HttpError>,
|
||||||
V: IntoHeaderValue,
|
V: TryIntoHeaderValue,
|
||||||
{
|
{
|
||||||
match HeaderName::try_from(key) {
|
match HeaderName::try_from(key) {
|
||||||
Ok(key) => match value.try_into_value() {
|
Ok(key) => match value.try_into_value() {
|
||||||
|
@ -209,7 +209,7 @@ impl WebsocketsRequest {
|
||||||
where
|
where
|
||||||
HeaderName: TryFrom<K>,
|
HeaderName: TryFrom<K>,
|
||||||
<HeaderName as TryFrom<K>>::Error: Into<HttpError>,
|
<HeaderName as TryFrom<K>>::Error: Into<HttpError>,
|
||||||
V: IntoHeaderValue,
|
V: TryIntoHeaderValue,
|
||||||
{
|
{
|
||||||
match HeaderName::try_from(key) {
|
match HeaderName::try_from(key) {
|
||||||
Ok(key) => {
|
Ok(key) => {
|
||||||
|
@ -445,7 +445,7 @@ mod tests {
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_header_override() {
|
async fn test_header_override() {
|
||||||
let req = Client::builder()
|
let req = Client::builder()
|
||||||
.header(header::CONTENT_TYPE, "111")
|
.add_default_header((header::CONTENT_TYPE, "111"))
|
||||||
.finish()
|
.finish()
|
||||||
.ws("/")
|
.ws("/")
|
||||||
.set_header(header::CONTENT_TYPE, "222");
|
.set_header(header::CONTENT_TYPE, "222");
|
||||||
|
|
|
@ -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().add(("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().add(("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().add(("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().add(("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")),
|
.add((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")),
|
.add((header::CONTENT_TYPE, HeaderValue::from_static("0001"))),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|
|
@ -2,7 +2,7 @@ use std::{cell::RefCell, fmt, io::Write as _};
|
||||||
|
|
||||||
use actix_http::{
|
use actix_http::{
|
||||||
body::BoxBody,
|
body::BoxBody,
|
||||||
header::{self, IntoHeaderValue as _},
|
header::{self, TryIntoHeaderValue as _},
|
||||||
StatusCode,
|
StatusCode,
|
||||||
};
|
};
|
||||||
use bytes::{BufMut as _, BytesMut};
|
use bytes::{BufMut as _, BytesMut};
|
||||||
|
|
|
@ -8,7 +8,7 @@ use std::{
|
||||||
|
|
||||||
use actix_http::{
|
use actix_http::{
|
||||||
body::BoxBody,
|
body::BoxBody,
|
||||||
header::{self, IntoHeaderValue},
|
header::{self, TryIntoHeaderValue},
|
||||||
Response, StatusCode,
|
Response, StatusCode,
|
||||||
};
|
};
|
||||||
use bytes::BytesMut;
|
use bytes::BytesMut;
|
||||||
|
|
|
@ -14,7 +14,7 @@ use once_cell::sync::Lazy;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use std::fmt::{self, Write};
|
use std::fmt::{self, Write};
|
||||||
|
|
||||||
use super::{ExtendedValue, Header, IntoHeaderValue, Writer};
|
use super::{ExtendedValue, Header, TryIntoHeaderValue, Writer};
|
||||||
use crate::http::header;
|
use crate::http::header;
|
||||||
|
|
||||||
/// Split at the index of the first `needle` if it exists or at the end.
|
/// 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;
|
type Error = header::InvalidHeaderValue;
|
||||||
|
|
||||||
fn try_into_value(self) -> Result<header::HeaderValue, Self::Error> {
|
fn try_into_value(self) -> Result<header::HeaderValue, Self::Error> {
|
||||||
|
|
|
@ -3,7 +3,7 @@ use std::{
|
||||||
str::FromStr,
|
str::FromStr,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{HeaderValue, IntoHeaderValue, InvalidHeaderValue, Writer, CONTENT_RANGE};
|
use super::{HeaderValue, InvalidHeaderValue, TryIntoHeaderValue, Writer, CONTENT_RANGE};
|
||||||
use crate::error::ParseError;
|
use crate::error::ParseError;
|
||||||
|
|
||||||
crate::http::header::common_header! {
|
crate::http::header::common_header! {
|
||||||
|
@ -196,7 +196,7 @@ impl Display for ContentRangeSpec {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IntoHeaderValue for ContentRangeSpec {
|
impl TryIntoHeaderValue for ContentRangeSpec {
|
||||||
type Error = InvalidHeaderValue;
|
type Error = InvalidHeaderValue;
|
||||||
|
|
||||||
fn try_into_value(self) -> Result<HeaderValue, Self::Error> {
|
fn try_into_value(self) -> Result<HeaderValue, Self::Error> {
|
||||||
|
|
|
@ -3,7 +3,7 @@ use std::{
|
||||||
str::FromStr,
|
str::FromStr,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{HeaderValue, IntoHeaderValue, InvalidHeaderValue, Writer};
|
use super::{HeaderValue, InvalidHeaderValue, TryIntoHeaderValue, Writer};
|
||||||
|
|
||||||
/// check that each char in the slice is either:
|
/// check that each char in the slice is either:
|
||||||
/// 1. `%x21`, or
|
/// 1. `%x21`, or
|
||||||
|
@ -159,7 +159,7 @@ impl FromStr for EntityTag {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IntoHeaderValue for EntityTag {
|
impl TryIntoHeaderValue for EntityTag {
|
||||||
type Error = InvalidHeaderValue;
|
type Error = InvalidHeaderValue;
|
||||||
|
|
||||||
fn try_into_value(self) -> Result<HeaderValue, Self::Error> {
|
fn try_into_value(self) -> Result<HeaderValue, Self::Error> {
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
use std::fmt::{self, Display, Write};
|
use std::fmt::{self, Display, Write};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
from_one_raw_str, EntityTag, Header, HeaderName, HeaderValue, HttpDate, IntoHeaderValue,
|
from_one_raw_str, EntityTag, Header, HeaderName, HeaderValue, HttpDate, InvalidHeaderValue,
|
||||||
InvalidHeaderValue, Writer,
|
TryIntoHeaderValue, Writer,
|
||||||
};
|
};
|
||||||
use crate::error::ParseError;
|
use crate::error::ParseError;
|
||||||
use crate::http::header;
|
use crate::http::header;
|
||||||
|
@ -96,7 +96,7 @@ impl Display for IfRange {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IntoHeaderValue for IfRange {
|
impl TryIntoHeaderValue for IfRange {
|
||||||
type Error = InvalidHeaderValue;
|
type Error = InvalidHeaderValue;
|
||||||
|
|
||||||
fn try_into_value(self) -> Result<HeaderValue, Self::Error> {
|
fn try_into_value(self) -> Result<HeaderValue, Self::Error> {
|
||||||
|
|
|
@ -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;
|
type Error = $crate::http::header::InvalidHeaderValue;
|
||||||
|
|
||||||
#[inline]
|
#[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;
|
type Error = $crate::http::header::InvalidHeaderValue;
|
||||||
|
|
||||||
#[inline]
|
#[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;
|
type Error = $crate::http::header::InvalidHeaderValue;
|
||||||
|
|
||||||
#[inline]
|
#[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;
|
type Error = $crate::http::header::InvalidHeaderValue;
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
|
|
@ -6,7 +6,7 @@ use std::{
|
||||||
|
|
||||||
use actix_http::{error::ParseError, header, HttpMessage};
|
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
|
/// `Range` header, defined
|
||||||
/// in [RFC 7233 §3.1](https://datatracker.ietf.org/doc/html/rfc7233#section-3.1)
|
/// 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;
|
type Error = InvalidHeaderValue;
|
||||||
|
|
||||||
fn try_into_value(self) -> Result<HeaderValue, Self::Error> {
|
fn try_into_value(self) -> Result<HeaderValue, Self::Error> {
|
||||||
|
|
|
@ -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,81 @@ 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().header("X-Version", "0.2"))
|
/// .wrap(middleware::DefaultHeaders::new().add(("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.
|
||||||
|
#[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
|
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.add((
|
||||||
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 add_content_type(self) -> Self {
|
||||||
Rc::get_mut(&mut self.inner)
|
self.add((
|
||||||
.expect("Multiple `Inner` copies exist.")
|
|
||||||
.headers
|
|
||||||
.insert(
|
|
||||||
CONTENT_TYPE,
|
CONTENT_TYPE,
|
||||||
HeaderValue::from_static("application/octet-stream"),
|
HeaderValue::from_static("application/octet-stream"),
|
||||||
);
|
))
|
||||||
|
|
||||||
self
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -119,7 +121,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 +199,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")
|
.add(("X-TEST", "0001"))
|
||||||
|
.add(("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 +224,7 @@ mod tests {
|
||||||
))
|
))
|
||||||
};
|
};
|
||||||
let mw = DefaultHeaders::new()
|
let mw = DefaultHeaders::new()
|
||||||
.header(CONTENT_TYPE, "0001")
|
.add((CONTENT_TYPE, "0001"))
|
||||||
.new_transform(srv.into_service())
|
.new_transform(srv.into_service())
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
@ -226,7 +233,7 @@ 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()
|
.add_content_type()
|
||||||
|
@ -241,4 +248,16 @@ mod tests {
|
||||||
"application/octet-stream"
|
"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"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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().add(("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().add(("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()));
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,7 @@ use crate::{dev::Payload, error::ErrorInternalServerError, Error, FromRequest, H
|
||||||
/// # Mutating Request Data
|
/// # Mutating Request Data
|
||||||
/// Note that since extractors must output owned data, only types that `impl Clone` can use this
|
/// 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
|
/// 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
|
/// 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.
|
/// provided to make this potential foot-gun more obvious.
|
||||||
///
|
///
|
||||||
|
|
|
@ -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")),
|
.add((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, TryIntoHeaderPair, TryIntoHeaderValue},
|
||||||
ConnectionType, Extensions, Response, ResponseHead, StatusCode,
|
ConnectionType, Extensions, Response, ResponseHead, StatusCode,
|
||||||
};
|
};
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
|
@ -67,12 +67,9 @@ 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_pair() {
|
||||||
Ok((key, value)) => {
|
Ok((key, value)) => {
|
||||||
parts.headers.insert(key, value);
|
parts.headers.insert(key, value);
|
||||||
}
|
}
|
||||||
|
@ -94,12 +91,9 @@ 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_pair() {
|
||||||
Ok((key, value)) => parts.headers.append(key, value),
|
Ok((key, value)) => parts.headers.append(key, value),
|
||||||
Err(e) => self.err = Some(e.into()),
|
Err(e) => self.err = Some(e.into()),
|
||||||
};
|
};
|
||||||
|
@ -118,7 +112,7 @@ impl HttpResponseBuilder {
|
||||||
where
|
where
|
||||||
K: TryInto<HeaderName>,
|
K: TryInto<HeaderName>,
|
||||||
K::Error: Into<HttpError>,
|
K::Error: Into<HttpError>,
|
||||||
V: IntoHeaderValue,
|
V: TryIntoHeaderValue,
|
||||||
{
|
{
|
||||||
if self.err.is_some() {
|
if self.err.is_some() {
|
||||||
return self;
|
return self;
|
||||||
|
@ -143,7 +137,7 @@ impl HttpResponseBuilder {
|
||||||
where
|
where
|
||||||
K: TryInto<HeaderName>,
|
K: TryInto<HeaderName>,
|
||||||
K::Error: Into<HttpError>,
|
K::Error: Into<HttpError>,
|
||||||
V: IntoHeaderValue,
|
V: TryIntoHeaderValue,
|
||||||
{
|
{
|
||||||
if self.err.is_some() {
|
if self.err.is_some() {
|
||||||
return self;
|
return self;
|
||||||
|
@ -180,7 +174,7 @@ impl HttpResponseBuilder {
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn upgrade<V>(&mut self, value: V) -> &mut Self
|
pub fn upgrade<V>(&mut self, value: V) -> &mut Self
|
||||||
where
|
where
|
||||||
V: IntoHeaderValue,
|
V: TryIntoHeaderValue,
|
||||||
{
|
{
|
||||||
if let Some(parts) = self.inner() {
|
if let Some(parts) = self.inner() {
|
||||||
parts.set_connection_type(ConnectionType::Upgrade);
|
parts.set_connection_type(ConnectionType::Upgrade);
|
||||||
|
@ -218,7 +212,7 @@ impl HttpResponseBuilder {
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn content_type<V>(&mut self, value: V) -> &mut Self
|
pub fn content_type<V>(&mut self, value: V) -> &mut Self
|
||||||
where
|
where
|
||||||
V: IntoHeaderValue,
|
V: TryIntoHeaderValue,
|
||||||
{
|
{
|
||||||
if let Some(parts) = self.inner() {
|
if let Some(parts) = self.inner() {
|
||||||
match value.try_into_value() {
|
match value.try_into_value() {
|
||||||
|
|
245
src/response/customize_responder.rs
Normal file
245
src/response/customize_responder.rs
Normal 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"),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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,58 @@ 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 to allow alteration of its response.
|
||||||
///
|
///
|
||||||
/// ```
|
/// See [`CustomizeResponder`] docs for its capabilities.
|
||||||
/// 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, 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
|
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(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 +175,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 +342,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")),
|
.add((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 a new issue