Getting started with hyper 0.13 and async/await syntax

You might have noticed that the latest version of the rust HTTP framework hyper shipped with async/await syntax.

The syntax for writing a simple handler and launching the server now looks like this:

src/main.rs


#![feature(proc_macro_hygiene)]
#![allow(non_snake_case)]
#![allow(dead_code)]
#![allow(non_upper_case_globals)]

use hyper::{Body,Request,StatusCode,Method, Response, Server};
use hyper::service::{make_service_fn, service_fn};
use futures_util::TryStreamExt;

/// This is our service handler. It receives a Request, routes on its
/// path, and returns a Future of a Response.
async fn echo(req: Request<Body>) -> Result<Response<Body>, hyper::Error> {
    match (req.method(), req.uri().path()) {
        // Serve some instructions at /
        (&Method::GET, "/") => {
            Ok(Response::new(Body::from("Try POSTing data to /echo such as: `curl localhost:3000/echo -XPOST -d 'hello world'`")))
        }

        // Simply echo the body back to the client.
        (&Method::POST, "/echo") => {
            Ok(Response::new(req.into_body()))
        }

        // Convert to uppercase before sending back to client using a stream.
        (&Method::POST, "/echo/uppercase") => {
            let chunk_stream = req.into_body().map_ok(|chunk| {
                chunk
                    .iter()
                    .map(|byte| byte.to_ascii_uppercase())
                    .collect::<Vec<u8>>()
            });
            Ok(Response::new(Body::wrap_stream(chunk_stream)))
        }

        // Reverse the entire body before sending back to the client.
        //
        // Since we don't know the end yet, we can't simply stream
        // the chunks as they arrive as we did with the above uppercase endpoint.
        // So here we do `.await` on the future, waiting on concatenating the full body,
        // then afterwards the content can be reversed. Only then can we return a `Response`.
        (&Method::POST, "/echo/reversed") => {
            let whole_chunk = req.into_body().try_concat().await;

            let reversed_chunk = whole_chunk.map(move |chunk| {
                chunk.iter().rev().cloned().collect::<Vec<u8>>()

            })?;
            Ok(Response::new(Body::from(reversed_chunk)))
        }

        // Return the 404 Not Found for other routes.
        _ => {
            let mut not_found = Response::default();
            *not_found.status_mut() = StatusCode::NOT_FOUND;
            Ok(not_found)
        }
    }
}



#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
    dotenv::dotenv().ok();
    std::env::set_var(
        "RUST_LOG",
        "hyper_auth_server=debug,actix_web=info,actix_server=info",
    );
    std::env::set_var("RUST_BACKTRACE", "1"); 
     pretty_env_logger::init();

    let make_svc = make_service_fn(|_| {
        async {
            Ok::<_, hyper::Error>(service_fn(echo))
        }
    });

    let addr = ([127, 0, 0, 1], 8080).into();
    let server = Server::bind(&addr)
        .serve(make_svc);

    println!("Listening on http://{}", addr);

    server.await?;

    Ok(())
}

Before we can run that, we need to grab the right versions of our dependencies:

Cargo.toml

[package]
name = "hyper-async"
version = "0.1.0"
authors = ["Nicolas Marshall <{surname}.{name}@{gml}.com>"]
edition = "2018"

[dependencies]
hyper = "=0.13.0-alpha.1"
tokio = "0.2.0-alpha.4"
pretty_env_logger = "0.3"
futures-util-preview = { version = "=0.3.0-alpha.18" }
dotenv = "0.14.1"

Finally, we need a recent version of nightly rust. you can get it with this command:

rustup default | grep nightly-2019-09-08 $s || rustup default nightly-2019-09-08

Better yet, add this to a makefile so that anyone else can run your project and it will download the same version of rust that you developed with:

Makefile

.DEFAULT_GOAL: dev
dev: deps
    cargo watch -x run

deps: rust-version
rust-version:
    @rustup default | grep nightly-2019-09-08 $s || rustup default nightly-2019-09-08

Now run make to launch your server !

Testing the server

From a second terminal, run:

curl -XPOST localhost:8080/echo -d hola

Your server should answer hola