diff --git a/Cargo.toml b/Cargo.toml
index 2654bf49..e67dd602 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -19,6 +19,7 @@ serde_urlencoded = "0.7"
thiserror = "1.0"
tower = { version = "0.4", features = ["util", "buffer"] }
tower-http = { version = "0.1", features = ["add-extension"] }
+regex = "1.5"
[dev-dependencies]
hyper = { version = "0.14", features = ["full"] }
diff --git a/examples/hello_world.rs b/examples/hello_world.rs
index 848ae949..cec31a14 100644
--- a/examples/hello_world.rs
+++ b/examples/hello_world.rs
@@ -2,20 +2,14 @@ use http::{Request, StatusCode};
use hyper::Server;
use std::net::SocketAddr;
use tower::make::Shared;
-use tower_web::{body::Body, extract, response::Html};
+use tower_web::{body::Body, response, get, route, AddRoute, extract};
#[tokio::main]
async fn main() {
tracing_subscriber::fmt::init();
// build our application with some routes
- let app = tower_web::app()
- .at("/")
- .get(handler)
- .at("/greet/:name")
- .get(greet)
- // convert it into a `Service`
- .into_service();
+ let app = route("/", get(handler)).route("/greet/:name", get(greet));
// run it with hyper
let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
@@ -24,8 +18,8 @@ async fn main() {
server.await.unwrap();
}
-async fn handler(_req: Request
) -> Html<&'static str> {
- Html("Hello, World!
")
+async fn handler(_req: Request) -> response::Html<&'static str> {
+ response::Html("Hello, World!
")
}
async fn greet(_req: Request, params: extract::UrlParamsMap) -> Result {
diff --git a/examples/key_value_store.rs b/examples/key_value_store.rs
index a6d41826..f9052874 100644
--- a/examples/key_value_store.rs
+++ b/examples/key_value_store.rs
@@ -14,7 +14,7 @@ use tower_http::{
use tower_web::{
body::Body,
extract::{BytesMaxLength, Extension, UrlParams},
- handler::Handler,
+ get, route, Handler,
};
#[tokio::main]
@@ -22,12 +22,10 @@ async fn main() {
tracing_subscriber::fmt::init();
// build our application with some routes
- let app = tower_web::app()
- .at("/:key")
- .get(get.layer(CompressionLayer::new()))
- .post(set)
- // convert it into a `Service`
- .into_service();
+ let app = route(
+ "/:key",
+ get(kv_get.layer(CompressionLayer::new())).post(kv_set),
+ );
// add some middleware
let app = ServiceBuilder::new()
@@ -50,7 +48,7 @@ struct State {
db: HashMap,
}
-async fn get(
+async fn kv_get(
_req: Request,
UrlParams((key,)): UrlParams<(String,)>,
Extension(state): Extension,
@@ -64,7 +62,7 @@ async fn get(
}
}
-async fn set(
+async fn kv_set(
_req: Request,
UrlParams((key,)): UrlParams<(String,)>,
BytesMaxLength(value): BytesMaxLength<{ 1024 * 5_000 }>, // ~5mb
diff --git a/examples/lots_of_routes.rs b/examples/lots_of_routes.rs
deleted file mode 100644
index dac28f85..00000000
--- a/examples/lots_of_routes.rs
+++ /dev/null
@@ -1,232 +0,0 @@
-use http::Request;
-use hyper::Server;
-use std::net::SocketAddr;
-use tower::make::Shared;
-use tower_web::body::Body;
-
-#[tokio::main]
-async fn main() {
- // 100 routes should still compile in a reasonable amount of time
- // add a .boxed() every 10 routes to improve compile times
- let app = tower_web::app()
- .at("/")
- .get(handler)
- .at("/")
- .get(handler)
- .at("/")
- .get(handler)
- .at("/")
- .get(handler)
- .at("/")
- .get(handler)
- .at("/")
- .get(handler)
- .at("/")
- .get(handler)
- .at("/")
- .get(handler)
- .at("/")
- .get(handler)
- .at("/")
- .get(handler)
- .boxed()
- .at("/")
- .get(handler)
- .at("/")
- .get(handler)
- .at("/")
- .get(handler)
- .at("/")
- .get(handler)
- .at("/")
- .get(handler)
- .at("/")
- .get(handler)
- .at("/")
- .get(handler)
- .at("/")
- .get(handler)
- .at("/")
- .get(handler)
- .at("/")
- .get(handler)
- .boxed()
- .at("/")
- .get(handler)
- .at("/")
- .get(handler)
- .at("/")
- .get(handler)
- .at("/")
- .get(handler)
- .at("/")
- .get(handler)
- .at("/")
- .get(handler)
- .at("/")
- .get(handler)
- .at("/")
- .get(handler)
- .at("/")
- .get(handler)
- .at("/")
- .get(handler)
- .boxed()
- .at("/")
- .get(handler)
- .at("/")
- .get(handler)
- .at("/")
- .get(handler)
- .at("/")
- .get(handler)
- .at("/")
- .get(handler)
- .at("/")
- .get(handler)
- .at("/")
- .get(handler)
- .at("/")
- .get(handler)
- .at("/")
- .get(handler)
- .at("/")
- .get(handler)
- .boxed()
- .at("/")
- .get(handler)
- .at("/")
- .get(handler)
- .at("/")
- .get(handler)
- .at("/")
- .get(handler)
- .at("/")
- .get(handler)
- .at("/")
- .get(handler)
- .at("/")
- .get(handler)
- .at("/")
- .get(handler)
- .at("/")
- .get(handler)
- .at("/")
- .get(handler)
- .boxed()
- .at("/")
- .get(handler)
- .at("/")
- .get(handler)
- .at("/")
- .get(handler)
- .at("/")
- .get(handler)
- .at("/")
- .get(handler)
- .at("/")
- .get(handler)
- .at("/")
- .get(handler)
- .at("/")
- .get(handler)
- .at("/")
- .get(handler)
- .at("/")
- .get(handler)
- .boxed()
- .at("/")
- .get(handler)
- .at("/")
- .get(handler)
- .at("/")
- .get(handler)
- .at("/")
- .get(handler)
- .at("/")
- .get(handler)
- .at("/")
- .get(handler)
- .at("/")
- .get(handler)
- .at("/")
- .get(handler)
- .at("/")
- .get(handler)
- .at("/")
- .get(handler)
- .boxed()
- .at("/")
- .get(handler)
- .at("/")
- .get(handler)
- .at("/")
- .get(handler)
- .at("/")
- .get(handler)
- .at("/")
- .get(handler)
- .at("/")
- .get(handler)
- .at("/")
- .get(handler)
- .at("/")
- .get(handler)
- .at("/")
- .get(handler)
- .at("/")
- .get(handler)
- .boxed()
- .at("/")
- .get(handler)
- .at("/")
- .get(handler)
- .at("/")
- .get(handler)
- .at("/")
- .get(handler)
- .at("/")
- .get(handler)
- .at("/")
- .get(handler)
- .at("/")
- .get(handler)
- .at("/")
- .get(handler)
- .at("/")
- .get(handler)
- .at("/")
- .get(handler)
- .boxed()
- .at("/")
- .get(handler)
- .at("/")
- .get(handler)
- .at("/")
- .get(handler)
- .at("/")
- .get(handler)
- .at("/")
- .get(handler)
- .at("/")
- .get(handler)
- .at("/")
- .get(handler)
- .at("/")
- .get(handler)
- .at("/")
- .get(handler)
- .at("/")
- .get(handler)
- .boxed()
- .into_service();
-
- 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();
-}
-
-async fn handler(_req: Request) -> &'static str {
- "Hello, World!"
-}
diff --git a/src/handler.rs b/src/handler.rs
index b9ddc3a7..f9f2aaae 100644
--- a/src/handler.rs
+++ b/src/handler.rs
@@ -143,11 +143,11 @@ impl Layered {
}
}
- pub fn handle_error(self, f: F) -> Layered, T>
+ pub fn handle_error(self, f: F) -> Layered, T>
where
S: Service, Response = Response>,
F: FnOnce(S::Error) -> Res,
- Res: IntoResponse,
+ Res: IntoResponse,
B: http_body::Body + Send + Sync + 'static,
B::Error: Into + Send + Sync + 'static,
{
diff --git a/src/lib.rs b/src/lib.rs
index bb634ba7..bd3a577b 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,13 +1,12 @@
-use self::{
- body::Body,
- routing::{AlwaysNotFound, RouteAt},
-};
+use self::body::Body;
use body::BoxBody;
use bytes::Bytes;
use futures_util::ready;
-use http::{Request, Response};
+use handler::HandlerSvc;
+use http::{Method, Request, Response};
use pin_project::pin_project;
use response::IntoResponse;
+use routing::{EmptyRouter, OnMethod, Route};
use std::{
convert::Infallible,
fmt,
@@ -15,7 +14,7 @@ use std::{
pin::Pin,
task::{Context, Poll},
};
-use tower::{BoxError, Service};
+use tower::{util::Oneshot, BoxError, Service, ServiceExt as _};
pub mod body;
pub mod extract;
@@ -23,66 +22,81 @@ pub mod handler;
pub mod response;
pub mod routing;
+#[doc(inline)]
+pub use self::handler::Handler;
+#[doc(inline)]
+pub use self::routing::AddRoute;
+
pub use async_trait::async_trait;
pub use tower_http::add_extension::{AddExtension, AddExtensionLayer};
+#[derive(Debug, Copy, Clone)]
+pub enum MethodFilter {
+ Any,
+ Connect,
+ Delete,
+ Get,
+ Head,
+ Options,
+ Patch,
+ Post,
+ Put,
+ Trace,
+}
+
+impl MethodFilter {
+ #[allow(clippy::match_like_matches_macro)]
+ fn matches(self, method: &Method) -> bool {
+ use MethodFilter::*;
+
+ match (self, method) {
+ (Any, _)
+ | (Connect, &Method::CONNECT)
+ | (Delete, &Method::DELETE)
+ | (Get, &Method::GET)
+ | (Head, &Method::HEAD)
+ | (Options, &Method::OPTIONS)
+ | (Patch, &Method::PATCH)
+ | (Post, &Method::POST)
+ | (Put, &Method::PUT)
+ | (Trace, &Method::TRACE) => true,
+ _ => false,
+ }
+ }
+}
+
+pub fn route(spec: &str, svc: S) -> Route
+where
+ S: Service, Error = Infallible> + Clone,
+{
+ routing::EmptyRouter.route(spec, svc)
+}
+
+pub fn get(handler: H) -> OnMethod, EmptyRouter>
+where
+ H: Handler,
+{
+ on_method(MethodFilter::Get, HandlerSvc::new(handler))
+}
+
+pub fn post(handler: H) -> OnMethod, EmptyRouter>
+where
+ H: Handler,
+{
+ on_method(MethodFilter::Post, HandlerSvc::new(handler))
+}
+
+pub fn on_method(method: MethodFilter, svc: S) -> OnMethod {
+ OnMethod {
+ method,
+ svc,
+ fallback: EmptyRouter,
+ }
+}
+
#[cfg(test)]
mod tests;
-pub fn app() -> App {
- App {
- service_tree: AlwaysNotFound(()),
- }
-}
-
-#[derive(Debug, Clone)]
-pub struct App {
- service_tree: R,
-}
-
-impl App {
- fn new(service_tree: R) -> Self {
- Self { service_tree }
- }
-
- pub fn at(self, route_spec: &str) -> RouteAt {
- self.at_bytes(Bytes::copy_from_slice(route_spec.as_bytes()))
- }
-
- fn at_bytes(self, route_spec: Bytes) -> RouteAt {
- RouteAt {
- app: self,
- route_spec,
- }
- }
-}
-
-#[derive(Clone)]
-pub struct IntoService {
- service_tree: R
-}
-
-impl Service for IntoService
-where
- R: Service, Error = Infallible>,
- B: Default,
-{
- type Response = Response;
- type Error = Infallible;
- type Future = R::Future;
-
- fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> {
- match ready!(self.service_tree.poll_ready(cx)) {
- Ok(_) => Poll::Ready(Ok(())),
- Err(err) => match err {},
- }
- }
-
- fn call(&mut self, req: T) -> Self::Future {
- self.service_tree.call(req)
- }
-}
-
pub(crate) trait ResultExt {
fn unwrap_infallible(self) -> T;
}
@@ -105,11 +119,11 @@ impl ResultExt for Result {
// Fixing that is a breaking change to tower-http so we should wait a bit, but should
// totally fix it at some point.
#[derive(Debug, thiserror::Error)]
-#[error("{0}")]
-pub struct BoxStdError(#[source] pub(crate) tower::BoxError);
+#[error(transparent)]
+pub struct BoxStdError(#[from] pub(crate) tower::BoxError);
pub trait ServiceExt: Service, Response = Response> {
- fn handle_error(self, f: F) -> HandleError
+ fn handle_error(self, f: F) -> HandleError
where
Self: Sized,
F: FnOnce(Self::Error) -> Res,
@@ -123,53 +137,33 @@ pub trait ServiceExt: Service, Response = Response> {
impl ServiceExt for S where S: Service, Response = Response> {}
-pub struct HandleError {
+#[derive(Clone)]
+pub struct HandleError {
inner: S,
f: F,
- poll_ready_error: Option,
}
-impl HandleError {
+impl HandleError {
pub(crate) fn new(inner: S, f: F) -> Self {
- Self {
- inner,
- f,
- poll_ready_error: None,
- }
+ Self { inner, f }
}
}
-impl fmt::Debug for HandleError
+impl fmt::Debug for HandleError
where
S: fmt::Debug,
- E: fmt::Debug,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("HandleError")
.field("inner", &self.inner)
.field("f", &format_args!("{}", std::any::type_name::()))
- .field("poll_ready_error", &self.poll_ready_error)
.finish()
}
}
-impl Clone for HandleError
+impl Service> for HandleError
where
- S: Clone,
- F: Clone,
-{
- fn clone(&self) -> Self {
- Self {
- inner: self.inner.clone(),
- f: self.f.clone(),
- poll_ready_error: None,
- }
- }
-}
-
-impl Service> for HandleError
-where
- S: Service, Response = Response>,
+ S: Service, Response = Response> + Clone,
F: FnOnce(S::Error) -> Res + Clone,
Res: IntoResponse,
B: http_body::Body + Send + Sync + 'static,
@@ -177,47 +171,28 @@ where
{
type Response = Response;
type Error = Infallible;
- type Future = HandleErrorFuture;
+ type Future = HandleErrorFuture>, F>;
- fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> {
- match ready!(self.inner.poll_ready(cx)) {
- Ok(_) => Poll::Ready(Ok(())),
- Err(err) => {
- self.poll_ready_error = Some(err);
- Poll::Ready(Ok(()))
- }
- }
+ fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll> {
+ Poll::Ready(Ok(()))
}
fn call(&mut self, req: Request) -> Self::Future {
- if let Some(err) = self.poll_ready_error.take() {
- return HandleErrorFuture {
- f: Some(self.f.clone()),
- kind: Kind::Error(Some(err)),
- };
- }
-
HandleErrorFuture {
f: Some(self.f.clone()),
- kind: Kind::Future(self.inner.call(req)),
+ inner: self.inner.clone().oneshot(req),
}
}
}
#[pin_project]
-pub struct HandleErrorFuture {
+pub struct HandleErrorFuture {
#[pin]
- kind: Kind,
+ inner: Fut,
f: Option,
}
-#[pin_project(project = KindProj)]
-enum Kind {
- Future(#[pin] Fut),
- Error(Option),
-}
-
-impl Future for HandleErrorFuture
+impl Future for HandleErrorFuture
where
Fut: Future