Skip to content

Commit 78de891

Browse files
authored
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.
1 parent e1138d7 commit 78de891

File tree

9 files changed

+381
-30
lines changed

9 files changed

+381
-30
lines changed

capi/include/hyper.h

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -355,6 +355,22 @@ void hyper_clientconn_free(struct hyper_clientconn *conn);
355355
*/
356356
struct hyper_clientconn_options *hyper_clientconn_options_new(void);
357357

358+
/*
359+
Set the whether or not header case is preserved.
360+
361+
Pass `0` to allow lowercase normalization (default), `1` to retain original case.
362+
*/
363+
void hyper_clientconn_options_set_preserve_header_case(struct hyper_clientconn_options *opts,
364+
int enabled);
365+
366+
/*
367+
Set the whether or not header order is preserved.
368+
369+
Pass `0` to allow reordering (default), `1` to retain original ordering.
370+
*/
371+
void hyper_clientconn_options_set_preserve_header_order(struct hyper_clientconn_options *opts,
372+
int enabled);
373+
358374
/*
359375
Free a `hyper_clientconn_options *`.
360376
*/

src/client/conn.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,8 @@ pub struct Builder {
156156
h1_writev: Option<bool>,
157157
h1_title_case_headers: bool,
158158
h1_preserve_header_case: bool,
159+
#[cfg(feature = "ffi")]
160+
h1_preserve_header_order: bool,
159161
h1_read_buf_exact_size: Option<usize>,
160162
h1_max_buf_size: Option<usize>,
161163
#[cfg(feature = "ffi")]
@@ -558,6 +560,8 @@ impl Builder {
558560
h1_parser_config: Default::default(),
559561
h1_title_case_headers: false,
560562
h1_preserve_header_case: false,
563+
#[cfg(feature = "ffi")]
564+
h1_preserve_header_order: false,
561565
h1_max_buf_size: None,
562566
#[cfg(feature = "ffi")]
563567
h1_headers_raw: false,
@@ -704,6 +708,21 @@ impl Builder {
704708
self
705709
}
706710

711+
/// Set whether to support preserving original header order.
712+
///
713+
/// Currently, this will record the order in which headers are received, and store this
714+
/// ordering in a private extension on the `Response`. It will also look for and use
715+
/// such an extension in any provided `Request`.
716+
///
717+
/// Note that this setting does not affect HTTP/2.
718+
///
719+
/// Default is false.
720+
#[cfg(feature = "ffi")]
721+
pub fn http1_preserve_header_order(&mut self, enabled: bool) -> &mut Builder {
722+
self.h1_preserve_header_order = enabled;
723+
self
724+
}
725+
707726
/// Sets the exact size of the read buffer to *always* use.
708727
///
709728
/// Note that setting this option unsets the `http1_max_buf_size` option.
@@ -948,9 +967,14 @@ impl Builder {
948967
if opts.h1_title_case_headers {
949968
conn.set_title_case_headers();
950969
}
970+
#[cfg(feature = "ffi")]
951971
if opts.h1_preserve_header_case {
952972
conn.set_preserve_header_case();
953973
}
974+
#[cfg(feature = "ffi")]
975+
if opts.h1_preserve_header_order {
976+
conn.set_preserve_header_order();
977+
}
954978
if opts.h09_responses {
955979
conn.set_h09_responses();
956980
}

src/ext.rs

Lines changed: 100 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
//! HTTP extensions.
22
33
use bytes::Bytes;
4+
use http::header::HeaderName;
45
#[cfg(feature = "http1")]
5-
use http::header::{HeaderName, IntoHeaderName, ValueIter};
6+
use http::header::{IntoHeaderName, ValueIter};
67
use http::HeaderMap;
8+
#[cfg(feature = "ffi")]
9+
use std::collections::HashMap;
710
#[cfg(feature = "http2")]
811
use std::fmt;
912

@@ -120,3 +123,99 @@ impl HeaderCaseMap {
120123
self.0.append(name, orig);
121124
}
122125
}
126+
127+
#[cfg(feature = "ffi")]
128+
#[derive(Clone, Debug)]
129+
/// Hashmap<Headername, numheaders with that name>
130+
pub(crate) struct OriginalHeaderOrder {
131+
/// Stores how many entries a Headername maps to. This is used
132+
/// for accounting.
133+
num_entries: HashMap<HeaderName, usize>,
134+
/// Stores the ordering of the headers. ex: `vec[i] = (headerName, idx)`,
135+
/// The vector is ordered such that the ith element
136+
/// represents the ith header that came in off the line.
137+
/// The `HeaderName` and `idx` are then used elsewhere to index into
138+
/// the multi map that stores the header values.
139+
entry_order: Vec<(HeaderName, usize)>,
140+
}
141+
142+
#[cfg(all(feature = "http1", feature = "ffi"))]
143+
impl OriginalHeaderOrder {
144+
pub(crate) fn default() -> Self {
145+
OriginalHeaderOrder {
146+
num_entries: HashMap::new(),
147+
entry_order: Vec::new(),
148+
}
149+
}
150+
151+
pub(crate) fn insert(&mut self, name: HeaderName) {
152+
if !self.num_entries.contains_key(&name) {
153+
let idx = 0;
154+
self.num_entries.insert(name.clone(), 1);
155+
self.entry_order.push((name, idx));
156+
}
157+
// Replacing an already existing element does not
158+
// change ordering, so we only care if its the first
159+
// header name encountered
160+
}
161+
162+
pub(crate) fn append<N>(&mut self, name: N)
163+
where
164+
N: IntoHeaderName + Into<HeaderName> + Clone,
165+
{
166+
let name: HeaderName = name.into();
167+
let idx;
168+
if self.num_entries.contains_key(&name) {
169+
idx = self.num_entries[&name];
170+
*self.num_entries.get_mut(&name).unwrap() += 1;
171+
} else {
172+
idx = 0;
173+
self.num_entries.insert(name.clone(), 1);
174+
}
175+
self.entry_order.push((name, idx));
176+
}
177+
178+
// No doc test is run here because `RUSTFLAGS='--cfg hyper_unstable_ffi'`
179+
// is needed to compile. Once ffi is stablized `no_run` should be removed
180+
// here.
181+
/// This returns an iterator that provides header names and indexes
182+
/// in the original order recieved.
183+
///
184+
/// # Examples
185+
/// ```no_run
186+
/// use hyper::ext::OriginalHeaderOrder;
187+
/// use hyper::header::{HeaderName, HeaderValue, HeaderMap};
188+
///
189+
/// let mut h_order = OriginalHeaderOrder::default();
190+
/// let mut h_map = Headermap::new();
191+
///
192+
/// let name1 = b"Set-CookiE";
193+
/// let value1 = b"a=b";
194+
/// h_map.append(name1);
195+
/// h_order.append(name1);
196+
///
197+
/// let name2 = b"Content-Encoding";
198+
/// let value2 = b"gzip";
199+
/// h_map.append(name2, value2);
200+
/// h_order.append(name2);
201+
///
202+
/// let name3 = b"SET-COOKIE";
203+
/// let value3 = b"c=d";
204+
/// h_map.append(name3, value3);
205+
/// h_order.append(name3)
206+
///
207+
/// let mut iter = h_order.get_in_order()
208+
///
209+
/// let (name, idx) = iter.next();
210+
/// assert_eq!(b"a=b", h_map.get_all(name).nth(idx).unwrap());
211+
///
212+
/// let (name, idx) = iter.next();
213+
/// assert_eq!(b"gzip", h_map.get_all(name).nth(idx).unwrap());
214+
///
215+
/// let (name, idx) = iter.next();
216+
/// assert_eq!(b"c=d", h_map.get_all(name).nth(idx).unwrap());
217+
/// ```
218+
pub(crate) fn get_in_order(&self) -> impl Iterator<Item = &(HeaderName, usize)> {
219+
self.entry_order.iter()
220+
}
221+
}

src/ffi/client.rs

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,8 +93,7 @@ unsafe impl AsTaskType for hyper_clientconn {
9393
ffi_fn! {
9494
/// Creates a new set of HTTP clientconn options to be used in a handshake.
9595
fn hyper_clientconn_options_new() -> *mut hyper_clientconn_options {
96-
let mut builder = conn::Builder::new();
97-
builder.http1_preserve_header_case(true);
96+
let builder = conn::Builder::new();
9897

9998
Box::into_raw(Box::new(hyper_clientconn_options {
10099
builder,
@@ -103,6 +102,26 @@ ffi_fn! {
103102
} ?= std::ptr::null_mut()
104103
}
105104

105+
ffi_fn! {
106+
/// Set the whether or not header case is preserved.
107+
///
108+
/// Pass `0` to allow lowercase normalization (default), `1` to retain original case.
109+
fn hyper_clientconn_options_set_preserve_header_case(opts: *mut hyper_clientconn_options, enabled: c_int) {
110+
let opts = non_null! { &mut *opts ?= () };
111+
opts.builder.http1_preserve_header_case(enabled != 0);
112+
}
113+
}
114+
115+
ffi_fn! {
116+
/// Set the whether or not header order is preserved.
117+
///
118+
/// Pass `0` to allow reordering (default), `1` to retain original ordering.
119+
fn hyper_clientconn_options_set_preserve_header_order(opts: *mut hyper_clientconn_options, enabled: c_int) {
120+
let opts = non_null! { &mut *opts ?= () };
121+
opts.builder.http1_preserve_header_order(enabled != 0);
122+
}
123+
}
124+
106125
ffi_fn! {
107126
/// Free a `hyper_clientconn_options *`.
108127
fn hyper_clientconn_options_free(opts: *mut hyper_clientconn_options) {

0 commit comments

Comments
 (0)