1
0
Fork 0
mirror of https://github.com/actix/actix-web.git synced 2024-06-02 13:29:24 +00:00

clean up header map (#1964)

This commit is contained in:
Rob Ede 2021-02-09 22:59:17 +00:00 committed by GitHub
parent a6ed4aee84
commit 949d14ae2b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 982 additions and 393 deletions

View file

@ -7,6 +7,9 @@
* `ResponseBuilder::append_header` method which allows using typed headers. [#1869]
* `TestRequest::insert_header` method which allows using typed headers. [#1869]
* `ContentEncoding` implements all necessary header traits. [#1912]
* `HeaderMap::len_keys` has the behavior of the old `len` method. [#1964]
* `HeaderMap::drain` as an efficient draining iterator. [#1964]
* Implement `IntoIterator` for owned `HeaderMap`. [#1964]
* `trust-dns` optional feature to enable `trust-dns-resolver` as client dns resolver. [#1969]
### Changed
@ -20,6 +23,9 @@
* `client::error::ConnectError` Resolver variant contains `Box<dyn std::error::Error>` type [#1905]
* `client::ConnectorConfig` default timeout changed to 5 seconds. [#1905]
* Simplify `BlockingError` type to a struct. It's only triggered with blocking thread pool is dead. [#1957]
* `HeaderMap::len` now returns number of values instead of number of keys. [#1964]
* `HeaderMap::insert` now returns iterator of removed values. [#1964]
* `HeaderMap::remove` now returns iterator of removed values. [#1964]
### Removed
* `ResponseBuilder::set`; use `ResponseBuilder::insert_header`. [#1869]
@ -30,6 +36,9 @@
* `actors` optional feature. [#1969]
* `ResponseError` impl for `actix::MailboxError`. [#1969]
### Documentation
* Vastly improve docs and add examples for `HeaderMap`. [#1964]
[#1869]: https://github.com/actix/actix-web/pull/1869
[#1894]: https://github.com/actix/actix-web/pull/1894
[#1903]: https://github.com/actix/actix-web/pull/1903
@ -37,6 +46,7 @@
[#1905]: https://github.com/actix/actix-web/pull/1905
[#1912]: https://github.com/actix/actix-web/pull/1912
[#1957]: https://github.com/actix/actix-web/pull/1957
[#1964]: https://github.com/actix/actix-web/pull/1964
[#1969]: https://github.com/actix/actix-web/pull/1969

View file

@ -52,7 +52,6 @@ bytes = "1"
bytestring = "1"
cookie = { version = "0.14.1", features = ["percent-encode"] }
derive_more = "0.99.5"
either = "1.5.3"
encoding_rs = "0.8"
futures-channel = { version = "0.3.7", default-features = false, features = ["alloc"] }
futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] }

View file

@ -48,11 +48,11 @@ where
match wrt.get_mut().split().freeze().try_into_value() {
Ok(value) => match head {
RequestHeadType::Owned(ref mut head) => {
head.headers.insert(HOST, value)
head.headers.insert(HOST, value);
}
RequestHeadType::Rc(_, ref mut extra_headers) => {
let headers = extra_headers.get_or_insert(HeaderMap::new());
headers.insert(HOST, value)
headers.insert(HOST, value);
}
},
Err(e) => log::error!("Can not set HOST header {}", e),

View file

@ -830,8 +830,8 @@ mod tests {
.get_all(SET_COOKIE)
.map(|v| v.to_str().unwrap().to_owned())
.collect();
assert_eq!(val[1], "c1=cookie1");
assert_eq!(val[0], "c2=cookie2");
assert_eq!(val[0], "c1=cookie1");
assert_eq!(val[1], "c2=cookie2");
}
#[test]

View file

@ -144,104 +144,54 @@ pub(crate) trait MessageType: Sized {
let k = key.as_str().as_bytes();
let k_len = k.len();
match value {
Value::One(ref val) => {
let v = val.as_ref();
let v_len = v.len();
// TODO: drain?
for val in value.iter() {
let v = val.as_ref();
let v_len = v.len();
// key length + value length + colon + space + \r\n
let len = k_len + v_len + 4;
// key length + value length + colon + space + \r\n
let len = k_len + v_len + 4;
if len > remaining {
// not enough room in buffer for this header; reserve more space
// SAFETY: all the bytes written up to position "pos" are initialized
// the written byte count and pointer advancement are kept in sync
unsafe {
dst.advance_mut(pos);
}
pos = 0;
dst.reserve(len * 2);
remaining = dst.capacity() - dst.len();
// re-assign buf raw pointer since it's possible that the buffer was
// reallocated and/or resized
buf = dst.chunk_mut().as_mut_ptr();
}
// SAFETY: on each write, it is enough to ensure that the advancement of the
// cursor matches the number of bytes written
if len > remaining {
// SAFETY: all the bytes written up to position "pos" are initialized
// the written byte count and pointer advancement are kept in sync
unsafe {
// use upper Camel-Case
if camel_case {
write_camel_case(k, from_raw_parts_mut(buf, k_len))
} else {
write_data(k, buf, k_len)
}
buf = buf.add(k_len);
write_data(b": ", buf, 2);
buf = buf.add(2);
write_data(v, buf, v_len);
buf = buf.add(v_len);
write_data(b"\r\n", buf, 2);
buf = buf.add(2);
dst.advance_mut(pos);
}
pos += len;
remaining -= len;
pos = 0;
dst.reserve(len * 2);
remaining = dst.capacity() - dst.len();
// re-assign buf raw pointer since it's possible that the buffer was
// reallocated and/or resized
buf = dst.chunk_mut().as_mut_ptr();
}
Value::Multi(ref vec) => {
for val in vec {
let v = val.as_ref();
let v_len = v.len();
let len = k_len + v_len + 4;
if len > remaining {
// SAFETY: all the bytes written up to position "pos" are initialized
// the written byte count and pointer advancement are kept in sync
unsafe {
dst.advance_mut(pos);
}
pos = 0;
dst.reserve(len * 2);
remaining = dst.capacity() - dst.len();
// re-assign buf raw pointer since it's possible that the buffer was
// reallocated and/or resized
buf = dst.chunk_mut().as_mut_ptr();
}
// SAFETY: on each write, it is enough to ensure that the advancement of
// the cursor matches the number of bytes written
unsafe {
if camel_case {
write_camel_case(k, from_raw_parts_mut(buf, k_len));
} else {
write_data(k, buf, k_len);
}
buf = buf.add(k_len);
write_data(b": ", buf, 2);
buf = buf.add(2);
write_data(v, buf, v_len);
buf = buf.add(v_len);
write_data(b"\r\n", buf, 2);
buf = buf.add(2);
};
pos += len;
remaining -= len;
// SAFETY: on each write, it is enough to ensure that the advancement of
// the cursor matches the number of bytes written
unsafe {
if camel_case {
// use Camel-Case headers
write_camel_case(k, from_raw_parts_mut(buf, k_len));
} else {
write_data(k, buf, k_len);
}
}
buf = buf.add(k_len);
write_data(b": ", buf, 2);
buf = buf.add(2);
write_data(v, buf, v_len);
buf = buf.add(v_len);
write_data(b"\r\n", buf, 2);
buf = buf.add(2);
};
pos += len;
remaining -= len;
}
});

View file

@ -0,0 +1,46 @@
//! Helper trait for types that can be effectively borrowed as a [HeaderValue].
use std::{borrow::Cow, str::FromStr};
use http::header::{HeaderName, InvalidHeaderName};
pub trait AsHeaderName: Sealed {}
pub trait Sealed {
fn try_as_name(&self) -> Result<Cow<'_, HeaderName>, InvalidHeaderName>;
}
impl Sealed for HeaderName {
fn try_as_name(&self) -> Result<Cow<'_, HeaderName>, InvalidHeaderName> {
Ok(Cow::Borrowed(self))
}
}
impl AsHeaderName for HeaderName {}
impl Sealed for &HeaderName {
fn try_as_name(&self) -> Result<Cow<'_, HeaderName>, InvalidHeaderName> {
Ok(Cow::Borrowed(*self))
}
}
impl AsHeaderName for &HeaderName {}
impl Sealed for &str {
fn try_as_name(&self) -> Result<Cow<'_, HeaderName>, InvalidHeaderName> {
HeaderName::from_str(self).map(Cow::Owned)
}
}
impl AsHeaderName for &str {}
impl Sealed for String {
fn try_as_name(&self) -> Result<Cow<'_, HeaderName>, InvalidHeaderName> {
HeaderName::from_str(self).map(Cow::Owned)
}
}
impl AsHeaderName for String {}
impl Sealed for &String {
fn try_as_name(&self) -> Result<Cow<'_, HeaderName>, InvalidHeaderName> {
HeaderName::from_str(self).map(Cow::Owned)
}
}
impl AsHeaderName for &String {}

File diff suppressed because it is too large Load diff

View file

@ -1,4 +1,4 @@
//! Typed HTTP headers, pre-defined `HeaderName`s, traits for parsing/conversion and other
//! Typed HTTP headers, pre-defined `HeaderName`s, traits for parsing and conversion, and other
//! header utility methods.
use std::fmt;
@ -11,6 +11,7 @@ pub use http::header::*;
use crate::error::ParseError;
use crate::httpmessage::HttpMessage;
mod as_name;
mod into_pair;
mod into_value;
mod utils;
@ -23,6 +24,7 @@ pub use self::common::*;
#[doc(hidden)]
pub use self::shared::*;
pub use self::as_name::AsHeaderName;
pub use self::into_pair::IntoHeaderPair;
pub use self::into_value::IntoHeaderValue;
#[doc(hidden)]
@ -39,16 +41,14 @@ pub trait Header: IntoHeaderValue {
fn parse<T: HttpMessage>(msg: &T) -> Result<Self, ParseError>;
}
#[doc(hidden)]
#[derive(Debug, Default)]
pub(crate) struct Writer {
buf: BytesMut,
}
impl Writer {
fn new() -> Writer {
Writer {
buf: BytesMut::new(),
}
Writer::default()
}
fn take(&mut self) -> Bytes {
@ -71,12 +71,8 @@ impl fmt::Write for Writer {
/// 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());
for (h, v) in map.iter() {
new_map.append(h.clone(), v.clone());
}
new_map
fn from(mut map: http::HeaderMap) -> HeaderMap {
HeaderMap::from_drain(map.drain())
}
}

View file

@ -177,13 +177,17 @@ impl<P> fmt::Debug for Request<P> {
self.method(),
self.path()
)?;
if let Some(q) = self.uri().query().as_ref() {
writeln!(f, " query: ?{:?}", q)?;
}
writeln!(f, " headers:")?;
for (key, val) in self.headers() {
for (key, val) in self.headers().iter() {
writeln!(f, " {:?}: {:?}", key, val)?;
}
Ok(())
}
}

View file

@ -363,7 +363,9 @@ impl ResponseBuilder {
{
if let Some(parts) = parts(&mut self.head, &self.err) {
match header.try_into_header_pair() {
Ok((key, value)) => parts.headers.insert(key, value),
Ok((key, value)) => {
parts.headers.insert(key, value);
}
Err(e) => self.err = Some(e.into()),
};
}
@ -752,9 +754,11 @@ impl<'a> From<&'a ResponseHead> for ResponseBuilder {
let mut msg = BoxedResponseHead::new(head.status);
msg.version = head.version;
msg.reason = head.reason;
for (k, v) in &head.headers {
for (k, v) in head.headers.iter() {
msg.headers.append(k.clone(), v.clone());
}
msg.no_chunking(!head.chunked());
ResponseBuilder {
@ -893,16 +897,20 @@ mod tests {
.max_age(time::Duration::days(1))
.finish(),
)
.del_cookie(&cookies[1])
.del_cookie(&cookies[0])
.finish();
let mut val: Vec<_> = resp
let mut val = resp
.headers()
.get_all(SET_COOKIE)
.map(|v| v.to_str().unwrap().to_owned())
.collect();
.collect::<Vec<_>>();
val.sort();
// the .del_cookie call
assert!(val[0].starts_with("cookie1=; Max-Age=0;"));
// the .cookie call
assert_eq!(
val[1],
"name=value; HttpOnly; Path=/test; Domain=www.rust-lang.org; Max-Age=86400"
@ -927,9 +935,9 @@ mod tests {
let mut iter = r.cookies();
let v = iter.next().unwrap();
assert_eq!((v.name(), v.value()), ("cookie3", "val300"));
let v = iter.next().unwrap();
assert_eq!((v.name(), v.value()), ("original", "val100"));
let v = iter.next().unwrap();
assert_eq!((v.name(), v.value()), ("cookie3", "val300"));
}
#[test]

View file

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

View file

@ -159,7 +159,9 @@ impl ClientRequest {
H: IntoHeaderPair,
{
match header.try_into_header_pair() {
Ok((key, value)) => self.head.headers.insert(key, value),
Ok((key, value)) => {
self.head.headers.insert(key, value);
}
Err(e) => self.err = Some(e.into()),
};
@ -232,7 +234,9 @@ impl ClientRequest {
<HeaderValue as TryFrom<V>>::Error: Into<HttpError>,
{
match HeaderValue::try_from(value) {
Ok(value) => self.head.headers.insert(header::CONTENT_TYPE, value),
Ok(value) => {
self.head.headers.insert(header::CONTENT_TYPE, value);
}
Err(e) => self.err = Some(e.into()),
}
self

View file

@ -304,7 +304,9 @@ impl RequestSender {
RequestSender::Owned(head) => {
if !head.headers.contains_key(&key) {
match value.try_into_value() {
Ok(value) => head.headers.insert(key, value),
Ok(value) => {
head.headers.insert(key, value);
}
Err(e) => return Err(e.into()),
}
}

View file

@ -445,10 +445,10 @@ mod tests {
{
let cookies = req.cookies().unwrap();
assert_eq!(cookies.len(), 2);
assert_eq!(cookies[0].name(), "cookie2");
assert_eq!(cookies[0].value(), "value2");
assert_eq!(cookies[1].name(), "cookie1");
assert_eq!(cookies[1].value(), "value1");
assert_eq!(cookies[0].name(), "cookie1");
assert_eq!(cookies[0].value(), "value1");
assert_eq!(cookies[1].name(), "cookie2");
assert_eq!(cookies[1].value(), "value2");
}
let cookie = req.cookie("cookie1");