DEV Community

nabbisen
nabbisen

Posted on β€’ Originally published at scqr.net

2 1

hyper (Rust) upgrade to v1: Higher-level Server / Client were removed

Summary

This series is about how I upgraded hyper (Rust) 0.14 to v1 (1.3).

The next theme is higher-level Server / Client. Those in v0 were removed. It was because they had stability and complexity problems.

The Server wasn't followed by any drop-in replacement, and the Client was in a way by client::legacy::Client (that I didn't use).

In addition, hyper-util helps.

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

As to HTTP client:

  [dev-dependencies]
- hyper = { version = "0.14", features = ["client"] }
+ hyper = { version = "1", features = ["client"] }
Enter fullscreen mode Exit fullscreen mode

Server change log

hyper::Server had gone. I used conn module of auto HTTP version in hyper-util instead of the specific version in hyper since I wanted to support both http1 and http2.

The diff was like:

- use hyper::service::{make_service_fn, service_fn};
- use hyper::Server;
+ use hyper::{body, body::Bytes, service::service_fn, Request, Response};
+ use hyper_util::{
+     rt::{TokioExecutor, TokioIo},
+     server::conn::auto::Builder,
+ };
+ use tokio::net::TcpListener;
  (...)
  let addr = (...)
- let make_svc = make_service_fn(|_| {
-     async move {
-         let service = service_fn(move |req| handle(req));
-         Ok::<_, Infallible>(service)
-     }
- });
- 
- let server = Server::bind(&addr).serve(make_svc);
+ let listener = TcpListener::bind(addr)
+     .await
+     .expect("tcp listener failed to bind address");
+ loop {
+     let (stream, _) = listener
+         .accept()
+         .await
+         .expect("tcp listener failed to accept");
+     let io = TokioIo::new(stream);
+ 
+     tokio::task::spawn(async move {
+         if let Err(err) = Builder::new(TokioExecutor::new())
+             .serve_connection(
+                 io,
+                 service_fn(move |req: Request<body::Incoming>| service(req )),
+             )
+             .await
+         {
+             eprintln!("error serving connection: {:?}", err);
+         }
+     });
+ }
+ 
+ async fn service(
+     req: Request<body::Incoming>,
+ ) -> Result<Response<BoxBody>, hyper::http::Error> {
+     handle(req).await
+ }
Enter fullscreen mode Exit fullscreen mode

As it is relatively lower-level module, tokio requires to be dealt with together.

Client change log

In contrast, on client, I used module in hyper which supported the specific HTTP version. It was testing module whose HTTP version didn't affect the result.

- use hyper::{body::to_bytes, Body, Client, Request, Response, StatusCode, Uri};
- (...)
- let request = Request::builder()
-     .uri(uri)
-     .method("POST")
-     .header("Content-Type", "text/plain")
-     .body(Body::from(body.to_owned()))
-     .unwrap();
- let client = Client::new();
- let response = client.request(request).await.unwrap();

+ let stream = TcpStream::connect(addr).await.unwrap();
+ let io = TokioIo::new(stream);
+ let (mut sender, conn) = hyper::client::conn::http1::handshake(io).await.unwrap();
+ tokio::task::spawn(async move {
+     if let Err(err) = conn.await {
+         println!("Connection failed: {:?}", err);
+     }
+ });
+ (...)
+ let req = Request::builder()
+     .uri(path)
+     .header(hyper::header::HOST, authority.as_str())
+     .body(body)
+     .unwrap();
+ let res = sender.send_request(req).await.unwrap()
Enter fullscreen mode Exit fullscreen mode

hyper::client::conn::http1 is directly used as above.


Reference

Their official documentation and examples are really helpful :)

πŸ‘‹ While you are here

Reinvent your career. Join DEV.

It takes one minute and is worth it for your career.

Get started

Top comments (0)

πŸ‘‹ Kindness is contagious

Discover a treasure trove of wisdom within this insightful piece, highly respected in the nurturing DEV Community enviroment. Developers, whether novice or expert, are encouraged to participate and add to our shared knowledge basin.

A simple "thank you" can illuminate someone's day. Express your appreciation in the comments section!

On DEV, sharing ideas smoothens our journey and strengthens our community ties. Learn something useful? Offering a quick thanks to the author is deeply appreciated.

Okay