[][src]Crate tower_web

Tower Web is a fast web framework that aims to remove boilerplate.

The goal is to decouple all HTTP concepts from the application logic. You implement your application using "plain old Rust types" and Tower Web uses a macro to generate the necessary glue to serve the application as an HTTP service.

The bulk of Tower Web lies in the impl_web macro. Tower web also provides #[derive(Extract)] (for extracting data out of the HTTP request) and #[derive(Response)] (for converting a struct to an HTTP response).

The examples directory contains a number of examples showing how to use Tower Web.

impl_web!

The impl_web! macro wraps one or more impl blocks and generates Resource implementations. These structs may then be passed to ServiceBuilder.

struct MyApp;

impl_web! {
    impl MyApp {
        #[get("/")]
        fn index(&self) -> Result<String, ()> {
            // implementation
        }
    }
}

impl_web! looks for methods that have a routing attribute. These methods will be exposed from the web service. All other methods will be ignored.

Routing

Routing attributes start with an HTTP verb and contain a path that is matched. For example:

  • #[get("/")]
  • #[post("/foo")]
  • #[put("/zomg/hello/world")]

Captures

Path segments that begin with : are captures. They match any path segment and allow the resource method to get access to the value. For example:

struct MyApp;

impl_web! {
    impl MyApp {
        #[get("/hello/:msg")]
        fn index(&self, msg: String) -> Result<String, ()> {
            Ok(format!("Got: {}", msg))
        }
    }
}

The function argument is named msg. The macro will match the argument name with the capture name and call index, passing the value captured from the path as the first argument.

Method Arguments

impl_web! populates resource method arguments using data from the HTTP request. The name of the argument is important as it tells the macro what part of the request to use. The rules are as follows:

  • Path captures: when the argument name matches a capture name.
  • Query string: when the argument is named query_string.
  • Request body: when the argument is named body.
  • All other names are pulled from HTTP headers.

The type of all method arguments must implement Extract. So, for a list of possible argument types, see what implements Extract.

For example:

struct MyApp;

impl_web! {
    impl MyApp {
        #[get("/path/:capture")]
        fn index(&self, capture: String, query_string: String) -> Result<String, ()> {
            Ok(format!("capture={}; query_string={}", capture, query_string))
        }

        #[post("/upload")]
        fn upload(&self, content_type: String, body: Vec<u8>) -> Result<String, ()> {
            // implementation
        }
    }
}

Validation

The HTTP request can be validated by specifying an argument type that enforces an invariant. For example, if a path segment must be numeric, the argument should be specified as such:

struct MyApp;

impl_web! {
    impl MyApp {
        #[get("/users/:id")]
        fn get_user(&self, id: u32) -> Result<String, ()> {
            // implementation
        }
    }
}

In the previous example, requests to /users/123 will succeed but a request to /users/foo will result in a response with a status code of 400 (bad request).

Option is another useful type for validating the request. If an argument is of type Option, the request will not be rejected if the argument is not present. For example:

struct MyApp;

impl_web! {
    impl MyApp {
        #[get("/")]
        fn get(&self, x_required: String, x_optional: Option<String>) -> Result<String, ()> {
            // implementation
        }
    }
}

In the previous example, requests to / must provide a X-Required heeader, but may (or may not) provide a X-Optional header.

Return type

Resource methods return types are futures yielding items that implement Response. This includes types like:

The return type is either specified explicitly or impl Future can be used:

struct MyApp;

impl_web! {
    impl MyApp {
        #[get("/foo")]
        fn foo(&self) -> MyResponseFuture {
            // implementation
        }

        #[get("/bar")]
        fn bar(&self) -> impl Future<Item = String> + Send {
            // implementation
        }
    }
}

Note that impl Future is bound by Send. Hyper currently requires Send on all types. So, in order for our service to run with Hyper, we also need to ensure that everything is bound by Send.

See the examples directory for more examples on responding to requests.

Limitations

In order to work on stable Rust, impl_web! is implemented using proc-macro-hack, which comes with some limitations. The main one being that it can be used only once per scope. This doesn't cause problems in practice multiple resource implementations can be included in a single impl_web! clause:

impl_web! {
    impl Resource1 {
        // impl...
    }

    impl Resource2 {
        // impl...
    }

    // additional impls
}

derive(Extract)

Using derive(Extract) on a struct generates an Extract implementation, which enables the struct to be used as an resource method argument.

derive(Extract) calls Serde's derive(Deserialize) internally, so all the various Serde annotations apply here as well. See Serde's documentation for more details on those.

struct MyApp;

