From 0c601c7f1b41213528bd86b985b0a5742de24235 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Tue, 9 Feb 2021 06:07:37 +0000 Subject: [PATCH] change internal rep of Value --- actix-http/src/h1/decoder.rs | 4 +- actix-http/src/h1/encoder.rs | 128 ++++-------- actix-http/src/header/as_name.rs | 44 ++++ actix-http/src/header/map.rs | 344 +++++++++++++++++++------------ actix-http/src/header/mod.rs | 25 +-- actix-http/src/response.rs | 16 +- 6 files changed, 305 insertions(+), 256 deletions(-) create mode 100644 actix-http/src/header/as_name.rs diff --git a/actix-http/src/h1/decoder.rs b/actix-http/src/h1/decoder.rs index 9da958563..122a815d5 100644 --- a/actix-http/src/h1/decoder.rs +++ b/actix-http/src/h1/decoder.rs @@ -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] diff --git a/actix-http/src/h1/encoder.rs b/actix-http/src/h1/encoder.rs index bd8287b26..6052bbacc 100644 --- a/actix-http/src/h1/encoder.rs +++ b/actix-http/src/h1/encoder.rs @@ -144,104 +144,50 @@ 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(); + 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 = 0; + dst.reserve(len * 2); + remaining = dst.capacity() - dst.len(); - pos += len; - remaining -= 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 { + 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; } }); diff --git a/actix-http/src/header/as_name.rs b/actix-http/src/header/as_name.rs new file mode 100644 index 000000000..160505ae1 --- /dev/null +++ b/actix-http/src/header/as_name.rs @@ -0,0 +1,44 @@ +use std::{borrow::Cow, str::FromStr}; + +use http::header::{HeaderName, InvalidHeaderName}; + +pub trait AsHeaderName: Sealed {} + +pub trait Sealed { + fn try_as_name(&self) -> Result, InvalidHeaderName>; +} + +impl Sealed for HeaderName { + fn try_as_name(&self) -> Result, InvalidHeaderName> { + Ok(Cow::Borrowed(self)) + } +} +impl AsHeaderName for HeaderName {} + +impl Sealed for &HeaderName { + fn try_as_name(&self) -> Result, InvalidHeaderName> { + Ok(Cow::Borrowed(*self)) + } +} +impl AsHeaderName for &HeaderName {} + +impl Sealed for &str { + fn try_as_name(&self) -> Result, InvalidHeaderName> { + HeaderName::from_str(self).map(Cow::Owned) + } +} +impl AsHeaderName for &str {} + +impl Sealed for String { + fn try_as_name(&self) -> Result, InvalidHeaderName> { + HeaderName::from_str(self).map(Cow::Owned) + } +} +impl AsHeaderName for String {} + +impl Sealed for &String { + fn try_as_name(&self) -> Result, InvalidHeaderName> { + HeaderName::from_str(self).map(Cow::Owned) + } +} +impl AsHeaderName for &String {} diff --git a/actix-http/src/header/map.rs b/actix-http/src/header/map.rs index e68ef291a..c60ca8492 100644 --- a/actix-http/src/header/map.rs +++ b/actix-http/src/header/map.rs @@ -1,58 +1,71 @@ //! A multi-value [`HeaderMap`], its iterators, and a helper trait for types that can be effectively //! borrowed as, or converted to, a [HeaderValue]. -use std::{borrow::Cow, collections::hash_map, mem, str::FromStr}; +use std::{borrow::Cow, collections::hash_map, ops}; use ahash::AHashMap; -use http::header::{HeaderName, HeaderValue, InvalidHeaderName}; +use http::header::{HeaderName, HeaderValue}; use smallvec::{smallvec, SmallVec}; -pub use as_header_name::AsHeaderName; +use crate::header::AsHeaderName; /// A multi-map of HTTP headers. /// /// `HeaderMap` is a "multi-map" of [`HeaderName`] to one or more [`HeaderValue`]s. +/// +/// # Examples +/// ``` +/// use actix_http::http::{header, HeaderMap, HeaderValue}; +/// +/// let mut map = HeaderMap::new(); +/// +/// map.insert(header::CONTENT_TYPE, HeaderValue::from_static("text/plain")); +/// map.insert(header::ORIGIN, HeaderValue::from_static("example.com")); +/// +/// assert!(map.contains_key(header::CONTENT_TYPE)); +/// assert!(map.contains_key(header::ORIGIN)); +/// +/// let mut removed = map.remove(header::ORIGIN); +/// assert_eq!(removed.next().unwrap(), "example.com"); +/// +/// assert!(!map.contains_key(header::ORIGIN)); +/// ``` #[derive(Debug, Clone, Default)] pub struct HeaderMap { pub(crate) inner: AHashMap, } +/// A bespoke non-empty list for HeaderMap values. #[derive(Debug, Clone)] -pub(crate) enum Value { - One(HeaderValue), - Multi(SmallVec<[HeaderValue; 4]>), +pub(crate) struct Value { + inner: SmallVec<[HeaderValue; 4]>, } impl Value { - fn len(&self) -> usize { - match self { - Value::One(_) => 1, - Value::Multi(vals) => vals.len(), + fn one(val: HeaderValue) -> Self { + Self { + inner: smallvec![val], } } fn first(&self) -> &HeaderValue { - match self { - Value::One(ref val) => val, - Value::Multi(ref val) => &val[0], - } + &self.inner[0] } fn first_mut(&mut self) -> &mut HeaderValue { - match self { - Value::One(ref mut val) => val, - Value::Multi(ref mut val) => &mut val[0], - } + &mut self.inner[0] } - fn append(&mut self, val: HeaderValue) { - match self { - Value::One(_) => match mem::replace(self, Value::Multi(smallvec![val])) { - Value::One(val) => self.append(val), - Value::Multi(_) => unreachable!(), - }, - Value::Multi(ref mut vals) => vals.push(val), - } + fn append(&mut self, new_val: HeaderValue) { + self.inner.push(new_val) + } +} + +impl ops::Deref for Value { + type Target = SmallVec<[HeaderValue; 4]>; + + fn deref(&self) -> &Self::Target { + &self.inner } } @@ -60,6 +73,15 @@ impl HeaderMap { /// Create an empty `HeaderMap`. /// /// The map will be created without any capacity; this function will not allocate. + /// + /// # Examples + /// ``` + /// # use actix_http::http::HeaderMap; + /// let map = HeaderMap::new(); + /// + /// assert!(map.is_empty()); + /// assert_eq!(0, map.capacity()); + /// ``` pub fn new() -> Self { HeaderMap::default() } @@ -68,6 +90,15 @@ impl HeaderMap { /// /// The map will be able to hold at least `capacity` elements without needing to reallocate. /// If `capacity` is 0, the map will be created without allocating. + /// + /// # Examples + /// ``` + /// # use actix_http::http::HeaderMap; + /// let map = HeaderMap::with_capacity(16); + /// + /// assert!(map.is_empty()); + /// assert!(map.capacity() >= 16); + /// ``` pub fn with_capacity(capacity: usize) -> Self { HeaderMap { inner: AHashMap::with_capacity(capacity), @@ -105,7 +136,21 @@ impl HeaderMap { /// Returns the number of values stored in the map. /// - /// Also see [`len_keys`](Self::len_keys). + /// See also: [`len_keys`](Self::len_keys). + /// + /// # Examples + /// ``` + /// # use actix_http::http::{header, HeaderMap, HeaderValue}; + /// let mut map = HeaderMap::new(); + /// assert_eq!(map.len(), 0); + /// + /// map.insert(header::ACCEPT, HeaderValue::from_static("text/plain")); + /// map.insert(header::SET_COOKIE, HeaderValue::from_static("one=1")); + /// assert_eq!(map.len(), 2); + /// + /// map.append(header::SET_COOKIE, HeaderValue::from_static("two=2")); + /// assert_eq!(map.len(), 3); + /// ``` pub fn len(&self) -> usize { self.inner .iter() @@ -114,12 +159,36 @@ impl HeaderMap { /// Returns the number of _keys_ stored in the map. /// - /// The number of _values_ stored will be at least this number. Also see [`Self::len`]. + /// The number of values stored will be at least this number. See also: [`Self::len`]. + /// + /// # Examples + /// ``` + /// # use actix_http::http::{header, HeaderMap, HeaderValue}; + /// let mut map = HeaderMap::new(); + /// assert_eq!(map.len_keys(), 0); + /// + /// map.insert(header::ACCEPT, HeaderValue::from_static("text/plain")); + /// map.insert(header::SET_COOKIE, HeaderValue::from_static("one=1")); + /// assert_eq!(map.len_keys(), 2); + /// + /// map.append(header::SET_COOKIE, HeaderValue::from_static("two=2")); + /// assert_eq!(map.len_keys(), 2); + /// ``` pub fn len_keys(&self) -> usize { self.inner.len() } /// Returns true if the map contains no elements. + /// + /// # Examples + /// ``` + /// # use actix_http::http::{header, HeaderMap, HeaderValue}; + /// let mut map = HeaderMap::new(); + /// assert!(map.is_empty()); + /// + /// map.insert(header::ACCEPT, HeaderValue::from_static("text/plain")); + /// assert!(!map.is_empty()); + /// ``` pub fn is_empty(&self) -> bool { self.inner.len() == 0 } @@ -127,6 +196,19 @@ impl HeaderMap { /// Clears the map, removing all name-value pairs. /// /// Keeps the allocated memory for reuse. + /// + /// # Examples + /// ``` + /// # use actix_http::http::{header, HeaderMap, HeaderValue}; + /// let mut map = HeaderMap::new(); + /// + /// map.insert(header::ACCEPT, HeaderValue::from_static("text/plain")); + /// map.insert(header::SET_COOKIE, HeaderValue::from_static("one=1")); + /// assert_eq!(map.len(), 2); + /// + /// map.clear(); + /// assert!(map.is_empty()); + /// ``` pub fn clear(&mut self) { self.inner.clear(); } @@ -140,18 +222,63 @@ impl HeaderMap { /// Returns a reference to the _first_ value associated with a header name. /// - /// Even when multiple values associated with the key, the "first" one is returned but is not - /// guaranteed to be chosen in with particular order. Use `get_all` to get all values associated - /// with a given key. Returns `None` if there are no values associated with the key. + /// Returns `None` if there is no value associated with the key. + /// + /// Even when multiple values are associated with the key, the "first" one is returned but is + /// not guaranteed to be chosen with any particular order; though, the returned item will be + /// consistent for each call to `get` if the map has not changed. + /// + /// See also: [`get_all`](Self::get_all). + /// + /// # Examples + /// ``` + /// # use actix_http::http::{header, HeaderMap, HeaderValue}; + /// let mut map = HeaderMap::new(); + /// + /// map.insert(header::SET_COOKIE, HeaderValue::from_static("one=1")); + /// + /// let cookie = map.get(header::SET_COOKIE).unwrap(); + /// assert_eq!(cookie, "one=1"); + /// + /// map.append(header::SET_COOKIE, HeaderValue::from_static("two=2")); + /// assert_eq!(map.get(header::SET_COOKIE).unwrap(), "one=1"); + /// + /// assert_eq!(map.get(header::SET_COOKIE), map.get("set-cookie")); + /// assert_eq!(map.get(header::SET_COOKIE), map.get("Set-Cookie")); + /// + /// assert!(map.get(header::HOST).is_none()); + /// assert!(map.get("INVALID HEADER NAME").is_none()); + /// ``` pub fn get(&self, key: impl AsHeaderName) -> Option<&HeaderValue> { self.get_value(key).map(|val| val.first()) } /// Returns a mutable reference to the _first_ value associated a header name. /// - /// Even when multiple values associated with the key, the "first" one is returned but is not - /// guaranteed to be chosen in with particular order. Use `get_all` to get all values associated - /// with a given key. Returns `None` if there are no values associated with the key. + /// Returns `None` if there is no value associated with the key. + /// + /// Even when multiple values are associated with the key, the "first" one is returned but is + /// not guaranteed to be chosen with any particular order; though, the returned item will be + /// consistent for each call to `get_mut` if the map has not changed. + /// + /// See also: [`get_all`](Self::get_all). + /// + /// # Examples + /// ``` + /// # use actix_http::http::{header, HeaderMap, HeaderValue}; + /// let mut map = HeaderMap::new(); + /// + /// map.insert(header::SET_COOKIE, HeaderValue::from_static("one=1")); + /// + /// let mut cookie = map.get_mut(header::SET_COOKIE).unwrap(); + /// assert_eq!(cookie, "one=1"); + /// + /// *cookie = HeaderValue::from_static("three=3"); + /// assert_eq!(map.get(header::SET_COOKIE).unwrap(), "three=3"); + /// + /// assert!(map.get(header::HOST).is_none()); + /// assert!(map.get("INVALID HEADER NAME").is_none()); + /// ``` pub fn get_mut(&mut self, key: impl AsHeaderName) -> Option<&mut HeaderValue> { match key.try_as_name().ok()? { Cow::Borrowed(name) => self.inner.get_mut(name).map(|v| v.first_mut()), @@ -164,6 +291,23 @@ impl HeaderMap { /// The returned iterator does not incur any allocations and will yield no items if there are no /// values associated with the key. Iteration order is **not** guaranteed to be the same as /// insertion order. + /// + /// # Examples + /// ``` + /// # use actix_http::http::{header, HeaderMap, HeaderValue}; + /// let mut map = HeaderMap::new(); + /// + /// let mut none_iter = map.get_all(header::ORIGIN); + /// assert!(none_iter.next().is_none()); + /// + /// map.insert(header::SET_COOKIE, HeaderValue::from_static("one=1")); + /// map.append(header::SET_COOKIE, HeaderValue::from_static("two=2")); + /// + /// let mut set_cookies_iter = map.get_all(header::SET_COOKIE); + /// assert_eq!(set_cookies_iter.next().unwrap(), "one=1"); + /// assert_eq!(set_cookies_iter.next().unwrap(), "two=2"); + /// assert!(set_cookies_iter.next().is_none()); + /// ``` pub fn get_all(&self, key: impl AsHeaderName) -> GetAll<'_> { GetAll::new(self.get_value(key)) } @@ -185,7 +329,8 @@ impl HeaderMap { /// previous values are removed and returned as a `Removed` iterator. The key is not updated; /// this matters for types that can be `==` without being identical. pub fn insert(&mut self, key: HeaderName, val: HeaderValue) -> Removed { - Removed::new(self.inner.insert(key, Value::One(val))) + let value = self.inner.insert(key, Value::one(val)); + Removed::new(value) } /// Inserts a name-value pair into the map. @@ -199,7 +344,7 @@ impl HeaderMap { entry.get_mut().append(value); } hash_map::Entry::Vacant(entry) => { - entry.insert(Value::One(value)); + entry.insert(Value::one(value)); } }; } @@ -282,51 +427,6 @@ impl<'a> IntoIterator for &'a HeaderMap { } } -mod as_header_name { - use super::*; - - pub trait AsHeaderName: Sealed {} - - pub trait Sealed { - fn try_as_name(&self) -> Result, InvalidHeaderName>; - } - - impl Sealed for HeaderName { - fn try_as_name(&self) -> Result, InvalidHeaderName> { - Ok(Cow::Borrowed(self)) - } - } - impl AsHeaderName for HeaderName {} - - impl Sealed for &HeaderName { - fn try_as_name(&self) -> Result, InvalidHeaderName> { - Ok(Cow::Borrowed(*self)) - } - } - impl AsHeaderName for &HeaderName {} - - impl Sealed for &str { - fn try_as_name(&self) -> Result, InvalidHeaderName> { - HeaderName::from_str(self).map(Cow::Owned) - } - } - impl AsHeaderName for &str {} - - impl Sealed for String { - fn try_as_name(&self) -> Result, InvalidHeaderName> { - HeaderName::from_str(self).map(Cow::Owned) - } - } - impl AsHeaderName for String {} - - impl Sealed for &String { - fn try_as_name(&self) -> Result, InvalidHeaderName> { - HeaderName::from_str(self).map(Cow::Owned) - } - } - impl AsHeaderName for &String {} -} - /// Iterator for all values with the same header name. #[derive(Debug)] pub struct GetAll<'a> { @@ -346,23 +446,16 @@ impl<'a> Iterator for GetAll<'a> { fn next(&mut self) -> Option { let val = self.value?; - match val { - Value::One(ref val) => { - // remove value to fast-path future next calls - self.value = None; + match val.get(self.idx) { + Some(val) => { + self.idx += 1; Some(val) } - Value::Multi(ref vals) => match vals.get(self.idx) { - Some(val) => { - self.idx += 1; - Some(val) - } - None => { - // current index is none; remove value to fast-path future next calls - self.value = None; - None - } - }, + None => { + // current index is none; remove value to fast-path future next calls + self.value = None; + None + } } } @@ -378,17 +471,12 @@ impl<'a> Iterator for GetAll<'a> { /// on [`HeaderMap`] that remove or replace items. #[derive(Debug)] pub struct Removed { - inner: smallvec::IntoIter<[HeaderValue; 4]>, + inner: Option>, } impl<'a> Removed { fn new(value: Option) -> Self { - let inner = match value { - Some(Value::One(val)) => smallvec![val].into_iter(), - Some(Value::Multi(vals)) => vals.into_iter(), - None => smallvec![].into_iter(), - }; - + let inner = value.map(|value| value.inner.into_iter()); Self { inner } } } @@ -398,12 +486,15 @@ impl Iterator for Removed { #[inline] fn next(&mut self) -> Option { - self.inner.next() + self.inner.as_mut()?.next() } #[inline] fn size_hint(&self) -> (usize, Option) { - self.inner.size_hint() + match self.inner { + Some(ref iter) => iter.size_hint(), + None => (0, None), + } } } @@ -463,14 +554,9 @@ impl<'a> Iterator for Iter<'a> { let (name, value) = self.inner.next()?; - match value { - Value::One(ref value) => Some((name, value)), - Value::Multi(ref vals) => { - // set up new multi value inner iter and recurse into it - self.multi_inner = Some((name, vals)); - self.next() - } - } + // set up new inner iter and recurse into it + self.multi_inner = Some((name, &value.inner)); + self.next() } #[inline] @@ -488,7 +574,7 @@ impl<'a> Iterator for Iter<'a> { pub struct Drain<'a> { inner: hash_map::Drain<'a, HeaderName, Value>, multi_inner: Option<(Option, SmallVec<[HeaderValue; 4]>)>, - multi_inner_idx: usize, + multi_idx: usize, } impl<'a> Drain<'a> { @@ -496,7 +582,7 @@ impl<'a> Drain<'a> { Self { inner: iter, multi_inner: None, - multi_inner_idx: 0, + multi_idx: 0, } } } @@ -508,25 +594,20 @@ impl<'a> Iterator for Drain<'a> { // handle in-progress multi value iterators first if let Some((ref mut name, ref mut vals)) = self.multi_inner { if vals.len() > 0 { - // OPTIMISE: array removals + // OPTIMIZE: array removals return Some((name.take(), vals.remove(0))); } else { // no more items in value iterator; reset state self.multi_inner = None; - self.multi_inner_idx = 0; + self.multi_idx = 0; } } - let (name, mut value) = self.inner.next()?; + let (name, value) = self.inner.next()?; - match value { - Value::One(value) => Some((Some(name), value)), - Value::Multi(ref mut vals) => { - // set up new multi value inner iter and recurse into it - self.multi_inner = Some((Some(name), mem::take(vals))); - self.next() - } - } + // set up new inner iter and recurse into it + self.multi_inner = Some((Some(name), value.inner)); + self.next() } #[inline] @@ -574,14 +655,9 @@ impl Iterator for IntoIter { let (name, value) = self.inner.next()?; - match value { - Value::One(value) => Some((name, value)), - Value::Multi(vals) => { - // set up new multi value inner iter and recurse into it - self.multi_inner = Some((name, vals.into_iter())); - self.next() - } - } + // set up new inner iter and recurse into it + self.multi_inner = Some((name, value.inner.into_iter())); + self.next() } #[inline] diff --git a/actix-http/src/header/mod.rs b/actix-http/src/header/mod.rs index 2a3996394..9543d43b6 100644 --- a/actix-http/src/header/mod.rs +++ b/actix-http/src/header/mod.rs @@ -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)] @@ -97,26 +99,3 @@ pub(crate) const HTTP_VALUE: &AsciiSet = &CONTROLS .add(b']') .add(b'{') .add(b'}'); - -#[cfg(test)] -mod tests { - use super::*; - use crate::header; - - #[test] - fn test_http_header_map_to_ours() { - let mut http_map = http::HeaderMap::new(); - let map = HeaderMap::from_drain(http_map.drain()); - assert!(map.is_empty()); - - let mut http_map = http::HeaderMap::new(); - http_map.append(header::HOST, HeaderValue::from_static("duck.com")); - http_map.append(header::COOKIE, HeaderValue::from_static("one=1")); - http_map.append(header::COOKIE, HeaderValue::from_static("two=2")); - - let map = HeaderMap::from_drain(http_map.drain()); - assert_eq!(map.len(), 3); - assert!(map.contains_key(header::HOST)); - assert!(map.contains_key(header::COOKIE)); - } -} diff --git a/actix-http/src/response.rs b/actix-http/src/response.rs index 56a5471fd..7dc37b093 100644 --- a/actix-http/src/response.rs +++ b/actix-http/src/response.rs @@ -897,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(); + .map(|v| v.to_str().unwrap()) + .collect::>(); 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" @@ -931,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]