Better HTTP Upgrades with hyper
It’s been possible to handle HTTP Upgrades (like Websockets) in hyper if you made use of the low-level APIs in the server and client, but it wasn’t especially nice to work with. It also meant to handle upgrades, you couldn’t use the nicer things that hyper takes care of for you with Client
or Server
.
In hyper v0.12.31, handling upgrades is much easier!
Body::on_upgrade()
The mechanism for handling upgrades and CONNECT
is unified into a Future
on the hyper::Body
type. The way this works is in either case, Client
or Server
, you’re already receiving a hyper::Body
that represents the streamed body from the remote. It also happens to be a great place to store a flag of whether an HTTP Upgrade is possible.
For now, after using a Body
to get any data, you can convert it into a Future
that yields the “upgraded” connection on success. With lessons learned from the lower-level upgrade process, the returned Upgraded
opts for a default of easier-to-use. It implements Read
and Write
, and those implementations will check the read buffer for any previously read bytes before the upgrade completed. The easiest thing to do is just to treat the yielded Upgraded
type as some impl Read + Write
, and use it as such for the next protocol you plan to use.
In order to provide this API, the Upgraded
holds the IO type as a boxed trait object internally. If dynamic dispatch is undesirable, there is Upgraded::downcast
to try to convert into the original type, along with the remaining read buffer.
Take a look at these simplified examples upgrading to Websockets:
Client Upgrades
let client = Client::new();
let req = Request::builder()
.uri("http://example.local/chat")
.header("upgrade", "websocket")
.header("connection", "upgrade")
.body(Body::empty()
.unwrap();
// This builds a future that should be spawned on an executor...
client
.request(req)
.and_then(|res| {
res.into_body().on_upgrade()
})
.and_then(|upgraded| {
// just use this as an IO
websocket_lib::client_thing(upgraded)
})
Server Upgrades
let service = service_fn_ok(|req| {
// Just assuming its always an upgrade for this example...
let upgrade = req
.into_body()
.on_upgrade()
.map(|upgraded| {
websocket_lib::server_thing(upgraded);
})
.map_err(|err| eprintln!("upgrade error: {}", err));
hyper::rt::spawn(upgrade);
Response::builder()
.status(101)
.header("upgrade", "websocket")
.header("connection", "upgrade")
.body(Body::empty())
.unwrap()
});
There’s a fuller example of a client and server that upgrade in the same program as well.