#[derive(Extract)]
struct MyData {
    foo: String,
    bar: u32,
    baz: Option<u32>,
}

impl_web! {
    impl MyApp {
        #[get("/")]
        fn index(&self, query_string: MyData) -> Result<String, ()> {
            // implementation
        }
    }
}

In the previous example, the query string will be deserialized into the MyQueryString struct and passed to the resource method. Both foo and bar are required, but baz is not. This means that the following query strings are acceptable:

  • ?foo=one&bar=2
  • ?foo=one&bar=2&baz=3

However, the following query strings will be rejected:

  • ?foo=one&bar=two: bar must be numeric
  • ?foo=one: bar is missing.

derive(Extract) can also be used to deserialize request bodies:

struct MyApp;

#[derive(Extract)]
struct MyData {
    foo: String,
    bar: u32,
    baz: Option<u32>,
}

impl_web! {
    impl MyApp {
        #[post("/data")]
        fn index(&self, body: MyData) -> Result<String, ()> {
            // implementation
        }
    }
}

This is the same example as earlier, but this time the argument is named body. This tells the macro to populate the argument by deserializing the request body. The request body is deserialized into an instance of MyData and passed to the resource method.

derive(Response)

Using derive(Response) on a struct generates a Response implementation, which enables the struct to be used as a resource method return type.

derive(Response) calls Serde's derive(Serialize) internally, so all the various Serde annotations apply here as well. See Serde's documentation for more details on those.

Tower Web provides some additional functionality on top of Serde. The following annotations can be used with derive(Response)

  • #[web(status)]
  • #[web(header)]

Using these two attributes allows configuring the HTTP response status code and header set.

For example:

struct MyApp;

#[derive(Response)]
#[web(status = "201")]
#[web(header(name = "x-foo", value = "bar"))]
struct MyData {
    foo: String,
}

impl_web! {
    impl MyApp {
        #[post("/data")]
        fn create(&self) -> Result<MyData, ()> {
            // implementation
        }
    }
}

In the previous example, the HTTP response generated by create will have an HTTP status code of 201 and includee the X-Foo HTTP header set to "bar".

These annotations may also be used to dynamically set the status code and response headers:

#[derive(Response)]
struct CustomResponse {
    #[web(status)]
    custom_status: u16,

    #[web(header)]
    x_foo: &'static str,
}

When responding with CustomResponse, the HTTP status code will be set to the value of the custom_status field and the X-Foo header will be set to the value of the x_foo field.

When a handler can return unrelated response types, like a file or a web page, derive(Response) can delegate the Response implementation to them, through an enum:

#[derive(Response)]
#[web(either)]
enum FileOrPage {
    File(tokio::fs::File),
    Page(String),
}

The web(either) attribute is only supported on enums whose variants a single unnamed field. Right now, the other web attributes have no effect when using web(either).

Starting a server

Once Resource implementations are generated, the types may be passed to ServiceBuilder::resource in order to define the web service.


let addr = "127.0.0.1:8080".parse().expect("Invalid address");
println!("Listening on http://{}", addr);

// A service builder is used to configure our service.
ServiceBuilder::new()
    // We add the resources that are part of the service.
    .resource(Resource1)
    .resource(Resource2)
    // We run the service
    .run(&addr)
    .unwrap();

Testing

Because web services build with Tower Web are "plain old Rust types" (PORT?), testing a method is done the exact same way you would test any other rust code.

struct MyApp;

impl_web! {
    impl MyApp {
        #[get("/:hello")]
        fn index(&self, hello: String) -> Result<&'static str, ()> {
            if hello == "hello" {
                Ok("correct")
            } else {
                Ok("nope")
            }
        }
    }
}

#[test]
fn test_my_app() {
    let app = MyApp;

    assert_eq!(app.index("hello".to_string()), Ok("correct"));
    assert_eq!(app.index("not-hello".to_string()), Ok("nope"));
}

Modules

config

Application level configuration.

error

Error types and traits.

extract

Extract data from the HTTP request.

middleware

Middleware traits and implementations.

net

Networking types and trait

response

Types and traits for responding to HTTP requests.

routing

Map HTTP requests to Resource methods.

service

Define the web service as a set of routes, resources, middlewares, serializers, ...

util

Utility types and traits.

view

Render content using templates

Macros

impl_web

Generate a Resource implementation based on the methods defined in the macro block.

Structs

Error

Errors that can happen inside Tower Web. The object of this type is serializable into "Problem Detail" as defined in RFC7807.

ErrorBuilder

Builder for Error objects.

ServiceBuilder

Configure and build a web service.