hyper (Rust) upgrade to v1: Body became Trait
Tags: rust, hyper, api, microservices
Published: 2024-05-21 14:31:36

## 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`](https://docs.rs/hyper/latest/hyper/body/index.html)`::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](https://hyper.rs/contrib/roadmap/#forwards-compatibility):

> 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`](https://docs.rs/http-body-util/latest/http_body_util/index.html)`::combinators::BoxBody`, `http_body_util::Full`, `http_body_util::Empty` and so on are available.

### My project challenge

[apimock-rs](https://github.com/nabbisen/apimock-rs) is API mock Server generating HTTP / JSON responses to help to develop microservices and APIs, written in [Rust](https://www.rust-lang.org/). It's one of my projects.

Its core dependencies is [hyper](https://hyper.rs/), "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](https://github.com/hyperium/hyper/releases/tag/v1.0.0) 🎉

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

```diff
  [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:

```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 ?

```diff
- 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](https://hyper.rs/guides/1/upgrading/#body)).

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

```diff
- 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.

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

- [Upgrade from v0.14 to v1](https://hyper.rs/guides/1/upgrading/)
- [Roadmap to v1](https://hyper.rs/contrib/roadmap/)
- [Getting Started with a Server](https://hyper.rs/guides/1/server/hello-world/)
- [hyper/examples/web_api.rs](https://github.com/hyperium/hyper/blob/master/examples/web_api.rs)