This article is part of the series on the rust-http redesign, Teepee.
An apology
Teepee has stood with nothing much happening for a while; this is my fault and I’m sorry to have caused it. Things are starting moving again and will continue moving now; I have the low‐level design of the HTTP/1 message reading parts of Teepee almost done and expect to post this within a few days. But I don’t blame you if you don’t trust my “few days”; I wouldn’t either.
There are also quite a number of people that have emailed me asking how you can help on Teepee—sorry, it’s a little hard to figure out ways just at present, for the project is not really mature enough for that yet. It will come, though. If you really want fun, start looking at HTTP/2 and figure out a sensible implementation strategy, especially for its stream dependencies and prioritisation…
Unless stated otherwise, all grammar comes from RFC 7230.
Relevant definitions
Here’s what rust-http has:
Here is what RFC 2616 had:
Here is what RFC 7230 has:
The contents of RFC 7231 are largely immaterial here, except insofar as the common properties that are defined for methods are specified (in section 8.1.1): these are safety and idempotency. Representing these properties seem to me desirable.
The token
type
By the way, if you’re wondering what characters are valid in a method, method = token
; token = 1*tchar
; tchar = "!" / "#" / "$" / "%" / "&" / "'" / "*" / "+" / "-" / "." / "^" / "_" / "`" / "|" / "~" / DIGIT / ALPHA
.
In other words, a method (or any other place we get token
in the grammar) is a sequence of letters, numbers or any of those other fancy characters—yes, !#$%&'*+-.^_`|~
is a valid HTTP method name.
This is the sort of thing that should probably be encoded in the type system, using a separate token type instead of using String
or Vec<u8>
, &str
or &[u8]
. Given this much, we will need both owned and borrowed versions of this, for I am imposing a rule in the low‐level API that it must not allocate, while causing the token to be owned is the only sensible route for the higher‐level API.
The solution is an equivalent to std::str::MaybeOwned<'a>
, which applies to strings and is an owned string or a string slice (possibly even of static duration): then the owned form can be MaybeOwnedToken<'static>
, and the unowned form MaybeOwnedToken<'a>
. (Incidentally, we really need a general solution to this problem.)
On second thoughts, I doubt that the owned and slice tokens will be considered of value by themselves, so we might just eliminate them altogether, putting them straight into the enum.
Here’s the gist of what it’ll end up as:
(The #[doc(hidden)] pub
part is to allow statics to be defined outside the defining module. You know, we really need a general solution to this problem also.)
While a little cumbersome, unifying these imposes certain constraints on the solution space for handling methods.
Design one: a struct and statics
Nothing like a bit of code.
Method::from_token
would then produce one of those statics where possible, thus automatically filling in the details with regards to safety and idempotency, which are important to model. (I don’t think I clarified this earlier; well, I have seen too much code comparing method names in an ad‐hoc way, where what they really wanted to be examining was whether the method was safe or idempotent.)
This looks very well, doesn’t it? Unfortunately, it simply won’t do because of its ergonomics. Because of how we unified the token type, Method<'static>
is no longer Copy
, and something like request.method = GET
will not work—can’t move out of static. We are left with request.method = GET.clone()
, and the hypothetical request(url, GET.clone())
, a construct which is all in all rather an untidy thing. Is this a reasonable trade‐off to make? I am not convinced it is.
Design two: back to the enum
The second option is a mere extension of what is in rust-http: simple variants (which avoids the Copy
problem) plus one at the end for custom things. Something like this:
Incidentally, Teepee will support all the methods in the IANA HTTP method registry.
This has in its favour that it is something that has good ergonomics for the standard case. It does, however, have a slight weakness in that when a new method is added to the registry, code that was already using it may break, depending on how things were done. Pattern matching may break, for example. But this is a rare thing, so I’m not terribly distressed about it. It does, however, stand against it—strict semver would require a new major release to add a new item to the method enum.
Summary
In the end, I think that what rust-http does at present is sound; it just needs to be expanded to cover the entire HTTP method registry, and expanded to also keep track of whether a method is safe and/or idempotent.
Pending a suitable outcome to discussion on Reddit, the second approach is what I intend to implement.