diff --git a/Cargo.toml b/Cargo.toml index a9423642..c5905eb0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,9 +10,9 @@ documentation = "https://docs.rs/http" repository = "https://github.com/hyperium/http" license = "MIT OR Apache-2.0" authors = [ - "Alex Crichton ", - "Carl Lerche ", - "Sean McArthur ", + "Alex Crichton ", + "Carl Lerche ", + "Sean McArthur ", ] description = """ A set of types for representing HTTP requests and responses. @@ -25,22 +25,26 @@ rust-version = "1.49.0" [workspace] members = [ - ".", + ".", ] exclude = [ - "fuzz", - "benches" + "fuzz", + "benches" ] [features] default = ["std"] std = [] double-write = ["default"] +fasthttp = ["double-write"] # compatible with fasthttp go version [dependencies] bytes = "1" fnv = "1.0.5" itoa = "1" +trie-rs = "0.4.2" +lazy_static = "1.5" +smallvec = { version = "1.15.0", features = ["specialization"]} [dev-dependencies] quickcheck = "1" diff --git a/src/ext/fasthttp/consts.rs b/src/ext/fasthttp/consts.rs new file mode 100644 index 00000000..a6c2887e --- /dev/null +++ b/src/ext/fasthttp/consts.rs @@ -0,0 +1,105 @@ +//! consts for fasthttp +use crate::header::{ + ACCEPT_ENCODING, ACCEPT_LANGUAGE, ACCEPT_RANGES, AUTHORIZATION, CONNECTION, CONTENT_ENCODING, + CONTENT_LENGTH, CONTENT_RANGE, CONTENT_TYPE, COOKIE, DATE, HOST, IF_MODIFIED_SINCE, + LAST_MODIFIED, LOCATION, ORIGIN, RANGE, REFERER, SERVER, SET_COOKIE, TRANSFER_ENCODING, + USER_AGENT, +}; +use crate::HeaderName; +use lazy_static::lazy_static; +use trie_rs::{Trie, TrieBuilder}; + +pub(crate) struct StandardHeaders(Trie); + +impl StandardHeaders { + fn new() -> StandardHeaders { + let mut builder = TrieBuilder::new(); + + // RFC标准header + builder.push(ACCEPT_RANGES); + builder.push(ACCEPT_LANGUAGE); + builder.push(ACCEPT_ENCODING); + builder.push(AUTHORIZATION); + builder.push("expect"); + builder.push(CONTENT_TYPE); + builder.push(CONTENT_ENCODING); + builder.push(CONTENT_RANGE); + builder.push(CONTENT_LENGTH); + builder.push(COOKIE); + builder.push(CONNECTION); + builder.push(DATE); + builder.push(HOST); + builder.push(IF_MODIFIED_SINCE); + builder.push(LOCATION); + builder.push(LAST_MODIFIED); + builder.push(REFERER); + builder.push(RANGE); + builder.push(SERVER); + builder.push(SET_COOKIE); + builder.push(TRANSFER_ENCODING); + builder.push(USER_AGENT); + builder.push(ORIGIN); + + // 内部标准header + builder.push("x-common-params"); + builder.push("use-ppe"); + builder.push("tt-logid"); + builder.push("tt-env"); + + let trie = builder.build(); + + StandardHeaders(trie) + } + + pub fn is_match(&self, header_name: &HeaderName) -> bool { + let lowercase_header = header_name.as_str().to_ascii_lowercase(); + self.0.exact_match(lowercase_header) + } +} + +pub(crate) struct NonAppendHeaders(Trie); + +impl NonAppendHeaders { + fn new() -> NonAppendHeaders { + let mut builder = TrieBuilder::new(); + + // 不可有多个值的特殊 std header + // 调用 append 等同于 insert + builder.push(CONTENT_LENGTH); + builder.push(CONNECTION); + builder.push(TRANSFER_ENCODING); + builder.push(DATE); + + let trie = builder.build(); + + NonAppendHeaders(trie) + } + + pub fn is_match(&self, header_name: &HeaderName) -> bool { + let lowercase_header = header_name.as_str().to_ascii_lowercase(); + self.0.exact_match(lowercase_header) + } +} + +lazy_static! { + pub(super) static ref STANDARD_HEADERS: StandardHeaders = StandardHeaders::new(); + pub(super) static ref NON_APPEND_HEADERS: NonAppendHeaders = NonAppendHeaders::new(); +} + +/// check if is standard header +pub fn is_standard_header(header_name: &HeaderName) -> bool { + STANDARD_HEADERS.is_match(header_name) +} + +/// check if is non-append standard header +pub fn is_non_append_header(header_name: &HeaderName) -> bool { + NON_APPEND_HEADERS.is_match(header_name) +} + +#[test] +fn test_is_std_header() { + assert!(is_standard_header(&"Content-Type".parse().unwrap())); + assert!(is_standard_header(&"content-type".parse().unwrap())); + assert!(is_standard_header(&"CONTENT-TYPE".parse().unwrap())); + assert_eq!(is_standard_header(&"content_type".parse().unwrap()), false); +} diff --git a/src/ext/fasthttp/header_name.rs b/src/ext/fasthttp/header_name.rs new file mode 100644 index 00000000..24157e75 --- /dev/null +++ b/src/ext/fasthttp/header_name.rs @@ -0,0 +1,131 @@ +//! normalization for fasthttp +use crate::ext::fasthttp::consts::is_standard_header; +use crate::HeaderName; + +const TO_LOWER: u8 = b'a' - b'A'; + +lazy_static::lazy_static! { + static ref TO_LOWER_TABLE: [u8; 256] = { + let mut a = [0u8; 256]; + #[allow(warnings)] + for i in 0..256 { + let mut c = i as u8; + if c.is_ascii_uppercase(){ + c += TO_LOWER; + } + a[i] = c; + } + a + }; + + static ref TO_UPPER_TABLE: [u8; 256] = { + let mut a = [0u8; 256]; + #[allow(warnings)] + for i in 0..256 { + let mut c = i as u8; + if c.is_ascii_lowercase(){ + c -= TO_LOWER; + } + a[i] = c; + } + a + }; +} + +/// 只normalize标准header,其他自定义header保持原样透传 +#[allow(warnings)] +pub(crate) fn normalize_header_key_for_std_header(header_name: HeaderName) -> HeaderName { + if !is_standard_header(&header_name) { + return header_name; + } + + normalize_header_key(header_name).into() +} + +pub(crate) fn normalize_header_key(header_name: K) -> impl Into +where + K: Into, +{ + let header_name: HeaderName = header_name.into(); + let mut header_name_str = header_name.as_raw_str().to_string(); + // let normalized_header_name = header_name.as_str(); + let n = header_name_str.len(); + if n == 0 { + return header_name; + } + + unsafe { + let header_name_bytes = header_name_str.as_bytes_mut(); + header_name_bytes[0] = TO_UPPER_TABLE[header_name_bytes[0] as usize]; + let mut i = 1; + while i < n { + let p = &mut header_name_bytes[i]; + if *p == b'-' { + i += 1; + if i < n { + header_name_bytes[i] = TO_UPPER_TABLE[header_name_bytes[i] as usize]; + i += 1; + } + continue; + } + *p = TO_LOWER_TABLE[*p as usize]; + i += 1; + } + + HeaderName::from_bytes(header_name_bytes).unwrap() + } +} + +#[allow(dead_code)] +#[cfg(test)] +mod tests { + use crate::ext::fasthttp::header_name::{ + normalize_header_key, normalize_header_key_for_std_header, + }; + use crate::HeaderName; + use std::str::FromStr; + + #[test] + fn test_normalize_header_key_for_std_header() { + let header_name = HeaderName::from_str("content-type").unwrap(); + assert_eq!( + normalize_header_key_for_std_header(header_name).as_raw_str(), + "Content-Type" + ); + + let header_name = HeaderName::from_str("Content-Type").unwrap(); + assert_eq!( + normalize_header_key_for_std_header(header_name).as_raw_str(), + "Content-Type" + ); + + // 非标准header,不做处理 + let header_name = HeaderName::from_str("x-tt-agw").unwrap(); + assert_eq!( + normalize_header_key_for_std_header(header_name).as_raw_str(), + "x-tt-agw" + ); + } + + #[test] + fn test_normalize_header_key() { + let header_name = HeaderName::from_str("content-type").unwrap(); + assert_eq!( + normalize_header_key(header_name).into().as_raw_str(), + "Content-Type" + ); + + let header_name = HeaderName::from_str("Content-Type").unwrap(); + assert_eq!( + normalize_header_key(header_name).into().as_raw_str(), + "Content-Type" + ); + + // 非标准header,不做处理 + let header_name = HeaderName::from_str("x-tt-agw").unwrap(); + assert_eq!( + normalize_header_key(header_name).into().as_raw_str(), + "X-Tt-Agw" + ); + } +} diff --git a/src/ext/fasthttp/mod.rs b/src/ext/fasthttp/mod.rs new file mode 100644 index 00000000..7d547753 --- /dev/null +++ b/src/ext/fasthttp/mod.rs @@ -0,0 +1,3 @@ +//! feature that compatible with fasthttp +pub mod consts; +pub mod header_name; diff --git a/src/ext/mod.rs b/src/ext/mod.rs new file mode 100644 index 00000000..64828a25 --- /dev/null +++ b/src/ext/mod.rs @@ -0,0 +1,3 @@ +//! ext module +#[cfg(feature = "fasthttp")] +pub mod fasthttp; diff --git a/src/header/map.rs b/src/header/map.rs index ebbc5937..cf4c22e8 100644 --- a/src/header/map.rs +++ b/src/header/map.rs @@ -1,19 +1,28 @@ +pub use self::as_header_name::AsHeaderName; +pub use self::into_header_name::IntoHeaderName; +use super::name::{HdrName, HeaderName, InvalidHeaderName}; +use super::HeaderValue; +#[cfg(feature = "fasthttp")] +use crate::ext::fasthttp::consts::is_non_append_header; +#[cfg(feature = "fasthttp")] +use crate::ext::fasthttp::header_name::normalize_header_key; +#[cfg(feature = "fasthttp")] +use crate::ext::fasthttp::header_name::normalize_header_key_for_std_header; +#[cfg(feature = "fasthttp")] +use crate::header::map::as_header_name::Sealed; +use crate::Error; +#[cfg(feature = "fasthttp")] +use smallvec::{smallvec, SmallVec}; use std::collections::hash_map::RandomState; use std::collections::HashMap; use std::convert::TryFrom; use std::hash::{BuildHasher, Hash, Hasher}; use std::iter::{FromIterator, FusedIterator}; use std::marker::PhantomData; +#[cfg(feature = "fasthttp")] +use std::str::FromStr; use std::{fmt, mem, ops, ptr, vec}; -use crate::Error; - -use super::name::{HdrName, HeaderName, InvalidHeaderName}; -use super::HeaderValue; - -pub use self::as_header_name::AsHeaderName; -pub use self::into_header_name::IntoHeaderName; - /// A set of HTTP headers /// /// `HeaderMap` is a multimap of [`HeaderName`] to values. @@ -49,6 +58,9 @@ pub struct HeaderMap { entries: Vec>, extra_values: Vec>, danger: Danger, + + #[cfg(feature = "fasthttp")] + mapped_keys: HashMap>, // normalized_key -> original_keys } // # Implementation notes @@ -451,6 +463,42 @@ impl HeaderMap { } impl HeaderMap { + #[cfg(feature = "fasthttp")] + fn append_to_mapped_keys(&mut self, original_key: HeaderName) { + let normalized_key = normalize_header_key(original_key.clone()).into(); + match self.mapped_keys.get_mut(&normalized_key) { + Some(original_keys) => original_keys.push(original_key), + None => { + let original_keys = smallvec![original_key.clone()]; + self.mapped_keys.insert(normalized_key, original_keys); + } + } + } + + #[cfg(feature = "fasthttp")] + fn remove_all_keys_insensitively(&mut self, original_key: &HeaderName) { + let normalized_key = normalize_header_key(original_key.clone()).into(); + if let Some(original_keys) = self.mapped_keys.remove(&normalized_key) { + for key in original_keys { + self.remove(key); + } + } + } + + #[cfg(feature = "fasthttp")] + fn remove_other_keys_insensitively(&mut self, original_key: &HeaderName) { + let normalized_key = normalize_header_key(original_key.clone()).into(); + if let Some(original_keys) = self.mapped_keys.remove(&normalized_key) { + for key in original_keys { + if key != original_key { + self.remove(key); + } + } + let keys = smallvec![original_key.clone()]; + self.mapped_keys.insert(normalized_key, keys); + } + } + /// Create an empty `HeaderMap` with the specified capacity. /// /// The returned map will allocate internal storage in order to hold about @@ -507,6 +555,8 @@ impl HeaderMap { entries: Vec::new(), extra_values: Vec::new(), danger: Danger::Green, + #[cfg(feature = "fasthttp")] + mapped_keys: HashMap::new(), }) } else { let raw_cap = match to_raw_capacity(capacity).checked_next_power_of_two() { @@ -524,6 +574,8 @@ impl HeaderMap { entries: Vec::with_capacity(usable_capacity(raw_cap)), extra_values: Vec::new(), danger: Danger::Green, + #[cfg(feature = "fasthttp")] + mapped_keys: HashMap::new(), }) } } @@ -621,6 +673,8 @@ impl HeaderMap { self.entries.clear(); self.extra_values.clear(); self.danger = Danger::Green; + #[cfg(feature = "fasthttp")] + self.mapped_keys.clear(); for e in self.indices.iter_mut() { *e = Pos::none(); @@ -1160,14 +1214,49 @@ impl HeaderMap { fn try_entry2(&mut self, key: K) -> Result, MaxSizeReached> where K: Hash + Into, - HeaderName: PartialEq, { // Ensure that there is space in the map self.try_reserve_one()?; + let header_name = key.into(); + + #[cfg(feature = "fasthttp")] + { + let normalized_header_key = normalize_header_key_for_std_header(header_name); + + Ok(insert_phase_one!( + self, + normalized_header_key, + probe, + pos, + hash, + danger, + Entry::Vacant(VacantEntry { + map: self, + hash, + key: normalized_header_key, + probe, + danger, + }), + Entry::Occupied(OccupiedEntry { + map: self, + index: pos, + probe, + }), + Entry::Vacant(VacantEntry { + map: self, + hash, + key: normalized_header_key, + probe, + danger, + }) + )) + } + + #[cfg(not(feature = "fasthttp"))] Ok(insert_phase_one!( self, - key, + header_name, probe, pos, hash, @@ -1175,7 +1264,7 @@ impl HeaderMap { Entry::Vacant(VacantEntry { map: self, hash, - key: key.into(), + key: header_name, probe, danger, }), @@ -1187,7 +1276,7 @@ impl HeaderMap { Entry::Vacant(VacantEntry { map: self, hash, - key: key.into(), + key: header_name, probe, danger, }) @@ -1274,13 +1363,43 @@ impl HeaderMap { fn try_insert2(&mut self, key: K, value: T) -> Result, MaxSizeReached> where K: Hash + Into, - HeaderName: PartialEq, { self.try_reserve_one()?; + let header_name = key.into(); + + #[cfg(feature = "fasthttp")] + { + let normalized_header_name = normalize_header_key_for_std_header(header_name); + Ok(insert_phase_one!( + self, + normalized_header_name, + probe, + pos, + hash, + danger, + // Vacant + { + let _ = danger; // Make lint happy + self.try_insert_entry(hash, normalized_header_name, value)?; + let index = self.entries.len() - 1; + self.indices[probe] = Pos::new(index, hash); + None + }, + // Occupied + Some(self.insert_occupied2(normalized_header_name, pos, value)), + // Robinhood + { + self.try_insert_phase_two(normalized_header_name, value, hash, probe, danger)?; + None + } + )) + } + + #[cfg(not(feature = "fasthttp"))] Ok(insert_phase_one!( self, - key, + header_name, probe, pos, hash, @@ -1289,7 +1408,38 @@ impl HeaderMap { { let _ = danger; // Make lint happy let index = self.entries.len(); - self.try_insert_entry(hash, key.into(), value)?; + self.try_insert_entry(hash, header_name, value)?; + self.indices[probe] = Pos::new(index, hash); + None + }, + // Occupied + Some(self.insert_occupied(pos, value)), + // Robinhood + { + self.try_insert_phase_two(header_name, value, hash, probe, danger)?; + None + } + )) + } + + #[cfg(feature = "fasthttp")] + fn try_insert2_do(&mut self, key: HeaderName, value: T) -> Result, MaxSizeReached> { + self.try_reserve_one()?; + + let normalized_header_name = normalize_header_key_for_std_header(key); + + Ok(insert_phase_one!( + self, + normalized_header_name, + probe, + pos, + hash, + danger, + // Vacant + { + let _ = danger; // Make lint happy + self.try_insert_entry(hash, normalized_header_name, value)?; + let index = self.entries.len() - 1; self.indices[probe] = Pos::new(index, hash); None }, @@ -1297,7 +1447,7 @@ impl HeaderMap { Some(self.insert_occupied(pos, value)), // Robinhood { - self.try_insert_phase_two(key.into(), value, hash, probe, danger)?; + self.try_insert_phase_two(normalized_header_name, value, hash, probe, danger)?; None } )) @@ -1314,6 +1464,19 @@ impl HeaderMap { mem::replace(&mut entry.value, value) } + #[cfg(feature = "fasthttp")] + #[inline] + fn insert_occupied2(&mut self, key: HeaderName, index: usize, value: T) -> T { + if let Some(links) = self.entries[index].links { + self.remove_all_extra_values(links.next); + } + + self.remove_other_keys_insensitively(&key); + + let entry = &mut self.entries[index]; + mem::replace(&mut entry.value, value) + } + fn insert_occupied_mult(&mut self, index: usize, value: T) -> ValueDrain<'_, T> { let old; let links; @@ -1417,13 +1580,62 @@ impl HeaderMap { fn try_append2(&mut self, key: K, value: T) -> Result where K: Hash + Into, - HeaderName: PartialEq, { self.try_reserve_one()?; + let header_name = key.into(); + + #[cfg(feature = "fasthttp")] + { + if is_non_append_header(&header_name) { + match self.try_insert2_do(header_name, value) { + Ok(None) => Ok(false), + Ok(Some(_)) => Ok(false), + Err(e) => Err(e), + } + } else { + let normalized_header_name = normalize_header_key_for_std_header(header_name); + + Ok(insert_phase_one!( + self, + normalized_header_name, + probe, + pos, + hash, + danger, + // Vacant + { + let _ = danger; + let index = self.entries.len(); + self.try_insert_entry_for_append(hash, normalized_header_name, value)?; + self.indices[probe] = Pos::new(index, hash); + false + }, + // Occupied + { + append_value(pos, &mut self.entries[pos], &mut self.extra_values, value); + true + }, + // Robinhood + { + self.try_insert_phase_two_for_append( + normalized_header_name, + value, + hash, + probe, + danger, + )?; + + false + } + )) + } + } + + #[cfg(not(feature = "fasthttp"))] Ok(insert_phase_one!( self, - key, + header_name, probe, pos, hash, @@ -1432,7 +1644,7 @@ impl HeaderMap { { let _ = danger; let index = self.entries.len(); - self.try_insert_entry(hash, key.into(), value)?; + self.try_insert_entry(hash, header_name, value)?; self.indices[probe] = Pos::new(index, hash); false }, @@ -1443,7 +1655,7 @@ impl HeaderMap { }, // Robinhood { - self.try_insert_phase_two(key.into(), value, hash, probe, danger)?; + self.try_insert_phase_two(header_name, value, hash, probe, danger)?; false } @@ -1453,13 +1665,45 @@ impl HeaderMap { #[inline] fn find(&self, key: &K) -> Option<(usize, usize)> where - K: Hash + Into + ?Sized, + K: Hash + Into + Clone, HeaderName: PartialEq, { if self.entries.is_empty() { return None; } + #[cfg(feature = "fasthttp")] + { + let normalized_header_key = normalize_header_key_for_std_header(key.clone().into()); + match self.find2::(&normalized_header_key) { + x @ Some(_) => x, + None => match self + .mapped_keys + .get(&normalize_header_key(normalized_header_key.clone()).into()) + { + Some(original_keys) => { + for original_key in original_keys { + if original_key != key { + return self.find2::(original_key); + } + } + None + } + None => None, + }, + } + } + + #[cfg(not(feature = "fasthttp"))] + self.find2(key) + } + + #[inline] + fn find2(&self, key: &K) -> Option<(usize, usize)> + where + K: Hash + Into, + HeaderName: PartialEq, + { let hash = hash_elem_using(&self.danger, key); let mask = self.mask; let mut probe = desired_pos(mask, hash); @@ -1491,9 +1735,31 @@ impl HeaderMap { probe: usize, danger: bool, ) -> Result { - // Push the value and get the index - let index = self.entries.len(); self.try_insert_entry(hash, key, value)?; + let index = self.entries.len() - 1; + + let num_displaced = do_insert_phase_two(&mut self.indices, probe, Pos::new(index, hash)); + + if danger || num_displaced >= DISPLACEMENT_THRESHOLD { + // Increase danger level + self.danger.set_yellow(); + } + + Ok(index) + } + + #[inline] + #[cfg(feature = "fasthttp")] + fn try_insert_phase_two_for_append( + &mut self, + key: HeaderName, + value: T, + hash: HashValue, + probe: usize, + danger: bool, + ) -> Result { + let index = self.entries.len(); + self.try_insert_entry_for_append(hash, key.clone(), value)?; let num_displaced = do_insert_phase_two(&mut self.indices, probe, Pos::new(index, hash)); @@ -1529,6 +1795,35 @@ impl HeaderMap { where K: AsHeaderName, { + #[cfg(feature = "fasthttp")] + { + let header_name = match HeaderName::from_str(key.as_str()) { + Ok(header_name) => header_name, + Err(_) => return None, + }; + + let normalized_header_name = normalize_header_key_for_std_header(header_name); + + let normalized_key = normalize_header_key(&normalized_header_name).into(); + + match normalized_header_name.find(self) { + Some((probe, idx)) => { + if let Some(links) = self.entries[idx].links { + self.remove_all_extra_values(links.next); + } + + let entry = self.remove_found(probe, idx); + + if let Some(original_keys) = self.mapped_keys.get_mut(&normalized_key) { + original_keys.retain(|x| x != &normalized_header_name); + } + Some(entry.value) + } + None => None, + } + } + + #[cfg(not(feature = "fasthttp"))] match key.find(self) { Some((probe, idx)) => { if let Some(links) = self.entries[idx].links { @@ -1543,6 +1838,43 @@ impl HeaderMap { } } + /// Remove all keys that case-insensitively equal + #[cfg(feature = "fasthttp")] + pub fn remove_all(&mut self, key: K) -> Option> + where + K: AsHeaderName, + { + let header_name = HeaderName::from_str(key.as_str()).ok(); + + let normalized_key = match header_name { + Some(header_name) => normalize_header_key(&header_name).into(), + None => { + return None; + } + }; + + let mut return_headers = vec![]; + + let original_keys = match self.mapped_keys.remove(&normalized_key) { + Some(original_keys) => original_keys.clone(), + None => { + return None; + } + }; + + for original_key in original_keys { + if let Some((probe, idx)) = self.find(&original_key) { + if let Some(links) = self.entries[idx].links { + self.remove_all_extra_values(links.next); + } + let entry = self.remove_found(probe, idx); + return_headers.push(entry.value); + } + } + + Some(return_headers) + } + /// Remove an entry from the map. /// /// Warning: To avoid inconsistent state, extra values _must_ be removed @@ -1634,6 +1966,36 @@ impl HeaderMap { return Err(MaxSizeReached::new()); } + #[cfg(feature = "fasthttp")] + { + self.remove_all_keys_insensitively(&key); + self.append_to_mapped_keys(key.clone()); + } + + self.entries.push(Bucket { + hash, + key, + value, + links: None, + }); + + Ok(()) + } + + #[inline] + #[cfg(feature = "fasthttp")] + fn try_insert_entry_for_append( + &mut self, + hash: HashValue, + key: HeaderName, + value: T, + ) -> Result<(), MaxSizeReached> { + if self.entries.len() >= MAX_SIZE { + return Err(MaxSizeReached::new()); + } + + self.append_to_mapped_keys(key.clone()); + self.entries.push(Bucket { hash, key, @@ -2315,7 +2677,7 @@ impl<'a, T> IterMut<'a, T> { self.cursor = Some(Cursor::Head); } - let entry = unsafe { &mut (*self.map).entries[self.entry] }; + let entry = unsafe { &mut (&mut (*self.map).entries)[self.entry] }; match self.cursor.unwrap() { Head => { @@ -2323,7 +2685,7 @@ impl<'a, T> IterMut<'a, T> { Some((&entry.key, &mut entry.value as *mut _)) } Values(idx) => { - let extra = unsafe { &mut (*self.map).extra_values[idx] }; + let extra = unsafe { &mut (&mut (*self.map).extra_values)[idx] }; match extra.next { Link::Entry(_) => self.cursor = None, @@ -2963,7 +3325,7 @@ impl<'a, T: 'a> Iterator for ValueIterMut<'a, T> { fn next(&mut self) -> Option { use self::Cursor::*; - let entry = unsafe { &mut (*self.map).entries[self.index] }; + let entry = unsafe { &mut (&mut (*self.map).entries)[self.index] }; match self.front { Some(Head) => { @@ -2983,7 +3345,7 @@ impl<'a, T: 'a> Iterator for ValueIterMut<'a, T> { Some(&mut entry.value) } Some(Values(idx)) => { - let extra = unsafe { &mut (*self.map).extra_values[idx] }; + let extra = unsafe { &mut (&mut (*self.map).extra_values)[idx] }; if self.front == self.back { self.front = None; @@ -3006,7 +3368,7 @@ impl<'a, T: 'a> DoubleEndedIterator for ValueIterMut<'a, T> { fn next_back(&mut self) -> Option { use self::Cursor::*; - let entry = unsafe { &mut (*self.map).entries[self.index] }; + let entry = unsafe { &mut (&mut (*self.map).entries)[self.index] }; match self.back { Some(Head) => { @@ -3015,7 +3377,7 @@ impl<'a, T: 'a> DoubleEndedIterator for ValueIterMut<'a, T> { Some(&mut entry.value) } Some(Values(idx)) => { - let extra = unsafe { &mut (*self.map).extra_values[idx] }; + let extra = unsafe { &mut (&mut (*self.map).extra_values)[idx] }; if self.front == self.back { self.front = None; @@ -3689,7 +4051,7 @@ mod into_header_name { impl IntoHeaderName for HeaderName {} - impl<'a> Sealed for &'a HeaderName { + impl Sealed for &HeaderName { #[inline] fn try_insert( self, @@ -3709,7 +4071,7 @@ mod into_header_name { } } - impl<'a> IntoHeaderName for &'a HeaderName {} + impl IntoHeaderName for &HeaderName {} impl Sealed for &'static str { #[inline] @@ -3793,13 +4155,13 @@ mod as_header_name { } fn as_str(&self) -> &str { - ::as_str(self) + ::as_raw_str(self) } } impl AsHeaderName for HeaderName {} - impl<'a> Sealed for &'a HeaderName { + impl Sealed for &HeaderName { #[inline] fn try_entry(self, map: &mut HeaderMap) -> Result, TryEntryError> { Ok(map.try_entry2(self)?) @@ -3811,13 +4173,13 @@ mod as_header_name { } fn as_str(&self) -> &str { - ::as_str(self) + ::as_raw_str(self) } } - impl<'a> AsHeaderName for &'a HeaderName {} + impl AsHeaderName for &HeaderName {} - impl<'a> Sealed for &'a str { + impl Sealed for &str { #[inline] fn try_entry(self, map: &mut HeaderMap) -> Result, TryEntryError> { Ok(HdrName::from_bytes(self.as_bytes(), move |hdr| { @@ -3835,7 +4197,7 @@ mod as_header_name { } } - impl<'a> AsHeaderName for &'a str {} + impl AsHeaderName for &str {} impl Sealed for String { #[inline] @@ -3855,7 +4217,7 @@ mod as_header_name { impl AsHeaderName for String {} - impl<'a> Sealed for &'a String { + impl Sealed for &String { #[inline] fn try_entry(self, map: &mut HeaderMap) -> Result, TryEntryError> { self.as_str().try_entry(map) @@ -3871,7 +4233,7 @@ mod as_header_name { } } - impl<'a> AsHeaderName for &'a String {} + impl AsHeaderName for &String {} } #[test] diff --git a/src/header/name.rs b/src/header/name.rs index 6b3d2a8c..082003ce 100644 --- a/src/header/name.rs +++ b/src/header/name.rs @@ -37,7 +37,7 @@ pub struct HeaderName { } // Almost a full `HeaderName` -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct HdrName<'a> { inner: Repr>, original: Option<&'a [u8]>, @@ -46,7 +46,7 @@ pub struct HdrName<'a> { impl<'a> Hash for HdrName<'a> { fn hash(&self, state: &mut H) { #[cfg(feature = "double-write")] - if let Some(original)= self.original{ + if let Some(original) = self.original { original.hash(state); return; } @@ -62,6 +62,7 @@ enum Repr { impl StructuralPartialEq for Repr {} +#[cfg(not(feature = "double-write"))] impl PartialEq for Repr { fn eq(&self, other: &Self) -> bool { match (self, other) { @@ -72,6 +73,25 @@ impl PartialEq for Repr { } } +#[cfg(feature = "double-write")] +impl PartialEq for Repr { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (Self::Standard(la, lb), Self::Standard(ra, rb)) => match (lb, rb) { + (Some(lb), Some(rb)) => lb == rb, + (None, None) => la == ra, + _ => false, + }, + (Self::Custom(la, lb), Self::Custom(ra, rb)) => match (lb, rb) { + (Some(lb), Some(rb)) => lb == rb, + (None, None) => la == ra, + _ => false, + }, + _ => false, + } + } +} + impl Eq for Repr {} #[cfg(not(feature = "double-write"))] @@ -87,27 +107,22 @@ impl Hash for Repr { impl Hash for Repr { fn hash(&self, state: &mut H) { match self { - Repr::Standard(inner, src) => { - match src { - Some(src) => src.hash(state), - None => { - inner.hash(state); - } + Repr::Standard(inner, src) => match src { + Some(src) => src.hash(state), + None => { + inner.hash(state); } - } - Repr::Custom(inner, src) => { - match src { - Some(src) => src.hash(state), - None => { - inner.hash(state); - } + }, + Repr::Custom(inner, src) => match src { + Some(src) => src.hash(state), + None => { + inner.hash(state); } }, } } } - // Used to hijack the Hash impl #[derive(Debug, Clone, Eq, PartialEq)] struct Custom(ByteStr); @@ -1580,7 +1595,7 @@ impl<'a> PartialEq<&'a HeaderName> for HeaderName { } } -impl<'a> PartialEq for &'a HeaderName { +impl PartialEq for &HeaderName { #[inline] fn eq(&self, other: &HeaderName) -> bool { *other == *self @@ -1634,7 +1649,7 @@ impl<'a> PartialEq<&'a str> for HeaderName { } } -impl<'a> PartialEq for &'a str { +impl PartialEq for &str { /// Performs a case-insensitive comparison of the string against the header /// name #[inline] @@ -1754,6 +1769,7 @@ impl<'a> From> for HeaderName { } #[doc(hidden)] +#[cfg(not(feature = "double-write"))] impl<'a> PartialEq> for HeaderName { #[inline] fn eq(&self, other: &HdrName<'a>) -> bool { @@ -1776,6 +1792,38 @@ impl<'a> PartialEq> for HeaderName { } } +#[doc(hidden)] +#[cfg(feature = "double-write")] +impl<'a> PartialEq> for HeaderName { + #[inline] + fn eq(&self, other: &HdrName<'a>) -> bool { + match &self.inner { + Repr::Standard(la, lb) => match (lb, other.original) { + (Some(lb), Some(rb)) => lb.as_bytes() == rb, + (None, None) => match &other.inner { + Repr::Standard(ra, _) => la == ra, + _ => false, + }, + _ => false, + }, + Repr::Custom(Custom(ref la), lb) => match (lb, other.original) { + (Some(lb), Some(rb)) => lb.as_bytes() == rb, + (None, None) => match other.inner { + Repr::Custom(ref ra, _) => { + if ra.lower { + la.as_bytes() == ra.buf + } else { + eq_ignore_ascii_case(la.as_bytes(), ra.buf) + } + } + _ => false, + }, + _ => false, + }, + } + } +} + // ===== Custom ===== impl Hash for Custom { @@ -1837,6 +1885,7 @@ unsafe fn slice_assume_init(slice: &[MaybeUninit]) -> &[T] { mod tests { use self::StandardHeader::Vary; use super::*; + use crate::HeaderMap; #[test] fn test_bounds() { @@ -2123,4 +2172,139 @@ mod tests { HeaderName::from_lowercase(&[0x1; 100]).unwrap_err(); HeaderName::from_lowercase(&[0xFF; 100]).unwrap_err(); } + + #[test] + fn test_insert() { + let mut header_map = HeaderMap::new(); + + header_map.insert("x-tt-agw-key1", "1".parse().unwrap()); + assert_eq!(header_map.get("x-tt-agw-key1").unwrap(), "1"); + + header_map.insert("X-Tt-agw-key1", "2".parse().unwrap()); + assert_eq!(header_map.get("X-Tt-agw-key1").unwrap(), "2"); + } +} + +#[cfg(test)] +#[cfg(feature = "fasthttp")] +mod fasthttp_tests { + use crate::header::HeaderMap; + use crate::{HeaderName, HeaderValue, Request}; + + #[test] + fn test_insert() { + let mut header_map = HeaderMap::new(); + + header_map.insert( + HeaderName::from_bytes("x-tt-agw-key1".as_bytes()).unwrap(), + HeaderValue::from_static("1"), + ); + assert_eq!(header_map.get("x-tt-agw-key1").unwrap(), "1"); + assert_eq!(header_map.get("X-Tt-Agw-Key1").unwrap(), "1"); + assert_eq!(header_map.len(), 1); + + header_map.insert( + HeaderName::from_bytes("X-Tt-agw-key1".as_bytes()).unwrap(), + HeaderValue::from_static("2"), + ); + assert_eq!(header_map.get("X-Tt-agw-key1").unwrap(), "2"); + assert_eq!(header_map.get("X-Tt-Agw-Key1").unwrap(), "2"); + assert_eq!(header_map.len(), 1); + + header_map.insert( + HeaderName::from_bytes("x-tt-agw-key2".as_bytes()).unwrap(), + HeaderValue::from_static("3"), + ); + assert_eq!(header_map.len(), 2); + + let mut iter = header_map.iter(); + + let (k, v) = iter.next().unwrap(); + assert_eq!( + (k.as_raw_str(), v.to_str().unwrap()), + ("X-Tt-agw-key1", "2") + ); + + let (k, v) = iter.next().unwrap(); + assert_eq!( + (k.as_raw_str(), v.to_str().unwrap()), + ("x-tt-agw-key2", "3") + ); + } + + #[test] + fn test_append() { + let mut header_map = HeaderMap::new(); + header_map.insert( + HeaderName::from_bytes("x-tt-agw-key1".as_bytes()).unwrap(), + HeaderValue::from_static("1"), + ); + header_map.append( + HeaderName::from_bytes("X-Tt-Agw-Key1".as_bytes()).unwrap(), + HeaderValue::from_static("2"), + ); + + assert_eq!(header_map.len(), 2); + + assert_eq!(header_map.get("x-tt-agw-key1").unwrap(), "1"); + assert_eq!(header_map.get("X-Tt-Agw-Key1").unwrap(), "2"); + + let mut iter = header_map.iter(); + let (k, v) = iter.next().unwrap(); + assert_eq!( + (k.as_raw_str(), v.to_str().unwrap()), + ("x-tt-agw-key1", "1") + ); + let (k, v) = iter.next().unwrap(); + assert_eq!( + (k.as_raw_str(), v.to_str().unwrap()), + ("X-Tt-Agw-Key1", "2") + ); + } + + #[test] + fn test_append2() { + let mut header_map = HeaderMap::new(); + header_map.insert( + HeaderName::from_bytes("rpc-persist-lane-p-aid".as_bytes()).unwrap(), + HeaderValue::from_static("1"), + ); + header_map.append( + HeaderName::from_bytes("rpc-persist-Lane-P-Aid".as_bytes()).unwrap(), + HeaderValue::from_static("1"), + ); + + assert_eq!(header_map.len(), 2); + + assert_eq!(header_map.get("rpc-persist-lane-p-aid").unwrap(), "1"); + assert_eq!(header_map.get("rpc-persist-Lane-P-Aid").unwrap(), "1"); + + let mut iter = header_map.iter(); + let (k, v) = iter.next().unwrap(); + assert_eq!( + (k.as_raw_str(), v.to_str().unwrap()), + ("rpc-persist-lane-p-aid", "1") + ); + let (k, v) = iter.next().unwrap(); + assert_eq!( + (k.as_raw_str(), v.to_str().unwrap()), + ("rpc-persist-Lane-P-Aid", "1") + ); + } + + #[test] + fn test_header_insert() { + let request = Request::builder() + .uri("/") + .header("X-Tt-Log-Id", "logid") + .body(()) + .unwrap(); + let logid = request + .headers() + .get("x-tt-log-id") + .unwrap() + .to_str() + .unwrap(); + assert_eq!(logid, "logid".to_string()); + } } diff --git a/src/header/value.rs b/src/header/value.rs index 99d1e155..6e4c347c 100644 --- a/src/header/value.rs +++ b/src/header/value.rs @@ -725,14 +725,14 @@ impl PartialOrd for String { } } -impl<'a> PartialEq for &'a HeaderValue { +impl PartialEq for &HeaderValue { #[inline] fn eq(&self, other: &HeaderValue) -> bool { **self == *other } } -impl<'a> PartialOrd for &'a HeaderValue { +impl PartialOrd for &HeaderValue { #[inline] fn partial_cmp(&self, other: &HeaderValue) -> Option { (**self).partial_cmp(other) @@ -759,14 +759,14 @@ where } } -impl<'a> PartialEq for &'a str { +impl PartialEq for &str { #[inline] fn eq(&self, other: &HeaderValue) -> bool { *other == *self } } -impl<'a> PartialOrd for &'a str { +impl PartialOrd for &str { #[inline] fn partial_cmp(&self, other: &HeaderValue) -> Option { self.as_bytes().partial_cmp(other.as_bytes()) diff --git a/src/lib.rs b/src/lib.rs index 89e169f6..2d4849ae 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -180,6 +180,8 @@ pub mod version; mod byte_str; mod error; +#[cfg(feature = "fasthttp")] +pub mod ext; mod extensions; pub use crate::error::{Error, Result}; diff --git a/src/method.rs b/src/method.rs index 7b4584ab..2567b739 100644 --- a/src/method.rs +++ b/src/method.rs @@ -194,7 +194,7 @@ impl<'a> PartialEq<&'a Method> for Method { } } -impl<'a> PartialEq for &'a Method { +impl PartialEq for &Method { #[inline] fn eq(&self, other: &Method) -> bool { *self == other @@ -222,7 +222,7 @@ impl<'a> PartialEq<&'a str> for Method { } } -impl<'a> PartialEq for &'a str { +impl PartialEq for &str { #[inline] fn eq(&self, other: &Method) -> bool { *self == other.as_ref() diff --git a/src/request.rs b/src/request.rs index 324b676c..9f10479f 100644 --- a/src/request.rs +++ b/src/request.rs @@ -164,6 +164,7 @@ pub struct Request { /// /// The HTTP request head consists of a method, uri, version, and a set of /// header fields. +#[allow(warnings)] #[derive(Clone)] pub struct Parts { /// The request's method diff --git a/src/response.rs b/src/response.rs index ab9e49bc..504eaeb4 100644 --- a/src/response.rs +++ b/src/response.rs @@ -186,6 +186,7 @@ pub struct Response { /// /// The HTTP response head consists of a status, version, and a set of /// header fields. +#[allow(warnings)] #[derive(Clone)] pub struct Parts { /// The response's status diff --git a/src/uri/authority.rs b/src/uri/authority.rs index 07aa6795..6481b158 100644 --- a/src/uri/authority.rs +++ b/src/uri/authority.rs @@ -302,7 +302,7 @@ impl PartialEq for str { } } -impl<'a> PartialEq for &'a str { +impl PartialEq for &str { fn eq(&self, other: &Authority) -> bool { self.eq_ignore_ascii_case(other.as_str()) } @@ -360,7 +360,7 @@ impl PartialOrd for str { } } -impl<'a> PartialOrd for &'a str { +impl PartialOrd for &str { fn partial_cmp(&self, other: &Authority) -> Option { let left = self.as_bytes().iter().map(|b| b.to_ascii_lowercase()); let right = other.data.as_bytes().iter().map(|b| b.to_ascii_lowercase()); diff --git a/src/uri/mod.rs b/src/uri/mod.rs index 767f0743..c6b3f2d5 100644 --- a/src/uri/mod.rs +++ b/src/uri/mod.rs @@ -102,6 +102,7 @@ pub struct Uri { /// The various parts of a URI. /// /// This struct is used to provide to and retrieve from a URI. +#[allow(warnings)] #[derive(Debug, Default)] pub struct Parts { /// The scheme component of a URI @@ -1001,7 +1002,7 @@ impl<'a> PartialEq<&'a str> for Uri { } } -impl<'a> PartialEq for &'a str { +impl PartialEq for &str { fn eq(&self, uri: &Uri) -> bool { uri == *self } diff --git a/src/uri/path.rs b/src/uri/path.rs index 42db1f92..8415a556 100644 --- a/src/uri/path.rs +++ b/src/uri/path.rs @@ -383,7 +383,7 @@ impl PartialEq for PathAndQuery { } } -impl<'a> PartialEq for &'a str { +impl PartialEq for &str { #[inline] fn eq(&self, other: &PathAndQuery) -> bool { self == &other.as_str() @@ -446,7 +446,7 @@ impl<'a> PartialOrd<&'a str> for PathAndQuery { } } -impl<'a> PartialOrd for &'a str { +impl PartialOrd for &str { #[inline] fn partial_cmp(&self, other: &PathAndQuery) -> Option { self.partial_cmp(&other.as_str()) diff --git a/tests/double_write.rs b/tests/double_write.rs new file mode 100644 index 00000000..cdc53948 --- /dev/null +++ b/tests/double_write.rs @@ -0,0 +1,52 @@ +#[cfg(feature = "double-write")] +#[cfg(not(feature = "fasthttp"))] +mod double_write_tests { + use http::{HeaderMap, HeaderName, HeaderValue}; + + #[test] + fn feature_double_write() { + let mut headers = HeaderMap::new(); + headers.insert( + HeaderName::from_static("foo"), + HeaderValue::from_static("bar"), + ); + + headers.insert( + HeaderName::from_bytes("Foo".as_bytes()).unwrap(), + HeaderValue::from_static("Bar"), + ); + + assert_eq!(headers.len(), 2); + + headers.append( + HeaderName::from_bytes("foo1".as_bytes()).unwrap(), + HeaderValue::from_static("baz1"), + ); + headers.append( + HeaderName::from_bytes("Foo1".as_bytes()).unwrap(), + HeaderValue::from_static("baz2"), + ); + headers.append( + HeaderName::from_bytes("Foo1".as_bytes()).unwrap(), + HeaderValue::from_static("baz3"), + ); + + assert_eq!(headers.get("foo"), Some(&HeaderValue::from_static("bar"))); + assert_eq!(headers.get("Foo"), Some(&HeaderValue::from_static("Bar"))); + assert_eq!( + headers.get(HeaderName::from_bytes("Foo".as_bytes()).unwrap()), + Some(&HeaderValue::from_static("Bar")) + ); + assert_eq!(headers.get("foo1"), Some(&HeaderValue::from_static("baz1"))); + assert_eq!( + headers.get_all("Foo1").into_iter().collect::>(), + vec![ + &HeaderValue::from_static("baz2"), + &HeaderValue::from_static("baz3") + ] + ); + headers.remove("Foo1"); + assert_eq!(headers.get("foo1"), Some(&HeaderValue::from_static("baz1"))); + assert_eq!(headers.get("Foo1"), None); + } +} diff --git a/tests/fasthttp.rs b/tests/fasthttp.rs new file mode 100644 index 00000000..d837f1b0 --- /dev/null +++ b/tests/fasthttp.rs @@ -0,0 +1,258 @@ +#[cfg(feature = "fasthttp")] +mod fasthttp_tests { + use http::{HeaderMap, HeaderName, HeaderValue, Request}; + + #[test] + fn test_insert() { + let mut header_map = HeaderMap::new(); + + header_map.insert( + HeaderName::from_bytes("foo".as_bytes()).unwrap(), + HeaderValue::from_static("bar1"), + ); + assert_eq!(header_map.len(), 1); + assert_eq!(header_map.get("foo").unwrap(), "bar1"); + assert_eq!(header_map.get("Foo").unwrap(), "bar1"); + + header_map.insert( + HeaderName::from_bytes("Foo".as_bytes()).unwrap(), + HeaderValue::from_static("bar2"), + ); + assert_eq!(header_map.len(), 1); + assert_eq!(header_map.get("foo").unwrap(), "bar2"); + assert_eq!(header_map.get("Foo").unwrap(), "bar2"); + + header_map.insert( + HeaderName::from_bytes("foo1".as_bytes()).unwrap(), + HeaderValue::from_static("bar1"), + ); + assert_eq!(header_map.len(), 2); + + let mut iter = header_map.iter(); + + let (k, v) = iter.next().unwrap(); + assert_eq!((k.as_raw_str(), v.to_str().unwrap()), ("Foo", "bar2")); + + let (k, v) = iter.next().unwrap(); + assert_eq!((k.as_raw_str(), v.to_str().unwrap()), ("foo1", "bar1")); + } + + // tes append for non-std headers + #[test] + fn test_append() { + let mut header_map = HeaderMap::new(); + + header_map.insert( + HeaderName::from_bytes("foo".as_bytes()).unwrap(), + HeaderValue::from_static("bar1"), + ); + header_map.append( + HeaderName::from_bytes("Foo".as_bytes()).unwrap(), + HeaderValue::from_static("bar2"), + ); + assert_eq!(header_map.len(), 2); + assert_eq!(header_map.get("foo").unwrap(), "bar1"); + assert_eq!(header_map.get("Foo").unwrap(), "bar2"); + + header_map.append( + HeaderName::from_bytes("foo".as_bytes()).unwrap(), + HeaderValue::from_static("bar3"), + ); + assert_eq!(header_map.len(), 3); + let mut header_value_iter = header_map.get_all("foo").iter(); + let header_value = header_value_iter.next().unwrap(); + assert_eq!(header_value.to_str().unwrap(), "bar1"); + let header_value = header_value_iter.next().unwrap(); + assert_eq!(header_value.to_str().unwrap(), "bar3"); + assert_eq!(header_map.get("foo").unwrap(), "bar1"); + assert_eq!(header_map.get("Foo").unwrap(), "bar2"); + + header_map.append( + HeaderName::from_bytes("Foo".as_bytes()).unwrap(), + HeaderValue::from_static("bar4"), + ); + assert_eq!(header_map.len(), 4); + assert_eq!(header_map.get("foo").unwrap(), "bar1"); + let mut header_value_iter = header_map.get_all("Foo").iter(); + let header_value = header_value_iter.next().unwrap(); + assert_eq!(header_value.to_str().unwrap(), "bar2"); + let header_value = header_value_iter.next().unwrap(); + assert_eq!(header_value.to_str().unwrap(), "bar4"); + + let mut iter = header_map.iter(); + let (k, v) = iter.next().unwrap(); + assert_eq!((k.as_raw_str(), v.to_str().unwrap()), ("foo", "bar1")); + let (k, v) = iter.next().unwrap(); + assert_eq!((k.as_raw_str(), v.to_str().unwrap()), ("foo", "bar3")); + let (k, v) = iter.next().unwrap(); + assert_eq!((k.as_raw_str(), v.to_str().unwrap()), ("Foo", "bar2")); + let (k, v) = iter.next().unwrap(); + assert_eq!((k.as_raw_str(), v.to_str().unwrap()), ("Foo", "bar4")) + } + + #[test] + fn test_append_for_non_append_header() { + // test append for special std header, should equal to insert + let mut header_map = HeaderMap::new(); + + header_map.insert( + HeaderName::from_bytes("content-length".as_bytes()).unwrap(), + HeaderValue::from_static("1"), + ); + header_map.append( + HeaderName::from_bytes("Content-Length".as_bytes()).unwrap(), + HeaderValue::from_static("2"), + ); + assert_eq!(header_map.len(), 1); + assert_eq!(header_map.get("content-length").unwrap(), "2"); + assert_eq!(header_map.get("Content-Length").unwrap(), "2"); + + header_map.append( + HeaderName::from_bytes("content-length".as_bytes()).unwrap(), + HeaderValue::from_static("3"), + ); + assert_eq!(header_map.len(), 1); + assert_eq!(header_map.get("content-length").unwrap(), "3"); + + header_map.append( + HeaderName::from_bytes("Content-Length".as_bytes()).unwrap(), + HeaderValue::from_static("4"), + ); + assert_eq!(header_map.len(), 1); + assert_eq!(header_map.get("content-length").unwrap(), "4"); + } + + #[test] + fn test_append_for_std_header() { + let mut header_map = HeaderMap::new(); + + header_map.append( + HeaderName::from_bytes("content-type".as_bytes()).unwrap(), + HeaderValue::from_static("application/json"), + ); + assert_eq!(header_map.len(), 1); + assert_eq!(header_map.get("content-type").unwrap(), "application/json"); + assert_eq!(header_map.get("Content-Type").unwrap(), "application/json"); + + header_map.append( + HeaderName::from_bytes("Content-Type".as_bytes()).unwrap(), + HeaderValue::from_static("text/html"), + ); + assert_eq!(header_map.len(), 2); + let mut iter = header_map.iter(); + let (k, v) = iter.next().unwrap(); + assert_eq!( + (k.as_raw_str(), v.to_str().unwrap()), + ("Content-Type", "application/json") + ); + let (k, v) = iter.next().unwrap(); + assert_eq!( + (k.as_raw_str(), v.to_str().unwrap()), + ("Content-Type", "text/html") + ); + } + + #[test] + fn test_remove() { + let mut header_map = HeaderMap::new(); + header_map.insert( + HeaderName::from_bytes("foo".as_bytes()).unwrap(), + HeaderValue::from_static("bar1"), + ); + assert_eq!(header_map.len(), 1); + assert_eq!(header_map.get("foo").unwrap(), "bar1"); + + header_map.remove(HeaderName::from_bytes("foo".as_bytes()).unwrap()); + assert_eq!(header_map.len(), 0); + assert_eq!(header_map.get("foo"), None); + let mut header_map = HeaderMap::new(); + + header_map.insert( + HeaderName::from_bytes("foo".as_bytes()).unwrap(), + HeaderValue::from_static("bar1"), + ); + header_map.append( + HeaderName::from_bytes("Foo".as_bytes()).unwrap(), + HeaderValue::from_static("bar2"), + ); + assert_eq!(header_map.len(), 2); + + header_map.remove(HeaderName::from_bytes("foo".as_bytes()).unwrap()); + assert_eq!(header_map.len(), 1); + assert_eq!(header_map.get("foo").unwrap(), "bar2"); + assert_eq!(header_map.get("Foo").unwrap(), "bar2"); + + let mut header_map = HeaderMap::new(); + header_map.insert( + HeaderName::from_bytes("foo".as_bytes()).unwrap(), + HeaderValue::from_static("bar1"), + ); + header_map.append( + HeaderName::from_bytes("Foo".as_bytes()).unwrap(), + HeaderValue::from_static("bar2"), + ); + assert_eq!(header_map.len(), 2); + + header_map.remove(HeaderName::from_bytes("Foo".as_bytes()).unwrap()); + assert_eq!(header_map.len(), 1); + assert_eq!(header_map.get("foo").unwrap(), "bar1"); + assert_eq!(header_map.get("Foo").unwrap(), "bar1"); + } + + #[test] + fn test_remove_all() { + let mut header_map = HeaderMap::new(); + header_map.insert( + HeaderName::from_bytes("foo".as_bytes()).unwrap(), + HeaderValue::from_static("foo1"), + ); + assert_eq!(header_map.len(), 1); + assert_eq!(header_map.get("foo").unwrap(), "foo1"); + header_map.remove_all(HeaderName::from_bytes("foo".as_bytes()).unwrap()); + assert_eq!(header_map.len(), 0); + assert_eq!(header_map.get("foo"), None); + let mut header_map = HeaderMap::new(); + header_map.insert( + HeaderName::from_bytes("foo".as_bytes()).unwrap(), + HeaderValue::from_static("foo1"), + ); + header_map.append( + HeaderName::from_bytes("Foo".as_bytes()).unwrap(), + HeaderValue::from_static("foo2"), + ); + assert_eq!(header_map.len(), 2); + header_map.remove_all(HeaderName::from_bytes("foo".as_bytes()).unwrap()); + assert_eq!(header_map.len(), 0); + let mut header_map = HeaderMap::new(); + header_map.insert( + HeaderName::from_bytes("foo".as_bytes()).unwrap(), + HeaderValue::from_static("foo1"), + ); + header_map.append( + HeaderName::from_bytes("Foo".as_bytes()).unwrap(), + HeaderValue::from_static("foo2"), + ); + assert_eq!(header_map.len(), 2); + header_map.remove_all(HeaderName::from_bytes("Foo".as_bytes()).unwrap()); + assert_eq!(header_map.len(), 0); + } + + #[test] + fn test_get_with_request() { + let request = Request::builder() + .uri("/") + .header("Foo", "bar1") + .header("Foo", "bar2") + .header("foo", "bar3") + .body(()) + .unwrap(); + + assert_eq!(request.headers().get("foo").unwrap(), "bar3"); + assert_eq!(request.headers().get("Foo").unwrap(), "bar1"); + let mut header_value_iter = request.headers().get_all("Foo").iter(); + let header_value = header_value_iter.next().unwrap(); + assert_eq!(header_value.to_str().unwrap(), "bar1"); + let header_value = header_value_iter.next().unwrap(); + assert_eq!(header_value.to_str().unwrap(), "bar2"); + } +} diff --git a/tests/header_map.rs b/tests/header_map.rs index 24c1d58c..9a9d7e12 100644 --- a/tests/header_map.rs +++ b/tests/header_map.rs @@ -672,40 +672,3 @@ fn ensure_miri_sharedreadonly_not_violated() { let _foo = &headers.iter().next(); } - -#[cfg(feature = "double-write")] -#[test] -fn feature_double_write() { - let mut headers = HeaderMap::new(); - headers.insert( - HeaderName::from_static("foo"), - HeaderValue::from_static("bar"), - ); - - headers.insert( - HeaderName::from_bytes("Foo".as_bytes()).unwrap(), - HeaderValue::from_static("Bar"), - ); - - headers.append( - HeaderName::from_bytes("foo1".as_bytes()).unwrap(), - HeaderValue::from_static("baz1"), - ); - headers.append( - HeaderName::from_bytes("Foo1".as_bytes()).unwrap(), - HeaderValue::from_static("baz2"), - ); - headers.append( - HeaderName::from_bytes("Foo1".as_bytes()).unwrap(), - HeaderValue::from_static("baz3"), - ); - - assert_eq!(headers.get("foo"), Some(&HeaderValue::from_static("bar"))); - assert_eq!(headers.get("Foo"), Some(&HeaderValue::from_static("Bar"))); - assert_eq!(headers.get(HeaderName::from_bytes("Foo".as_bytes()).unwrap()), Some(&HeaderValue::from_static("Bar"))); - assert_eq!(headers.get("foo1"), Some(&HeaderValue::from_static("baz1"))); - assert_eq!(headers.get_all("Foo1").into_iter().collect::>(), vec![&HeaderValue::from_static("baz2"), &HeaderValue::from_static("baz3")]); - headers.remove("Foo1"); - assert_eq!(headers.get("foo1"), Some(&HeaderValue::from_static("baz1"))); - assert_eq!(headers.get("Foo1"), None); -} \ No newline at end of file diff --git a/tests/header_map_fuzz.rs b/tests/header_map_fuzz.rs index 40db0494..cc6a97b9 100644 --- a/tests/header_map_fuzz.rs +++ b/tests/header_map_fuzz.rs @@ -6,6 +6,10 @@ use rand::rngs::StdRng; use rand::seq::SliceRandom; use rand::{Rng, SeedableRng}; +#[cfg(feature = "fasthttp")] +use crate::ext::fasthttp::consts::is_non_append_header; +#[cfg(feature = "fasthttp")] +use http::ext::fasthttp::consts::is_standard_header; use std::collections::HashMap; #[cfg(not(miri))] @@ -191,9 +195,39 @@ impl AltMap { if let Some(name) = self.find_random_name(rng) { name } else { + #[cfg(feature = "fasthttp")] + { + let mut header_name = gen_header_name(rng); + + loop { + if is_non_append_header(&header_name) || is_standard_header(&header_name) { + header_name = gen_header_name(rng); + } else { + break; + } + } + + header_name + } + #[cfg(not(feature = "fasthttp"))] gen_header_name(rng) } } else { + #[cfg(feature = "fasthttp")] + { + let mut header_name = gen_header_name(rng); + + loop { + if is_non_append_header(&header_name) || is_standard_header(&header_name) { + header_name = gen_header_name(rng); + } else { + break; + } + } + + header_name + } + #[cfg(not(feature = "fasthttp"))] gen_header_name(rng) } }