hyper (Rust) upgrade to v1: Body became Trait
nabbisen
Posted on May 21, 2024
Summary
This series is about how I upgraded hyper (Rust) 0.14 to v1 (1.3).
The first theme is Body
. hyper::Body
in v0 was Struct
changed to Trait
. At the same time, the successors such as hyper::body
::Bytes
and hyper::body::Incoming
came in. It was because it brought more flexibility to hyper even in the future. According to their 1.0 Roadmap:
Forwards-compatibility
There’s a concern about forwards-compatibility. We want to be able to add support for new HTTP features without needing a new major version.
In addition, http_body_util
::combinators::BoxBody
, http_body_util::Full
, http_body_util::Empty
and so on are available.
My project challenge
apimock-rs is API mock Server generating HTTP / JSON responses to help to develop microservices and APIs, written in Rust. It's one of my projects.
Its core dependencies is hyper, "a protective and efficient HTTP library for all" which is rather low-level.
Upgraded hyper
I started with hyper 0.14, and 1.0.0 was released last November 🎉
I have recently upgraded it which was a kind of somehow tough work. The change log was as below:
https://github.com/nabbisen/apimock-rs/pull/62/files
Cargo.toml
change log
As to HTTP server:
[dependencies]
(...)
- hyper = { version = "0.14", features = ["server", "http1", "http2", "tcp"] }
+ hyper = { version = "1", features = ["server", "http1", "http2"] }
+ hyper-util = { version = "^0.1", features = ["server", "http1", "http2", "tokio"] }
+ http-body-util = "^0.1"
Body
change log
Here comes the point. Let's see a part of the diff:
- use hyper::Body;
+ use hyper::body::{Bytes, Incoming}};
Again, Body
Struct disappeared. Instead, std::body::*
Structs implementing Body
Trait are available. They are such as std::body::Bytes
and std::body::Incoming
.
How did usage changed ?
- pub async fn handle(
- req: Request<Body>,
- app_state: Arc<Mutex<Config>>,
- ) -> Result<Response<Body>, Error> {
+ type BoxBody = http_body_util::combinators::BoxBody<Bytes, Infallible>;
+ pub async fn handle(
+ req: Request<Incoming>,
+ app_state: Arc<Mutex<Config>>,
+ ) -> Result<Response<BoxBody>, Error> {
Not big, because "The 0.14 Body could be multiple variants, and in v1 they have been split into distinct types" (due to their Upgrade guide).
Well, they seem familiar with streaming process. It's very nice. However, it is sometimes unnecessary to deal with it, especially around either testing or mocking. Therefore, on the app, I managed to deal with the whole bulk full of the frames at once with BoxBody
:
- let body_bytes = hyper::body::to_bytes(request_body).await.unwrap();
+ let body_bytes = request_body
+ .boxed() // here, `BoxBody` comes !
+ .collect()
+ .await
+ .expect("failed to collect request incoming body")
+ .to_bytes();
The client case was similar with it.
- hyper::body::to_bytes;
- (...)
- let response = client.request(request).await.unwrap();
- (...)
- let response_body_str =
- String::from_utf8(to_bytes(response.into_body()).await.unwrap().to_vec()).unwrap();
+ use http_body_util::{BodyExt, Empty, Full};
+ (...)
+ let request_body = Full::new(Bytes::from(body.unwrap().to_owned())).boxed()
+ (...)
+ let response_body_bytes = response
+ .into_body()
+ .boxed()
+ .collect()
+ .await
+ .unwrap()
+ .to_bytes();
+ let response_body_str = String::from_utf8(body_bytes.into()).unwrap();
You can see modules were changed, high-level client was removed and BoxBody
also works here.
Reference
Their official documentation and examples are really helpful :)
Posted on May 21, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.