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