More work

This commit is contained in:
David Pedersen
2021-05-31 16:28:26 +02:00
parent 867dd8012c
commit f6b1a6f435
8 changed files with 600 additions and 27 deletions
+1 -1
View File
@@ -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,
+10
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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