feature(ffi): add connection option to preserve header order (#2798)
Libcurl expects that headers are iterated in the same order that they
are recieved. Previously this caused curl tests 580 and 581 to fail.
This necessitated exposing a way to preserve the original ordering of
http headers.
SUMMARY OF CHANGES: Add a new data structure called OriginalHeaderOrder that
represents the order in which headers originally appear in a HTTP
message. This datastructure is `Vec<(Headername, multimap-index)>`.
This vector is ordered by the order which headers were recieved.
Add the following ffi functions:
- ffi::client::hyper_clientconn_options_set_preserve_header_order : An
ffi interface to configure a connection to preserve header order.
- ffi::client::hyper_clientconn_options_set_preserve_header_case : An
ffi interface to configure a connection to preserve header case.
- Add a new option to ParseContext, and Conn::State called `preserve_header_order`.
This option, and all the code paths it creates are behind the `ffi`
feature flag. This should not change performance of response parsing for
non-ffi users.
Closes #2780
BREAKING CHANGE: hyper_clientconn_options_new no longer
sets the http1_preserve_header_case connection option by default.
Users should now call
hyper_clientconn_options_set_preserve_header_case
if they desire that functionality.
This commit is contained in:
101
src/ext.rs
101
src/ext.rs
@@ -1,9 +1,12 @@
|
||||
//! HTTP extensions.
|
||||
|
||||
use bytes::Bytes;
|
||||
use http::header::HeaderName;
|
||||
#[cfg(feature = "http1")]
|
||||
use http::header::{HeaderName, IntoHeaderName, ValueIter};
|
||||
use http::header::{IntoHeaderName, ValueIter};
|
||||
use http::HeaderMap;
|
||||
#[cfg(feature = "ffi")]
|
||||
use std::collections::HashMap;
|
||||
#[cfg(feature = "http2")]
|
||||
use std::fmt;
|
||||
|
||||
@@ -120,3 +123,99 @@ impl HeaderCaseMap {
|
||||
self.0.append(name, orig);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "ffi")]
|
||||
#[derive(Clone, Debug)]
|
||||
/// Hashmap<Headername, numheaders with that name>
|
||||
pub(crate) struct OriginalHeaderOrder {
|
||||
/// Stores how many entries a Headername maps to. This is used
|
||||
/// for accounting.
|
||||
num_entries: HashMap<HeaderName, usize>,
|
||||
/// Stores the ordering of the headers. ex: `vec[i] = (headerName, idx)`,
|
||||
/// The vector is ordered such that the ith element
|
||||
/// represents the ith header that came in off the line.
|
||||
/// The `HeaderName` and `idx` are then used elsewhere to index into
|
||||
/// the multi map that stores the header values.
|
||||
entry_order: Vec<(HeaderName, usize)>,
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "http1", feature = "ffi"))]
|
||||
impl OriginalHeaderOrder {
|
||||
pub(crate) fn default() -> Self {
|
||||
OriginalHeaderOrder {
|
||||
num_entries: HashMap::new(),
|
||||
entry_order: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn insert(&mut self, name: HeaderName) {
|
||||
if !self.num_entries.contains_key(&name) {
|
||||
let idx = 0;
|
||||
self.num_entries.insert(name.clone(), 1);
|
||||
self.entry_order.push((name, idx));
|
||||
}
|
||||
// Replacing an already existing element does not
|
||||
// change ordering, so we only care if its the first
|
||||
// header name encountered
|
||||
}
|
||||
|
||||
pub(crate) fn append<N>(&mut self, name: N)
|
||||
where
|
||||
N: IntoHeaderName + Into<HeaderName> + Clone,
|
||||
{
|
||||
let name: HeaderName = name.into();
|
||||
let idx;
|
||||
if self.num_entries.contains_key(&name) {
|
||||
idx = self.num_entries[&name];
|
||||
*self.num_entries.get_mut(&name).unwrap() += 1;
|
||||
} else {
|
||||
idx = 0;
|
||||
self.num_entries.insert(name.clone(), 1);
|
||||
}
|
||||
self.entry_order.push((name, idx));
|
||||
}
|
||||
|
||||
// No doc test is run here because `RUSTFLAGS='--cfg hyper_unstable_ffi'`
|
||||
// is needed to compile. Once ffi is stablized `no_run` should be removed
|
||||
// here.
|
||||
/// This returns an iterator that provides header names and indexes
|
||||
/// in the original order recieved.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```no_run
|
||||
/// use hyper::ext::OriginalHeaderOrder;
|
||||
/// use hyper::header::{HeaderName, HeaderValue, HeaderMap};
|
||||
///
|
||||
/// let mut h_order = OriginalHeaderOrder::default();
|
||||
/// let mut h_map = Headermap::new();
|
||||
///
|
||||
/// let name1 = b"Set-CookiE";
|
||||
/// let value1 = b"a=b";
|
||||
/// h_map.append(name1);
|
||||
/// h_order.append(name1);
|
||||
///
|
||||
/// let name2 = b"Content-Encoding";
|
||||
/// let value2 = b"gzip";
|
||||
/// h_map.append(name2, value2);
|
||||
/// h_order.append(name2);
|
||||
///
|
||||
/// let name3 = b"SET-COOKIE";
|
||||
/// let value3 = b"c=d";
|
||||
/// h_map.append(name3, value3);
|
||||
/// h_order.append(name3)
|
||||
///
|
||||
/// let mut iter = h_order.get_in_order()
|
||||
///
|
||||
/// let (name, idx) = iter.next();
|
||||
/// assert_eq!(b"a=b", h_map.get_all(name).nth(idx).unwrap());
|
||||
///
|
||||
/// let (name, idx) = iter.next();
|
||||
/// assert_eq!(b"gzip", h_map.get_all(name).nth(idx).unwrap());
|
||||
///
|
||||
/// let (name, idx) = iter.next();
|
||||
/// assert_eq!(b"c=d", h_map.get_all(name).nth(idx).unwrap());
|
||||
/// ```
|
||||
pub(crate) fn get_in_order(&self) -> impl Iterator<Item = &(HeaderName, usize)> {
|
||||
self.entry_order.iter()
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user