Skip to content

Commit f4fec5c

Browse files
liamwarfieldLiam Warfield
authored andcommitted
feature(ffi): Added connection option to preserve header order.
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. - ffi::http_types::hyper_headers_foreach_ordered : Iterates the headers in the order the were recieved, passing each name and value pair to the callback. 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 ISSUE: #2780
1 parent 740654e commit f4fec5c

File tree

9 files changed

+327
-20
lines changed

9 files changed

+327
-20
lines changed

capi/include/hyper.h

Lines changed: 28 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
*/
@@ -595,6 +611,18 @@ void hyper_headers_foreach(const struct hyper_headers *headers,
595611
hyper_headers_foreach_callback func,
596612
void *userdata);
597613

614+
/*
615+
Iterates the headers in the order the were recieved, passing each name and value pair to the callback.
616+
617+
The `userdata` pointer is also passed to the callback.
618+
619+
The callback should return `HYPER_ITER_CONTINUE` to keep iterating, or
620+
`HYPER_ITER_BREAK` to stop.
621+
*/
622+
void hyper_headers_foreach_ordered(const struct hyper_headers *headers,
623+
hyper_headers_foreach_callback func,
624+
void *userdata);
625+
598626
/*
599627
Sets the header with the provided name to the provided value.
600628

src/client/conn.rs

Lines changed: 21 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,7 @@ impl Builder {
558560
h1_parser_config: Default::default(),
559561
h1_title_case_headers: false,
560562
h1_preserve_header_case: false,
563+
h1_preserve_header_order: false,
561564
h1_max_buf_size: None,
562565
#[cfg(feature = "ffi")]
563566
h1_headers_raw: false,
@@ -704,6 +707,21 @@ impl Builder {
704707
self
705708
}
706709

710+
/// Set whether to support preserving original header order.
711+
///
712+
/// Currently, this will record the order in which headers are received, and store this
713+
/// ordering in a private extension on the `Response`. It will also look for and use
714+
/// such an extension in any provided `Request`.
715+
///
716+
/// Note that this setting does not affect HTTP/2.
717+
///
718+
/// Default is false.
719+
#[cfg(feature = "ffi")]
720+
pub fn http1_preserve_header_order(&mut self, enabled: bool) -> &mut Builder {
721+
self.h1_preserve_header_order = enabled;
722+
self
723+
}
724+
707725
/// Sets the exact size of the read buffer to *always* use.
708726
///
709727
/// Note that setting this option unsets the `http1_max_buf_size` option.
@@ -951,6 +969,9 @@ impl Builder {
951969
if opts.h1_preserve_header_case {
952970
conn.set_preserve_header_case();
953971
}
972+
if opts.h1_preserve_header_order {
973+
conn.set_preserve_header_order();
974+
}
954975
if opts.h09_responses {
955976
conn.set_h09_responses();
956977
}

src/ext.rs

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
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+
use std::collections::HashMap;
79
#[cfg(feature = "http2")]
810
use std::fmt;
911

@@ -120,3 +122,49 @@ impl HeaderCaseMap {
120122
self.0.append(name, orig);
121123
}
122124
}
125+
126+
#[derive(Clone, Debug)]
127+
/// Hashmap<Headername, numheaders with that name>
128+
pub(crate) struct OriginalHeaderOrder(HashMap<HeaderName, usize>, Vec<(HeaderName, usize)>);
129+
130+
#[cfg(all(feature = "http1", feature = "ffi"))]
131+
impl OriginalHeaderOrder {
132+
pub(crate) fn default() -> Self {
133+
Self(Default::default(), Default::default())
134+
}
135+
136+
#[cfg(any(test, feature = "ffi"))]
137+
pub(crate) fn insert(&mut self, name: HeaderName) {
138+
if !self.0.contains_key(&name) {
139+
let idx = 0;
140+
self.0.insert(name.clone(), 1);
141+
self.1.push((name, idx));
142+
}
143+
// replacing an already existing element does not
144+
// change ordering, so we only care if its the first
145+
// header name encountered
146+
}
147+
148+
pub(crate) fn append<N>(&mut self, name: N)
149+
where
150+
N: IntoHeaderName + Into<HeaderName> + Clone,
151+
{
152+
let name: HeaderName = name.into();
153+
let idx;
154+
if self.0.contains_key(&name) {
155+
idx = self.0[&name];
156+
*self.0.get_mut(&name).unwrap() += 1;
157+
} else {
158+
idx = 0;
159+
self.0.insert(name.clone(), 1);
160+
}
161+
self.1.push((name, idx));
162+
}
163+
164+
/// This returns an iterator that provides header names and indexes
165+
/// in the original order recieved.
166+
#[cfg(any(test, feature = "ffi"))]
167+
pub(crate) fn get_in_order(&self) -> impl Iterator<Item = &(HeaderName, usize)> {
168+
self.1.iter()
169+
}
170+
}

src/ffi/client.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ ffi_fn! {
9595
fn hyper_clientconn_options_new() -> *mut hyper_clientconn_options {
9696
let mut builder = conn::Builder::new();
9797
builder.http1_preserve_header_case(true);
98+
builder.http1_preserve_header_order(true);
9899

99100
Box::into_raw(Box::new(hyper_clientconn_options {
100101
builder,
@@ -103,6 +104,26 @@ ffi_fn! {
103104
} ?= std::ptr::null_mut()
104105
}
105106

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

src/ffi/http_types.rs

Lines changed: 105 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use super::body::{hyper_body, hyper_buf};
66
use super::error::hyper_code;
77
use super::task::{hyper_task_return_type, AsTaskType};
88
use super::{UserDataPointer, HYPER_ITER_CONTINUE};
9-
use crate::ext::HeaderCaseMap;
9+
use crate::ext::{HeaderCaseMap, OriginalHeaderOrder};
1010
use crate::header::{HeaderName, HeaderValue};
1111
use crate::{Body, HeaderMap, Method, Request, Response, Uri};
1212

@@ -22,6 +22,7 @@ pub struct hyper_response(pub(super) Response<Body>);
2222
pub struct hyper_headers {
2323
pub(super) headers: HeaderMap,
2424
orig_casing: HeaderCaseMap,
25+
orig_order: OriginalHeaderOrder,
2526
}
2627

2728
#[derive(Debug)]
@@ -233,6 +234,7 @@ impl hyper_request {
233234
if let Some(headers) = self.0.extensions_mut().remove::<hyper_headers>() {
234235
*self.0.headers_mut() = headers.headers;
235236
self.0.extensions_mut().insert(headers.orig_casing);
237+
self.0.extensions_mut().insert(headers.orig_order);
236238
}
237239
}
238240
}
@@ -348,9 +350,14 @@ impl hyper_response {
348350
.extensions_mut()
349351
.remove::<HeaderCaseMap>()
350352
.unwrap_or_else(HeaderCaseMap::default);
353+
let orig_order = resp
354+
.extensions_mut()
355+
.remove::<OriginalHeaderOrder>()
356+
.unwrap_or_else(OriginalHeaderOrder::default);
351357
resp.extensions_mut().insert(hyper_headers {
352358
headers,
353359
orig_casing,
360+
orig_order,
354361
});
355362

356363
hyper_response(resp)
@@ -428,6 +435,37 @@ ffi_fn! {
428435
}
429436
}
430437

438+
ffi_fn! {
439+
/// Iterates the headers in the order the were recieved, passing each name and value pair to the callback.
440+
///
441+
/// The `userdata` pointer is also passed to the callback.
442+
///
443+
/// The callback should return `HYPER_ITER_CONTINUE` to keep iterating, or
444+
/// `HYPER_ITER_BREAK` to stop.
445+
fn hyper_headers_foreach_ordered(headers: *const hyper_headers, func: hyper_headers_foreach_callback, userdata: *mut c_void) {
446+
let headers = non_null!(&*headers ?= ());
447+
// For each header name/value pair, there may be a value in the casemap
448+
// that corresponds to the HeaderValue. So, we iterator all the keys,
449+
// and for each one, try to pair the originally cased name with the value.
450+
//
451+
// TODO: consider adding http::HeaderMap::entries() iterator
452+
for (name, idx) in headers.orig_order.get_in_order() {
453+
let orig_name = headers.orig_casing.get_all(&name).nth(*idx).unwrap();
454+
let value = headers.headers.get_all(name).iter().nth(*idx).unwrap();
455+
456+
let name_ptr = orig_name.as_ref().as_ptr();
457+
let name_len = orig_name.as_ref().len();
458+
459+
let val_ptr = value.as_bytes().as_ptr();
460+
let val_len = value.as_bytes().len();
461+
462+
if HYPER_ITER_CONTINUE != func(userdata, name_ptr, name_len, val_ptr, val_len) {
463+
return;
464+
}
465+
}
466+
}
467+
}
468+
431469
ffi_fn! {
432470
/// Sets the header with the provided name to the provided value.
433471
///
@@ -437,7 +475,8 @@ ffi_fn! {
437475
match unsafe { raw_name_value(name, name_len, value, value_len) } {
438476
Ok((name, value, orig_name)) => {
439477
headers.headers.insert(&name, value);
440-
headers.orig_casing.insert(name, orig_name);
478+
headers.orig_casing.insert(name.clone(), orig_name.clone());
479+
headers.orig_order.insert(name);
441480
hyper_code::HYPERE_OK
442481
}
443482
Err(code) => code,
@@ -456,7 +495,8 @@ ffi_fn! {
456495
match unsafe { raw_name_value(name, name_len, value, value_len) } {
457496
Ok((name, value, orig_name)) => {
458497
headers.headers.append(&name, value);
459-
headers.orig_casing.append(name, orig_name);
498+
headers.orig_casing.append(&name, orig_name.clone());
499+
headers.orig_order.append(name);
460500
hyper_code::HYPERE_OK
461501
}
462502
Err(code) => code,
@@ -469,6 +509,7 @@ impl Default for hyper_headers {
469509
Self {
470510
headers: Default::default(),
471511
orig_casing: HeaderCaseMap::default(),
512+
orig_order: OriginalHeaderOrder::default(),
472513
}
473514
}
474515
}
@@ -555,4 +596,65 @@ mod tests {
555596
HYPER_ITER_CONTINUE
556597
}
557598
}
599+
600+
#[cfg(all(feature = "http1", feature = "ffi"))]
601+
#[test]
602+
fn test_headers_foreach_order_preserved() {
603+
let mut headers = hyper_headers::default();
604+
605+
let name1 = b"Set-CookiE";
606+
let value1 = b"a=b";
607+
hyper_headers_add(
608+
&mut headers,
609+
name1.as_ptr(),
610+
name1.len(),
611+
value1.as_ptr(),
612+
value1.len(),
613+
);
614+
615+
let name2 = b"Content-Encoding";
616+
let value2 = b"gzip";
617+
hyper_headers_add(
618+
&mut headers,
619+
name2.as_ptr(),
620+
name2.len(),
621+
value2.as_ptr(),
622+
value2.len(),
623+
);
624+
625+
let name3 = b"SET-COOKIE";
626+
let value3 = b"c=d";
627+
hyper_headers_add(
628+
&mut headers,
629+
name3.as_ptr(),
630+
name3.len(),
631+
value3.as_ptr(),
632+
value3.len(),
633+
);
634+
635+
let mut vec = Vec::<u8>::new();
636+
hyper_headers_foreach_ordered(&headers, concat, &mut vec as *mut _ as *mut c_void);
637+
638+
println!("{}", std::str::from_utf8(&vec).unwrap());
639+
assert_eq!(vec, b"Set-CookiE: a=b\r\nContent-Encoding: gzip\r\nSET-COOKIE: c=d\r\n");
640+
641+
extern "C" fn concat(
642+
vec: *mut c_void,
643+
name: *const u8,
644+
name_len: usize,
645+
value: *const u8,
646+
value_len: usize,
647+
) -> c_int {
648+
unsafe {
649+
let vec = &mut *(vec as *mut Vec<u8>);
650+
let name = std::slice::from_raw_parts(name, name_len);
651+
let value = std::slice::from_raw_parts(value, value_len);
652+
vec.extend(name);
653+
vec.extend(b": ");
654+
vec.extend(value);
655+
vec.extend(b"\r\n");
656+
}
657+
HYPER_ITER_CONTINUE
658+
}
659+
}
558660
}

src/proto/h1/conn.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,8 @@ where
5858
#[cfg(all(feature = "server", feature = "runtime"))]
5959
h1_header_read_timeout_running: false,
6060
preserve_header_case: false,
61+
#[cfg(feature = "ffi")]
62+
preserve_header_order: false,
6163
title_case_headers: false,
6264
h09_responses: false,
6365
#[cfg(feature = "ffi")]
@@ -111,6 +113,11 @@ where
111113
self.state.preserve_header_case = true;
112114
}
113115

116+
#[cfg(feature = "ffi")]
117+
pub(crate) fn set_preserve_header_order(&mut self) {
118+
self.state.preserve_header_order = true;
119+
}
120+
114121
#[cfg(feature = "client")]
115122
pub(crate) fn set_h09_responses(&mut self) {
116123
self.state.h09_responses = true;
@@ -200,6 +207,8 @@ where
200207
#[cfg(all(feature = "server", feature = "runtime"))]
201208
h1_header_read_timeout_running: &mut self.state.h1_header_read_timeout_running,
202209
preserve_header_case: self.state.preserve_header_case,
210+
#[cfg(feature = "ffi")]
211+
preserve_header_order: self.state.preserve_header_order,
203212
h09_responses: self.state.h09_responses,
204213
#[cfg(feature = "ffi")]
205214
on_informational: &mut self.state.on_informational,
@@ -824,6 +833,8 @@ struct State {
824833
#[cfg(all(feature = "server", feature = "runtime"))]
825834
h1_header_read_timeout_running: bool,
826835
preserve_header_case: bool,
836+
#[cfg(feature = "ffi")]
837+
preserve_header_order: bool,
827838
title_case_headers: bool,
828839
h09_responses: bool,
829840
/// If set, called with each 1xx informational response received for

0 commit comments

Comments
 (0)