Make Request<Body> an extractor

This commit is contained in:
David Pedersen 2021-06-09 09:03:09 +02:00
parent 90c3e5ba74
commit c91dc7ce29
10 changed files with 268 additions and 200 deletions

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

View file

@ -1,4 +1,4 @@
use http::{Request, StatusCode};
use http::StatusCode;
use hyper::Server;
use std::net::SocketAddr;
use tower::make::Shared;
@ -18,11 +18,11 @@ async fn main() {
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>")
}
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") {
Ok(format!("Hello {}!", name))
} else {

View file

@ -7,7 +7,7 @@
//! ```
use bytes::Bytes;
use http::{Request, StatusCode};
use http::StatusCode;
use hyper::Server;
use std::{
borrow::Cow,
@ -22,7 +22,7 @@ use tower_http::{
compression::CompressionLayer, trace::TraceLayer,
};
use tower_web::{
body::{Body, BoxBody},
body::BoxBody,
extract::{ContentLengthLimit, Extension, UrlParams},
prelude::*,
response::IntoResponse,
@ -72,7 +72,6 @@ struct State {
}
async fn kv_get(
_req: Request<Body>,
UrlParams((key,)): UrlParams<(String,)>,
Extension(state): Extension<SharedState>,
) -> Result<Bytes, StatusCode> {
@ -86,7 +85,6 @@ async fn kv_get(
}
async fn kv_set(
_req: Request<Body>,
UrlParams((key,)): UrlParams<(String,)>,
ContentLengthLimit(bytes): ContentLengthLimit<Bytes, { 1024 * 5_000 }>, // ~5mb
Extension(state): Extension<SharedState>,
@ -94,7 +92,7 @@ async fn kv_set(
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;
db.keys()
@ -104,12 +102,11 @@ async fn list_keys(_req: Request<Body>, Extension(state): Extension<SharedState>
}
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();
}
async fn remove_key(
_req: Request<Body>,
UrlParams((key,)): UrlParams<(String,)>,
Extension(state): Extension<SharedState>,
) {

View file

@ -1,8 +1,8 @@
//! Types and traits for 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`](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, [`Json`] is an extractor that consumes the request body and
//! deserializes it as JSON into some target type:
@ -17,7 +17,7 @@
//! 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;
//!
//! // ...
@ -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;
//!
//! // ...
@ -73,7 +73,6 @@
//! use std::collections::HashMap;
//!
//! async fn handler(
//! req: Request<Body>,
//! // Extract captured parameters from the URL
//! params: extract::UrlParamsMap,
//! // Parse query string into a `HashMap`
@ -98,7 +97,7 @@
//! use tower_web::{extract::Json, prelude::*};
//! 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 {
//! // We got a valid JSON payload
//! } else {
@ -121,7 +120,7 @@
//! use tower_web::{extract::Json, prelude::*};
//! 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`
//! }
//!
@ -134,14 +133,14 @@
use crate::{body::Body, response::IntoResponse};
use async_trait::async_trait;
use bytes::Bytes;
use http::{header, Request, Response};
use http::{HeaderMap, Method, Request, Response, Uri, Version, header};
use rejection::{
BodyAlreadyTaken, FailedToBufferBody, InvalidJsonBody, InvalidUrlParam, InvalidUtf8,
BodyAlreadyExtracted, FailedToBufferBody, InvalidJsonBody, InvalidUrlParam, InvalidUtf8,
LengthRequired, MissingExtension, MissingJsonContentType, MissingRouteParams, PayloadTooLarge,
QueryStringMissing, UrlParamsAlreadyTaken,
QueryStringMissing, RequestAlreadyExtracted, UrlParamsAlreadyExtracted,
};
use serde::de::DeserializeOwned;
use std::{collections::HashMap, convert::Infallible, str::FromStr};
use std::{collections::HashMap, mem, convert::Infallible, str::FromStr};
pub mod rejection;
@ -188,7 +187,7 @@ where
///
/// // This will parse query strings like `?page=2&per_page=30` into `Pagination`
/// // 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;
///
/// // ...
@ -231,7 +230,7 @@ where
/// 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;
///
/// // ...
@ -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]
impl FromRequest for Body {
type Rejection = BodyAlreadyTaken;
type Rejection = BodyAlreadyExtracted;
async fn from_request(req: &mut Request<Body>) -> Result<Self, Self::Rejection> {
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.
///
/// # Example
@ -395,7 +449,7 @@ impl FromRequest for Body {
/// ```rust,no_run
/// 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
/// 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");
///
/// // ...
@ -483,7 +537,7 @@ impl FromRequest for UrlParamsMap {
if let Some(params) = params.take() {
Ok(Self(params.0.into_iter().collect()))
} else {
Err(UrlParamsAlreadyTaken.into_response())
Err(UrlParamsAlreadyExtracted.into_response())
}
} else {
Err(MissingRouteParams.into_response())
@ -500,7 +554,6 @@ impl FromRequest for UrlParamsMap {
/// use uuid::Uuid;
///
/// async fn users_teams_show(
/// req: Request<Body>,
/// UrlParams(params): UrlParams<(Uuid, Uuid)>,
/// ) {
/// let user_id: Uuid = params.0;
@ -538,7 +591,7 @@ macro_rules! impl_parse_url {
if let Some(params) = params.take() {
params.0
} else {
return Err(UrlParamsAlreadyTaken.into_response());
return Err(UrlParamsAlreadyExtracted.into_response());
}
} else {
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);
fn take_body(req: &mut Request<Body>) -> Result<Body, BodyAlreadyTaken> {
struct BodyAlreadyTakenExt;
fn take_body(req: &mut Request<Body>) -> Result<Body, BodyAlreadyExtracted> {
struct BodyAlreadyExtractedExt;
if req.extensions_mut().insert(BodyAlreadyTakenExt).is_some() {
Err(BodyAlreadyTaken)
if req
.extensions_mut()
.insert(BodyAlreadyExtractedExt)
.is_some()
{
Err(BodyAlreadyExtracted)
} else {
let body = std::mem::take(req.body_mut());
Ok(body)
Ok(mem::take(req.body_mut()))
}
}

View file

@ -129,7 +129,7 @@ define_rejection! {
#[status = INTERNAL_SERVER_ERROR]
#[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.
pub struct UrlParamsAlreadyTaken;
pub struct UrlParamsAlreadyExtracted;
}
define_rejection! {
@ -137,7 +137,14 @@ define_rejection! {
#[body = "Cannot have two request body extractors for a single handler"]
/// Rejection type used if you try and extract the request body more than
/// 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

View file

@ -2,13 +2,9 @@
//!
//! # What is a handler?
//!
//! In tower-web a "handler" is an async function that accepts a request and
//! produces a response. Handler functions must take
//! `http::Request<tower_web::body::Body>` as they first argument and return
//! something that implements [`IntoResponse`].
//!
//! Additionally handlers can use ["extractors"](crate::extract) to extract data
//! from incoming requests.
//! In tower-web a "handler" is an async function that accepts zero or more
//! ["extractors"](crate::extract) as arguments and returns something that
//! implements [`IntoResponse`].
//!
//! # Example
//!
@ -19,17 +15,17 @@
//! use bytes::Bytes;
//! use http::StatusCode;
//!
//! // Handlers must take `Request<Body>` as the first argument and must return
//! // something that implements `IntoResponse`, which `()` does
//! async fn unit_handler(request: Request<Body>) {}
//! // Handler that immediately returns an empty `200 OK` response.
//! async fn unit_handler() {}
//!
//! // `String` also implements `IntoResponse`
//! async fn string_handler(request: Request<Body>) -> String {
//! // Handler that immediately returns an empty `200 Ok` response with a plain
//! /// text body.
//! async fn string_handler() -> String {
//! "Hello, World!".to_string()
//! }
//!
//! // 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()) {
//! Ok(string)
//! } else {
@ -72,7 +68,7 @@ pub mod future;
/// ```rust
/// 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.
/// let app = route("/", any(handler));
@ -111,7 +107,7 @@ where
/// ```rust
/// use tower_web::prelude::*;
///
/// async fn handler(request: Request<Body>) {}
/// async fn handler() {}
///
/// // Requests to `GET /` will go to `handler`.
/// let app = route("/", get(handler));
@ -190,7 +186,7 @@ where
/// ```rust
/// use tower_web::{handler::on, routing::MethodFilter, prelude::*};
///
/// async fn handler(request: Request<Body>) {}
/// async fn handler() {}
///
/// // Requests to `POST /` will go to `handler`.
/// let app = route("/", on(MethodFilter::Post, handler));
@ -250,7 +246,7 @@ pub trait Handler<In>: Sized {
/// use tower_web::prelude::*;
/// use tower::limit::{ConcurrencyLimitLayer, ConcurrencyLimit};
///
/// async fn handler(request: Request<Body>) { /* ... */ }
/// async fn handler() { /* ... */ }
///
/// let layered_handler = handler.layer(ConcurrencyLimitLayer::new(64));
/// ```
@ -273,14 +269,14 @@ pub trait Handler<In>: Sized {
#[async_trait]
impl<F, Fut, Res> Handler<()> for F
where
F: FnOnce(Request<Body>) -> Fut + Send + Sync,
F: FnOnce() -> Fut + Send + Sync,
Fut: Future<Output = Res> + Send,
Res: IntoResponse,
{
type Sealed = sealed::Hidden;
async fn call(self, req: Request<Body>) -> Response<BoxBody> {
self(req).await.into_response().map(BoxBody::new)
async fn call(self, _req: Request<Body>) -> Response<BoxBody> {
self().await.into_response().map(BoxBody::new)
}
}
@ -292,7 +288,7 @@ macro_rules! impl_handler {
#[allow(non_snake_case)]
impl<F, Fut, Res, $head, $($tail,)*> Handler<($head, $($tail,)*)> for F
where
F: FnOnce(Request<Body>, $head, $($tail,)*) -> Fut + Send + Sync,
F: FnOnce($head, $($tail,)*) -> Fut + Send + Sync,
Fut: Future<Output = Res> + Send,
Res: IntoResponse,
$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)
}
@ -398,7 +394,7 @@ impl<S, T> Layered<S, T> {
/// use tower::{BoxError, timeout::TimeoutLayer};
/// use std::time::Duration;
///
/// async fn handler(request: Request<Body>) { /* ... */ }
/// async fn handler() { /* ... */ }
///
/// // `Timeout` will fail with `BoxError` if the timeout elapses...
/// let layered_handler = handler
@ -545,9 +541,9 @@ impl<S, F> OnMethod<S, F> {
/// ```rust
/// 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
/// // `other_handler`.
@ -628,9 +624,9 @@ impl<S, F> OnMethod<S, F> {
/// ```rust
/// 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
/// // `other_handler`

View file

@ -30,9 +30,7 @@
//! #[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));
@ -53,15 +51,15 @@
//! 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
//! }
//! # async {
@ -79,57 +77,57 @@
//!
//! ```rust,no_run
//! 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()
//! }
//!
@ -152,13 +150,12 @@
//!
//! # 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,no_run
//! use tower_web::prelude::*;
@ -172,7 +169,7 @@
//! 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;
//!
//! // ...
@ -192,7 +189,7 @@
//!
//! 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;
//!
//! // ...
@ -227,7 +224,6 @@
//! }
//!
//! async fn get_user_things(
//! req: Request<Body>,
//! params: extract::UrlParams<(Uuid,)>,
//! 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.
//!
//! [`Uuid`]: https://docs.rs/uuid/latest/uuid/
@ -263,7 +277,7 @@
//! get(handler.layer(ConcurrencyLimitLayer::new(100))),
//! );
//!
//! async fn handler(req: Request<Body>) {}
//! async fn handler() {}
//! # async {
//! # hyper::Server::bind(&"".parse().unwrap()).serve(tower::make::Shared::new(app)).await;
//! # };
@ -281,9 +295,9 @@
//! .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() {}
//! # async {
//! # 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 {
//! # 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 {
//! # 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));
//!
//! async fn handler(
//! req: Request<Body>,
//! state: extract::Extension<Arc<State>>,
//! ) {
//! let state: Arc<State> = state.0;

View file

@ -88,11 +88,11 @@ pub trait RoutingDsl: crate::sealed::Sealed + Sized {
/// ```rust
/// 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`,
/// // and `GET /foo` goes to third_handler.
@ -132,11 +132,11 @@ pub trait RoutingDsl: crate::sealed::Sealed + Sized {
/// ```rust
/// 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> {
/// route("/", get(first_handler).post(second_handler))
@ -181,11 +181,11 @@ pub trait RoutingDsl: crate::sealed::Sealed + Sized {
/// use tower_web::prelude::*;
/// 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
/// // `ConcurrencyLimit`
@ -207,11 +207,11 @@ pub trait RoutingDsl: crate::sealed::Sealed + Sized {
/// use tower_web::prelude::*;
/// 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))
/// .route("/foo", get(second_handler))
@ -584,7 +584,7 @@ impl<S> Layered<S> {
/// use tower::{BoxError, timeout::TimeoutLayer};
/// use std::time::Duration;
///
/// async fn handler(request: Request<Body>) { /* ... */ }
/// async fn handler() { /* ... */ }
///
/// // `Timeout` will fail with `BoxError` if the timeout elapses...
/// let layered_handler = route("/", get(handler))
@ -646,16 +646,17 @@ where
///
/// ```
/// 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
/// // `/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));
///
@ -671,7 +672,7 @@ where
/// ```
/// 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
/// // explicitly captures `id`.
/// let version = params.get("version");

View file

@ -63,7 +63,7 @@
//! # let some_backpressure_sensitive_middleware =
//! # tower::layer::util::Identity::new();
//!
//! async fn handler(request: Request<Body>) { /* ... */ }
//! async fn handler() { /* ... */ }
//!
//! let app = route("/", get(handler));
//!

View file

@ -54,7 +54,7 @@ async fn hello_world() {
#[tokio::test]
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;
@ -79,7 +79,7 @@ async fn deserialize_body() {
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;
@ -137,11 +137,7 @@ async fn body_with_length_limit() {
let app = route(
"/",
post(
|req: Request<Body>, _body: extract::ContentLengthLimit<Bytes, LIMIT>| async move {
dbg!(&req);
},
),
post(|_body: extract::ContentLengthLimit<Bytes, LIMIT>| async {}),
);
let addr = run_in_background(app).await;
@ -240,24 +236,20 @@ async fn routing() {
async fn extracting_url_params() {
let app = route(
"/users/:id",
get(
|_: Request<Body>, params: extract::UrlParams<(i32,)>| async move {
let (id,) = params.0;
assert_eq!(id, 42);
},
)
.post(
|_: Request<Body>, params_map: extract::UrlParamsMap| async move {
assert_eq!(params_map.get("id").unwrap(), "1337");
assert_eq!(
params_map
.get_typed::<i32>("id")
.expect("missing")
.expect("failed to parse"),
1337
);
},
),
get(|params: extract::UrlParams<(i32,)>| async move {
let (id,) = params.0;
assert_eq!(id, 42);
})
.post(|params_map: extract::UrlParamsMap| async move {
assert_eq!(params_map.get("id").unwrap(), "1337");
assert_eq!(
params_map
.get_typed::<i32>("id")
.expect("missing")
.expect("failed to parse"),
1337
);
}),
);
let addr = run_in_background(app).await;
@ -502,35 +494,30 @@ async fn layer_on_whole_router() {
async fn disjunction() {
let api_routes = route(
"/users",
get(|_: Request<Body>| async { "users#index" })
.post(|_: Request<Body>| async { "users#create" }),
get(|| async { "users#index" }).post(|| async { "users#create" }),
)
.route(
"/users/:id",
get(
|_: Request<Body>, params: extract::UrlParamsMap| async move {
format!(
"{}: users#show ({})",
params.get("version").unwrap(),
params.get("id").unwrap()
)
},
),
get(|params: extract::UrlParamsMap| async move {
format!(
"{}: users#show ({})",
params.get("version").unwrap(),
params.get("id").unwrap()
)
}),
)
.route(
"/games/:id",
get(
|_: Request<Body>, params: extract::UrlParamsMap| async move {
format!(
"{}: games#show ({})",
params.get("version").unwrap(),
params.get("id").unwrap()
)
},
),
get(|params: extract::UrlParamsMap| async move {
format!(
"{}: games#show ({})",
params.get("version").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;