Happy Friday!
Today, Tower Web 0.3 has been released and it comes with two major new features:
- Experimental support for async/await.
- Support for using templates to render responses.
Async/await support
In case you haven’t heard, async
/await
is a big new feature that is being
worked on for Rust. It aims to make asynchronous programming easy (well, at
least a little bit easier than it is today). The work has been on going for a
while and is already usable today on the Rust nightly channel.
With Tower Web 0.3, you can easily implement handlers using this new syntax.
struct MyResource;
impl_web! {
impl MyResource {
#[get("/")]
async fn index(&self) -> String {
let data = await!(load_data());
format!("Data = {}", data)
}
}
}
In this example, load_data
is an asynchronous operation. The await!
macro
waits (asynchronously) for the result of load_data()
before continuing to
process the response. This enables asynchronous implementations that feel like
writing blocking code.
Tower Web’s async
/ await
support is based on
Tokio’s. To get started, you will
need to be on a recent Rust nightly release. In your Cargo.toml
file, you will
need to opt into Rust 2018 edition as well as Tower Web’s async-await-preview
feature flag.
[package]
# ...
edition = "2018"
[dependencies]
tower-web = { version = "0.3.0", features = ["async-await-preview"] }
# ...
You will also need to enable some unstable features:
#![feature(await_macro, async_await, futures_api)]
Implementation wise, there is not much of a difference between how Tower Web
handles async
functions compared to functions returning impl Future
. The
response future ends up being boxed into a trait object.
For a slightly more complex example, lets look at defining a response handler that issues an HTTP request of its own.
#[derive(Clone)]
pub struct HelloWorld {
client: HttpClient,
}
impl_web! {
impl HelloWorld {
#[get("/")]
async fn hello_world(&self) -> String {
// Get the URI of the server by issuing a query to `/ip`.
let uri = "http://httpbin.org/ip".parse().unwrap();
// Issue the request and wait for the response
let response = await!(self.client.get(uri)).unwrap();
// Get the body component of the HTTP response. This is a stream and as such, it must
// be asynchronously collected.
let mut body = response.into_body();
// The body chunks will be appended to this string.
let mut ret = String::new();
while let Some(chunk) = await!(body.next()) {
let chunk = chunk.unwrap();
// Convert to a string
let chunk = str::from_utf8(&chunk[..]).unwrap();
// Append to buffer
ret.push_str(chunk);
}
// Return the collected string
ret
}
}
}
You find see the full example here.
Template support
Templates may now be used to render responses. This is done by returning a “plain old Rust struct” as the return value and using an attribute to define the template to use to render the response. The template engine will render the template, populating variables using the data returned from the response handler.
This work paves the way to support custom response serializer. A response serializer handles a specific content type and is able to take a “plain old Rust types”, converting it into a response of that content type.
Let’s look at how it works in practice.
First, the template engine has to be added to the service. Currently, only the Handlebars template engine is supported, but this will change over time.
use tower_web::view::Handlebars;
ServiceBuilder::new()
.resource(MyResource)
.serializer(Handlebars::new())
.run(&addr)
.unwrap();
By default, the Handlebars
serializer expectes a templates
directory at the
root of your crate. Templates are added to this directory. For example,
hello_world.hbs
is added containing the following:
<html>
<head>{{ title }}</head>
<body>
<div><h1>Hello World</h1></div>
</body>
</html>
The template engine replaces{{ title }}
with the value of the title
field
from the response handler’s return value.
Now the response handler is defined:
#[derive(Debug, Response)]
struct MyResponse {
title: &'static str,
}
impl_web! {
impl MyResource {
#[get("/")]
#[content_type("html")]
#[web(template = "hello_world")]
fn hello_world(&self) -> Result<MyResponse, ()> {
Ok(MyResponse {
title: "Handler variable",
})
}
}
}
MyResponse
contains a field named title
. This will be used to populate the
HTML title in the template. The response handler is annotated with#[content_type("html")]
and #[web(template = "hello_world")]
. The
content_type
annotation informs Tower Web that the response should be
serialized as HTML. The set of available serializers is searched for one that
handles this content type. The addedHandlebars
serializer handles the HTML
content type and is used to serialize MyResponse
. The Handlebars
serializer
looks for the template attribute to pick which template is used to serialize
MyResponse
.
Ideally, neither of these annotations would be required. Eventually, the content
type will be inferred using the request’s Accept
header and the template will
be picked using a convention. These are features for future releases.
You find see the full example here.
More to come
Work on Tower Web will continue. It is ground for experimenting with Web APIs
for Rust. It is allowing for abstractions like
BufStream
,
Middleware
,
serializers (discussed above), and more to be fleshed out. These abstractions
are being extracted as they are ready to higher level crates.
As mentioned previously, the plan is still to merge the ideas developed in Tower Web with Warp. This is taking longer than initially expected, but mostly because I hope to get to the point where the Tower Web abstractions are somewhat stable before merging.
As always, please try out Tower Web and provide feedback. The Tower Gitter channel is open for discussion.