[−][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 |
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. |