axum/README.md

506 lines
13 KiB
Markdown
Raw Normal View History

2021-05-31 16:28:26 +02:00
# tower-web
2021-06-06 15:20:27 +02:00
tower-web (name pending) is a tiny web application framework that focuses on
2021-06-07 16:31:11 +02:00
ergonomics and modularity.
2021-05-31 16:28:26 +02:00
2021-06-06 15:20:27 +02:00
### Goals
2021-05-31 16:28:26 +02:00
2021-06-06 20:39:54 +02:00
- Ease of use. Building web apps in Rust should be as easy as `async fn
2021-06-06 15:20:27 +02:00
handle(Request) -> Response`.
- Solid foundation. tower-web is built on top of tower and makes it easy to
plug in any middleware from the [tower] and [tower-http] ecosystem.
2021-06-07 16:31:11 +02:00
- Focus on routing, extracting data from requests, and generating responses.
2021-06-08 21:21:20 +02:00
Tower middleware can handle the rest.
2021-06-06 15:20:27 +02:00
- Macro free core. Macro frameworks have their place but tower-web focuses
on providing a core that is macro free.
2021-05-31 16:28:26 +02:00
2021-06-08 21:21:20 +02:00
## Compatibility
tower-web is designed to work with [tokio] and [hyper]. Runtime and
transport layer independence is not a goal, at least for the time being.
2021-06-06 15:20:27 +02:00
## Example
2021-05-31 16:28:26 +02:00
2021-06-06 15:20:27 +02:00
The "Hello, World!" of tower-web is:
2021-05-31 16:28:26 +02:00
2021-06-06 15:20:27 +02:00
```rust
use tower_web::prelude::*;
use hyper::Server;
use std::net::SocketAddr;
use tower::make::Shared;
2021-06-01 00:47:12 +02:00
2021-06-06 15:20:27 +02:00
#[tokio::main]
async fn main() {
// build our application with a single route
2021-06-09 09:03:09 +02:00
let app = route("/", get(|| async { "Hello, World!" }));
2021-05-31 16:28:26 +02:00
2021-06-06 15:20:27 +02:00
// run it with hyper on localhost:3000
let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
2021-06-08 21:21:20 +02:00
Server::bind(&addr)
.serve(Shared::new(app))
.await
.unwrap();
2021-06-06 15:20:27 +02:00
}
2021-05-31 16:28:26 +02:00
```
2021-06-06 15:20:27 +02:00
## Routing
Routing between handlers looks like this:
2021-05-31 16:28:26 +02:00
```rust
2021-06-06 15:20:27 +02:00
use tower_web::prelude::*;
2021-05-31 16:28:26 +02:00
2021-06-06 15:20:27 +02:00
let app = route("/", get(get_slash).post(post_slash))
.route("/foo", get(get_foo));
2021-05-31 16:28:26 +02:00
2021-06-09 09:03:09 +02:00
async fn get_slash() {
2021-06-06 15:20:27 +02:00
// `GET /` called
}
2021-06-09 09:03:09 +02:00
async fn post_slash() {
2021-06-06 15:20:27 +02:00
// `POST /` called
}
2021-06-09 09:03:09 +02:00
async fn get_foo() {
2021-06-06 15:20:27 +02:00
// `GET /foo` called
2021-05-31 16:28:26 +02:00
}
```
2021-06-06 15:20:27 +02:00
Routes can also be dynamic like `/users/:id`. See ["Extracting data from
requests"](#extracting-data-from-requests) for more details on that.
## Responses
2021-06-08 21:21:20 +02:00
Anything that implements [`IntoResponse`](response::IntoResponse) can be
returned from a handler:
2021-05-31 16:28:26 +02:00
```rust
2021-06-06 15:20:27 +02:00
use tower_web::{body::Body, response::{Html, Json}, prelude::*};
2021-06-09 09:03:09 +02:00
use http::{StatusCode, Response, Uri};
2021-06-06 15:20:27 +02:00
use serde_json::{Value, json};
// We've already seen returning &'static str
2021-06-09 09:03:09 +02:00
async fn plain_text() -> &'static str {
2021-06-06 15:20:27 +02:00
"foo"
2021-05-31 16:28:26 +02:00
}
2021-06-06 15:20:27 +02:00
// String works too and will get a text/plain content-type
2021-06-09 09:03:09 +02:00
async fn plain_text_string(uri: Uri) -> String {
format!("Hi from {}", uri.path())
2021-05-31 16:28:26 +02:00
}
2021-06-06 15:20:27 +02:00
// Bytes will get a `application/octet-stream` content-type
2021-06-09 09:03:09 +02:00
async fn bytes() -> Vec<u8> {
2021-06-06 15:20:27 +02:00
vec![1, 2, 3, 4]
}
2021-05-31 16:28:26 +02:00
2021-06-06 15:20:27 +02:00
// `()` gives an empty response
2021-06-09 09:03:09 +02:00
async fn empty() {}
2021-06-06 15:20:27 +02:00
// `StatusCode` gives an empty response with that status code
2021-06-09 09:03:09 +02:00
async fn empty_with_status() -> StatusCode {
2021-06-06 15:20:27 +02:00
StatusCode::NOT_FOUND
2021-05-31 16:28:26 +02:00
}
2021-06-06 15:20:27 +02:00
// A tuple of `StatusCode` and something that implements `IntoResponse` can
// be used to override the status code
2021-06-09 09:03:09 +02:00
async fn with_status() -> (StatusCode, &'static str) {
2021-06-06 15:20:27 +02:00
(StatusCode::INTERNAL_SERVER_ERROR, "Something went wrong")
}
2021-05-31 16:32:56 +02:00
2021-06-06 15:20:27 +02:00
// `Html` gives a content-type of `text/html`
2021-06-09 09:03:09 +02:00
async fn html() -> Html<&'static str> {
2021-06-06 15:20:27 +02:00
Html("<h1>Hello, World!</h1>")
2021-05-31 16:32:56 +02:00
}
2021-06-08 21:21:20 +02:00
// `Json` gives a content-type of `application/json` and works with any type
2021-06-06 15:20:27 +02:00
// that implements `serde::Serialize`
2021-06-09 09:03:09 +02:00
async fn json() -> Json<Value> {
2021-06-06 15:20:27 +02:00
Json(json!({ "data": 42 }))
}
2021-05-31 16:28:26 +02:00
2021-06-06 15:20:27 +02:00
// `Result<T, E>` where `T` and `E` implement `IntoResponse` is useful for
// returning errors
2021-06-09 09:03:09 +02:00
async fn result() -> Result<&'static str, StatusCode> {
2021-06-06 15:20:27 +02:00
Ok("all good")
}
// `Response` gives full control
2021-06-09 09:03:09 +02:00
async fn response() -> Response<Body> {
2021-06-06 15:20:27 +02:00
Response::builder().body(Body::empty()).unwrap()
2021-05-31 16:28:26 +02:00
}
2021-06-06 15:20:27 +02:00
let app = route("/plain_text", get(plain_text))
.route("/plain_text_string", get(plain_text_string))
.route("/bytes", get(bytes))
.route("/empty", get(empty))
.route("/empty_with_status", get(empty_with_status))
.route("/with_status", get(with_status))
.route("/html", get(html))
.route("/json", get(json))
.route("/result", get(result))
.route("/response", get(response));
2021-05-31 16:28:26 +02:00
```
2021-06-06 15:20:27 +02:00
See the [`response`] module for more details.
## Extracting data from requests
2021-06-09 09:03:09 +02:00
A handler function is an async function take takes any number of
"extractors" as arguments. An extractor is a type that implements
[`FromRequest`](crate::extract::FromRequest).
2021-06-06 15:20:27 +02:00
2021-06-09 09:03:09 +02:00
For example, [`extract::Json`] is an extractor that consumes the request
body and deserializes it as JSON into some target type:
2021-05-31 16:28:26 +02:00
```rust
2021-06-06 15:20:27 +02:00
use tower_web::prelude::*;
use serde::Deserialize;
let app = route("/users", post(create_user));
#[derive(Deserialize)]
struct CreateUser {
email: String,
password: String,
}
2021-06-09 09:03:09 +02:00
async fn create_user(payload: extract::Json<CreateUser>) {
2021-06-06 15:20:27 +02:00
let payload: CreateUser = payload.0;
2021-06-01 12:08:00 +02:00
// ...
}
```
2021-06-06 15:20:27 +02:00
[`extract::UrlParams`] can be used to extract params from a dynamic URL. It
is compatible with any type that implements [`std::str::FromStr`], such as
[`Uuid`]:
2021-06-01 12:08:00 +02:00
```rust
2021-06-06 15:20:27 +02:00
use tower_web::prelude::*;
use uuid::Uuid;
2021-06-01 12:08:00 +02:00
2021-06-06 15:20:27 +02:00
let app = route("/users/:id", post(create_user));
2021-06-01 12:08:00 +02:00
2021-06-09 09:03:09 +02:00
async fn create_user(params: extract::UrlParams<(Uuid,)>) {
2021-06-08 21:21:20 +02:00
let user_id: Uuid = (params.0).0;
2021-06-01 12:08:00 +02:00
// ...
2021-05-31 16:28:26 +02:00
}
```
2021-06-06 15:20:27 +02:00
There is also [`UrlParamsMap`](extract::UrlParamsMap) which provide a map
like API for extracting URL params.
You can also apply multiple extractors:
2021-06-01 15:12:22 +02:00
```rust
2021-06-06 15:20:27 +02:00
use tower_web::prelude::*;
use uuid::Uuid;
use serde::Deserialize;
let app = route("/users/:id/things", get(get_user_things));
#[derive(Deserialize)]
struct Pagination {
page: usize,
per_page: usize,
}
impl Default for Pagination {
fn default() -> Self {
Self { page: 1, per_page: 30 }
}
}
async fn get_user_things(
params: extract::UrlParams<(Uuid,)>,
pagination: Option<extract::Query<Pagination>>,
) {
let user_id: Uuid = (params.0).0;
let pagination: Pagination = pagination.unwrap_or_default().0;
2021-06-01 15:12:22 +02:00
// ...
}
```
2021-06-09 09:03:09 +02:00
Additionally `Request<Body>` is itself an extractor:
```rust
use tower_web::prelude::*;
let app = route("/users/:id", post(handler));
async fn handler(req: Request<Body>) {
// ...
}
```
However it cannot be combined with other extractors since it consumes the
entire request.
2021-06-06 15:20:27 +02:00
See the [`extract`] module for more details.
[`Uuid`]: https://docs.rs/uuid/latest/uuid/
## Applying middleware
tower-web is designed to take full advantage of the tower and tower-http
ecosystem of middleware:
2021-06-08 21:21:20 +02:00
### Applying middleware to individual handlers
2021-06-06 15:20:27 +02:00
A middleware can be applied to a single handler like so:
2021-05-31 16:28:26 +02:00
```rust
2021-06-06 15:20:27 +02:00
use tower_web::prelude::*;
use tower::limit::ConcurrencyLimitLayer;
2021-06-01 12:08:00 +02:00
2021-06-06 15:20:27 +02:00
let app = route(
"/",
get(handler.layer(ConcurrencyLimitLayer::new(100))),
);
2021-06-09 09:03:09 +02:00
async fn handler() {}
2021-05-31 16:28:26 +02:00
```
2021-06-08 21:21:20 +02:00
### Applying middleware to groups of routes
2021-05-31 16:32:56 +02:00
2021-06-06 15:20:27 +02:00
Middleware can also be applied to a group of routes like so:
2021-05-31 16:28:26 +02:00
```rust
2021-06-06 15:20:27 +02:00
use tower_web::prelude::*;
use tower::limit::ConcurrencyLimitLayer;
2021-06-01 12:08:00 +02:00
2021-06-06 15:20:27 +02:00
let app = route("/", get(get_slash))
.route("/foo", post(post_foo))
.layer(ConcurrencyLimitLayer::new(100));
2021-05-31 16:28:26 +02:00
2021-06-09 09:03:09 +02:00
async fn get_slash() {}
2021-05-31 16:28:26 +02:00
2021-06-09 09:03:09 +02:00
async fn post_foo() {}
2021-06-06 15:20:27 +02:00
```
2021-05-31 16:28:26 +02:00
2021-06-06 15:20:27 +02:00
### Error handling
2021-06-01 12:08:00 +02:00
2021-06-06 15:20:27 +02:00
tower-web requires all errors to be handled. That is done by using
[`std::convert::Infallible`] as the error type in all its [`Service`]
implementations.
2021-05-31 16:28:26 +02:00
2021-06-06 15:20:27 +02:00
For handlers created from async functions this is works automatically since
2021-06-08 21:21:20 +02:00
handlers must return something that implements
[`IntoResponse`](response::IntoResponse), even if its a `Result`.
2021-06-01 12:08:00 +02:00
2021-06-06 15:20:27 +02:00
However middleware might add new failure cases that has to be handled. For
2021-06-08 21:21:20 +02:00
that tower-web provides a [`handle_error`](handler::Layered::handle_error)
combinator:
2021-05-31 16:28:26 +02:00
```rust
2021-06-06 15:20:27 +02:00
use tower_web::prelude::*;
use tower::{
BoxError, timeout::{TimeoutLayer, error::Elapsed},
};
use std::{borrow::Cow, time::Duration};
use http::StatusCode;
let app = route(
"/",
get(handle
.layer(TimeoutLayer::new(Duration::from_secs(30)))
// `Timeout` uses `BoxError` as the error type
.handle_error(|error: BoxError| {
// Check if the actual error type is `Elapsed` which
// `Timeout` returns
if error.is::<Elapsed>() {
return (StatusCode::REQUEST_TIMEOUT, "Request took too long".into());
}
// If we encounter some error we don't handle return a generic
// error
return (
StatusCode::INTERNAL_SERVER_ERROR,
// `Cow` lets us return either `&str` or `String`
Cow::from(format!("Unhandled internal error: {}", error)),
);
})),
);
2021-06-09 09:03:09 +02:00
async fn handle() {}
2021-05-31 16:28:26 +02:00
```
2021-06-08 21:21:20 +02:00
The closure passed to [`handle_error`](handler::Layered::handle_error) must
return something that implements [`IntoResponse`](response::IntoResponse).
2021-06-06 15:20:27 +02:00
2021-06-08 21:21:20 +02:00
[`handle_error`](routing::Layered::handle_error) is also available on a
group of routes with middleware applied:
2021-05-31 16:28:26 +02:00
```rust
2021-06-06 15:20:27 +02:00
use tower_web::prelude::*;
2021-06-08 21:21:20 +02:00
use tower::{BoxError, timeout::TimeoutLayer};
use std::time::Duration;
2021-06-06 15:20:27 +02:00
let app = route("/", get(handle))
2021-06-08 21:21:20 +02:00
.route("/foo", post(other_handle))
2021-06-06 15:20:27 +02:00
.layer(TimeoutLayer::new(Duration::from_secs(30)))
.handle_error(|error: BoxError| {
// ...
});
2021-06-09 09:03:09 +02:00
async fn handle() {}
2021-06-08 21:21:20 +02:00
2021-06-09 09:03:09 +02:00
async fn other_handle() {}
2021-06-06 15:20:27 +02:00
```
2021-05-31 16:28:26 +02:00
2021-06-06 15:20:27 +02:00
### Applying multiple middleware
[`tower::ServiceBuilder`] can be used to combine multiple middleware:
```rust
use tower_web::prelude::*;
use tower::{
ServiceBuilder, BoxError,
load_shed::error::Overloaded,
timeout::error::Elapsed,
};
use tower_http::compression::CompressionLayer;
use std::{borrow::Cow, time::Duration};
use http::StatusCode;
let middleware_stack = ServiceBuilder::new()
// Return an error after 30 seconds
2021-05-31 16:28:26 +02:00
.timeout(Duration::from_secs(30))
2021-06-06 15:20:27 +02:00
// Shed load if we're receiving too many requests
.load_shed()
// Process at most 100 requests concurrently
.concurrency_limit(100)
// Compress response bodies
2021-05-31 16:28:26 +02:00
.layer(CompressionLayer::new())
2021-06-06 15:20:27 +02:00
.into_inner();
let app = route("/", get(|_: Request<Body>| async { /* ... */ }))
.layer(middleware_stack)
.handle_error(|error: BoxError| {
if error.is::<Overloaded>() {
return (
StatusCode::SERVICE_UNAVAILABLE,
"Try again later".into(),
);
}
if error.is::<Elapsed>() {
return (
StatusCode::REQUEST_TIMEOUT,
"Request took too long".into(),
);
};
return (
StatusCode::INTERNAL_SERVER_ERROR,
Cow::from(format!("Unhandled internal error: {}", error)),
);
});
2021-05-31 16:28:26 +02:00
```
2021-06-06 15:20:27 +02:00
## Sharing state with handlers
It is common to share some state between handlers for example to share a
pool of database connections or clients to other services. That can be done
using the [`AddExtension`] middleware (applied with [`AddExtensionLayer`])
and the [`extract::Extension`] extractor:
2021-05-31 16:28:26 +02:00
```rust
2021-06-06 15:20:27 +02:00
use tower_web::{AddExtensionLayer, prelude::*};
use std::sync::Arc;
struct State {
// ...
}
2021-05-31 16:28:26 +02:00
2021-06-06 15:20:27 +02:00
let shared_state = Arc::new(State { /* ... */ });
2021-05-31 16:28:26 +02:00
2021-06-06 15:20:27 +02:00
let app = route("/", get(handler)).layer(AddExtensionLayer::new(shared_state));
2021-05-31 16:28:26 +02:00
2021-06-06 15:20:27 +02:00
async fn handler(
state: extract::Extension<Arc<State>>,
) {
let state: Arc<State> = state.0;
// ...
2021-05-31 16:28:26 +02:00
}
```
2021-06-06 15:20:27 +02:00
## Routing to any [`Service`]
tower-web also supports routing to general [`Service`]s:
```rust
use tower_web::{
// `ServiceExt` adds `handle_error` to any `Service`
2021-06-08 21:21:20 +02:00
service::{self, ServiceExt}, prelude::*,
2021-06-06 15:20:27 +02:00
};
use tower_http::services::ServeFile;
use http::Response;
use std::convert::Infallible;
use tower::{service_fn, BoxError};
let app = route(
// Any request to `/` goes to a service
"/",
service_fn(|_: Request<Body>| async {
let res = Response::new(Body::from("Hi from `GET /`"));
Ok::<_, Infallible>(res)
})
).route(
// GET `/static/Cargo.toml` goes to a service from tower-http
"/static/Cargo.toml",
service::get(
ServeFile::new("Cargo.toml")
// Errors must be handled
.handle_error(|error: std::io::Error| { /* ... */ })
)
);
```
2021-06-09 09:03:09 +02:00
Routing to arbitrary services in this way has complications for backpressure
([`Service::poll_ready`]). See the [`service`] module for more details.
2021-06-06 15:20:27 +02:00
## Nesting applications
2021-05-31 16:28:26 +02:00
2021-06-08 21:21:20 +02:00
Applications can be nested by calling [`nest`](routing::nest):
2021-06-06 20:39:54 +02:00
```rust
use tower_web::{prelude::*, routing::BoxRoute, body::BoxBody};
use tower_http::services::ServeFile;
use http::Response;
use std::convert::Infallible;
fn api_routes() -> BoxRoute<BoxBody> {
route("/users", get(|_: Request<Body>| async { /* ... */ })).boxed()
}
let app = route("/", get(|_: Request<Body>| async { /* ... */ }))
.nest("/api", api_routes());
```
2021-05-31 16:28:26 +02:00
2021-06-08 21:21:20 +02:00
[`nest`](routing::nest) can also be used to serve static files from a directory:
2021-06-07 16:31:11 +02:00
```rust
2021-06-08 21:21:20 +02:00
use tower_web::{prelude::*, service::ServiceExt, routing::nest};
2021-06-07 16:31:11 +02:00
use tower_http::services::ServeDir;
use http::Response;
use std::convert::Infallible;
use tower::{service_fn, BoxError};
let app = nest(
"/images",
ServeDir::new("public/images").handle_error(|error: std::io::Error| {
// ...
})
);
```
2021-06-06 15:20:27 +02:00
[tower]: https://crates.io/crates/tower
[tower-http]: https://crates.io/crates/tower-http
2021-06-08 21:21:20 +02:00
[tokio]: http://crates.io/crates/tokio
[hyper]: http://crates.io/crates/hyper