mirror of
https://github.com/tokio-rs/axum.git
synced 2026-08-18 00:00:15 +02:00
More work
This commit is contained in:
+1
-1
@@ -26,7 +26,7 @@ impl<D, E> BoxBody<D, E> {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(david): upstream this to http-body?
|
||||
// TODO: upstream this to http-body?
|
||||
impl<D, E> Default for BoxBody<D, E>
|
||||
where
|
||||
D: bytes::Buf + 'static,
|
||||
|
||||
@@ -49,6 +49,16 @@ pub enum Error {
|
||||
InvalidUtf8,
|
||||
}
|
||||
|
||||
impl Error {
|
||||
/// Create an `Error` from a `BoxError` coming from a `Service`
|
||||
pub(crate) fn from_service_error(error: BoxError) -> Error {
|
||||
match error.downcast::<Error>() {
|
||||
Ok(err) => *err,
|
||||
Err(err) => Error::Service(err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Infallible> for Error {
|
||||
fn from(err: Infallible) -> Self {
|
||||
match err {}
|
||||
|
||||
+6
-6
@@ -1,6 +1,6 @@
|
||||
use self::{
|
||||
body::Body,
|
||||
routing::{EmptyRouter, RouteAt},
|
||||
routing::{AlwaysNotFound, RouteAt},
|
||||
};
|
||||
use bytes::Bytes;
|
||||
use futures_util::ready;
|
||||
@@ -26,15 +26,15 @@ mod tests;
|
||||
|
||||
pub use self::error::Error;
|
||||
|
||||
pub fn app() -> App<EmptyRouter> {
|
||||
pub fn app() -> App<AlwaysNotFound> {
|
||||
App {
|
||||
router: EmptyRouter(()),
|
||||
service_tree: AlwaysNotFound(()),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct App<R> {
|
||||
router: R,
|
||||
service_tree: R,
|
||||
}
|
||||
|
||||
impl<R> App<R> {
|
||||
@@ -79,7 +79,7 @@ where
|
||||
|
||||
#[inline]
|
||||
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
if let Err(err) = ready!(self.app.router.poll_ready(cx)).map_err(Into::into) {
|
||||
if let Err(err) = ready!(self.app.service_tree.poll_ready(cx)).map_err(Into::into) {
|
||||
self.poll_ready_error = Some(err);
|
||||
}
|
||||
|
||||
@@ -97,7 +97,7 @@ where
|
||||
}
|
||||
}
|
||||
}
|
||||
HandleErrorFuture(Kind::Future(self.app.router.call(req)))
|
||||
HandleErrorFuture(Kind::Future(self.app.service_tree.call(req)))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+104
-18
@@ -10,16 +10,21 @@ use http::{Method, Request, Response, StatusCode};
|
||||
use pin_project::pin_project;
|
||||
use std::{
|
||||
convert::Infallible,
|
||||
fmt,
|
||||
future::Future,
|
||||
pin::Pin,
|
||||
task::{Context, Poll},
|
||||
};
|
||||
use tower::{BoxError, Service};
|
||||
use tower::{
|
||||
buffer::{Buffer, BufferLayer},
|
||||
util::BoxService,
|
||||
BoxError, Service, ServiceBuilder,
|
||||
};
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct EmptyRouter(pub(crate) ());
|
||||
pub struct AlwaysNotFound(pub(crate) ());
|
||||
|
||||
impl<R> Service<R> for EmptyRouter {
|
||||
impl<R> Service<R> for AlwaysNotFound {
|
||||
type Response = Response<Body>;
|
||||
type Error = Infallible;
|
||||
type Future = future::Ready<Result<Self::Response, Self::Error>>;
|
||||
@@ -42,14 +47,14 @@ pub struct RouteAt<R> {
|
||||
}
|
||||
|
||||
impl<R> RouteAt<R> {
|
||||
pub fn get<F, B, T>(self, handler_fn: F) -> RouteBuilder<Route<HandlerSvc<F, B, T>, R>>
|
||||
pub fn get<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::GET)
|
||||
}
|
||||
|
||||
pub fn get_service<S, B>(self, service: S) -> RouteBuilder<Route<S, R>>
|
||||
pub fn get_service<S, B>(self, service: S) -> RouteBuilder<Or<S, R>>
|
||||
where
|
||||
S: Service<Request<Body>, Response = Response<B>> + Clone,
|
||||
S::Error: Into<BoxError>,
|
||||
@@ -57,14 +62,14 @@ impl<R> RouteAt<R> {
|
||||
self.add_route_service(service, Method::GET)
|
||||
}
|
||||
|
||||
pub fn post<F, B, T>(self, handler_fn: F) -> RouteBuilder<Route<HandlerSvc<F, B, T>, R>>
|
||||
pub fn post<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::POST)
|
||||
}
|
||||
|
||||
pub fn post_service<S, B>(self, service: S) -> RouteBuilder<Route<S, R>>
|
||||
pub fn post_service<S, B>(self, service: S) -> RouteBuilder<Or<S, R>>
|
||||
where
|
||||
S: Service<Request<Body>, Response = Response<B>> + Clone,
|
||||
S::Error: Into<BoxError>,
|
||||
@@ -76,24 +81,24 @@ impl<R> RouteAt<R> {
|
||||
self,
|
||||
handler: H,
|
||||
method: Method,
|
||||
) -> RouteBuilder<Route<HandlerSvc<H, B, T>, R>>
|
||||
) -> RouteBuilder<Or<HandlerSvc<H, B, T>, R>>
|
||||
where
|
||||
H: Handler<B, T>,
|
||||
{
|
||||
self.add_route_service(HandlerSvc::new(handler), method)
|
||||
}
|
||||
|
||||
fn add_route_service<S>(self, service: S, method: Method) -> RouteBuilder<Route<S, R>> {
|
||||
fn add_route_service<S>(self, service: S, method: Method) -> RouteBuilder<Or<S, R>> {
|
||||
assert!(
|
||||
self.route_spec.starts_with(b"/"),
|
||||
"route spec must start with a slash (`/`)"
|
||||
);
|
||||
|
||||
let new_app = App {
|
||||
router: Route {
|
||||
service_tree: Or {
|
||||
service,
|
||||
route_spec: RouteSpec::new(method, self.route_spec.clone()),
|
||||
fallback: self.app.router,
|
||||
fallback: self.app.service_tree,
|
||||
handler_ready: false,
|
||||
fallback_ready: false,
|
||||
},
|
||||
@@ -128,14 +133,14 @@ impl<R> RouteBuilder<R> {
|
||||
self.app.at(route_spec)
|
||||
}
|
||||
|
||||
pub fn get<F, B, T>(self, handler_fn: F) -> RouteBuilder<Route<HandlerSvc<F, B, T>, R>>
|
||||
pub fn get<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).get(handler_fn)
|
||||
}
|
||||
|
||||
pub fn get_service<S, B>(self, service: S) -> RouteBuilder<Route<S, R>>
|
||||
pub fn get_service<S, B>(self, service: S) -> RouteBuilder<Or<S, R>>
|
||||
where
|
||||
S: Service<Request<Body>, Response = Response<B>> + Clone,
|
||||
S::Error: Into<BoxError>,
|
||||
@@ -143,14 +148,14 @@ impl<R> RouteBuilder<R> {
|
||||
self.app.at_bytes(self.route_spec).get_service(service)
|
||||
}
|
||||
|
||||
pub fn post<F, B, T>(self, handler_fn: F) -> RouteBuilder<Route<HandlerSvc<F, B, T>, R>>
|
||||
pub fn post<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).post(handler_fn)
|
||||
}
|
||||
|
||||
pub fn post_service<S, B>(self, service: S) -> RouteBuilder<Route<S, R>>
|
||||
pub fn post_service<S, B>(self, service: S) -> RouteBuilder<Or<S, R>>
|
||||
where
|
||||
S: Service<Request<Body>, Response = Response<B>> + Clone,
|
||||
S::Error: Into<BoxError>,
|
||||
@@ -164,9 +169,30 @@ impl<R> RouteBuilder<R> {
|
||||
poll_ready_error: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn boxed<B>(self) -> RouteBuilder<BoxServiceTree<B>>
|
||||
where
|
||||
R: Service<Request<Body>, Response = Response<B>, Error = Error> + Send + 'static,
|
||||
R::Future: Send,
|
||||
B: Default + 'static,
|
||||
{
|
||||
let svc = ServiceBuilder::new()
|
||||
.layer(BufferLayer::new(1024))
|
||||
.layer(BoxService::layer())
|
||||
.service(self.app.service_tree);
|
||||
|
||||
let app = App {
|
||||
service_tree: BoxServiceTree { inner: svc },
|
||||
};
|
||||
|
||||
RouteBuilder {
|
||||
app,
|
||||
route_spec: self.route_spec,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Route<H, F> {
|
||||
pub struct Or<H, F> {
|
||||
service: H,
|
||||
route_spec: RouteSpec,
|
||||
fallback: F,
|
||||
@@ -174,7 +200,7 @@ pub struct Route<H, F> {
|
||||
fallback_ready: bool,
|
||||
}
|
||||
|
||||
impl<H, F> Clone for Route<H, F>
|
||||
impl<H, F> Clone for Or<H, F>
|
||||
where
|
||||
H: Clone,
|
||||
F: Clone,
|
||||
@@ -242,7 +268,7 @@ impl RouteSpec {
|
||||
}
|
||||
}
|
||||
|
||||
impl<H, F, HB, FB> Service<Request<Body>> for Route<H, F>
|
||||
impl<H, F, HB, FB> Service<Request<Body>> for Or<H, F>
|
||||
where
|
||||
H: Service<Request<Body>, Response = Response<HB>>,
|
||||
H::Error: Into<Error>,
|
||||
@@ -327,6 +353,66 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
pub struct BoxServiceTree<B> {
|
||||
inner: Buffer<BoxService<Request<Body>, Response<B>, Error>, Request<Body>>,
|
||||
}
|
||||
|
||||
impl<B> Clone for BoxServiceTree<B> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
inner: self.inner.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<B> fmt::Debug for BoxServiceTree<B> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("BoxServiceTree").finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<B> Service<Request<Body>> for BoxServiceTree<B>
|
||||
where
|
||||
B: 'static,
|
||||
{
|
||||
type Response = Response<B>;
|
||||
type Error = Error;
|
||||
type Future = BoxServiceTreeResponseFuture<B>;
|
||||
|
||||
#[inline]
|
||||
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
self.inner.poll_ready(cx).map_err(Error::from_service_error)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn call(&mut self, req: Request<Body>) -> Self::Future {
|
||||
BoxServiceTreeResponseFuture {
|
||||
inner: self.inner.call(req),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[pin_project]
|
||||
pub struct BoxServiceTreeResponseFuture<B> {
|
||||
#[pin]
|
||||
inner: InnerFuture<B>,
|
||||
}
|
||||
|
||||
type InnerFuture<B> = tower::buffer::future::ResponseFuture<
|
||||
Pin<Box<dyn Future<Output = Result<Response<B>, Error>> + Send + 'static>>,
|
||||
>;
|
||||
|
||||
impl<B> Future for BoxServiceTreeResponseFuture<B> {
|
||||
type Output = Result<Response<B>, Error>;
|
||||
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
self.project()
|
||||
.inner
|
||||
.poll(cx)
|
||||
.map_err(Error::from_service_error)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[allow(unused_imports)]
|
||||
|
||||
+29
-1
@@ -250,7 +250,35 @@ async fn extracting_url_params() {
|
||||
assert_eq!(res.status(), StatusCode::OK);
|
||||
}
|
||||
|
||||
// TODO(david): lots of routes and boxing, shouldn't take forever to compile
|
||||
#[tokio::test]
|
||||
async fn boxing() {
|
||||
let app = app()
|
||||
.at("/")
|
||||
.get(|_: Request<Body>| async { Ok("hi from GET") })
|
||||
.boxed()
|
||||
.post(|_: Request<Body>| async { Ok("hi from POST") })
|
||||
.into_service();
|
||||
|
||||
let addr = run_in_background(app).await;
|
||||
|
||||
let client = reqwest::Client::new();
|
||||
|
||||
let res = client
|
||||
.get(format!("http://{}", addr))
|
||||
.send()
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(res.status(), StatusCode::OK);
|
||||
assert_eq!(res.text().await.unwrap(), "hi from GET");
|
||||
|
||||
let res = client
|
||||
.post(format!("http://{}", addr))
|
||||
.send()
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(res.status(), StatusCode::OK);
|
||||
assert_eq!(res.text().await.unwrap(), "hi from POST");
|
||||
}
|
||||
|
||||
/// Run a `tower::Service` in the background and get a URI for it.
|
||||
pub async fn run_in_background<S, ResBody>(svc: S) -> SocketAddr
|
||||
|
||||
Reference in New Issue
Block a user