hyper (Rust) upgrade to v1: Body became Trait

created
( modified )
@nabbisen

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 :)

Series

hyper (Rust) upgrade to v1
  1. hyper (Rust) upgrade to v1: Body became Trait
  2. hyper (Rust) upgrade to v1: Higher-level Server / Client were removed

Comments or feedbacks are welcomed and appreciated.