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.