diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 005c0a54..2e6fd566 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -30,6 +30,18 @@ jobs: command: fmt args: --all -- --check + check-docs: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@master + - uses: actions-rs/toolchain@v1 + with: + toolchain: stable + profile: minimal + - name: cargo doc + working-directory: ${{ matrix.subcrate }} + run: cargo doc --all-features --no-deps + cargo-hack: runs-on: ubuntu-latest steps: diff --git a/Cargo.toml b/Cargo.toml index 98561cf3..bab9ec77 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,7 +13,10 @@ repository = "https://github.com/davidpdrsn/tower-web" version = "0.1.0" [features] +default = ["hyper-h1"] ws = ["tokio-tungstenite", "sha-1", "base64"] +hyper-h1 = ["hyper/http1"] +hyper-h2 = ["hyper/http2"] [dependencies] async-trait = "0.1" @@ -21,14 +24,14 @@ bytes = "1.0" futures-util = "0.3" http = "0.2" http-body = "0.4" -hyper = "0.14" +hyper = { version = "0.14", features = ["server", "tcp"] } pin-project = "1.0" regex = "1.5" serde = "1.0" serde_json = "1.0" serde_urlencoded = "0.7" tokio = { version = "1", features = ["time"] } -tower = { version = "0.4", features = ["util", "buffer"] } +tower = { version = "0.4", features = ["util", "buffer", "make"] } tower-http = { version = "0.1", features = ["add-extension"] } # optional dependencies @@ -49,7 +52,6 @@ uuid = "0.8" version = "0.4" features = [ "util", - "make", "timeout", "limit", "load-shed", diff --git a/examples/hello_world.rs b/examples/hello_world.rs index dfdf7f97..3d01ba59 100644 --- a/examples/hello_world.rs +++ b/examples/hello_world.rs @@ -1,7 +1,5 @@ use http::StatusCode; -use hyper::Server; use std::net::SocketAddr; -use tower::make::Shared; use tower_web::prelude::*; #[tokio::main] @@ -11,11 +9,10 @@ async fn main() { // build our application with some routes let app = route("/", get(handler)).route("/greet/:name", get(greet)); - // run it with hyper + // run it let addr = SocketAddr::from(([127, 0, 0, 1], 3000)); tracing::debug!("listening on {}", addr); - let server = Server::bind(&addr).serve(Shared::new(app)); - server.await.unwrap(); + app.serve(&addr).await.unwrap(); } async fn handler() -> response::Html<&'static str> { diff --git a/examples/key_value_store.rs b/examples/key_value_store.rs index f8a2cd2e..06d61f17 100644 --- a/examples/key_value_store.rs +++ b/examples/key_value_store.rs @@ -8,7 +8,6 @@ use bytes::Bytes; use http::StatusCode; -use hyper::Server; use std::{ borrow::Cow, collections::HashMap, @@ -16,7 +15,7 @@ use std::{ sync::{Arc, RwLock}, time::Duration, }; -use tower::{make::Shared, BoxError, ServiceBuilder}; +use tower::{BoxError, ServiceBuilder}; use tower_http::{ add_extension::AddExtensionLayer, auth::RequireAuthorizationLayer, compression::CompressionLayer, trace::TraceLayer, @@ -60,8 +59,7 @@ async fn main() { // Run our app with hyper let addr = SocketAddr::from(([127, 0, 0, 1], 3000)); tracing::debug!("listening on {}", addr); - let server = Server::bind(&addr).serve(Shared::new(app)); - server.await.unwrap(); + app.serve(&addr).await.unwrap(); } type SharedState = Arc>; diff --git a/examples/static_file_server.rs b/examples/static_file_server.rs index d16da746..295bf081 100644 --- a/examples/static_file_server.rs +++ b/examples/static_file_server.rs @@ -1,7 +1,5 @@ use http::StatusCode; -use hyper::Server; use std::net::SocketAddr; -use tower::make::Shared; use tower_http::{services::ServeDir, trace::TraceLayer}; use tower_web::{prelude::*, service::ServiceExt}; @@ -22,6 +20,5 @@ async fn main() { let addr = SocketAddr::from(([127, 0, 0, 1], 3000)); tracing::debug!("listening on {}", addr); - let server = Server::bind(&addr).serve(Shared::new(app)); - server.await.unwrap(); + app.serve(&addr).await.unwrap(); } diff --git a/examples/websocket.rs b/examples/websocket.rs index f58ce0ad..73b5fa1d 100644 --- a/examples/websocket.rs +++ b/examples/websocket.rs @@ -10,9 +10,7 @@ //! ``` use http::StatusCode; -use hyper::Server; use std::net::SocketAddr; -use tower::make::Shared; use tower_http::{ services::ServeDir, trace::{DefaultMakeSpan, TraceLayer}, @@ -46,8 +44,7 @@ async fn main() { // run it with hyper let addr = SocketAddr::from(([127, 0, 0, 1], 3000)); tracing::debug!("listening on {}", addr); - let server = Server::bind(&addr).serve(Shared::new(app)); - server.await.unwrap(); + app.serve(&addr).await.unwrap(); } async fn handle_socket(mut socket: WebSocket) { diff --git a/src/extract/mod.rs b/src/extract/mod.rs index 75401d7a..7aa55445 100644 --- a/src/extract/mod.rs +++ b/src/extract/mod.rs @@ -25,7 +25,7 @@ //! //! let app = route("/users", post(create_user)); //! # async { -//! # hyper::Server::bind(&"".parse().unwrap()).serve(tower::make::Shared::new(app)).await; +//! # app.serve(&"".parse().unwrap()).await.unwrap(); //! # }; //! ``` //! @@ -60,7 +60,7 @@ //! //! let app = route("/foo", get(handler)); //! # async { -//! # hyper::Server::bind(&"".parse().unwrap()).serve(tower::make::Shared::new(app)).await; +//! # app.serve(&"".parse().unwrap()).await.unwrap(); //! # }; //! ``` //! @@ -85,7 +85,7 @@ //! //! let app = route("/foo", get(handler)); //! # async { -//! # hyper::Server::bind(&"".parse().unwrap()).serve(tower::make::Shared::new(app)).await; +//! # app.serve(&"".parse().unwrap()).await.unwrap(); //! # }; //! ``` //! @@ -107,7 +107,7 @@ //! //! let app = route("/users", post(create_user)); //! # async { -//! # hyper::Server::bind(&"".parse().unwrap()).serve(tower::make::Shared::new(app)).await; +//! # app.serve(&"".parse().unwrap()).await.unwrap(); //! # }; //! ``` //! @@ -126,7 +126,7 @@ //! //! let app = route("/users", post(create_user)); //! # async { -//! # hyper::Server::bind(&"".parse().unwrap()).serve(tower::make::Shared::new(app)).await; +//! # app.serve(&"".parse().unwrap()).await.unwrap(); //! # }; //! ``` diff --git a/src/lib.rs b/src/lib.rs index 031f8e5c..0fe2313a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -33,9 +33,8 @@ //! let app = route("/", get(|| async { "Hello, World!" })); //! //! // run it with hyper on localhost:3000 -//! let addr = SocketAddr::from(([127, 0, 0, 1], 3000)); -//! Server::bind(&addr) -//! .serve(Shared::new(app)) +//! app +//! .serve(&"0.0.0.0:3000".parse().unwrap()) //! .await //! .unwrap(); //! } @@ -63,7 +62,7 @@ //! // `GET /foo` called //! } //! # async { -//! # hyper::Server::bind(&"".parse().unwrap()).serve(tower::make::Shared::new(app)).await; +//! # app.serve(&"".parse().unwrap()).await.unwrap(); //! # }; //! ``` //! @@ -142,7 +141,7 @@ //! .route("/result", get(result)) //! .route("/response", get(response)); //! # async { -//! # hyper::Server::bind(&"".parse().unwrap()).serve(tower::make::Shared::new(app)).await; +//! # app.serve(&"".parse().unwrap()).await.unwrap(); //! # }; //! ``` //! @@ -175,7 +174,7 @@ //! // ... //! } //! # async { -//! # hyper::Server::bind(&"".parse().unwrap()).serve(tower::make::Shared::new(app)).await; +//! # app.serve(&"".parse().unwrap()).await.unwrap(); //! # }; //! ``` //! @@ -195,7 +194,7 @@ //! // ... //! } //! # async { -//! # hyper::Server::bind(&"".parse().unwrap()).serve(tower::make::Shared::new(app)).await; +//! # app.serve(&"".parse().unwrap()).await.unwrap(); //! # }; //! ``` //! @@ -233,7 +232,7 @@ //! // ... //! } //! # async { -//! # hyper::Server::bind(&"".parse().unwrap()).serve(tower::make::Shared::new(app)).await; +//! # app.serve(&"".parse().unwrap()).await.unwrap(); //! # }; //! ``` //! @@ -248,7 +247,7 @@ //! // ... //! } //! # async { -//! # hyper::Server::bind(&"".parse().unwrap()).serve(tower::make::Shared::new(app)).await; +//! # app.serve(&"".parse().unwrap()).await.unwrap(); //! # }; //! ``` //! @@ -279,7 +278,7 @@ //! //! async fn handler() {} //! # async { -//! # hyper::Server::bind(&"".parse().unwrap()).serve(tower::make::Shared::new(app)).await; +//! # app.serve(&"".parse().unwrap()).await.unwrap(); //! # }; //! ``` //! @@ -299,7 +298,7 @@ //! //! async fn post_foo() {} //! # async { -//! # hyper::Server::bind(&"".parse().unwrap()).serve(tower::make::Shared::new(app)).await; +//! # app.serve(&"".parse().unwrap()).await.unwrap(); //! # }; //! ``` //! @@ -349,7 +348,7 @@ //! //! async fn handle() {} //! # async { -//! # hyper::Server::bind(&"".parse().unwrap()).serve(tower::make::Shared::new(app)).await; +//! # app.serve(&"".parse().unwrap()).await.unwrap(); //! # }; //! ``` //! @@ -375,7 +374,7 @@ //! //! async fn other_handle() {} //! # async { -//! # hyper::Server::bind(&"".parse().unwrap()).serve(tower::make::Shared::new(app)).await; +//! # app.serve(&"".parse().unwrap()).await.unwrap(); //! # }; //! ``` //! @@ -428,7 +427,7 @@ //! ); //! }); //! # async { -//! # hyper::Server::bind(&"".parse().unwrap()).serve(tower::make::Shared::new(app)).await; +//! # app.serve(&"".parse().unwrap()).await.unwrap(); //! # }; //! ``` //! @@ -459,7 +458,7 @@ //! // ... //! } //! # async { -//! # hyper::Server::bind(&"".parse().unwrap()).serve(tower::make::Shared::new(app)).await; +//! # app.serve(&"".parse().unwrap()).await.unwrap(); //! # }; //! ``` //! @@ -494,7 +493,7 @@ //! ) //! ); //! # async { -//! # hyper::Server::bind(&"".parse().unwrap()).serve(tower::make::Shared::new(app)).await; +//! # app.serve(&"".parse().unwrap()).await.unwrap(); //! # }; //! ``` //! @@ -518,7 +517,7 @@ //! let app = route("/", get(|_: Request| async { /* ... */ })) //! .nest("/api", api_routes()); //! # async { -//! # hyper::Server::bind(&"".parse().unwrap()).serve(tower::make::Shared::new(app)).await; +//! # app.serve(&"".parse().unwrap()).await.unwrap(); //! # }; //! ``` //! @@ -538,14 +537,26 @@ //! }) //! ); //! # async { -//! # hyper::Server::bind(&"".parse().unwrap()).serve(tower::make::Shared::new(app)).await; +//! # app.serve(&"".parse().unwrap()).await.unwrap(); //! # }; //! ``` //! +//! # Features +//! +//! tower-web uses a set of [feature flags] to reduce the amount of compiled and +//! optional dependencies. +//! +//! The following optional features are available: +//! +//! - `ws`: Enables WebSockets support. +//! - `hyper-h1`: Enables hyper's `http1` feature. On by default. +//! - `hyper-h2`: Enables hyper's `http2` feature. +//! //! [tower]: https://crates.io/crates/tower //! [tower-http]: https://crates.io/crates/tower-http //! [tokio]: http://crates.io/crates/tokio //! [hyper]: http://crates.io/crates/hyper +//! [feature flags]: https://doc.rust-lang.org/cargo/reference/features.html#the-features-section #![doc(html_root_url = "https://docs.rs/tower-http/0.1.0")] #![warn( diff --git a/src/routing.rs b/src/routing.rs index 19579e45..82bb9fe7 100644 --- a/src/routing.rs +++ b/src/routing.rs @@ -1,6 +1,7 @@ //! Routing between [`Service`]s. use crate::{body::BoxBody, response::IntoResponse, ResultExt}; +use async_trait::async_trait; use bytes::Bytes; use futures_util::{future, ready}; use http::{Method, Request, Response, StatusCode, Uri}; @@ -80,6 +81,7 @@ pub struct Route { /// Trait for building routers. // TODO(david): this name isn't great +#[async_trait] pub trait RoutingDsl: crate::sealed::Sealed + Sized { /// Add another route to the router. /// @@ -196,7 +198,7 @@ pub trait RoutingDsl: crate::sealed::Sealed + Sized { /// // wont be sent through `ConcurrencyLimit` /// .route("/bar", get(third_handler)); /// # async { - /// # hyper::Server::bind(&"".parse().unwrap()).serve(tower::make::Shared::new(app)).await; + /// # app.serve(&"".parse().unwrap()).await.unwrap(); /// # }; /// ``` /// @@ -228,6 +230,59 @@ pub trait RoutingDsl: crate::sealed::Sealed + Sized { { Layered(layer.layer(self)) } + + /// Convert this router into a [`MakeService`], that is a [`Service`] who's + /// response is another service. + /// + /// This is useful when running your application with hyper's + /// [`Server`](hyper::server::Server): + /// + /// ``` + /// use tower_web::prelude::*; + /// + /// let app = route("/", get(|| async { "Hi!" })); + /// + /// # async { + /// hyper::Server::bind(&"0.0.0.0:3000".parse().unwrap()) + /// .serve(app.into_make_service()) + /// .await + /// .expect("server failed"); + /// # }; + /// ``` + /// + /// [`MakeService`]: tower::make::MakeService + fn into_make_service(self) -> tower::make::Shared + where + Self: Clone, + { + tower::make::Shared::new(self) + } + + /// Serve this router with [hyper] on the given address. + /// + /// Uses [`hyper::server::Server`]'s default configuration. Creating a + /// [`hyper::server::Server`] manually is recommended if different + /// configuration is needed. In that case [`into_make_service`] can be used + /// to easily serve this router. + /// + /// [hyper]: http://crates.io/crates/hyper + /// [`into_make_service`]: RoutingDsl::into_make_service + #[cfg(any(feature = "hyper-h1", feature = "hyper-h2"))] + #[cfg_attr(docsrs, doc(cfg(any(feature = "hyper-h1", feature = "hyper-h2"))))] + async fn serve(self, addr: &std::net::SocketAddr) -> Result<(), hyper::Error> + where + Self: Service, Response = Response, Error = Infallible> + + Clone + + Send + + 'static, + Self::Future: Send, + B: http_body::Body + Send + Sync + 'static, + B::Error: Into + Send + Sync + 'static, + { + hyper::server::Server::bind(addr) + .serve(self.into_make_service()) + .await + } } impl RoutingDsl for Route {} @@ -662,7 +717,7 @@ where /// /// let app = nest("/api", users_api).route("/careers", get(careers)); /// # async { -/// # hyper::Server::bind(&"".parse().unwrap()).serve(tower::make::Shared::new(app)).await; +/// # app.serve(&"".parse().unwrap()).await.unwrap(); /// # }; /// ``` /// @@ -683,7 +738,7 @@ where /// /// let app = nest("/:version/api", users_api); /// # async { -/// # hyper::Server::bind(&"".parse().unwrap()).serve(tower::make::Shared::new(app)).await; +/// # app.serve(&"".parse().unwrap()).await.unwrap(); /// # }; /// ``` /// @@ -702,7 +757,7 @@ where /// /// let app = nest("/public", get(serve_dir_service)); /// # async { -/// # hyper::Server::bind(&"".parse().unwrap()).serve(tower::make::Shared::new(app)).await; +/// # app.serve(&"".parse().unwrap()).await.unwrap(); /// # }; /// ``` /// diff --git a/src/service/mod.rs b/src/service/mod.rs index df393db6..9054a22e 100644 --- a/src/service/mod.rs +++ b/src/service/mod.rs @@ -20,7 +20,7 @@ //! let app = route("/old", service::get(redirect_service)) //! .route("/new", handler::get(handler)); //! # async { -//! # hyper::Server::bind(&"".parse().unwrap()).serve(tower::make::Shared::new(app)).await; +//! # app.serve(&"".parse().unwrap()).await.unwrap(); //! # }; //! ``` //! @@ -527,7 +527,7 @@ pub trait ServiceExt: Service, Response = Response> { /// ); /// # /// # async { - /// # hyper::Server::bind(&"".parse().unwrap()).serve(tower::make::Shared::new(app)).await; + /// # app.serve(&"".parse().unwrap()).await.unwrap(); /// # }; /// ``` fn handle_error(self, f: F) -> HandleError