diff --git a/README.md b/README.md
index 048f1216..c6fea0cd 100644
--- a/README.md
+++ b/README.md
@@ -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
| 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) {
+async fn get_slash() {
// `GET /` called
}
-async fn post_slash(req: Request) {
+async fn post_slash() {
// `POST /` called
}
-async fn get_foo(req: Request) {
+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) -> &'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) -> 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) -> Vec {
+async fn bytes() -> Vec {
vec![1, 2, 3, 4]
}
// `()` gives an empty response
-async fn empty(req: Request) {}
+async fn empty() {}
// `StatusCode` gives an empty response with that status code
-async fn empty_with_status(req: Request) -> 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) -> (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) -> Html<&'static str> {
+async fn html() -> Html<&'static str> {
Html("
Hello, World!
")
}
// `Json` gives a content-type of `application/json` and works with any type
// that implements `serde::Serialize`
-async fn json(req: Request) -> Json {
+async fn json() -> Json {
Json(json!({ "data": 42 }))
}
// `Result` where `T` and `E` implement `IntoResponse` is useful for
// returning errors
-async fn result(req: Request) -> Result<&'static str, StatusCode> {
+async fn result() -> Result<&'static str, StatusCode> {
Ok("all good")
}
// `Response` gives full control
-async fn response(req: Request) -> Response {
+async fn response() -> Response {
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` 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, payload: extract::Json) {
+async fn create_user(payload: extract::Json) {
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, 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,
params: extract::UrlParams<(Uuid,)>,
pagination: Option>,
) {
@@ -228,6 +224,21 @@ async fn get_user_things(
}
```
+Additionally `Request` is itself an extractor:
+
+```rust
+use tower_web::prelude::*;
+
+let app = route("/users/:id", post(handler));
+
+async fn handler(req: Request) {
+ // ...
+}
+```
+
+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) {}
+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) {}
+async fn get_slash() {}
-async fn post_foo(req: Request) {}
+async fn post_foo() {}
```
### Error handling
@@ -314,7 +325,7 @@ let app = route(
})),
);
-async fn handle(req: Request) {}
+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) {}
+async fn handle() {}
-async fn other_handle(req: Request) {}
+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,
state: extract::Extension>,
) {
let state: Arc = 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
diff --git a/examples/hello_world.rs b/examples/hello_world.rs
index 3d6f9d0f..dfdf7f97 100644
--- a/examples/hello_world.rs
+++ b/examples/hello_world.rs
@@ -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) -> response::Html<&'static str> {
+async fn handler() -> response::Html<&'static str> {
response::Html("
Hello, World!
")
}
-async fn greet(_req: Request, params: extract::UrlParamsMap) -> Result {
+async fn greet(params: extract::UrlParamsMap) -> Result {
if let Some(name) = params.get("name") {
Ok(format!("Hello {}!", name))
} else {
diff --git a/examples/key_value_store.rs b/examples/key_value_store.rs
index f76ab510..f8a2cd2e 100644
--- a/examples/key_value_store.rs
+++ b/examples/key_value_store.rs
@@ -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,
UrlParams((key,)): UrlParams<(String,)>,
Extension(state): Extension,
) -> Result {
@@ -86,7 +85,6 @@ async fn kv_get(
}
async fn kv_set(
- _req: Request,
UrlParams((key,)): UrlParams<(String,)>,
ContentLengthLimit(bytes): ContentLengthLimit, // ~5mb
Extension(state): Extension,
@@ -94,7 +92,7 @@ async fn kv_set(
state.write().unwrap().db.insert(key, bytes);
}
-async fn list_keys(_req: Request, Extension(state): Extension) -> String {
+async fn list_keys(Extension(state): Extension) -> String {
let db = &state.read().unwrap().db;
db.keys()
@@ -104,12 +102,11 @@ async fn list_keys(_req: Request, Extension(state): Extension
}
fn admin_routes() -> BoxRoute {
- async fn delete_all_keys(_req: Request, Extension(state): Extension) {
+ async fn delete_all_keys(Extension(state): Extension) {
state.write().unwrap().db.clear();
}
async fn remove_key(
- _req: Request,
UrlParams((key,)): UrlParams<(String,)>,
Extension(state): Extension,
) {
diff --git a/src/extract/mod.rs b/src/extract/mod.rs
index bebb5257..d0ccc76a 100644
--- a/src/extract/mod.rs
+++ b/src/extract/mod.rs
@@ -1,8 +1,8 @@
//! Types and traits for extracting data from requests.
//!
-//! A handler function must always take `Request` 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, payload: extract::Json) {
+//! async fn create_user(payload: extract::Json) {
//! let payload: CreateUser = payload.0;
//!
//! // ...
@@ -52,7 +52,7 @@
//! }
//! }
//!
-//! async fn handler(req: Request, 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,
//! // 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, payload: Option>) {
+//! async fn create_user(payload: Option>) {
//! 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, Json(value): Json) {
+//! async fn create_user(Json(value): Json) {
//! // `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, pagination: extract::Query) {
+/// async fn list_things(pagination: extract::Query) {
/// let pagination: Pagination = pagination.0;
///
/// // ...
@@ -231,7 +230,7 @@ where
/// password: String,
/// }
///
-/// async fn create_user(req: Request, payload: extract::Json) {
+/// async fn create_user(payload: extract::Json) {
/// let payload: CreateUser = payload.0;
///
/// // ...
@@ -307,7 +306,7 @@ fn has_content_type(req: &Request, expected_content_type: &str) -> bool {
/// // ...
/// }
///
-/// async fn handler(req: Request, state: extract::Extension>) {
+/// async fn handler(state: extract::Extension>) {
/// // ...
/// }
///
@@ -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) -> Result {
take_body(req)
}
}
+#[async_trait]
+impl FromRequest for Request {
+ type Rejection = RequestAlreadyExtracted;
+
+ async fn from_request(req: &mut Request) -> Result {
+ 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) -> Result {
+ Ok(req.method().clone())
+ }
+}
+
+#[async_trait]
+impl FromRequest for Uri {
+ type Rejection = Infallible;
+
+ async fn from_request(req: &mut Request) -> Result {
+ Ok(req.uri().clone())
+ }
+}
+
+#[async_trait]
+impl FromRequest for Version {
+ type Rejection = Infallible;
+
+ async fn from_request(req: &mut Request) -> Result {
+ Ok(req.version())
+ }
+}
+
+#[async_trait]
+impl FromRequest for HeaderMap {
+ type Rejection = Infallible;
+
+ async fn from_request(req: &mut Request) -> Result {
+ 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: extract::ContentLengthLimit) {
+/// async fn handler(body: extract::ContentLengthLimit) {
/// // ...
/// }
///
@@ -442,7 +496,7 @@ where
/// ```rust,no_run
/// use tower_web::prelude::*;
///
-/// async fn users_show(req: Request, 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,
/// 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) -> Result {
- struct BodyAlreadyTakenExt;
+fn take_body(req: &mut Request) -> Result {
+ 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()))
}
}
diff --git a/src/extract/rejection.rs b/src/extract/rejection.rs
index af388d77..ef961cab 100644
--- a/src/extract/rejection.rs
+++ b/src/extract/rejection.rs
@@ -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` 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
diff --git a/src/handler/mod.rs b/src/handler/mod.rs
index 32d5b57c..48c8d9a1 100644
--- a/src/handler/mod.rs
+++ b/src/handler/mod.rs
@@ -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` 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` as the first argument and must return
-//! // something that implements `IntoResponse`, which `()` does
-//! async fn unit_handler(request: Request) {}
+//! // Handler that immediately returns an empty `200 OK` response.
+//! async fn unit_handler() {}
//!
-//! // `String` also implements `IntoResponse`
-//! async fn string_handler(request: Request) -> 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: Bytes) -> Result {
+//! async fn buffer_body(body: Bytes) -> Result {
//! 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) {}
+/// 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) {}
+/// 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) {}
+/// async fn handler() {}
///
/// // Requests to `POST /` will go to `handler`.
/// let app = route("/", on(MethodFilter::Post, handler));
@@ -250,7 +246,7 @@ pub trait Handler: Sized {
/// use tower_web::prelude::*;
/// use tower::limit::{ConcurrencyLimitLayer, ConcurrencyLimit};
///
- /// async fn handler(request: Request) { /* ... */ }
+ /// async fn handler() { /* ... */ }
///
/// let layered_handler = handler.layer(ConcurrencyLimitLayer::new(64));
/// ```
@@ -273,14 +269,14 @@ pub trait Handler: Sized {
#[async_trait]
impl Handler<()> for F
where
- F: FnOnce(Request) -> Fut + Send + Sync,
+ F: FnOnce() -> Fut + Send + Sync,
Fut: Future