1
0
mirror of https://github.com/fafhrd91/actix-web synced 2024-11-24 00:21:08 +01:00

response header rework (#1869)

This commit is contained in:
Rob Ede 2021-01-15 02:11:10 +00:00 committed by GitHub
parent 4edeb5ce47
commit b1dd8d28bc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
76 changed files with 1568 additions and 1347 deletions

View File

@ -15,10 +15,12 @@
### Removed
* Public field of `web::Path` has been made private. [#1894]
* Public field of `web::Query` has been made private. [#1894]
* `TestRequest::with_header`; use `TestRequest::default().insert_header()`. [#1869]
[#1891]: https://github.com/actix/actix-web/pull/1891
[#1893]: https://github.com/actix/actix-web/pull/1893
[#1894]: https://github.com/actix/actix-web/pull/1894
[#1869]: https://github.com/actix/actix-web/pull/1869
## 4.0.0-beta.1 - 2021-01-07

View File

@ -101,7 +101,7 @@ mod tests {
header::HttpDate::from(SystemTime::now().add(Duration::from_secs(60)));
let req = TestRequest::default()
.header(header::IF_MODIFIED_SINCE, since)
.insert_header((header::IF_MODIFIED_SINCE, since))
.to_http_request();
let resp = file.respond_to(&req).await.unwrap();
assert_eq!(resp.status(), StatusCode::NOT_MODIFIED);
@ -113,7 +113,7 @@ mod tests {
let since = file.last_modified().unwrap();
let req = TestRequest::default()
.header(header::IF_MODIFIED_SINCE, since)
.insert_header((header::IF_MODIFIED_SINCE, since))
.to_http_request();
let resp = file.respond_to(&req).await.unwrap();
assert_eq!(resp.status(), StatusCode::NOT_MODIFIED);
@ -126,8 +126,8 @@ mod tests {
header::HttpDate::from(SystemTime::now().add(Duration::from_secs(60)));
let req = TestRequest::default()
.header(header::IF_NONE_MATCH, "miss_etag")
.header(header::IF_MODIFIED_SINCE, since)
.insert_header((header::IF_NONE_MATCH, "miss_etag"))
.insert_header((header::IF_MODIFIED_SINCE, since))
.to_http_request();
let resp = file.respond_to(&req).await.unwrap();
assert_ne!(resp.status(), StatusCode::NOT_MODIFIED);
@ -139,7 +139,7 @@ mod tests {
let since = file.last_modified().unwrap();
let req = TestRequest::default()
.header(header::IF_UNMODIFIED_SINCE, since)
.insert_header((header::IF_UNMODIFIED_SINCE, since))
.to_http_request();
let resp = file.respond_to(&req).await.unwrap();
assert_eq!(resp.status(), StatusCode::OK);
@ -151,7 +151,7 @@ mod tests {
let since = header::HttpDate::from(SystemTime::UNIX_EPOCH);
let req = TestRequest::default()
.header(header::IF_UNMODIFIED_SINCE, since)
.insert_header((header::IF_UNMODIFIED_SINCE, since))
.to_http_request();
let resp = file.respond_to(&req).await.unwrap();
assert_eq!(resp.status(), StatusCode::PRECONDITION_FAILED);
@ -398,7 +398,7 @@ mod tests {
// Valid range header
let request = TestRequest::get()
.uri("/t%65st/Cargo.toml")
.header(header::RANGE, "bytes=10-20")
.insert_header((header::RANGE, "bytes=10-20"))
.to_request();
let response = test::call_service(&mut srv, request).await;
assert_eq!(response.status(), StatusCode::PARTIAL_CONTENT);
@ -406,7 +406,7 @@ mod tests {
// Invalid range header
let request = TestRequest::get()
.uri("/t%65st/Cargo.toml")
.header(header::RANGE, "bytes=1-0")
.insert_header((header::RANGE, "bytes=1-0"))
.to_request();
let response = test::call_service(&mut srv, request).await;
@ -420,7 +420,7 @@ mod tests {
// Valid range header
let response = srv
.get("/tests/test.binary")
.header(header::RANGE, "bytes=10-20")
.insert_header((header::RANGE, "bytes=10-20"))
.send()
.await
.unwrap();
@ -430,7 +430,7 @@ mod tests {
// Invalid range header
let response = srv
.get("/tests/test.binary")
.header(header::RANGE, "bytes=10-5")
.insert_header((header::RANGE, "bytes=10-5"))
.send()
.await
.unwrap();
@ -445,7 +445,7 @@ mod tests {
// Valid range header
let response = srv
.get("/tests/test.binary")
.header(header::RANGE, "bytes=10-20")
.insert_header((header::RANGE, "bytes=10-20"))
.send()
.await
.unwrap();
@ -455,7 +455,7 @@ mod tests {
// Valid range header, starting from 0
let response = srv
.get("/tests/test.binary")
.header(header::RANGE, "bytes=0-20")
.insert_header((header::RANGE, "bytes=0-20"))
.send()
.await
.unwrap();
@ -560,7 +560,7 @@ mod tests {
let request = TestRequest::get()
.uri("/")
.header(header::ACCEPT_ENCODING, "gzip")
.insert_header((header::ACCEPT_ENCODING, "gzip"))
.to_request();
let res = test::call_service(&mut srv, request).await;
assert_eq!(res.status(), StatusCode::OK);
@ -580,7 +580,7 @@ mod tests {
let request = TestRequest::get()
.uri("/")
.header(header::ACCEPT_ENCODING, "gzip")
.insert_header((header::ACCEPT_ENCODING, "gzip"))
.to_request();
let res = test::call_service(&mut srv, request).await;
assert_eq!(res.status(), StatusCode::OK);

View File

@ -282,16 +282,16 @@ impl NamedFile {
if self.flags.contains(Flags::PREFER_UTF8) {
let ct = equiv_utf8_text(self.content_type.clone());
res.header(header::CONTENT_TYPE, ct.to_string());
res.insert_header((header::CONTENT_TYPE, ct.to_string()));
} else {
res.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) {
res.header(
res.insert_header((
header::CONTENT_DISPOSITION,
self.content_disposition.to_string(),
);
));
}
if let Some(current_encoding) = self.encoding {
@ -361,16 +361,16 @@ impl NamedFile {
if self.flags.contains(Flags::PREFER_UTF8) {
let ct = equiv_utf8_text(self.content_type.clone());
resp.header(header::CONTENT_TYPE, ct.to_string());
resp.insert_header((header::CONTENT_TYPE, ct.to_string()));
} else {
resp.header(header::CONTENT_TYPE, self.content_type.to_string());
resp.insert_header((header::CONTENT_TYPE, self.content_type.to_string()));
}
if self.flags.contains(Flags::CONTENT_DISPOSITION) {
resp.header(
resp.insert_header((
header::CONTENT_DISPOSITION,
self.content_disposition.to_string(),
);
));
}
// default compressing
@ -379,14 +379,14 @@ impl NamedFile {
}
if let Some(lm) = last_modified {
resp.header(header::LAST_MODIFIED, lm.to_string());
resp.insert_header((header::LAST_MODIFIED, lm.to_string()));
}
if let Some(etag) = etag {
resp.header(header::ETAG, etag.to_string());
resp.insert_header((header::ETAG, etag.to_string()));
}
resp.header(header::ACCEPT_RANGES, "bytes");
resp.insert_header((header::ACCEPT_RANGES, "bytes"));
let mut length = self.md.len();
let mut offset = 0;
@ -399,7 +399,7 @@ impl NamedFile {
offset = ranges[0].start;
resp.encoding(ContentEncoding::Identity);
resp.header(
resp.insert_header((
header::CONTENT_RANGE,
format!(
"bytes {}-{}/{}",
@ -407,9 +407,12 @@ impl NamedFile {
offset + length - 1,
self.md.len()
),
);
));
} else {
resp.header(header::CONTENT_RANGE, format!("bytes */{}", length));
resp.insert_header((
header::CONTENT_RANGE,
format!("bytes */{}", length),
));
return resp.status(StatusCode::RANGE_NOT_SATISFIABLE).finish();
};
} else {

View File

@ -78,7 +78,7 @@ impl Service<ServiceRequest> for FilesService {
if !is_method_valid {
return Either::Left(ok(req.into_response(
actix_web::HttpResponse::MethodNotAllowed()
.header(header::CONTENT_TYPE, "text/plain")
.insert_header(header::ContentType(mime::TEXT_PLAIN_UTF_8))
.body("Request did not meet this resource's requirements."),
)));
}
@ -102,7 +102,7 @@ impl Service<ServiceRequest> for FilesService {
return Either::Left(ok(req.into_response(
HttpResponse::Found()
.header(header::LOCATION, redirect_to)
.insert_header((header::LOCATION, redirect_to))
.body("")
.into_body(),
)));

View File

@ -1,8 +1,26 @@
# Changes
## Unreleased - 2021-xx-xx
* `Response::content_type` now takes an `impl IntoHeaderValue` to support `mime` types. [#1894]
### Added
* `IntoHeaderPair` trait that allows using typed and untyped headers in the same methods. [#1869]
* `ResponseBuilder::insert_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]
### Changed
* `ResponseBuilder::content_type` now takes an `impl IntoHeaderValue` to support using typed
`mime` types. [#1894]
* Renamed `IntoHeaderValue::{try_into => try_into_value}` to avoid ambiguity with std
`TryInto` trait. [#1894]
### Removed
* `ResponseBuilder::set`; use `ResponseBuilder::insert_header`. [#1869]
* `ResponseBuilder::set_header`; use `ResponseBuilder::insert_header`. [#1869]
* `ResponseBuilder::header`; use `ResponseBuilder::append_header`. [#1869]
* `TestRequest::with_hdr`; use `TestRequest::default().insert_header()`. [#1869]
* `TestRequest::with_header`; use `TestRequest::default().insert_header()`. [#1869]
[#1869]: https://github.com/actix/actix-web/pull/1869
[#1894]: https://github.com/actix/actix-web/pull/1894
@ -34,6 +52,7 @@
[#1864]: https://github.com/actix/actix-web/pull/1864
[#1878]: https://github.com/actix/actix-web/pull/1878
## 2.2.0 - 2020-11-25
### Added
* HttpResponse builders for 1xx status codes. [#1768]

View File

@ -58,7 +58,7 @@ encoding_rs = "0.8"
futures-channel = { version = "0.3.7", default-features = false }
futures-core = { version = "0.3.7", default-features = false }
futures-util = { version = "0.3.7", default-features = false, features = ["sink"] }
fxhash = "0.2.1"
ahash = "0.6"
h2 = "0.3.0"
http = "0.2.2"
httparse = "1.3"
@ -75,6 +75,7 @@ regex = "1.3"
serde = "1.0"
serde_json = "1.0"
sha-1 = "0.9"
smallvec = "1.6"
slab = "0.4"
serde_urlencoded = "0.7"
time = { version = "0.2.7", default-features = false, features = ["std"] }

View File

@ -26,7 +26,10 @@ async fn main() -> io::Result<()> {
info!("request body: {:?}", body);
Ok::<_, Error>(
Response::Ok()
.header("x-head", HeaderValue::from_static("dummy value!"))
.insert_header((
"x-head",
HeaderValue::from_static("dummy value!"),
))
.body(body),
)
})

View File

@ -15,7 +15,7 @@ async fn handle_request(mut req: Request) -> Result<Response, Error> {
info!("request body: {:?}", body);
Ok(Response::Ok()
.header("x-head", HeaderValue::from_static("dummy value!"))
.insert_header(("x-head", HeaderValue::from_static("dummy value!")))
.body(body))
}

View File

@ -19,7 +19,10 @@ async fn main() -> io::Result<()> {
.finish(|_req| {
info!("{:?}", _req);
let mut res = Response::Ok();
res.header("x-head", HeaderValue::from_static("dummy value!"));
res.insert_header((
"x-head",
HeaderValue::from_static("dummy value!"),
));
future::ok::<_, ()>(res.body("Hello world!"))
})
.tcp()

View File

@ -45,7 +45,7 @@ where
Some(port) => write!(wrt, "{}:{}", host, port),
};
match wrt.get_mut().split().freeze().try_into() {
match wrt.get_mut().split().freeze().try_into_value() {
Ok(value) => match head {
RequestHeadType::Owned(ref mut head) => {
head.headers.insert(HOST, value)

View File

@ -10,10 +10,10 @@ use actix_codec::{AsyncRead, AsyncWrite, ReadBuf};
use actix_rt::time::{sleep, Sleep};
use actix_service::Service;
use actix_utils::task::LocalWaker;
use ahash::AHashMap;
use bytes::Bytes;
use futures_channel::oneshot;
use futures_util::future::{poll_fn, FutureExt, LocalBoxFuture};
use fxhash::FxHashMap;
use h2::client::{Connection, SendRequest};
use http::uri::Authority;
use indexmap::IndexSet;
@ -59,7 +59,7 @@ where
acquired: 0,
waiters: Slab::new(),
waiters_queue: IndexSet::new(),
available: FxHashMap::default(),
available: AHashMap::default(),
waker: LocalWaker::new(),
}));
@ -257,7 +257,7 @@ struct AvailableConnection<Io> {
pub(crate) struct Inner<Io> {
config: ConnectorConfig,
acquired: usize,
available: FxHashMap<Key, VecDeque<AvailableConnection<Io>>>,
available: AHashMap<Key, VecDeque<AvailableConnection<Io>>>,
waiters: Slab<
Option<(
Connect,

View File

@ -1,14 +1,16 @@
use std::any::{Any, TypeId};
use std::{fmt, mem};
use std::{
any::{Any, TypeId},
fmt, mem,
};
use fxhash::FxHashMap;
use ahash::AHashMap;
/// A type map of request extensions.
#[derive(Default)]
pub struct Extensions {
/// Use FxHasher with a std HashMap with for faster
/// lookups on the small `TypeId` (u64 equivalent) keys.
map: FxHashMap<TypeId, Box<dyn Any>>,
map: AHashMap<TypeId, Box<dyn Any>>,
}
impl Extensions {
@ -16,7 +18,7 @@ impl Extensions {
#[inline]
pub fn new() -> Extensions {
Extensions {
map: FxHashMap::default(),
map: AHashMap::default(),
}
}

View File

@ -32,50 +32,36 @@ header! {
/// * `text/plain; q=0.5, text/html, text/x-dvi; q=0.8, text/x-c`
///
/// # Examples
/// ```rust
/// # extern crate actix_http;
/// extern crate mime;
/// ```
/// use actix_http::Response;
/// use actix_http::http::header::{Accept, qitem};
///
/// # fn main() {
/// let mut builder = Response::Ok();
///
/// builder.set(
/// builder.insert_header(
/// Accept(vec![
/// qitem(mime::TEXT_HTML),
/// ])
/// );
/// # }
/// ```
///
/// ```rust
/// # extern crate actix_http;
/// extern crate mime;
/// ```
/// use actix_http::Response;
/// use actix_http::http::header::{Accept, qitem};
///
/// # fn main() {
/// let mut builder = Response::Ok();
///
/// builder.set(
/// builder.insert_header(
/// Accept(vec![
/// qitem(mime::APPLICATION_JSON),
/// ])
/// );
/// # }
/// ```
///
/// ```rust
/// # extern crate actix_http;
/// extern crate mime;
/// ```
/// use actix_http::Response;
/// use actix_http::http::header::{Accept, QualityItem, q, qitem};
///
/// # fn main() {
/// let mut builder = Response::Ok();
///
/// builder.set(
/// builder.insert_header(
/// Accept(vec![
/// qitem(mime::TEXT_HTML),
/// qitem("application/xhtml+xml".parse().unwrap()),
@ -90,7 +76,6 @@ header! {
/// ),
/// ])
/// );
/// # }
/// ```
(Accept, header::ACCEPT) => (QualityItem<Mime>)+
@ -132,7 +117,7 @@ header! {
#[test]
fn test_fuzzing1() {
use crate::test::TestRequest;
let req = TestRequest::with_header(crate::header::ACCEPT, "chunk#;e").finish();
let req = TestRequest::default().insert_header((crate::header::ACCEPT, "chunk#;e")).finish();
let header = Accept::parse(&req);
assert!(header.is_ok());
}

View File

@ -21,44 +21,37 @@ header! {
/// * `iso-8859-5, unicode-1-1;q=0.8`
///
/// # Examples
/// ```rust
/// # extern crate actix_http;
/// ```
/// use actix_http::Response;
/// use actix_http::http::header::{AcceptCharset, Charset, qitem};
///
/// # fn main() {
/// let mut builder = Response::Ok();
/// builder.set(
/// builder.insert_header(
/// AcceptCharset(vec![qitem(Charset::Us_Ascii)])
/// );
/// # }
/// ```
/// ```rust
/// # extern crate actix_http;
///
/// ```
/// use actix_http::Response;
/// use actix_http::http::header::{AcceptCharset, Charset, q, QualityItem};
///
/// # fn main() {
/// let mut builder = Response::Ok();
/// builder.set(
/// builder.insert_header(
/// AcceptCharset(vec![
/// QualityItem::new(Charset::Us_Ascii, q(900)),
/// QualityItem::new(Charset::Iso_8859_10, q(200)),
/// ])
/// );
/// # }
/// ```
/// ```rust
/// # extern crate actix_http;
///
/// ```
/// use actix_http::Response;
/// use actix_http::http::header::{AcceptCharset, Charset, qitem};
///
/// # fn main() {
/// let mut builder = Response::Ok();
/// builder.set(
/// builder.insert_header(
/// AcceptCharset(vec![qitem(Charset::Ext("utf-8".to_owned()))])
/// );
/// # }
/// ```
(AcceptCharset, ACCEPT_CHARSET) => (QualityItem<Charset>)+

View File

@ -22,41 +22,35 @@ header! {
///
/// # Examples
///
/// ```rust
/// # extern crate actix_http;
/// # extern crate language_tags;
/// ```
/// use language_tags::langtag;
/// use actix_http::Response;
/// use actix_http::http::header::{AcceptLanguage, LanguageTag, qitem};
///
/// # fn main() {
/// let mut builder = Response::Ok();
/// let mut langtag: LanguageTag = Default::default();
/// langtag.language = Some("en".to_owned());
/// langtag.region = Some("US".to_owned());
/// builder.set(
/// builder.insert_header(
/// AcceptLanguage(vec![
/// qitem(langtag),
/// ])
/// );
/// # }
/// ```
///
/// ```rust
/// # extern crate actix_http;
/// # #[macro_use] extern crate language_tags;
/// ```
/// use language_tags::langtag;
/// use actix_http::Response;
/// use actix_http::http::header::{AcceptLanguage, QualityItem, q, qitem};
/// #
/// # fn main() {
///
/// let mut builder = Response::Ok();
/// builder.set(
/// builder.insert_header(
/// AcceptLanguage(vec![
/// qitem(langtag!(da)),
/// QualityItem::new(langtag!(en;;;GB), q(800)),
/// QualityItem::new(langtag!(en), q(700)),
/// ])
/// );
/// # }
/// ```
(AcceptLanguage, ACCEPT_LANGUAGE) => (QualityItem<LanguageTag>)+

View File

@ -22,38 +22,28 @@ header! {
///
/// # Examples
///
/// ```rust
/// # extern crate http;
/// # extern crate actix_http;
/// ```
/// use actix_http::Response;
/// use actix_http::http::header::Allow;
/// use http::Method;
/// use actix_http::http::{header::Allow, Method};
///
/// # fn main() {
/// let mut builder = Response::Ok();
/// builder.set(
/// builder.insert_header(
/// Allow(vec![Method::GET])
/// );
/// # }
/// ```
///
/// ```rust
/// # extern crate http;
/// # extern crate actix_http;
/// ```
/// use actix_http::Response;
/// use actix_http::http::header::Allow;
/// use http::Method;
/// use actix_http::http::{header::Allow, Method};
///
/// # fn main() {
/// let mut builder = Response::Ok();
/// builder.set(
/// builder.insert_header(
/// Allow(vec![
/// Method::GET,
/// Method::POST,
/// Method::PATCH,
/// ])
/// );
/// # }
/// ```
(Allow, header::ALLOW) => (Method)*

View File

@ -28,12 +28,12 @@ use crate::header::{
/// * `max-age=30`
///
/// # Examples
/// ```rust
/// ```
/// use actix_http::Response;
/// use actix_http::http::header::{CacheControl, CacheDirective};
///
/// let mut builder = Response::Ok();
/// builder.set(CacheControl(vec![CacheDirective::MaxAge(86400u32)]));
/// builder.insert_header(CacheControl(vec![CacheDirective::MaxAge(86400u32)]));
/// ```
///
/// ```rust
@ -41,7 +41,7 @@ use crate::header::{
/// use actix_http::http::header::{CacheControl, CacheDirective};
///
/// let mut builder = Response::Ok();
/// builder.set(CacheControl(vec![
/// builder.insert_header(CacheControl(vec![
/// CacheDirective::NoCache,
/// CacheDirective::Private,
/// CacheDirective::MaxAge(360u32),
@ -82,7 +82,7 @@ impl fmt::Display for CacheControl {
impl IntoHeaderValue for CacheControl {
type Error = header::InvalidHeaderValue;
fn try_into(self) -> Result<header::HeaderValue, Self::Error> {
fn try_into_value(self) -> Result<header::HeaderValue, Self::Error> {
let mut writer = Writer::new();
let _ = write!(&mut writer, "{}", self);
header::HeaderValue::from_maybe_shared(writer.take())
@ -196,7 +196,8 @@ mod tests {
#[test]
fn test_parse_multiple_headers() {
let req = TestRequest::with_header(header::CACHE_CONTROL, "no-cache, private")
let req = TestRequest::default()
.insert_header((header::CACHE_CONTROL, "no-cache, private"))
.finish();
let cache = Header::parse(&req);
assert_eq!(
@ -210,8 +211,8 @@ mod tests {
#[test]
fn test_parse_argument() {
let req =
TestRequest::with_header(header::CACHE_CONTROL, "max-age=100, private")
let req = TestRequest::default()
.insert_header((header::CACHE_CONTROL, "max-age=100, private"))
.finish();
let cache = Header::parse(&req);
assert_eq!(
@ -225,8 +226,9 @@ mod tests {
#[test]
fn test_parse_quote_form() {
let req =
TestRequest::with_header(header::CACHE_CONTROL, "max-age=\"200\"").finish();
let req = TestRequest::default()
.insert_header((header::CACHE_CONTROL, "max-age=\"200\""))
.finish();
let cache = Header::parse(&req);
assert_eq!(
cache.ok(),
@ -236,8 +238,9 @@ mod tests {
#[test]
fn test_parse_extension() {
let req =
TestRequest::with_header(header::CACHE_CONTROL, "foo, bar=baz").finish();
let req = TestRequest::default()
.insert_header((header::CACHE_CONTROL, "foo, bar=baz"))
.finish();
let cache = Header::parse(&req);
assert_eq!(
cache.ok(),
@ -250,7 +253,9 @@ mod tests {
#[test]
fn test_parse_bad_syntax() {
let req = TestRequest::with_header(header::CACHE_CONTROL, "foo=").finish();
let req = TestRequest::default()
.insert_header((header::CACHE_CONTROL, "foo="))
.finish();
let cache: Result<CacheControl, _> = Header::parse(&req);
assert_eq!(cache.ok(), None)
}

View File

@ -454,7 +454,7 @@ impl ContentDisposition {
impl IntoHeaderValue for ContentDisposition {
type Error = header::InvalidHeaderValue;
fn try_into(self) -> Result<header::HeaderValue, Self::Error> {
fn try_into_value(self) -> Result<header::HeaderValue, Self::Error> {
let mut writer = Writer::new();
let _ = write!(&mut writer, "{}", self);
header::HeaderValue::from_maybe_shared(writer.take())

View File

@ -0,0 +1,64 @@
/// Represents a supported content encoding.
#[derive(Copy, Clone, PartialEq, Debug)]
pub enum ContentEncoding {
/// Automatically select encoding based on encoding negotiation.
Auto,
/// A format using the Brotli algorithm.
Br,
/// A format using the zlib structure with deflate algorithm.
Deflate,
/// Gzip algorithm.
Gzip,
/// Indicates the identity function (i.e. no compression, nor modification).
Identity,
}
impl ContentEncoding {
/// Is the content compressed?
#[inline]
pub fn is_compression(self) -> bool {
matches!(self, ContentEncoding::Identity | ContentEncoding::Auto)
}
/// Convert content encoding to string
#[inline]
pub fn as_str(self) -> &'static str {
match self {
ContentEncoding::Br => "br",
ContentEncoding::Gzip => "gzip",
ContentEncoding::Deflate => "deflate",
ContentEncoding::Identity | ContentEncoding::Auto => "identity",
}
}
/// Default Q-factor (quality) value.
#[inline]
pub fn quality(self) -> f64 {
match self {
ContentEncoding::Br => 1.1,
ContentEncoding::Gzip => 1.0,
ContentEncoding::Deflate => 0.9,
ContentEncoding::Identity | ContentEncoding::Auto => 0.1,
}
}
}
impl From<&str> for ContentEncoding {
fn from(val: &str) -> ContentEncoding {
let val = val.trim();
if val.eq_ignore_ascii_case("br") {
ContentEncoding::Br
} else if val.eq_ignore_ascii_case("gzip") {
ContentEncoding::Gzip
} else if val.eq_ignore_ascii_case("deflate") {
ContentEncoding::Deflate
} else {
ContentEncoding::Identity
}
}
}

View File

@ -23,38 +23,31 @@ header! {
///
/// # Examples
///
/// ```rust
/// # extern crate actix_http;
/// # #[macro_use] extern crate language_tags;
/// ```
/// use language_tags::langtag;
/// use actix_http::Response;
/// # use actix_http::http::header::{ContentLanguage, qitem};
/// #
/// # fn main() {
/// use actix_http::http::header::{ContentLanguage, qitem};
///
/// let mut builder = Response::Ok();
/// builder.set(
/// builder.insert_header(
/// ContentLanguage(vec![
/// qitem(langtag!(en)),
/// ])
/// );
/// # }
/// ```
///
/// ```rust
/// # extern crate actix_http;
/// # #[macro_use] extern crate language_tags;
/// ```
/// use language_tags::langtag;
/// use actix_http::Response;
/// # use actix_http::http::header::{ContentLanguage, qitem};
/// #
/// # fn main() {
/// use actix_http::http::header::{ContentLanguage, qitem};
///
/// let mut builder = Response::Ok();
/// builder.set(
/// builder.insert_header(
/// ContentLanguage(vec![
/// qitem(langtag!(da)),
/// qitem(langtag!(en;;;GB)),
/// ])
/// );
/// # }
/// ```
(ContentLanguage, CONTENT_LANGUAGE) => (QualityItem<LanguageTag>)+

View File

@ -200,7 +200,7 @@ impl Display for ContentRangeSpec {
impl IntoHeaderValue for ContentRangeSpec {
type Error = InvalidHeaderValue;
fn try_into(self) -> Result<HeaderValue, Self::Error> {
fn try_into_value(self) -> Result<HeaderValue, Self::Error> {
let mut writer = Writer::new();
let _ = write!(&mut writer, "{}", self);
HeaderValue::from_maybe_shared(writer.take())

View File

@ -30,31 +30,24 @@ header! {
///
/// # Examples
///
/// ```rust
/// ```
/// use actix_http::Response;
/// use actix_http::http::header::ContentType;
///
/// # fn main() {
/// let mut builder = Response::Ok();
/// builder.set(
/// builder.insert_header(
/// ContentType::json()
/// );
/// # }
/// ```
///
/// ```rust
/// # extern crate mime;
/// # extern crate actix_http;
/// use mime::TEXT_HTML;
/// ```
/// use actix_http::Response;
/// use actix_http::http::header::ContentType;
///
/// # fn main() {
/// let mut builder = Response::Ok();
/// builder.set(
/// ContentType(TEXT_HTML)
/// builder.insert_header(
/// ContentType(mime::TEXT_HTML)
/// );
/// # }
/// ```
(ContentType, CONTENT_TYPE) => [Mime]
@ -99,6 +92,7 @@ impl ContentType {
pub fn form_url_encoded() -> ContentType {
ContentType(mime::APPLICATION_WWW_FORM_URLENCODED)
}
/// A constructor to easily create a `Content-Type: image/jpeg` header.
#[inline]
pub fn jpeg() -> ContentType {

View File

@ -19,13 +19,15 @@ header! {
///
/// # Example
///
/// ```rust
/// ```
/// use std::time::SystemTime;
/// use actix_http::Response;
/// use actix_http::http::header::Date;
/// use std::time::SystemTime;
///
/// let mut builder = Response::Ok();
/// builder.set(Date(SystemTime::now().into()));
/// builder.insert_header(
/// Date(SystemTime::now().into())
/// );
/// ```
(Date, DATE) => [HttpDate]

View File

@ -27,20 +27,24 @@ header! {
///
/// # Examples
///
/// ```rust
/// ```
/// use actix_http::Response;
/// use actix_http::http::header::{ETag, EntityTag};
///
/// let mut builder = Response::Ok();
/// builder.set(ETag(EntityTag::new(false, "xyzzy".to_owned())));
/// builder.insert_header(
/// ETag(EntityTag::new(false, "xyzzy".to_owned()))
/// );
/// ```
///
/// ```rust
/// ```
/// use actix_http::Response;
/// use actix_http::http::header::{ETag, EntityTag};
///
/// let mut builder = Response::Ok();
/// builder.set(ETag(EntityTag::new(true, "xyzzy".to_owned())));
/// builder.insert_header(
/// ETag(EntityTag::new(true, "xyzzy".to_owned()))
/// );
/// ```
(ETag, ETAG) => [EntityTag]

View File

@ -21,14 +21,16 @@ header! {
///
/// # Example
///
/// ```rust
/// ```
/// use std::time::{SystemTime, Duration};
/// use actix_http::Response;
/// use actix_http::http::header::Expires;
/// use std::time::{SystemTime, Duration};
///
/// let mut builder = Response::Ok();
/// let expiration = SystemTime::now() + Duration::from_secs(60 * 60 * 24);
/// builder.set(Expires(expiration.into()));
/// builder.insert_header(
/// Expires(expiration.into())
/// );
/// ```
(Expires, EXPIRES) => [HttpDate]

View File

@ -29,20 +29,20 @@ header! {
///
/// # Examples
///
/// ```rust
/// ```
/// use actix_http::Response;
/// use actix_http::http::header::IfMatch;
///
/// let mut builder = Response::Ok();
/// builder.set(IfMatch::Any);
/// builder.insert_header(IfMatch::Any);
/// ```
///
/// ```rust
/// ```
/// use actix_http::Response;
/// use actix_http::http::header::{IfMatch, EntityTag};
///
/// let mut builder = Response::Ok();
/// builder.set(
/// builder.insert_header(
/// IfMatch::Items(vec![
/// EntityTag::new(false, "xyzzy".to_owned()),
/// EntityTag::new(false, "foobar".to_owned()),

View File

@ -21,14 +21,16 @@ header! {
///
/// # Example
///
/// ```rust
/// ```
/// use std::time::{SystemTime, Duration};
/// use actix_http::Response;
/// use actix_http::http::header::IfModifiedSince;
/// use std::time::{SystemTime, Duration};
///
/// let mut builder = Response::Ok();
/// let modified = SystemTime::now() - Duration::from_secs(60 * 60 * 24);
/// builder.set(IfModifiedSince(modified.into()));
/// builder.insert_header(
/// IfModifiedSince(modified.into())
/// );
/// ```
(IfModifiedSince, IF_MODIFIED_SINCE) => [HttpDate]

View File

@ -31,20 +31,20 @@ header! {
///
/// # Examples
///
/// ```rust
/// ```
/// use actix_http::Response;
/// use actix_http::http::header::IfNoneMatch;
///
/// let mut builder = Response::Ok();
/// builder.set(IfNoneMatch::Any);
/// builder.insert_header(IfNoneMatch::Any);
/// ```
///
/// ```rust
/// ```
/// use actix_http::Response;
/// use actix_http::http::header::{IfNoneMatch, EntityTag};
///
/// let mut builder = Response::Ok();
/// builder.set(
/// builder.insert_header(
/// IfNoneMatch::Items(vec![
/// EntityTag::new(false, "xyzzy".to_owned()),
/// EntityTag::new(false, "foobar".to_owned()),
@ -73,12 +73,14 @@ mod tests {
fn test_if_none_match() {
let mut if_none_match: Result<IfNoneMatch, _>;
let req = TestRequest::with_header(IF_NONE_MATCH, "*").finish();
let req = TestRequest::default()
.insert_header((IF_NONE_MATCH, "*"))
.finish();
if_none_match = Header::parse(&req);
assert_eq!(if_none_match.ok(), Some(IfNoneMatch::Any));
let req =
TestRequest::with_header(IF_NONE_MATCH, &b"\"foobar\", W/\"weak-etag\""[..])
let req = TestRequest::default()
.insert_header((IF_NONE_MATCH, &b"\"foobar\", W/\"weak-etag\""[..]))
.finish();
if_none_match = Header::parse(&req);

View File

@ -35,31 +35,34 @@ use crate::httpmessage::HttpMessage;
///
/// # Examples
///
/// ```rust
/// ```
/// use actix_http::Response;
/// use actix_http::http::header::{EntityTag, IfRange};
///
/// let mut builder = Response::Ok();
/// builder.set(IfRange::EntityTag(EntityTag::new(
/// false,
/// "xyzzy".to_owned(),
/// )));
/// builder.insert_header(
/// IfRange::EntityTag(
/// EntityTag::new(false, "abc".to_owned())
/// )
/// );
/// ```
///
/// ```rust
/// use actix_http::Response;
/// use actix_http::http::header::IfRange;
/// ```
/// use std::time::{Duration, SystemTime};
/// use actix_http::{http::header::IfRange, Response};
///
/// let mut builder = Response::Ok();
/// let fetched = SystemTime::now() - Duration::from_secs(60 * 60 * 24);
/// builder.set(IfRange::Date(fetched.into()));
/// builder.insert_header(
/// IfRange::Date(fetched.into())
/// );
/// ```
#[derive(Clone, Debug, PartialEq)]
pub enum IfRange {
/// The entity-tag the client has of the resource
/// The entity-tag the client has of the resource.
EntityTag(EntityTag),
/// The date when the client retrieved the resource
/// The date when the client retrieved the resource.
Date(HttpDate),
}
@ -98,7 +101,7 @@ impl Display for IfRange {
impl IntoHeaderValue for IfRange {
type Error = InvalidHeaderValue;
fn try_into(self) -> Result<HeaderValue, Self::Error> {
fn try_into_value(self) -> Result<HeaderValue, Self::Error> {
let mut writer = Writer::new();
let _ = write!(&mut writer, "{}", self);
HeaderValue::from_maybe_shared(writer.take())
@ -110,7 +113,8 @@ mod test_if_range {
use super::IfRange as HeaderField;
use crate::header::*;
use std::str;
test_header!(test1, vec![b"Sat, 29 Oct 1994 19:43:31 GMT"]);
test_header!(test2, vec![b"\"xyzzy\""]);
test_header!(test2, vec![b"\"abc\""]);
test_header!(test3, vec![b"this-is-invalid"], None::<IfRange>);
}

View File

@ -22,14 +22,16 @@ header! {
///
/// # Example
///
/// ```rust
/// ```
/// use std::time::{SystemTime, Duration};
/// use actix_http::Response;
/// use actix_http::http::header::IfUnmodifiedSince;
/// use std::time::{SystemTime, Duration};
///
/// let mut builder = Response::Ok();
/// let modified = SystemTime::now() - Duration::from_secs(60 * 60 * 24);
/// builder.set(IfUnmodifiedSince(modified.into()));
/// builder.insert_header(
/// IfUnmodifiedSince(modified.into())
/// );
/// ```
(IfUnmodifiedSince, IF_UNMODIFIED_SINCE) => [HttpDate]

View File

@ -21,14 +21,16 @@ header! {
///
/// # Example
///
/// ```rust
/// ```
/// use std::time::{SystemTime, Duration};
/// use actix_http::Response;
/// use actix_http::http::header::LastModified;
/// use std::time::{SystemTime, Duration};
///
/// let mut builder = Response::Ok();
/// let modified = SystemTime::now() - Duration::from_secs(60 * 60 * 24);
/// builder.set(LastModified(modified.into()));
/// builder.insert_header(
/// LastModified(modified.into())
/// );
/// ```
(LastModified, LAST_MODIFIED) => [HttpDate]

View File

@ -18,6 +18,7 @@ pub use self::content_disposition::{
};
pub use self::content_language::ContentLanguage;
pub use self::content_range::{ContentRange, ContentRangeSpec};
pub use self::content_encoding::{ContentEncoding};
pub use self::content_type::ContentType;
pub use self::date::Date;
pub use self::etag::ETag;
@ -83,7 +84,7 @@ macro_rules! test_header {
let a: Vec<Vec<u8>> = raw.iter().map(|x| x.to_vec()).collect();
let mut req = test::TestRequest::default();
for item in a {
req = req.header(HeaderField::name(), item).take();
req = req.insert_header((HeaderField::name(), item)).take();
}
let req = req.finish();
let value = HeaderField::parse(&req);
@ -110,7 +111,7 @@ macro_rules! test_header {
let a: Vec<Vec<u8>> = $raw.iter().map(|x| x.to_vec()).collect();
let mut req = test::TestRequest::default();
for item in a {
req.header(HeaderField::name(), item);
req.insert_header((HeaderField::name(), item));
}
let req = req.finish();
let val = HeaderField::parse(&req);
@ -168,7 +169,7 @@ macro_rules! header {
impl $crate::http::header::IntoHeaderValue for $id {
type Error = $crate::http::header::InvalidHeaderValue;
fn try_into(self) -> Result<$crate::http::header::HeaderValue, Self::Error> {
fn try_into_value(self) -> Result<$crate::http::header::HeaderValue, Self::Error> {
use std::fmt::Write;
let mut writer = $crate::http::header::Writer::new();
let _ = write!(&mut writer, "{}", self);
@ -204,7 +205,7 @@ macro_rules! header {
impl $crate::http::header::IntoHeaderValue for $id {
type Error = $crate::http::header::InvalidHeaderValue;
fn try_into(self) -> Result<$crate::http::header::HeaderValue, Self::Error> {
fn try_into_value(self) -> Result<$crate::http::header::HeaderValue, Self::Error> {
use std::fmt::Write;
let mut writer = $crate::http::header::Writer::new();
let _ = write!(&mut writer, "{}", self);
@ -240,8 +241,8 @@ macro_rules! header {
impl $crate::http::header::IntoHeaderValue for $id {
type Error = $crate::http::header::InvalidHeaderValue;
fn try_into(self) -> Result<$crate::http::header::HeaderValue, Self::Error> {
self.0.try_into()
fn try_into_value(self) -> Result<$crate::http::header::HeaderValue, Self::Error> {
self.0.try_into_value()
}
}
};
@ -289,7 +290,7 @@ macro_rules! header {
impl $crate::http::header::IntoHeaderValue for $id {
type Error = $crate::http::header::InvalidHeaderValue;
fn try_into(self) -> Result<$crate::http::header::HeaderValue, Self::Error> {
fn try_into_value(self) -> Result<$crate::http::header::HeaderValue, Self::Error> {
use std::fmt::Write;
let mut writer = $crate::http::header::Writer::new();
let _ = write!(&mut writer, "{}", self);
@ -333,13 +334,14 @@ macro_rules! header {
}
mod accept_charset;
//mod accept_encoding;
// mod accept_encoding;
mod accept;
mod accept_language;
mod allow;
mod cache_control;
mod content_disposition;
mod content_language;
mod content_encoding;
mod content_range;
mod content_type;
mod date;

View File

@ -0,0 +1,117 @@
use std::convert::TryFrom;
use http::{
header::{HeaderName, InvalidHeaderName, InvalidHeaderValue},
Error as HttpError, HeaderValue,
};
use super::{Header, IntoHeaderValue};
/// Transforms structures into header K/V pairs for inserting into `HeaderMap`s.
pub trait IntoHeaderPair: Sized {
type Error: Into<HttpError>;
fn try_into_header_pair(self) -> Result<(HeaderName, HeaderValue), Self::Error>;
}
#[derive(Debug)]
pub enum InvalidHeaderPart {
Name(InvalidHeaderName),
Value(InvalidHeaderValue),
}
impl From<InvalidHeaderPart> for HttpError {
fn from(part_err: InvalidHeaderPart) -> Self {
match part_err {
InvalidHeaderPart::Name(err) => err.into(),
InvalidHeaderPart::Value(err) => err.into(),
}
}
}
impl<V> IntoHeaderPair for (HeaderName, V)
where
V: IntoHeaderValue,
V::Error: Into<InvalidHeaderValue>,
{
type Error = InvalidHeaderPart;
fn try_into_header_pair(self) -> Result<(HeaderName, HeaderValue), Self::Error> {
let (name, value) = self;
let value = value
.try_into_value()
.map_err(|err| InvalidHeaderPart::Value(err.into()))?;
Ok((name, value))
}
}
impl<V> IntoHeaderPair for (&HeaderName, V)
where
V: IntoHeaderValue,
V::Error: Into<InvalidHeaderValue>,
{
type Error = InvalidHeaderPart;
fn try_into_header_pair(self) -> Result<(HeaderName, HeaderValue), Self::Error> {
let (name, value) = self;
let value = value
.try_into_value()
.map_err(|err| InvalidHeaderPart::Value(err.into()))?;
Ok((name.clone(), value))
}
}
impl<V> IntoHeaderPair for (&[u8], V)
where
V: IntoHeaderValue,
V::Error: Into<InvalidHeaderValue>,
{
type Error = InvalidHeaderPart;
fn try_into_header_pair(self) -> Result<(HeaderName, HeaderValue), Self::Error> {
let (name, value) = self;
let name = HeaderName::try_from(name).map_err(InvalidHeaderPart::Name)?;
let value = value
.try_into_value()
.map_err(|err| InvalidHeaderPart::Value(err.into()))?;
Ok((name, value))
}
}
impl<V> IntoHeaderPair for (&str, V)
where
V: IntoHeaderValue,
V::Error: Into<InvalidHeaderValue>,
{
type Error = InvalidHeaderPart;
fn try_into_header_pair(self) -> Result<(HeaderName, HeaderValue), Self::Error> {
let (name, value) = self;
let name = HeaderName::try_from(name).map_err(InvalidHeaderPart::Name)?;
let value = value
.try_into_value()
.map_err(|err| InvalidHeaderPart::Value(err.into()))?;
Ok((name, value))
}
}
impl<V> IntoHeaderPair for (String, V)
where
V: IntoHeaderValue,
V::Error: Into<InvalidHeaderValue>,
{
type Error = InvalidHeaderPart;
fn try_into_header_pair(self) -> Result<(HeaderName, HeaderValue), Self::Error> {
let (name, value) = self;
(name.as_str(), value).try_into_header_pair()
}
}
impl<T: Header> IntoHeaderPair for T {
type Error = <T as IntoHeaderValue>::Error;
fn try_into_header_pair(self) -> Result<(HeaderName, HeaderValue), Self::Error> {
Ok((T::name(), self.try_into_value()?))
}
}

View File

@ -0,0 +1,131 @@
use std::convert::TryFrom;
use bytes::Bytes;
use http::{header::InvalidHeaderValue, Error as HttpError, HeaderValue};
use mime::Mime;
/// A trait for any object that can be Converted to a `HeaderValue`
pub trait IntoHeaderValue: Sized {
/// The type returned in the event of a conversion error.
type Error: Into<HttpError>;
/// Try to convert value to a HeaderValue.
fn try_into_value(self) -> Result<HeaderValue, Self::Error>;
}
impl IntoHeaderValue for HeaderValue {
type Error = InvalidHeaderValue;
#[inline]
fn try_into_value(self) -> Result<HeaderValue, Self::Error> {
Ok(self)
}
}
impl IntoHeaderValue for &HeaderValue {
type Error = InvalidHeaderValue;
#[inline]
fn try_into_value(self) -> Result<HeaderValue, Self::Error> {
Ok(self.clone())
}
}
impl IntoHeaderValue for &str {
type Error = InvalidHeaderValue;
#[inline]
fn try_into_value(self) -> Result<HeaderValue, Self::Error> {
self.parse()
}
}
impl IntoHeaderValue for &[u8] {
type Error = InvalidHeaderValue;
#[inline]
fn try_into_value(self) -> Result<HeaderValue, Self::Error> {
HeaderValue::from_bytes(self)
}
}
impl IntoHeaderValue for Bytes {
type Error = InvalidHeaderValue;
#[inline]
fn try_into_value(self) -> Result<HeaderValue, Self::Error> {
HeaderValue::from_maybe_shared(self)
}
}
impl IntoHeaderValue for Vec<u8> {
type Error = InvalidHeaderValue;
#[inline]
fn try_into_value(self) -> Result<HeaderValue, Self::Error> {
HeaderValue::try_from(self)
}
}
impl IntoHeaderValue for String {
type Error = InvalidHeaderValue;
#[inline]
fn try_into_value(self) -> Result<HeaderValue, Self::Error> {
HeaderValue::try_from(self)
}
}
impl IntoHeaderValue for usize {
type Error = InvalidHeaderValue;
#[inline]
fn try_into_value(self) -> Result<HeaderValue, Self::Error> {
HeaderValue::try_from(self.to_string())
}
}
impl IntoHeaderValue for i64 {
type Error = InvalidHeaderValue;
#[inline]
fn try_into_value(self) -> Result<HeaderValue, Self::Error> {
HeaderValue::try_from(self.to_string())
}
}
impl IntoHeaderValue for u64 {
type Error = InvalidHeaderValue;
#[inline]
fn try_into_value(self) -> Result<HeaderValue, Self::Error> {
HeaderValue::try_from(self.to_string())
}
}
impl IntoHeaderValue for i32 {
type Error = InvalidHeaderValue;
#[inline]
fn try_into_value(self) -> Result<HeaderValue, Self::Error> {
HeaderValue::try_from(self.to_string())
}
}
impl IntoHeaderValue for u32 {
type Error = InvalidHeaderValue;
#[inline]
fn try_into_value(self) -> Result<HeaderValue, Self::Error> {
HeaderValue::try_from(self.to_string())
}
}
impl IntoHeaderValue for Mime {
type Error = InvalidHeaderValue;
#[inline]
fn try_into_value(self) -> Result<HeaderValue, Self::Error> {
HeaderValue::try_from(format!("{}", self))
}
}

View File

@ -1,33 +1,36 @@
use std::collections::hash_map::{self, Entry};
use std::convert::TryFrom;
use std::{
collections::hash_map::{self, Entry},
convert::TryFrom,
};
use ahash::AHashMap;
use either::Either;
use fxhash::FxHashMap;
use http::header::{HeaderName, HeaderValue};
use smallvec::{smallvec, SmallVec};
/// A set of HTTP headers
/// A multi-map of HTTP headers.
///
/// `HeaderMap` is an multi-map of [`HeaderName`] to values.
#[derive(Debug, Clone)]
/// `HeaderMap` is a "multi-map" of [`HeaderName`] to one or more values.
#[derive(Debug, Clone, Default)]
pub struct HeaderMap {
pub(crate) inner: FxHashMap<HeaderName, Value>,
pub(crate) inner: AHashMap<HeaderName, Value>,
}
#[derive(Debug, Clone)]
pub(crate) enum Value {
One(HeaderValue),
Multi(Vec<HeaderValue>),
Multi(SmallVec<[HeaderValue; 4]>),
}
impl Value {
fn get(&self) -> &HeaderValue {
fn first(&self) -> &HeaderValue {
match self {
Value::One(ref val) => val,
Value::Multi(ref val) => &val[0],
}
}
fn get_mut(&mut self) -> &mut HeaderValue {
fn first_mut(&mut self) -> &mut HeaderValue {
match self {
Value::One(ref mut val) => val,
Value::Multi(ref mut val) => &mut val[0],
@ -37,7 +40,7 @@ impl Value {
fn append(&mut self, val: HeaderValue) {
match self {
Value::One(_) => {
let data = std::mem::replace(self, Value::Multi(vec![val]));
let data = std::mem::replace(self, Value::Multi(smallvec![val]));
match data {
Value::One(val) => self.append(val),
Value::Multi(_) => unreachable!(),
@ -55,7 +58,7 @@ impl HeaderMap {
/// allocate.
pub fn new() -> Self {
HeaderMap {
inner: FxHashMap::default(),
inner: AHashMap::default(),
}
}
@ -69,7 +72,7 @@ impl HeaderMap {
/// More capacity than requested may be allocated.
pub fn with_capacity(capacity: usize) -> HeaderMap {
HeaderMap {
inner: FxHashMap::with_capacity_and_hasher(capacity, Default::default()),
inner: AHashMap::with_capacity_and_hasher(capacity, Default::default()),
}
}
@ -118,7 +121,7 @@ impl HeaderMap {
/// is returned. Use `get_all` to get all values associated with a given
/// key. Returns `None` if there are no values associated with the key.
pub fn get<N: AsName>(&self, name: N) -> Option<&HeaderValue> {
self.get2(name).map(|v| v.get())
self.get2(name).map(|v| v.first())
}
fn get2<N: AsName>(&self, name: N) -> Option<&Value> {
@ -134,11 +137,11 @@ impl HeaderMap {
}
}
/// Returns a view of all values associated with a key.
/// Returns an iterator of all values associated with a key.
///
/// The returned view does not incur any allocations and allows iterating
/// the values associated with the key. See [`GetAll`] for more details.
/// Returns `None` if there are no values associated with the key.
/// The returned view does not incur any allocations and allows iterating the values associated
/// with the key. Returns `None` if there are no values associated with the key. Iteration order
/// is not guaranteed to be the same as insertion order.
pub fn get_all<N: AsName>(&self, name: N) -> GetAll<'_> {
GetAll {
idx: 0,
@ -153,10 +156,10 @@ impl HeaderMap {
/// key. Returns `None` if there are no values associated with the key.
pub fn get_mut<N: AsName>(&mut self, name: N) -> Option<&mut HeaderValue> {
match name.as_name() {
Either::Left(name) => self.inner.get_mut(name).map(|v| v.get_mut()),
Either::Left(name) => self.inner.get_mut(name).map(|v| v.first_mut()),
Either::Right(s) => {
if let Ok(name) = HeaderName::try_from(s) {
self.inner.get_mut(&name).map(|v| v.get_mut())
self.inner.get_mut(&name).map(|v| v.first_mut())
} else {
None
}
@ -282,6 +285,7 @@ impl<'a> AsName for &'a String {
}
}
/// Iterator for all values in a `HeaderMap` with the same name.
pub struct GetAll<'a> {
idx: usize,
item: Option<&'a Value>,
@ -337,7 +341,7 @@ impl<'a> IntoIterator for &'a HeaderMap {
pub struct Iter<'a> {
idx: usize,
current: Option<(&'a HeaderName, &'a Vec<HeaderValue>)>,
current: Option<(&'a HeaderName, &'a SmallVec<[HeaderValue; 4]>)>,
iter: hash_map::Iter<'a, HeaderName, Value>,
}

View File

@ -1,12 +1,9 @@
//! Various http headers
// This is mostly copy of [hyper](https://github.com/hyperium/hyper/tree/master/src/header)
//! Typed HTTP headers, pre-defined `HeaderName`s, traits for parsing/conversion and other
//! header utility methods.
use std::convert::TryFrom;
use std::{fmt, str::FromStr};
use std::fmt;
use bytes::{Bytes, BytesMut};
use http::Error as HttpError;
use mime::Mime;
use percent_encoding::{AsciiSet, CONTROLS};
pub use http::header::*;
@ -14,22 +11,27 @@ pub use http::header::*;
use crate::error::ParseError;
use crate::httpmessage::HttpMessage;
mod into_pair;
mod into_value;
mod utils;
mod common;
pub(crate) mod map;
mod shared;
pub use self::common::*;
#[doc(hidden)]
pub use self::shared::*;
pub use self::into_pair::IntoHeaderPair;
pub use self::into_value::IntoHeaderValue;
#[doc(hidden)]
pub use self::map::GetAll;
pub use self::map::HeaderMap;
pub use self::utils::*;
/// A trait for any object that will represent a header field and value.
pub trait Header
where
Self: IntoHeaderValue,
{
/// A trait for any object that already represents a valid header field and value.
pub trait Header: IntoHeaderValue {
/// Returns the name of the header field
fn name() -> HeaderName;
@ -37,159 +39,6 @@ where
fn parse<T: HttpMessage>(msg: &T) -> Result<Self, ParseError>;
}
/// A trait for any object that can be Converted to a `HeaderValue`
pub trait IntoHeaderValue: Sized {
/// The type returned in the event of a conversion error.
type Error: Into<HttpError>;
/// Try to convert value to a Header value.
fn try_into(self) -> Result<HeaderValue, Self::Error>;
}
impl IntoHeaderValue for HeaderValue {
type Error = InvalidHeaderValue;
#[inline]
fn try_into(self) -> Result<HeaderValue, Self::Error> {
Ok(self)
}
}
impl<'a> IntoHeaderValue for &'a str {
type Error = InvalidHeaderValue;
#[inline]
fn try_into(self) -> Result<HeaderValue, Self::Error> {
self.parse()
}
}
impl<'a> IntoHeaderValue for &'a [u8] {
type Error = InvalidHeaderValue;
#[inline]
fn try_into(self) -> Result<HeaderValue, Self::Error> {
HeaderValue::from_bytes(self)
}
}
impl IntoHeaderValue for Bytes {
type Error = InvalidHeaderValue;
#[inline]
fn try_into(self) -> Result<HeaderValue, Self::Error> {
HeaderValue::from_maybe_shared(self)
}
}
impl IntoHeaderValue for Vec<u8> {
type Error = InvalidHeaderValue;
#[inline]
fn try_into(self) -> Result<HeaderValue, Self::Error> {
HeaderValue::try_from(self)
}
}
impl IntoHeaderValue for String {
type Error = InvalidHeaderValue;
#[inline]
fn try_into(self) -> Result<HeaderValue, Self::Error> {
HeaderValue::try_from(self)
}
}
impl IntoHeaderValue for usize {
type Error = InvalidHeaderValue;
#[inline]
fn try_into(self) -> Result<HeaderValue, Self::Error> {
let s = format!("{}", self);
HeaderValue::try_from(s)
}
}
impl IntoHeaderValue for u64 {
type Error = InvalidHeaderValue;
#[inline]
fn try_into(self) -> Result<HeaderValue, Self::Error> {
let s = format!("{}", self);
HeaderValue::try_from(s)
}
}
impl IntoHeaderValue for Mime {
type Error = InvalidHeaderValue;
#[inline]
fn try_into(self) -> Result<HeaderValue, Self::Error> {
HeaderValue::try_from(format!("{}", self))
}
}
/// Represents supported types of content encodings
#[derive(Copy, Clone, PartialEq, Debug)]
pub enum ContentEncoding {
/// Automatically select encoding based on encoding negotiation
Auto,
/// A format using the Brotli algorithm
Br,
/// A format using the zlib structure with deflate algorithm
Deflate,
/// Gzip algorithm
Gzip,
/// Indicates the identity function (i.e. no compression, nor modification)
Identity,
}
impl ContentEncoding {
#[inline]
/// Is the content compressed?
pub fn is_compression(self) -> bool {
matches!(self, ContentEncoding::Identity | ContentEncoding::Auto)
}
#[inline]
/// Convert content encoding to string
pub fn as_str(self) -> &'static str {
match self {
ContentEncoding::Br => "br",
ContentEncoding::Gzip => "gzip",
ContentEncoding::Deflate => "deflate",
ContentEncoding::Identity | ContentEncoding::Auto => "identity",
}
}
#[inline]
/// default quality value
pub fn quality(self) -> f64 {
match self {
ContentEncoding::Br => 1.1,
ContentEncoding::Gzip => 1.0,
ContentEncoding::Deflate => 0.9,
ContentEncoding::Identity | ContentEncoding::Auto => 0.1,
}
}
}
impl<'a> From<&'a str> for ContentEncoding {
fn from(s: &'a str) -> ContentEncoding {
let s = s.trim();
if s.eq_ignore_ascii_case("br") {
ContentEncoding::Br
} else if s.eq_ignore_ascii_case("gzip") {
ContentEncoding::Gzip
} else if s.eq_ignore_ascii_case("deflate") {
ContentEncoding::Deflate
} else {
ContentEncoding::Identity
}
}
}
#[doc(hidden)]
pub(crate) struct Writer {
buf: BytesMut,
@ -201,6 +50,7 @@ impl Writer {
buf: BytesMut::new(),
}
}
fn take(&mut self) -> Bytes {
self.buf.split().freeze()
}
@ -219,164 +69,7 @@ impl fmt::Write for Writer {
}
}
#[inline]
#[doc(hidden)]
/// Reads a comma-delimited raw header into a Vec.
pub fn from_comma_delimited<'a, I: Iterator<Item = &'a HeaderValue> + 'a, T: FromStr>(
all: I,
) -> Result<Vec<T>, ParseError> {
let mut result = Vec::new();
for h in all {
let s = h.to_str().map_err(|_| ParseError::Header)?;
result.extend(
s.split(',')
.filter_map(|x| match x.trim() {
"" => None,
y => Some(y),
})
.filter_map(|x| x.trim().parse().ok()),
)
}
Ok(result)
}
#[inline]
#[doc(hidden)]
/// Reads a single string when parsing a header.
pub fn from_one_raw_str<T: FromStr>(val: Option<&HeaderValue>) -> Result<T, ParseError> {
if let Some(line) = val {
let line = line.to_str().map_err(|_| ParseError::Header)?;
if !line.is_empty() {
return T::from_str(line).or(Err(ParseError::Header));
}
}
Err(ParseError::Header)
}
#[inline]
#[doc(hidden)]
/// Format an array into a comma-delimited string.
pub fn fmt_comma_delimited<T>(f: &mut fmt::Formatter<'_>, parts: &[T]) -> fmt::Result
where
T: fmt::Display,
{
let mut iter = parts.iter();
if let Some(part) = iter.next() {
fmt::Display::fmt(part, f)?;
}
for part in iter {
f.write_str(", ")?;
fmt::Display::fmt(part, f)?;
}
Ok(())
}
// From hyper v0.11.27 src/header/parsing.rs
/// The value part of an extended parameter consisting of three parts:
/// the REQUIRED character set name (`charset`), the OPTIONAL language information (`language_tag`),
/// and a character sequence representing the actual value (`value`), separated by single quote
/// characters. It is defined in [RFC 5987](https://tools.ietf.org/html/rfc5987#section-3.2).
#[derive(Clone, Debug, PartialEq)]
pub struct ExtendedValue {
/// The character set that is used to encode the `value` to a string.
pub charset: Charset,
/// The human language details of the `value`, if available.
pub language_tag: Option<LanguageTag>,
/// The parameter value, as expressed in octets.
pub value: Vec<u8>,
}
/// Parses extended header parameter values (`ext-value`), as defined in
/// [RFC 5987](https://tools.ietf.org/html/rfc5987#section-3.2).
///
/// Extended values are denoted by parameter names that end with `*`.
///
/// ## ABNF
///
/// ```text
/// ext-value = charset "'" [ language ] "'" value-chars
/// ; like RFC 2231's <extended-initial-value>
/// ; (see [RFC2231], Section 7)
///
/// charset = "UTF-8" / "ISO-8859-1" / mime-charset
///
/// mime-charset = 1*mime-charsetc
/// mime-charsetc = ALPHA / DIGIT
/// / "!" / "#" / "$" / "%" / "&"
/// / "+" / "-" / "^" / "_" / "`"
/// / "{" / "}" / "~"
/// ; as <mime-charset> in Section 2.3 of [RFC2978]
/// ; except that the single quote is not included
/// ; SHOULD be registered in the IANA charset registry
///
/// language = <Language-Tag, defined in [RFC5646], Section 2.1>
///
/// value-chars = *( pct-encoded / attr-char )
///
/// pct-encoded = "%" HEXDIG HEXDIG
/// ; see [RFC3986], Section 2.1
///
/// attr-char = ALPHA / DIGIT
/// / "!" / "#" / "$" / "&" / "+" / "-" / "."
/// / "^" / "_" / "`" / "|" / "~"
/// ; token except ( "*" / "'" / "%" )
/// ```
pub fn parse_extended_value(
val: &str,
) -> Result<ExtendedValue, crate::error::ParseError> {
// Break into three pieces separated by the single-quote character
let mut parts = val.splitn(3, '\'');
// Interpret the first piece as a Charset
let charset: Charset = match parts.next() {
None => return Err(crate::error::ParseError::Header),
Some(n) => FromStr::from_str(n).map_err(|_| crate::error::ParseError::Header)?,
};
// Interpret the second piece as a language tag
let language_tag: Option<LanguageTag> = match parts.next() {
None => return Err(crate::error::ParseError::Header),
Some("") => None,
Some(s) => match s.parse() {
Ok(lt) => Some(lt),
Err(_) => return Err(crate::error::ParseError::Header),
},
};
// Interpret the third piece as a sequence of value characters
let value: Vec<u8> = match parts.next() {
None => return Err(crate::error::ParseError::Header),
Some(v) => percent_encoding::percent_decode(v.as_bytes()).collect(),
};
Ok(ExtendedValue {
value,
charset,
language_tag,
})
}
impl fmt::Display for ExtendedValue {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let encoded_value =
percent_encoding::percent_encode(&self.value[..], HTTP_VALUE);
if let Some(ref lang) = self.language_tag {
write!(f, "{}'{}'{}", self.charset, lang, encoded_value)
} else {
write!(f, "{}''{}", self.charset, encoded_value)
}
}
}
/// Percent encode a sequence of bytes with a character set defined in
/// <https://tools.ietf.org/html/rfc5987#section-3.2>
pub fn http_percent_encode(f: &mut fmt::Formatter<'_>, bytes: &[u8]) -> fmt::Result {
let encoded = percent_encoding::percent_encode(bytes, HTTP_VALUE);
fmt::Display::fmt(&encoded, f)
}
/// Convert http::HeaderMap to a HeaderMap
/// Convert `http::HeaderMap` to our `HeaderMap`.
impl From<http::HeaderMap> for HeaderMap {
fn from(map: http::HeaderMap) -> HeaderMap {
let mut new_map = HeaderMap::with_capacity(map.capacity());
@ -388,7 +81,7 @@ impl From<http::HeaderMap> for HeaderMap {
}
// This encode set is used for HTTP header values and is defined at
// https://tools.ietf.org/html/rfc5987#section-3.2
// https://tools.ietf.org/html/rfc5987#section-3.2.
pub(crate) const HTTP_VALUE: &AsciiSet = &CONTROLS
.add(b' ')
.add(b'"')
@ -410,91 +103,3 @@ pub(crate) const HTTP_VALUE: &AsciiSet = &CONTROLS
.add(b']')
.add(b'{')
.add(b'}');
#[cfg(test)]
mod tests {
use super::shared::Charset;
use super::{parse_extended_value, ExtendedValue};
use language_tags::LanguageTag;
#[test]
fn test_parse_extended_value_with_encoding_and_language_tag() {
let expected_language_tag = "en".parse::<LanguageTag>().unwrap();
// RFC 5987, Section 3.2.2
// Extended notation, using the Unicode character U+00A3 (POUND SIGN)
let result = parse_extended_value("iso-8859-1'en'%A3%20rates");
assert!(result.is_ok());
let extended_value = result.unwrap();
assert_eq!(Charset::Iso_8859_1, extended_value.charset);
assert!(extended_value.language_tag.is_some());
assert_eq!(expected_language_tag, extended_value.language_tag.unwrap());
assert_eq!(
vec![163, b' ', b'r', b'a', b't', b'e', b's'],
extended_value.value
);
}
#[test]
fn test_parse_extended_value_with_encoding() {
// RFC 5987, Section 3.2.2
// Extended notation, using the Unicode characters U+00A3 (POUND SIGN)
// and U+20AC (EURO SIGN)
let result = parse_extended_value("UTF-8''%c2%a3%20and%20%e2%82%ac%20rates");
assert!(result.is_ok());
let extended_value = result.unwrap();
assert_eq!(Charset::Ext("UTF-8".to_string()), extended_value.charset);
assert!(extended_value.language_tag.is_none());
assert_eq!(
vec![
194, 163, b' ', b'a', b'n', b'd', b' ', 226, 130, 172, b' ', b'r', b'a',
b't', b'e', b's',
],
extended_value.value
);
}
#[test]
fn test_parse_extended_value_missing_language_tag_and_encoding() {
// From: https://greenbytes.de/tech/tc2231/#attwithfn2231quot2
let result = parse_extended_value("foo%20bar.html");
assert!(result.is_err());
}
#[test]
fn test_parse_extended_value_partially_formatted() {
let result = parse_extended_value("UTF-8'missing third part");
assert!(result.is_err());
}
#[test]
fn test_parse_extended_value_partially_formatted_blank() {
let result = parse_extended_value("blank second part'");
assert!(result.is_err());
}
#[test]
fn test_fmt_extended_value_with_encoding_and_language_tag() {
let extended_value = ExtendedValue {
charset: Charset::Iso_8859_1,
language_tag: Some("en".parse().expect("Could not parse language tag")),
value: vec![163, b' ', b'r', b'a', b't', b'e', b's'],
};
assert_eq!("ISO-8859-1'en'%A3%20rates", format!("{}", extended_value));
}
#[test]
fn test_fmt_extended_value_with_encoding() {
let extended_value = ExtendedValue {
charset: Charset::Ext("UTF-8".to_string()),
language_tag: None,
value: vec![
194, 163, b' ', b'a', b'n', b'd', b' ', 226, 130, 172, b' ', b'r', b'a',
b't', b'e', b's',
],
};
assert_eq!(
"UTF-8''%C2%A3%20and%20%E2%82%AC%20rates",
format!("{}", extended_value)
);
}
}

View File

@ -161,7 +161,7 @@ impl FromStr for EntityTag {
impl IntoHeaderValue for EntityTag {
type Error = InvalidHeaderValue;
fn try_into(self) -> Result<HeaderValue, Self::Error> {
fn try_into_value(self) -> Result<HeaderValue, Self::Error> {
let mut wrt = Writer::new();
write!(wrt, "{}", self).unwrap();
HeaderValue::from_maybe_shared(wrt.take())

View File

@ -0,0 +1,193 @@
use std::{fmt, str::FromStr};
use language_tags::LanguageTag;
use crate::header::{Charset, HTTP_VALUE};
// From hyper v0.11.27 src/header/parsing.rs
/// The value part of an extended parameter consisting of three parts:
/// - The REQUIRED character set name (`charset`).
/// - The OPTIONAL language information (`language_tag`).
/// - A character sequence representing the actual value (`value`), separated by single quotes.
///
/// It is defined in [RFC 5987](https://tools.ietf.org/html/rfc5987#section-3.2).
#[derive(Clone, Debug, PartialEq)]
pub struct ExtendedValue {
/// The character set that is used to encode the `value` to a string.
pub charset: Charset,
/// The human language details of the `value`, if available.
pub language_tag: Option<LanguageTag>,
/// The parameter value, as expressed in octets.
pub value: Vec<u8>,
}
/// Parses extended header parameter values (`ext-value`), as defined in
/// [RFC 5987](https://tools.ietf.org/html/rfc5987#section-3.2).
///
/// Extended values are denoted by parameter names that end with `*`.
///
/// ## ABNF
///
/// ```text
/// ext-value = charset "'" [ language ] "'" value-chars
/// ; like RFC 2231's <extended-initial-value>
/// ; (see [RFC2231], Section 7)
///
/// charset = "UTF-8" / "ISO-8859-1" / mime-charset
///
/// mime-charset = 1*mime-charsetc
/// mime-charsetc = ALPHA / DIGIT
/// / "!" / "#" / "$" / "%" / "&"
/// / "+" / "-" / "^" / "_" / "`"
/// / "{" / "}" / "~"
/// ; as <mime-charset> in Section 2.3 of [RFC2978]
/// ; except that the single quote is not included
/// ; SHOULD be registered in the IANA charset registry
///
/// language = <Language-Tag, defined in [RFC5646], Section 2.1>
///
/// value-chars = *( pct-encoded / attr-char )
///
/// pct-encoded = "%" HEXDIG HEXDIG
/// ; see [RFC3986], Section 2.1
///
/// attr-char = ALPHA / DIGIT
/// / "!" / "#" / "$" / "&" / "+" / "-" / "."
/// / "^" / "_" / "`" / "|" / "~"
/// ; token except ( "*" / "'" / "%" )
/// ```
pub fn parse_extended_value(
val: &str,
) -> Result<ExtendedValue, crate::error::ParseError> {
// Break into three pieces separated by the single-quote character
let mut parts = val.splitn(3, '\'');
// Interpret the first piece as a Charset
let charset: Charset = match parts.next() {
None => return Err(crate::error::ParseError::Header),
Some(n) => FromStr::from_str(n).map_err(|_| crate::error::ParseError::Header)?,
};
// Interpret the second piece as a language tag
let language_tag: Option<LanguageTag> = match parts.next() {
None => return Err(crate::error::ParseError::Header),
Some("") => None,
Some(s) => match s.parse() {
Ok(lt) => Some(lt),
Err(_) => return Err(crate::error::ParseError::Header),
},
};
// Interpret the third piece as a sequence of value characters
let value: Vec<u8> = match parts.next() {
None => return Err(crate::error::ParseError::Header),
Some(v) => percent_encoding::percent_decode(v.as_bytes()).collect(),
};
Ok(ExtendedValue {
value,
charset,
language_tag,
})
}
impl fmt::Display for ExtendedValue {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let encoded_value =
percent_encoding::percent_encode(&self.value[..], HTTP_VALUE);
if let Some(ref lang) = self.language_tag {
write!(f, "{}'{}'{}", self.charset, lang, encoded_value)
} else {
write!(f, "{}''{}", self.charset, encoded_value)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_extended_value_with_encoding_and_language_tag() {
let expected_language_tag = "en".parse::<LanguageTag>().unwrap();
// RFC 5987, Section 3.2.2
// Extended notation, using the Unicode character U+00A3 (POUND SIGN)
let result = parse_extended_value("iso-8859-1'en'%A3%20rates");
assert!(result.is_ok());
let extended_value = result.unwrap();
assert_eq!(Charset::Iso_8859_1, extended_value.charset);
assert!(extended_value.language_tag.is_some());
assert_eq!(expected_language_tag, extended_value.language_tag.unwrap());
assert_eq!(
vec![163, b' ', b'r', b'a', b't', b'e', b's'],
extended_value.value
);
}
#[test]
fn test_parse_extended_value_with_encoding() {
// RFC 5987, Section 3.2.2
// Extended notation, using the Unicode characters U+00A3 (POUND SIGN)
// and U+20AC (EURO SIGN)
let result = parse_extended_value("UTF-8''%c2%a3%20and%20%e2%82%ac%20rates");
assert!(result.is_ok());
let extended_value = result.unwrap();
assert_eq!(Charset::Ext("UTF-8".to_string()), extended_value.charset);
assert!(extended_value.language_tag.is_none());
assert_eq!(
vec![
194, 163, b' ', b'a', b'n', b'd', b' ', 226, 130, 172, b' ', b'r', b'a',
b't', b'e', b's',
],
extended_value.value
);
}
#[test]
fn test_parse_extended_value_missing_language_tag_and_encoding() {
// From: https://greenbytes.de/tech/tc2231/#attwithfn2231quot2
let result = parse_extended_value("foo%20bar.html");
assert!(result.is_err());
}
#[test]
fn test_parse_extended_value_partially_formatted() {
let result = parse_extended_value("UTF-8'missing third part");
assert!(result.is_err());
}
#[test]
fn test_parse_extended_value_partially_formatted_blank() {
let result = parse_extended_value("blank second part'");
assert!(result.is_err());
}
#[test]
fn test_fmt_extended_value_with_encoding_and_language_tag() {
let extended_value = ExtendedValue {
charset: Charset::Iso_8859_1,
language_tag: Some("en".parse().expect("Could not parse language tag")),
value: vec![163, b' ', b'r', b'a', b't', b'e', b's'],
};
assert_eq!("ISO-8859-1'en'%A3%20rates", format!("{}", extended_value));
}
#[test]
fn test_fmt_extended_value_with_encoding() {
let extended_value = ExtendedValue {
charset: Charset::Ext("UTF-8".to_string()),
language_tag: None,
value: vec![
194, 163, b' ', b'a', b'n', b'd', b' ', 226, 130, 172, b' ', b'r', b'a',
b't', b'e', b's',
],
};
assert_eq!(
"UTF-8''%C2%A3%20and%20%E2%82%AC%20rates",
format!("{}", extended_value)
);
}
}

View File

@ -48,7 +48,7 @@ impl From<SystemTime> for HttpDate {
impl IntoHeaderValue for HttpDate {
type Error = InvalidHeaderValue;
fn try_into(self) -> Result<HeaderValue, Self::Error> {
fn try_into_value(self) -> Result<HeaderValue, Self::Error> {
let mut wrt = BytesMut::with_capacity(29).writer();
write!(
wrt,

View File

@ -1,14 +1,16 @@
//! Copied for `hyper::header::shared`;
pub use self::charset::Charset;
pub use self::encoding::Encoding;
pub use self::entity::EntityTag;
pub use self::httpdate::HttpDate;
pub use self::quality_item::{q, qitem, Quality, QualityItem};
pub use language_tags::LanguageTag;
//! Originally taken from `hyper::header::shared`.
mod charset;
mod encoding;
mod entity;
mod extended;
mod httpdate;
mod quality_item;
pub use self::charset::Charset;
pub use self::encoding::Encoding;
pub use self::entity::EntityTag;
pub use self::extended::{parse_extended_value, ExtendedValue};
pub use self::httpdate::HttpDate;
pub use self::quality_item::{q, qitem, Quality, QualityItem};
pub use language_tags::LanguageTag;

View File

@ -0,0 +1,63 @@
use std::{fmt, str::FromStr};
use http::HeaderValue;
use crate::{error::ParseError, header::HTTP_VALUE};
/// Reads a comma-delimited raw header into a Vec.
#[inline]
pub fn from_comma_delimited<'a, I, T>(all: I) -> Result<Vec<T>, ParseError>
where
I: Iterator<Item = &'a HeaderValue> + 'a,
T: FromStr,
{
let mut result = Vec::new();
for h in all {
let s = h.to_str().map_err(|_| ParseError::Header)?;
result.extend(
s.split(',')
.filter_map(|x| match x.trim() {
"" => None,
y => Some(y),
})
.filter_map(|x| x.trim().parse().ok()),
)
}
Ok(result)
}
/// Reads a single string when parsing a header.
#[inline]
pub fn from_one_raw_str<T: FromStr>(val: Option<&HeaderValue>) -> Result<T, ParseError> {
if let Some(line) = val {
let line = line.to_str().map_err(|_| ParseError::Header)?;
if !line.is_empty() {
return T::from_str(line).or(Err(ParseError::Header));
}
}
Err(ParseError::Header)
}
/// Format an array into a comma-delimited string.
#[inline]
pub fn fmt_comma_delimited<T>(f: &mut fmt::Formatter<'_>, parts: &[T]) -> fmt::Result
where
T: fmt::Display,
{
let mut iter = parts.iter();
if let Some(part) = iter.next() {
fmt::Display::fmt(part, f)?;
}
for part in iter {
f.write_str(", ")?;
fmt::Display::fmt(part, f)?;
}
Ok(())
}
/// Percent encode a sequence of bytes with a character set defined in
/// <https://tools.ietf.org/html/rfc5987#section-3.2>
pub fn http_percent_encode(f: &mut fmt::Formatter<'_>, bytes: &[u8]) -> fmt::Result {
let encoded = percent_encoding::percent_encode(bytes, HTTP_VALUE);
fmt::Display::fmt(&encoded, f)
}

View File

@ -13,7 +13,7 @@ use crate::payload::Payload;
struct Cookies(Vec<Cookie<'static>>);
/// Trait that implements general purpose operations on http messages
/// Trait that implements general purpose operations on HTTP messages.
pub trait HttpMessage: Sized {
/// Type of message payload stream
type Stream;
@ -30,8 +30,8 @@ pub trait HttpMessage: Sized {
/// Mutable reference to a the request's extensions container
fn extensions_mut(&self) -> RefMut<'_, Extensions>;
/// Get a header.
#[doc(hidden)]
/// Get a header
fn get_header<H: Header>(&self) -> Option<H>
where
Self: Sized,
@ -43,8 +43,8 @@ pub trait HttpMessage: Sized {
}
}
/// Read the request content type. If request does not contain
/// *Content-Type* header, empty str get returned.
/// Read the request content type. If request did not contain a *Content-Type* header, an empty
/// string is returned.
fn content_type(&self) -> &str {
if let Some(content_type) = self.headers().get(header::CONTENT_TYPE) {
if let Ok(content_type) = content_type.to_str() {
@ -90,7 +90,7 @@ pub trait HttpMessage: Sized {
Ok(None)
}
/// Check if request has chunked transfer encoding
/// Check if request has chunked transfer encoding.
fn chunked(&self) -> Result<bool, ParseError> {
if let Some(encodings) = self.headers().get(header::TRANSFER_ENCODING) {
if let Ok(s) = encodings.to_str() {
@ -173,10 +173,12 @@ mod tests {
#[test]
fn test_content_type() {
let req = TestRequest::with_header("content-type", "text/plain").finish();
let req = TestRequest::default()
.insert_header(("content-type", "text/plain"))
.finish();
assert_eq!(req.content_type(), "text/plain");
let req =
TestRequest::with_header("content-type", "application/json; charset=utf=8")
let req = TestRequest::default()
.insert_header(("content-type", "application/json; charset=utf=8"))
.finish();
assert_eq!(req.content_type(), "application/json");
let req = TestRequest::default().finish();
@ -185,12 +187,14 @@ mod tests {
#[test]
fn test_mime_type() {
let req = TestRequest::with_header("content-type", "application/json").finish();
let req = TestRequest::default()
.insert_header(("content-type", "application/json"))
.finish();
assert_eq!(req.mime_type().unwrap(), Some(mime::APPLICATION_JSON));
let req = TestRequest::default().finish();
assert_eq!(req.mime_type().unwrap(), None);
let req =
TestRequest::with_header("content-type", "application/json; charset=utf-8")
let req = TestRequest::default()
.insert_header(("content-type", "application/json; charset=utf-8"))
.finish();
let mt = req.mime_type().unwrap().unwrap();
assert_eq!(mt.get_param(mime::CHARSET), Some(mime::UTF_8));
@ -200,10 +204,8 @@ mod tests {
#[test]
fn test_mime_type_error() {
let req = TestRequest::with_header(
"content-type",
"applicationadfadsfasdflknadsfklnadsfjson",
)
let req = TestRequest::default()
.insert_header(("content-type", "applicationadfadsfasdflknadsfklnadsfjson"))
.finish();
assert_eq!(Err(ContentTypeError::ParseError), req.mime_type());
}
@ -213,26 +215,26 @@ mod tests {
let req = TestRequest::default().finish();
assert_eq!(UTF_8.name(), req.encoding().unwrap().name());
let req = TestRequest::with_header("content-type", "application/json").finish();
let req = TestRequest::default()
.insert_header(("content-type", "application/json"))
.finish();
assert_eq!(UTF_8.name(), req.encoding().unwrap().name());
let req = TestRequest::with_header(
"content-type",
"application/json; charset=ISO-8859-2",
)
let req = TestRequest::default()
.insert_header(("content-type", "application/json; charset=ISO-8859-2"))
.finish();
assert_eq!(ISO_8859_2, req.encoding().unwrap());
}
#[test]
fn test_encoding_error() {
let req = TestRequest::with_header("content-type", "applicatjson").finish();
let req = TestRequest::default()
.insert_header(("content-type", "applicatjson"))
.finish();
assert_eq!(Some(ContentTypeError::ParseError), req.encoding().err());
let req = TestRequest::with_header(
"content-type",
"application/json; charset=kkkttktk",
)
let req = TestRequest::default()
.insert_header(("content-type", "application/json; charset=kkkttktk"))
.finish();
assert_eq!(
Some(ContentTypeError::UnknownEncoding),
@ -245,15 +247,16 @@ mod tests {
let req = TestRequest::default().finish();
assert!(!req.chunked().unwrap());
let req =
TestRequest::with_header(header::TRANSFER_ENCODING, "chunked").finish();
let req = TestRequest::default()
.insert_header((header::TRANSFER_ENCODING, "chunked"))
.finish();
assert!(req.chunked().unwrap());
let req = TestRequest::default()
.header(
.insert_header((
header::TRANSFER_ENCODING,
Bytes::from_static(b"some va\xadscc\xacas0xsdasdlue"),
)
))
.finish();
assert!(req.chunked().is_err());
}

View File

@ -53,7 +53,7 @@ pub use self::response::{Response, ResponseBuilder};
pub use self::service::HttpService;
pub mod http {
//! Various HTTP related types
//! Various HTTP related types.
// re-exports
pub use http::header::{HeaderName, HeaderValue};
@ -64,7 +64,7 @@ pub mod http {
pub use crate::cookie::{Cookie, CookieBuilder};
pub use crate::header::HeaderMap;
/// Various http headers
/// A collection of HTTP headers and helpers.
pub mod header {
pub use crate::header::*;
}

View File

@ -1,5 +1,9 @@
use std::cell::{Ref, RefMut};
use std::{fmt, net};
//! HTTP requests.
use std::{
cell::{Ref, RefMut},
fmt, net,
};
use http::{header, Method, Uri, Version};

View File

@ -1,10 +1,14 @@
//! Http response
use std::cell::{Ref, RefMut};
use std::convert::TryFrom;
use std::future::Future;
use std::pin::Pin;
use std::task::{Context, Poll};
use std::{fmt, str};
//! HTTP responses.
use std::{
cell::{Ref, RefMut},
convert::TryInto,
fmt,
future::Future,
pin::Pin,
str,
task::{Context, Poll},
};
use bytes::{Bytes, BytesMut};
use futures_core::Stream;
@ -14,7 +18,7 @@ use crate::body::{Body, BodyStream, MessageBody, ResponseBody};
use crate::cookie::{Cookie, CookieJar};
use crate::error::Error;
use crate::extensions::Extensions;
use crate::header::{Header, IntoHeaderValue};
use crate::header::{IntoHeaderPair, IntoHeaderValue};
use crate::http::header::{self, HeaderName, HeaderValue};
use crate::http::{Error as HttpError, HeaderMap, StatusCode};
use crate::message::{BoxedResponseHead, ConnectionType, ResponseHead};
@ -341,93 +345,96 @@ impl ResponseBuilder {
self
}
/// Set a header.
/// Insert a header, replacing any that were set with an equivalent field name.
///
/// ```rust
/// use actix_http::{http, Request, Response, Result};
/// # use actix_http::Response;
/// use actix_http::http::header::ContentType;
///
/// fn index(req: Request) -> Result<Response> {
/// Ok(Response::Ok()
/// .set(http::header::IfModifiedSince(
/// "Sun, 07 Nov 1994 08:48:37 GMT".parse()?,
/// ))
/// .finish())
/// }
/// ```
#[doc(hidden)]
pub fn set<H: Header>(&mut self, hdr: H) -> &mut Self {
if let Some(parts) = parts(&mut self.head, &self.err) {
match hdr.try_into() {
Ok(value) => {
parts.headers.append(H::name(), value);
}
Err(e) => self.err = Some(e.into()),
}
}
self
}
/// Append a header to existing headers.
///
/// ```rust
/// use actix_http::{http, Request, Response};
///
/// fn index(req: Request) -> Response {
/// Response::Ok()
/// .header("X-TEST", "value")
/// .header(http::header::CONTENT_TYPE, "application/json")
/// .finish()
/// }
/// .insert_header(ContentType(mime::APPLICATION_JSON))
/// .insert_header(("X-TEST", "value"))
/// .finish();
/// ```
pub fn header<K, V>(&mut self, key: K, value: V) -> &mut Self
pub fn insert_header<H>(&mut self, header: H) -> &mut Self
where
HeaderName: TryFrom<K>,
<HeaderName as TryFrom<K>>::Error: Into<HttpError>,
V: IntoHeaderValue,
H: IntoHeaderPair,
{
if let Some(parts) = parts(&mut self.head, &self.err) {
match HeaderName::try_from(key) {
Ok(key) => match value.try_into() {
Ok(value) => {
parts.headers.append(key, value);
}
Err(e) => self.err = Some(e.into()),
},
match header.try_into_header_pair() {
Ok((key, value)) => parts.headers.insert(key, value),
Err(e) => self.err = Some(e.into()),
};
}
self
}
/// Set a header.
/// Append a header, keeping any that were set with an equivalent field name.
///
/// ```rust
/// use actix_http::{http, Request, Response};
/// # use actix_http::Response;
/// use actix_http::http::header::ContentType;
///
/// fn index(req: Request) -> Response {
/// Response::Ok()
/// .set_header("X-TEST", "value")
/// .set_header(http::header::CONTENT_TYPE, "application/json")
/// .finish()
/// }
/// .append_header(ContentType(mime::APPLICATION_JSON))
/// .append_header(("X-TEST", "value1"))
/// .append_header(("X-TEST", "value2"))
/// .finish();
/// ```
pub fn append_header<H>(&mut self, header: H) -> &mut Self
where
H: IntoHeaderPair,
{
if let Some(parts) = parts(&mut self.head, &self.err) {
match header.try_into_header_pair() {
Ok((key, value)) => parts.headers.append(key, value),
Err(e) => self.err = Some(e.into()),
};
}
self
}
/// Replaced with [`Self::insert_header()`].
#[deprecated = "Replaced with `insert_header((key, value))`."]
pub fn set_header<K, V>(&mut self, key: K, value: V) -> &mut Self
where
HeaderName: TryFrom<K>,
<HeaderName as TryFrom<K>>::Error: Into<HttpError>,
K: TryInto<HeaderName>,
K::Error: Into<HttpError>,
V: IntoHeaderValue,
{
if let Some(parts) = parts(&mut self.head, &self.err) {
match HeaderName::try_from(key) {
Ok(key) => match value.try_into() {
Ok(value) => {
parts.headers.insert(key, value);
if self.err.is_some() {
return self;
}
Err(e) => self.err = Some(e.into()),
},
Err(e) => self.err = Some(e.into()),
};
match (key.try_into(), value.try_into_value()) {
(Ok(name), Ok(value)) => return self.insert_header((name, value)),
(Err(err), _) => self.err = Some(err.into()),
(_, Err(err)) => self.err = Some(err.into()),
}
self
}
/// Replaced with [`Self::append_header()`].
#[deprecated = "Replaced with `append_header((key, value))`."]
pub fn header<K, V>(&mut self, key: K, value: V) -> &mut Self
where
K: TryInto<HeaderName>,
K::Error: Into<HttpError>,
V: IntoHeaderValue,
{
if self.err.is_some() {
return self;
}
match (key.try_into(), value.try_into_value()) {
(Ok(name), Ok(value)) => return self.append_header((name, value)),
(Err(err), _) => self.err = Some(err.into()),
(_, Err(err)) => self.err = Some(err.into()),
}
self
}
@ -458,7 +465,12 @@ impl ResponseBuilder {
if let Some(parts) = parts(&mut self.head, &self.err) {
parts.set_connection_type(ConnectionType::Upgrade);
}
self.set_header(header::UPGRADE, value)
if let Ok(value) = value.try_into_value() {
self.insert_header((header::UPGRADE, value));
}
self
}
/// Force close connection, even if it is marked as keep-alive
@ -473,7 +485,7 @@ impl ResponseBuilder {
/// Disable chunked transfer encoding for HTTP/1.1 streaming responses.
#[inline]
pub fn no_chunking(&mut self, len: u64) -> &mut Self {
self.header(header::CONTENT_LENGTH, len);
self.insert_header((header::CONTENT_LENGTH, len));
if let Some(parts) = parts(&mut self.head, &self.err) {
parts.no_chunking(true);
@ -488,7 +500,7 @@ impl ResponseBuilder {
V: IntoHeaderValue,
{
if let Some(parts) = parts(&mut self.head, &self.err) {
match value.try_into() {
match value.try_into_value() {
Ok(value) => {
parts.headers.insert(header::CONTENT_TYPE, value);
}
@ -658,8 +670,9 @@ impl ResponseBuilder {
} else {
true
};
if !contains {
self.header(header::CONTENT_TYPE, "application/json");
self.insert_header(header::ContentType(mime::APPLICATION_JSON));
}
self.body(Body::from(body))
@ -848,6 +861,8 @@ impl From<BytesMut> for Response {
#[cfg(test)]
mod tests {
use serde_json::json;
use super::*;
use crate::body::Body;
use crate::http::header::{HeaderValue, CONTENT_TYPE, COOKIE, SET_COOKIE};
@ -855,8 +870,8 @@ mod tests {
#[test]
fn test_debug() {
let resp = Response::Ok()
.header(COOKIE, HeaderValue::from_static("cookie1=value1; "))
.header(COOKIE, HeaderValue::from_static("cookie2=value2; "))
.append_header((COOKIE, HeaderValue::from_static("cookie1=value1; ")))
.append_header((COOKIE, HeaderValue::from_static("cookie2=value2; ")))
.finish();
let dbg = format!("{:?}", resp);
assert!(dbg.contains("Response"));
@ -867,8 +882,8 @@ mod tests {
use crate::httpmessage::HttpMessage;
let req = crate::test::TestRequest::default()
.header(COOKIE, "cookie1=value1")
.header(COOKIE, "cookie2=value2")
.append_header((COOKIE, "cookie1=value1"))
.append_header((COOKIE, "cookie2=value2"))
.finish();
let cookies = req.cookies().unwrap();
@ -922,7 +937,7 @@ mod tests {
#[test]
fn test_basic_builder() {
let resp = Response::Ok().header("X-TEST", "value").finish();
let resp = Response::Ok().insert_header(("X-TEST", "value")).finish();
assert_eq!(resp.status(), StatusCode::OK);
}
@ -963,7 +978,7 @@ mod tests {
#[test]
fn test_json_ct() {
let resp = Response::build(StatusCode::OK)
.header(CONTENT_TYPE, "text/json")
.insert_header((CONTENT_TYPE, "text/json"))
.json(vec!["v1", "v2", "v3"]);
let ct = resp.headers().get(CONTENT_TYPE).unwrap();
assert_eq!(ct, HeaderValue::from_static("text/json"));
@ -981,7 +996,7 @@ mod tests {
#[test]
fn test_json2_ct() {
let resp = Response::build(StatusCode::OK)
.header(CONTENT_TYPE, "text/json")
.insert_header((CONTENT_TYPE, "text/json"))
.json2(&vec!["v1", "v2", "v3"]);
let ct = resp.headers().get(CONTENT_TYPE).unwrap();
assert_eq!(ct, HeaderValue::from_static("text/json"));
@ -1081,4 +1096,54 @@ mod tests {
let cookie = resp.cookies().next().unwrap();
assert_eq!((cookie.name(), cookie.value()), ("cookie1", "val100"));
}
#[test]
fn response_builder_header_insert_kv() {
let mut res = Response::Ok();
res.insert_header(("Content-Type", "application/octet-stream"));
let res = res.finish();
assert_eq!(
res.headers().get("Content-Type"),
Some(&HeaderValue::from_static("application/octet-stream"))
);
}
#[test]
fn response_builder_header_insert_typed() {
let mut res = Response::Ok();
res.insert_header(header::ContentType(mime::APPLICATION_OCTET_STREAM));
let res = res.finish();
assert_eq!(
res.headers().get("Content-Type"),
Some(&HeaderValue::from_static("application/octet-stream"))
);
}
#[test]
fn response_builder_header_append_kv() {
let mut res = Response::Ok();
res.append_header(("Content-Type", "application/octet-stream"));
res.append_header(("Content-Type", "application/json"));
let res = res.finish();
let headers: Vec<_> = res.headers().get_all("Content-Type").cloned().collect();
assert_eq!(headers.len(), 2);
assert!(headers.contains(&HeaderValue::from_static("application/octet-stream")));
assert!(headers.contains(&HeaderValue::from_static("application/json")));
}
#[test]
fn response_builder_header_append_typed() {
let mut res = Response::Ok();
res.append_header(header::ContentType(mime::APPLICATION_OCTET_STREAM));
res.append_header(header::ContentType(mime::APPLICATION_JSON));
let res = res.finish();
let headers: Vec<_> = res.headers().get_all("Content-Type").cloned().collect();
assert_eq!(headers.len(), 2);
assert!(headers.contains(&HeaderValue::from_static("application/octet-stream")));
assert!(headers.contains(&HeaderValue::from_static("application/json")));
}
}

View File

@ -2,7 +2,6 @@
use std::{
cell::{Ref, RefCell},
convert::TryFrom,
io::{self, Read, Write},
pin::Pin,
rc::Rc,
@ -12,14 +11,17 @@ use std::{
use actix_codec::{AsyncRead, AsyncWrite, ReadBuf};
use bytes::{Bytes, BytesMut};
use http::header::{self, HeaderName, HeaderValue};
use http::{Error as HttpError, Method, Uri, Version};
use http::{
header::{self, HeaderValue},
Method, Uri, Version,
};
use crate::cookie::{Cookie, CookieJar};
use crate::header::HeaderMap;
use crate::header::{Header, IntoHeaderValue};
use crate::payload::Payload;
use crate::Request;
use crate::{
cookie::{Cookie, CookieJar},
header::{HeaderMap, IntoHeaderPair},
payload::Payload,
Request,
};
/// Test `Request` builder
///
@ -36,7 +38,7 @@ use crate::Request;
/// }
/// }
///
/// let resp = TestRequest::with_header("content-type", "text/plain")
/// let resp = TestRequest::default().insert_header("content-type", "text/plain")
/// .run(&index)
/// .unwrap();
/// assert_eq!(resp.status(), StatusCode::OK);
@ -69,76 +71,73 @@ impl Default for TestRequest {
}
impl TestRequest {
/// Create TestRequest and set request uri
/// Create a default TestRequest and then set its URI.
pub fn with_uri(path: &str) -> TestRequest {
TestRequest::default().uri(path).take()
}
/// Create TestRequest and set header
pub fn with_hdr<H: Header>(hdr: H) -> TestRequest {
TestRequest::default().set(hdr).take()
}
/// Create TestRequest and set header
pub fn with_header<K, V>(key: K, value: V) -> TestRequest
where
HeaderName: TryFrom<K>,
<HeaderName as TryFrom<K>>::Error: Into<HttpError>,
V: IntoHeaderValue,
{
TestRequest::default().header(key, value).take()
}
/// Set HTTP version of this request
/// Set HTTP version of this request.
pub fn version(&mut self, ver: Version) -> &mut Self {
parts(&mut self.0).version = ver;
self
}
/// Set HTTP method of this request
/// Set HTTP method of this request.
pub fn method(&mut self, meth: Method) -> &mut Self {
parts(&mut self.0).method = meth;
self
}
/// Set HTTP Uri of this request
/// Set URI of this request.
///
/// # Panics
/// If provided URI is invalid.
pub fn uri(&mut self, path: &str) -> &mut Self {
parts(&mut self.0).uri = Uri::from_str(path).unwrap();
self
}
/// Set a header
pub fn set<H: Header>(&mut self, hdr: H) -> &mut Self {
if let Ok(value) = hdr.try_into() {
parts(&mut self.0).headers.append(H::name(), value);
return self;
}
panic!("Can not set header");
}
/// Set a header
pub fn header<K, V>(&mut self, key: K, value: V) -> &mut Self
/// Insert a header, replacing any that were set with an equivalent field name.
pub fn insert_header<H>(&mut self, header: H) -> &mut Self
where
HeaderName: TryFrom<K>,
<HeaderName as TryFrom<K>>::Error: Into<HttpError>,
V: IntoHeaderValue,
H: IntoHeaderPair,
{
if let Ok(key) = HeaderName::try_from(key) {
if let Ok(value) = value.try_into() {
parts(&mut self.0).headers.append(key, value);
return self;
match header.try_into_header_pair() {
Ok((key, value)) => {
parts(&mut self.0).headers.insert(key, value);
}
Err(err) => {
panic!("Error inserting test header: {}.", err.into());
}
panic!("Can not create header");
}
/// Set cookie for this request
self
}
/// Append a header, keeping any that were set with an equivalent field name.
pub fn append_header<H>(&mut self, header: H) -> &mut Self
where
H: IntoHeaderPair,
{
match header.try_into_header_pair() {
Ok((key, value)) => {
parts(&mut self.0).headers.append(key, value);
}
Err(err) => {
panic!("Error inserting test header: {}.", err.into());
}
}
self
}
/// Set cookie for this request.
pub fn cookie<'a>(&mut self, cookie: Cookie<'a>) -> &mut Self {
parts(&mut self.0).cookies.add(cookie.into_owned());
self
}
/// Set request payload
/// Set request payload.
pub fn set_payload<B: Into<Bytes>>(&mut self, data: B) -> &mut Self {
let mut payload = crate::h1::Payload::empty();
payload.unread_data(data.into());
@ -150,7 +149,7 @@ impl TestRequest {
TestRequest(self.0.take())
}
/// Complete request creation and generate `Request` instance
/// Complete request creation and generate `Request` instance.
pub fn finish(&mut self) -> Request {
let inner = self.0.take().expect("cannot reuse test request builder");

View File

@ -101,7 +101,7 @@ impl ResponseError for HandshakeError {
fn error_response(&self) -> Response {
match self {
HandshakeError::GetMethodRequired => Response::MethodNotAllowed()
.header(header::ALLOW, "GET")
.insert_header((header::ALLOW, "GET"))
.finish(),
HandshakeError::NoWebsocketUpgrade => Response::BadRequest()
@ -198,8 +198,8 @@ pub fn handshake_response(req: &RequestHead) -> ResponseBuilder {
Response::build(StatusCode::SWITCHING_PROTOCOLS)
.upgrade("websocket")
.header(header::TRANSFER_ENCODING, "chunked")
.header(header::SEC_WEBSOCKET_ACCEPT, key.as_str())
.insert_header((header::TRANSFER_ENCODING, "chunked"))
.insert_header((header::SEC_WEBSOCKET_ACCEPT, key))
.take()
}
@ -224,7 +224,7 @@ mod tests {
);
let req = TestRequest::default()
.header(header::UPGRADE, header::HeaderValue::from_static("test"))
.insert_header((header::UPGRADE, header::HeaderValue::from_static("test")))
.finish();
assert_eq!(
HandshakeError::NoWebsocketUpgrade,
@ -232,10 +232,10 @@ mod tests {
);
let req = TestRequest::default()
.header(
.insert_header((
header::UPGRADE,
header::HeaderValue::from_static("websocket"),
)
))
.finish();
assert_eq!(
HandshakeError::NoConnectionUpgrade,
@ -243,14 +243,14 @@ mod tests {
);
let req = TestRequest::default()
.header(
.insert_header((
header::UPGRADE,
header::HeaderValue::from_static("websocket"),
)
.header(
))
.insert_header((
header::CONNECTION,
header::HeaderValue::from_static("upgrade"),
)
))
.finish();
assert_eq!(
HandshakeError::NoVersionHeader,
@ -258,18 +258,18 @@ mod tests {
);
let req = TestRequest::default()
.header(
.insert_header((
header::UPGRADE,
header::HeaderValue::from_static("websocket"),
)
.header(
))
.insert_header((
header::CONNECTION,
header::HeaderValue::from_static("upgrade"),
)
.header(
))
.insert_header((
header::SEC_WEBSOCKET_VERSION,
header::HeaderValue::from_static("5"),
)
))
.finish();
assert_eq!(
HandshakeError::UnsupportedVersion,
@ -277,18 +277,18 @@ mod tests {
);
let req = TestRequest::default()
.header(
.insert_header((
header::UPGRADE,
header::HeaderValue::from_static("websocket"),
)
.header(
))
.insert_header((
header::CONNECTION,
header::HeaderValue::from_static("upgrade"),
)
.header(
))
.insert_header((
header::SEC_WEBSOCKET_VERSION,
header::HeaderValue::from_static("13"),
)
))
.finish();
assert_eq!(
HandshakeError::BadWebsocketKey,
@ -296,22 +296,22 @@ mod tests {
);
let req = TestRequest::default()
.header(
.insert_header((
header::UPGRADE,
header::HeaderValue::from_static("websocket"),
)
.header(
))
.insert_header((
header::CONNECTION,
header::HeaderValue::from_static("upgrade"),
)
.header(
))
.insert_header((
header::SEC_WEBSOCKET_VERSION,
header::HeaderValue::from_static("13"),
)
.header(
))
.insert_header((
header::SEC_WEBSOCKET_KEY,
header::HeaderValue::from_static("13"),
)
))
.finish();
assert_eq!(
StatusCode::SWITCHING_PROTOCOLS,

View File

@ -38,7 +38,7 @@ async fn test_h1_v2() {
let response = srv.get("/").send().await.unwrap();
assert!(response.status().is_success());
let request = srv.get("/").header("x-test", "111").send();
let request = srv.get("/").insert_header(("x-test", "111")).send();
let mut response = request.await.unwrap();
assert!(response.status().is_success());

View File

@ -173,8 +173,8 @@ async fn test_h2_headers() {
HttpService::build().h2(move |_| {
let mut builder = Response::Ok();
for idx in 0..90 {
builder.header(
format!("X-TEST-{}", idx).as_str(),
builder.insert_header(
(format!("X-TEST-{}", idx).as_str(),
"TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \
TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \
TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \
@ -188,7 +188,7 @@ async fn test_h2_headers() {
TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \
TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \
TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST ",
);
));
}
ok::<_, ()>(builder.body(data.clone()))
})
@ -341,7 +341,7 @@ async fn test_h2_body_chunked_explicit() {
let body = once(ok::<_, Error>(Bytes::from_static(STR.as_ref())));
ok::<_, ()>(
Response::Ok()
.header(header::TRANSFER_ENCODING, "chunked")
.insert_header((header::TRANSFER_ENCODING, "chunked"))
.streaming(body),
)
})
@ -369,7 +369,7 @@ async fn test_h2_response_http_error_handling() {
let broken_header = Bytes::from_static(b"\0\0\0");
ok::<_, ()>(
Response::Ok()
.header(header::CONTENT_TYPE, broken_header)
.insert_header((header::CONTENT_TYPE, broken_header))
.body(STR),
)
}))

View File

@ -181,7 +181,7 @@ async fn test_h2_headers() {
HttpService::build().h2(move |_| {
let mut config = Response::Ok();
for idx in 0..90 {
config.header(
config.insert_header((
format!("X-TEST-{}", idx).as_str(),
"TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \
TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \
@ -196,7 +196,7 @@ async fn test_h2_headers() {
TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \
TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \
TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST ",
);
));
}
future::ok::<_, ()>(config.body(data.clone()))
})
@ -352,7 +352,7 @@ async fn test_h2_body_chunked_explicit() {
let body = once(ok::<_, Error>(Bytes::from_static(STR.as_ref())));
ok::<_, ()>(
Response::Ok()
.header(header::TRANSFER_ENCODING, "chunked")
.insert_header((header::TRANSFER_ENCODING, "chunked"))
.streaming(body),
)
})
@ -380,7 +380,7 @@ async fn test_h2_response_http_error_handling() {
let broken_header = Bytes::from_static(b"\0\0\0");
ok::<_, ()>(
Response::Ok()
.header(http::header::CONTENT_TYPE, broken_header)
.insert_header((http::header::CONTENT_TYPE, broken_header))
.body(STR),
)
}))

View File

@ -392,7 +392,7 @@ async fn test_h1_headers() {
HttpService::build().h1(move |_| {
let mut builder = Response::Ok();
for idx in 0..90 {
builder.header(
builder.insert_header((
format!("X-TEST-{}", idx).as_str(),
"TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \
TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \
@ -407,7 +407,7 @@ async fn test_h1_headers() {
TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \
TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \
TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST ",
);
));
}
future::ok::<_, ()>(builder.body(data.clone()))
}).tcp()
@ -561,7 +561,7 @@ async fn test_h1_body_chunked_explicit() {
let body = once(ok::<_, Error>(Bytes::from_static(STR.as_ref())));
ok::<_, ()>(
Response::Ok()
.header(header::TRANSFER_ENCODING, "chunked")
.insert_header((header::TRANSFER_ENCODING, "chunked"))
.streaming(body),
)
})
@ -625,7 +625,7 @@ async fn test_h1_response_http_error_handling() {
let broken_header = Bytes::from_static(b"\0\0\0");
ok::<_, ()>(
Response::Ok()
.header(http::header::CONTENT_TYPE, broken_header)
.insert_header((http::header::CONTENT_TYPE, broken_header))
.body(STR),
)
}))

View File

@ -166,11 +166,11 @@ pub fn handshake_with_protocols(
let mut response = HttpResponse::build(StatusCode::SWITCHING_PROTOCOLS)
.upgrade("websocket")
.header(header::SEC_WEBSOCKET_ACCEPT, key.as_str())
.insert_header((header::SEC_WEBSOCKET_ACCEPT, key))
.take();
if let Some(protocol) = protocol {
response.header(&header::SEC_WEBSOCKET_PROTOCOL, protocol);
response.insert_header((header::SEC_WEBSOCKET_PROTOCOL, protocol));
}
Ok(response)
@ -573,7 +573,7 @@ mod tests {
);
let req = TestRequest::default()
.header(header::UPGRADE, header::HeaderValue::from_static("test"))
.insert_header((header::UPGRADE, header::HeaderValue::from_static("test")))
.to_http_request();
assert_eq!(
HandshakeError::NoWebsocketUpgrade,
@ -581,10 +581,10 @@ mod tests {
);
let req = TestRequest::default()
.header(
.insert_header((
header::UPGRADE,
header::HeaderValue::from_static("websocket"),
)
))
.to_http_request();
assert_eq!(
HandshakeError::NoConnectionUpgrade,
@ -592,14 +592,14 @@ mod tests {
);
let req = TestRequest::default()
.header(
.insert_header((
header::UPGRADE,
header::HeaderValue::from_static("websocket"),
)
.header(
))
.insert_header((
header::CONNECTION,
header::HeaderValue::from_static("upgrade"),
)
))
.to_http_request();
assert_eq!(
HandshakeError::NoVersionHeader,
@ -607,18 +607,18 @@ mod tests {
);
let req = TestRequest::default()
.header(
.insert_header((
header::UPGRADE,
header::HeaderValue::from_static("websocket"),
)
.header(
))
.insert_header((
header::CONNECTION,
header::HeaderValue::from_static("upgrade"),
)
.header(
))
.insert_header((
header::SEC_WEBSOCKET_VERSION,
header::HeaderValue::from_static("5"),
)
))
.to_http_request();
assert_eq!(
HandshakeError::UnsupportedVersion,
@ -626,18 +626,18 @@ mod tests {
);
let req = TestRequest::default()
.header(
.insert_header((
header::UPGRADE,
header::HeaderValue::from_static("websocket"),
)
.header(
))
.insert_header((
header::CONNECTION,
header::HeaderValue::from_static("upgrade"),
)
.header(
))
.insert_header((
header::SEC_WEBSOCKET_VERSION,
header::HeaderValue::from_static("13"),
)
))
.to_http_request();
assert_eq!(
HandshakeError::BadWebsocketKey,
@ -645,22 +645,22 @@ mod tests {
);
let req = TestRequest::default()
.header(
.insert_header((
header::UPGRADE,
header::HeaderValue::from_static("websocket"),
)
.header(
))
.insert_header((
header::CONNECTION,
header::HeaderValue::from_static("upgrade"),
)
.header(
))
.insert_header((
header::SEC_WEBSOCKET_VERSION,
header::HeaderValue::from_static("13"),
)
.header(
))
.insert_header((
header::SEC_WEBSOCKET_KEY,
header::HeaderValue::from_static("13"),
)
))
.to_http_request();
let resp = handshake(&req).unwrap().finish();
@ -669,26 +669,26 @@ mod tests {
assert_eq!(None, resp.headers().get(&header::TRANSFER_ENCODING));
let req = TestRequest::default()
.header(
.insert_header((
header::UPGRADE,
header::HeaderValue::from_static("websocket"),
)
.header(
))
.insert_header((
header::CONNECTION,
header::HeaderValue::from_static("upgrade"),
)
.header(
))
.insert_header((
header::SEC_WEBSOCKET_VERSION,
header::HeaderValue::from_static("13"),
)
.header(
))
.insert_header((
header::SEC_WEBSOCKET_KEY,
header::HeaderValue::from_static("13"),
)
.header(
))
.insert_header((
header::SEC_WEBSOCKET_PROTOCOL,
header::HeaderValue::from_static("graphql"),
)
))
.to_http_request();
let protocols = ["graphql"];
@ -710,26 +710,26 @@ mod tests {
);
let req = TestRequest::default()
.header(
.insert_header((
header::UPGRADE,
header::HeaderValue::from_static("websocket"),
)
.header(
))
.insert_header((
header::CONNECTION,
header::HeaderValue::from_static("upgrade"),
)
.header(
))
.insert_header((
header::SEC_WEBSOCKET_VERSION,
header::HeaderValue::from_static("13"),
)
.header(
))
.insert_header((
header::SEC_WEBSOCKET_KEY,
header::HeaderValue::from_static("13"),
)
.header(
))
.insert_header((
header::SEC_WEBSOCKET_PROTOCOL,
header::HeaderValue::from_static("p1, p2, p3"),
)
))
.to_http_request();
let protocols = vec!["p3", "p2"];
@ -751,26 +751,26 @@ mod tests {
);
let req = TestRequest::default()
.header(
.insert_header((
header::UPGRADE,
header::HeaderValue::from_static("websocket"),
)
.header(
))
.insert_header((
header::CONNECTION,
header::HeaderValue::from_static("upgrade"),
)
.header(
))
.insert_header((
header::SEC_WEBSOCKET_VERSION,
header::HeaderValue::from_static("13"),
)
.header(
))
.insert_header((
header::SEC_WEBSOCKET_KEY,
header::HeaderValue::from_static("13"),
)
.header(
))
.insert_header((
header::SEC_WEBSOCKET_PROTOCOL,
header::HeaderValue::from_static("p1,p2,p3"),
)
))
.to_http_request();
let protocols = vec!["p3", "p2"];

View File

@ -1,6 +1,17 @@
# Changes
## Unreleased - 2021-xx-xx
### Added
* `ClientRequest::insert_header` method which allows using typed headers. [#1869]
* `ClientRequest::append_header` method which allows using typed headers. [#1869]
### Removed
* `ClientRequest::set`; use `ClientRequest::insert_header`. [#1869]
* `ClientRequest::set_header`; use `ClientRequest::insert_header`. [#1869]
* `ClientRequest::set_header_if_none`; use `ClientRequest::insert_header_if_none`. [#1869]
* `ClientRequest::header`; use `ClientRequest::append_header`. [#1869]
[#1869]: https://github.com/actix/actix-web/pull/1869
## 3.0.0-beta.1 - 2021-01-07

View File

@ -133,7 +133,7 @@ impl ClientBuilder {
V::Error: fmt::Debug,
{
match HeaderName::try_from(key) {
Ok(key) => match value.try_into() {
Ok(key) => match value.try_into_value() {
Ok(value) => {
self.headers.append(key, value);
}

View File

@ -144,7 +144,7 @@ impl FrozenSendBuilder {
V: IntoHeaderValue,
{
match HeaderName::try_from(key) {
Ok(key) => match value.try_into() {
Ok(key) => match value.try_into_value() {
Ok(value) => self.extra_headers.insert(key, value),
Err(e) => self.err = Some(e.into()),
},

View File

@ -7,7 +7,7 @@
//! # async fn main() -> Result<(), awc::error::SendRequestError> {
//! let mut client = awc::Client::default();
//! let response = client.get("http://www.rust-lang.org") // <- Create request builder
//! .header("User-Agent", "Actix-web")
//! .insert_header(("User-Agent", "Actix-web"))
//! .send() // <- Send http request
//! .await?;
//!
@ -134,7 +134,7 @@ use self::connect::{Connect, ConnectorWrapper};
/// let mut client = Client::default();
///
/// let res = client.get("http://www.rust-lang.org") // <- Create request builder
/// .header("User-Agent", "Actix-web")
/// .insert_header(("User-Agent", "Actix-web"))
/// .send() // <- Send http request
/// .await; // <- send request and wait for response
///
@ -182,8 +182,8 @@ impl Client {
{
let mut req = ClientRequest::new(method, url, self.0.clone());
for (key, value) in self.0.headers.iter() {
req = req.set_header_if_none(key.clone(), value.clone());
for header in self.0.headers.iter() {
req = req.insert_header_if_none(header);
}
req
}
@ -198,8 +198,8 @@ impl Client {
<Uri as TryFrom<U>>::Error: Into<HttpError>,
{
let mut req = self.request(head.method.clone(), url);
for (key, value) in head.headers.iter() {
req = req.set_header_if_none(key.clone(), value.clone());
for header in head.headers.iter() {
req = req.insert_header_if_none(header);
}
req
}

View File

@ -9,10 +9,10 @@ use serde::Serialize;
use actix_http::body::Body;
use actix_http::cookie::{Cookie, CookieJar};
use actix_http::http::header::{self, Header, IntoHeaderValue};
use actix_http::http::header::{self, IntoHeaderPair};
use actix_http::http::{
uri, ConnectionType, Error as HttpError, HeaderMap, HeaderName, HeaderValue, Method,
Uri, Version,
uri, ConnectionType, Error as HttpError, HeaderMap, HeaderValue, Method, Uri,
Version,
};
use actix_http::{Error, RequestHead};
@ -37,13 +37,11 @@ cfg_if::cfg_if! {
/// builder-like pattern.
///
/// ```rust
/// use actix_rt::System;
///
/// #[actix_rt::main]
/// async fn main() {
/// let response = awc::Client::new()
/// .get("http://www.rust-lang.org") // <- Create request builder
/// .header("User-Agent", "Actix-web")
/// .insert_header(("User-Agent", "Actix-web"))
/// .send() // <- Send http request
/// .await;
///
@ -143,110 +141,71 @@ impl ClientRequest {
&self.head.peer_addr
}
#[inline]
/// Returns request's headers.
#[inline]
pub fn headers(&self) -> &HeaderMap {
&self.head.headers
}
#[inline]
/// Returns request's mutable headers.
#[inline]
pub fn headers_mut(&mut self) -> &mut HeaderMap {
&mut self.head.headers
}
/// Set a header.
///
/// ```rust
/// fn main() {
/// # actix_rt::System::new("test").block_on(futures_util::future::lazy(|_| {
/// let req = awc::Client::new()
/// .get("http://www.rust-lang.org")
/// .set(awc::http::header::Date::now())
/// .set(awc::http::header::ContentType(mime::TEXT_HTML));
/// # Ok::<_, ()>(())
/// # }));
/// }
/// ```
pub fn set<H: Header>(mut self, hdr: H) -> Self {
match hdr.try_into() {
Ok(value) => {
self.head.headers.insert(H::name(), value);
}
Err(e) => self.err = Some(e.into()),
}
self
}
/// Append a header.
///
/// Header gets appended to existing header.
/// To override header use `set_header()` method.
///
/// ```rust
/// use awc::{http, Client};
///
/// fn main() {
/// # actix_rt::System::new("test").block_on(async {
/// let req = Client::new()
/// .get("http://www.rust-lang.org")
/// .header("X-TEST", "value")
/// .header(http::header::CONTENT_TYPE, "application/json");
/// # Ok::<_, ()>(())
/// # });
/// }
/// ```
pub fn header<K, V>(mut self, key: K, value: V) -> Self
/// Insert a header, replacing any that were set with an equivalent field name.
pub fn insert_header<H>(mut self, header: H) -> Self
where
HeaderName: TryFrom<K>,
<HeaderName as TryFrom<K>>::Error: Into<HttpError>,
V: IntoHeaderValue,
H: IntoHeaderPair,
{
match HeaderName::try_from(key) {
Ok(key) => match value.try_into() {
Ok(value) => self.head.headers.append(key, value),
match header.try_into_header_pair() {
Ok((key, value)) => self.head.headers.insert(key, value),
Err(e) => self.err = Some(e.into()),
},
Err(e) => self.err = Some(e.into()),
}
self
}
};
/// Insert a header, replaces existing header.
pub fn set_header<K, V>(mut self, key: K, value: V) -> Self
where
HeaderName: TryFrom<K>,
<HeaderName as TryFrom<K>>::Error: Into<HttpError>,
V: IntoHeaderValue,
{
match HeaderName::try_from(key) {
Ok(key) => match value.try_into() {
Ok(value) => self.head.headers.insert(key, value),
Err(e) => self.err = Some(e.into()),
},
Err(e) => self.err = Some(e.into()),
}
self
}
/// Insert a header only if it is not yet set.
pub fn set_header_if_none<K, V>(mut self, key: K, value: V) -> Self
pub fn insert_header_if_none<H>(mut self, header: H) -> Self
where
HeaderName: TryFrom<K>,
<HeaderName as TryFrom<K>>::Error: Into<HttpError>,
V: IntoHeaderValue,
H: IntoHeaderPair,
{
match HeaderName::try_from(key) {
Ok(key) => {
match header.try_into_header_pair() {
Ok((key, value)) => {
if !self.head.headers.contains_key(&key) {
match value.try_into() {
Ok(value) => self.head.headers.insert(key, value),
Err(e) => self.err = Some(e.into()),
}
self.head.headers.insert(key, value);
}
}
Err(e) => self.err = Some(e.into()),
};
self
}
/// Append a header, keeping any that were set with an equivalent field name.
///
/// ```rust
/// # #[actix_rt::main]
/// # async fn main() {
/// # use awc::Client;
/// use awc::http::header::ContentType;
///
/// Client::new()
/// .get("http://www.rust-lang.org")
/// .insert_header(("X-TEST", "value"))
/// .insert_header(ContentType(mime::APPLICATION_JSON));
/// # }
/// ```
pub fn append_header<H>(mut self, header: H) -> Self
where
H: IntoHeaderPair,
{
match header.try_into_header_pair() {
Ok((key, value)) => self.head.headers.append(key, value),
Err(e) => self.err = Some(e.into()),
};
self
}
@ -282,7 +241,7 @@ impl ClientRequest {
/// Set content length
#[inline]
pub fn content_length(self, len: u64) -> Self {
self.header(header::CONTENT_LENGTH, len)
self.append_header((header::CONTENT_LENGTH, len))
}
/// Set HTTP basic authorization header
@ -294,10 +253,10 @@ impl ClientRequest {
Some(password) => format!("{}:{}", username, password),
None => format!("{}:", username),
};
self.header(
self.append_header((
header::AUTHORIZATION,
format!("Basic {}", base64::encode(&auth)),
)
))
}
/// Set HTTP bearer authentication header
@ -305,7 +264,7 @@ impl ClientRequest {
where
T: fmt::Display,
{
self.header(header::AUTHORIZATION, format!("Bearer {}", token))
self.append_header((header::AUTHORIZATION, format!("Bearer {}", token)))
}
/// Set a cookie
@ -557,12 +516,15 @@ impl ClientRequest {
.unwrap_or(true);
if https {
slf = slf.set_header_if_none(header::ACCEPT_ENCODING, HTTPS_ENCODING)
slf =
slf.insert_header_if_none((header::ACCEPT_ENCODING, HTTPS_ENCODING))
} else {
#[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))]
{
slf =
slf.set_header_if_none(header::ACCEPT_ENCODING, "gzip, deflate")
slf = slf.insert_header_if_none((
header::ACCEPT_ENCODING,
"gzip, deflate",
))
}
};
}
@ -595,7 +557,7 @@ mod tests {
#[actix_rt::test]
async fn test_debug() {
let request = Client::new().get("/").header("x-test", "111");
let request = Client::new().get("/").append_header(("x-test", "111"));
let repr = format!("{:?}", request);
assert!(repr.contains("ClientRequest"));
assert!(repr.contains("x-test"));
@ -606,18 +568,18 @@ mod tests {
let req = Client::new()
.put("/")
.version(Version::HTTP_2)
.set(header::Date(SystemTime::now().into()))
.insert_header(header::Date(SystemTime::now().into()))
.content_type("plain/text")
.header(header::SERVER, "awc");
.append_header((header::SERVER, "awc"));
let req = if let Some(val) = Some("server") {
req.header(header::USER_AGENT, val)
req.append_header((header::USER_AGENT, val))
} else {
req
};
let req = if let Some(_val) = Option::<&str>::None {
req.header(header::ALLOW, "1")
req.append_header((header::ALLOW, "1"))
} else {
req
};
@ -660,7 +622,7 @@ mod tests {
.header(header::CONTENT_TYPE, "111")
.finish()
.get("/")
.set_header(header::CONTENT_TYPE, "222");
.insert_header((header::CONTENT_TYPE, "222"));
assert_eq!(
req.head

View File

@ -296,7 +296,7 @@ impl RequestSender {
match self {
RequestSender::Owned(head) => {
if !head.headers.contains_key(&key) {
match value.try_into() {
match value.try_into_value() {
Ok(value) => head.headers.insert(key, value),
Err(e) => return Err(e.into()),
}
@ -306,7 +306,7 @@ impl RequestSender {
if !head.headers.contains_key(&key)
&& !extra_headers.iter().any(|h| h.contains_key(&key))
{
match value.try_into() {
match value.try_into_value() {
Ok(v) => {
let h = extra_headers.get_or_insert(HeaderMap::new());
h.insert(key, v)

View File

@ -45,7 +45,7 @@ impl TestResponse {
/// Set a header
pub fn set<H: Header>(mut self, hdr: H) -> Self {
if let Ok(value) = hdr.try_into() {
if let Ok(value) = hdr.try_into_value() {
self.head.headers.append(H::name(), value);
return self;
}
@ -60,7 +60,7 @@ impl TestResponse {
V: IntoHeaderValue,
{
if let Ok(key) = HeaderName::try_from(key) {
if let Ok(value) = value.try_into() {
if let Ok(value) = value.try_into_value() {
self.head.headers.append(key, value);
return self;
}

View File

@ -170,7 +170,7 @@ impl WebsocketsRequest {
V: IntoHeaderValue,
{
match HeaderName::try_from(key) {
Ok(key) => match value.try_into() {
Ok(key) => match value.try_into_value() {
Ok(value) => {
self.head.headers.append(key, value);
}
@ -189,7 +189,7 @@ impl WebsocketsRequest {
V: IntoHeaderValue,
{
match HeaderName::try_from(key) {
Ok(key) => match value.try_into() {
Ok(key) => match value.try_into_value() {
Ok(value) => {
self.head.headers.insert(key, value);
}
@ -210,7 +210,7 @@ impl WebsocketsRequest {
match HeaderName::try_from(key) {
Ok(key) => {
if !self.head.headers.contains_key(&key) {
match value.try_into() {
match value.try_into_value() {
Ok(value) => {
self.head.headers.insert(key, value);
}

View File

@ -52,7 +52,7 @@ async fn test_simple() {
.service(web::resource("/").route(web::to(|| HttpResponse::Ok().body(STR))))
});
let request = srv.get("/").header("x-test", "111").send();
let request = srv.get("/").insert_header(("x-test", "111")).send();
let mut response = request.await.unwrap();
assert!(response.status().is_success());
@ -82,7 +82,7 @@ async fn test_json() {
let request = srv
.get("/")
.header("x-test", "111")
.insert_header(("x-test", "111"))
.send_json(&"TEST".to_string());
let response = request.await.unwrap();
assert!(response.status().is_success());
@ -99,7 +99,10 @@ async fn test_form() {
let mut data = HashMap::new();
let _ = data.insert("key".to_string(), "TEST".to_string());
let request = srv.get("/").header("x-test", "111").send_form(&data);
let request = srv
.get("/")
.append_header(("x-test", "111"))
.send_form(&data);
let response = request.await.unwrap();
assert!(response.status().is_success());
}
@ -438,7 +441,7 @@ async fn test_client_gzip_encoding() {
let data = e.finish().unwrap();
HttpResponse::Ok()
.header("content-encoding", "gzip")
.insert_header(("content-encoding", "gzip"))
.body(data)
})))
});
@ -461,7 +464,7 @@ async fn test_client_gzip_encoding_large() {
let data = e.finish().unwrap();
HttpResponse::Ok()
.header("content-encoding", "gzip")
.insert_header(("content-encoding", "gzip"))
.body(data)
})))
});
@ -489,7 +492,7 @@ async fn test_client_gzip_encoding_large_random() {
e.write_all(&data).unwrap();
let data = e.finish().unwrap();
HttpResponse::Ok()
.header("content-encoding", "gzip")
.insert_header(("content-encoding", "gzip"))
.body(data)
})))
});
@ -511,7 +514,7 @@ async fn test_client_brotli_encoding() {
e.write_all(&data).unwrap();
let data = e.finish().unwrap();
HttpResponse::Ok()
.header("content-encoding", "br")
.insert_header(("content-encoding", "br"))
.body(data)
})))
});
@ -539,7 +542,7 @@ async fn test_client_brotli_encoding_large_random() {
e.write_all(&data).unwrap();
let data = e.finish().unwrap();
HttpResponse::Ok()
.header("content-encoding", "br")
.insert_header(("content-encoding", "br"))
.body(data)
})))
});

View File

@ -10,7 +10,7 @@ async fn main() -> Result<(), Error> {
// Create request builder, configure request and send
let mut response = client
.get("https://www.rust-lang.org/")
.header("User-Agent", "Actix-web")
.append_header(("User-Agent", "Actix-web"))
.send()
.await?;

View File

@ -347,10 +347,8 @@ mod tests {
#[actix_rt::test]
async fn test_option() {
let (req, mut pl) = TestRequest::with_header(
header::CONTENT_TYPE,
"application/x-www-form-urlencoded",
)
let (req, mut pl) = TestRequest::default()
.insert_header((header::CONTENT_TYPE, "application/x-www-form-urlencoded"))
.data(FormConfig::default().limit(4096))
.to_http_parts();
@ -359,11 +357,9 @@ mod tests {
.unwrap();
assert_eq!(r, None);
let (req, mut pl) = TestRequest::with_header(
header::CONTENT_TYPE,
"application/x-www-form-urlencoded",
)
.header(header::CONTENT_LENGTH, "9")
let (req, mut pl) = TestRequest::default()
.insert_header((header::CONTENT_TYPE, "application/x-www-form-urlencoded"))
.insert_header((header::CONTENT_LENGTH, "9"))
.set_payload(Bytes::from_static(b"hello=world"))
.to_http_parts();
@ -377,11 +373,9 @@ mod tests {
}))
);
let (req, mut pl) = TestRequest::with_header(
header::CONTENT_TYPE,
"application/x-www-form-urlencoded",
)
.header(header::CONTENT_LENGTH, "9")
let (req, mut pl) = TestRequest::default()
.insert_header((header::CONTENT_TYPE, "application/x-www-form-urlencoded"))
.insert_header((header::CONTENT_LENGTH, "9"))
.set_payload(Bytes::from_static(b"bye=world"))
.to_http_parts();
@ -393,11 +387,9 @@ mod tests {
#[actix_rt::test]
async fn test_result() {
let (req, mut pl) = TestRequest::with_header(
header::CONTENT_TYPE,
"application/x-www-form-urlencoded",
)
.header(header::CONTENT_LENGTH, "11")
let (req, mut pl) = TestRequest::default()
.insert_header((header::CONTENT_TYPE, "application/x-www-form-urlencoded"))
.insert_header((header::CONTENT_LENGTH, "11"))
.set_payload(Bytes::from_static(b"hello=world"))
.to_http_parts();
@ -412,11 +404,9 @@ mod tests {
})
);
let (req, mut pl) = TestRequest::with_header(
header::CONTENT_TYPE,
"application/x-www-form-urlencoded",
)
.header(header::CONTENT_LENGTH, "9")
let (req, mut pl) = TestRequest::default()
.insert_header((header::CONTENT_TYPE, "application/x-www-form-urlencoded"))
.insert_header((header::CONTENT_LENGTH, 9))
.set_payload(Bytes::from_static(b"bye=world"))
.to_http_parts();

View File

@ -330,7 +330,8 @@ mod tests {
#[test]
fn test_header() {
let req = TestRequest::with_header(header::TRANSFER_ENCODING, "chunked")
let req = TestRequest::default()
.insert_header((header::TRANSFER_ENCODING, "chunked"))
.to_http_request();
let pred = Header("transfer-encoding", "chunked");
@ -346,10 +347,10 @@ mod tests {
#[test]
fn test_host() {
let req = TestRequest::default()
.header(
.insert_header((
header::HOST,
header::HeaderValue::from_static("www.rust-lang.org"),
)
))
.to_http_request();
let pred = Host("www.rust-lang.org");
@ -374,10 +375,10 @@ mod tests {
#[test]
fn test_host_scheme() {
let req = TestRequest::default()
.header(
.insert_header((
header::HOST,
header::HeaderValue::from_static("https://www.rust-lang.org"),
)
))
.to_http_request();
let pred = Host("www.rust-lang.org").scheme("https");

View File

@ -200,10 +200,10 @@ mod tests {
assert_eq!(info.host(), "localhost:8080");
let req = TestRequest::default()
.header(
.insert_header((
header::FORWARDED,
"for=192.0.2.60; proto=https; by=203.0.113.43; host=rust-lang.org",
)
))
.to_http_request();
let info = req.connection_info();
@ -212,7 +212,7 @@ mod tests {
assert_eq!(info.realip_remote_addr(), Some("192.0.2.60"));
let req = TestRequest::default()
.header(header::HOST, "rust-lang.org")
.insert_header((header::HOST, "rust-lang.org"))
.to_http_request();
let info = req.connection_info();
@ -221,20 +221,20 @@ mod tests {
assert_eq!(info.realip_remote_addr(), None);
let req = TestRequest::default()
.header(X_FORWARDED_FOR, "192.0.2.60")
.insert_header((X_FORWARDED_FOR, "192.0.2.60"))
.to_http_request();
let info = req.connection_info();
assert_eq!(info.realip_remote_addr(), Some("192.0.2.60"));
let req = TestRequest::default()
.header(X_FORWARDED_HOST, "192.0.2.60")
.insert_header((X_FORWARDED_HOST, "192.0.2.60"))
.to_http_request();
let info = req.connection_info();
assert_eq!(info.host(), "192.0.2.60");
assert_eq!(info.realip_remote_addr(), None);
let req = TestRequest::default()
.header(X_FORWARDED_PROTO, "https")
.insert_header((X_FORWARDED_PROTO, "https"))
.to_http_request();
let info = req.connection_info();
assert_eq!(info.scheme(), "https");

View File

@ -211,7 +211,7 @@ pub mod client {
//!
//! // Create request builder and send request
//! let response = client.get("http://www.rust-lang.org")
//! .header("User-Agent", "actix-web/3.0")
//! .insert_header(("User-Agent", "actix-web/3.0"))
//! .send() // <- Send request
//! .await; // <- Wait for response
//!

View File

@ -212,8 +212,11 @@ mod tests {
let req = TestRequest::default().to_srv_request();
let srv = |req: ServiceRequest| {
ok(req
.into_response(HttpResponse::Ok().header(CONTENT_TYPE, "0002").finish()))
ok(req.into_response(
HttpResponse::Ok()
.insert_header((CONTENT_TYPE, "0002"))
.finish(),
))
};
let mut mw = DefaultHeaders::new()
.header(CONTENT_TYPE, "0001")

View File

@ -603,7 +603,7 @@ mod tests {
let srv = |req: ServiceRequest| {
ok(req.into_response(
HttpResponse::build(StatusCode::OK)
.header("X-Test", "ttt")
.insert_header(("X-Test", "ttt"))
.finish(),
))
};
@ -611,10 +611,11 @@ mod tests {
let mut srv = logger.new_transform(srv.into_service()).await.unwrap();
let req = TestRequest::with_header(
let req = TestRequest::default()
.insert_header((
header::USER_AGENT,
header::HeaderValue::from_static("ACTIX-WEB"),
)
))
.to_srv_request();
let _res = srv.call(req).await;
}
@ -624,7 +625,7 @@ mod tests {
let srv = |req: ServiceRequest| {
ok(req.into_response(
HttpResponse::build(StatusCode::OK)
.header("X-Test", "ttt")
.insert_header(("X-Test", "ttt"))
.finish(),
))
};
@ -633,10 +634,11 @@ mod tests {
let mut srv = logger.new_transform(srv.into_service()).await.unwrap();
let req = TestRequest::with_header(
let req = TestRequest::default()
.insert_header((
header::USER_AGENT,
header::HeaderValue::from_static("ACTIX-WEB"),
)
))
.to_srv_request();
let _res = srv.call(req).await.unwrap();
}
@ -644,10 +646,11 @@ mod tests {
#[actix_rt::test]
async fn test_url_path() {
let mut format = Format::new("%T %U");
let req = TestRequest::with_header(
let req = TestRequest::default()
.insert_header((
header::USER_AGENT,
header::HeaderValue::from_static("ACTIX-WEB"),
)
))
.uri("/test/route/yeah")
.to_srv_request();
@ -676,10 +679,11 @@ mod tests {
async fn test_default_format() {
let mut format = Format::default();
let req = TestRequest::with_header(
let req = TestRequest::default()
.insert_header((
header::USER_AGENT,
header::HeaderValue::from_static("ACTIX-WEB"),
)
))
.peer_addr("127.0.0.1:8081".parse().unwrap())
.to_srv_request();
@ -736,12 +740,13 @@ mod tests {
async fn test_remote_addr_format() {
let mut format = Format::new("%{r}a");
let req = TestRequest::with_header(
let req = TestRequest::default()
.insert_header((
header::FORWARDED,
header::HeaderValue::from_static(
"for=192.0.2.60;proto=http;by=203.0.113.43",
),
)
))
.to_srv_request();
let now = OffsetDateTime::now_utc();

View File

@ -423,8 +423,9 @@ mod tests {
#[test]
fn test_debug() {
let req =
TestRequest::with_header("content-type", "text/plain").to_http_request();
let req = TestRequest::default()
.insert_header(("content-type", "text/plain"))
.to_http_request();
let dbg = format!("{:?}", req);
assert!(dbg.contains("HttpRequest"));
}
@ -438,8 +439,8 @@ mod tests {
#[test]
fn test_request_cookies() {
let req = TestRequest::default()
.header(header::COOKIE, "cookie1=value1")
.header(header::COOKIE, "cookie2=value2")
.append_header((header::COOKIE, "cookie1=value1"))
.append_header((header::COOKIE, "cookie2=value2"))
.to_http_request();
{
let cookies = req.cookies().unwrap();
@ -476,7 +477,8 @@ mod tests {
assert!(rmap.has_resource("/user/test.html"));
assert!(!rmap.has_resource("/test/unknown"));
let req = TestRequest::with_header(header::HOST, "www.rust-lang.org")
let req = TestRequest::default()
.insert_header((header::HOST, "www.rust-lang.org"))
.rmap(rmap)
.to_http_request();
@ -506,7 +508,7 @@ mod tests {
assert!(rmap.has_resource("/index.html"));
let req = TestRequest::with_uri("/test")
.header(header::HOST, "www.rust-lang.org")
.insert_header((header::HOST, "www.rust-lang.org"))
.rmap(rmap)
.to_http_request();
let url = req.url_for_static("index");
@ -557,7 +559,7 @@ mod tests {
let mut srv = init_service(App::new().service(web::resource("/").to(
|req: HttpRequest| {
HttpResponse::Ok()
.set_header("pool_cap", req.app_state().pool().cap)
.insert_header(("pool_cap", req.app_state().pool().cap))
.finish()
},
)))

View File

@ -1,17 +1,17 @@
use std::convert::TryFrom;
use std::fmt;
use actix_http::error::InternalError;
use actix_http::http::{
header::IntoHeaderValue, Error as HttpError, HeaderMap, HeaderName, StatusCode,
use actix_http::{
error::InternalError,
http::{header::IntoHeaderPair, Error as HttpError, HeaderMap, StatusCode},
ResponseBuilder,
};
use actix_http::ResponseBuilder;
use bytes::{Bytes, BytesMut};
use crate::{Error, HttpRequest, HttpResponse};
/// Trait implemented by types that can be converted to a http response.
/// Trait implemented by types that can be converted to an HTTP response.
///
/// Types that implement this trait can be used as the return type of a handler.
/// Any types that implement this trait can be used in the return type of a handler.
pub trait Responder {
/// Convert self to `HttpResponse`.
fn respond_to(self, req: &HttpRequest) -> HttpResponse;
@ -19,12 +19,11 @@ pub trait Responder {
/// Override a status code for a Responder.
///
/// ```rust
/// use actix_web::{HttpRequest, Responder, http::StatusCode};
/// use actix_web::{http::StatusCode, HttpRequest, Responder};
///
/// fn index(req: HttpRequest) -> impl Responder {
/// "Welcome!".with_status(StatusCode::OK)
/// }
/// # fn main() {}
/// ```
fn with_status(self, status: StatusCode) -> CustomResponder<Self>
where
@ -33,7 +32,9 @@ pub trait Responder {
CustomResponder::new(self).with_status(status)
}
/// Add header to the Responder's response.
/// Insert header to the final response.
///
/// Overrides other headers with the same name.
///
/// ```rust
/// use actix_web::{web, HttpRequest, Responder};
@ -45,21 +46,16 @@ pub trait Responder {
/// }
///
/// fn index(req: HttpRequest) -> impl Responder {
/// web::Json(
/// MyObj{name: "Name".to_string()}
/// )
/// .with_header("x-version", "1.2.3")
/// web::Json(MyObj { name: "Name".to_owned() })
/// .with_header(("x-version", "1.2.3"))
/// }
/// # fn main() {}
/// ```
fn with_header<K, V>(self, key: K, value: V) -> CustomResponder<Self>
fn with_header<H>(self, header: H) -> CustomResponder<Self>
where
Self: Sized,
HeaderName: TryFrom<K>,
<HeaderName as TryFrom<K>>::Error: Into<HttpError>,
V: IntoHeaderValue,
H: IntoHeaderPair,
{
CustomResponder::new(self).with_header(key, value)
CustomResponder::new(self).with_header(header)
}
}
@ -155,7 +151,7 @@ impl Responder for BytesMut {
}
}
/// Allows to override status code and headers for a responder.
/// Allows overriding status code and headers for a responder.
pub struct CustomResponder<T> {
responder: T,
status: Option<StatusCode>,
@ -181,14 +177,15 @@ impl<T: Responder> CustomResponder<T> {
/// fn index(req: HttpRequest) -> impl Responder {
/// "Welcome!".with_status(StatusCode::OK)
/// }
/// # fn main() {}
/// ```
pub fn with_status(mut self, status: StatusCode) -> Self {
self.status = Some(status);
self
}
/// Add header to the Responder's response.
/// Insert header to the final response.
///
/// Overrides other headers with the same name.
///
/// ```rust
/// use actix_web::{web, HttpRequest, Responder};
@ -200,32 +197,24 @@ impl<T: Responder> CustomResponder<T> {
/// }
///
/// fn index(req: HttpRequest) -> impl Responder {
/// web::Json(
/// MyObj{name: "Name".to_string()}
/// )
/// .with_header("x-version", "1.2.3")
/// web::Json(MyObj { name: "Name".to_string() })
/// .with_header(("x-version", "1.2.3"))
/// .with_header(("x-version", "1.2.3"))
/// }
/// # fn main() {}
/// ```
pub fn with_header<K, V>(mut self, key: K, value: V) -> Self
pub fn with_header<H>(mut self, header: H) -> Self
where
HeaderName: TryFrom<K>,
<HeaderName as TryFrom<K>>::Error: Into<HttpError>,
V: IntoHeaderValue,
H: IntoHeaderPair,
{
if self.headers.is_none() {
self.headers = Some(HeaderMap::new());
}
match HeaderName::try_from(key) {
Ok(key) => match value.try_into() {
Ok(value) => {
self.headers.as_mut().unwrap().append(key, value);
}
Err(e) => self.error = Some(e.into()),
},
match header.try_into_header_pair() {
Ok((key, value)) => self.headers.as_mut().unwrap().append(key, value),
Err(e) => self.error = Some(e.into()),
};
self
}
}
@ -240,6 +229,7 @@ impl<T: Responder> Responder for CustomResponder<T> {
if let Some(ref headers) = self.headers {
for (k, v) in headers {
// TODO: before v4, decide if this should be append instead
res.headers_mut().insert(k.clone(), v.clone());
}
}
@ -250,7 +240,7 @@ impl<T: Responder> Responder for CustomResponder<T> {
impl<T> Responder for InternalError<T>
where
T: std::fmt::Debug + std::fmt::Display + 'static,
T: fmt::Debug + fmt::Display + 'static,
{
fn respond_to(self, _: &HttpRequest) -> HttpResponse {
HttpResponse::from_error(self.into())
@ -412,7 +402,7 @@ pub(crate) mod tests {
let res = "test"
.to_string()
.with_header("content-type", "json")
.with_header(("content-type", "json"))
.respond_to(&req);
assert_eq!(res.status(), StatusCode::OK);
@ -432,13 +422,13 @@ pub(crate) mod tests {
let req = TestRequest::default().to_http_request();
let res = ("test".to_string(), StatusCode::OK)
.with_header("content-type", "json")
.with_header((CONTENT_TYPE, mime::APPLICATION_JSON))
.respond_to(&req);
assert_eq!(res.status(), StatusCode::OK);
assert_eq!(res.body().bin_ref(), b"test");
assert_eq!(
res.headers().get(CONTENT_TYPE).unwrap(),
HeaderValue::from_static("json")
HeaderValue::from_static("application/json")
);
}
}

View File

@ -588,14 +588,14 @@ mod tests {
fn test_fmt_debug() {
let req = TestRequest::get()
.uri("/index.html?test=1")
.header("x-test", "111")
.insert_header(("x-test", "111"))
.to_srv_request();
let s = format!("{:?}", req);
assert!(s.contains("ServiceRequest"));
assert!(s.contains("test=1"));
assert!(s.contains("x-test"));
let res = HttpResponse::Ok().header("x-test", "111").finish();
let res = HttpResponse::Ok().insert_header(("x-test", "111")).finish();
let res = TestRequest::post()
.uri("/index.html?test=1")
.to_srv_response(res);

View File

@ -1,13 +1,13 @@
//! Various helpers for Actix applications to use during testing.
use std::convert::TryFrom;
use std::net::SocketAddr;
use std::rc::Rc;
use std::sync::mpsc;
use std::{fmt, net, thread, time};
use actix_codec::{AsyncRead, AsyncWrite, Framed};
use actix_http::http::header::{ContentType, Header, HeaderName, IntoHeaderValue};
use actix_http::http::{Error as HttpError, Method, StatusCode, Uri, Version};
use actix_http::http::header::{ContentType, IntoHeaderPair};
use actix_http::http::{Method, StatusCode, Uri, Version};
use actix_http::test::TestRequest as HttpTestRequest;
use actix_http::{cookie::Cookie, ws, Extensions, HttpService, Request};
use actix_router::{Path, ResourceDef, Url};
@ -349,7 +349,7 @@ where
///
/// #[test]
/// fn test_index() {
/// let req = test::TestRequest::with_header("content-type", "text/plain")
/// let req = test::TestRequest::default().insert_header("content-type", "text/plain")
/// .to_http_request();
///
/// let resp = index(req).await.unwrap();
@ -389,21 +389,6 @@ impl TestRequest {
TestRequest::default().uri(path)
}
/// Create TestRequest and set header
pub fn with_hdr<H: Header>(hdr: H) -> TestRequest {
TestRequest::default().set(hdr)
}
/// Create TestRequest and set header
pub fn with_header<K, V>(key: K, value: V) -> TestRequest
where
HeaderName: TryFrom<K>,
<HeaderName as TryFrom<K>>::Error: Into<HttpError>,
V: IntoHeaderValue,
{
TestRequest::default().header(key, value)
}
/// Create TestRequest and set method to `Method::GET`
pub fn get() -> TestRequest {
TestRequest::default().method(Method::GET)
@ -447,24 +432,25 @@ impl TestRequest {
self
}
/// Set a header
pub fn set<H: Header>(mut self, hdr: H) -> Self {
self.req.set(hdr);
self
}
/// Set a header
pub fn header<K, V>(mut self, key: K, value: V) -> Self
/// Insert a header, replacing any that were set with an equivalent field name.
pub fn insert_header<H>(mut self, header: H) -> Self
where
HeaderName: TryFrom<K>,
<HeaderName as TryFrom<K>>::Error: Into<HttpError>,
V: IntoHeaderValue,
H: IntoHeaderPair,
{
self.req.header(key, value);
self.req.insert_header(header);
self
}
/// Set cookie for this request
/// Append a header, keeping any that were set with an equivalent field name.
pub fn append_header<H>(mut self, header: H) -> Self
where
H: IntoHeaderPair,
{
self.req.append_header(header);
self
}
/// Set cookie for this request.
pub fn cookie(mut self, cookie: Cookie<'_>) -> Self {
self.req.cookie(cookie);
self
@ -494,7 +480,7 @@ impl TestRequest {
let bytes = serde_urlencoded::to_string(data)
.expect("Failed to serialize test data as a urlencoded form");
self.req.set_payload(bytes);
self.req.set(ContentType::form_url_encoded());
self.req.insert_header(ContentType::form_url_encoded());
self
}
@ -504,7 +490,7 @@ impl TestRequest {
let bytes =
serde_json::to_string(data).expect("Failed to serialize test data to json");
self.req.set_payload(bytes);
self.req.set(ContentType::json());
self.req.insert_header(ContentType::json());
self
}
@ -1029,9 +1015,10 @@ mod tests {
#[actix_rt::test]
async fn test_basics() {
let req = TestRequest::with_hdr(header::ContentType::json())
let req = TestRequest::default()
.version(Version::HTTP_2)
.set(header::Date(SystemTime::now().into()))
.insert_header(header::ContentType::json())
.insert_header(header::Date(SystemTime::now().into()))
.param("test", "123")
.data(10u32)
.app_data(20u64)
@ -1068,7 +1055,7 @@ mod tests {
let put_req = TestRequest::put()
.uri("/index.html")
.header(header::CONTENT_TYPE, "application/json")
.insert_header((header::CONTENT_TYPE, "application/json"))
.to_request();
let result = read_response(&mut app, put_req).await;
@ -1076,7 +1063,7 @@ mod tests {
let patch_req = TestRequest::patch()
.uri("/index.html")
.header(header::CONTENT_TYPE, "application/json")
.insert_header((header::CONTENT_TYPE, "application/json"))
.to_request();
let result = read_response(&mut app, patch_req).await;
@ -1099,7 +1086,7 @@ mod tests {
let req = TestRequest::post()
.uri("/index.html")
.header(header::CONTENT_TYPE, "application/json")
.insert_header((header::CONTENT_TYPE, "application/json"))
.to_request();
let result = read_response(&mut app, req).await;
@ -1144,7 +1131,7 @@ mod tests {
let req = TestRequest::post()
.uri("/people")
.header(header::CONTENT_TYPE, "application/json")
.insert_header((header::CONTENT_TYPE, "application/json"))
.set_payload(payload)
.to_request();
@ -1165,7 +1152,7 @@ mod tests {
let resp = TestRequest::post()
.uri("/people")
.header(header::CONTENT_TYPE, "application/json")
.insert_header((header::CONTENT_TYPE, "application/json"))
.set_payload(payload)
.send_request(&mut app)
.await;

View File

@ -381,9 +381,9 @@ mod tests {
#[actix_rt::test]
async fn test_form() {
let (req, mut pl) =
TestRequest::with_header(CONTENT_TYPE, "application/x-www-form-urlencoded")
.header(CONTENT_LENGTH, "11")
let (req, mut pl) = TestRequest::default()
.insert_header((CONTENT_TYPE, "application/x-www-form-urlencoded"))
.insert_header((CONTENT_LENGTH, 11))
.set_payload(Bytes::from_static(b"hello=world&counter=123"))
.to_http_parts();
@ -414,16 +414,16 @@ mod tests {
#[actix_rt::test]
async fn test_urlencoded_error() {
let (req, mut pl) =
TestRequest::with_header(CONTENT_TYPE, "application/x-www-form-urlencoded")
.header(CONTENT_LENGTH, "xxxx")
let (req, mut pl) = TestRequest::default()
.insert_header((CONTENT_TYPE, "application/x-www-form-urlencoded"))
.insert_header((CONTENT_LENGTH, "xxxx"))
.to_http_parts();
let info = UrlEncoded::<Info>::new(&req, &mut pl).await;
assert!(eq(info.err().unwrap(), UrlencodedError::UnknownLength));
let (req, mut pl) =
TestRequest::with_header(CONTENT_TYPE, "application/x-www-form-urlencoded")
.header(CONTENT_LENGTH, "1000000")
let (req, mut pl) = TestRequest::default()
.insert_header((CONTENT_TYPE, "application/x-www-form-urlencoded"))
.insert_header((CONTENT_LENGTH, "1000000"))
.to_http_parts();
let info = UrlEncoded::<Info>::new(&req, &mut pl).await;
assert!(eq(
@ -431,8 +431,9 @@ mod tests {
UrlencodedError::Overflow { size: 0, limit: 0 }
));
let (req, mut pl) = TestRequest::with_header(CONTENT_TYPE, "text/plain")
.header(CONTENT_LENGTH, "10")
let (req, mut pl) = TestRequest::default()
.insert_header((CONTENT_TYPE, "text/plain"))
.insert_header((CONTENT_LENGTH, 10))
.to_http_parts();
let info = UrlEncoded::<Info>::new(&req, &mut pl).await;
assert!(eq(info.err().unwrap(), UrlencodedError::ContentType));
@ -440,9 +441,9 @@ mod tests {
#[actix_rt::test]
async fn test_urlencoded() {
let (req, mut pl) =
TestRequest::with_header(CONTENT_TYPE, "application/x-www-form-urlencoded")
.header(CONTENT_LENGTH, "11")
let (req, mut pl) = TestRequest::default()
.insert_header((CONTENT_TYPE, "application/x-www-form-urlencoded"))
.insert_header((CONTENT_LENGTH, 11))
.set_payload(Bytes::from_static(b"hello=world&counter=123"))
.to_http_parts();
@ -455,11 +456,12 @@ mod tests {
}
);
let (req, mut pl) = TestRequest::with_header(
let (req, mut pl) = TestRequest::default()
.insert_header((
CONTENT_TYPE,
"application/x-www-form-urlencoded; charset=utf-8",
)
.header(CONTENT_LENGTH, "11")
))
.insert_header((CONTENT_LENGTH, 11))
.set_payload(Bytes::from_static(b"hello=world&counter=123"))
.to_http_parts();
@ -497,8 +499,8 @@ mod tests {
let ctype = HeaderValue::from_static("application/x-www-form-urlencoded");
let (req, mut pl) = TestRequest::default()
.header(CONTENT_TYPE, ctype)
.header(CONTENT_LENGTH, HeaderValue::from_static("20"))
.insert_header((CONTENT_TYPE, ctype))
.insert_header((CONTENT_LENGTH, HeaderValue::from_static("20")))
.set_payload(Bytes::from_static(b"hello=test&counter=4"))
.app_data(web::Data::new(FormConfig::default().limit(10)))
.to_http_parts();

View File

@ -427,7 +427,7 @@ mod tests {
use crate::{
error::InternalError,
http::{
header::{self, HeaderValue, CONTENT_LENGTH, CONTENT_TYPE},
header::{self, CONTENT_LENGTH, CONTENT_TYPE},
StatusCode,
},
test::{load_stream, TestRequest},
@ -469,14 +469,14 @@ mod tests {
#[actix_rt::test]
async fn test_custom_error_responder() {
let (req, mut pl) = TestRequest::default()
.header(
.insert_header((
header::CONTENT_TYPE,
header::HeaderValue::from_static("application/json"),
)
.header(
))
.insert_header((
header::CONTENT_LENGTH,
header::HeaderValue::from_static("16"),
)
))
.set_payload(Bytes::from_static(b"{\"name\": \"test\"}"))
.app_data(JsonConfig::default().limit(10).error_handler(|err, _| {
let msg = MyObject {
@ -500,14 +500,14 @@ mod tests {
#[actix_rt::test]
async fn test_extract() {
let (req, mut pl) = TestRequest::default()
.header(
.insert_header((
header::CONTENT_TYPE,
header::HeaderValue::from_static("application/json"),
)
.header(
))
.insert_header((
header::CONTENT_LENGTH,
header::HeaderValue::from_static("16"),
)
))
.set_payload(Bytes::from_static(b"{\"name\": \"test\"}"))
.to_http_parts();
@ -521,14 +521,14 @@ mod tests {
);
let (req, mut pl) = TestRequest::default()
.header(
.insert_header((
header::CONTENT_TYPE,
header::HeaderValue::from_static("application/json"),
)
.header(
))
.insert_header((
header::CONTENT_LENGTH,
header::HeaderValue::from_static("16"),
)
))
.set_payload(Bytes::from_static(b"{\"name\": \"test\"}"))
.app_data(JsonConfig::default().limit(10))
.to_http_parts();
@ -538,14 +538,14 @@ mod tests {
.contains("Json payload size is bigger than allowed"));
let (req, mut pl) = TestRequest::default()
.header(
.insert_header((
header::CONTENT_TYPE,
header::HeaderValue::from_static("application/json"),
)
.header(
))
.insert_header((
header::CONTENT_LENGTH,
header::HeaderValue::from_static("16"),
)
))
.set_payload(Bytes::from_static(b"{\"name\": \"test\"}"))
.app_data(
JsonConfig::default()
@ -564,23 +564,23 @@ mod tests {
assert!(json_eq(json.err().unwrap(), JsonPayloadError::ContentType));
let (req, mut pl) = TestRequest::default()
.header(
.insert_header((
header::CONTENT_TYPE,
header::HeaderValue::from_static("application/text"),
)
))
.to_http_parts();
let json = JsonBody::<MyObject>::new(&req, &mut pl, None).await;
assert!(json_eq(json.err().unwrap(), JsonPayloadError::ContentType));
let (req, mut pl) = TestRequest::default()
.header(
.insert_header((
header::CONTENT_TYPE,
header::HeaderValue::from_static("application/json"),
)
.header(
))
.insert_header((
header::CONTENT_LENGTH,
header::HeaderValue::from_static("10000"),
)
))
.to_http_parts();
let json = JsonBody::<MyObject>::new(&req, &mut pl, None)
@ -589,14 +589,14 @@ mod tests {
assert!(json_eq(json.err().unwrap(), JsonPayloadError::Overflow));
let (req, mut pl) = TestRequest::default()
.header(
.insert_header((
header::CONTENT_TYPE,
header::HeaderValue::from_static("application/json"),
)
.header(
))
.insert_header((
header::CONTENT_LENGTH,
header::HeaderValue::from_static("16"),
)
))
.set_payload(Bytes::from_static(b"{\"name\": \"test\"}"))
.to_http_parts();
@ -611,14 +611,15 @@ mod tests {
#[actix_rt::test]
async fn test_with_json_and_bad_content_type() {
let (req, mut pl) = TestRequest::with_header(
let (req, mut pl) = TestRequest::default()
.insert_header((
header::CONTENT_TYPE,
header::HeaderValue::from_static("text/plain"),
)
.header(
))
.insert_header((
header::CONTENT_LENGTH,
header::HeaderValue::from_static("16"),
)
))
.set_payload(Bytes::from_static(b"{\"name\": \"test\"}"))
.app_data(JsonConfig::default().limit(4096))
.to_http_parts();
@ -629,14 +630,15 @@ mod tests {
#[actix_rt::test]
async fn test_with_json_and_good_custom_content_type() {
let (req, mut pl) = TestRequest::with_header(
let (req, mut pl) = TestRequest::default()
.insert_header((
header::CONTENT_TYPE,
header::HeaderValue::from_static("text/plain"),
)
.header(
))
.insert_header((
header::CONTENT_LENGTH,
header::HeaderValue::from_static("16"),
)
))
.set_payload(Bytes::from_static(b"{\"name\": \"test\"}"))
.app_data(JsonConfig::default().content_type(|mime: mime::Mime| {
mime.type_() == mime::TEXT && mime.subtype() == mime::PLAIN
@ -649,14 +651,15 @@ mod tests {
#[actix_rt::test]
async fn test_with_json_and_bad_custom_content_type() {
let (req, mut pl) = TestRequest::with_header(
let (req, mut pl) = TestRequest::default()
.insert_header((
header::CONTENT_TYPE,
header::HeaderValue::from_static("text/html"),
)
.header(
))
.insert_header((
header::CONTENT_LENGTH,
header::HeaderValue::from_static("16"),
)
))
.set_payload(Bytes::from_static(b"{\"name\": \"test\"}"))
.app_data(JsonConfig::default().content_type(|mime: mime::Mime| {
mime.type_() == mime::TEXT && mime.subtype() == mime::PLAIN
@ -670,8 +673,8 @@ mod tests {
#[actix_rt::test]
async fn test_with_config_in_data_wrapper() {
let (req, mut pl) = TestRequest::default()
.header(CONTENT_TYPE, HeaderValue::from_static("application/json"))
.header(CONTENT_LENGTH, HeaderValue::from_static("16"))
.insert_header((CONTENT_TYPE, mime::APPLICATION_JSON))
.insert_header((CONTENT_LENGTH, 16))
.set_payload(Bytes::from_static(b"{\"name\": \"test\"}"))
.app_data(web::Data::new(JsonConfig::default().limit(10)))
.to_http_parts();

View File

@ -364,14 +364,13 @@ mod tests {
let cfg = PayloadConfig::default().mimetype(mime::APPLICATION_JSON);
assert!(cfg.check_mimetype(&req).is_err());
let req = TestRequest::with_header(
header::CONTENT_TYPE,
"application/x-www-form-urlencoded",
)
let req = TestRequest::default()
.insert_header((header::CONTENT_TYPE, "application/x-www-form-urlencoded"))
.to_http_request();
assert!(cfg.check_mimetype(&req).is_err());
let req = TestRequest::with_header(header::CONTENT_TYPE, "application/json")
let req = TestRequest::default()
.insert_header((header::CONTENT_TYPE, "application/json"))
.to_http_request();
assert!(cfg.check_mimetype(&req).is_ok());
}
@ -432,25 +431,25 @@ mod tests {
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
let req = TestRequest::with_uri("/bytes-app-data")
.header(header::CONTENT_TYPE, mime::APPLICATION_JSON)
.insert_header(header::ContentType(mime::APPLICATION_JSON))
.to_request();
let resp = call_service(&mut srv, req).await;
assert_eq!(resp.status(), StatusCode::OK);
let req = TestRequest::with_uri("/bytes-data")
.header(header::CONTENT_TYPE, mime::APPLICATION_JSON)
.insert_header(header::ContentType(mime::APPLICATION_JSON))
.to_request();
let resp = call_service(&mut srv, req).await;
assert_eq!(resp.status(), StatusCode::OK);
let req = TestRequest::with_uri("/string-app-data")
.header(header::CONTENT_TYPE, mime::APPLICATION_JSON)
.insert_header(header::ContentType(mime::APPLICATION_JSON))
.to_request();
let resp = call_service(&mut srv, req).await;
assert_eq!(resp.status(), StatusCode::OK);
let req = TestRequest::with_uri("/string-data")
.header(header::CONTENT_TYPE, mime::APPLICATION_JSON)
.insert_header(header::ContentType(mime::APPLICATION_JSON))
.to_request();
let resp = call_service(&mut srv, req).await;
assert_eq!(resp.status(), StatusCode::OK);
@ -458,7 +457,8 @@ mod tests {
#[actix_rt::test]
async fn test_bytes() {
let (req, mut pl) = TestRequest::with_header(header::CONTENT_LENGTH, "11")
let (req, mut pl) = TestRequest::default()
.insert_header((header::CONTENT_LENGTH, "11"))
.set_payload(Bytes::from_static(b"hello=world"))
.to_http_parts();
@ -468,7 +468,8 @@ mod tests {
#[actix_rt::test]
async fn test_string() {
let (req, mut pl) = TestRequest::with_header(header::CONTENT_LENGTH, "11")
let (req, mut pl) = TestRequest::default()
.insert_header((header::CONTENT_LENGTH, "11"))
.set_payload(Bytes::from_static(b"hello=world"))
.to_http_parts();
@ -478,7 +479,8 @@ mod tests {
#[actix_rt::test]
async fn test_message_body() {
let (req, mut pl) = TestRequest::with_header(header::CONTENT_LENGTH, "xxxx")
let (req, mut pl) = TestRequest::default()
.insert_header((header::CONTENT_LENGTH, "xxxx"))
.to_srv_request()
.into_parts();
let res = HttpMessageBody::new(&req, &mut pl).await;
@ -487,7 +489,8 @@ mod tests {
_ => unreachable!("error"),
}
let (req, mut pl) = TestRequest::with_header(header::CONTENT_LENGTH, "1000000")
let (req, mut pl) = TestRequest::default()
.insert_header((header::CONTENT_LENGTH, "1000000"))
.to_srv_request()
.into_parts();
let res = HttpMessageBody::new(&req, &mut pl).await;

View File

@ -108,7 +108,7 @@ async fn test_body_gzip() {
let mut response = srv
.get("/")
.no_decompress()
.header(ACCEPT_ENCODING, "gzip")
.append_header((ACCEPT_ENCODING, "gzip"))
.send()
.await
.unwrap();
@ -137,7 +137,7 @@ async fn test_body_gzip2() {
let mut response = srv
.get("/")
.no_decompress()
.header(ACCEPT_ENCODING, "gzip")
.append_header((ACCEPT_ENCODING, "gzip"))
.send()
.await
.unwrap();
@ -178,7 +178,7 @@ async fn test_body_encoding_override() {
let mut response = srv
.get("/")
.no_decompress()
.header(ACCEPT_ENCODING, "deflate")
.append_header((ACCEPT_ENCODING, "deflate"))
.send()
.await
.unwrap();
@ -197,7 +197,7 @@ async fn test_body_encoding_override() {
let mut response = srv
.request(actix_web::http::Method::GET, srv.url("/raw"))
.no_decompress()
.header(ACCEPT_ENCODING, "deflate")
.append_header((ACCEPT_ENCODING, "deflate"))
.send()
.await
.unwrap();
@ -231,7 +231,7 @@ async fn test_body_gzip_large() {
let mut response = srv
.get("/")
.no_decompress()
.header(ACCEPT_ENCODING, "gzip")
.append_header((ACCEPT_ENCODING, "gzip"))
.send()
.await
.unwrap();
@ -269,7 +269,7 @@ async fn test_body_gzip_large_random() {
let mut response = srv
.get("/")
.no_decompress()
.header(ACCEPT_ENCODING, "gzip")
.append_header((ACCEPT_ENCODING, "gzip"))
.send()
.await
.unwrap();
@ -300,7 +300,7 @@ async fn test_body_chunked_implicit() {
let mut response = srv
.get("/")
.no_decompress()
.header(ACCEPT_ENCODING, "gzip")
.append_header((ACCEPT_ENCODING, "gzip"))
.send()
.await
.unwrap();
@ -333,7 +333,7 @@ async fn test_body_br_streaming() {
let mut response = srv
.get("/")
.header(ACCEPT_ENCODING, "br")
.append_header((ACCEPT_ENCODING, "br"))
.no_decompress()
.send()
.await
@ -406,7 +406,7 @@ async fn test_body_deflate() {
// client request
let mut response = srv
.get("/")
.header(ACCEPT_ENCODING, "deflate")
.append_header((ACCEPT_ENCODING, "deflate"))
.no_decompress()
.send()
.await
@ -433,7 +433,7 @@ async fn test_body_brotli() {
// client request
let mut response = srv
.get("/")
.header(ACCEPT_ENCODING, "br")
.append_header((ACCEPT_ENCODING, "br"))
.no_decompress()
.send()
.await
@ -466,7 +466,7 @@ async fn test_encoding() {
let request = srv
.post("/")
.header(CONTENT_ENCODING, "gzip")
.insert_header((CONTENT_ENCODING, "gzip"))
.send_body(enc.clone());
let mut response = request.await.unwrap();
assert!(response.status().is_success());
@ -492,7 +492,7 @@ async fn test_gzip_encoding() {
let request = srv
.post("/")
.header(CONTENT_ENCODING, "gzip")
.append_header((CONTENT_ENCODING, "gzip"))
.send_body(enc.clone());
let mut response = request.await.unwrap();
assert!(response.status().is_success());
@ -519,7 +519,7 @@ async fn test_gzip_encoding_large() {
let request = srv
.post("/")
.header(CONTENT_ENCODING, "gzip")
.append_header((CONTENT_ENCODING, "gzip"))
.send_body(enc.clone());
let mut response = request.await.unwrap();
assert!(response.status().is_success());
@ -551,7 +551,7 @@ async fn test_reading_gzip_encoding_large_random() {
let request = srv
.post("/")
.header(CONTENT_ENCODING, "gzip")
.append_header((CONTENT_ENCODING, "gzip"))
.send_body(enc.clone());
let mut response = request.await.unwrap();
assert!(response.status().is_success());
@ -578,7 +578,7 @@ async fn test_reading_deflate_encoding() {
// client request
let request = srv
.post("/")
.header(CONTENT_ENCODING, "deflate")
.append_header((CONTENT_ENCODING, "deflate"))
.send_body(enc.clone());
let mut response = request.await.unwrap();
assert!(response.status().is_success());
@ -605,7 +605,7 @@ async fn test_reading_deflate_encoding_large() {
// client request
let request = srv
.post("/")
.header(CONTENT_ENCODING, "deflate")
.append_header((CONTENT_ENCODING, "deflate"))
.send_body(enc.clone());
let mut response = request.await.unwrap();
assert!(response.status().is_success());
@ -637,7 +637,7 @@ async fn test_reading_deflate_encoding_large_random() {
// client request
let request = srv
.post("/")
.header(CONTENT_ENCODING, "deflate")
.append_header((CONTENT_ENCODING, "deflate"))
.send_body(enc.clone());
let mut response = request.await.unwrap();
assert!(response.status().is_success());
@ -664,7 +664,7 @@ async fn test_brotli_encoding() {
// client request
let request = srv
.post("/")
.header(CONTENT_ENCODING, "br")
.append_header((CONTENT_ENCODING, "br"))
.send_body(enc.clone());
let mut response = request.await.unwrap();
assert!(response.status().is_success());
@ -699,7 +699,7 @@ async fn test_brotli_encoding_large() {
// client request
let request = srv
.post("/")
.header(CONTENT_ENCODING, "br")
.append_header((CONTENT_ENCODING, "br"))
.send_body(enc.clone());
let mut response = request.await.unwrap();
assert!(response.status().is_success());
@ -739,7 +739,7 @@ async fn test_brotli_encoding_large_openssl() {
// client request
let mut response = srv
.post("/")
.header(actix_web::http::header::CONTENT_ENCODING, "br")
.append_header((actix_web::http::header::CONTENT_ENCODING, "br"))
.send_body(enc)
.await
.unwrap();
@ -788,7 +788,7 @@ async fn test_reading_deflate_encoding_large_random_rustls() {
// client request
let req = srv
.post("/")
.header(actix_web::http::header::CONTENT_ENCODING, "deflate")
.insert_header((actix_web::http::header::CONTENT_ENCODING, "deflate"))
.send_stream(TestBody::new(Bytes::from(enc), 1024));
let mut response = req.await.unwrap();