hyper v0.12
Today sees the release of hyper v0.12.0, a fast and correct HTTP library for the Rust language.
This release adds support for several new features, while taking the opportunity to fix some annoyances, and improve the extreme speeds! Look, some wild bullet points appeared:
- Faster!
- More correct.
- Embraces the
http
crate types. - Adds HTTP2 support to both the client and server.
- The
Client
andServer
are easier to setup and use. - Better runtime support.
- Better body streams.
Faster
hyper 0.11 is already one of the fastest HTTP libraries out there. However, the original server API based around ServerProto
prevented it from going full speed. While a new API with a faster dispatcher has existed for a little while now, 0.12 is able to remove that slower way completely, and just default everyone to hyperspeed. But it doesn’t stop there.
Switching the headers types has meant a noticeable boost in serializing of HTTP headers, skipping std::fmt
overhead, and removing the need for replacing newlines in header values, thanks to HeaderValue
preventing those bytes entirely. While replacing the serialization pathway, further optimizations of checking for semantically important headers is able to be done while serializing, reducing hashmap lookups.
By taking control of the trait used to represent bodies , implementations of Payload
can provide hints such as if the body is empty, or exactly how big it thinks it is. This allows some more optimizations, and some API niceties, explained later on in this article.
HTTP Correctness
Being fast is important, but being correct is critical.
There’s quite a few edge cases in the HTTP/1 protocol, and an implementation that you trust to use should make sure to handle all those cases. Browsing various other HTTP/1 implementations, you’ll find that some of the faster ones ignore them, leaving it up to the users to protect themselves manually. Some of the edge cases can mean security vulnerabilities (like not sanitizing newlines out of headers resulting in message splitting), others just mean clients cannot understand you.
hyper’s correct handling of HTTP/1 continues to improve, all while getting faster. For instance, consider some of these things an HTTP implementation should handle:
- Receiving a Request with
Content-Length: 100
andTransfer-Encoding: chunked
means it must be chunked encoded. - The reverse is also true: if you insert both a
Content-Length
header andTransfer-Encoding: chunked
header, perhaps in different parts of your code, the following body must be chunked. - When responding to a
CONNECT
request,200 OK
responses cannot have bodies. However, responses with other status codes can! - If you send a
Content-Length: 100
header, but then try to send a body of 150 bytes, the recipient may try to parse bytes 101-150 as a new message.
There’s plenty more, spelled out in RFC7230, and hyper tries to repair any that it finds, or provides an error if there are no repairs that can be done, instead of silently ignoring and having incorrect state compared to the peer. Thanks to hyper’s usage in the Conduit proxy, we continue to find and fix more and more!
HTTP2
There is now built-in support for both the client and server to make use HTTP2.
- The
Server
by default will handle both HTTP/1 and HTTP2 connections, and can be configured to only accept HTTP2 if desired. - The
Client
requires explicit configuration for now, enabling a requirement for “prior knowledge” usage of HTTP2. Work is happening to allow for ALPN support to get “automatic” HTTP/1 + HTTP2 usage.
Streaming
As mentioned above, hyper changed the way it describes its streaming bodies. Before, it was just a futures::Stream
, but that meant the only thing hyper knew about it was that it could produce some data. Now, hyper defines a Payload
trait, which besides streaming data, can also stream trailers, declare its length, and say when it is finished.
By owning the Payload
trait, hyper can grow its capabilities when needed, such as to add HTTP2 push promises, or other new features.
The Data
type of payloads changed from have AsRef<[u8]>
bounds to Buf
instead. This importantly allows for custom application implementations to return data chunks that may be from different contiguous memory sources, taking advantage of hyper’s automatic writev
support, meaning less copies!
Ergonomics
Using both clients and servers has gotten much easier. There is default support for the new Tokio runtime, removing a lot of boilerplate from getting an event loop and reactor up and running.
Look how simple it is to get a naive HTTP proxy working:
let addr = ([0, 0, 0, 0], 3000).into();
let client = Client::new();
let new_svc = move || {
let client = client.clone();
service_fn(move |req|{
client.request(req)
})
};
let server = Server::bind(&addr)
.serve(new_svc)
.map_err(|e| eprintln!("server error: {}", e));
hyper::rt::run(server);
Of course, the example above doesn’t actually do all the things a proxy should do, but it shows off how simple it is to create a client and server.
These easy builders are enabled by default via the runtime
Cargo feature. Importantly, this means that if you already have some other sort of runtime, hyper can still integrate! Disabling the runtime
feature removes the dependency on Tokio, though you will need to use the configuration to explain how your runtime works to hyper.
Errors
There has been confusion with hyper’s usage of errors in 0.11 that 0.12 cleans up. Before, both Service::Error
and Stream::Error
were required to be hyper::Error
. However, it wasn’t clear what that really meant, and picking how to create a hyper::Error
was equally confusing.
Now, any place that a user would return an error to hyper, it has been changed to just have the bounds Into<Box<std::error::Error>>
. This means you can return any custom application error type that implements std::error::Error
. hyper doesn’t particularly do anything special with those errors, besides some logging and trying to pass them back up to a higher level error handler, but it’s at least easier to determine what error to return: whatever you want to describe the failure you encountered!
The documentation around Service::Error
has been clarified with this reminder as well: servers usually shouldn’t return an error back to hyper in most cases, and instead should return a Response
with an appropriate 400 or 500 status code. Returning an error in a server will signal to hyper that it should abort the connection immediately.
Thanks!
Thanks to all those who helped get us this far! Whether it’s through writing code, diagnosing bugs, discussing design and issues, running pre-release versions, it’s all what gets our community to a place with such awesome tools. Thank you.
As a wrap up, some links if you want to see more: