DEV Community

nabbisen
nabbisen

Posted on • Edited on • Originally published at scqr.net

hyper (Rust) upgrade to v1: Body became Trait

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"
Enter fullscreen mode Exit fullscreen mode

Body change log

Here comes the point. Let's see a part of the diff:

- use hyper::Body;

+ use hyper::body::{Bytes, Incoming}};
Enter fullscreen mode Exit fullscreen mode

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> {
Enter fullscreen mode Exit fullscreen mode

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();
Enter fullscreen mode Exit fullscreen mode

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();
Enter fullscreen mode Exit fullscreen mode

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

Top comments (0)