mirror of https://github.com/tokio-rs/axum
Change readme
This commit is contained in:
parent
a005427d40
commit
35ab973acb
607
README.md
607
README.md
|
@ -1,283 +1,462 @@
|
||||||
# tower-web
|
# tower-web
|
||||||
|
|
||||||
This is *not* https://github.com/carllerche/tower-web even though the name is
|
tower-web (name pending) is a tiny web application framework that focuses on
|
||||||
the same. Its just a prototype of a minimal HTTP framework I've been toying
|
ergonimics and modularity.
|
||||||
with. Will probably change the name to something else.
|
|
||||||
|
|
||||||
# What is this?
|
### Goals
|
||||||
|
|
||||||
## Goals
|
- Ease of use. Build web apps in Rust should be as easy as `async fn
|
||||||
|
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.
|
||||||
|
- Focus on routing, extracing data from requests, and generating responses.
|
||||||
|
tower middleware can handle the rest.
|
||||||
|
- Macro free core. Macro frameworks have their place but tower-web focuses
|
||||||
|
on providing a core that is macro free.
|
||||||
|
|
||||||
- As easy to use as tide. I don't really consider warp easy to use due to type
|
### Non-goals
|
||||||
tricks it uses. `fn route() -> impl Filter<...>` also isn't very ergonomic.
|
|
||||||
Just `async fn(Request) -> Response` would be nicer.
|
|
||||||
- Deep integration with Tower meaning you can
|
|
||||||
- Apply middleware to the entire application.
|
|
||||||
- Apply middleware to a single route.
|
|
||||||
- Apply middleware to subset of routes.
|
|
||||||
- Just focus on routing and generating responses. Tower can do the rest.
|
|
||||||
Want timeouts? Use `tower::timeout::Timeout`. Want logging? Use
|
|
||||||
`tower_http::trace::Trace`.
|
|
||||||
- Work with Tokio. tide is cool but requires async-std.
|
|
||||||
- Not macro based. Heavy macro based APIs can be very ergonomic but comes at a
|
|
||||||
complexity cost. Would like to see if I can design an API that is ergonomic
|
|
||||||
and doesn't require macros.
|
|
||||||
|
|
||||||
## Non-goals
|
- Runtime independent. tower-web is designed to work with tokio and hyper
|
||||||
|
and focused on bringing a good to experience to that stack.
|
||||||
|
- Speed. tower-web is a of course a fast framework, and wont be the
|
||||||
|
bottleneck in your app, but the goal is not to top the benchmarks.
|
||||||
|
|
||||||
- Runtime independent. If becoming runtime independent isn't too much then fine
|
## Example
|
||||||
but explicitly designing for runtime independence isn't a goal.
|
|
||||||
- Speed. As long as things are reasonably fast that is fine. For example using
|
|
||||||
async-trait for ergonomics is fine even though it comes at a cost.
|
|
||||||
|
|
||||||
# Example usage
|
The "Hello, World!" of tower-web is:
|
||||||
|
|
||||||
NOTE: Error handling has changed quite a bit and these examples are slightly out
|
|
||||||
of date. See the examples for working examples.
|
|
||||||
|
|
||||||
Defining a single route looks like this:
|
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
let app = tower_web::app().at("/").get(root);
|
use tower_web::prelude::*;
|
||||||
|
use hyper::Server;
|
||||||
|
use std::net::SocketAddr;
|
||||||
|
use tower::make::Shared;
|
||||||
|
|
||||||
async fn root(req: Request<Body>) -> &'static str {
|
#[tokio::main]
|
||||||
"Hello, World!"
|
async fn main() {
|
||||||
|
// build our application with a single route
|
||||||
|
let app = route("/", get(handler));
|
||||||
|
|
||||||
|
// run it with hyper on localhost:3000
|
||||||
|
let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
|
||||||
|
let server = Server::bind(&addr).serve(Shared::new(app));
|
||||||
|
server.await.unwrap();
|
||||||
}
|
}
|
||||||
```
|
|
||||||
|
|
||||||
Adding more routes follows the same pattern:
|
|
||||||
|
|
||||||
```rust
|
|
||||||
let app = tower_web::app()
|
|
||||||
.at("/")
|
|
||||||
.get(root)
|
|
||||||
.at("/users")
|
|
||||||
.get(users_index)
|
|
||||||
.post(users_create);
|
|
||||||
```
|
|
||||||
|
|
||||||
Handler functions are just async functions like:
|
|
||||||
|
|
||||||
```rust
|
|
||||||
async fn handler(req: Request<Body>) -> &'static str {
|
async fn handler(req: Request<Body>) -> &'static str {
|
||||||
"Hello, World!"
|
"Hello, World!"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
They must take the request as the first argument but all arguments following
|
## Routing
|
||||||
are called "extractors" and are used to extract data from the request (similar
|
|
||||||
to rocket but without macros):
|
Routing between handlers looks like this:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
#[derive(Deserialize)]
|
use tower_web::prelude::*;
|
||||||
struct UserPayload {
|
|
||||||
username: String,
|
let app = route("/", get(get_slash).post(post_slash))
|
||||||
|
.route("/foo", get(get_foo));
|
||||||
|
|
||||||
|
async fn get_slash(req: Request<Body>) {
|
||||||
|
// `GET /` called
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn post_slash(req: Request<Body>) {
|
||||||
|
// `POST /` called
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_foo(req: Request<Body>) {
|
||||||
|
// `GET /foo` called
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Routes can also be dynamic like `/users/:id`. See ["Extracting data from
|
||||||
|
requests"](#extracting-data-from-requests) for more details on that.
|
||||||
|
|
||||||
|
## Responses
|
||||||
|
|
||||||
|
Anything that implements [`IntoResponse`] can be returned from a handler:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use tower_web::{body::Body, response::{Html, Json}, prelude::*};
|
||||||
|
use http::{StatusCode, Response};
|
||||||
|
use serde_json::{Value, json};
|
||||||
|
|
||||||
|
// We've already seen returning &'static str
|
||||||
|
async fn plain_text(req: Request<Body>) -> &'static str {
|
||||||
|
"foo"
|
||||||
|
}
|
||||||
|
|
||||||
|
// String works too and will get a text/plain content-type
|
||||||
|
async fn plain_text_string(req: Request<Body>) -> String {
|
||||||
|
format!("Hi from {}", req.uri().path())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bytes will get a `application/octet-stream` content-type
|
||||||
|
async fn bytes(req: Request<Body>) -> Vec<u8> {
|
||||||
|
vec![1, 2, 3, 4]
|
||||||
|
}
|
||||||
|
|
||||||
|
// `()` gives an empty response
|
||||||
|
async fn empty(req: Request<Body>) {}
|
||||||
|
|
||||||
|
// `StatusCode` gives an empty response with that status code
|
||||||
|
async fn empty_with_status(req: Request<Body>) -> StatusCode {
|
||||||
|
StatusCode::NOT_FOUND
|
||||||
|
}
|
||||||
|
|
||||||
|
// A tuple of `StatusCode` and something that implements `IntoResponse` can
|
||||||
|
// be used to override the status code
|
||||||
|
async fn with_status(req: Request<Body>) -> (StatusCode, &'static str) {
|
||||||
|
(StatusCode::INTERNAL_SERVER_ERROR, "Something went wrong")
|
||||||
|
}
|
||||||
|
|
||||||
|
// `Html` gives a content-type of `text/html`
|
||||||
|
async fn html(req: Request<Body>) -> Html<&'static str> {
|
||||||
|
Html("<h1>Hello, World!</h1>")
|
||||||
|
}
|
||||||
|
|
||||||
|
// `Json` gives a content-type of `application/json` and works with my type
|
||||||
|
// that implements `serde::Serialize`
|
||||||
|
async fn json(req: Request<Body>) -> Json<Value> {
|
||||||
|
Json(json!({ "data": 42 }))
|
||||||
|
}
|
||||||
|
|
||||||
|
// `Result<T, E>` where `T` and `E` implement `IntoResponse` is useful for
|
||||||
|
// returning errors
|
||||||
|
async fn result(req: Request<Body>) -> Result<&'static str, StatusCode> {
|
||||||
|
Ok("all good")
|
||||||
|
}
|
||||||
|
|
||||||
|
// `Response` gives full control
|
||||||
|
async fn response(req: Request<Body>) -> Response<Body> {
|
||||||
|
Response::builder().body(Body::empty()).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
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));
|
||||||
|
```
|
||||||
|
|
||||||
|
See the [`response`] module for more details.
|
||||||
|
|
||||||
|
## Extracting data from requests
|
||||||
|
|
||||||
|
A handler function must always take `Request<Body>` as its first argument
|
||||||
|
but any arguments following are called "extractors". Any type that
|
||||||
|
implements [`FromRequest`](crate::extract::FromRequest) can be used as an
|
||||||
|
extractor.
|
||||||
|
|
||||||
|
[`extract::Json`] is an extractor that consumes the request body and
|
||||||
|
deserializes as as JSON into some target type:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use tower_web::prelude::*;
|
||||||
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
let app = route("/users", post(create_user));
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct CreateUser {
|
||||||
|
email: String,
|
||||||
|
password: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn create_user(req: Request<Body>, payload: extract::Json<CreateUser>) {
|
||||||
|
let payload: CreateUser = payload.0;
|
||||||
|
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
[`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`]:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use tower_web::prelude::*;
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
let app = route("/users/:id", post(create_user));
|
||||||
|
|
||||||
|
async fn create_user(req: Request<Body>, params: extract::UrlParams<(Uuid,)>) {
|
||||||
|
let (user_id,) = params.0;
|
||||||
|
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
There is also [`UrlParamsMap`](extract::UrlParamsMap) which provide a map
|
||||||
|
like API for extracting URL params.
|
||||||
|
|
||||||
|
You can also apply multiple extractors:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use tower_web::prelude::*;
|
||||||
|
use uuid::Uuid;
|
||||||
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
let app = route("/users/:id/things", get(get_user_things));
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
struct Pagination {
|
struct Pagination {
|
||||||
page: usize,
|
page: usize,
|
||||||
per_page: usize,
|
per_page: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn handler(
|
impl Default for Pagination {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self { page: 1, per_page: 30 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_user_things(
|
||||||
req: Request<Body>,
|
req: Request<Body>,
|
||||||
// deserialize response body with `serde_json` into a `UserPayload`
|
params: extract::UrlParams<(Uuid,)>,
|
||||||
user: extract::Json<UserPayload>,
|
pagination: Option<extract::Query<Pagination>>,
|
||||||
// deserialize query string into a `Pagination`
|
) {
|
||||||
pagination: extract::Query<Pagination>,
|
let user_id: Uuid = (params.0).0;
|
||||||
) -> &'static str {
|
let pagination: Pagination = pagination.unwrap_or_default().0;
|
||||||
let user: UserPayload = user.0;
|
|
||||||
let pagination: Pagination = pagination.0;
|
|
||||||
|
|
||||||
// ...
|
// ...
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
The inputs can also be optional:
|
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:
|
||||||
|
|
||||||
|
### To individual handlers
|
||||||
|
|
||||||
|
A middleware can be applied to a single handler like so:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
async fn handler(
|
use tower_web::prelude::*;
|
||||||
req: Request<Body>,
|
use tower::limit::ConcurrencyLimitLayer;
|
||||||
user: Option<extract::Json<UserPayload>>,
|
|
||||||
) -> &'static str {
|
let app = route(
|
||||||
// ...
|
"/",
|
||||||
}
|
get(handler.layer(ConcurrencyLimitLayer::new(100))),
|
||||||
|
);
|
||||||
|
|
||||||
|
async fn handler(req: Request<Body>) {}
|
||||||
```
|
```
|
||||||
|
|
||||||
You can also get the raw response body:
|
### To groups of routes
|
||||||
|
|
||||||
|
Middleware can also be applied to a group of routes like so:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
async fn handler(
|
use tower_web::prelude::*;
|
||||||
req: Request<Body>,
|
use tower::limit::ConcurrencyLimitLayer;
|
||||||
// buffer the whole request body
|
|
||||||
body: Bytes,
|
let app = route("/", get(get_slash))
|
||||||
) -> &'static str {
|
.route("/foo", post(post_foo))
|
||||||
// ...
|
.layer(ConcurrencyLimitLayer::new(100));
|
||||||
}
|
|
||||||
|
async fn get_slash(req: Request<Body>) {}
|
||||||
|
|
||||||
|
async fn post_foo(req: Request<Body>) {}
|
||||||
```
|
```
|
||||||
|
|
||||||
Or limit the body size:
|
### Error handling
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
For handlers created from async functions this is works automatically since
|
||||||
|
handlers must return something that implements [`IntoResponse`], even if its
|
||||||
|
a `Result`.
|
||||||
|
|
||||||
|
However middleware might add new failure cases that has to be handled. For
|
||||||
|
that tower-web provides a `handle_error` combinator:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
async fn handler(
|
use tower_web::prelude::*;
|
||||||
req: Request<Body>,
|
use tower::{
|
||||||
// max body size in bytes
|
BoxError, timeout::{TimeoutLayer, error::Elapsed},
|
||||||
body: extract::BytesMaxLength<1024>,
|
};
|
||||||
) -> &'static str {
|
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)),
|
||||||
|
);
|
||||||
|
})),
|
||||||
|
);
|
||||||
|
|
||||||
|
async fn handle(req: Request<Body>) {}
|
||||||
```
|
```
|
||||||
|
|
||||||
Params from dynamic routes like `GET /users/:id` can be extracted like so
|
The closure passed to `handle_error` must return something that implements
|
||||||
|
`IntoResponse`.
|
||||||
|
|
||||||
|
`handle_error` is also available on a group of routes with middleware
|
||||||
|
applied:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
async fn handle(
|
use tower_web::prelude::*;
|
||||||
req: Request<Body>,
|
use tower::{
|
||||||
// get a map of key value pairs
|
BoxError, timeout::{TimeoutLayer, error::Elapsed},
|
||||||
map: extract::UrlParamsMap,
|
};
|
||||||
) -> &'static str {
|
use std::{borrow::Cow, time::Duration};
|
||||||
let raw_id: Option<&str> = map.get("id");
|
use http::StatusCode;
|
||||||
let parsed_id: Option<i32> = map.get_typed::<i32>("id");
|
|
||||||
|
|
||||||
// ...
|
let app = route("/", get(handle))
|
||||||
}
|
.layer(TimeoutLayer::new(Duration::from_secs(30)))
|
||||||
|
.handle_error(|error: BoxError| {
|
||||||
|
// ...
|
||||||
|
});
|
||||||
|
|
||||||
async fn handle(
|
async fn handle(req: Request<Body>) {}
|
||||||
req: Request<Body>,
|
|
||||||
// or get a tuple with each param
|
|
||||||
params: extract::UrlParams<(i32, String)>,
|
|
||||||
) -> &'static str {
|
|
||||||
let (id, name) = params.0;
|
|
||||||
|
|
||||||
// ...
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
If you wanna go all out you can even deconstruct the extractor directly in the
|
### Applying multiple middleware
|
||||||
function signature:
|
|
||||||
|
[`tower::ServiceBuilder`] can be used to combine multiple middleware:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
async fn handle(
|
use tower_web::prelude::*;
|
||||||
req: Request<Body>,
|
use tower::{
|
||||||
UrlParams((id, name)): UrlParams<(i32, String)>,
|
ServiceBuilder, BoxError,
|
||||||
) -> &'static str {
|
load_shed::error::Overloaded,
|
||||||
// ...
|
timeout::error::Elapsed,
|
||||||
}
|
};
|
||||||
```
|
use tower_http::compression::CompressionLayer;
|
||||||
|
use std::{borrow::Cow, time::Duration};
|
||||||
|
use http::StatusCode;
|
||||||
|
|
||||||
Anything that implements `FromRequest` can work as an extractor where
|
let middleware_stack = ServiceBuilder::new()
|
||||||
`FromRequest` is an async trait:
|
// Return an error after 30 seconds
|
||||||
|
|
||||||
```rust
|
|
||||||
#[async_trait]
|
|
||||||
pub trait FromRequest: Sized {
|
|
||||||
type Rejection: IntoResponse<B>;
|
|
||||||
|
|
||||||
async fn from_request(req: &mut Request<Body>) -> Result<Self, Self::Rejection>;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
This "extractor" pattern is inspired by Bevy's ECS. The idea is that it should
|
|
||||||
be easy to pick apart the request without having to repeat yourself a lot or use
|
|
||||||
macros.
|
|
||||||
|
|
||||||
The return type must implement `IntoResponse`:
|
|
||||||
|
|
||||||
```rust
|
|
||||||
async fn empty_response(req: Request<Body>) {
|
|
||||||
// ...
|
|
||||||
}
|
|
||||||
|
|
||||||
// gets `content-type: text/plain`
|
|
||||||
async fn string_response(req: Request<Body>) -> String {
|
|
||||||
// ...
|
|
||||||
}
|
|
||||||
|
|
||||||
// gets `content-type: appliation/json`. `Json` can contain any `T: Serialize`
|
|
||||||
async fn json_response(req: Request<Body>) -> response::Json<User> {
|
|
||||||
// ...
|
|
||||||
}
|
|
||||||
|
|
||||||
// gets `content-type: text/html`. `Html` can contain any `T: Into<Bytes>`
|
|
||||||
async fn html_response(req: Request<Body>) -> response::Html<String> {
|
|
||||||
// ...
|
|
||||||
}
|
|
||||||
|
|
||||||
// or for full control
|
|
||||||
async fn response(req: Request<Body>) -> Response<Body> {
|
|
||||||
// ...
|
|
||||||
}
|
|
||||||
|
|
||||||
// Result is supported if each type implements `IntoResponse`
|
|
||||||
async fn response(req: Request<Body>) -> Result<Html<String>, StatusCode> {
|
|
||||||
// ...
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
This makes error handling quite simple. Basically handlers are not allowed to
|
|
||||||
fail and must always produce a response. This also means users are in charge of
|
|
||||||
how their errors are mapped to responses rather than a framework providing some
|
|
||||||
opaque catch all error type.
|
|
||||||
|
|
||||||
You can also apply Tower middleware to single routes:
|
|
||||||
|
|
||||||
```rust
|
|
||||||
let app = tower_web::app()
|
|
||||||
.at("/")
|
|
||||||
.get(send_some_large_file.layer(CompressionLayer::new()))
|
|
||||||
```
|
|
||||||
|
|
||||||
Or to the whole app:
|
|
||||||
|
|
||||||
```rust
|
|
||||||
let service = tower_web::app()
|
|
||||||
.at("/")
|
|
||||||
.get(root)
|
|
||||||
.into_service()
|
|
||||||
|
|
||||||
let app = ServiceBuilder::new()
|
|
||||||
.timeout(Duration::from_secs(30))
|
.timeout(Duration::from_secs(30))
|
||||||
.layer(TraceLayer::new_for_http())
|
// Shed load if we're receiving too many requests
|
||||||
|
.load_shed()
|
||||||
|
// Process at most 100 requests concurrently
|
||||||
|
.concurrency_limit(100)
|
||||||
|
// Compress response bodies
|
||||||
.layer(CompressionLayer::new())
|
.layer(CompressionLayer::new())
|
||||||
.service(app);
|
.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)),
|
||||||
|
);
|
||||||
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
And of course run it with Hyper:
|
## 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:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
#[tokio::main]
|
use tower_web::{AddExtensionLayer, prelude::*};
|
||||||
async fn main() {
|
use std::sync::Arc;
|
||||||
tracing_subscriber::fmt::init();
|
|
||||||
|
|
||||||
// build our application with some routes
|
struct State {
|
||||||
let app = tower_web::app()
|
// ...
|
||||||
.at("/")
|
}
|
||||||
.get(handler)
|
|
||||||
// convert it into a `Service`
|
|
||||||
.into_service();
|
|
||||||
|
|
||||||
// add some middleware
|
let shared_state = Arc::new(State { /* ... */ });
|
||||||
let app = ServiceBuilder::new()
|
|
||||||
.layer(TraceLayer::new_for_http())
|
|
||||||
.service(app);
|
|
||||||
|
|
||||||
// run it
|
let app = route("/", get(handler)).layer(AddExtensionLayer::new(shared_state));
|
||||||
let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
|
|
||||||
tracing::debug!("listening on {}", addr);
|
async fn handler(
|
||||||
let server = Server::bind(&addr).serve(Shared::new(app));
|
req: Request<Body>,
|
||||||
server.await.unwrap();
|
state: extract::Extension<Arc<State>>,
|
||||||
|
) {
|
||||||
|
let state: Arc<State> = state.0;
|
||||||
|
|
||||||
|
// ...
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
See the examples directory for more examples.
|
## Routing to any [`Service`]
|
||||||
|
|
||||||
# TODO
|
tower-web also supports routing to general [`Service`]s:
|
||||||
|
|
||||||
- `RouteBuilder` should have an `async fn serve(self) -> Result<(),
|
```rust
|
||||||
hyper::Error>` for users who just wanna create a hyper server and not care
|
use tower_web::{
|
||||||
about the lower level details. Should be gated by a `hyper` feature.
|
service, prelude::*,
|
||||||
- Each new route makes a new allocation for the response body, since `Or` needs
|
// `ServiceExt` adds `handle_error` to any `Service`
|
||||||
to unify the response body types. Would be nice to find a way to avoid that.
|
ServiceExt,
|
||||||
- It should be possible to package some routes together and apply a tower
|
};
|
||||||
middleware to that collection and then merge those routes into the app.
|
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| { /* ... */ })
|
||||||
|
)
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
See the [`service`] module for more details.
|
||||||
|
|
||||||
|
## Nesting applications
|
||||||
|
|
||||||
|
TODO
|
||||||
|
|
||||||
|
[tower]: https://crates.io/crates/tower
|
||||||
|
[tower-http]: https://crates.io/crates/tower-http
|
||||||
|
|
Loading…
Reference in New Issue