hyper
hyper copied to clipboard
Original Header Cases API
This is a tracking issue for the feature to allow for sending and receiving the exact casing of header names, instead of the forced lowercasing of http. In most situations, simply forcing to lowercase is the better option, as it allows for a more performant HeaderMap, and is the required format for newer versions of HTTP (HTTP/2 and 3). However, there are rare occasions when being able to see the exact casing is needed.
Some initial support is available in hyper, and is exposed in the C API. But a public Rust API is not yet available.
- [x] Initial internal support
- [x] Expose in C API (#2278)
- [x] Provide builder options to enable support in Rust, useful to proxies (#2480)
- [ ] Design and export the public extension API
any recent progress? I'm also having issues with header casing
We update with progress as it happens, it's all here. I'd recommend filing a bug with the server you're interacting with, it is wrongly assuming that HTTP headers have meaning.
in my case, the openconenct client assume the HTTP header is "X-CSTP-*" and it never convert to lowercase.
the client code is openconnect client
if (strncmp(buf, "X-DTLS-", 7) &&
strncmp(buf, "X-CSTP-", 7) &&
strncmp(buf, "X-DTLS12-", 9))
continue;
bad situation this is client code, there are millions of users relying on this client to use VPN. it is impossible to make these users upgrade their openconnect client.
so the server must be return the uppercase header.
We've also been bitten by this unfortunately. We know and realize that HTTP headers should be lower case.
Alas, some of our customers have compatibility requirements because they've been sending them as upper case (or camel case, depending) and some of their customers rely on them being of specific casing.
I'd be really grateful for even an experimental feature that we could enable to support this, because our current (terrible) alternative is to maintain our own Hyper fork. :(
The last item on the todo list above says "Design and export the public extension API". If I understand correctly, "extension" there refers to the http crate's extension concept. That would imply that this continues to be a hyper feature that uses extensions as a hack to encode header cases (and possibly order) separately from the actual header map of the http::Request / http::Response. IMHO that is a rather inelegant solution, and it becomes a non-solution as soon as other HTTP implementations (e.g. curl via the isahc crate) enter the picture. Are alternative solutions at the http crate level up for consideration¹?
¹ for example (though I haven't thought this through all the way): adding a defaulted generic parameter for the headermap type to http::Request, http::Response and thus allowing different header maps that chose other trade-offs than https to be written while having interoparability with other crates using http, if those are updated to be generic over the headermap type
The idea of making all the types in http actually be traits has been thought of a few times, and it becomes extremely complicated for users when all the things are generic.
trait Request {
type Method;
type RequestTarget;
type Version;
type Headers: Headers;
}
trait Headers {
type Name;
type Value;
type Iter;
type IterMut;
// etc
}
I have liked the idea of them being traits, but it hasn't seemed practical to users. It'd be cool if we could come up with a solution. But, that is all better for a different topic.
This issue is about the support in hyper. So, I'm going to hide these comments.
How about this as a public API?
pub trait HttpMessageExt {
fn headers_case_sensitive(&self) -> CaseSensitiveHeaderMap<'_>;
fn headers_case_sensitive_mut(&mut self) -> CaseSensitiveHeaderMapMut<'_>;
}
impl<T> HttpMessageExt for http::Request<T> { /* ... */ }
impl<T> HttpMessageExt for http::Response<T> { /* ... */ }
// CaseSensitiveHeaderMap and CaseSensitiveHeaderMapMut methods would be more or less obvious, I guess?
For CaseSensitiveHeaderMapMut to work, we'd need extra API in http to allow mutably borrowing headers and extensions simultaneously, but I think that would "just" be a matter of
// request.rs
impl<T> Request<T> {
pub fn parts_mut(&mut self) -> (PartsMut<'_>, &mut T) { /* ... */ }
}
pub struct PartsMut<'a> {
/// The request's method
pub method: &'a mut Method,
/// The request's URI
pub uri: &'a mut Uri,
/// The request's version
pub version: &'a mut Version,
/// The request's headers
pub headers: &'a mut HeaderMap<HeaderValue>,
/// The request's extensions
pub extensions: &'a mut Extensions,
_priv: (),
}
// response.rs - equivalent with slightly different fields
Hm. What would be the specific way a user would interact with this? What would the methods of HeaderCaseMap look like?
Some examples
let headers = response.headers_case_sensitive();
// Check for a header using the exact provided casing
if let Some(val) = message.headers_case_sensitive().get("Foo-Bar") {
// read val: HeaderValue
}
let mut headers = request.headers_case_sensitive();
// Insert a header using the exact provided casing
headers.insert("Foo-Bar", <header value>);
Methods-wise, I could see this type having all of the methods (not constructors of course) of HeaderMap plus even a few _ignore_case ones (if you want to check an existing header ignoring casing to insert a new one with specific casing from one map object), but starting small, these seems like the important ones:
impl<'a> CaseSensitiveHeaderMap<'a> {
pub fn contains_key(&self, key: impl AsCaseSensitiveHeaderName) -> bool;
pub fn get(&self, key: impl AsaseSensitiveHeaderName) -> Option<&'a HeaderValue>;
// GetAll<'a>: IntoIterator<Item = &'a HeaderValue>
pub fn get_all(&self, key: impl AsCaseSensitiveHeaderName) -> GetAll<'a>;
// Iter<'a>: Iterator<Item = (&'a CaseSensitiveHeaderName, &'a HeaderValue)
pub fn iter(&self) -> Iter<'a>;
}
impl<'a> CaseSensitiveHeaderMapMut<'a> {
// same methods as CaseSensitiveHeaderMap
pub fn append(&mut self, key: impl IntoCaseSensitiveHeaderName, value: HeaderValue) -> bool;
pub fn insert(&mut self, key: impl IntoCaseSensitiveHeaderName, value: HeaderValue) -> Option<HeaderValue>;
// also try_append, try_insert
pub fn get_mut(&mut self, key: impl IntoCaseSensitiveHeaderName) -> Option<&mut HeaderValue);
pub fn remove(&mut self, key: impl IntoCaseSensitiveHeaderName) -> Option<HeaderValue>;
// IterMut<'a>: Iterator<Item = (&'a CaseSensitiveHeaderName, &'a mut HeaderValue)>
pub fn iter_mut(&mut self) -> IterMut<'_>;
}
pub trait AsCaseSensitiveHeaderName: hdr_as::Sealed {}
pub trait IntoCaseSensitiveHeaderName: hdr_into::Sealed {}
// similar impls, sealed traits as for upstream traits
#[repr(transparent)]
struct CaseSensitiveHeaderName([u8]);