refactor(ffi): Add HeaderCaseMap preserving http1 header casing
This commit is contained in:
@@ -228,7 +228,7 @@ int main(int argc, char *argv[]) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
hyper_headers *req_headers = hyper_request_headers(req);
|
hyper_headers *req_headers = hyper_request_headers(req);
|
||||||
hyper_headers_set(req_headers, STR_ARG("host"), STR_ARG(host));
|
hyper_headers_set(req_headers, STR_ARG("Host"), STR_ARG(host));
|
||||||
|
|
||||||
// Send it!
|
// Send it!
|
||||||
hyper_task *send = hyper_clientconn_send(client, req);
|
hyper_task *send = hyper_clientconn_send(client, req);
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ pub(crate) struct UserBody {
|
|||||||
type hyper_body_foreach_callback = extern "C" fn(*mut c_void, *const hyper_buf) -> c_int;
|
type hyper_body_foreach_callback = extern "C" fn(*mut c_void, *const hyper_buf) -> c_int;
|
||||||
|
|
||||||
type hyper_body_data_callback =
|
type hyper_body_data_callback =
|
||||||
extern "C" fn(*mut c_void, *mut hyper_context, *mut *mut hyper_buf) -> c_int;
|
extern "C" fn(*mut c_void, *mut hyper_context<'_>, *mut *mut hyper_buf) -> c_int;
|
||||||
|
|
||||||
ffi_fn! {
|
ffi_fn! {
|
||||||
/// Create a new "empty" body.
|
/// Create a new "empty" body.
|
||||||
|
|||||||
@@ -67,11 +67,15 @@ ffi_fn! {
|
|||||||
return std::ptr::null_mut();
|
return std::ptr::null_mut();
|
||||||
}
|
}
|
||||||
|
|
||||||
let req = unsafe { Box::from_raw(req) };
|
let mut req = unsafe { Box::from_raw(req) };
|
||||||
|
|
||||||
|
// Update request with original-case map of headers
|
||||||
|
req.finalize_request();
|
||||||
|
|
||||||
let fut = unsafe { &mut *conn }.tx.send_request(req.0);
|
let fut = unsafe { &mut *conn }.tx.send_request(req.0);
|
||||||
|
|
||||||
let fut = async move {
|
let fut = async move {
|
||||||
fut.await.map(hyper_response)
|
fut.await.map(hyper_response::wrap)
|
||||||
};
|
};
|
||||||
|
|
||||||
Box::into_raw(Task::boxed(fut))
|
Box::into_raw(Task::boxed(fut))
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ impl hyper_error {
|
|||||||
ErrorKind::IncompleteMessage => hyper_code::HYPERE_UNEXPECTED_EOF,
|
ErrorKind::IncompleteMessage => hyper_code::HYPERE_UNEXPECTED_EOF,
|
||||||
ErrorKind::User(User::AbortedByCallback) => hyper_code::HYPERE_ABORTED_BY_CALLBACK,
|
ErrorKind::User(User::AbortedByCallback) => hyper_code::HYPERE_ABORTED_BY_CALLBACK,
|
||||||
// TODO: add more variants
|
// TODO: add more variants
|
||||||
_ => hyper_code::HYPERE_ERROR
|
_ => hyper_code::HYPERE_ERROR,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
use bytes::Bytes;
|
||||||
use libc::{c_int, size_t};
|
use libc::{c_int, size_t};
|
||||||
use std::ffi::c_void;
|
use std::ffi::c_void;
|
||||||
|
|
||||||
@@ -8,13 +9,21 @@ use super::HYPER_ITER_CONTINUE;
|
|||||||
use crate::header::{HeaderName, HeaderValue};
|
use crate::header::{HeaderName, HeaderValue};
|
||||||
use crate::{Body, HeaderMap, Method, Request, Response, Uri};
|
use crate::{Body, HeaderMap, Method, Request, Response, Uri};
|
||||||
|
|
||||||
// ===== impl Request =====
|
|
||||||
|
|
||||||
pub struct hyper_request(pub(super) Request<Body>);
|
pub struct hyper_request(pub(super) Request<Body>);
|
||||||
|
|
||||||
pub struct hyper_response(pub(super) Response<Body>);
|
pub struct hyper_response(pub(super) Response<Body>);
|
||||||
|
|
||||||
pub struct hyper_headers(pub(super) HeaderMap);
|
#[derive(Default)]
|
||||||
|
pub struct hyper_headers {
|
||||||
|
pub(super) headers: HeaderMap,
|
||||||
|
orig_casing: HeaderCaseMap,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Will probably be moved to `hyper::ext::http1`
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
pub(crate) struct HeaderCaseMap(HeaderMap<Bytes>);
|
||||||
|
|
||||||
|
// ===== impl hyper_request =====
|
||||||
|
|
||||||
ffi_fn! {
|
ffi_fn! {
|
||||||
/// Construct a new HTTP request.
|
/// Construct a new HTTP request.
|
||||||
@@ -96,7 +105,7 @@ ffi_fn! {
|
|||||||
/// This is not an owned reference, so it should not be accessed after the
|
/// This is not an owned reference, so it should not be accessed after the
|
||||||
/// `hyper_request` has been consumed.
|
/// `hyper_request` has been consumed.
|
||||||
fn hyper_request_headers(req: *mut hyper_request) -> *mut hyper_headers {
|
fn hyper_request_headers(req: *mut hyper_request) -> *mut hyper_headers {
|
||||||
hyper_headers::wrap(unsafe { &mut *req }.0.headers_mut())
|
hyper_headers::get_or_default(unsafe { &mut *req }.0.extensions_mut())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -114,7 +123,16 @@ ffi_fn! {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ===== impl Response =====
|
impl hyper_request {
|
||||||
|
pub(super) fn finalize_request(&mut self) {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== impl hyper_response =====
|
||||||
|
|
||||||
ffi_fn! {
|
ffi_fn! {
|
||||||
/// Free an HTTP response after using it.
|
/// Free an HTTP response after using it.
|
||||||
@@ -159,7 +177,7 @@ ffi_fn! {
|
|||||||
/// This is not an owned reference, so it should not be accessed after the
|
/// This is not an owned reference, so it should not be accessed after the
|
||||||
/// `hyper_response` has been freed.
|
/// `hyper_response` has been freed.
|
||||||
fn hyper_response_headers(resp: *mut hyper_response) -> *mut hyper_headers {
|
fn hyper_response_headers(resp: *mut hyper_response) -> *mut hyper_headers {
|
||||||
hyper_headers::wrap(unsafe { &mut *resp }.0.headers_mut())
|
hyper_headers::get_or_default(unsafe { &mut *resp }.0.extensions_mut())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -173,6 +191,22 @@ ffi_fn! {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl hyper_response {
|
||||||
|
pub(super) fn wrap(mut resp: Response<Body>) -> hyper_response {
|
||||||
|
let headers = std::mem::take(resp.headers_mut());
|
||||||
|
let orig_casing = resp
|
||||||
|
.extensions_mut()
|
||||||
|
.remove::<HeaderCaseMap>()
|
||||||
|
.unwrap_or_default();
|
||||||
|
resp.extensions_mut().insert(hyper_headers {
|
||||||
|
headers,
|
||||||
|
orig_casing,
|
||||||
|
});
|
||||||
|
|
||||||
|
hyper_response(resp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
unsafe impl AsTaskType for hyper_response {
|
unsafe impl AsTaskType for hyper_response {
|
||||||
fn as_task_type(&self) -> hyper_task_return_type {
|
fn as_task_type(&self) -> hyper_task_return_type {
|
||||||
hyper_task_return_type::HYPER_TASK_RESPONSE
|
hyper_task_return_type::HYPER_TASK_RESPONSE
|
||||||
@@ -185,9 +219,15 @@ type hyper_headers_foreach_callback =
|
|||||||
extern "C" fn(*mut c_void, *const u8, size_t, *const u8, size_t) -> c_int;
|
extern "C" fn(*mut c_void, *const u8, size_t, *const u8, size_t) -> c_int;
|
||||||
|
|
||||||
impl hyper_headers {
|
impl hyper_headers {
|
||||||
pub(crate) fn wrap(cx: &mut HeaderMap) -> &mut hyper_headers {
|
pub(super) fn get_or_default(ext: &mut http::Extensions) -> &mut hyper_headers {
|
||||||
// A struct with only one field has the same layout as that field.
|
if let None = ext.get_mut::<hyper_headers>() {
|
||||||
unsafe { std::mem::transmute::<&mut HeaderMap, &mut hyper_headers>(cx) }
|
ext.insert(hyper_headers {
|
||||||
|
headers: Default::default(),
|
||||||
|
orig_casing: Default::default(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
ext.get_mut::<hyper_headers>().unwrap()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -199,14 +239,31 @@ ffi_fn! {
|
|||||||
/// The callback should return `HYPER_ITER_CONTINUE` to keep iterating, or
|
/// The callback should return `HYPER_ITER_CONTINUE` to keep iterating, or
|
||||||
/// `HYPER_ITER_BREAK` to stop.
|
/// `HYPER_ITER_BREAK` to stop.
|
||||||
fn hyper_headers_foreach(headers: *const hyper_headers, func: hyper_headers_foreach_callback, userdata: *mut c_void) {
|
fn hyper_headers_foreach(headers: *const hyper_headers, func: hyper_headers_foreach_callback, userdata: *mut c_void) {
|
||||||
for (name, value) in unsafe { &*headers }.0.iter() {
|
let headers = unsafe { &*headers };
|
||||||
let name_ptr = name.as_str().as_bytes().as_ptr();
|
// For each header name/value pair, there may be a value in the casemap
|
||||||
let name_len = name.as_str().as_bytes().len();
|
// that corresponds to the HeaderValue. So, we iterator all the keys,
|
||||||
let val_ptr = value.as_bytes().as_ptr();
|
// and for each one, try to pair the originally cased name with the value.
|
||||||
let val_len = value.as_bytes().len();
|
//
|
||||||
|
// TODO: consider adding http::HeaderMap::entries() iterator
|
||||||
|
for name in headers.headers.keys() {
|
||||||
|
let mut names = headers.orig_casing.get_all(name).iter();
|
||||||
|
|
||||||
if HYPER_ITER_CONTINUE != func(userdata, name_ptr, name_len, val_ptr, val_len) {
|
for value in headers.headers.get_all(name) {
|
||||||
break;
|
let (name_ptr, name_len) = if let Some(orig_name) = names.next() {
|
||||||
|
(orig_name.as_ptr(), orig_name.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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -219,8 +276,9 @@ ffi_fn! {
|
|||||||
fn hyper_headers_set(headers: *mut hyper_headers, name: *const u8, name_len: size_t, value: *const u8, value_len: size_t) -> hyper_code {
|
fn hyper_headers_set(headers: *mut hyper_headers, name: *const u8, name_len: size_t, value: *const u8, value_len: size_t) -> hyper_code {
|
||||||
let headers = unsafe { &mut *headers };
|
let headers = unsafe { &mut *headers };
|
||||||
match unsafe { raw_name_value(name, name_len, value, value_len) } {
|
match unsafe { raw_name_value(name, name_len, value, value_len) } {
|
||||||
Ok((name, value)) => {
|
Ok((name, value, orig_name)) => {
|
||||||
headers.0.insert(name, value);
|
headers.headers.insert(&name, value);
|
||||||
|
headers.orig_casing.insert(name, orig_name);
|
||||||
hyper_code::HYPERE_OK
|
hyper_code::HYPERE_OK
|
||||||
}
|
}
|
||||||
Err(code) => code,
|
Err(code) => code,
|
||||||
@@ -237,8 +295,9 @@ ffi_fn! {
|
|||||||
let headers = unsafe { &mut *headers };
|
let headers = unsafe { &mut *headers };
|
||||||
|
|
||||||
match unsafe { raw_name_value(name, name_len, value, value_len) } {
|
match unsafe { raw_name_value(name, name_len, value, value_len) } {
|
||||||
Ok((name, value)) => {
|
Ok((name, value, orig_name)) => {
|
||||||
headers.0.append(name, value);
|
headers.headers.append(&name, value);
|
||||||
|
headers.orig_casing.append(name, orig_name);
|
||||||
hyper_code::HYPERE_OK
|
hyper_code::HYPERE_OK
|
||||||
}
|
}
|
||||||
Err(code) => code,
|
Err(code) => code,
|
||||||
@@ -251,8 +310,9 @@ unsafe fn raw_name_value(
|
|||||||
name_len: size_t,
|
name_len: size_t,
|
||||||
value: *const u8,
|
value: *const u8,
|
||||||
value_len: size_t,
|
value_len: size_t,
|
||||||
) -> Result<(HeaderName, HeaderValue), hyper_code> {
|
) -> Result<(HeaderName, HeaderValue, Bytes), hyper_code> {
|
||||||
let name = std::slice::from_raw_parts(name, name_len);
|
let name = std::slice::from_raw_parts(name, name_len);
|
||||||
|
let orig_name = Bytes::copy_from_slice(name);
|
||||||
let name = match HeaderName::from_bytes(name) {
|
let name = match HeaderName::from_bytes(name) {
|
||||||
Ok(name) => name,
|
Ok(name) => name,
|
||||||
Err(_) => return Err(hyper_code::HYPERE_INVALID_ARG),
|
Err(_) => return Err(hyper_code::HYPERE_INVALID_ARG),
|
||||||
@@ -263,5 +323,78 @@ unsafe fn raw_name_value(
|
|||||||
Err(_) => return Err(hyper_code::HYPERE_INVALID_ARG),
|
Err(_) => return Err(hyper_code::HYPERE_INVALID_ARG),
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok((name, value))
|
Ok((name, value, orig_name))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== impl HeaderCaseMap =====
|
||||||
|
|
||||||
|
impl HeaderCaseMap {
|
||||||
|
pub(crate) fn get_all(&self, name: &HeaderName) -> http::header::GetAll<'_, Bytes> {
|
||||||
|
self.0.get_all(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn insert(&mut self, name: HeaderName, orig: Bytes) {
|
||||||
|
self.0.insert(name, orig);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn append<N>(&mut self, name: N, orig: Bytes)
|
||||||
|
where
|
||||||
|
N: http::header::IntoHeaderName,
|
||||||
|
{
|
||||||
|
self.0.append(name, orig);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_headers_foreach_cases_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"SET-COOKIE";
|
||||||
|
let value2 = b"c=d";
|
||||||
|
hyper_headers_add(
|
||||||
|
&mut headers,
|
||||||
|
name2.as_ptr(),
|
||||||
|
name2.len(),
|
||||||
|
value2.as_ptr(),
|
||||||
|
value2.len(),
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut vec = Vec::<u8>::new();
|
||||||
|
hyper_headers_foreach(&headers, concat, &mut vec as *mut _ as *mut c_void);
|
||||||
|
|
||||||
|
assert_eq!(vec, b"Set-CookiE: a=b\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
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ mod io;
|
|||||||
mod task;
|
mod task;
|
||||||
|
|
||||||
pub(crate) use self::body::UserBody;
|
pub(crate) use self::body::UserBody;
|
||||||
|
pub(crate) use self::http_types::HeaderCaseMap;
|
||||||
|
|
||||||
pub const HYPER_ITER_CONTINUE: libc::c_int = 0;
|
pub const HYPER_ITER_CONTINUE: libc::c_int = 0;
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
|
|||||||
@@ -44,6 +44,8 @@ where
|
|||||||
error: None,
|
error: None,
|
||||||
keep_alive: KA::Busy,
|
keep_alive: KA::Busy,
|
||||||
method: None,
|
method: None,
|
||||||
|
#[cfg(feature = "ffi")]
|
||||||
|
preserve_header_case: false,
|
||||||
title_case_headers: false,
|
title_case_headers: false,
|
||||||
notify_read: false,
|
notify_read: false,
|
||||||
reading: Reading::Init,
|
reading: Reading::Init,
|
||||||
@@ -142,6 +144,8 @@ where
|
|||||||
ParseContext {
|
ParseContext {
|
||||||
cached_headers: &mut self.state.cached_headers,
|
cached_headers: &mut self.state.cached_headers,
|
||||||
req_method: &mut self.state.method,
|
req_method: &mut self.state.method,
|
||||||
|
#[cfg(feature = "ffi")]
|
||||||
|
preserve_header_case: self.state.preserve_header_case,
|
||||||
}
|
}
|
||||||
)) {
|
)) {
|
||||||
Ok(msg) => msg,
|
Ok(msg) => msg,
|
||||||
@@ -474,6 +478,16 @@ where
|
|||||||
|
|
||||||
self.enforce_version(&mut head);
|
self.enforce_version(&mut head);
|
||||||
|
|
||||||
|
// Maybe check if we should preserve header casing on received
|
||||||
|
// message headers...
|
||||||
|
#[cfg(feature = "ffi")]
|
||||||
|
{
|
||||||
|
if T::is_client() && !self.state.preserve_header_case {
|
||||||
|
self.state.preserve_header_case =
|
||||||
|
head.extensions.get::<crate::ffi::HeaderCaseMap>().is_some();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let buf = self.io.headers_buf();
|
let buf = self.io.headers_buf();
|
||||||
match super::role::encode_headers::<T>(
|
match super::role::encode_headers::<T>(
|
||||||
Encode {
|
Encode {
|
||||||
@@ -736,6 +750,8 @@ struct State {
|
|||||||
/// This is used to know things such as if the message can include
|
/// This is used to know things such as if the message can include
|
||||||
/// a body or not.
|
/// a body or not.
|
||||||
method: Option<Method>,
|
method: Option<Method>,
|
||||||
|
#[cfg(feature = "ffi")]
|
||||||
|
preserve_header_case: bool,
|
||||||
title_case_headers: bool,
|
title_case_headers: bool,
|
||||||
/// Set to true when the Dispatcher should poll read operations
|
/// Set to true when the Dispatcher should poll read operations
|
||||||
/// again. See the `maybe_notify` method for more.
|
/// again. See the `maybe_notify` method for more.
|
||||||
|
|||||||
@@ -492,7 +492,7 @@ cfg_server! {
|
|||||||
version: parts.version,
|
version: parts.version,
|
||||||
subject: parts.status,
|
subject: parts.status,
|
||||||
headers: parts.headers,
|
headers: parts.headers,
|
||||||
extensions: http::Extensions::default(),
|
extensions: parts.extensions,
|
||||||
};
|
};
|
||||||
Poll::Ready(Some(Ok((head, body))))
|
Poll::Ready(Some(Ok((head, body))))
|
||||||
} else {
|
} else {
|
||||||
@@ -576,7 +576,7 @@ cfg_client! {
|
|||||||
version: parts.version,
|
version: parts.version,
|
||||||
subject: crate::proto::RequestLine(parts.method, parts.uri),
|
subject: crate::proto::RequestLine(parts.method, parts.uri),
|
||||||
headers: parts.headers,
|
headers: parts.headers,
|
||||||
extensions: http::Extensions::default(),
|
extensions: parts.extensions,
|
||||||
};
|
};
|
||||||
*this.callback = Some(cb);
|
*this.callback = Some(cb);
|
||||||
Poll::Ready(Some(Ok((head, body))))
|
Poll::Ready(Some(Ok((head, body))))
|
||||||
|
|||||||
@@ -159,6 +159,8 @@ where
|
|||||||
ParseContext {
|
ParseContext {
|
||||||
cached_headers: parse_ctx.cached_headers,
|
cached_headers: parse_ctx.cached_headers,
|
||||||
req_method: parse_ctx.req_method,
|
req_method: parse_ctx.req_method,
|
||||||
|
#[cfg(feature = "ffi")]
|
||||||
|
preserve_header_case: parse_ctx.preserve_header_case,
|
||||||
},
|
},
|
||||||
)? {
|
)? {
|
||||||
Some(msg) => {
|
Some(msg) => {
|
||||||
@@ -636,6 +638,8 @@ mod tests {
|
|||||||
let parse_ctx = ParseContext {
|
let parse_ctx = ParseContext {
|
||||||
cached_headers: &mut None,
|
cached_headers: &mut None,
|
||||||
req_method: &mut None,
|
req_method: &mut None,
|
||||||
|
#[cfg(feature = "ffi")]
|
||||||
|
preserve_header_case: false,
|
||||||
};
|
};
|
||||||
assert!(buffered
|
assert!(buffered
|
||||||
.parse::<ClientTransaction>(cx, parse_ctx)
|
.parse::<ClientTransaction>(cx, parse_ctx)
|
||||||
|
|||||||
@@ -70,6 +70,8 @@ pub(crate) struct ParsedMessage<T> {
|
|||||||
pub(crate) struct ParseContext<'a> {
|
pub(crate) struct ParseContext<'a> {
|
||||||
cached_headers: &'a mut Option<HeaderMap>,
|
cached_headers: &'a mut Option<HeaderMap>,
|
||||||
req_method: &'a mut Option<Method>,
|
req_method: &'a mut Option<Method>,
|
||||||
|
#[cfg(feature = "ffi")]
|
||||||
|
preserve_header_case: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Passed to Http1Transaction::encode
|
/// Passed to Http1Transaction::encode
|
||||||
|
|||||||
@@ -148,6 +148,7 @@ impl Http1Transaction for Server {
|
|||||||
is_http_11 = false;
|
is_http_11 = false;
|
||||||
Version::HTTP_10
|
Version::HTTP_10
|
||||||
};
|
};
|
||||||
|
trace!("headers: {:?}", &req.headers);
|
||||||
|
|
||||||
record_header_indices(bytes, &req.headers, &mut headers_indices)?;
|
record_header_indices(bytes, &req.headers, &mut headers_indices)?;
|
||||||
headers_len = req.headers.len();
|
headers_len = req.headers.len();
|
||||||
@@ -692,6 +693,9 @@ impl Http1Transaction for Client {
|
|||||||
|
|
||||||
let mut keep_alive = version == Version::HTTP_11;
|
let mut keep_alive = version == Version::HTTP_11;
|
||||||
|
|
||||||
|
#[cfg(feature = "ffi")]
|
||||||
|
let mut header_case_map = crate::ffi::HeaderCaseMap::default();
|
||||||
|
|
||||||
headers.reserve(headers_len);
|
headers.reserve(headers_len);
|
||||||
for header in &headers_indices[..headers_len] {
|
for header in &headers_indices[..headers_len] {
|
||||||
let name = header_name!(&slice[header.name.0..header.name.1]);
|
let name = header_name!(&slice[header.name.0..header.name.1]);
|
||||||
@@ -707,14 +711,28 @@ impl Http1Transaction for Client {
|
|||||||
keep_alive = headers::connection_keep_alive(&value);
|
keep_alive = headers::connection_keep_alive(&value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "ffi")]
|
||||||
|
if ctx.preserve_header_case {
|
||||||
|
header_case_map.append(&name, slice.slice(header.name.0..header.name.1));
|
||||||
|
}
|
||||||
|
|
||||||
headers.append(name, value);
|
headers.append(name, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(unused_mut)]
|
||||||
|
let mut extensions = http::Extensions::default();
|
||||||
|
|
||||||
|
#[cfg(feature = "ffi")]
|
||||||
|
if ctx.preserve_header_case {
|
||||||
|
extensions.insert(header_case_map);
|
||||||
|
}
|
||||||
|
|
||||||
let head = MessageHead {
|
let head = MessageHead {
|
||||||
version,
|
version,
|
||||||
subject: status,
|
subject: status,
|
||||||
headers,
|
headers,
|
||||||
extensions: http::Extensions::default(),
|
extensions,
|
||||||
};
|
};
|
||||||
if let Some((decode, is_upgrade)) = Client::decoder(&head, ctx.req_method)? {
|
if let Some((decode, is_upgrade)) = Client::decoder(&head, ctx.req_method)? {
|
||||||
return Ok(Some(ParsedMessage {
|
return Ok(Some(ParsedMessage {
|
||||||
@@ -766,11 +784,28 @@ impl Http1Transaction for Client {
|
|||||||
}
|
}
|
||||||
extend(dst, b"\r\n");
|
extend(dst, b"\r\n");
|
||||||
|
|
||||||
if msg.title_case_headers {
|
#[cfg(feature = "ffi")]
|
||||||
write_headers_title_case(&msg.head.headers, dst);
|
{
|
||||||
} else {
|
if msg.title_case_headers {
|
||||||
write_headers(&msg.head.headers, dst);
|
write_headers_title_case(&msg.head.headers, dst);
|
||||||
|
} else if let Some(orig_headers) =
|
||||||
|
msg.head.extensions.get::<crate::ffi::HeaderCaseMap>()
|
||||||
|
{
|
||||||
|
write_headers_original_case(&msg.head.headers, orig_headers, dst);
|
||||||
|
} else {
|
||||||
|
write_headers(&msg.head.headers, dst);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "ffi"))]
|
||||||
|
{
|
||||||
|
if msg.title_case_headers {
|
||||||
|
write_headers_title_case(&msg.head.headers, dst);
|
||||||
|
} else {
|
||||||
|
write_headers(&msg.head.headers, dst);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
extend(dst, b"\r\n");
|
extend(dst, b"\r\n");
|
||||||
msg.head.headers.clear(); //TODO: remove when switching to drain()
|
msg.head.headers.clear(); //TODO: remove when switching to drain()
|
||||||
|
|
||||||
@@ -1081,6 +1116,40 @@ fn write_headers(headers: &HeaderMap, dst: &mut Vec<u8>) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "ffi")]
|
||||||
|
#[cold]
|
||||||
|
fn write_headers_original_case(
|
||||||
|
headers: &HeaderMap,
|
||||||
|
orig_case: &crate::ffi::HeaderCaseMap,
|
||||||
|
dst: &mut Vec<u8>,
|
||||||
|
) {
|
||||||
|
// For each header name/value pair, there may be a value in the casemap
|
||||||
|
// that corresponds to the HeaderValue. So, we iterator all the keys,
|
||||||
|
// 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.keys() {
|
||||||
|
let mut names = orig_case.get_all(name).iter();
|
||||||
|
|
||||||
|
for value in headers.get_all(name) {
|
||||||
|
if let Some(orig_name) = names.next() {
|
||||||
|
extend(dst, orig_name);
|
||||||
|
} else {
|
||||||
|
extend(dst, name.as_str().as_bytes());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wanted for curl test cases that send `X-Custom-Header:\r\n`
|
||||||
|
if value.is_empty() {
|
||||||
|
extend(dst, b":\r\n");
|
||||||
|
} else {
|
||||||
|
extend(dst, b": ");
|
||||||
|
extend(dst, value.as_bytes());
|
||||||
|
extend(dst, b"\r\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
struct FastWrite<'a>(&'a mut Vec<u8>);
|
struct FastWrite<'a>(&'a mut Vec<u8>);
|
||||||
|
|
||||||
impl<'a> fmt::Write for FastWrite<'a> {
|
impl<'a> fmt::Write for FastWrite<'a> {
|
||||||
@@ -1117,6 +1186,8 @@ mod tests {
|
|||||||
ParseContext {
|
ParseContext {
|
||||||
cached_headers: &mut None,
|
cached_headers: &mut None,
|
||||||
req_method: &mut method,
|
req_method: &mut method,
|
||||||
|
#[cfg(feature = "ffi")]
|
||||||
|
preserve_header_case: false,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
@@ -1137,6 +1208,8 @@ mod tests {
|
|||||||
let ctx = ParseContext {
|
let ctx = ParseContext {
|
||||||
cached_headers: &mut None,
|
cached_headers: &mut None,
|
||||||
req_method: &mut Some(crate::Method::GET),
|
req_method: &mut Some(crate::Method::GET),
|
||||||
|
#[cfg(feature = "ffi")]
|
||||||
|
preserve_header_case: false,
|
||||||
};
|
};
|
||||||
let msg = Client::parse(&mut raw, ctx).unwrap().unwrap();
|
let msg = Client::parse(&mut raw, ctx).unwrap().unwrap();
|
||||||
assert_eq!(raw.len(), 0);
|
assert_eq!(raw.len(), 0);
|
||||||
@@ -1152,6 +1225,8 @@ mod tests {
|
|||||||
let ctx = ParseContext {
|
let ctx = ParseContext {
|
||||||
cached_headers: &mut None,
|
cached_headers: &mut None,
|
||||||
req_method: &mut None,
|
req_method: &mut None,
|
||||||
|
#[cfg(feature = "ffi")]
|
||||||
|
preserve_header_case: false,
|
||||||
};
|
};
|
||||||
Server::parse(&mut raw, ctx).unwrap_err();
|
Server::parse(&mut raw, ctx).unwrap_err();
|
||||||
}
|
}
|
||||||
@@ -1165,6 +1240,8 @@ mod tests {
|
|||||||
ParseContext {
|
ParseContext {
|
||||||
cached_headers: &mut None,
|
cached_headers: &mut None,
|
||||||
req_method: &mut None,
|
req_method: &mut None,
|
||||||
|
#[cfg(feature = "ffi")]
|
||||||
|
preserve_header_case: false,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.expect("parse ok")
|
.expect("parse ok")
|
||||||
@@ -1178,6 +1255,8 @@ mod tests {
|
|||||||
ParseContext {
|
ParseContext {
|
||||||
cached_headers: &mut None,
|
cached_headers: &mut None,
|
||||||
req_method: &mut None,
|
req_method: &mut None,
|
||||||
|
#[cfg(feature = "ffi")]
|
||||||
|
preserve_header_case: false,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.expect_err(comment)
|
.expect_err(comment)
|
||||||
@@ -1380,6 +1459,8 @@ mod tests {
|
|||||||
ParseContext {
|
ParseContext {
|
||||||
cached_headers: &mut None,
|
cached_headers: &mut None,
|
||||||
req_method: &mut Some(Method::GET),
|
req_method: &mut Some(Method::GET),
|
||||||
|
#[cfg(feature = "ffi")]
|
||||||
|
preserve_header_case: false,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.expect("parse ok")
|
.expect("parse ok")
|
||||||
@@ -1393,6 +1474,8 @@ mod tests {
|
|||||||
ParseContext {
|
ParseContext {
|
||||||
cached_headers: &mut None,
|
cached_headers: &mut None,
|
||||||
req_method: &mut Some(m),
|
req_method: &mut Some(m),
|
||||||
|
#[cfg(feature = "ffi")]
|
||||||
|
preserve_header_case: false,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.expect("parse ok")
|
.expect("parse ok")
|
||||||
@@ -1406,6 +1489,8 @@ mod tests {
|
|||||||
ParseContext {
|
ParseContext {
|
||||||
cached_headers: &mut None,
|
cached_headers: &mut None,
|
||||||
req_method: &mut Some(Method::GET),
|
req_method: &mut Some(Method::GET),
|
||||||
|
#[cfg(feature = "ffi")]
|
||||||
|
preserve_header_case: false,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.expect_err("parse should err")
|
.expect_err("parse should err")
|
||||||
@@ -1719,6 +1804,8 @@ mod tests {
|
|||||||
ParseContext {
|
ParseContext {
|
||||||
cached_headers: &mut None,
|
cached_headers: &mut None,
|
||||||
req_method: &mut Some(Method::GET),
|
req_method: &mut Some(Method::GET),
|
||||||
|
#[cfg(feature = "ffi")]
|
||||||
|
preserve_header_case: false,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.expect("parse ok")
|
.expect("parse ok")
|
||||||
@@ -1727,6 +1814,42 @@ mod tests {
|
|||||||
assert_eq!(parsed.head.headers["server"], "hello\tworld");
|
assert_eq!(parsed.head.headers["server"], "hello\tworld");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "ffi")]
|
||||||
|
#[test]
|
||||||
|
fn test_write_headers_orig_case_empty_value() {
|
||||||
|
let mut headers = HeaderMap::new();
|
||||||
|
let name = http::header::HeaderName::from_static("x-empty");
|
||||||
|
headers.insert(&name, "".parse().expect("parse empty"));
|
||||||
|
let mut orig_cases = crate::ffi::HeaderCaseMap::default();
|
||||||
|
orig_cases.insert(name, Bytes::from_static(b"X-EmptY"));
|
||||||
|
|
||||||
|
let mut dst = Vec::new();
|
||||||
|
super::write_headers_original_case(&headers, &orig_cases, &mut dst);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
dst, b"X-EmptY:\r\n",
|
||||||
|
"there should be no space between the colon and CRLF"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "ffi")]
|
||||||
|
#[test]
|
||||||
|
fn test_write_headers_orig_case_multiple_entries() {
|
||||||
|
let mut headers = HeaderMap::new();
|
||||||
|
let name = http::header::HeaderName::from_static("x-empty");
|
||||||
|
headers.insert(&name, "a".parse().unwrap());
|
||||||
|
headers.append(&name, "b".parse().unwrap());
|
||||||
|
|
||||||
|
let mut orig_cases = crate::ffi::HeaderCaseMap::default();
|
||||||
|
orig_cases.insert(name.clone(), Bytes::from_static(b"X-Empty"));
|
||||||
|
orig_cases.append(name, Bytes::from_static(b"X-EMPTY"));
|
||||||
|
|
||||||
|
let mut dst = Vec::new();
|
||||||
|
super::write_headers_original_case(&headers, &orig_cases, &mut dst);
|
||||||
|
|
||||||
|
assert_eq!(dst, b"X-Empty: a\r\nX-EMPTY: b\r\n");
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(feature = "nightly")]
|
#[cfg(feature = "nightly")]
|
||||||
use test::Bencher;
|
use test::Bencher;
|
||||||
|
|
||||||
@@ -1762,6 +1885,8 @@ mod tests {
|
|||||||
ParseContext {
|
ParseContext {
|
||||||
cached_headers: &mut headers,
|
cached_headers: &mut headers,
|
||||||
req_method: &mut None,
|
req_method: &mut None,
|
||||||
|
#[cfg(feature = "ffi")]
|
||||||
|
preserve_header_case: false,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
@@ -1795,6 +1920,8 @@ mod tests {
|
|||||||
ParseContext {
|
ParseContext {
|
||||||
cached_headers: &mut headers,
|
cached_headers: &mut headers,
|
||||||
req_method: &mut None,
|
req_method: &mut None,
|
||||||
|
#[cfg(feature = "ffi")]
|
||||||
|
preserve_header_case: false,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
|
|||||||
@@ -24,7 +24,6 @@ pub struct MessageHead<S> {
|
|||||||
pub subject: S,
|
pub subject: S,
|
||||||
/// Headers of the Incoming message.
|
/// Headers of the Incoming message.
|
||||||
pub headers: http::HeaderMap,
|
pub headers: http::HeaderMap,
|
||||||
|
|
||||||
/// Extensions.
|
/// Extensions.
|
||||||
extensions: http::Extensions,
|
extensions: http::Extensions,
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user