mirror of
https://github.com/tokio-rs/axum.git
synced 2026-08-27 00:00:24 +02:00
Change routing DSL
This commit is contained in:
+398
-428
@@ -1,369 +1,145 @@
|
||||
use crate::{
|
||||
body::{Body, BoxBody},
|
||||
body::BoxBody,
|
||||
handler::{Handler, HandlerSvc},
|
||||
response::IntoResponse,
|
||||
App, HandleError, IntoService, ResultExt,
|
||||
MethodFilter, ResultExt,
|
||||
};
|
||||
use bytes::Bytes;
|
||||
use futures_util::{future, ready};
|
||||
use http::{Method, Request, Response, StatusCode};
|
||||
use itertools::{EitherOrBoth, Itertools};
|
||||
use http::{Request, Response, StatusCode};
|
||||
use hyper::Body;
|
||||
use itertools::Itertools;
|
||||
use pin_project::pin_project;
|
||||
use regex::Regex;
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
convert::Infallible,
|
||||
fmt,
|
||||
future::Future,
|
||||
pin::Pin,
|
||||
str,
|
||||
sync::Arc,
|
||||
task::{Context, Poll},
|
||||
};
|
||||
use tower::{
|
||||
buffer::{Buffer, BufferLayer},
|
||||
util::BoxService,
|
||||
buffer::Buffer,
|
||||
util::{BoxService, Oneshot, ServiceExt},
|
||||
BoxError, Layer, Service, ServiceBuilder,
|
||||
};
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct AlwaysNotFound(pub(crate) ());
|
||||
// ===== DSL =====
|
||||
|
||||
impl<R> Service<R> for AlwaysNotFound {
|
||||
type Response = Response<Body>;
|
||||
type Error = Infallible;
|
||||
type Future = future::Ready<Result<Self::Response, Self::Error>>;
|
||||
#[derive(Clone)]
|
||||
pub struct Route<S, F> {
|
||||
pub(crate) pattern: PathPattern,
|
||||
pub(crate) svc: S,
|
||||
pub(crate) fallback: F,
|
||||
}
|
||||
|
||||
fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
Poll::Ready(Ok(()))
|
||||
#[derive(Clone)]
|
||||
pub struct OnMethod<S, F> {
|
||||
pub(crate) method: MethodFilter,
|
||||
pub(crate) svc: S,
|
||||
pub(crate) fallback: F,
|
||||
}
|
||||
|
||||
pub trait AddRoute: Sized {
|
||||
fn route<T>(self, spec: &str, svc: T) -> Route<T, Self>
|
||||
where
|
||||
T: Service<Request<Body>, Error = Infallible> + Clone;
|
||||
}
|
||||
|
||||
impl<S, F> Route<S, F> {
|
||||
pub fn boxed<B>(self) -> BoxRoute<B>
|
||||
where
|
||||
Self: Service<Request<Body>, Response = Response<B>, Error = Infallible> + Send + 'static,
|
||||
<Self as Service<Request<Body>>>::Future: Send,
|
||||
B: From<String> + 'static,
|
||||
{
|
||||
ServiceBuilder::new()
|
||||
.layer_fn(BoxRoute)
|
||||
.buffer(1024)
|
||||
.layer(BoxService::layer())
|
||||
.service(self)
|
||||
}
|
||||
|
||||
fn call(&mut self, _req: R) -> Self::Future {
|
||||
let mut res = Response::new(Body::empty());
|
||||
*res.status_mut() = StatusCode::NOT_FOUND;
|
||||
future::ok(res)
|
||||
pub fn layer<L>(self, layer: L) -> Layered<L::Service>
|
||||
where
|
||||
L: Layer<Self>,
|
||||
L::Service: Service<Request<Body>> + Clone,
|
||||
{
|
||||
Layered(layer.layer(self))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct RouteAt<R> {
|
||||
pub(crate) app: App<R>,
|
||||
pub(crate) route_spec: Bytes,
|
||||
impl<S, F> AddRoute for Route<S, F> {
|
||||
fn route<T>(self, spec: &str, svc: T) -> Route<T, Self>
|
||||
where
|
||||
T: Service<Request<Body>, Error = Infallible> + Clone,
|
||||
{
|
||||
Route {
|
||||
pattern: PathPattern::new(spec),
|
||||
svc,
|
||||
fallback: self,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! define_route_at_methods {
|
||||
(
|
||||
RouteAt:
|
||||
$name:ident,
|
||||
$svc_method_name:ident,
|
||||
$method:ident
|
||||
) => {
|
||||
pub fn $name<F, B, T>(self, handler_fn: F) -> RouteBuilder<Or<HandlerSvc<F, B, T>, R>>
|
||||
where
|
||||
F: Handler<B, T>,
|
||||
{
|
||||
self.add_route(handler_fn, Method::$method)
|
||||
}
|
||||
|
||||
pub fn $svc_method_name<S, B>(self, service: S) -> RouteBuilder<Or<S, R>>
|
||||
where
|
||||
S: Service<Request<Body>, Response = Response<B>, Error = Infallible> + Clone,
|
||||
{
|
||||
self.add_route_service(service, Method::$method)
|
||||
}
|
||||
};
|
||||
|
||||
(
|
||||
RouteBuilder:
|
||||
$name:ident,
|
||||
$svc_method_name:ident,
|
||||
$method:ident
|
||||
) => {
|
||||
pub fn $name<F, B, T>(self, handler_fn: F) -> RouteBuilder<Or<HandlerSvc<F, B, T>, R>>
|
||||
where
|
||||
F: Handler<B, T>,
|
||||
{
|
||||
self.app.at_bytes(self.route_spec).$name(handler_fn)
|
||||
}
|
||||
|
||||
pub fn $svc_method_name<S, B>(self, service: S) -> RouteBuilder<Or<S, R>>
|
||||
where
|
||||
S: Service<Request<Body>, Response = Response<B>, Error = Infallible> + Clone,
|
||||
{
|
||||
self.app.at_bytes(self.route_spec).$svc_method_name(service)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl<R> RouteAt<R> {
|
||||
define_route_at_methods!(RouteAt: get, get_service, GET);
|
||||
define_route_at_methods!(RouteAt: post, post_service, POST);
|
||||
define_route_at_methods!(RouteAt: put, put_service, PUT);
|
||||
define_route_at_methods!(RouteAt: patch, patch_service, PATCH);
|
||||
define_route_at_methods!(RouteAt: delete, delete_service, DELETE);
|
||||
define_route_at_methods!(RouteAt: head, head_service, HEAD);
|
||||
define_route_at_methods!(RouteAt: options, options_service, OPTIONS);
|
||||
define_route_at_methods!(RouteAt: connect, connect_service, CONNECT);
|
||||
define_route_at_methods!(RouteAt: trace, trace_service, TRACE);
|
||||
|
||||
fn add_route<H, B, T>(
|
||||
self,
|
||||
handler: H,
|
||||
method: Method,
|
||||
) -> RouteBuilder<Or<HandlerSvc<H, B, T>, R>>
|
||||
impl<S, F> OnMethod<S, F> {
|
||||
pub fn get<H, B, T>(self, handler: H) -> OnMethod<HandlerSvc<H, B, T>, Self>
|
||||
where
|
||||
H: Handler<B, T>,
|
||||
{
|
||||
self.add_route_service(HandlerSvc::new(handler), method)
|
||||
self.with_method(MethodFilter::Get, HandlerSvc::new(handler))
|
||||
}
|
||||
|
||||
fn add_route_service<S>(self, service: S, method: Method) -> RouteBuilder<Or<S, R>> {
|
||||
let route_spec = self.route_spec.clone();
|
||||
self.add_route_service_with_spec(service, RouteSpec::new(method, route_spec))
|
||||
}
|
||||
|
||||
fn add_route_service_with_spec<S>(
|
||||
self,
|
||||
service: S,
|
||||
route_spec: RouteSpec,
|
||||
) -> RouteBuilder<Or<S, R>> {
|
||||
assert!(
|
||||
self.route_spec.starts_with(b"/"),
|
||||
"route spec must start with a slash (`/`)"
|
||||
);
|
||||
|
||||
let new_app = App {
|
||||
service_tree: Or {
|
||||
service,
|
||||
route_spec,
|
||||
fallback: self.app.service_tree,
|
||||
handler_ready: false,
|
||||
fallback_ready: false,
|
||||
},
|
||||
};
|
||||
|
||||
RouteBuilder {
|
||||
app: new_app,
|
||||
route_spec: self.route_spec,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct RouteBuilder<R> {
|
||||
app: App<R>,
|
||||
route_spec: Bytes,
|
||||
}
|
||||
|
||||
impl<R> Clone for RouteBuilder<R>
|
||||
where
|
||||
R: Clone,
|
||||
{
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
app: self.app.clone(),
|
||||
route_spec: self.route_spec.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<R> RouteBuilder<R> {
|
||||
fn new(app: App<R>, route_spec: impl Into<Bytes>) -> Self {
|
||||
Self {
|
||||
app,
|
||||
route_spec: route_spec.into(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn at(self, route_spec: &str) -> RouteAt<R> {
|
||||
self.app.at(route_spec)
|
||||
}
|
||||
|
||||
define_route_at_methods!(RouteBuilder: get, get_service, GET);
|
||||
define_route_at_methods!(RouteBuilder: post, post_service, POST);
|
||||
define_route_at_methods!(RouteBuilder: put, put_service, PUT);
|
||||
define_route_at_methods!(RouteBuilder: patch, patch_service, PATCH);
|
||||
define_route_at_methods!(RouteBuilder: delete, delete_service, DELETE);
|
||||
define_route_at_methods!(RouteBuilder: head, head_service, HEAD);
|
||||
define_route_at_methods!(RouteBuilder: options, options_service, OPTIONS);
|
||||
define_route_at_methods!(RouteBuilder: connect, connect_service, CONNECT);
|
||||
define_route_at_methods!(RouteBuilder: trace, trace_service, TRACE);
|
||||
|
||||
pub fn into_service(self) -> IntoService<R> {
|
||||
IntoService {
|
||||
service_tree: self.app.service_tree,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn layer<L>(self, layer: L) -> RouteBuilder<L::Service>
|
||||
pub fn post<H, B, T>(self, handler: H) -> OnMethod<HandlerSvc<H, B, T>, Self>
|
||||
where
|
||||
L: Layer<R>,
|
||||
H: Handler<B, T>,
|
||||
{
|
||||
let layered = layer.layer(self.app.service_tree);
|
||||
let app = App::new(layered);
|
||||
RouteBuilder::new(app, self.route_spec)
|
||||
self.with_method(MethodFilter::Post, HandlerSvc::new(handler))
|
||||
}
|
||||
|
||||
pub fn handle_error<F, B, Res>(self, f: F) -> RouteBuilder<HandleError<R, F, R::Error>>
|
||||
where
|
||||
R: Service<Request<Body>, Response = Response<B>>,
|
||||
F: FnOnce(R::Error) -> Res,
|
||||
Res: IntoResponse<Body>,
|
||||
B: http_body::Body<Data = Bytes> + Send + Sync + 'static,
|
||||
B::Error: Into<BoxError> + Send + Sync + 'static,
|
||||
{
|
||||
let svc = HandleError::new(self.app.service_tree, f);
|
||||
let app = App::new(svc);
|
||||
RouteBuilder::new(app, self.route_spec)
|
||||
}
|
||||
|
||||
pub fn boxed<B>(self) -> RouteBuilder<BoxServiceTree<B>>
|
||||
where
|
||||
R: Service<Request<Body>, Response = Response<B>, Error = Infallible> + Send + 'static,
|
||||
R::Future: Send,
|
||||
B: From<String> + 'static,
|
||||
{
|
||||
let svc = ServiceBuilder::new()
|
||||
.layer(BufferLayer::new(1024))
|
||||
.layer(BoxService::layer())
|
||||
.service(self.app.service_tree);
|
||||
|
||||
let app = App::new(BoxServiceTree {
|
||||
inner: svc,
|
||||
poll_ready_error: None,
|
||||
});
|
||||
RouteBuilder::new(app, self.route_spec)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Or<H, F> {
|
||||
service: H,
|
||||
route_spec: RouteSpec,
|
||||
fallback: F,
|
||||
handler_ready: bool,
|
||||
fallback_ready: bool,
|
||||
}
|
||||
|
||||
impl<H, F> Clone for Or<H, F>
|
||||
where
|
||||
H: Clone,
|
||||
F: Clone,
|
||||
{
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
service: self.service.clone(),
|
||||
fallback: self.fallback.clone(),
|
||||
route_spec: self.route_spec.clone(),
|
||||
// important to reset readiness when cloning
|
||||
handler_ready: false,
|
||||
fallback_ready: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct RouteSpec {
|
||||
method: Method,
|
||||
spec: Bytes,
|
||||
}
|
||||
|
||||
impl RouteSpec {
|
||||
fn new(method: Method, spec: impl Into<Bytes>) -> Self {
|
||||
Self {
|
||||
pub fn with_method<T>(self, method: MethodFilter, svc: T) -> OnMethod<T, Self> {
|
||||
OnMethod {
|
||||
method,
|
||||
spec: spec.into(),
|
||||
svc,
|
||||
fallback: self,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl RouteSpec {
|
||||
fn matches<B>(&self, req: &Request<B>) -> Option<Vec<(String, String)>> {
|
||||
// TODO(david): perform this matching outside
|
||||
if req.method() != self.method {
|
||||
return None;
|
||||
}
|
||||
// ===== Routing service impls =====
|
||||
|
||||
let spec_parts = self.spec.split(|b| *b == b'/');
|
||||
|
||||
let path = req.uri().path().as_bytes();
|
||||
let path_parts = path.split(|b| *b == b'/');
|
||||
|
||||
let mut params = Vec::new();
|
||||
|
||||
for pair in spec_parts.zip_longest(path_parts) {
|
||||
match pair {
|
||||
EitherOrBoth::Both(spec, path) => {
|
||||
if let Some(key) = spec.strip_prefix(b":") {
|
||||
let key = str::from_utf8(key).unwrap().to_string();
|
||||
if let Ok(value) = std::str::from_utf8(path) {
|
||||
params.push((key, value.to_string()));
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
} else if spec != path {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
EitherOrBoth::Left(_) | EitherOrBoth::Right(_) => {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some(params)
|
||||
}
|
||||
}
|
||||
|
||||
impl<H, F, HB, FB> Service<Request<Body>> for Or<H, F>
|
||||
impl<S, F, SB, FB> Service<Request<Body>> for Route<S, F>
|
||||
where
|
||||
H: Service<Request<Body>, Response = Response<HB>, Error = Infallible>,
|
||||
HB: http_body::Body<Data = Bytes> + Send + Sync + 'static,
|
||||
HB::Error: Into<BoxError>,
|
||||
S: Service<Request<Body>, Response = Response<SB>, Error = Infallible> + Clone,
|
||||
SB: http_body::Body<Data = Bytes> + Send + Sync + 'static,
|
||||
SB::Error: Into<BoxError>,
|
||||
|
||||
F: Service<Request<Body>, Response = Response<FB>, Error = Infallible>,
|
||||
F: Service<Request<Body>, Response = Response<FB>, Error = Infallible> + Clone,
|
||||
FB: http_body::Body<Data = Bytes> + Send + Sync + 'static,
|
||||
FB::Error: Into<BoxError>,
|
||||
{
|
||||
type Response = Response<BoxBody>;
|
||||
type Error = Infallible;
|
||||
type Future = future::Either<BoxResponseBody<H::Future>, BoxResponseBody<F::Future>>;
|
||||
|
||||
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
loop {
|
||||
if !self.handler_ready {
|
||||
ready!(self.service.poll_ready(cx)).unwrap_infallible();
|
||||
self.handler_ready = true;
|
||||
}
|
||||
#[allow(clippy::type_complexity)]
|
||||
type Future = future::Either<
|
||||
BoxResponseBody<Oneshot<S, Request<Body>>>,
|
||||
BoxResponseBody<Oneshot<F, Request<Body>>>,
|
||||
>;
|
||||
|
||||
if !self.fallback_ready {
|
||||
ready!(self.fallback.poll_ready(cx)).unwrap_infallible();
|
||||
self.fallback_ready = true;
|
||||
}
|
||||
|
||||
if self.handler_ready && self.fallback_ready {
|
||||
return Poll::Ready(Ok(()));
|
||||
}
|
||||
}
|
||||
fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
Poll::Ready(Ok(()))
|
||||
}
|
||||
|
||||
fn call(&mut self, mut req: Request<Body>) -> Self::Future {
|
||||
if let Some(params) = self.route_spec.matches(&req) {
|
||||
assert!(
|
||||
self.handler_ready,
|
||||
"handler not ready. Did you forget to call `poll_ready`?"
|
||||
);
|
||||
|
||||
self.handler_ready = false;
|
||||
|
||||
insert_url_params(&mut req, params);
|
||||
|
||||
future::Either::Left(BoxResponseBody(self.service.call(req)))
|
||||
if let Some(captures) = self.pattern.matches(req.uri().path()) {
|
||||
insert_url_params(&mut req, captures);
|
||||
let response_future = self.svc.clone().oneshot(req);
|
||||
future::Either::Left(BoxResponseBody(response_future))
|
||||
} else {
|
||||
assert!(
|
||||
self.fallback_ready,
|
||||
"fallback not ready. Did you forget to call `poll_ready`?"
|
||||
);
|
||||
|
||||
self.fallback_ready = false;
|
||||
|
||||
// TODO(david): this leads to each route creating one box body, probably not great
|
||||
future::Either::Right(BoxResponseBody(self.fallback.call(req)))
|
||||
let response_future = self.fallback.clone().oneshot(req);
|
||||
future::Either::Right(BoxResponseBody(response_future))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -371,6 +147,50 @@ where
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct UrlParams(pub(crate) Vec<(String, String)>);
|
||||
|
||||
fn insert_url_params<B>(req: &mut Request<B>, params: Vec<(String, String)>) {
|
||||
if let Some(current) = req.extensions_mut().get_mut::<Option<UrlParams>>() {
|
||||
let mut current = current.take().unwrap();
|
||||
current.0.extend(params);
|
||||
req.extensions_mut().insert(Some(current));
|
||||
} else {
|
||||
req.extensions_mut().insert(Some(UrlParams(params)));
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, F, SB, FB> Service<Request<Body>> for OnMethod<S, F>
|
||||
where
|
||||
S: Service<Request<Body>, Response = Response<SB>, Error = Infallible> + Clone,
|
||||
SB: http_body::Body<Data = Bytes> + Send + Sync + 'static,
|
||||
SB::Error: Into<BoxError>,
|
||||
|
||||
F: Service<Request<Body>, Response = Response<FB>, Error = Infallible> + Clone,
|
||||
FB: http_body::Body<Data = Bytes> + Send + Sync + 'static,
|
||||
FB::Error: Into<BoxError>,
|
||||
{
|
||||
type Response = Response<BoxBody>;
|
||||
type Error = Infallible;
|
||||
|
||||
#[allow(clippy::type_complexity)]
|
||||
type Future = future::Either<
|
||||
BoxResponseBody<Oneshot<S, Request<Body>>>,
|
||||
BoxResponseBody<Oneshot<F, Request<Body>>>,
|
||||
>;
|
||||
|
||||
fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
Poll::Ready(Ok(()))
|
||||
}
|
||||
|
||||
fn call(&mut self, req: Request<Body>) -> Self::Future {
|
||||
if self.method.matches(req.method()) {
|
||||
let response_future = self.svc.clone().oneshot(req);
|
||||
future::Either::Left(BoxResponseBody(response_future))
|
||||
} else {
|
||||
let response_future = self.fallback.clone().oneshot(req);
|
||||
future::Either::Right(BoxResponseBody(response_future))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[pin_project]
|
||||
pub struct BoxResponseBody<F>(#[pin] F);
|
||||
|
||||
@@ -392,91 +212,156 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
pub struct BoxServiceTree<B> {
|
||||
inner: Buffer<BoxService<Request<Body>, Response<B>, Infallible>, Request<Body>>,
|
||||
poll_ready_error: Option<BoxError>,
|
||||
}
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct EmptyRouter;
|
||||
|
||||
impl<B> Clone for BoxServiceTree<B> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
inner: self.inner.clone(),
|
||||
poll_ready_error: None,
|
||||
impl AddRoute for EmptyRouter {
|
||||
fn route<S>(self, spec: &str, svc: S) -> Route<S, Self>
|
||||
where
|
||||
S: Service<Request<Body>, Error = Infallible> + Clone,
|
||||
{
|
||||
Route {
|
||||
pattern: PathPattern::new(spec),
|
||||
svc,
|
||||
fallback: self,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<B> fmt::Debug for BoxServiceTree<B> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("BoxServiceTree").finish()
|
||||
impl<R> Service<R> for EmptyRouter {
|
||||
type Response = Response<Body>;
|
||||
type Error = Infallible;
|
||||
type Future = future::Ready<Result<Self::Response, Self::Error>>;
|
||||
|
||||
fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
Poll::Ready(Ok(()))
|
||||
}
|
||||
|
||||
fn call(&mut self, _req: R) -> Self::Future {
|
||||
let mut res = Response::new(Body::empty());
|
||||
*res.status_mut() = StatusCode::NOT_FOUND;
|
||||
future::ok(res)
|
||||
}
|
||||
}
|
||||
|
||||
impl<B> Service<Request<Body>> for BoxServiceTree<B>
|
||||
// ===== PathPattern =====
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct PathPattern(Arc<Inner>);
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Inner {
|
||||
full_path_regex: Regex,
|
||||
capture_group_names: Box<[Bytes]>,
|
||||
}
|
||||
|
||||
impl PathPattern {
|
||||
pub(crate) fn new(pattern: &str) -> Self {
|
||||
let mut capture_group_names = Vec::new();
|
||||
|
||||
let pattern = pattern
|
||||
.split('/')
|
||||
.map(|part| {
|
||||
if let Some(key) = part.strip_prefix(':') {
|
||||
capture_group_names.push(Bytes::copy_from_slice(key.as_bytes()));
|
||||
|
||||
Cow::Owned(format!("(?P<{}>[^/]*)", key))
|
||||
} else {
|
||||
Cow::Borrowed(part)
|
||||
}
|
||||
})
|
||||
.join("/");
|
||||
|
||||
let full_path_regex =
|
||||
Regex::new(&format!("^{}$", pattern)).expect("invalid regex generated from route");
|
||||
|
||||
Self(Arc::new(Inner {
|
||||
full_path_regex,
|
||||
capture_group_names: capture_group_names.into(),
|
||||
}))
|
||||
}
|
||||
|
||||
pub(crate) fn matches(&self, path: &str) -> Option<Captures> {
|
||||
self.0.full_path_regex.captures(path).map(|captures| {
|
||||
let captures = self
|
||||
.0
|
||||
.capture_group_names
|
||||
.iter()
|
||||
.map(|bytes| {
|
||||
std::str::from_utf8(bytes)
|
||||
.expect("bytes were created from str so is valid utf-8")
|
||||
})
|
||||
.filter_map(|name| captures.name(name).map(|value| (name, value.as_str())))
|
||||
.map(|(key, value)| (key.to_string(), value.to_string()))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
captures
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type Captures = Vec<(String, String)>;
|
||||
|
||||
// ===== BoxRoute =====
|
||||
|
||||
pub struct BoxRoute<B>(Buffer<BoxService<Request<Body>, Response<B>, Infallible>, Request<Body>>);
|
||||
|
||||
impl<B> Clone for BoxRoute<B> {
|
||||
fn clone(&self) -> Self {
|
||||
Self(self.0.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl<B> AddRoute for BoxRoute<B> {
|
||||
fn route<S>(self, spec: &str, svc: S) -> Route<S, Self>
|
||||
where
|
||||
S: Service<Request<Body>, Error = Infallible> + Clone,
|
||||
{
|
||||
Route {
|
||||
pattern: PathPattern::new(spec),
|
||||
svc,
|
||||
fallback: self,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<B> Service<Request<Body>> for BoxRoute<B>
|
||||
where
|
||||
B: From<String> + 'static,
|
||||
{
|
||||
type Response = Response<B>;
|
||||
type Error = Infallible;
|
||||
type Future = BoxServiceTreeResponseFuture<B>;
|
||||
type Future = BoxRouteResponseFuture<B>;
|
||||
|
||||
#[inline]
|
||||
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
// TODO(david): downcast this into one of the cases in `tower::buffer::error`
|
||||
// and convert the error into a response. `ServiceError` should never be able to happen
|
||||
// since all inner services use `Infallible` as the error type.
|
||||
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<Result<(), Self::Error>> {
|
||||
Poll::Ready(Ok(()))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn call(&mut self, req: Request<Body>) -> Self::Future {
|
||||
if let Some(err) = self.poll_ready_error.take() {
|
||||
return BoxServiceTreeResponseFuture {
|
||||
kind: Kind::Response(Some(handle_buffer_error(err))),
|
||||
};
|
||||
}
|
||||
|
||||
BoxServiceTreeResponseFuture {
|
||||
kind: Kind::Future(self.inner.call(req)),
|
||||
}
|
||||
BoxRouteResponseFuture(self.0.clone().oneshot(req))
|
||||
}
|
||||
}
|
||||
|
||||
#[pin_project]
|
||||
pub struct BoxServiceTreeResponseFuture<B> {
|
||||
#[pin]
|
||||
kind: Kind<B>,
|
||||
}
|
||||
pub struct BoxRouteResponseFuture<B>(#[pin] InnerFuture<B>);
|
||||
|
||||
#[pin_project(project = KindProj)]
|
||||
enum Kind<B> {
|
||||
Response(Option<Response<B>>),
|
||||
Future(#[pin] InnerFuture<B>),
|
||||
}
|
||||
|
||||
type InnerFuture<B> = tower::buffer::future::ResponseFuture<
|
||||
Pin<Box<dyn Future<Output = Result<Response<B>, Infallible>> + Send + 'static>>,
|
||||
type InnerFuture<B> = Oneshot<
|
||||
Buffer<BoxService<Request<Body>, Response<B>, Infallible>, Request<Body>>,
|
||||
Request<Body>,
|
||||
>;
|
||||
|
||||
impl<B> Future for BoxServiceTreeResponseFuture<B>
|
||||
impl<B> Future for BoxRouteResponseFuture<B>
|
||||
where
|
||||
B: From<String>,
|
||||
{
|
||||
type Output = Result<Response<B>, Infallible>;
|
||||
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
match self.project().kind.project() {
|
||||
KindProj::Response(res) => Poll::Ready(Ok(res.take().unwrap())),
|
||||
KindProj::Future(future) => match ready!(future.poll(cx)) {
|
||||
Ok(res) => Poll::Ready(Ok(res)),
|
||||
Err(err) => Poll::Ready(Ok(handle_buffer_error(err))),
|
||||
},
|
||||
match ready!(self.project().0.poll(cx)) {
|
||||
Ok(res) => Poll::Ready(Ok(res)),
|
||||
Err(err) => Poll::Ready(Ok(handle_buffer_error(err))),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -516,85 +401,170 @@ where
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn insert_url_params<B>(req: &mut Request<B>, params: Vec<(String, String)>) {
|
||||
if let Some(current) = req.extensions_mut().get_mut::<Option<UrlParams>>() {
|
||||
let mut current = current.take().unwrap();
|
||||
current.0.extend(params);
|
||||
req.extensions_mut().insert(Some(current));
|
||||
} else {
|
||||
req.extensions_mut().insert(Some(UrlParams(params)));
|
||||
// ===== Layered =====
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Layered<S>(S);
|
||||
|
||||
impl<S> AddRoute for Layered<S> {
|
||||
fn route<T>(self, spec: &str, svc: T) -> Route<T, Self>
|
||||
where
|
||||
T: Service<Request<Body>, Error = Infallible> + Clone,
|
||||
{
|
||||
Route {
|
||||
pattern: PathPattern::new(spec),
|
||||
svc,
|
||||
fallback: self,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> Layered<S> {
|
||||
pub fn handle_error<F, B, Res>(self, f: F) -> HandleError<Self, F>
|
||||
where
|
||||
S: Service<Request<Body>, Response = Response<B>> + Clone,
|
||||
F: FnOnce(S::Error) -> Res,
|
||||
Res: IntoResponse<Body>,
|
||||
B: http_body::Body<Data = Bytes> + Send + Sync + 'static,
|
||||
B::Error: Into<BoxError> + Send + Sync + 'static,
|
||||
{
|
||||
HandleError { inner: self, f }
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, B> Service<Request<Body>> for Layered<S>
|
||||
where
|
||||
S: Service<Request<Body>, Response = Response<B>>,
|
||||
{
|
||||
type Response = S::Response;
|
||||
type Error = S::Error;
|
||||
type Future = S::Future;
|
||||
|
||||
#[inline]
|
||||
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
self.0.poll_ready(cx)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn call(&mut self, req: Request<Body>) -> Self::Future {
|
||||
self.0.call(req)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct HandleError<S, F> {
|
||||
inner: S,
|
||||
f: F,
|
||||
}
|
||||
|
||||
impl<S, F> AddRoute for HandleError<S, F> {
|
||||
fn route<T>(self, spec: &str, svc: T) -> Route<T, Self>
|
||||
where
|
||||
T: Service<Request<Body>, Error = Infallible> + Clone,
|
||||
{
|
||||
Route {
|
||||
pattern: PathPattern::new(spec),
|
||||
svc,
|
||||
fallback: self,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, F, B, Res> Service<Request<Body>> for HandleError<S, F>
|
||||
where
|
||||
S: Service<Request<Body>, Response = Response<B>> + Clone,
|
||||
F: FnOnce(S::Error) -> Res + Clone,
|
||||
Res: IntoResponse<Body>,
|
||||
B: http_body::Body<Data = Bytes> + Send + Sync + 'static,
|
||||
B::Error: Into<BoxError> + Send + Sync + 'static,
|
||||
{
|
||||
type Response = Response<BoxBody>;
|
||||
type Error = Infallible;
|
||||
type Future = HandleErrorFuture<Oneshot<S, Request<Body>>, F>;
|
||||
|
||||
fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
Poll::Ready(Ok(()))
|
||||
}
|
||||
|
||||
fn call(&mut self, req: Request<Body>) -> Self::Future {
|
||||
HandleErrorFuture {
|
||||
inner: self.inner.clone().oneshot(req),
|
||||
f: Some(self.f.clone()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[pin_project]
|
||||
pub struct HandleErrorFuture<Fut, F> {
|
||||
#[pin]
|
||||
inner: Fut,
|
||||
f: Option<F>,
|
||||
}
|
||||
|
||||
impl<Fut, F, B, E, Res> Future for HandleErrorFuture<Fut, F>
|
||||
where
|
||||
Fut: Future<Output = Result<Response<B>, E>>,
|
||||
F: FnOnce(E) -> Res,
|
||||
Res: IntoResponse<Body>,
|
||||
B: http_body::Body<Data = Bytes> + Send + Sync + 'static,
|
||||
B::Error: Into<BoxError> + Send + Sync + 'static,
|
||||
{
|
||||
type Output = Result<Response<BoxBody>, Infallible>;
|
||||
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
let this = self.project();
|
||||
match ready!(this.inner.poll(cx)) {
|
||||
Ok(res) => Ok(res.map(BoxBody::new)).into(),
|
||||
Err(err) => {
|
||||
let f = this.f.take().unwrap();
|
||||
let res = f(err).into_response();
|
||||
Ok(res.map(BoxBody::new)).into()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[allow(unused_imports)]
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_routing() {
|
||||
assert_match((Method::GET, "/"), (Method::GET, "/"));
|
||||
refute_match((Method::GET, "/"), (Method::POST, "/"));
|
||||
refute_match((Method::POST, "/"), (Method::GET, "/"));
|
||||
assert_match("/", "/");
|
||||
|
||||
assert_match((Method::GET, "/foo"), (Method::GET, "/foo"));
|
||||
assert_match((Method::GET, "/foo/"), (Method::GET, "/foo/"));
|
||||
refute_match((Method::GET, "/foo"), (Method::GET, "/foo/"));
|
||||
refute_match((Method::GET, "/foo/"), (Method::GET, "/foo"));
|
||||
assert_match("/foo", "/foo");
|
||||
assert_match("/foo/", "/foo/");
|
||||
refute_match("/foo", "/foo/");
|
||||
refute_match("/foo/", "/foo");
|
||||
|
||||
assert_match((Method::GET, "/foo/bar"), (Method::GET, "/foo/bar"));
|
||||
refute_match((Method::GET, "/foo/bar/"), (Method::GET, "/foo/bar"));
|
||||
refute_match((Method::GET, "/foo/bar"), (Method::GET, "/foo/bar/"));
|
||||
assert_match("/foo/bar", "/foo/bar");
|
||||
refute_match("/foo/bar/", "/foo/bar");
|
||||
refute_match("/foo/bar", "/foo/bar/");
|
||||
|
||||
assert_match((Method::GET, "/:value"), (Method::GET, "/foo"));
|
||||
assert_match((Method::GET, "/users/:id"), (Method::GET, "/users/1"));
|
||||
assert_match(
|
||||
(Method::GET, "/users/:id/action"),
|
||||
(Method::GET, "/users/42/action"),
|
||||
);
|
||||
refute_match(
|
||||
(Method::GET, "/users/:id/action"),
|
||||
(Method::GET, "/users/42"),
|
||||
);
|
||||
refute_match(
|
||||
(Method::GET, "/users/:id"),
|
||||
(Method::GET, "/users/42/action"),
|
||||
assert_match("/:value", "/foo");
|
||||
assert_match("/users/:id", "/users/1");
|
||||
assert_match("/users/:id/action", "/users/42/action");
|
||||
refute_match("/users/:id/action", "/users/42");
|
||||
refute_match("/users/:id", "/users/42/action");
|
||||
}
|
||||
|
||||
fn assert_match(route_spec: &'static str, path: &'static str) {
|
||||
let route = PathPattern::new(route_spec);
|
||||
assert!(
|
||||
route.matches(&path).is_some(),
|
||||
"`{}` doesn't match `{}`",
|
||||
path,
|
||||
route_spec
|
||||
);
|
||||
}
|
||||
|
||||
fn assert_match(route_spec: (Method, &'static str), req_spec: (Method, &'static str)) {
|
||||
let route = RouteSpec::new(route_spec.0.clone(), route_spec.1);
|
||||
let req = Request::builder()
|
||||
.method(req_spec.0.clone())
|
||||
.uri(req_spec.1)
|
||||
.body(())
|
||||
.unwrap();
|
||||
|
||||
fn refute_match(route_spec: &'static str, path: &'static str) {
|
||||
let route = PathPattern::new(route_spec);
|
||||
assert!(
|
||||
route.matches(&req).is_some(),
|
||||
"`{} {}` doesn't match `{:?} {}`",
|
||||
req.method(),
|
||||
req.uri().path(),
|
||||
route.method,
|
||||
str::from_utf8(&route.spec).unwrap(),
|
||||
);
|
||||
}
|
||||
|
||||
fn refute_match(route_spec: (Method, &'static str), req_spec: (Method, &'static str)) {
|
||||
let route = RouteSpec::new(route_spec.0.clone(), route_spec.1);
|
||||
let req = Request::builder()
|
||||
.method(req_spec.0.clone())
|
||||
.uri(req_spec.1)
|
||||
.body(())
|
||||
.unwrap();
|
||||
|
||||
assert!(
|
||||
route.matches(&req).is_none(),
|
||||
"`{} {}` shouldn't match `{:?} {}`",
|
||||
req.method(),
|
||||
req.uri().path(),
|
||||
route.method,
|
||||
str::from_utf8(&route.spec).unwrap(),
|
||||
route.matches(&path).is_none(),
|
||||
"`{}` did match `{}` (but shouldn't)",
|
||||
path,
|
||||
route_spec
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user