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:
Liam Warfield
2022-04-23 10:05:37 -06:00
committed by GitHub
parent e1138d716d
commit 78de8914ea
9 changed files with 381 additions and 30 deletions

View File

@@ -355,6 +355,22 @@ void hyper_clientconn_free(struct hyper_clientconn *conn);
*/
struct hyper_clientconn_options *hyper_clientconn_options_new(void);
/*
Set the whether or not header case is preserved.
Pass `0` to allow lowercase normalization (default), `1` to retain original case.
*/
void hyper_clientconn_options_set_preserve_header_case(struct hyper_clientconn_options *opts,
int enabled);
/*
Set the whether or not header order is preserved.
Pass `0` to allow reordering (default), `1` to retain original ordering.
*/
void hyper_clientconn_options_set_preserve_header_order(struct hyper_clientconn_options *opts,
int enabled);
/*
Free a `hyper_clientconn_options *`.
*/

View File

@@ -156,6 +156,8 @@ pub struct Builder {
h1_writev: Option<bool>,
h1_title_case_headers: bool,
h1_preserve_header_case: bool,
#[cfg(feature = "ffi")]
h1_preserve_header_order: bool,
h1_read_buf_exact_size: Option<usize>,
h1_max_buf_size: Option<usize>,
#[cfg(feature = "ffi")]
@@ -558,6 +560,8 @@ impl Builder {
h1_parser_config: Default::default(),
h1_title_case_headers: false,
h1_preserve_header_case: false,
#[cfg(feature = "ffi")]
h1_preserve_header_order: false,
h1_max_buf_size: None,
#[cfg(feature = "ffi")]
h1_headers_raw: false,
@@ -704,6 +708,21 @@ impl Builder {
self
}
/// Set whether to support preserving original header order.
///
/// Currently, this will record the order in which headers are received, and store this
/// ordering in a private extension on the `Response`. It will also look for and use
/// such an extension in any provided `Request`.
///
/// Note that this setting does not affect HTTP/2.
///
/// Default is false.
#[cfg(feature = "ffi")]
pub fn http1_preserve_header_order(&mut self, enabled: bool) -> &mut Builder {
self.h1_preserve_header_order = enabled;
self
}
/// Sets the exact size of the read buffer to *always* use.
///
/// Note that setting this option unsets the `http1_max_buf_size` option.
@@ -948,9 +967,14 @@ impl Builder {
if opts.h1_title_case_headers {
conn.set_title_case_headers();
}
#[cfg(feature = "ffi")]
if opts.h1_preserve_header_case {
conn.set_preserve_header_case();
}
#[cfg(feature = "ffi")]
if opts.h1_preserve_header_order {
conn.set_preserve_header_order();
}
if opts.h09_responses {
conn.set_h09_responses();
}

View File

@@ -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()
}
}

View File

@@ -93,8 +93,7 @@ unsafe impl AsTaskType for hyper_clientconn {
ffi_fn! {
/// Creates a new set of HTTP clientconn options to be used in a handshake.
fn hyper_clientconn_options_new() -> *mut hyper_clientconn_options {
let mut builder = conn::Builder::new();
builder.http1_preserve_header_case(true);
let builder = conn::Builder::new();
Box::into_raw(Box::new(hyper_clientconn_options {
builder,
@@ -103,6 +102,26 @@ ffi_fn! {
} ?= std::ptr::null_mut()
}
ffi_fn! {
/// Set the whether or not header case is preserved.
///
/// Pass `0` to allow lowercase normalization (default), `1` to retain original case.
fn hyper_clientconn_options_set_preserve_header_case(opts: *mut hyper_clientconn_options, enabled: c_int) {
let opts = non_null! { &mut *opts ?= () };
opts.builder.http1_preserve_header_case(enabled != 0);
}
}
ffi_fn! {
/// Set the whether or not header order is preserved.
///
/// Pass `0` to allow reordering (default), `1` to retain original ordering.
fn hyper_clientconn_options_set_preserve_header_order(opts: *mut hyper_clientconn_options, enabled: c_int) {
let opts = non_null! { &mut *opts ?= () };
opts.builder.http1_preserve_header_order(enabled != 0);
}
}
ffi_fn! {
/// Free a `hyper_clientconn_options *`.
fn hyper_clientconn_options_free(opts: *mut hyper_clientconn_options) {

View File

@@ -6,7 +6,7 @@ use super::body::{hyper_body, hyper_buf};
use super::error::hyper_code;
use super::task::{hyper_task_return_type, AsTaskType};
use super::{UserDataPointer, HYPER_ITER_CONTINUE};
use crate::ext::HeaderCaseMap;
use crate::ext::{HeaderCaseMap, OriginalHeaderOrder};
use crate::header::{HeaderName, HeaderValue};
use crate::{Body, HeaderMap, Method, Request, Response, Uri};
@@ -22,6 +22,7 @@ pub struct hyper_response(pub(super) Response<Body>);
pub struct hyper_headers {
pub(super) headers: HeaderMap,
orig_casing: HeaderCaseMap,
orig_order: OriginalHeaderOrder,
}
#[derive(Debug)]
@@ -233,6 +234,7 @@ impl hyper_request {
if let Some(headers) = self.0.extensions_mut().remove::<hyper_headers>() {
*self.0.headers_mut() = headers.headers;
self.0.extensions_mut().insert(headers.orig_casing);
self.0.extensions_mut().insert(headers.orig_order);
}
}
}
@@ -348,9 +350,14 @@ impl hyper_response {
.extensions_mut()
.remove::<HeaderCaseMap>()
.unwrap_or_else(HeaderCaseMap::default);
let orig_order = resp
.extensions_mut()
.remove::<OriginalHeaderOrder>()
.unwrap_or_else(OriginalHeaderOrder::default);
resp.extensions_mut().insert(hyper_headers {
headers,
orig_casing,
orig_order,
});
hyper_response(resp)
@@ -404,6 +411,33 @@ ffi_fn! {
// and for each one, try to pair the originally cased name with the value.
//
// TODO: consider adding http::HeaderMap::entries() iterator
let mut ordered_iter = headers.orig_order.get_in_order().peekable();
if ordered_iter.peek().is_some() {
for (name, idx) in ordered_iter {
let (name_ptr, name_len) = if let Some(orig_name) = headers.orig_casing.get_all(name).nth(*idx) {
(orig_name.as_ref().as_ptr(), orig_name.as_ref().len())
} else {
(
name.as_str().as_bytes().as_ptr(),
name.as_str().as_bytes().len(),
)
};
let val_ptr;
let val_len;
if let Some(value) = headers.headers.get_all(name).iter().nth(*idx) {
val_ptr = value.as_bytes().as_ptr();
val_len = value.as_bytes().len();
} else {
// Stop iterating, something has gone wrong.
return;
}
if HYPER_ITER_CONTINUE != func(userdata, name_ptr, name_len, val_ptr, val_len) {
return;
}
}
} else {
for name in headers.headers.keys() {
let mut names = headers.orig_casing.get_all(name);
@@ -426,6 +460,7 @@ ffi_fn! {
}
}
}
}
}
ffi_fn! {
@@ -437,7 +472,8 @@ ffi_fn! {
match unsafe { raw_name_value(name, name_len, value, value_len) } {
Ok((name, value, orig_name)) => {
headers.headers.insert(&name, value);
headers.orig_casing.insert(name, orig_name);
headers.orig_casing.insert(name.clone(), orig_name.clone());
headers.orig_order.insert(name);
hyper_code::HYPERE_OK
}
Err(code) => code,
@@ -456,7 +492,8 @@ ffi_fn! {
match unsafe { raw_name_value(name, name_len, value, value_len) } {
Ok((name, value, orig_name)) => {
headers.headers.append(&name, value);
headers.orig_casing.append(name, orig_name);
headers.orig_casing.append(&name, orig_name.clone());
headers.orig_order.append(name);
hyper_code::HYPERE_OK
}
Err(code) => code,
@@ -469,6 +506,7 @@ impl Default for hyper_headers {
Self {
headers: Default::default(),
orig_casing: HeaderCaseMap::default(),
orig_order: OriginalHeaderOrder::default(),
}
}
}
@@ -555,4 +593,68 @@ mod tests {
HYPER_ITER_CONTINUE
}
}
#[cfg(all(feature = "http1", feature = "ffi"))]
#[test]
fn test_headers_foreach_order_preserved() {
let mut headers = hyper_headers::default();
let name1 = b"Set-CookiE";
let value1 = b"a=b";
hyper_headers_add(
&mut headers,
name1.as_ptr(),
name1.len(),
value1.as_ptr(),
value1.len(),
);
let name2 = b"Content-Encoding";
let value2 = b"gzip";
hyper_headers_add(
&mut headers,
name2.as_ptr(),
name2.len(),
value2.as_ptr(),
value2.len(),
);
let name3 = b"SET-COOKIE";
let value3 = b"c=d";
hyper_headers_add(
&mut headers,
name3.as_ptr(),
name3.len(),
value3.as_ptr(),
value3.len(),
);
let mut vec = Vec::<u8>::new();
hyper_headers_foreach(&headers, concat, &mut vec as *mut _ as *mut c_void);
println!("{}", std::str::from_utf8(&vec).unwrap());
assert_eq!(
vec,
b"Set-CookiE: a=b\r\nContent-Encoding: gzip\r\nSET-COOKIE: c=d\r\n"
);
extern "C" fn concat(
vec: *mut c_void,
name: *const u8,
name_len: usize,
value: *const u8,
value_len: usize,
) -> c_int {
unsafe {
let vec = &mut *(vec as *mut Vec<u8>);
let name = std::slice::from_raw_parts(name, name_len);
let value = std::slice::from_raw_parts(value, value_len);
vec.extend(name);
vec.extend(b": ");
vec.extend(value);
vec.extend(b"\r\n");
}
HYPER_ITER_CONTINUE
}
}
}

View File

@@ -58,6 +58,8 @@ where
#[cfg(all(feature = "server", feature = "runtime"))]
h1_header_read_timeout_running: false,
preserve_header_case: false,
#[cfg(feature = "ffi")]
preserve_header_order: false,
title_case_headers: false,
h09_responses: false,
#[cfg(feature = "ffi")]
@@ -111,6 +113,11 @@ where
self.state.preserve_header_case = true;
}
#[cfg(feature = "ffi")]
pub(crate) fn set_preserve_header_order(&mut self) {
self.state.preserve_header_order = true;
}
#[cfg(feature = "client")]
pub(crate) fn set_h09_responses(&mut self) {
self.state.h09_responses = true;
@@ -200,6 +207,8 @@ where
#[cfg(all(feature = "server", feature = "runtime"))]
h1_header_read_timeout_running: &mut self.state.h1_header_read_timeout_running,
preserve_header_case: self.state.preserve_header_case,
#[cfg(feature = "ffi")]
preserve_header_order: self.state.preserve_header_order,
h09_responses: self.state.h09_responses,
#[cfg(feature = "ffi")]
on_informational: &mut self.state.on_informational,
@@ -824,6 +833,8 @@ struct State {
#[cfg(all(feature = "server", feature = "runtime"))]
h1_header_read_timeout_running: bool,
preserve_header_case: bool,
#[cfg(feature = "ffi")]
preserve_header_order: bool,
title_case_headers: bool,
h09_responses: bool,
/// If set, called with each 1xx informational response received for

View File

@@ -1,17 +1,17 @@
use std::cmp;
use std::fmt;
#[cfg(all(feature = "server", feature = "runtime"))]
use std::future::Future;
use std::io::{self, IoSlice};
use std::marker::Unpin;
use std::mem::MaybeUninit;
#[cfg(all(feature = "server", feature = "runtime"))]
use std::future::Future;
#[cfg(all(feature = "server", feature = "runtime"))]
use std::time::Duration;
#[cfg(all(feature = "server", feature = "runtime"))]
use tokio::time::Instant;
use bytes::{Buf, BufMut, Bytes, BytesMut};
use tokio::io::{AsyncRead, AsyncWrite, ReadBuf};
#[cfg(all(feature = "server", feature = "runtime"))]
use tokio::time::Instant;
use tracing::{debug, trace};
use super::{Http1Transaction, ParseContext, ParsedMessage};
@@ -194,6 +194,8 @@ where
#[cfg(all(feature = "server", feature = "runtime"))]
h1_header_read_timeout_running: parse_ctx.h1_header_read_timeout_running,
preserve_header_case: parse_ctx.preserve_header_case,
#[cfg(feature = "ffi")]
preserve_header_order: parse_ctx.preserve_header_order,
h09_responses: parse_ctx.h09_responses,
#[cfg(feature = "ffi")]
on_informational: parse_ctx.on_informational,
@@ -208,9 +210,13 @@ where
{
*parse_ctx.h1_header_read_timeout_running = false;
if let Some(h1_header_read_timeout_fut) = parse_ctx.h1_header_read_timeout_fut {
if let Some(h1_header_read_timeout_fut) =
parse_ctx.h1_header_read_timeout_fut
{
// Reset the timer in order to avoid woken up when the timeout finishes
h1_header_read_timeout_fut.as_mut().reset(Instant::now() + Duration::from_secs(30 * 24 * 60 * 60));
h1_header_read_timeout_fut
.as_mut()
.reset(Instant::now() + Duration::from_secs(30 * 24 * 60 * 60));
}
}
return Poll::Ready(Ok(msg));
@@ -224,12 +230,14 @@ where
#[cfg(all(feature = "server", feature = "runtime"))]
if *parse_ctx.h1_header_read_timeout_running {
if let Some(h1_header_read_timeout_fut) = parse_ctx.h1_header_read_timeout_fut {
if Pin::new( h1_header_read_timeout_fut).poll(cx).is_ready() {
if let Some(h1_header_read_timeout_fut) =
parse_ctx.h1_header_read_timeout_fut
{
if Pin::new(h1_header_read_timeout_fut).poll(cx).is_ready() {
*parse_ctx.h1_header_read_timeout_running = false;
tracing::warn!("read header from client timeout");
return Poll::Ready(Err(crate::Error::new_header_timeout()))
return Poll::Ready(Err(crate::Error::new_header_timeout()));
}
}
}
@@ -734,6 +742,8 @@ mod tests {
#[cfg(feature = "runtime")]
h1_header_read_timeout_running: &mut false,
preserve_header_case: false,
#[cfg(feature = "ffi")]
preserve_header_order: false,
h09_responses: false,
#[cfg(feature = "ffi")]
on_informational: &mut None,
@@ -897,9 +907,7 @@ mod tests {
async fn write_buf_flatten() {
let _ = pretty_env_logger::try_init();
let mock = Mock::new()
.write(b"hello world, it's hyper!")
.build();
let mock = Mock::new().write(b"hello world, it's hyper!").build();
let mut buffered = Buffered::<_, Cursor<Vec<u8>>>::new(mock);
buffered.write_buf.set_strategy(WriteStrategy::Flatten);

View File

@@ -14,7 +14,7 @@ pub(crate) use self::conn::Conn;
pub(crate) use self::decode::Decoder;
pub(crate) use self::dispatch::Dispatcher;
pub(crate) use self::encode::{EncodedBuf, Encoder};
//TODO: move out of h1::io
//TODO: move out of h1::io
pub(crate) use self::io::MINIMUM_MAX_BUFFER_SIZE;
mod conn;
@@ -24,7 +24,6 @@ mod encode;
mod io;
mod role;
cfg_client! {
pub(crate) type ClientTransaction = role::Client;
}
@@ -84,6 +83,8 @@ pub(crate) struct ParseContext<'a> {
#[cfg(all(feature = "server", feature = "runtime"))]
h1_header_read_timeout_running: &'a mut bool,
preserve_header_case: bool,
#[cfg(feature = "ffi")]
preserve_header_order: bool,
h09_responses: bool,
#[cfg(feature = "ffi")]
on_informational: &'a mut Option<crate::ffi::OnInformational>,

View File

@@ -17,6 +17,8 @@ use crate::body::DecodedLength;
use crate::common::date;
use crate::error::Parse;
use crate::ext::HeaderCaseMap;
#[cfg(feature = "ffi")]
use crate::ext::OriginalHeaderOrder;
use crate::headers;
use crate::proto::h1::{
Encode, Encoder, Http1Transaction, ParseContext, ParseResult, ParsedMessage,
@@ -214,6 +216,13 @@ impl Http1Transaction for Server {
None
};
#[cfg(feature = "ffi")]
let mut header_order = if ctx.preserve_header_order {
Some(OriginalHeaderOrder::default())
} else {
None
};
let mut headers = ctx.cached_headers.take().unwrap_or_else(HeaderMap::new);
headers.reserve(headers_len);
@@ -290,6 +299,11 @@ impl Http1Transaction for Server {
header_case_map.append(&name, slice.slice(header.name.0..header.name.1));
}
#[cfg(feature = "ffi")]
if let Some(ref mut header_order) = header_order {
header_order.append(&name);
}
headers.append(name, value);
}
@@ -304,6 +318,11 @@ impl Http1Transaction for Server {
extensions.insert(header_case_map);
}
#[cfg(feature = "ffi")]
if let Some(header_order) = header_order {
extensions.insert(header_order);
}
*ctx.req_method = Some(subject.0.clone());
Ok(Some(ParsedMessage {
@@ -957,7 +976,10 @@ impl Http1Transaction for Client {
let mut slice = buf.split_to(len);
if ctx.h1_parser_config.obsolete_multiline_headers_in_responses_are_allowed() {
if ctx
.h1_parser_config
.obsolete_multiline_headers_in_responses_are_allowed()
{
for header in &headers_indices[..headers_len] {
// SAFETY: array is valid up to `headers_len`
let header = unsafe { &*header.as_ptr() };
@@ -981,6 +1003,13 @@ impl Http1Transaction for Client {
None
};
#[cfg(feature = "ffi")]
let mut header_order = if ctx.preserve_header_order {
Some(OriginalHeaderOrder::default())
} else {
None
};
headers.reserve(headers_len);
for header in &headers_indices[..headers_len] {
// SAFETY: array is valid up to `headers_len`
@@ -1003,6 +1032,11 @@ impl Http1Transaction for Client {
header_case_map.append(&name, slice.slice(header.name.0..header.name.1));
}
#[cfg(feature = "ffi")]
if let Some(ref mut header_order) = header_order {
header_order.append(&name);
}
headers.append(name, value);
}
@@ -1012,6 +1046,11 @@ impl Http1Transaction for Client {
extensions.insert(header_case_map);
}
#[cfg(feature = "ffi")]
if let Some(header_order) = header_order {
extensions.insert(header_order);
}
#[cfg(feature = "ffi")]
if let Some(reason) = reason {
extensions.insert(crate::ffi::ReasonPhrase(reason));
@@ -1481,6 +1520,8 @@ mod tests {
#[cfg(feature = "runtime")]
h1_header_read_timeout_running: &mut false,
preserve_header_case: false,
#[cfg(feature = "ffi")]
preserve_header_order: false,
h09_responses: false,
#[cfg(feature = "ffi")]
on_informational: &mut None,
@@ -1514,6 +1555,8 @@ mod tests {
#[cfg(feature = "runtime")]
h1_header_read_timeout_running: &mut false,
preserve_header_case: false,
#[cfg(feature = "ffi")]
preserve_header_order: false,
h09_responses: false,
#[cfg(feature = "ffi")]
on_informational: &mut None,
@@ -1542,6 +1585,8 @@ mod tests {
#[cfg(feature = "runtime")]
h1_header_read_timeout_running: &mut false,
preserve_header_case: false,
#[cfg(feature = "ffi")]
preserve_header_order: false,
h09_responses: false,
#[cfg(feature = "ffi")]
on_informational: &mut None,
@@ -1568,6 +1613,8 @@ mod tests {
#[cfg(feature = "runtime")]
h1_header_read_timeout_running: &mut false,
preserve_header_case: false,
#[cfg(feature = "ffi")]
preserve_header_order: false,
h09_responses: true,
#[cfg(feature = "ffi")]
on_informational: &mut None,
@@ -1596,6 +1643,8 @@ mod tests {
#[cfg(feature = "runtime")]
h1_header_read_timeout_running: &mut false,
preserve_header_case: false,
#[cfg(feature = "ffi")]
preserve_header_order: false,
h09_responses: false,
#[cfg(feature = "ffi")]
on_informational: &mut None,
@@ -1628,6 +1677,8 @@ mod tests {
#[cfg(feature = "runtime")]
h1_header_read_timeout_running: &mut false,
preserve_header_case: false,
#[cfg(feature = "ffi")]
preserve_header_order: false,
h09_responses: false,
#[cfg(feature = "ffi")]
on_informational: &mut None,
@@ -1657,6 +1708,8 @@ mod tests {
#[cfg(feature = "runtime")]
h1_header_read_timeout_running: &mut false,
preserve_header_case: false,
#[cfg(feature = "ffi")]
preserve_header_order: false,
h09_responses: false,
#[cfg(feature = "ffi")]
on_informational: &mut None,
@@ -1681,6 +1734,8 @@ mod tests {
#[cfg(feature = "runtime")]
h1_header_read_timeout_running: &mut false,
preserve_header_case: true,
#[cfg(feature = "ffi")]
preserve_header_order: false,
h09_responses: false,
#[cfg(feature = "ffi")]
on_informational: &mut None,
@@ -1726,6 +1781,8 @@ mod tests {
#[cfg(feature = "runtime")]
h1_header_read_timeout_running: &mut false,
preserve_header_case: false,
#[cfg(feature = "ffi")]
preserve_header_order: false,
h09_responses: false,
#[cfg(feature = "ffi")]
on_informational: &mut None,
@@ -1752,6 +1809,8 @@ mod tests {
#[cfg(feature = "runtime")]
h1_header_read_timeout_running: &mut false,
preserve_header_case: false,
#[cfg(feature = "ffi")]
preserve_header_order: false,
h09_responses: false,
#[cfg(feature = "ffi")]
on_informational: &mut None,
@@ -1987,6 +2046,8 @@ mod tests {
#[cfg(feature = "runtime")]
h1_header_read_timeout_running: &mut false,
preserve_header_case: false,
#[cfg(feature = "ffi")]
preserve_header_order: false,
h09_responses: false,
#[cfg(feature = "ffi")]
on_informational: &mut None,
@@ -2013,6 +2074,8 @@ mod tests {
#[cfg(feature = "runtime")]
h1_header_read_timeout_running: &mut false,
preserve_header_case: false,
#[cfg(feature = "ffi")]
preserve_header_order: false,
h09_responses: false,
#[cfg(feature = "ffi")]
on_informational: &mut None,
@@ -2039,6 +2102,8 @@ mod tests {
#[cfg(feature = "runtime")]
h1_header_read_timeout_running: &mut false,
preserve_header_case: false,
#[cfg(feature = "ffi")]
preserve_header_order: false,
h09_responses: false,
#[cfg(feature = "ffi")]
on_informational: &mut None,
@@ -2542,6 +2607,8 @@ mod tests {
#[cfg(feature = "runtime")]
h1_header_read_timeout_running: &mut false,
preserve_header_case: false,
#[cfg(feature = "ffi")]
preserve_header_order: false,
h09_responses: false,
#[cfg(feature = "ffi")]
on_informational: &mut None,
@@ -2632,6 +2699,8 @@ mod tests {
#[cfg(feature = "runtime")]
h1_header_read_timeout_running: &mut false,
preserve_header_case: false,
#[cfg(feature = "ffi")]
preserve_header_order: false,
h09_responses: false,
#[cfg(feature = "ffi")]
on_informational: &mut None,
@@ -2678,6 +2747,8 @@ mod tests {
#[cfg(feature = "runtime")]
h1_header_read_timeout_running: &mut false,
preserve_header_case: false,
#[cfg(feature = "ffi")]
preserve_header_order: false,
h09_responses: false,
#[cfg(feature = "ffi")]
on_informational: &mut None,