diff --git a/actix-files/src/named.rs b/actix-files/src/named.rs index 717b058cf..1f54c8288 100644 --- a/actix-files/src/named.rs +++ b/actix-files/src/named.rs @@ -344,7 +344,7 @@ impl Responder for NamedFile { // check last modified let not_modified = if !none_match(etag.as_ref(), req) { true - } else if req.headers().contains_key(header::IF_NONE_MATCH) { + } else if req.headers().contains_key(&header::IF_NONE_MATCH) { false } else if let (Some(ref m), Some(header::IfModifiedSince(ref since))) = (last_modified, req.get_header()) @@ -378,7 +378,7 @@ impl Responder for NamedFile { let mut offset = 0; // check for range header - if let Some(ranges) = req.headers().get(header::RANGE) { + if let Some(ranges) = req.headers().get(&header::RANGE) { if let Ok(rangesheader) = ranges.to_str() { if let Ok(rangesvec) = HttpRange::parse(rangesheader, length) { length = rangesvec[0].length; diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 315dcd5ca..fe9b62d14 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -60,6 +60,7 @@ bitflags = "1.0" bytes = "0.4" byteorder = "1.2" derive_more = "0.14" +either = "1.5.2" encoding = "0.2" futures = "0.1" hashbrown = "0.1.8" diff --git a/actix-http/src/client/h2proto.rs b/actix-http/src/client/h2proto.rs index d45716ab8..222e442c5 100644 --- a/actix-http/src/client/h2proto.rs +++ b/actix-http/src/client/h2proto.rs @@ -107,7 +107,7 @@ where let mut head = ResponseHead::default(); head.version = parts.version; head.status = parts.status; - head.headers = parts.headers; + head.headers = parts.headers.into(); Ok((head, payload)) }) diff --git a/actix-http/src/encoding/decoder.rs b/actix-http/src/encoding/decoder.rs index 16d15e905..4b56a1b62 100644 --- a/actix-http/src/encoding/decoder.rs +++ b/actix-http/src/encoding/decoder.rs @@ -55,7 +55,7 @@ where #[inline] pub fn from_headers(stream: S, headers: &HeaderMap) -> Decoder { // check content-encoding - let encoding = if let Some(enc) = headers.get(CONTENT_ENCODING) { + let encoding = if let Some(enc) = headers.get(&CONTENT_ENCODING) { if let Ok(enc) = enc.to_str() { ContentEncoding::from(enc) } else { diff --git a/actix-http/src/encoding/encoder.rs b/actix-http/src/encoding/encoder.rs index 50e9d068b..6537379f5 100644 --- a/actix-http/src/encoding/encoder.rs +++ b/actix-http/src/encoding/encoder.rs @@ -31,7 +31,7 @@ impl Encoder { head: &mut ResponseHead, body: ResponseBody, ) -> ResponseBody> { - let can_encode = !(head.headers().contains_key(CONTENT_ENCODING) + let can_encode = !(head.headers().contains_key(&CONTENT_ENCODING) || head.status == StatusCode::SWITCHING_PROTOCOLS || encoding == ContentEncoding::Identity || encoding == ContentEncoding::Auto); diff --git a/actix-http/src/h1/codec.rs b/actix-http/src/h1/codec.rs index 3834254a2..1f3983adb 100644 --- a/actix-http/src/h1/codec.rs +++ b/actix-http/src/h1/codec.rs @@ -22,8 +22,8 @@ use crate::response::Response; bitflags! { struct Flags: u8 { const HEAD = 0b0000_0001; - const KEEPALIVE_ENABLED = 0b0000_1000; - const STREAM = 0b0001_0000; + const KEEPALIVE_ENABLED = 0b0000_0010; + const STREAM = 0b0000_0100; } } @@ -39,8 +39,8 @@ pub struct Codec { // encoder part flags: Flags, - headers_size: u32, encoder: encoder::MessageEncoder>, + // headers_size: u32, } impl Default for Codec { @@ -73,7 +73,7 @@ impl Codec { ctype: ConnectionType::Close, flags, - headers_size: 0, + // headers_size: 0, encoder: encoder::MessageEncoder::default(), } } @@ -159,37 +159,33 @@ impl Encoder for Codec { ) -> Result<(), Self::Error> { match item { Message::Item((mut res, length)) => { - if res.head().status == StatusCode::CONTINUE { - dst.extend_from_slice(b"HTTP/1.1 100 Continue\r\n\r\n"); - } else { - // set response version - res.head_mut().version = self.version; + // set response version + res.head_mut().version = self.version; - // connection status - self.ctype = if let Some(ct) = res.head().ctype() { - if ct == ConnectionType::KeepAlive { - self.ctype - } else { - ct - } - } else { + // connection status + self.ctype = if let Some(ct) = res.head().ctype() { + if ct == ConnectionType::KeepAlive { self.ctype - }; + } else { + ct + } + } else { + self.ctype + }; - // encode message - let len = dst.len(); - self.encoder.encode( - dst, - &mut res, - self.flags.contains(Flags::HEAD), - self.flags.contains(Flags::STREAM), - self.version, - length, - self.ctype, - &self.config, - )?; - self.headers_size = (dst.len() - len) as u32; - } + // encode message + let len = dst.len(); + self.encoder.encode( + dst, + &mut res, + self.flags.contains(Flags::HEAD), + self.flags.contains(Flags::STREAM), + self.version, + length, + self.ctype, + &self.config, + )?; + // self.headers_size = (dst.len() - len) as u32; } Message::Chunk(Some(bytes)) => { self.encoder.encode_chunk(bytes.as_ref(), dst)?; diff --git a/actix-http/src/h1/decoder.rs b/actix-http/src/h1/decoder.rs index 88de9bc6d..417441c6a 100644 --- a/actix-http/src/h1/decoder.rs +++ b/actix-http/src/h1/decoder.rs @@ -5,11 +5,12 @@ use actix_codec::Decoder; use bytes::{Bytes, BytesMut}; use futures::{Async, Poll}; use http::header::{HeaderName, HeaderValue}; -use http::{header, HeaderMap, HttpTryFrom, Method, StatusCode, Uri, Version}; +use http::{header, HttpTryFrom, Method, StatusCode, Uri, Version}; use httparse; use log::{debug, error, trace}; use crate::error::ParseError; +use crate::header::HeaderMap; use crate::message::{ConnectionType, ResponseHead}; use crate::request::Request; @@ -630,6 +631,7 @@ mod tests { use super::*; use crate::error::ParseError; + use crate::http::header::{HeaderName, SET_COOKIE}; use crate::httpmessage::HttpMessage; impl PayloadType { @@ -790,7 +792,13 @@ mod tests { assert_eq!(req.version(), Version::HTTP_11); assert_eq!(*req.method(), Method::GET); assert_eq!(req.path(), "/test"); - assert_eq!(req.headers().get("test").unwrap().as_bytes(), b"value"); + assert_eq!( + req.headers() + .get(HeaderName::try_from("test").unwrap()) + .unwrap() + .as_bytes(), + b"value" + ); } #[test] @@ -805,12 +813,11 @@ mod tests { let val: Vec<_> = req .headers() - .get_all("Set-Cookie") - .iter() + .get_all(SET_COOKIE) .map(|v| v.to_str().unwrap().to_owned()) .collect(); - assert_eq!(val[0], "c1=cookie1"); - assert_eq!(val[1], "c2=cookie2"); + assert_eq!(val[1], "c1=cookie1"); + assert_eq!(val[0], "c2=cookie2"); } #[test] diff --git a/actix-http/src/h1/encoder.rs b/actix-http/src/h1/encoder.rs index 374b8bec9..9a81fb2b8 100644 --- a/actix-http/src/h1/encoder.rs +++ b/actix-http/src/h1/encoder.rs @@ -6,15 +6,16 @@ use std::str::FromStr; use std::{cmp, fmt, io, mem}; use bytes::{BufMut, Bytes, BytesMut}; -use http::header::{ - HeaderValue, ACCEPT_ENCODING, CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCODING, -}; -use http::{HeaderMap, Method, StatusCode, Version}; use crate::body::BodySize; use crate::config::ServiceConfig; +use crate::header::map; use crate::header::ContentEncoding; use crate::helpers; +use crate::http::header::{ + HeaderValue, ACCEPT_ENCODING, CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCODING, +}; +use crate::http::{HeaderMap, Method, StatusCode, Version}; use crate::message::{ConnectionType, Head, RequestHead, ResponseHead}; use crate::request::Request; use crate::response::Response; @@ -109,7 +110,7 @@ pub(crate) trait MessageType: Sized { let mut has_date = false; let mut remaining = dst.remaining_mut(); let mut buf = unsafe { &mut *(dst.bytes_mut() as *mut [u8]) }; - for (key, value) in self.headers() { + for (key, value) in self.headers().inner.iter() { match *key { CONNECTION => continue, TRANSFER_ENCODING | CONTENT_LENGTH if skip_len => continue, @@ -118,31 +119,59 @@ pub(crate) trait MessageType: Sized { } _ => (), } - - let v = value.as_ref(); let k = key.as_str().as_bytes(); - let len = k.len() + v.len() + 4; - if len > remaining { - unsafe { - dst.advance_mut(pos); + match value { + map::Value::One(ref val) => { + let v = val.as_ref(); + let len = k.len() + v.len() + 4; + if len > remaining { + unsafe { + dst.advance_mut(pos); + } + pos = 0; + dst.reserve(len); + remaining = dst.remaining_mut(); + unsafe { + buf = &mut *(dst.bytes_mut() as *mut _); + } + } + buf[pos..pos + k.len()].copy_from_slice(k); + pos += k.len(); + buf[pos..pos + 2].copy_from_slice(b": "); + pos += 2; + buf[pos..pos + v.len()].copy_from_slice(v); + pos += v.len(); + buf[pos..pos + 2].copy_from_slice(b"\r\n"); + pos += 2; + remaining -= len; } - pos = 0; - dst.reserve(len); - remaining = dst.remaining_mut(); - unsafe { - buf = &mut *(dst.bytes_mut() as *mut _); + map::Value::Multi(ref vec) => { + for val in vec { + let v = val.as_ref(); + let len = k.len() + v.len() + 4; + if len > remaining { + unsafe { + dst.advance_mut(pos); + } + pos = 0; + dst.reserve(len); + remaining = dst.remaining_mut(); + unsafe { + buf = &mut *(dst.bytes_mut() as *mut _); + } + } + buf[pos..pos + k.len()].copy_from_slice(k); + pos += k.len(); + buf[pos..pos + 2].copy_from_slice(b": "); + pos += 2; + buf[pos..pos + v.len()].copy_from_slice(v); + pos += v.len(); + buf[pos..pos + 2].copy_from_slice(b"\r\n"); + pos += 2; + remaining -= len; + } } } - - buf[pos..pos + k.len()].copy_from_slice(k); - pos += k.len(); - buf[pos..pos + 2].copy_from_slice(b": "); - pos += 2; - buf[pos..pos + v.len()].copy_from_slice(v); - pos += v.len(); - buf[pos..pos + 2].copy_from_slice(b"\r\n"); - pos += 2; - remaining -= len; } unsafe { dst.advance_mut(pos); diff --git a/actix-http/src/h2/dispatcher.rs b/actix-http/src/h2/dispatcher.rs index e00996048..cbb74f609 100644 --- a/actix-http/src/h2/dispatcher.rs +++ b/actix-http/src/h2/dispatcher.rs @@ -116,7 +116,7 @@ where head.uri = parts.uri; head.method = parts.method; head.version = parts.version; - head.headers = parts.headers; + head.headers = parts.headers.into(); tokio_current_thread::spawn(ServiceResponse:: { state: ServiceResponseState::ServiceCall( self.service.call(req), diff --git a/actix-http/src/header/common/cache_control.rs b/actix-http/src/header/common/cache_control.rs index 0b79ea7c0..55774619b 100644 --- a/actix-http/src/header/common/cache_control.rs +++ b/actix-http/src/header/common/cache_control.rs @@ -64,7 +64,7 @@ impl Header for CacheControl { where T: crate::HttpMessage, { - let directives = from_comma_delimited(msg.headers().get_all(Self::name()))?; + let directives = from_comma_delimited(msg.headers().get_all(&Self::name()))?; if !directives.is_empty() { Ok(CacheControl(directives)) } else { diff --git a/actix-http/src/header/common/content_disposition.rs b/actix-http/src/header/common/content_disposition.rs index 700400dad..badf307a0 100644 --- a/actix-http/src/header/common/content_disposition.rs +++ b/actix-http/src/header/common/content_disposition.rs @@ -444,7 +444,7 @@ impl Header for ContentDisposition { } fn parse(msg: &T) -> Result { - if let Some(h) = msg.headers().get(Self::name()) { + if let Some(h) = msg.headers().get(&Self::name()) { Self::from_raw(&h) } else { Err(crate::error::ParseError::Header) diff --git a/actix-http/src/header/common/if_range.rs b/actix-http/src/header/common/if_range.rs index 2140ccbb3..e910ebd96 100644 --- a/actix-http/src/header/common/if_range.rs +++ b/actix-http/src/header/common/if_range.rs @@ -73,12 +73,12 @@ impl Header for IfRange { T: HttpMessage, { let etag: Result = - from_one_raw_str(msg.headers().get(header::IF_RANGE)); + from_one_raw_str(msg.headers().get(&header::IF_RANGE)); if let Ok(etag) = etag { return Ok(IfRange::EntityTag(etag)); } let date: Result = - from_one_raw_str(msg.headers().get(header::IF_RANGE)); + from_one_raw_str(msg.headers().get(&header::IF_RANGE)); if let Ok(date) = date { return Ok(IfRange::Date(date)); } diff --git a/actix-http/src/header/map.rs b/actix-http/src/header/map.rs new file mode 100644 index 000000000..694aed02a --- /dev/null +++ b/actix-http/src/header/map.rs @@ -0,0 +1,384 @@ +use either::Either; +use hashbrown::hash_map::{self, Entry}; +use hashbrown::HashMap; +use http::header::{HeaderName, HeaderValue}; +use http::HttpTryFrom; + +/// A set of HTTP headers +/// +/// `HeaderMap` is an multimap of [`HeaderName`] to values. +/// +/// [`HeaderName`]: struct.HeaderName.html +#[derive(Debug)] +pub struct HeaderMap { + pub(crate) inner: HashMap, +} + +#[derive(Debug)] +pub(crate) enum Value { + One(HeaderValue), + Multi(Vec), +} + +impl Value { + fn get(&self) -> &HeaderValue { + match self { + Value::One(ref val) => val, + Value::Multi(ref val) => &val[0], + } + } + + fn get_mut(&mut self) -> &mut HeaderValue { + match self { + Value::One(ref mut val) => val, + Value::Multi(ref mut val) => &mut val[0], + } + } + + fn append(&mut self, val: HeaderValue) { + match self { + Value::One(_) => { + let data = std::mem::replace(self, Value::Multi(vec![val])); + match data { + Value::One(val) => self.append(val), + Value::Multi(_) => unreachable!(), + } + } + Value::Multi(ref mut vec) => vec.push(val), + } + } +} + +impl HeaderMap { + /// Create an empty `HeaderMap`. + /// + /// The map will be created without any capacity. This function will not + /// allocate. + pub fn new() -> Self { + HeaderMap { + inner: HashMap::new(), + } + } + + /// Create an empty `HeaderMap` with the specified capacity. + /// + /// The returned map will allocate internal storage in order to hold about + /// `capacity` elements without reallocating. However, this is a "best + /// effort" as there are usage patterns that could cause additional + /// allocations before `capacity` headers are stored in the map. + /// + /// More capacity than requested may be allocated. + pub fn with_capacity(capacity: usize) -> HeaderMap { + HeaderMap { + inner: HashMap::with_capacity(capacity), + } + } + + /// Returns the number of keys stored in the map. + /// + /// This number could be be less than or equal to actual headers stored in + /// the map. + pub fn len(&self) -> usize { + self.inner.len() + } + + /// Returns true if the map contains no elements. + pub fn is_empty(&self) -> bool { + self.inner.len() == 0 + } + + /// Clears the map, removing all key-value pairs. Keeps the allocated memory + /// for reuse. + pub fn clear(&mut self) { + self.inner.clear(); + } + + /// Returns the number of headers the map can hold without reallocating. + /// + /// This number is an approximation as certain usage patterns could cause + /// additional allocations before the returned capacity is filled. + pub fn capacity(&self) -> usize { + self.inner.capacity() + } + + /// Reserves capacity for at least `additional` more headers to be inserted + /// into the `HeaderMap`. + /// + /// The header map may reserve more space to avoid frequent reallocations. + /// Like with `with_capacity`, this will be a "best effort" to avoid + /// allocations until `additional` more headers are inserted. Certain usage + /// patterns could cause additional allocations before the number is + /// reached. + pub fn reserve(&mut self, additional: usize) { + self.inner.reserve(additional) + } + + /// Returns a reference to the value associated with the key. + /// + /// If there are multiple values associated with the key, then the first one + /// 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(&self, name: N) -> Option<&HeaderValue> { + self.get2(name).map(|v| v.get()) + } + + fn get2(&self, name: N) -> Option<&Value> { + match name.as_name() { + Either::Left(name) => self.inner.get(name), + Either::Right(s) => { + if let Ok(name) = HeaderName::try_from(s) { + self.inner.get(&name) + } else { + None + } + } + } + } + + /// Returns a view 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. + /// + /// [`GetAll`]: struct.GetAll.html + pub fn get_all(&self, name: N) -> GetAll { + GetAll { + idx: 0, + item: self.get2(name), + } + } + + /// Returns a mutable reference to the value associated with the key. + /// + /// If there are multiple values associated with the key, then the first one + /// is returned. Use `entry` to get all values associated with a given + /// key. Returns `None` if there are no values associated with the key. + pub fn get_mut(&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::Right(s) => { + if let Ok(name) = HeaderName::try_from(s) { + self.inner.get_mut(&name).map(|v| v.get_mut()) + } else { + None + } + } + } + } + + /// Returns true if the map contains a value for the specified key. + pub fn contains_key(&self, key: N) -> bool { + match key.as_name() { + Either::Left(name) => self.inner.contains_key(name), + Either::Right(s) => { + if let Ok(name) = HeaderName::try_from(s) { + self.inner.contains_key(&name) + } else { + false + } + } + } + } + + /// An iterator visiting all key-value pairs. + /// + /// The iteration order is arbitrary, but consistent across platforms for + /// the same crate version. Each key will be yielded once per associated + /// value. So, if a key has 3 associated values, it will be yielded 3 times. + pub fn iter(&self) -> Iter { + Iter::new(self.inner.iter()) + } + + /// An iterator visiting all keys. + /// + /// The iteration order is arbitrary, but consistent across platforms for + /// the same crate version. Each key will be yielded only once even if it + /// has multiple associated values. + pub fn keys(&self) -> Keys { + Keys(self.inner.keys()) + } + + /// Inserts a key-value pair into the map. + /// + /// If the map did not previously have this key present, then `None` is + /// returned. + /// + /// If the map did have this key present, the new value is associated with + /// the key and all previous values are removed. **Note** that only a single + /// one of the previous values is returned. If there are multiple values + /// that have been previously associated with the key, then the first one is + /// returned. See `insert_mult` on `OccupiedEntry` for an API that returns + /// all values. + /// + /// The key is not updated, though; this matters for types that can be `==` + /// without being identical. + pub fn insert(&mut self, key: HeaderName, val: HeaderValue) { + let _ = self.inner.insert(key, Value::One(val)); + } + + /// Inserts a key-value pair into the map. + /// + /// If the map did not previously have this key present, then `false` is + /// returned. + /// + /// If the map did have this key present, the new value is pushed to the end + /// of the list of values currently associated with the key. The key is not + /// updated, though; this matters for types that can be `==` without being + /// identical. + pub fn append(&mut self, key: HeaderName, value: HeaderValue) { + match self.inner.entry(key) { + Entry::Occupied(mut entry) => entry.get_mut().append(value), + Entry::Vacant(entry) => { + entry.insert(Value::One(value)); + } + } + } + + /// Removes all headers for a particular header name from the map. + pub fn remove(&mut self, key: N) { + match key.as_name() { + Either::Left(name) => { + let _ = self.inner.remove(name); + } + Either::Right(s) => { + if let Ok(name) = HeaderName::try_from(s) { + let _ = self.inner.remove(&name); + } + } + } + } +} + +#[doc(hidden)] +pub trait AsName { + fn as_name(&self) -> Either<&HeaderName, &str>; +} + +impl AsName for HeaderName { + fn as_name(&self) -> Either<&HeaderName, &str> { + Either::Left(self) + } +} + +impl<'a> AsName for &'a HeaderName { + fn as_name(&self) -> Either<&HeaderName, &str> { + Either::Left(self) + } +} + +impl<'a> AsName for &'a str { + fn as_name(&self) -> Either<&HeaderName, &str> { + Either::Right(self) + } +} + +impl AsName for String { + fn as_name(&self) -> Either<&HeaderName, &str> { + Either::Right(self.as_str()) + } +} + +impl<'a> AsName for &'a String { + fn as_name(&self) -> Either<&HeaderName, &str> { + Either::Right(self.as_str()) + } +} + +pub struct GetAll<'a> { + idx: usize, + item: Option<&'a Value>, +} + +impl<'a> Iterator for GetAll<'a> { + type Item = &'a HeaderValue; + + #[inline] + fn next(&mut self) -> Option<&'a HeaderValue> { + if let Some(ref val) = self.item { + match val { + Value::One(ref val) => { + self.item.take(); + Some(val) + } + Value::Multi(ref vec) => { + if self.idx < vec.len() { + let item = Some(&vec[self.idx]); + self.idx += 1; + item + } else { + self.item.take(); + None + } + } + } + } else { + None + } + } +} + +pub struct Keys<'a>(hash_map::Keys<'a, HeaderName, Value>); + +impl<'a> Iterator for Keys<'a> { + type Item = &'a HeaderName; + + #[inline] + fn next(&mut self) -> Option<&'a HeaderName> { + self.0.next() + } +} + +impl<'a> IntoIterator for &'a HeaderMap { + type Item = (&'a HeaderName, &'a HeaderValue); + type IntoIter = Iter<'a>; + + fn into_iter(self) -> Self::IntoIter { + self.iter() + } +} + +pub struct Iter<'a> { + idx: usize, + current: Option<(&'a HeaderName, &'a Vec)>, + iter: hash_map::Iter<'a, HeaderName, Value>, +} + +impl<'a> Iter<'a> { + fn new(iter: hash_map::Iter<'a, HeaderName, Value>) -> Self { + Self { + iter, + idx: 0, + current: None, + } + } +} + +impl<'a> Iterator for Iter<'a> { + type Item = (&'a HeaderName, &'a HeaderValue); + + #[inline] + fn next(&mut self) -> Option<(&'a HeaderName, &'a HeaderValue)> { + if let Some(ref mut item) = self.current { + if self.idx < item.1.len() { + let item = (item.0, &item.1[self.idx]); + self.idx += 1; + return Some(item); + } else { + self.idx = 0; + self.current.take(); + } + } + if let Some(item) = self.iter.next() { + match item.1 { + Value::One(ref value) => Some((item.0, value)), + Value::Multi(ref vec) => { + self.current = Some((item.0, vec)); + self.next() + } + } + } else { + None + } + } +} diff --git a/actix-http/src/header/mod.rs b/actix-http/src/header/mod.rs index deedd693f..620183476 100644 --- a/actix-http/src/header/mod.rs +++ b/actix-http/src/header/mod.rs @@ -4,7 +4,6 @@ use std::{fmt, str::FromStr}; use bytes::{Bytes, BytesMut}; -use http::header::GetAll; use http::Error as HttpError; use mime::Mime; @@ -14,12 +13,17 @@ use crate::error::ParseError; use crate::httpmessage::HttpMessage; mod common; +pub(crate) mod map; mod shared; #[doc(hidden)] pub use self::common::*; #[doc(hidden)] pub use self::shared::*; +#[doc(hidden)] +pub use self::map::GetAll; +pub use self::map::HeaderMap; + /// A trait for any object that will represent a header field and value. pub trait Header where @@ -220,8 +224,8 @@ impl fmt::Write for Writer { #[inline] #[doc(hidden)] /// Reads a comma-delimited raw header into a Vec. -pub fn from_comma_delimited( - all: GetAll, +pub fn from_comma_delimited<'a, I: Iterator + 'a, T: FromStr>( + all: I, ) -> Result, ParseError> { let mut result = Vec::new(); for h in all { @@ -379,6 +383,17 @@ pub fn http_percent_encode(f: &mut fmt::Formatter, bytes: &[u8]) -> fmt::Result fmt::Display::fmt(&encoded, f) } +/// Convert http::HeaderMap to a HeaderMap +impl From 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 + } +} + mod percent_encoding_http { use percent_encoding::{self, define_encode_set}; diff --git a/actix-http/src/httpmessage.rs b/actix-http/src/httpmessage.rs index 7a2db52d6..1534973a8 100644 --- a/actix-http/src/httpmessage.rs +++ b/actix-http/src/httpmessage.rs @@ -4,13 +4,13 @@ use std::str; use encoding::all::UTF_8; use encoding::label::encoding_from_whatwg_label; use encoding::EncodingRef; -use http::{header, HeaderMap}; +use http::header; use mime::Mime; use crate::cookie::Cookie; use crate::error::{ContentTypeError, CookieParseError, ParseError}; use crate::extensions::Extensions; -use crate::header::Header; +use crate::header::{Header, HeaderMap}; use crate::payload::Payload; struct Cookies(Vec>); diff --git a/actix-http/src/lib.rs b/actix-http/src/lib.rs index ed3669e85..5879e1915 100644 --- a/actix-http/src/lib.rs +++ b/actix-http/src/lib.rs @@ -45,10 +45,11 @@ pub mod http { // re-exports pub use http::header::{HeaderName, HeaderValue}; pub use http::uri::PathAndQuery; - pub use http::{uri, Error, HeaderMap, HttpTryFrom, Uri}; + pub use http::{uri, Error, HttpTryFrom, Uri}; pub use http::{Method, StatusCode, Version}; pub use crate::cookie::{Cookie, CookieBuilder}; + pub use crate::header::HeaderMap; /// Various http headers pub mod header { diff --git a/actix-http/src/message.rs b/actix-http/src/message.rs index 2fdb28e40..25bc55baf 100644 --- a/actix-http/src/message.rs +++ b/actix-http/src/message.rs @@ -5,7 +5,8 @@ use std::rc::Rc; use bitflags::bitflags; use crate::extensions::Extensions; -use crate::http::{header, HeaderMap, Method, StatusCode, Uri, Version}; +use crate::header::HeaderMap; +use crate::http::{header, Method, StatusCode, Uri, Version}; /// Represents various types of connection #[derive(Copy, Clone, PartialEq, Debug)] @@ -174,7 +175,7 @@ impl Default for ResponseHead { ResponseHead { version: Version::default(), status: StatusCode::OK, - headers: HeaderMap::with_capacity(16), + headers: HeaderMap::with_capacity(12), reason: None, flags: Flags::empty(), extensions: RefCell::new(Extensions::new()), @@ -182,18 +183,6 @@ impl Default for ResponseHead { } } -impl Head for ResponseHead { - fn clear(&mut self) { - self.reason = None; - self.flags = Flags::empty(); - self.headers.clear(); - } - - fn pool() -> &'static MessagePool { - RESPONSE_POOL.with(|p| *p) - } -} - impl ResponseHead { /// Message extensions #[inline] @@ -300,7 +289,6 @@ impl ResponseHead { pub struct Message { head: Rc, - pool: &'static MessagePool, } impl Message { @@ -314,7 +302,6 @@ impl Clone for Message { fn clone(&self) -> Self { Message { head: self.head.clone(), - pool: self.pool, } } } @@ -336,17 +323,52 @@ impl std::ops::DerefMut for Message { impl Drop for Message { fn drop(&mut self) { if Rc::strong_count(&self.head) == 1 { - self.pool.release(self.head.clone()); + T::pool().release(self.head.clone()); } } } +pub(crate) struct BoxedResponseHead { + head: Option>, +} + +impl BoxedResponseHead { + /// Get new message from the pool of objects + pub fn new() -> Self { + RESPONSE_POOL.with(|p| p.get_message()) + } +} + +impl std::ops::Deref for BoxedResponseHead { + type Target = ResponseHead; + + fn deref(&self) -> &Self::Target { + self.head.as_ref().unwrap() + } +} + +impl std::ops::DerefMut for BoxedResponseHead { + fn deref_mut(&mut self) -> &mut Self::Target { + self.head.as_mut().unwrap() + } +} + +impl Drop for BoxedResponseHead { + fn drop(&mut self) { + RESPONSE_POOL.with(|p| p.release(self.head.take().unwrap())) + } +} + #[doc(hidden)] /// Request's objects pool pub struct MessagePool(RefCell>>); +#[doc(hidden)] +/// Request's objects pool +pub struct BoxedResponsePool(RefCell>>); + thread_local!(static REQUEST_POOL: &'static MessagePool = MessagePool::::create()); -thread_local!(static RESPONSE_POOL: &'static MessagePool = MessagePool::::create()); +thread_local!(static RESPONSE_POOL: &'static BoxedResponsePool = BoxedResponsePool::create()); impl MessagePool { fn create() -> &'static MessagePool { @@ -361,14 +383,10 @@ impl MessagePool { if let Some(r) = Rc::get_mut(&mut msg) { r.clear(); } - Message { - head: msg, - pool: self, - } + Message { head: msg } } else { Message { head: Rc::new(T::default()), - pool: self, } } } @@ -382,3 +400,34 @@ impl MessagePool { } } } + +impl BoxedResponsePool { + fn create() -> &'static BoxedResponsePool { + let pool = BoxedResponsePool(RefCell::new(VecDeque::with_capacity(128))); + Box::leak(Box::new(pool)) + } + + /// Get message from the pool + #[inline] + fn get_message(&'static self) -> BoxedResponseHead { + if let Some(mut head) = self.0.borrow_mut().pop_front() { + head.reason = None; + head.headers.clear(); + head.flags = Flags::empty(); + BoxedResponseHead { head: Some(head) } + } else { + BoxedResponseHead { + head: Some(Box::new(ResponseHead::default())), + } + } + } + + #[inline] + /// Release request instance + fn release(&self, msg: Box) { + let v = &mut self.0.borrow_mut(); + if v.len() < 128 { + v.push_front(msg); + } + } +} diff --git a/actix-http/src/request.rs b/actix-http/src/request.rs index a645c7aeb..468b4e337 100644 --- a/actix-http/src/request.rs +++ b/actix-http/src/request.rs @@ -1,9 +1,10 @@ use std::cell::{Ref, RefMut}; use std::fmt; -use http::{header, HeaderMap, Method, Uri, Version}; +use http::{header, Method, Uri, Version}; use crate::extensions::Extensions; +use crate::header::HeaderMap; use crate::httpmessage::HttpMessage; use crate::message::{Message, RequestHead}; use crate::payload::{Payload, PayloadStream}; @@ -161,7 +162,7 @@ impl

fmt::Debug for Request

{ writeln!(f, " query: ?{:?}", q)?; } writeln!(f, " headers:")?; - for (key, val) in self.headers().iter() { + for (key, val) in self.headers() { writeln!(f, " {:?}: {:?}", key, val)?; } Ok(()) diff --git a/actix-http/src/response.rs b/actix-http/src/response.rs index c3fed133d..707c9af63 100644 --- a/actix-http/src/response.rs +++ b/actix-http/src/response.rs @@ -6,8 +6,6 @@ use std::{fmt, io, str}; use bytes::{BufMut, Bytes, BytesMut}; use futures::future::{ok, FutureResult, IntoFuture}; use futures::Stream; -use http::header::{self, HeaderName, HeaderValue}; -use http::{Error as HttpError, HeaderMap, HttpTryFrom, StatusCode}; use serde::Serialize; use serde_json; @@ -16,11 +14,13 @@ use crate::cookie::{Cookie, CookieJar}; use crate::error::Error; use crate::extensions::Extensions; use crate::header::{Header, IntoHeaderValue}; -use crate::message::{ConnectionType, Message, ResponseHead}; +use crate::http::header::{self, HeaderName, HeaderValue}; +use crate::http::{Error as HttpError, HeaderMap, HttpTryFrom, StatusCode}; +use crate::message::{BoxedResponseHead, ConnectionType, ResponseHead}; /// An HTTP Response pub struct Response { - head: Message, + head: BoxedResponseHead, body: ResponseBody, error: Option, } @@ -41,7 +41,7 @@ impl Response { /// Constructs a response #[inline] pub fn new(status: StatusCode) -> Response { - let mut head: Message = Message::new(); + let mut head = BoxedResponseHead::new(); head.status = status; Response { @@ -93,7 +93,7 @@ impl Response { /// Constructs a response with body #[inline] pub fn with_body(status: StatusCode, body: B) -> Response { - let mut head: Message = Message::new(); + let mut head = BoxedResponseHead::new(); head.status = status; Response { head, @@ -136,7 +136,7 @@ impl Response { #[inline] pub fn cookies(&self) -> CookieIter { CookieIter { - iter: self.head.headers.get_all(header::SET_COOKIE).iter(), + iter: self.head.headers.get_all(header::SET_COOKIE), } } @@ -158,7 +158,6 @@ impl Response { let h = &mut self.head.headers; let vals: Vec = h .get_all(header::SET_COOKIE) - .iter() .map(|v| v.to_owned()) .collect(); h.remove(header::SET_COOKIE); @@ -286,7 +285,7 @@ impl IntoFuture for Response { } pub struct CookieIter<'a> { - iter: header::ValueIter<'a, HeaderValue>, + iter: header::GetAll<'a>, } impl<'a> Iterator for CookieIter<'a> { @@ -320,7 +319,7 @@ impl<'a> io::Write for Writer<'a> { /// This type can be used to construct an instance of `Response` through a /// builder-like pattern. pub struct ResponseBuilder { - head: Option>, + head: Option, err: Option, cookies: Option, } @@ -328,7 +327,7 @@ pub struct ResponseBuilder { impl ResponseBuilder { /// Create response builder pub fn new(status: StatusCode) -> Self { - let mut head: Message = Message::new(); + let mut head = BoxedResponseHead::new(); head.status = status; ResponseBuilder { @@ -701,13 +700,13 @@ impl ResponseBuilder { #[inline] fn parts<'a>( - parts: &'a mut Option>, + parts: &'a mut Option, err: &Option, -) -> Option<&'a mut Message> { +) -> Option<&'a mut ResponseHead> { if err.is_some() { return None; } - parts.as_mut() + parts.as_mut().map(|r| &mut **r) } /// Convert `Response` to a `ResponseBuilder`. Body get dropped. @@ -740,7 +739,7 @@ impl<'a> From<&'a ResponseHead> for ResponseBuilder { let mut jar: Option = None; let cookies = CookieIter { - iter: head.headers.get_all(header::SET_COOKIE).iter(), + iter: head.headers.get_all(header::SET_COOKIE), }; for c in cookies { if let Some(ref mut j) = jar { @@ -752,11 +751,11 @@ impl<'a> From<&'a ResponseHead> for ResponseBuilder { } } - let mut msg: Message = Message::new(); + let mut msg = BoxedResponseHead::new(); msg.version = head.version; msg.status = head.status; msg.reason = head.reason; - msg.headers = head.headers.clone(); + // msg.headers = head.headers.clone(); msg.no_chunking(!head.chunked()); ResponseBuilder { @@ -845,7 +844,7 @@ impl From for Response { mod tests { use super::*; use crate::body::Body; - use crate::http::header::{HeaderValue, CONTENT_TYPE, COOKIE}; + use crate::http::header::{HeaderValue, CONTENT_TYPE, COOKIE, SET_COOKIE}; #[test] fn test_debug() { @@ -876,13 +875,12 @@ mod tests { .max_age(time::Duration::days(1)) .finish(), ) - .del_cookie(&cookies[0]) + .del_cookie(&cookies[1]) .finish(); let mut val: Vec<_> = resp .headers() - .get_all("Set-Cookie") - .iter() + .get_all(SET_COOKIE) .map(|v| v.to_str().unwrap().to_owned()) .collect(); val.sort(); @@ -911,9 +909,9 @@ mod tests { let mut iter = r.cookies(); 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")); + let v = iter.next().unwrap(); + assert_eq!((v.name(), v.value()), ("original", "val100")); } #[test] @@ -1049,6 +1047,7 @@ mod tests { resp.headers().get(CONTENT_TYPE).unwrap(), HeaderValue::from_static("application/octet-stream") ); + assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.body().get_ref(), b"test"); } diff --git a/actix-http/src/test.rs b/actix-http/src/test.rs index 023161246..302d75d73 100644 --- a/actix-http/src/test.rs +++ b/actix-http/src/test.rs @@ -4,10 +4,11 @@ use std::str::FromStr; use bytes::Bytes; use http::header::{self, HeaderName, HeaderValue}; -use http::{HeaderMap, HttpTryFrom, Method, Uri, Version}; +use http::{HttpTryFrom, Method, Uri, Version}; use percent_encoding::{percent_encode, USERINFO_ENCODE_SET}; use crate::cookie::{Cookie, CookieJar}; +use crate::header::HeaderMap; use crate::header::{Header, IntoHeaderValue}; use crate::payload::Payload; use crate::Request; diff --git a/actix-multipart/src/server.rs b/actix-multipart/src/server.rs index c1536af60..39559b082 100644 --- a/actix-multipart/src/server.rs +++ b/actix-multipart/src/server.rs @@ -92,7 +92,7 @@ impl Multipart { /// Extract boundary info from headers. fn boundary(headers: &HeaderMap) -> Result { - if let Some(content_type) = headers.get(header::CONTENT_TYPE) { + if let Some(content_type) = headers.get(&header::CONTENT_TYPE) { if let Ok(content_type) = content_type.to_str() { if let Ok(ct) = content_type.parse::() { if let Some(boundary) = ct.get_param(mime::BOUNDARY) { @@ -334,7 +334,7 @@ impl InnerMultipart { // content type let mut mt = mime::APPLICATION_OCTET_STREAM; - if let Some(content_type) = headers.get(header::CONTENT_TYPE) { + if let Some(content_type) = headers.get(&header::CONTENT_TYPE) { if let Ok(content_type) = content_type.to_str() { if let Ok(ct) = content_type.parse::() { mt = ct; @@ -427,7 +427,7 @@ impl Field { pub fn content_disposition(&self) -> Option { // RFC 7578: 'Each part MUST contain a Content-Disposition header field // where the disposition type is "form-data".' - if let Some(content_disposition) = self.headers.get(header::CONTENT_DISPOSITION) + if let Some(content_disposition) = self.headers.get(&header::CONTENT_DISPOSITION) { ContentDisposition::from_raw(content_disposition).ok() } else { @@ -480,7 +480,7 @@ impl InnerField { boundary: String, headers: &HeaderMap, ) -> Result { - let len = if let Some(len) = headers.get(header::CONTENT_LENGTH) { + let len = if let Some(len) = headers.get(&header::CONTENT_LENGTH) { if let Ok(s) = len.to_str() { if let Ok(len) = s.parse::() { Some(len) diff --git a/actix-web-actors/src/ws.rs b/actix-web-actors/src/ws.rs index 642222560..0ef3c9169 100644 --- a/actix-web-actors/src/ws.rs +++ b/actix-web-actors/src/ws.rs @@ -50,7 +50,7 @@ pub fn handshake(req: &HttpRequest) -> Result Result Result, { let mut req = self.request(head.method.clone(), url); - for (key, value) in &head.headers { + for (key, value) in head.headers.iter() { req = req.set_header_if_none(key.clone(), value.clone()); } req @@ -187,7 +187,7 @@ impl Client { Uri: HttpTryFrom, { let mut req = ws::WebsocketsRequest::new(url, self.0.clone()); - for (key, value) in &self.0.headers { + for (key, value) in self.0.headers.iter() { req.head.headers.insert(key.clone(), value.clone()); } req diff --git a/awc/src/request.rs b/awc/src/request.rs index 32ab7d78f..09a7eecc4 100644 --- a/awc/src/request.rs +++ b/awc/src/request.rs @@ -396,7 +396,7 @@ impl ClientRequest { if self.default_headers { // set request host header if let Some(host) = self.head.uri.host() { - if !self.head.headers.contains_key(header::HOST) { + if !self.head.headers.contains_key(&header::HOST) { let mut wrt = BytesMut::with_capacity(host.len() + 5).writer(); let _ = match self.head.uri.port_u16() { @@ -414,7 +414,7 @@ impl ClientRequest { } // user agent - if !self.head.headers.contains_key(header::USER_AGENT) { + if !self.head.headers.contains_key(&header::USER_AGENT) { self.head.headers.insert( header::USER_AGENT, HeaderValue::from_static(concat!("awc/", env!("CARGO_PKG_VERSION"))), diff --git a/awc/src/response.rs b/awc/src/response.rs index b6d7bba65..b9b007b87 100644 --- a/awc/src/response.rs +++ b/awc/src/response.rs @@ -46,7 +46,7 @@ impl HttpMessage for ClientResponse { if self.extensions().get::().is_none() { let mut cookies = Vec::new(); - for hdr in self.headers().get_all(SET_COOKIE) { + for hdr in self.headers().get_all(&SET_COOKIE) { let s = std::str::from_utf8(hdr.as_bytes()) .map_err(CookieParseError::from)?; cookies.push(Cookie::parse_encoded(s)?.into_owned()); @@ -160,7 +160,7 @@ where /// Create `MessageBody` for request. pub fn new(res: &mut ClientResponse) -> MessageBody { let mut len = None; - if let Some(l) = res.headers().get(CONTENT_LENGTH) { + if let Some(l) = res.headers().get(&CONTENT_LENGTH) { if let Ok(s) = l.to_str() { if let Ok(l) = s.parse::() { len = Some(l) @@ -254,7 +254,7 @@ where } let mut len = None; - if let Some(l) = req.headers().get(CONTENT_LENGTH) { + if let Some(l) = req.headers().get(&CONTENT_LENGTH) { if let Ok(s) = l.to_str() { if let Ok(l) = s.parse::() { len = Some(l) diff --git a/awc/src/ws.rs b/awc/src/ws.rs index a28518983..967820f3f 100644 --- a/awc/src/ws.rs +++ b/awc/src/ws.rs @@ -236,7 +236,7 @@ impl WebsocketsRequest { let mut slf = if self.default_headers { // set request host header if let Some(host) = self.head.uri.host() { - if !self.head.headers.contains_key(header::HOST) { + if !self.head.headers.contains_key(&header::HOST) { let mut wrt = BytesMut::with_capacity(host.len() + 5).writer(); let _ = match self.head.uri.port_u16() { @@ -324,7 +324,7 @@ impl WebsocketsRequest { return Err(WsClientError::InvalidResponseStatus(head.status)); } // Check for "UPGRADE" to websocket header - let has_hdr = if let Some(hdr) = head.headers.get(header::UPGRADE) { + let has_hdr = if let Some(hdr) = head.headers.get(&header::UPGRADE) { if let Ok(s) = hdr.to_str() { s.to_ascii_lowercase().contains("websocket") } else { @@ -338,7 +338,7 @@ impl WebsocketsRequest { return Err(WsClientError::InvalidUpgradeHeader); } // Check for "CONNECTION" header - if let Some(conn) = head.headers.get(header::CONNECTION) { + if let Some(conn) = head.headers.get(&header::CONNECTION) { if let Ok(s) = conn.to_str() { if !s.to_ascii_lowercase().contains("upgrade") { log::trace!("Invalid connection header: {}", s); @@ -355,7 +355,7 @@ impl WebsocketsRequest { return Err(WsClientError::MissingConnectionHeader); } - if let Some(hdr_key) = head.headers.get(header::SEC_WEBSOCKET_ACCEPT) { + if let Some(hdr_key) = head.headers.get(&header::SEC_WEBSOCKET_ACCEPT) { let encoded = ws::hash_key(key.as_ref()); if hdr_key.as_bytes() != encoded.as_bytes() { log::trace!( diff --git a/src/info.rs b/src/info.rs index 9a97c3353..ece17bf04 100644 --- a/src/info.rs +++ b/src/info.rs @@ -33,7 +33,7 @@ impl ConnectionInfo { let peer = None; // load forwarded header - for hdr in req.headers.get_all(header::FORWARDED) { + for hdr in req.headers.get_all(&header::FORWARDED) { if let Ok(val) = hdr.to_str() { for pair in val.split(';') { for el in pair.split(',') { @@ -69,7 +69,7 @@ impl ConnectionInfo { if scheme.is_none() { if let Some(h) = req .headers - .get(HeaderName::from_lowercase(X_FORWARDED_PROTO).unwrap()) + .get(&HeaderName::from_lowercase(X_FORWARDED_PROTO).unwrap()) { if let Ok(h) = h.to_str() { scheme = h.split(',').next().map(|v| v.trim()); @@ -87,14 +87,14 @@ impl ConnectionInfo { if host.is_none() { if let Some(h) = req .headers - .get(HeaderName::from_lowercase(X_FORWARDED_HOST).unwrap()) + .get(&HeaderName::from_lowercase(X_FORWARDED_HOST).unwrap()) { if let Ok(h) = h.to_str() { host = h.split(',').next().map(|v| v.trim()); } } if host.is_none() { - if let Some(h) = req.headers.get(header::HOST) { + if let Some(h) = req.headers.get(&header::HOST) { host = h.to_str().ok(); } if host.is_none() { @@ -110,7 +110,7 @@ impl ConnectionInfo { if remote.is_none() { if let Some(h) = req .headers - .get(HeaderName::from_lowercase(X_FORWARDED_FOR).unwrap()) + .get(&HeaderName::from_lowercase(X_FORWARDED_FOR).unwrap()) { if let Ok(h) = h.to_str() { remote = h.split(',').next().map(|v| v.trim()); diff --git a/src/middleware/compress.rs b/src/middleware/compress.rs index ed3943711..a4b6a4608 100644 --- a/src/middleware/compress.rs +++ b/src/middleware/compress.rs @@ -109,7 +109,7 @@ where fn call(&mut self, req: ServiceRequest

) -> Self::Future { // negotiate content-encoding - let encoding = if let Some(val) = req.headers().get(ACCEPT_ENCODING) { + let encoding = if let Some(val) = req.headers().get(&ACCEPT_ENCODING) { if let Ok(enc) = val.to_str() { AcceptEncoding::parse(enc, self.encoding) } else { diff --git a/src/middleware/cors.rs b/src/middleware/cors.rs index f003ac95b..27d24b432 100644 --- a/src/middleware/cors.rs +++ b/src/middleware/cors.rs @@ -584,7 +584,7 @@ struct Inner { impl Inner { fn validate_origin(&self, req: &RequestHead) -> Result<(), CorsError> { - if let Some(hdr) = req.headers().get(header::ORIGIN) { + if let Some(hdr) = req.headers().get(&header::ORIGIN) { if let Ok(origin) = hdr.to_str() { return match self.origins { AllOrSome::All => Ok(()), @@ -608,7 +608,7 @@ impl Inner { AllOrSome::All => { if self.send_wildcard { Some(HeaderValue::from_static("*")) - } else if let Some(origin) = req.headers().get(header::ORIGIN) { + } else if let Some(origin) = req.headers().get(&header::ORIGIN) { Some(origin.clone()) } else { None @@ -617,7 +617,7 @@ impl Inner { AllOrSome::Some(ref origins) => { if let Some(origin) = req.headers() - .get(header::ORIGIN) + .get(&header::ORIGIN) .filter(|o| match o.to_str() { Ok(os) => origins.contains(os), _ => false, @@ -632,7 +632,7 @@ impl Inner { } fn validate_allowed_method(&self, req: &RequestHead) -> Result<(), CorsError> { - if let Some(hdr) = req.headers().get(header::ACCESS_CONTROL_REQUEST_METHOD) { + if let Some(hdr) = req.headers().get(&header::ACCESS_CONTROL_REQUEST_METHOD) { if let Ok(meth) = hdr.to_str() { if let Ok(method) = Method::try_from(meth) { return self @@ -653,7 +653,7 @@ impl Inner { AllOrSome::All => Ok(()), AllOrSome::Some(ref allowed_headers) => { if let Some(hdr) = - req.headers().get(header::ACCESS_CONTROL_REQUEST_HEADERS) + req.headers().get(&header::ACCESS_CONTROL_REQUEST_HEADERS) { if let Ok(headers) = hdr.to_str() { let mut hdrs = HashSet::new(); @@ -720,7 +720,7 @@ where .unwrap(), ) } else if let Some(hdr) = - req.headers().get(header::ACCESS_CONTROL_REQUEST_HEADERS) + req.headers().get(&header::ACCESS_CONTROL_REQUEST_HEADERS) { Some(hdr.clone()) } else { @@ -759,7 +759,7 @@ where .into_body(); Either::A(ok(req.into_response(res))) - } else if req.headers().contains_key(header::ORIGIN) { + } else if req.headers().contains_key(&header::ORIGIN) { // Only check requests with a origin header. if let Err(e) = self.inner.validate_origin(req.head()) { return Either::A(ok(req.error_response(e))); @@ -790,7 +790,7 @@ where } if inner.vary_header { let value = - if let Some(hdr) = res.headers_mut().get(header::VARY) { + if let Some(hdr) = res.headers_mut().get(&header::VARY) { let mut val: Vec = Vec::with_capacity(hdr.as_bytes().len() + 8); val.extend(hdr.as_bytes()); @@ -893,20 +893,20 @@ mod tests { assert_eq!( &b"*"[..], resp.headers() - .get(header::ACCESS_CONTROL_ALLOW_ORIGIN) + .get(&header::ACCESS_CONTROL_ALLOW_ORIGIN) .unwrap() .as_bytes() ); assert_eq!( &b"3600"[..], resp.headers() - .get(header::ACCESS_CONTROL_MAX_AGE) + .get(&header::ACCESS_CONTROL_MAX_AGE) .unwrap() .as_bytes() ); let hdr = resp .headers() - .get(header::ACCESS_CONTROL_ALLOW_HEADERS) + .get(&header::ACCESS_CONTROL_ALLOW_HEADERS) .unwrap() .to_str() .unwrap(); diff --git a/src/middleware/defaultheaders.rs b/src/middleware/defaultheaders.rs index 72e866dbd..a2bc6f27a 100644 --- a/src/middleware/defaultheaders.rs +++ b/src/middleware/defaultheaders.rs @@ -131,11 +131,11 @@ where // set response headers for (key, value) in inner.headers.iter() { if !res.headers().contains_key(key) { - res.headers_mut().insert(key, value.clone()); + res.headers_mut().insert(key.clone(), value.clone()); } } // default content-type - if inner.ct && !res.headers().contains_key(CONTENT_TYPE) { + if inner.ct && !res.headers().contains_key(&CONTENT_TYPE) { res.headers_mut().insert( CONTENT_TYPE, HeaderValue::from_static("application/octet-stream"), diff --git a/src/middleware/logger.rs b/src/middleware/logger.rs index f4b7517de..aaf381386 100644 --- a/src/middleware/logger.rs +++ b/src/middleware/logger.rs @@ -14,6 +14,7 @@ use time; use crate::dev::{BodySize, MessageBody, ResponseBody}; use crate::error::{Error, Result}; +use crate::http::{HeaderName, HttpTryFrom}; use crate::service::{ServiceRequest, ServiceResponse}; use crate::HttpResponse; @@ -288,8 +289,12 @@ impl Format { if let Some(key) = cap.get(2) { results.push(match cap.get(3).unwrap().as_str() { - "i" => FormatText::RequestHeader(key.as_str().to_owned()), - "o" => FormatText::ResponseHeader(key.as_str().to_owned()), + "i" => FormatText::RequestHeader( + HeaderName::try_from(key.as_str()).unwrap(), + ), + "o" => FormatText::ResponseHeader( + HeaderName::try_from(key.as_str()).unwrap(), + ), "e" => FormatText::EnvironHeader(key.as_str().to_owned()), _ => unreachable!(), }) @@ -332,8 +337,8 @@ pub enum FormatText { TimeMillis, RemoteAddr, UrlPath, - RequestHeader(String), - ResponseHeader(String), + RequestHeader(HeaderName), + ResponseHeader(HeaderName), EnvironHeader(String), } diff --git a/src/request.rs b/src/request.rs index b5ba74122..2eab1ee19 100644 --- a/src/request.rs +++ b/src/request.rs @@ -286,10 +286,10 @@ mod tests { { let cookies = req.cookies().unwrap(); assert_eq!(cookies.len(), 2); - assert_eq!(cookies[0].name(), "cookie1"); - assert_eq!(cookies[0].value(), "value1"); - assert_eq!(cookies[1].name(), "cookie2"); - assert_eq!(cookies[1].value(), "value2"); + assert_eq!(cookies[0].name(), "cookie2"); + assert_eq!(cookies[0].value(), "value2"); + assert_eq!(cookies[1].name(), "cookie1"); + assert_eq!(cookies[1].value(), "value1"); } let cookie = req.cookie("cookie1"); diff --git a/src/types/form.rs b/src/types/form.rs index 812a08e52..b2171e540 100644 --- a/src/types/form.rs +++ b/src/types/form.rs @@ -209,7 +209,7 @@ where }; let mut len = None; - if let Some(l) = req.headers().get(CONTENT_LENGTH) { + if let Some(l) = req.headers().get(&CONTENT_LENGTH) { if let Ok(s) = l.to_str() { if let Ok(l) = s.parse::() { len = Some(l) diff --git a/src/types/json.rs b/src/types/json.rs index c8ed5afd3..f7b94a9bd 100644 --- a/src/types/json.rs +++ b/src/types/json.rs @@ -297,7 +297,7 @@ where } let mut len = None; - if let Some(l) = req.headers().get(CONTENT_LENGTH) { + if let Some(l) = req.headers().get(&CONTENT_LENGTH) { if let Ok(s) = l.to_str() { if let Ok(l) = s.parse::() { len = Some(l) diff --git a/src/types/payload.rs b/src/types/payload.rs index 170b9c627..9cdbd0577 100644 --- a/src/types/payload.rs +++ b/src/types/payload.rs @@ -313,7 +313,7 @@ where /// Create `MessageBody` for request. pub fn new(req: &mut T) -> HttpMessageBody { let mut len = None; - if let Some(l) = req.headers().get(header::CONTENT_LENGTH) { + if let Some(l) = req.headers().get(&header::CONTENT_LENGTH) { if let Ok(s) = l.to_str() { if let Ok(l) = s.parse::() { len = Some(l)