Make Request<Body> an extractor

This commit is contained in:
David Pedersen
2021-06-09 09:42:06 +02:00
parent 90c3e5ba74
commit c91dc7ce29
10 changed files with 268 additions and 200 deletions
+46 -35
View File
@@ -32,9 +32,7 @@ use tower::make::Shared;
#[tokio::main]
async fn main() {
// build our application with a single route
let app = route("/", get(|request: Request<Body>| async {
"Hello, World!"
}));
let app = route("/", get(|| async { "Hello, World!" }));
// run it with hyper on localhost:3000
let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
@@ -55,15 +53,15 @@ use tower_web::prelude::*;
let app = route("/", get(get_slash).post(post_slash))
.route("/foo", get(get_foo));
async fn get_slash(req: Request<Body>) {
async fn get_slash() {
// `GET /` called
}
async fn post_slash(req: Request<Body>) {
async fn post_slash() {
// `POST /` called
}
async fn get_foo(req: Request<Body>) {
async fn get_foo() {
// `GET /foo` called
}
```
@@ -78,57 +76,57 @@ returned from a handler:
```rust
use tower_web::{body::Body, response::{Html, Json}, prelude::*};
use http::{StatusCode, Response};
use http::{StatusCode, Response, Uri};
use serde_json::{Value, json};
// We've already seen returning &'static str
async fn plain_text(req: Request<Body>) -> &'static str {
async fn plain_text() -> &'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())
async fn plain_text_string(uri: Uri) -> String {
format!("Hi from {}", uri.path())
}
// Bytes will get a `application/octet-stream` content-type
async fn bytes(req: Request<Body>) -> Vec<u8> {
async fn bytes() -> Vec<u8> {
vec![1, 2, 3, 4]
}
// `()` gives an empty response
async fn empty(req: Request<Body>) {}
async fn empty() {}
// `StatusCode` gives an empty response with that status code
async fn empty_with_status(req: Request<Body>) -> StatusCode {
async fn empty_with_status() -> 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) {
async fn with_status() -> (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> {
async fn html() -> Html<&'static str> {
Html("<h1>Hello, World!</h1>")
}
// `Json` gives a content-type of `application/json` and works with any type
// that implements `serde::Serialize`
async fn json(req: Request<Body>) -> Json<Value> {
async fn json() -> 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> {
async fn result() -> Result<&'static str, StatusCode> {
Ok("all good")
}
// `Response` gives full control
async fn response(req: Request<Body>) -> Response<Body> {
async fn response() -> Response<Body> {
Response::builder().body(Body::empty()).unwrap()
}
@@ -148,13 +146,12 @@ 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.
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).
For example, [`extract::Json`] is an extractor that consumes the request body and
deserializes it as JSON into some target type:
For example, [`extract::Json`] is an extractor that consumes the request
body and deserializes it as JSON into some target type:
```rust
use tower_web::prelude::*;
@@ -168,7 +165,7 @@ struct CreateUser {
password: String,
}
async fn create_user(req: Request<Body>, payload: extract::Json<CreateUser>) {
async fn create_user(payload: extract::Json<CreateUser>) {
let payload: CreateUser = payload.0;
// ...
@@ -185,7 +182,7 @@ use uuid::Uuid;
let app = route("/users/:id", post(create_user));
async fn create_user(req: Request<Body>, params: extract::UrlParams<(Uuid,)>) {
async fn create_user(params: extract::UrlParams<(Uuid,)>) {
let user_id: Uuid = (params.0).0;
// ...
@@ -217,7 +214,6 @@ impl Default for Pagination {
}
async fn get_user_things(
req: Request<Body>,
params: extract::UrlParams<(Uuid,)>,
pagination: Option<extract::Query<Pagination>>,
) {
@@ -228,6 +224,21 @@ async fn get_user_things(
}
```
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.
See the [`extract`] module for more details.
[`Uuid`]: https://docs.rs/uuid/latest/uuid/
@@ -250,7 +261,7 @@ let app = route(
get(handler.layer(ConcurrencyLimitLayer::new(100))),
);
async fn handler(req: Request<Body>) {}
async fn handler() {}
```
### Applying middleware to groups of routes
@@ -265,9 +276,9 @@ let app = route("/", get(get_slash))
.route("/foo", post(post_foo))
.layer(ConcurrencyLimitLayer::new(100));
async fn get_slash(req: Request<Body>) {}
async fn get_slash() {}
async fn post_foo(req: Request<Body>) {}
async fn post_foo() {}
```
### Error handling
@@ -314,7 +325,7 @@ let app = route(
})),
);
async fn handle(req: Request<Body>) {}
async fn handle() {}
```
The closure passed to [`handle_error`](handler::Layered::handle_error) must
@@ -335,9 +346,9 @@ let app = route("/", get(handle))
// ...
});
async fn handle(req: Request<Body>) {}
async fn handle() {}
async fn other_handle(req: Request<Body>) {}
async fn other_handle() {}
```
### Applying multiple middleware
@@ -410,7 +421,6 @@ let shared_state = Arc::new(State { /* ... */ });
let app = route("/", get(handler)).layer(AddExtensionLayer::new(shared_state));
async fn handler(
req: Request<Body>,
state: extract::Extension<Arc<State>>,
) {
let state: Arc<State> = state.0;
@@ -451,7 +461,8 @@ let app = route(
);
```
See the [`service`] module for more details.
Routing to arbitrary services in this way has complications for backpressure
([`Service::poll_ready`]). See the [`service`] module for more details.
## Nesting applications