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:
@@ -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 *`.
|
||||
*/
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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,26 +411,54 @@ ffi_fn! {
|
||||
// and for each one, try to pair the originally cased name with the value.
|
||||
//
|
||||
// TODO: consider adding http::HeaderMap::entries() iterator
|
||||
for name in headers.headers.keys() {
|
||||
let mut names = headers.orig_casing.get_all(name);
|
||||
|
||||
for value in headers.headers.get_all(name) {
|
||||
let (name_ptr, name_len) = if let Some(orig_name) = names.next() {
|
||||
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(),
|
||||
name.as_str().as_bytes().as_ptr(),
|
||||
name.as_str().as_bytes().len(),
|
||||
)
|
||||
};
|
||||
|
||||
let val_ptr = value.as_bytes().as_ptr();
|
||||
let val_len = value.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);
|
||||
|
||||
for value in headers.headers.get_all(name) {
|
||||
let (name_ptr, name_len) = if let Some(orig_name) = names.next() {
|
||||
(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 = value.as_bytes().as_ptr();
|
||||
let val_len = value.as_bytes().len();
|
||||
|
||||
if HYPER_ITER_CONTINUE != func(userdata, name_ptr, name_len, val_ptr, val_len) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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>,
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user