From d8b0153939875dde2e3119250bb30fff16225a5a Mon Sep 17 00:00:00 2001 From: David Pedersen Date: Mon, 7 Jun 2021 15:45:19 +0200 Subject: [PATCH] Write some more docs --- examples/static_file_server.rs | 4 +- src/body.rs | 2 + src/handler.rs | 247 ++++++++++++++++++++++++++++++++- src/lib.rs | 3 +- src/response.rs | 2 + src/routing.rs | 114 +++++++++++++-- src/service.rs | 5 + 7 files changed, 358 insertions(+), 19 deletions(-) diff --git a/examples/static_file_server.rs b/examples/static_file_server.rs index 265fc47b..07f165e9 100644 --- a/examples/static_file_server.rs +++ b/examples/static_file_server.rs @@ -11,12 +11,12 @@ async fn main() { let app = tower_web::routing::nest( "/static", - ServeDir::new(".").handle_error(|error: std::io::Error| { + tower_web::service::get(ServeDir::new(".").handle_error(|error: std::io::Error| { ( StatusCode::INTERNAL_SERVER_ERROR, format!("Unhandled interal error: {}", error), ) - }), + })), ) .layer(TraceLayer::new_for_http()); diff --git a/src/body.rs b/src/body.rs index a25f79eb..b490dd97 100644 --- a/src/body.rs +++ b/src/body.rs @@ -1,3 +1,5 @@ +//! HTTP body utilities. + use bytes::Bytes; use http_body::{Empty, Full}; use std::{ diff --git a/src/handler.rs b/src/handler.rs index 8ba99047..c8c3fd47 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -11,12 +11,26 @@ use futures_util::future; use http::{Request, Response}; use std::{ convert::Infallible, + fmt, future::Future, marker::PhantomData, task::{Context, Poll}, }; use tower::{BoxError, Layer, Service, ServiceExt}; +/// Route requests to the given handler regardless of the HTTP method of the +/// request. +/// +/// # Example +/// +/// ```rust +/// use tower_web::prelude::*; +/// +/// async fn handler(request: Request) {} +/// +/// // All requests to `/` will go to `handler` regardless of the HTTP method. +/// let app = route("/", any(handler)); +/// ``` pub fn any(handler: H) -> OnMethod, EmptyRouter> where H: Handler, @@ -24,6 +38,9 @@ where on(MethodFilter::Any, handler) } +/// Route `CONNECT` requests to the given handler. +/// +/// See [`get`] for an example. pub fn connect(handler: H) -> OnMethod, EmptyRouter> where H: Handler, @@ -31,6 +48,9 @@ where on(MethodFilter::Connect, handler) } +/// Route `DELETE` requests to the given handler. +/// +/// See [`get`] for an example. pub fn delete(handler: H) -> OnMethod, EmptyRouter> where H: Handler, @@ -38,6 +58,18 @@ where on(MethodFilter::Delete, handler) } +/// Route `GET` requests to the given handler. +/// +/// # Example +/// +/// ```rust +/// use tower_web::prelude::*; +/// +/// async fn handler(request: Request) {} +/// +/// // Requests to `GET /` will go to `handler`. +/// let app = route("/", get(handler)); +/// ``` pub fn get(handler: H) -> OnMethod, EmptyRouter> where H: Handler, @@ -45,6 +77,9 @@ where on(MethodFilter::Get, handler) } +/// Route `HEAD` requests to the given handler. +/// +/// See [`get`] for an example. pub fn head(handler: H) -> OnMethod, EmptyRouter> where H: Handler, @@ -52,6 +87,9 @@ where on(MethodFilter::Head, handler) } +/// Route `OPTIONS` requests to the given handler. +/// +/// See [`get`] for an example. pub fn options(handler: H) -> OnMethod, EmptyRouter> where H: Handler, @@ -59,6 +97,9 @@ where on(MethodFilter::Options, handler) } +/// Route `PATCH` requests to the given handler. +/// +/// See [`get`] for an example. pub fn patch(handler: H) -> OnMethod, EmptyRouter> where H: Handler, @@ -66,6 +107,9 @@ where on(MethodFilter::Patch, handler) } +/// Route `POST` requests to the given handler. +/// +/// See [`get`] for an example. pub fn post(handler: H) -> OnMethod, EmptyRouter> where H: Handler, @@ -73,6 +117,9 @@ where on(MethodFilter::Post, handler) } +/// Route `PUT` requests to the given handler. +/// +/// See [`get`] for an example. pub fn put(handler: H) -> OnMethod, EmptyRouter> where H: Handler, @@ -80,6 +127,9 @@ where on(MethodFilter::Put, handler) } +/// Route `TRACE` requests to the given handler. +/// +/// See [`get`] for an example. pub fn trace(handler: H) -> OnMethod, EmptyRouter> where H: Handler, @@ -87,6 +137,18 @@ where on(MethodFilter::Trace, handler) } +/// Route requests with the given method to the handler. +/// +/// # Example +/// +/// ```rust +/// use tower_web::{handler::on, routing::MethodFilter, prelude::*}; +/// +/// async fn handler(request: Request) {} +/// +/// // Requests to `POST /` will go to `handler`. +/// let app = route("/", on(MethodFilter::Post, handler)); +/// ``` pub fn on(method: MethodFilter, handler: H) -> OnMethod, EmptyRouter> where H: Handler, @@ -99,13 +161,49 @@ where } mod sealed { - #![allow(unreachable_pub)] + #![allow(unreachable_pub, missing_docs, missing_debug_implementations)] pub trait HiddentTrait {} pub struct Hidden; impl HiddentTrait for Hidden {} } +/// Trait for async functions that can be used to handle requests. +/// +/// You shouldn't need to depend on this trait directly. It is automatically +/// implemented to closures of the right types. +/// +/// # Example +/// +/// Some examples of handlers: +/// +/// ```rust +/// use tower_web::prelude::*; +/// 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) {} +/// +/// // `String` also implements `IntoResponse` +/// async fn string_handler(request: Request) -> String { +/// "Hello, World!".to_string() +/// } +/// +/// // Handler the buffers the request body and returns it if it is valid UTF-8 +/// async fn buffer_body(request: Request, body: Bytes) -> Result { +/// if let Ok(string) = String::from_utf8(body.to_vec()) { +/// Ok(string) +/// } else { +/// Err(StatusCode::BAD_REQUEST) +/// } +/// } +/// ``` +/// +/// For more details on generating responses see the +/// [`response`](crate::response) module and for more details on extractors see +/// the [`extract`](crate::extract) module. #[async_trait] pub trait Handler: Sized { // This seals the trait. We cannot use the regular "sealed super trait" approach @@ -113,8 +211,27 @@ pub trait Handler: Sized { #[doc(hidden)] type Sealed: sealed::HiddentTrait; + /// Call the handler with the given request. async fn call(self, req: Request) -> Response; + /// Apply a [`tower::Layer`] to the handler. + /// + /// # Example + /// + /// Adding the [`tower::limit::ConcurrencyLimit`] middleware to a handler + /// can be done with [`tower::limit::ConcurrencyLimitLayer`]: + /// + /// ```rust + /// use tower_web::prelude::*; + /// use tower::limit::{ConcurrencyLimitLayer, ConcurrencyLimit}; + /// + /// async fn handler(request: Request) { /* ... */ } + /// + /// let layered_handler = handler.layer(ConcurrencyLimitLayer::new(64)); + /// ``` + /// + /// When adding middleware that might fail its required to handle those + /// errors. See [`Layered::handle_error`] for more details. fn layer(self, layer: L) -> Layered where L: Layer>, @@ -122,6 +239,7 @@ pub trait Handler: Sized { Layered::new(layer.layer(IntoService::new(self))) } + /// Convert the handler into a [`Service`]. fn into_service(self) -> IntoService { IntoService::new(self) } @@ -182,11 +300,23 @@ macro_rules! impl_handler { impl_handler!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16); +/// A [`Service`] created from a [`Handler`] by applying a Tower middleware. +/// +/// Created with [`Handler::layer`]. pub struct Layered { svc: S, _input: PhantomData T>, } +impl fmt::Debug for Layered +where + S: fmt::Debug, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Layered").field("svc", &self.svc).finish() + } +} + impl Clone for Layered where S: Clone, @@ -229,6 +359,44 @@ impl Layered { } } + /// Create a new [`Layered`] handler where errors will be handled using the + /// given closure. + /// + /// tower-web requires that services gracefully handles all errors. That + /// means when you apply a Tower middleware that adds a new failure + /// condition you have to handle that as well. + /// + /// That can be done using `handle_error` like so: + /// + /// ```rust + /// use tower_web::prelude::*; + /// use http::StatusCode; + /// use tower::{BoxError, timeout::TimeoutLayer}; + /// use std::time::Duration; + /// + /// async fn handler(request: Request) { /* ... */ } + /// + /// // `Timeout` will fail with `BoxError` if the timeout elapses... + /// let layered_handler = handler + /// .layer(TimeoutLayer::new(Duration::from_secs(30))); + /// + /// // ...so we must handle that error + /// let layered_handler = layered_handler.handle_error(|error: BoxError| { + /// if error.is::() { + /// ( + /// StatusCode::REQUEST_TIMEOUT, + /// "request took too long".to_string(), + /// ) + /// } else { + /// ( + /// StatusCode::INTERNAL_SERVER_ERROR, + /// format!("Unhandled internal error: {}", error), + /// ) + /// } + /// }); + /// ``` + /// + /// The closure can return any type that implements [`IntoResponse`]. pub fn handle_error(self, f: F) -> Layered, T> where S: Service, Response = Response>, @@ -240,6 +408,9 @@ impl Layered { } } +/// An adapter that makes a [`Handler`] into a [`Service`]. +/// +/// Created with [`Handler::into_service`]. pub struct IntoService { handler: H, _marker: PhantomData T>, @@ -254,6 +425,17 @@ impl IntoService { } } +impl fmt::Debug for IntoService +where + H: fmt::Debug, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("IntoService") + .field("handler", &self.handler) + .finish() + } +} + impl Clone for IntoService where H: Clone, @@ -292,11 +474,13 @@ where } opaque_future! { + /// The response future for [`IntoService`]. pub type IntoServiceFuture = future::BoxFuture<'static, Result, Infallible>>; } -#[derive(Clone)] +/// A handler [`Service`] that accepts requests based on a [`MethodFilter`]. +#[derive(Debug, Clone, Copy)] pub struct OnMethod { pub(crate) method: MethodFilter, pub(crate) svc: S, @@ -304,6 +488,10 @@ pub struct OnMethod { } impl OnMethod { + /// Chain an additional handler that will accept all requests regardless of + /// its HTTP method. + /// + /// See [`OnMethod::get`] for an example. pub fn any(self, handler: H) -> OnMethod, Self> where H: Handler, @@ -311,6 +499,9 @@ impl OnMethod { self.on(MethodFilter::Any, handler) } + /// Chain an additional handler that will only accept `CONNECT` requests. + /// + /// See [`OnMethod::get`] for an example. pub fn connect(self, handler: H) -> OnMethod, Self> where H: Handler, @@ -318,6 +509,9 @@ impl OnMethod { self.on(MethodFilter::Connect, handler) } + /// Chain an additional handler that will only accept `DELETE` requests. + /// + /// See [`OnMethod::get`] for an example. pub fn delete(self, handler: H) -> OnMethod, Self> where H: Handler, @@ -325,6 +519,21 @@ impl OnMethod { self.on(MethodFilter::Delete, handler) } + /// Chain an additional handler that will only accept `GET` requests. + /// + /// # Example + /// + /// ```rust + /// use tower_web::prelude::*; + /// + /// async fn handler(request: Request) {} + /// + /// async fn other_handler(request: Request) {} + /// + /// // Requests to `GET /` will go to `handler` and `POST /` will go to + /// // `other_handler`. + /// let app = route("/", post(handler).get(other_handler)); + /// ``` pub fn get(self, handler: H) -> OnMethod, Self> where H: Handler, @@ -332,6 +541,9 @@ impl OnMethod { self.on(MethodFilter::Get, handler) } + /// Chain an additional handler that will only accept `HEAD` requests. + /// + /// See [`OnMethod::get`] for an example. pub fn head(self, handler: H) -> OnMethod, Self> where H: Handler, @@ -339,6 +551,9 @@ impl OnMethod { self.on(MethodFilter::Head, handler) } + /// Chain an additional handler that will only accept `OPTIONS` requests. + /// + /// See [`OnMethod::get`] for an example. pub fn options(self, handler: H) -> OnMethod, Self> where H: Handler, @@ -346,6 +561,9 @@ impl OnMethod { self.on(MethodFilter::Options, handler) } + /// Chain an additional handler that will only accept `PATCH` requests. + /// + /// See [`OnMethod::get`] for an example. pub fn patch(self, handler: H) -> OnMethod, Self> where H: Handler, @@ -353,6 +571,9 @@ impl OnMethod { self.on(MethodFilter::Patch, handler) } + /// Chain an additional handler that will only accept `POST` requests. + /// + /// See [`OnMethod::get`] for an example. pub fn post(self, handler: H) -> OnMethod, Self> where H: Handler, @@ -360,6 +581,9 @@ impl OnMethod { self.on(MethodFilter::Post, handler) } + /// Chain an additional handler that will only accept `PUT` requests. + /// + /// See [`OnMethod::get`] for an example. pub fn put(self, handler: H) -> OnMethod, Self> where H: Handler, @@ -367,6 +591,9 @@ impl OnMethod { self.on(MethodFilter::Put, handler) } + /// Chain an additional handler that will only accept `TRACE` requests. + /// + /// See [`OnMethod::get`] for an example. pub fn trace(self, handler: H) -> OnMethod, Self> where H: Handler, @@ -374,6 +601,22 @@ impl OnMethod { self.on(MethodFilter::Trace, handler) } + /// Chain an additional handler that will accept requests matching the given + /// `MethodFilter`. + /// + /// # Example + /// + /// ```rust + /// use tower_web::{routing::MethodFilter, prelude::*}; + /// + /// async fn handler(request: Request) {} + /// + /// async fn other_handler(request: Request) {} + /// + /// // Requests to `GET /` will go to `handler` and `DELETE /` will go to + /// // `other_handler` + /// let app = route("/", get(handler).on(MethodFilter::Delete, other_handler)); + /// ``` pub fn on(self, method: MethodFilter, handler: H) -> OnMethod, Self> where H: Handler, diff --git a/src/lib.rs b/src/lib.rs index f0360878..98806a50 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -570,6 +570,7 @@ rust_2018_idioms, future_incompatible, nonstandard_style, + missing_debug_implementations, missing_docs )] #![deny(unreachable_pub, broken_intra_doc_links, private_in_public)] @@ -629,7 +630,7 @@ pub mod prelude { /// /// - `/foo/bar/baz` will only match requests where the path is `/foo/bar/bar`. /// - `/:foo` will match any route with exactly one segment _and_ it will -/// capture the first segment and store it only the key `foo`. +/// capture the first segment and store it at the key `foo`. /// /// `service` is the [`Service`] that should receive the request if the path /// matches `description`. diff --git a/src/response.rs b/src/response.rs index d7331465..15d85b2e 100644 --- a/src/response.rs +++ b/src/response.rs @@ -160,6 +160,7 @@ where /// An HTML response. /// /// Will automatically get `Content-Type: text/html`. +#[derive(Clone, Copy, Debug)] pub struct Html(pub T); impl IntoResponse for Html @@ -198,6 +199,7 @@ where /// "application/json", /// ); /// ``` +#[derive(Clone, Copy, Debug)] pub struct Json(pub T); impl IntoResponse for Json diff --git a/src/routing.rs b/src/routing.rs index b6735f68..47161fa3 100644 --- a/src/routing.rs +++ b/src/routing.rs @@ -21,19 +21,28 @@ use tower::{ BoxError, Layer, Service, ServiceBuilder, }; -// ===== DSL ===== - +/// A filter that matches one or more HTTP method. #[derive(Debug, Copy, Clone)] pub enum MethodFilter { + /// Match any method. Any, + /// Match `CONNECT` requests. Connect, + /// Match `DELETE` requests. Delete, + /// Match `GET` requests. Get, + /// Match `HEAD` requests. Head, + /// Match `OPTIONS` requests. Options, + /// Match `PATCH` requests. Patch, + /// Match `POST` requests. Post, + /// Match `PUT` requests. Put, + /// Match `TRACE` requests. Trace, } @@ -56,7 +65,11 @@ impl MethodFilter { } } -#[derive(Clone)] +/// A route that sends requests to one of two [`Service`]s depending on the +/// path. +/// +/// Created with [`route`](crate::route). See that function for more details. +#[derive(Debug, Clone)] pub struct Route { pub(crate) pattern: PathPattern, pub(crate) svc: S, @@ -111,8 +124,6 @@ pub trait RoutingDsl: Sized { impl RoutingDsl for Route {} -// ===== Routing service impls ===== - impl Service> for Route where S: Service, Response = Response, Error = Infallible> + Clone, @@ -144,7 +155,9 @@ where } } +/// The response future for [`Route`]. #[pin_project] +#[derive(Debug)] pub struct RouteFuture( #[pin] pub(crate) future::Either< @@ -185,7 +198,9 @@ fn insert_url_params(req: &mut Request, params: Vec<(String, String)>) { } } +/// A response future that boxes the response body with [`BoxBody`]. #[pin_project] +#[derive(Debug)] pub struct BoxResponseBody(#[pin] pub(crate) F); impl Future for BoxResponseBody @@ -206,7 +221,11 @@ where } } -#[derive(Clone, Copy)] +/// A [`Service`] that responds with `404 Not Found` to all requests. +/// +/// This is used as the bottom service in a router stack. You shouldn't have to +/// use to manually. +#[derive(Debug, Clone, Copy)] pub struct EmptyRouter; impl RoutingDsl for EmptyRouter {} @@ -228,12 +247,11 @@ impl Service> for EmptyRouter { } opaque_future! { + /// Response future for [`EmptyRouter`]. pub type EmptyRouterFuture = future::Ready, Infallible>>; } -// ===== PathPattern ===== - #[derive(Debug, Clone)] pub(crate) struct PathPattern(Arc); @@ -324,8 +342,6 @@ struct Match<'a> { type Captures = Vec<(String, String)>; -// ===== BoxRoute ===== - pub struct BoxRoute(Buffer, Response, Infallible>, Request>); impl Clone for BoxRoute { @@ -356,6 +372,7 @@ where } } +/// The response future for [`BoxRoute`]. #[pin_project] pub struct BoxRouteFuture(#[pin] InnerFuture); @@ -411,8 +428,6 @@ fn handle_buffer_error(error: BoxError) -> Response { .unwrap() } -// ===== Layered ===== - #[derive(Clone, Debug)] pub struct Layered(S); @@ -450,8 +465,76 @@ where } } -// ===== nesting ===== - +/// Nest a group of routes (or [`Service`]) at some path. +/// +/// This allows you to break your application into smaller pieces and compose +/// them together. This will strip the matching prefix from the URL so the +/// nested route will only see the part of URL: +/// +/// ``` +/// use tower_web::{routing::nest, prelude::*}; +/// +/// async fn users_get(request: Request) { +/// // `users_get` doesn't see the whole URL. `nest` will strip the matching +/// // `/api` prefix. +/// assert_eq!(request.uri().path(), "/users"); +/// } +/// +/// async fn users_post(request: Request) {} +/// +/// async fn careers(request: Request) {} +/// +/// let users_api = route("/users", get(users_get).post(users_post)); +/// +/// let app = nest("/api", users_api).route("/careers", get(careers)); +/// # async { +/// # hyper::Server::bind(&"".parse().unwrap()).serve(tower::make::Shared::new(app)).await; +/// # }; +/// ``` +/// +/// Take care when using `nest` together with dynamic routes as nesting also +/// captures from the outer routes: +/// +/// ``` +/// use tower_web::{routing::nest, prelude::*}; +/// +/// async fn users_get(request: Request, params: extract::UrlParamsMap) { +/// // Both `version` and `id` were captured even though `users_api` only +/// // explicitly captures `id`. +/// let version = params.get("version"); +/// let id = params.get("id"); +/// } +/// +/// let users_api = route("/users/:id", get(users_get)); +/// +/// let app = nest("/:version/api", users_api); +/// # async { +/// # hyper::Server::bind(&"".parse().unwrap()).serve(tower::make::Shared::new(app)).await; +/// # }; +/// ``` +/// +/// `nest` also accepts any [`Service`]. This can for example be used with +/// [`tower_http::services::ServeDir`] to serve static files from a directory: +/// +/// ``` +/// use tower_web::{ +/// routing::nest, service::get, ServiceExt, prelude::*, +/// }; +/// use tower_http::services::ServeDir; +/// +/// // Serves files inside the `public` directory at `GET /public/*` +/// let serve_dir_service = ServeDir::new("public") +/// .handle_error(|error: std::io::Error| { /* ... */ }); +/// +/// let app = nest("/public", get(serve_dir_service)); +/// # async { +/// # hyper::Server::bind(&"".parse().unwrap()).serve(tower::make::Shared::new(app)).await; +/// # }; +/// ``` +/// +/// If necessary you can use [`RoutingDsl::boxed`] to box a group of routes +/// making the type easier to name. This is sometimes useful when working with +/// `nest`. pub fn nest(description: &str, svc: S) -> Nested where S: Service, Error = Infallible> + Clone, @@ -463,6 +546,9 @@ where } } +/// A [`Service`] that has been nested inside a router at some path. +/// +/// Created with [`nest`] or [`RoutingDsl::nest`]. #[derive(Debug, Clone)] pub struct Nested { pattern: PathPattern, diff --git a/src/service.rs b/src/service.rs index 927451a2..6b41e0a1 100644 --- a/src/service.rs +++ b/src/service.rs @@ -187,6 +187,11 @@ where } } +/// A [`Service`] adapter that handles errors with a closure. +/// +/// Create with [`handler::Layered::handle_error`](crate::handler::Layered::handle_error) or +/// [`routing::Layered::handle_error`](crate::routing::Layered::handle_error). See those methods +/// for more details. #[derive(Clone)] pub struct HandleError { pub(crate) inner: S,