-
Notifications
You must be signed in to change notification settings - Fork 85
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[tracking] typed headers #99
Comments
A good part of them are implemented in #107 |
Example for a typed expires constructor: use http_types::cache::Expires;
use std::time::{SystemTime, Duration};
let when = SystemTime::now() + Duration::from_secs(60 * 60);
let expires = Expires::new(when);
res.insert_header(expires.name(), expires.value()); |
Hello @yoshuawuyts , I have implemented a basic HTTP Range support (no multipart support) for a private project of mine using http-rs/tide. I have implemented a I would be happy to help implementing this back to http-types, async-h1 and tide. I believe things start with http-types but I am not sure about the API your are expecting "typed headers" to follow. Should I start with your example above and submit a PR for review ? |
@ririsoft hey! -- that's a good question. I'm not sure either what such an API should look like, but I'm sure we can figure it out. Opening a PR with a usage example would indeed be a great starting point. |
Hello !
I will submit one shortly for further brainstorming, addressing the HTTP range headers parsing and validation use case. |
After my implementation of range request headers (PR #180) I am not convinced that a typed header will bring much value over the existing The only added value I see is the automation of header name handling when fetching header values. For instance we could have the following (simplified version): Gist: https://gist.github.com/rust-play/07ab2a72218d80531af7225dc0579c17 use std::collections::HashMap;
use std::str::FromStr;
use std::string::ToString;
// Here FromStr should be TryFrom<&HeaderValue>
// and ToString should be ToHeaderValues.
trait TypedHeader: FromStr + ToString {
const HEADER_NAME: &'static str;
}
struct Headers {
vals: HashMap<String, String>,
}
impl Headers {
fn get<T: TypedHeader>(&self) -> Option<Result<T, <T as FromStr>::Err>> {
self.vals.get(T::HEADER_NAME).map(|v| T::from_str(&v))
}
fn set<T: TypedHeader>(&mut self, val: T) {
self.vals
.insert(T::HEADER_NAME.to_string(), val.to_string());
}
}
#[derive(Debug, Eq, PartialEq, Clone)]
struct Range {
start: u64,
end: u64,
}
impl ToString for Range {
fn to_string(&self) -> String {
format!("bytes={}-{}", self.start, self.end)
}
}
impl FromStr for Range {
type Err = &'static str;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let err = "invalid range header";
let mut bounds = s.trim_start_matches("bytes=").split('-');
let start = bounds
.next()
.ok_or(err)
.and_then(|v| u64::from_str(v).map_err(|_| err))?;
let end = bounds
.next()
.ok_or(err)
.and_then(|v| u64::from_str(v).map_err(|_| err))?;
Ok(Range { start, end })
}
}
impl TypedHeader for Range {
const HEADER_NAME: &'static str = "Range";
}
fn main() {
let mut h = Headers {
vals: HashMap::new(),
};
let r = Range {
start: 10,
end: 100,
};
h.set(r.clone());
let other = h.get::<Range>();
assert_eq!(Some(Ok(r)), other);
} We would need to add some Actually the true additional value of the above implementation is the possibility to get the first header value instead of having to get all headers value and map option to What do you think ? |
I think we may be able to think about this better. After thinking about this since yesterday, I was thinking about my wip port of negotiator - i.e. This is derived from the neat api of accepts, a JS lib which builds on negotiator: var accept = accepts(req)
// the order of this list is significant; should be server preferred order
switch (accept.type(['json', 'html'])) {
case 'json':
res.setHeader('Content-Type', 'application/json')
res.write('{"hello":"world!"}')
break i.e. You could specify a req.set_header(AcceptEncoding::new(["gzip", "deflate", "identity"])); let accept: Option<AcceptEncoding> = res.get_header(AcceptEncoding::new(["gzip", "deflate", "identity"]));
match accept?.get_preferred_match() {
"gzip" => ...
...
}; in this case, impl Headers {
fn get<T>(&self, mut header: T) -> Option<T> where T: TypedHeader {
self.vals.get(T::HEADER_NAME).map(|v| header.populate(&v))
}
fn set<T>(&mut self, header: T) where T: TypedHeader {
self.vals
.insert(T::HEADER_NAME.to_string(), header.to_string());
}
} |
Hi @Fishrock123, I did not know about negotiator, this is interesting. Thanks for sharing ! It seems we are ending with 2 close APIs with some subtle differences that suite best one case or another. The
I need to mature more on the differences of the two design, please excuse me if I misunderstood some points, but here is what comes to my mind: When fetching a header value from string we might want to do the following steps:
My concern with
|
I think that would happen either: when setting the header value, or, when requesting the header value. Given that, we might be able to do a slight twist on v2 to get something that is even more mirrored for getting and setting: trait TypedHeader: FromStr + ToString {
const HEADER_NAME: &'static str;
fn set_value(value: impl Into<HeaderValue>) -> Result<(), Error>; // #2, #3.
fn get_value() -> Result<&HeaderValue, Error>; // #2, #3.
}
impl Headers {
fn get<T>(&self) -> Option<T> where T: TypedHeader { // #1
self.vals.get(T::HEADER_NAME).map(|v| T::from_str(&v))
}
fn set<T>(&mut self, header: T) where T: TypedHeader {
self.vals
.insert(T::HEADER_NAME.to_string(), header.to_string());
}
} Perhaps that's not quite flexible enough for |
I've made a PR for another typed header impl in #203. I've also noted down the patterns we're using in Header design overviewThis is the current snapshot of the gist above. We're already using this API for
Together these form a base template that should work for all typed header implementations. use crate::headers::{HeaderName, HeaderValue, Headers, ToHeaderValues};
struct CustomHeader {}
impl CustomHeader {
/// Create a new instance of `CustomHeader`.
fn new() -> Self {
todo!();
}
/// Create an instance of `CustomHeader` from a `Headers` instance.
fn from_headers(headers: impl AsRef<Headers>) -> crate::Result<Option<Self>> {
todo!();
}
/// Insert a `HeaderName` + `HeaderValue` pair into a `Headers` instance.
fn apply(&self, headers: impl AsMut<Headers>) {
todo!();
}
/// Get the `HeaderName`.
fn name(&self) -> HeaderName {
todo!();
}
/// Get the `HeaderValue`.
fn value(&self) -> HeaderValue {
todo!();
}
}
// Conversion from `CustomHeader` -> `HeaderValue`.
impl ToHeaderValues for CustomHeader {
type Iter;
fn to_header_values(&self) -> crate::Result<Self::Iter> {
todo!()
}
} |
I think there's still a couple things missing here. For example, Also this doesn't really answer any questions regarding header value matching/verification... I suppose for, say, content encoding matching you would do something like this: if let Some(header) = ContentEncoding::from_headers(req) {
if let Some(encoding) = header.accepts(&["br", "gzip"]) {
// `encoding` is the preferred encoding here
}
} |
Oops, yeah I'm not quite sure what the issue is with the example? I'm not sure I would nest |
While experiencing with such APIs on my side I wonder whether |
How would you write that? Also if we are considering |
That being said on further thought maybe header errors should just be "none" unless inspected otherwise? |
Added to list: |
WWW-Authenticate header: From RFC 7235#appendix-A, the "realm" parameter is optional now. |
@langbamit thanks for reporting; I've filed #280 to track it for the next major. |
Idempotentency-Key IETF draft. Perhaps we should implement this as a separate crate (submodule) until it stabilizes? |
IMO: implement draft headers under some compile time flag with unstable-like reduced guarantees. |
This is a tracking issue for typed headers. I looked over the
HTTP headers page and categorized which headers would need to be ported.
There are probably some nuances in how we want to implement them, but this provides an overview of how far along we are in constructing typed constructors for all header types.
edit: I realize now that this might read a bit dated. I drafted this text back in December but forgot to post it. Some things may seem a bit outdated or different; the most important part is that we can track whether we can construct headers .
Authentication (
auth
)WWW-Authenticate
(auth::Authenticate
)Authorization
(auth::Authorization
)Proxy-Authenticate
(auth::ProxyAuthenticate
)Proxy-Authorization
(auth::ProxyAuthorization
)Caching (
cache
)Age
(cache::Age
)Cache-Control
(cache::Control
)Clear-Site-Data
(cache::ClearSite
)Expires
(cache::Expires
)(HTTP/1.0 only)Pragma
(cache::Pragma
)(soon to be deprecated)Warning
(cache::Warning
)Client Hints (
client_hints
)(experimental, implementation postponed)Accept-CH
(ch::Accept
)(experimental, implementation postponed)Accept-CH-Lifetime
(ch::Lifetime
)(experimental, implementation postponed)Early-Data
(ch::EarlyData
)(experimental, implementation postponed)Content-DPR
(ch::ContentDpr
)(experimental, implementation postponed)DPR
(ch::Dpr
)(experimental, implementation postponed)Device-Memory
(ch::DeviceMemory
)(experimental, implementation postponed)Save-Data
(ch::SaveData
)(experimental, implementation postponed)Viewport-Width
(ch::ViewportWidth
)(experimental, implementation postponed)Width
(ch::Width
)Conditionals (
conditionals
)Last-Modified
(conditionals::LastModified
)Etag
(conditionals::Etag
)If-Match
(conditionals::IfMatch
)If-Modified-Since
(conditionals::IfModifiedSince
)If-Unmodified-Since
(conditionals::IfUnmodifiedSince
)Vary
(conditionals::Vary
)Content Negotiation (
content
)Accept
(content::Accept
)(no longer supported by any browser)Accept-Charset
(content::Charset
)Accept-Encoding
(content::Encoding
)Accept-Language
(content::Language
)Controls (
controls
)Expect
(controls::Expect
)Cookies
I think this one is special, and we should re-export the
cookie
crate, and just expose get /set cookies as methods on the
Request
andResponse
types. Let's just treatthem as built-ins to evade the name collision.
CORS (
cors
)Access-Control-Allow-Origin
(cors::AllowOrigin
)Access-Control-Allow-Credentials
(cors::AllowCredentials
)Access-Control-Allow-Headers
(cors::AllowHeaders
)Access-Control-Allow-Methods
(cors::AllowMethods
)Access-Control-Expose-Headers
(cors::ExposeHeaders
)Access-Control-Max-Age
(cors::MaxAge
)Access-Control-Request-Headers
(cors::RequestHeaders
)Access-Control-Request-Method
(cors::RequestMethod
)Access-Control-Origin
(cors::Origin
)Access-Control-Timing-Allow-Origin
(cors::TimingAllowOrigin
)Do Not Track (
privacy
)DNT
(privacy::DoNotTrack
)Tk
(privacy::TrackingStatus
)Content Information (
content
)Content-Disposition
(downloads::Disposition
)Content-Length
(content::Length
)Content-Type
(content::Type
)Content-Encoding
(content::Encoding
)Content-Language
(content::Language
)Content-Location
(content::Location
)Proxies (
proxies
)Forwarded
(proxies::Forwarded
)Via
(proxies::Via
)Response (
response
)Allow
(response::Allow
)Range (
content
)Accept-Ranges
(range::Accept
)Range
(range::Range
)If-Range
(range::If
)Content-Range
(range::Content
)Security (
security
)Cross-Origin-Opener-Policy
(security::Coop
)Cross-Origin-Resource-Policy
(security::Cors
)Content-Security-Policy
(security::Csp
)Content-Security-Policy-Report-Only
(security::Cspro
)Expect-CT
(security::Cspro
)Feature-Policy
(security::FeaturePolicy
)(deprecated)Public-Key-Pins
(security::HPKP
)Other
Large-Allocation(unspecced; firefox only)The text was updated successfully, but these errors were encountered: