mirror of
https://github.com/tokio-rs/axum.git
synced 2026-08-29 00:00:18 +02:00
Make Request<Body> an extractor
This commit is contained in:
@@ -32,9 +32,7 @@ use tower::make::Shared;
|
|||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
// build our application with a single route
|
// build our application with a single route
|
||||||
let app = route("/", get(|request: Request<Body>| async {
|
let app = route("/", get(|| async { "Hello, World!" }));
|
||||||
"Hello, World!"
|
|
||||||
}));
|
|
||||||
|
|
||||||
// run it with hyper on localhost:3000
|
// run it with hyper on localhost:3000
|
||||||
let addr = SocketAddr::from(([127, 0, 0, 1], 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))
|
let app = route("/", get(get_slash).post(post_slash))
|
||||||
.route("/foo", get(get_foo));
|
.route("/foo", get(get_foo));
|
||||||
|
|
||||||
async fn get_slash(req: Request<Body>) {
|
async fn get_slash() {
|
||||||
// `GET /` called
|
// `GET /` called
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn post_slash(req: Request<Body>) {
|
async fn post_slash() {
|
||||||
// `POST /` called
|
// `POST /` called
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_foo(req: Request<Body>) {
|
async fn get_foo() {
|
||||||
// `GET /foo` called
|
// `GET /foo` called
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@@ -78,57 +76,57 @@ returned from a handler:
|
|||||||
|
|
||||||
```rust
|
```rust
|
||||||
use tower_web::{body::Body, response::{Html, Json}, prelude::*};
|
use tower_web::{body::Body, response::{Html, Json}, prelude::*};
|
||||||
use http::{StatusCode, Response};
|
use http::{StatusCode, Response, Uri};
|
||||||
use serde_json::{Value, json};
|
use serde_json::{Value, json};
|
||||||
|
|
||||||
// We've already seen returning &'static str
|
// We've already seen returning &'static str
|
||||||
async fn plain_text(req: Request<Body>) -> &'static str {
|
async fn plain_text() -> &'static str {
|
||||||
"foo"
|
"foo"
|
||||||
}
|
}
|
||||||
|
|
||||||
// String works too and will get a text/plain content-type
|
// String works too and will get a text/plain content-type
|
||||||
async fn plain_text_string(req: Request<Body>) -> String {
|
async fn plain_text_string(uri: Uri) -> String {
|
||||||
format!("Hi from {}", req.uri().path())
|
format!("Hi from {}", uri.path())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bytes will get a `application/octet-stream` content-type
|
// 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]
|
vec![1, 2, 3, 4]
|
||||||
}
|
}
|
||||||
|
|
||||||
// `()` gives an empty response
|
// `()` gives an empty response
|
||||||
async fn empty(req: Request<Body>) {}
|
async fn empty() {}
|
||||||
|
|
||||||
// `StatusCode` gives an empty response with that status code
|
// `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
|
StatusCode::NOT_FOUND
|
||||||
}
|
}
|
||||||
|
|
||||||
// A tuple of `StatusCode` and something that implements `IntoResponse` can
|
// A tuple of `StatusCode` and something that implements `IntoResponse` can
|
||||||
// be used to override the status code
|
// 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")
|
(StatusCode::INTERNAL_SERVER_ERROR, "Something went wrong")
|
||||||
}
|
}
|
||||||
|
|
||||||
// `Html` gives a content-type of `text/html`
|
// `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>")
|
Html("<h1>Hello, World!</h1>")
|
||||||
}
|
}
|
||||||
|
|
||||||
// `Json` gives a content-type of `application/json` and works with any type
|
// `Json` gives a content-type of `application/json` and works with any type
|
||||||
// that implements `serde::Serialize`
|
// that implements `serde::Serialize`
|
||||||
async fn json(req: Request<Body>) -> Json<Value> {
|
async fn json() -> Json<Value> {
|
||||||
Json(json!({ "data": 42 }))
|
Json(json!({ "data": 42 }))
|
||||||
}
|
}
|
||||||
|
|
||||||
// `Result<T, E>` where `T` and `E` implement `IntoResponse` is useful for
|
// `Result<T, E>` where `T` and `E` implement `IntoResponse` is useful for
|
||||||
// returning errors
|
// returning errors
|
||||||
async fn result(req: Request<Body>) -> Result<&'static str, StatusCode> {
|
async fn result() -> Result<&'static str, StatusCode> {
|
||||||
Ok("all good")
|
Ok("all good")
|
||||||
}
|
}
|
||||||
|
|
||||||
// `Response` gives full control
|
// `Response` gives full control
|
||||||
async fn response(req: Request<Body>) -> Response<Body> {
|
async fn response() -> Response<Body> {
|
||||||
Response::builder().body(Body::empty()).unwrap()
|
Response::builder().body(Body::empty()).unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -148,13 +146,12 @@ See the [`response`] module for more details.
|
|||||||
|
|
||||||
## Extracting data from requests
|
## Extracting data from requests
|
||||||
|
|
||||||
A handler function must always take `Request<Body>` as its first argument
|
A handler function is an async function take takes any number of
|
||||||
but any arguments following are called "extractors". Any type that
|
"extractors" as arguments. An extractor is a type that implements
|
||||||
implements [`FromRequest`](crate::extract::FromRequest) can be used as an
|
[`FromRequest`](crate::extract::FromRequest).
|
||||||
extractor.
|
|
||||||
|
|
||||||
For example, [`extract::Json`] is an extractor that consumes the request body and
|
For example, [`extract::Json`] is an extractor that consumes the request
|
||||||
deserializes it as JSON into some target type:
|
body and deserializes it as JSON into some target type:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
use tower_web::prelude::*;
|
use tower_web::prelude::*;
|
||||||
@@ -168,7 +165,7 @@ struct CreateUser {
|
|||||||
password: String,
|
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;
|
let payload: CreateUser = payload.0;
|
||||||
|
|
||||||
// ...
|
// ...
|
||||||
@@ -185,7 +182,7 @@ use uuid::Uuid;
|
|||||||
|
|
||||||
let app = route("/users/:id", post(create_user));
|
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;
|
let user_id: Uuid = (params.0).0;
|
||||||
|
|
||||||
// ...
|
// ...
|
||||||
@@ -217,7 +214,6 @@ impl Default for Pagination {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn get_user_things(
|
async fn get_user_things(
|
||||||
req: Request<Body>,
|
|
||||||
params: extract::UrlParams<(Uuid,)>,
|
params: extract::UrlParams<(Uuid,)>,
|
||||||
pagination: Option<extract::Query<Pagination>>,
|
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.
|
See the [`extract`] module for more details.
|
||||||
|
|
||||||
[`Uuid`]: https://docs.rs/uuid/latest/uuid/
|
[`Uuid`]: https://docs.rs/uuid/latest/uuid/
|
||||||
@@ -250,7 +261,7 @@ let app = route(
|
|||||||
get(handler.layer(ConcurrencyLimitLayer::new(100))),
|
get(handler.layer(ConcurrencyLimitLayer::new(100))),
|
||||||
);
|
);
|
||||||
|
|
||||||
async fn handler(req: Request<Body>) {}
|
async fn handler() {}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Applying middleware to groups of routes
|
### Applying middleware to groups of routes
|
||||||
@@ -265,9 +276,9 @@ let app = route("/", get(get_slash))
|
|||||||
.route("/foo", post(post_foo))
|
.route("/foo", post(post_foo))
|
||||||
.layer(ConcurrencyLimitLayer::new(100));
|
.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
|
### 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
|
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
|
### Applying multiple middleware
|
||||||
@@ -410,7 +421,6 @@ let shared_state = Arc::new(State { /* ... */ });
|
|||||||
let app = route("/", get(handler)).layer(AddExtensionLayer::new(shared_state));
|
let app = route("/", get(handler)).layer(AddExtensionLayer::new(shared_state));
|
||||||
|
|
||||||
async fn handler(
|
async fn handler(
|
||||||
req: Request<Body>,
|
|
||||||
state: extract::Extension<Arc<State>>,
|
state: extract::Extension<Arc<State>>,
|
||||||
) {
|
) {
|
||||||
let state: Arc<State> = state.0;
|
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
|
## Nesting applications
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use http::{Request, StatusCode};
|
use http::StatusCode;
|
||||||
use hyper::Server;
|
use hyper::Server;
|
||||||
use std::net::SocketAddr;
|
use std::net::SocketAddr;
|
||||||
use tower::make::Shared;
|
use tower::make::Shared;
|
||||||
@@ -18,11 +18,11 @@ async fn main() {
|
|||||||
server.await.unwrap();
|
server.await.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn handler(_req: Request<Body>) -> response::Html<&'static str> {
|
async fn handler() -> response::Html<&'static str> {
|
||||||
response::Html("<h1>Hello, World!</h1>")
|
response::Html("<h1>Hello, World!</h1>")
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn greet(_req: Request<Body>, params: extract::UrlParamsMap) -> Result<String, StatusCode> {
|
async fn greet(params: extract::UrlParamsMap) -> Result<String, StatusCode> {
|
||||||
if let Some(name) = params.get("name") {
|
if let Some(name) = params.get("name") {
|
||||||
Ok(format!("Hello {}!", name))
|
Ok(format!("Hello {}!", name))
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
//! ```
|
//! ```
|
||||||
|
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
use http::{Request, StatusCode};
|
use http::StatusCode;
|
||||||
use hyper::Server;
|
use hyper::Server;
|
||||||
use std::{
|
use std::{
|
||||||
borrow::Cow,
|
borrow::Cow,
|
||||||
@@ -22,7 +22,7 @@ use tower_http::{
|
|||||||
compression::CompressionLayer, trace::TraceLayer,
|
compression::CompressionLayer, trace::TraceLayer,
|
||||||
};
|
};
|
||||||
use tower_web::{
|
use tower_web::{
|
||||||
body::{Body, BoxBody},
|
body::BoxBody,
|
||||||
extract::{ContentLengthLimit, Extension, UrlParams},
|
extract::{ContentLengthLimit, Extension, UrlParams},
|
||||||
prelude::*,
|
prelude::*,
|
||||||
response::IntoResponse,
|
response::IntoResponse,
|
||||||
@@ -72,7 +72,6 @@ struct State {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn kv_get(
|
async fn kv_get(
|
||||||
_req: Request<Body>,
|
|
||||||
UrlParams((key,)): UrlParams<(String,)>,
|
UrlParams((key,)): UrlParams<(String,)>,
|
||||||
Extension(state): Extension<SharedState>,
|
Extension(state): Extension<SharedState>,
|
||||||
) -> Result<Bytes, StatusCode> {
|
) -> Result<Bytes, StatusCode> {
|
||||||
@@ -86,7 +85,6 @@ async fn kv_get(
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn kv_set(
|
async fn kv_set(
|
||||||
_req: Request<Body>,
|
|
||||||
UrlParams((key,)): UrlParams<(String,)>,
|
UrlParams((key,)): UrlParams<(String,)>,
|
||||||
ContentLengthLimit(bytes): ContentLengthLimit<Bytes, { 1024 * 5_000 }>, // ~5mb
|
ContentLengthLimit(bytes): ContentLengthLimit<Bytes, { 1024 * 5_000 }>, // ~5mb
|
||||||
Extension(state): Extension<SharedState>,
|
Extension(state): Extension<SharedState>,
|
||||||
@@ -94,7 +92,7 @@ async fn kv_set(
|
|||||||
state.write().unwrap().db.insert(key, bytes);
|
state.write().unwrap().db.insert(key, bytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn list_keys(_req: Request<Body>, Extension(state): Extension<SharedState>) -> String {
|
async fn list_keys(Extension(state): Extension<SharedState>) -> String {
|
||||||
let db = &state.read().unwrap().db;
|
let db = &state.read().unwrap().db;
|
||||||
|
|
||||||
db.keys()
|
db.keys()
|
||||||
@@ -104,12 +102,11 @@ async fn list_keys(_req: Request<Body>, Extension(state): Extension<SharedState>
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn admin_routes() -> BoxRoute<BoxBody> {
|
fn admin_routes() -> BoxRoute<BoxBody> {
|
||||||
async fn delete_all_keys(_req: Request<Body>, Extension(state): Extension<SharedState>) {
|
async fn delete_all_keys(Extension(state): Extension<SharedState>) {
|
||||||
state.write().unwrap().db.clear();
|
state.write().unwrap().db.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn remove_key(
|
async fn remove_key(
|
||||||
_req: Request<Body>,
|
|
||||||
UrlParams((key,)): UrlParams<(String,)>,
|
UrlParams((key,)): UrlParams<(String,)>,
|
||||||
Extension(state): Extension<SharedState>,
|
Extension(state): Extension<SharedState>,
|
||||||
) {
|
) {
|
||||||
|
|||||||
+83
-27
@@ -1,8 +1,8 @@
|
|||||||
//! Types and traits for extracting data from requests.
|
//! Types and traits for extracting data from requests.
|
||||||
//!
|
//!
|
||||||
//! A handler function must always take `Request<Body>` as its first argument
|
//! A handler function is an async function take takes any number of
|
||||||
//! but any arguments following are called "extractors". Any type that
|
//! "extractors" as arguments. An extractor is a type that implements
|
||||||
//! implements [`FromRequest`](FromRequest) can be used as an extractor.
|
//! [`FromRequest`](crate::extract::FromRequest).
|
||||||
//!
|
//!
|
||||||
//! For example, [`Json`] is an extractor that consumes the request body and
|
//! For example, [`Json`] is an extractor that consumes the request body and
|
||||||
//! deserializes it as JSON into some target type:
|
//! deserializes it as JSON into some target type:
|
||||||
@@ -17,7 +17,7 @@
|
|||||||
//! password: String,
|
//! 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;
|
//! let payload: CreateUser = payload.0;
|
||||||
//!
|
//!
|
||||||
//! // ...
|
//! // ...
|
||||||
@@ -52,7 +52,7 @@
|
|||||||
//! }
|
//! }
|
||||||
//! }
|
//! }
|
||||||
//!
|
//!
|
||||||
//! async fn handler(req: Request<Body>, user_agent: ExtractUserAgent) {
|
//! async fn handler(user_agent: ExtractUserAgent) {
|
||||||
//! let user_agent: HeaderValue = user_agent.0;
|
//! let user_agent: HeaderValue = user_agent.0;
|
||||||
//!
|
//!
|
||||||
//! // ...
|
//! // ...
|
||||||
@@ -73,7 +73,6 @@
|
|||||||
//! use std::collections::HashMap;
|
//! use std::collections::HashMap;
|
||||||
//!
|
//!
|
||||||
//! async fn handler(
|
//! async fn handler(
|
||||||
//! req: Request<Body>,
|
|
||||||
//! // Extract captured parameters from the URL
|
//! // Extract captured parameters from the URL
|
||||||
//! params: extract::UrlParamsMap,
|
//! params: extract::UrlParamsMap,
|
||||||
//! // Parse query string into a `HashMap`
|
//! // Parse query string into a `HashMap`
|
||||||
@@ -98,7 +97,7 @@
|
|||||||
//! use tower_web::{extract::Json, prelude::*};
|
//! use tower_web::{extract::Json, prelude::*};
|
||||||
//! use serde_json::Value;
|
//! use serde_json::Value;
|
||||||
//!
|
//!
|
||||||
//! async fn create_user(req: Request<Body>, payload: Option<Json<Value>>) {
|
//! async fn create_user(payload: Option<Json<Value>>) {
|
||||||
//! if let Some(payload) = payload {
|
//! if let Some(payload) = payload {
|
||||||
//! // We got a valid JSON payload
|
//! // We got a valid JSON payload
|
||||||
//! } else {
|
//! } else {
|
||||||
@@ -121,7 +120,7 @@
|
|||||||
//! use tower_web::{extract::Json, prelude::*};
|
//! use tower_web::{extract::Json, prelude::*};
|
||||||
//! use serde_json::Value;
|
//! use serde_json::Value;
|
||||||
//!
|
//!
|
||||||
//! async fn create_user(req: Request<Body>, Json(value): Json<Value>) {
|
//! async fn create_user(Json(value): Json<Value>) {
|
||||||
//! // `value` is of type `Value`
|
//! // `value` is of type `Value`
|
||||||
//! }
|
//! }
|
||||||
//!
|
//!
|
||||||
@@ -134,14 +133,14 @@
|
|||||||
use crate::{body::Body, response::IntoResponse};
|
use crate::{body::Body, response::IntoResponse};
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
use http::{header, Request, Response};
|
use http::{HeaderMap, Method, Request, Response, Uri, Version, header};
|
||||||
use rejection::{
|
use rejection::{
|
||||||
BodyAlreadyTaken, FailedToBufferBody, InvalidJsonBody, InvalidUrlParam, InvalidUtf8,
|
BodyAlreadyExtracted, FailedToBufferBody, InvalidJsonBody, InvalidUrlParam, InvalidUtf8,
|
||||||
LengthRequired, MissingExtension, MissingJsonContentType, MissingRouteParams, PayloadTooLarge,
|
LengthRequired, MissingExtension, MissingJsonContentType, MissingRouteParams, PayloadTooLarge,
|
||||||
QueryStringMissing, UrlParamsAlreadyTaken,
|
QueryStringMissing, RequestAlreadyExtracted, UrlParamsAlreadyExtracted,
|
||||||
};
|
};
|
||||||
use serde::de::DeserializeOwned;
|
use serde::de::DeserializeOwned;
|
||||||
use std::{collections::HashMap, convert::Infallible, str::FromStr};
|
use std::{collections::HashMap, mem, convert::Infallible, str::FromStr};
|
||||||
|
|
||||||
pub mod rejection;
|
pub mod rejection;
|
||||||
|
|
||||||
@@ -188,7 +187,7 @@ where
|
|||||||
///
|
///
|
||||||
/// // This will parse query strings like `?page=2&per_page=30` into `Pagination`
|
/// // This will parse query strings like `?page=2&per_page=30` into `Pagination`
|
||||||
/// // structs.
|
/// // structs.
|
||||||
/// async fn list_things(req: Request<Body>, pagination: extract::Query<Pagination>) {
|
/// async fn list_things(pagination: extract::Query<Pagination>) {
|
||||||
/// let pagination: Pagination = pagination.0;
|
/// let pagination: Pagination = pagination.0;
|
||||||
///
|
///
|
||||||
/// // ...
|
/// // ...
|
||||||
@@ -231,7 +230,7 @@ where
|
|||||||
/// password: String,
|
/// 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;
|
/// let payload: CreateUser = payload.0;
|
||||||
///
|
///
|
||||||
/// // ...
|
/// // ...
|
||||||
@@ -307,7 +306,7 @@ fn has_content_type<B>(req: &Request<B>, expected_content_type: &str) -> bool {
|
|||||||
/// // ...
|
/// // ...
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
/// async fn handler(req: Request<Body>, state: extract::Extension<Arc<State>>) {
|
/// async fn handler(state: extract::Extension<Arc<State>>) {
|
||||||
/// // ...
|
/// // ...
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
@@ -381,13 +380,68 @@ impl FromRequest for String {
|
|||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl FromRequest for Body {
|
impl FromRequest for Body {
|
||||||
type Rejection = BodyAlreadyTaken;
|
type Rejection = BodyAlreadyExtracted;
|
||||||
|
|
||||||
async fn from_request(req: &mut Request<Body>) -> Result<Self, Self::Rejection> {
|
async fn from_request(req: &mut Request<Body>) -> Result<Self, Self::Rejection> {
|
||||||
take_body(req)
|
take_body(req)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl FromRequest for Request<Body> {
|
||||||
|
type Rejection = RequestAlreadyExtracted;
|
||||||
|
|
||||||
|
async fn from_request(req: &mut Request<Body>) -> Result<Self, Self::Rejection> {
|
||||||
|
struct RequestAlreadyExtractedExt;
|
||||||
|
|
||||||
|
if req
|
||||||
|
.extensions_mut()
|
||||||
|
.insert(RequestAlreadyExtractedExt)
|
||||||
|
.is_some()
|
||||||
|
{
|
||||||
|
Err(RequestAlreadyExtracted)
|
||||||
|
} else {
|
||||||
|
Ok(mem::take(req))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl FromRequest for Method {
|
||||||
|
type Rejection = Infallible;
|
||||||
|
|
||||||
|
async fn from_request(req: &mut Request<Body>) -> Result<Self, Self::Rejection> {
|
||||||
|
Ok(req.method().clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl FromRequest for Uri {
|
||||||
|
type Rejection = Infallible;
|
||||||
|
|
||||||
|
async fn from_request(req: &mut Request<Body>) -> Result<Self, Self::Rejection> {
|
||||||
|
Ok(req.uri().clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl FromRequest for Version {
|
||||||
|
type Rejection = Infallible;
|
||||||
|
|
||||||
|
async fn from_request(req: &mut Request<Body>) -> Result<Self, Self::Rejection> {
|
||||||
|
Ok(req.version())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl FromRequest for HeaderMap {
|
||||||
|
type Rejection = Infallible;
|
||||||
|
|
||||||
|
async fn from_request(req: &mut Request<Body>) -> Result<Self, Self::Rejection> {
|
||||||
|
Ok(mem::take(req.headers_mut()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Extractor that will reject requests with a body larger than some size.
|
/// Extractor that will reject requests with a body larger than some size.
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
@@ -395,7 +449,7 @@ impl FromRequest for Body {
|
|||||||
/// ```rust,no_run
|
/// ```rust,no_run
|
||||||
/// use tower_web::prelude::*;
|
/// use tower_web::prelude::*;
|
||||||
///
|
///
|
||||||
/// async fn handler(req: Request<Body>, body: extract::ContentLengthLimit<String, 1024>) {
|
/// async fn handler(body: extract::ContentLengthLimit<String, 1024>) {
|
||||||
/// // ...
|
/// // ...
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
@@ -442,7 +496,7 @@ where
|
|||||||
/// ```rust,no_run
|
/// ```rust,no_run
|
||||||
/// use tower_web::prelude::*;
|
/// use tower_web::prelude::*;
|
||||||
///
|
///
|
||||||
/// async fn users_show(req: Request<Body>, params: extract::UrlParamsMap) {
|
/// async fn users_show(params: extract::UrlParamsMap) {
|
||||||
/// let id: Option<&str> = params.get("id");
|
/// let id: Option<&str> = params.get("id");
|
||||||
///
|
///
|
||||||
/// // ...
|
/// // ...
|
||||||
@@ -483,7 +537,7 @@ impl FromRequest for UrlParamsMap {
|
|||||||
if let Some(params) = params.take() {
|
if let Some(params) = params.take() {
|
||||||
Ok(Self(params.0.into_iter().collect()))
|
Ok(Self(params.0.into_iter().collect()))
|
||||||
} else {
|
} else {
|
||||||
Err(UrlParamsAlreadyTaken.into_response())
|
Err(UrlParamsAlreadyExtracted.into_response())
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Err(MissingRouteParams.into_response())
|
Err(MissingRouteParams.into_response())
|
||||||
@@ -500,7 +554,6 @@ impl FromRequest for UrlParamsMap {
|
|||||||
/// use uuid::Uuid;
|
/// use uuid::Uuid;
|
||||||
///
|
///
|
||||||
/// async fn users_teams_show(
|
/// async fn users_teams_show(
|
||||||
/// req: Request<Body>,
|
|
||||||
/// UrlParams(params): UrlParams<(Uuid, Uuid)>,
|
/// UrlParams(params): UrlParams<(Uuid, Uuid)>,
|
||||||
/// ) {
|
/// ) {
|
||||||
/// let user_id: Uuid = params.0;
|
/// let user_id: Uuid = params.0;
|
||||||
@@ -538,7 +591,7 @@ macro_rules! impl_parse_url {
|
|||||||
if let Some(params) = params.take() {
|
if let Some(params) = params.take() {
|
||||||
params.0
|
params.0
|
||||||
} else {
|
} else {
|
||||||
return Err(UrlParamsAlreadyTaken.into_response());
|
return Err(UrlParamsAlreadyExtracted.into_response());
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return Err(MissingRouteParams.into_response())
|
return Err(MissingRouteParams.into_response())
|
||||||
@@ -572,14 +625,17 @@ macro_rules! impl_parse_url {
|
|||||||
|
|
||||||
impl_parse_url!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16);
|
impl_parse_url!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16);
|
||||||
|
|
||||||
fn take_body(req: &mut Request<Body>) -> Result<Body, BodyAlreadyTaken> {
|
fn take_body(req: &mut Request<Body>) -> Result<Body, BodyAlreadyExtracted> {
|
||||||
struct BodyAlreadyTakenExt;
|
struct BodyAlreadyExtractedExt;
|
||||||
|
|
||||||
if req.extensions_mut().insert(BodyAlreadyTakenExt).is_some() {
|
if req
|
||||||
Err(BodyAlreadyTaken)
|
.extensions_mut()
|
||||||
|
.insert(BodyAlreadyExtractedExt)
|
||||||
|
.is_some()
|
||||||
|
{
|
||||||
|
Err(BodyAlreadyExtracted)
|
||||||
} else {
|
} else {
|
||||||
let body = std::mem::take(req.body_mut());
|
Ok(mem::take(req.body_mut()))
|
||||||
Ok(body)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -129,7 +129,7 @@ define_rejection! {
|
|||||||
#[status = INTERNAL_SERVER_ERROR]
|
#[status = INTERNAL_SERVER_ERROR]
|
||||||
#[body = "Cannot have two URL capture extractors for a single handler"]
|
#[body = "Cannot have two URL capture extractors for a single handler"]
|
||||||
/// Rejection type used if you try and extract the URL params more than once.
|
/// Rejection type used if you try and extract the URL params more than once.
|
||||||
pub struct UrlParamsAlreadyTaken;
|
pub struct UrlParamsAlreadyExtracted;
|
||||||
}
|
}
|
||||||
|
|
||||||
define_rejection! {
|
define_rejection! {
|
||||||
@@ -137,7 +137,14 @@ define_rejection! {
|
|||||||
#[body = "Cannot have two request body extractors for a single handler"]
|
#[body = "Cannot have two request body extractors for a single handler"]
|
||||||
/// Rejection type used if you try and extract the request body more than
|
/// Rejection type used if you try and extract the request body more than
|
||||||
/// once.
|
/// once.
|
||||||
pub struct BodyAlreadyTaken;
|
pub struct BodyAlreadyExtracted;
|
||||||
|
}
|
||||||
|
|
||||||
|
define_rejection! {
|
||||||
|
#[status = INTERNAL_SERVER_ERROR]
|
||||||
|
#[body = "Cannot have two `Request<Body>` extractors for a single handler"]
|
||||||
|
/// Rejection type used if you try and extract the request more than once.
|
||||||
|
pub struct RequestAlreadyExtracted;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Rejection type for [`UrlParams`](super::UrlParams) if the capture route
|
/// Rejection type for [`UrlParams`](super::UrlParams) if the capture route
|
||||||
|
|||||||
+23
-27
@@ -2,13 +2,9 @@
|
|||||||
//!
|
//!
|
||||||
//! # What is a handler?
|
//! # What is a handler?
|
||||||
//!
|
//!
|
||||||
//! In tower-web a "handler" is an async function that accepts a request and
|
//! In tower-web a "handler" is an async function that accepts zero or more
|
||||||
//! produces a response. Handler functions must take
|
//! ["extractors"](crate::extract) as arguments and returns something that
|
||||||
//! `http::Request<tower_web::body::Body>` as they first argument and return
|
//! implements [`IntoResponse`].
|
||||||
//! something that implements [`IntoResponse`].
|
|
||||||
//!
|
|
||||||
//! Additionally handlers can use ["extractors"](crate::extract) to extract data
|
|
||||||
//! from incoming requests.
|
|
||||||
//!
|
//!
|
||||||
//! # Example
|
//! # Example
|
||||||
//!
|
//!
|
||||||
@@ -19,17 +15,17 @@
|
|||||||
//! use bytes::Bytes;
|
//! use bytes::Bytes;
|
||||||
//! use http::StatusCode;
|
//! use http::StatusCode;
|
||||||
//!
|
//!
|
||||||
//! // Handlers must take `Request<Body>` as the first argument and must return
|
//! // Handler that immediately returns an empty `200 OK` response.
|
||||||
//! // something that implements `IntoResponse`, which `()` does
|
//! async fn unit_handler() {}
|
||||||
//! async fn unit_handler(request: Request<Body>) {}
|
|
||||||
//!
|
//!
|
||||||
//! // `String` also implements `IntoResponse`
|
//! // Handler that immediately returns an empty `200 Ok` response with a plain
|
||||||
//! async fn string_handler(request: Request<Body>) -> String {
|
//! /// text body.
|
||||||
|
//! async fn string_handler() -> String {
|
||||||
//! "Hello, World!".to_string()
|
//! "Hello, World!".to_string()
|
||||||
//! }
|
//! }
|
||||||
//!
|
//!
|
||||||
//! // Handler that buffers the request body and returns it if it is valid UTF-8
|
//! // Handler that buffers the request body and returns it if it is valid UTF-8
|
||||||
//! async fn buffer_body(request: Request<Body>, body: Bytes) -> Result<String, StatusCode> {
|
//! async fn buffer_body(body: Bytes) -> Result<String, StatusCode> {
|
||||||
//! if let Ok(string) = String::from_utf8(body.to_vec()) {
|
//! if let Ok(string) = String::from_utf8(body.to_vec()) {
|
||||||
//! Ok(string)
|
//! Ok(string)
|
||||||
//! } else {
|
//! } else {
|
||||||
@@ -72,7 +68,7 @@ pub mod future;
|
|||||||
/// ```rust
|
/// ```rust
|
||||||
/// use tower_web::prelude::*;
|
/// use tower_web::prelude::*;
|
||||||
///
|
///
|
||||||
/// async fn handler(request: Request<Body>) {}
|
/// async fn handler() {}
|
||||||
///
|
///
|
||||||
/// // All requests to `/` will go to `handler` regardless of the HTTP method.
|
/// // All requests to `/` will go to `handler` regardless of the HTTP method.
|
||||||
/// let app = route("/", any(handler));
|
/// let app = route("/", any(handler));
|
||||||
@@ -111,7 +107,7 @@ where
|
|||||||
/// ```rust
|
/// ```rust
|
||||||
/// use tower_web::prelude::*;
|
/// use tower_web::prelude::*;
|
||||||
///
|
///
|
||||||
/// async fn handler(request: Request<Body>) {}
|
/// async fn handler() {}
|
||||||
///
|
///
|
||||||
/// // Requests to `GET /` will go to `handler`.
|
/// // Requests to `GET /` will go to `handler`.
|
||||||
/// let app = route("/", get(handler));
|
/// let app = route("/", get(handler));
|
||||||
@@ -190,7 +186,7 @@ where
|
|||||||
/// ```rust
|
/// ```rust
|
||||||
/// use tower_web::{handler::on, routing::MethodFilter, prelude::*};
|
/// use tower_web::{handler::on, routing::MethodFilter, prelude::*};
|
||||||
///
|
///
|
||||||
/// async fn handler(request: Request<Body>) {}
|
/// async fn handler() {}
|
||||||
///
|
///
|
||||||
/// // Requests to `POST /` will go to `handler`.
|
/// // Requests to `POST /` will go to `handler`.
|
||||||
/// let app = route("/", on(MethodFilter::Post, handler));
|
/// let app = route("/", on(MethodFilter::Post, handler));
|
||||||
@@ -250,7 +246,7 @@ pub trait Handler<In>: Sized {
|
|||||||
/// use tower_web::prelude::*;
|
/// use tower_web::prelude::*;
|
||||||
/// use tower::limit::{ConcurrencyLimitLayer, ConcurrencyLimit};
|
/// use tower::limit::{ConcurrencyLimitLayer, ConcurrencyLimit};
|
||||||
///
|
///
|
||||||
/// async fn handler(request: Request<Body>) { /* ... */ }
|
/// async fn handler() { /* ... */ }
|
||||||
///
|
///
|
||||||
/// let layered_handler = handler.layer(ConcurrencyLimitLayer::new(64));
|
/// let layered_handler = handler.layer(ConcurrencyLimitLayer::new(64));
|
||||||
/// ```
|
/// ```
|
||||||
@@ -273,14 +269,14 @@ pub trait Handler<In>: Sized {
|
|||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl<F, Fut, Res> Handler<()> for F
|
impl<F, Fut, Res> Handler<()> for F
|
||||||
where
|
where
|
||||||
F: FnOnce(Request<Body>) -> Fut + Send + Sync,
|
F: FnOnce() -> Fut + Send + Sync,
|
||||||
Fut: Future<Output = Res> + Send,
|
Fut: Future<Output = Res> + Send,
|
||||||
Res: IntoResponse,
|
Res: IntoResponse,
|
||||||
{
|
{
|
||||||
type Sealed = sealed::Hidden;
|
type Sealed = sealed::Hidden;
|
||||||
|
|
||||||
async fn call(self, req: Request<Body>) -> Response<BoxBody> {
|
async fn call(self, _req: Request<Body>) -> Response<BoxBody> {
|
||||||
self(req).await.into_response().map(BoxBody::new)
|
self().await.into_response().map(BoxBody::new)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -292,7 +288,7 @@ macro_rules! impl_handler {
|
|||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
impl<F, Fut, Res, $head, $($tail,)*> Handler<($head, $($tail,)*)> for F
|
impl<F, Fut, Res, $head, $($tail,)*> Handler<($head, $($tail,)*)> for F
|
||||||
where
|
where
|
||||||
F: FnOnce(Request<Body>, $head, $($tail,)*) -> Fut + Send + Sync,
|
F: FnOnce($head, $($tail,)*) -> Fut + Send + Sync,
|
||||||
Fut: Future<Output = Res> + Send,
|
Fut: Future<Output = Res> + Send,
|
||||||
Res: IntoResponse,
|
Res: IntoResponse,
|
||||||
$head: FromRequest + Send,
|
$head: FromRequest + Send,
|
||||||
@@ -313,7 +309,7 @@ macro_rules! impl_handler {
|
|||||||
};
|
};
|
||||||
)*
|
)*
|
||||||
|
|
||||||
let res = self(req, $head, $($tail,)*).await;
|
let res = self($head, $($tail,)*).await;
|
||||||
|
|
||||||
res.into_response().map(BoxBody::new)
|
res.into_response().map(BoxBody::new)
|
||||||
}
|
}
|
||||||
@@ -398,7 +394,7 @@ impl<S, T> Layered<S, T> {
|
|||||||
/// use tower::{BoxError, timeout::TimeoutLayer};
|
/// use tower::{BoxError, timeout::TimeoutLayer};
|
||||||
/// use std::time::Duration;
|
/// use std::time::Duration;
|
||||||
///
|
///
|
||||||
/// async fn handler(request: Request<Body>) { /* ... */ }
|
/// async fn handler() { /* ... */ }
|
||||||
///
|
///
|
||||||
/// // `Timeout` will fail with `BoxError` if the timeout elapses...
|
/// // `Timeout` will fail with `BoxError` if the timeout elapses...
|
||||||
/// let layered_handler = handler
|
/// let layered_handler = handler
|
||||||
@@ -545,9 +541,9 @@ impl<S, F> OnMethod<S, F> {
|
|||||||
/// ```rust
|
/// ```rust
|
||||||
/// use tower_web::prelude::*;
|
/// use tower_web::prelude::*;
|
||||||
///
|
///
|
||||||
/// async fn handler(request: Request<Body>) {}
|
/// async fn handler() {}
|
||||||
///
|
///
|
||||||
/// async fn other_handler(request: Request<Body>) {}
|
/// async fn other_handler() {}
|
||||||
///
|
///
|
||||||
/// // Requests to `GET /` will go to `handler` and `POST /` will go to
|
/// // Requests to `GET /` will go to `handler` and `POST /` will go to
|
||||||
/// // `other_handler`.
|
/// // `other_handler`.
|
||||||
@@ -628,9 +624,9 @@ impl<S, F> OnMethod<S, F> {
|
|||||||
/// ```rust
|
/// ```rust
|
||||||
/// use tower_web::{routing::MethodFilter, prelude::*};
|
/// use tower_web::{routing::MethodFilter, prelude::*};
|
||||||
///
|
///
|
||||||
/// async fn handler(request: Request<Body>) {}
|
/// async fn handler() {}
|
||||||
///
|
///
|
||||||
/// async fn other_handler(request: Request<Body>) {}
|
/// async fn other_handler() {}
|
||||||
///
|
///
|
||||||
/// // Requests to `GET /` will go to `handler` and `DELETE /` will go to
|
/// // Requests to `GET /` will go to `handler` and `DELETE /` will go to
|
||||||
/// // `other_handler`
|
/// // `other_handler`
|
||||||
|
|||||||
+47
-34
@@ -30,9 +30,7 @@
|
|||||||
//! #[tokio::main]
|
//! #[tokio::main]
|
||||||
//! async fn main() {
|
//! async fn main() {
|
||||||
//! // build our application with a single route
|
//! // build our application with a single route
|
||||||
//! let app = route("/", get(|request: Request<Body>| async {
|
//! let app = route("/", get(|| async { "Hello, World!" }));
|
||||||
//! "Hello, World!"
|
|
||||||
//! }));
|
|
||||||
//!
|
//!
|
||||||
//! // run it with hyper on localhost:3000
|
//! // run it with hyper on localhost:3000
|
||||||
//! let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
|
//! let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
|
||||||
@@ -53,15 +51,15 @@
|
|||||||
//! let app = route("/", get(get_slash).post(post_slash))
|
//! let app = route("/", get(get_slash).post(post_slash))
|
||||||
//! .route("/foo", get(get_foo));
|
//! .route("/foo", get(get_foo));
|
||||||
//!
|
//!
|
||||||
//! async fn get_slash(req: Request<Body>) {
|
//! async fn get_slash() {
|
||||||
//! // `GET /` called
|
//! // `GET /` called
|
||||||
//! }
|
//! }
|
||||||
//!
|
//!
|
||||||
//! async fn post_slash(req: Request<Body>) {
|
//! async fn post_slash() {
|
||||||
//! // `POST /` called
|
//! // `POST /` called
|
||||||
//! }
|
//! }
|
||||||
//!
|
//!
|
||||||
//! async fn get_foo(req: Request<Body>) {
|
//! async fn get_foo() {
|
||||||
//! // `GET /foo` called
|
//! // `GET /foo` called
|
||||||
//! }
|
//! }
|
||||||
//! # async {
|
//! # async {
|
||||||
@@ -79,57 +77,57 @@
|
|||||||
//!
|
//!
|
||||||
//! ```rust,no_run
|
//! ```rust,no_run
|
||||||
//! use tower_web::{body::Body, response::{Html, Json}, prelude::*};
|
//! use tower_web::{body::Body, response::{Html, Json}, prelude::*};
|
||||||
//! use http::{StatusCode, Response};
|
//! use http::{StatusCode, Response, Uri};
|
||||||
//! use serde_json::{Value, json};
|
//! use serde_json::{Value, json};
|
||||||
//!
|
//!
|
||||||
//! // We've already seen returning &'static str
|
//! // We've already seen returning &'static str
|
||||||
//! async fn plain_text(req: Request<Body>) -> &'static str {
|
//! async fn plain_text() -> &'static str {
|
||||||
//! "foo"
|
//! "foo"
|
||||||
//! }
|
//! }
|
||||||
//!
|
//!
|
||||||
//! // String works too and will get a text/plain content-type
|
//! // String works too and will get a text/plain content-type
|
||||||
//! async fn plain_text_string(req: Request<Body>) -> String {
|
//! async fn plain_text_string(uri: Uri) -> String {
|
||||||
//! format!("Hi from {}", req.uri().path())
|
//! format!("Hi from {}", uri.path())
|
||||||
//! }
|
//! }
|
||||||
//!
|
//!
|
||||||
//! // Bytes will get a `application/octet-stream` content-type
|
//! // 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]
|
//! vec![1, 2, 3, 4]
|
||||||
//! }
|
//! }
|
||||||
//!
|
//!
|
||||||
//! // `()` gives an empty response
|
//! // `()` gives an empty response
|
||||||
//! async fn empty(req: Request<Body>) {}
|
//! async fn empty() {}
|
||||||
//!
|
//!
|
||||||
//! // `StatusCode` gives an empty response with that status code
|
//! // `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
|
//! StatusCode::NOT_FOUND
|
||||||
//! }
|
//! }
|
||||||
//!
|
//!
|
||||||
//! // A tuple of `StatusCode` and something that implements `IntoResponse` can
|
//! // A tuple of `StatusCode` and something that implements `IntoResponse` can
|
||||||
//! // be used to override the status code
|
//! // 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")
|
//! (StatusCode::INTERNAL_SERVER_ERROR, "Something went wrong")
|
||||||
//! }
|
//! }
|
||||||
//!
|
//!
|
||||||
//! // `Html` gives a content-type of `text/html`
|
//! // `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>")
|
//! Html("<h1>Hello, World!</h1>")
|
||||||
//! }
|
//! }
|
||||||
//!
|
//!
|
||||||
//! // `Json` gives a content-type of `application/json` and works with any type
|
//! // `Json` gives a content-type of `application/json` and works with any type
|
||||||
//! // that implements `serde::Serialize`
|
//! // that implements `serde::Serialize`
|
||||||
//! async fn json(req: Request<Body>) -> Json<Value> {
|
//! async fn json() -> Json<Value> {
|
||||||
//! Json(json!({ "data": 42 }))
|
//! Json(json!({ "data": 42 }))
|
||||||
//! }
|
//! }
|
||||||
//!
|
//!
|
||||||
//! // `Result<T, E>` where `T` and `E` implement `IntoResponse` is useful for
|
//! // `Result<T, E>` where `T` and `E` implement `IntoResponse` is useful for
|
||||||
//! // returning errors
|
//! // returning errors
|
||||||
//! async fn result(req: Request<Body>) -> Result<&'static str, StatusCode> {
|
//! async fn result() -> Result<&'static str, StatusCode> {
|
||||||
//! Ok("all good")
|
//! Ok("all good")
|
||||||
//! }
|
//! }
|
||||||
//!
|
//!
|
||||||
//! // `Response` gives full control
|
//! // `Response` gives full control
|
||||||
//! async fn response(req: Request<Body>) -> Response<Body> {
|
//! async fn response() -> Response<Body> {
|
||||||
//! Response::builder().body(Body::empty()).unwrap()
|
//! Response::builder().body(Body::empty()).unwrap()
|
||||||
//! }
|
//! }
|
||||||
//!
|
//!
|
||||||
@@ -152,13 +150,12 @@
|
|||||||
//!
|
//!
|
||||||
//! # Extracting data from requests
|
//! # Extracting data from requests
|
||||||
//!
|
//!
|
||||||
//! A handler function must always take `Request<Body>` as its first argument
|
//! A handler function is an async function take takes any number of
|
||||||
//! but any arguments following are called "extractors". Any type that
|
//! "extractors" as arguments. An extractor is a type that implements
|
||||||
//! implements [`FromRequest`](crate::extract::FromRequest) can be used as an
|
//! [`FromRequest`](crate::extract::FromRequest).
|
||||||
//! extractor.
|
|
||||||
//!
|
//!
|
||||||
//! For example, [`extract::Json`] is an extractor that consumes the request body and
|
//! For example, [`extract::Json`] is an extractor that consumes the request
|
||||||
//! deserializes it as JSON into some target type:
|
//! body and deserializes it as JSON into some target type:
|
||||||
//!
|
//!
|
||||||
//! ```rust,no_run
|
//! ```rust,no_run
|
||||||
//! use tower_web::prelude::*;
|
//! use tower_web::prelude::*;
|
||||||
@@ -172,7 +169,7 @@
|
|||||||
//! password: String,
|
//! 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;
|
//! let payload: CreateUser = payload.0;
|
||||||
//!
|
//!
|
||||||
//! // ...
|
//! // ...
|
||||||
@@ -192,7 +189,7 @@
|
|||||||
//!
|
//!
|
||||||
//! let app = route("/users/:id", post(create_user));
|
//! 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;
|
//! let user_id: Uuid = (params.0).0;
|
||||||
//!
|
//!
|
||||||
//! // ...
|
//! // ...
|
||||||
@@ -227,7 +224,6 @@
|
|||||||
//! }
|
//! }
|
||||||
//!
|
//!
|
||||||
//! async fn get_user_things(
|
//! async fn get_user_things(
|
||||||
//! req: Request<Body>,
|
|
||||||
//! params: extract::UrlParams<(Uuid,)>,
|
//! params: extract::UrlParams<(Uuid,)>,
|
||||||
//! pagination: Option<extract::Query<Pagination>>,
|
//! pagination: Option<extract::Query<Pagination>>,
|
||||||
//! ) {
|
//! ) {
|
||||||
@@ -241,6 +237,24 @@
|
|||||||
//! # };
|
//! # };
|
||||||
//! ```
|
//! ```
|
||||||
//!
|
//!
|
||||||
|
//! Additionally `Request<Body>` is itself an extractor:
|
||||||
|
//!
|
||||||
|
//! ```rust,no_run
|
||||||
|
//! use tower_web::prelude::*;
|
||||||
|
//!
|
||||||
|
//! let app = route("/users/:id", post(handler));
|
||||||
|
//!
|
||||||
|
//! async fn handler(req: Request<Body>) {
|
||||||
|
//! // ...
|
||||||
|
//! }
|
||||||
|
//! # async {
|
||||||
|
//! # hyper::Server::bind(&"".parse().unwrap()).serve(tower::make::Shared::new(app)).await;
|
||||||
|
//! # };
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! However it cannot be combined with other extractors since it consumes the
|
||||||
|
//! entire request.
|
||||||
|
//!
|
||||||
//! See the [`extract`] module for more details.
|
//! See the [`extract`] module for more details.
|
||||||
//!
|
//!
|
||||||
//! [`Uuid`]: https://docs.rs/uuid/latest/uuid/
|
//! [`Uuid`]: https://docs.rs/uuid/latest/uuid/
|
||||||
@@ -263,7 +277,7 @@
|
|||||||
//! get(handler.layer(ConcurrencyLimitLayer::new(100))),
|
//! get(handler.layer(ConcurrencyLimitLayer::new(100))),
|
||||||
//! );
|
//! );
|
||||||
//!
|
//!
|
||||||
//! async fn handler(req: Request<Body>) {}
|
//! async fn handler() {}
|
||||||
//! # async {
|
//! # async {
|
||||||
//! # hyper::Server::bind(&"".parse().unwrap()).serve(tower::make::Shared::new(app)).await;
|
//! # hyper::Server::bind(&"".parse().unwrap()).serve(tower::make::Shared::new(app)).await;
|
||||||
//! # };
|
//! # };
|
||||||
@@ -281,9 +295,9 @@
|
|||||||
//! .route("/foo", post(post_foo))
|
//! .route("/foo", post(post_foo))
|
||||||
//! .layer(ConcurrencyLimitLayer::new(100));
|
//! .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() {}
|
||||||
//! # async {
|
//! # async {
|
||||||
//! # hyper::Server::bind(&"".parse().unwrap()).serve(tower::make::Shared::new(app)).await;
|
//! # hyper::Server::bind(&"".parse().unwrap()).serve(tower::make::Shared::new(app)).await;
|
||||||
//! # };
|
//! # };
|
||||||
@@ -333,7 +347,7 @@
|
|||||||
//! })),
|
//! })),
|
||||||
//! );
|
//! );
|
||||||
//!
|
//!
|
||||||
//! async fn handle(req: Request<Body>) {}
|
//! async fn handle() {}
|
||||||
//! # async {
|
//! # async {
|
||||||
//! # hyper::Server::bind(&"".parse().unwrap()).serve(tower::make::Shared::new(app)).await;
|
//! # hyper::Server::bind(&"".parse().unwrap()).serve(tower::make::Shared::new(app)).await;
|
||||||
//! # };
|
//! # };
|
||||||
@@ -357,9 +371,9 @@
|
|||||||
//! // ...
|
//! // ...
|
||||||
//! });
|
//! });
|
||||||
//!
|
//!
|
||||||
//! async fn handle(req: Request<Body>) {}
|
//! async fn handle() {}
|
||||||
//!
|
//!
|
||||||
//! async fn other_handle(req: Request<Body>) {}
|
//! async fn other_handle() {}
|
||||||
//! # async {
|
//! # async {
|
||||||
//! # hyper::Server::bind(&"".parse().unwrap()).serve(tower::make::Shared::new(app)).await;
|
//! # hyper::Server::bind(&"".parse().unwrap()).serve(tower::make::Shared::new(app)).await;
|
||||||
//! # };
|
//! # };
|
||||||
@@ -438,7 +452,6 @@
|
|||||||
//! let app = route("/", get(handler)).layer(AddExtensionLayer::new(shared_state));
|
//! let app = route("/", get(handler)).layer(AddExtensionLayer::new(shared_state));
|
||||||
//!
|
//!
|
||||||
//! async fn handler(
|
//! async fn handler(
|
||||||
//! req: Request<Body>,
|
|
||||||
//! state: extract::Extension<Arc<State>>,
|
//! state: extract::Extension<Arc<State>>,
|
||||||
//! ) {
|
//! ) {
|
||||||
//! let state: Arc<State> = state.0;
|
//! let state: Arc<State> = state.0;
|
||||||
|
|||||||
+19
-18
@@ -88,11 +88,11 @@ pub trait RoutingDsl: crate::sealed::Sealed + Sized {
|
|||||||
/// ```rust
|
/// ```rust
|
||||||
/// use tower_web::prelude::*;
|
/// use tower_web::prelude::*;
|
||||||
///
|
///
|
||||||
/// async fn first_handler(request: Request<Body>) { /* ... */ }
|
/// async fn first_handler() { /* ... */ }
|
||||||
///
|
///
|
||||||
/// async fn second_handler(request: Request<Body>) { /* ... */ }
|
/// async fn second_handler() { /* ... */ }
|
||||||
///
|
///
|
||||||
/// async fn third_handler(request: Request<Body>) { /* ... */ }
|
/// async fn third_handler() { /* ... */ }
|
||||||
///
|
///
|
||||||
/// // `GET /` goes to `first_handler`, `POST /` goes to `second_handler`,
|
/// // `GET /` goes to `first_handler`, `POST /` goes to `second_handler`,
|
||||||
/// // and `GET /foo` goes to third_handler.
|
/// // and `GET /foo` goes to third_handler.
|
||||||
@@ -132,11 +132,11 @@ pub trait RoutingDsl: crate::sealed::Sealed + Sized {
|
|||||||
/// ```rust
|
/// ```rust
|
||||||
/// use tower_web::{body::BoxBody, routing::BoxRoute, prelude::*};
|
/// use tower_web::{body::BoxBody, routing::BoxRoute, prelude::*};
|
||||||
///
|
///
|
||||||
/// async fn first_handler(request: Request<Body>) { /* ... */ }
|
/// async fn first_handler() { /* ... */ }
|
||||||
///
|
///
|
||||||
/// async fn second_handler(request: Request<Body>) { /* ... */ }
|
/// async fn second_handler() { /* ... */ }
|
||||||
///
|
///
|
||||||
/// async fn third_handler(request: Request<Body>) { /* ... */ }
|
/// async fn third_handler() { /* ... */ }
|
||||||
///
|
///
|
||||||
/// fn app() -> BoxRoute<BoxBody> {
|
/// fn app() -> BoxRoute<BoxBody> {
|
||||||
/// route("/", get(first_handler).post(second_handler))
|
/// route("/", get(first_handler).post(second_handler))
|
||||||
@@ -181,11 +181,11 @@ pub trait RoutingDsl: crate::sealed::Sealed + Sized {
|
|||||||
/// use tower_web::prelude::*;
|
/// use tower_web::prelude::*;
|
||||||
/// use tower::limit::{ConcurrencyLimitLayer, ConcurrencyLimit};
|
/// use tower::limit::{ConcurrencyLimitLayer, ConcurrencyLimit};
|
||||||
///
|
///
|
||||||
/// async fn first_handler(request: Request<Body>) { /* ... */ }
|
/// async fn first_handler() { /* ... */ }
|
||||||
///
|
///
|
||||||
/// async fn second_handler(request: Request<Body>) { /* ... */ }
|
/// async fn second_handler() { /* ... */ }
|
||||||
///
|
///
|
||||||
/// async fn third_handler(request: Request<Body>) { /* ... */ }
|
/// async fn third_handler() { /* ... */ }
|
||||||
///
|
///
|
||||||
/// // All requests to `handler` and `other_handler` will be sent through
|
/// // All requests to `handler` and `other_handler` will be sent through
|
||||||
/// // `ConcurrencyLimit`
|
/// // `ConcurrencyLimit`
|
||||||
@@ -207,11 +207,11 @@ pub trait RoutingDsl: crate::sealed::Sealed + Sized {
|
|||||||
/// use tower_web::prelude::*;
|
/// use tower_web::prelude::*;
|
||||||
/// use tower_http::trace::TraceLayer;
|
/// use tower_http::trace::TraceLayer;
|
||||||
///
|
///
|
||||||
/// async fn first_handler(request: Request<Body>) { /* ... */ }
|
/// async fn first_handler() { /* ... */ }
|
||||||
///
|
///
|
||||||
/// async fn second_handler(request: Request<Body>) { /* ... */ }
|
/// async fn second_handler() { /* ... */ }
|
||||||
///
|
///
|
||||||
/// async fn third_handler(request: Request<Body>) { /* ... */ }
|
/// async fn third_handler() { /* ... */ }
|
||||||
///
|
///
|
||||||
/// let app = route("/", get(first_handler))
|
/// let app = route("/", get(first_handler))
|
||||||
/// .route("/foo", get(second_handler))
|
/// .route("/foo", get(second_handler))
|
||||||
@@ -584,7 +584,7 @@ impl<S> Layered<S> {
|
|||||||
/// use tower::{BoxError, timeout::TimeoutLayer};
|
/// use tower::{BoxError, timeout::TimeoutLayer};
|
||||||
/// use std::time::Duration;
|
/// use std::time::Duration;
|
||||||
///
|
///
|
||||||
/// async fn handler(request: Request<Body>) { /* ... */ }
|
/// async fn handler() { /* ... */ }
|
||||||
///
|
///
|
||||||
/// // `Timeout` will fail with `BoxError` if the timeout elapses...
|
/// // `Timeout` will fail with `BoxError` if the timeout elapses...
|
||||||
/// let layered_handler = route("/", get(handler))
|
/// let layered_handler = route("/", get(handler))
|
||||||
@@ -646,16 +646,17 @@ where
|
|||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// use tower_web::{routing::nest, prelude::*};
|
/// use tower_web::{routing::nest, prelude::*};
|
||||||
|
/// use http::Uri;
|
||||||
///
|
///
|
||||||
/// async fn users_get(request: Request<Body>) {
|
/// async fn users_get(uri: Uri) {
|
||||||
/// // `users_get` doesn't see the whole URL. `nest` will strip the matching
|
/// // `users_get` doesn't see the whole URL. `nest` will strip the matching
|
||||||
/// // `/api` prefix.
|
/// // `/api` prefix.
|
||||||
/// assert_eq!(request.uri().path(), "/users");
|
/// assert_eq!(uri.path(), "/users");
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
/// async fn users_post(request: Request<Body>) {}
|
/// async fn users_post() {}
|
||||||
///
|
///
|
||||||
/// async fn careers(request: Request<Body>) {}
|
/// async fn careers() {}
|
||||||
///
|
///
|
||||||
/// let users_api = route("/users", get(users_get).post(users_post));
|
/// let users_api = route("/users", get(users_get).post(users_post));
|
||||||
///
|
///
|
||||||
@@ -671,7 +672,7 @@ where
|
|||||||
/// ```
|
/// ```
|
||||||
/// use tower_web::{routing::nest, prelude::*};
|
/// use tower_web::{routing::nest, prelude::*};
|
||||||
///
|
///
|
||||||
/// async fn users_get(request: Request<Body>, params: extract::UrlParamsMap) {
|
/// async fn users_get(params: extract::UrlParamsMap) {
|
||||||
/// // Both `version` and `id` were captured even though `users_api` only
|
/// // Both `version` and `id` were captured even though `users_api` only
|
||||||
/// // explicitly captures `id`.
|
/// // explicitly captures `id`.
|
||||||
/// let version = params.get("version");
|
/// let version = params.get("version");
|
||||||
|
|||||||
+1
-1
@@ -63,7 +63,7 @@
|
|||||||
//! # let some_backpressure_sensitive_middleware =
|
//! # let some_backpressure_sensitive_middleware =
|
||||||
//! # tower::layer::util::Identity::new();
|
//! # tower::layer::util::Identity::new();
|
||||||
//!
|
//!
|
||||||
//! async fn handler(request: Request<Body>) { /* ... */ }
|
//! async fn handler() { /* ... */ }
|
||||||
//!
|
//!
|
||||||
//! let app = route("/", get(handler));
|
//! let app = route("/", get(handler));
|
||||||
//!
|
//!
|
||||||
|
|||||||
+33
-46
@@ -54,7 +54,7 @@ async fn hello_world() {
|
|||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn consume_body() {
|
async fn consume_body() {
|
||||||
let app = route("/", get(|_: Request<Body>, body: String| async { body }));
|
let app = route("/", get(|body: String| async { body }));
|
||||||
|
|
||||||
let addr = run_in_background(app).await;
|
let addr = run_in_background(app).await;
|
||||||
|
|
||||||
@@ -79,7 +79,7 @@ async fn deserialize_body() {
|
|||||||
|
|
||||||
let app = route(
|
let app = route(
|
||||||
"/",
|
"/",
|
||||||
post(|_: Request<Body>, input: extract::Json<Input>| async { input.0.foo }),
|
post(|input: extract::Json<Input>| async { input.0.foo }),
|
||||||
);
|
);
|
||||||
|
|
||||||
let addr = run_in_background(app).await;
|
let addr = run_in_background(app).await;
|
||||||
@@ -137,11 +137,7 @@ async fn body_with_length_limit() {
|
|||||||
|
|
||||||
let app = route(
|
let app = route(
|
||||||
"/",
|
"/",
|
||||||
post(
|
post(|_body: extract::ContentLengthLimit<Bytes, LIMIT>| async {}),
|
||||||
|req: Request<Body>, _body: extract::ContentLengthLimit<Bytes, LIMIT>| async move {
|
|
||||||
dbg!(&req);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let addr = run_in_background(app).await;
|
let addr = run_in_background(app).await;
|
||||||
@@ -240,24 +236,20 @@ async fn routing() {
|
|||||||
async fn extracting_url_params() {
|
async fn extracting_url_params() {
|
||||||
let app = route(
|
let app = route(
|
||||||
"/users/:id",
|
"/users/:id",
|
||||||
get(
|
get(|params: extract::UrlParams<(i32,)>| async move {
|
||||||
|_: Request<Body>, params: extract::UrlParams<(i32,)>| async move {
|
let (id,) = params.0;
|
||||||
let (id,) = params.0;
|
assert_eq!(id, 42);
|
||||||
assert_eq!(id, 42);
|
})
|
||||||
},
|
.post(|params_map: extract::UrlParamsMap| async move {
|
||||||
)
|
assert_eq!(params_map.get("id").unwrap(), "1337");
|
||||||
.post(
|
assert_eq!(
|
||||||
|_: Request<Body>, params_map: extract::UrlParamsMap| async move {
|
params_map
|
||||||
assert_eq!(params_map.get("id").unwrap(), "1337");
|
.get_typed::<i32>("id")
|
||||||
assert_eq!(
|
.expect("missing")
|
||||||
params_map
|
.expect("failed to parse"),
|
||||||
.get_typed::<i32>("id")
|
1337
|
||||||
.expect("missing")
|
);
|
||||||
.expect("failed to parse"),
|
}),
|
||||||
1337
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let addr = run_in_background(app).await;
|
let addr = run_in_background(app).await;
|
||||||
@@ -502,35 +494,30 @@ async fn layer_on_whole_router() {
|
|||||||
async fn disjunction() {
|
async fn disjunction() {
|
||||||
let api_routes = route(
|
let api_routes = route(
|
||||||
"/users",
|
"/users",
|
||||||
get(|_: Request<Body>| async { "users#index" })
|
get(|| async { "users#index" }).post(|| async { "users#create" }),
|
||||||
.post(|_: Request<Body>| async { "users#create" }),
|
|
||||||
)
|
)
|
||||||
.route(
|
.route(
|
||||||
"/users/:id",
|
"/users/:id",
|
||||||
get(
|
get(|params: extract::UrlParamsMap| async move {
|
||||||
|_: Request<Body>, params: extract::UrlParamsMap| async move {
|
format!(
|
||||||
format!(
|
"{}: users#show ({})",
|
||||||
"{}: users#show ({})",
|
params.get("version").unwrap(),
|
||||||
params.get("version").unwrap(),
|
params.get("id").unwrap()
|
||||||
params.get("id").unwrap()
|
)
|
||||||
)
|
}),
|
||||||
},
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
.route(
|
.route(
|
||||||
"/games/:id",
|
"/games/:id",
|
||||||
get(
|
get(|params: extract::UrlParamsMap| async move {
|
||||||
|_: Request<Body>, params: extract::UrlParamsMap| async move {
|
format!(
|
||||||
format!(
|
"{}: games#show ({})",
|
||||||
"{}: games#show ({})",
|
params.get("version").unwrap(),
|
||||||
params.get("version").unwrap(),
|
params.get("id").unwrap()
|
||||||
params.get("id").unwrap()
|
)
|
||||||
)
|
}),
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let app = route("/", get(|_: Request<Body>| async { "hi" })).nest("/:version/api", api_routes);
|
let app = route("/", get(|| async { "hi" })).nest("/:version/api", api_routes);
|
||||||
|
|
||||||
let addr = run_in_background(app).await;
|
let addr = run_in_background(app).await;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user