mirror of
https://github.com/tokio-rs/axum.git
synced 2026-08-28 00:00:20 +02:00
Make Request<Body> an extractor
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user