mirror of
https://github.com/tokio-rs/axum.git
synced 2025-10-02 15:24:54 +00:00
More work
This commit is contained in:
parent
867dd8012c
commit
f6b1a6f435
@ -16,7 +16,7 @@ serde = "1.0"
|
|||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
serde_urlencoded = "0.7"
|
serde_urlencoded = "0.7"
|
||||||
thiserror = "1.0"
|
thiserror = "1.0"
|
||||||
tower = { version = "0.4", features = ["util"] }
|
tower = { version = "0.4", features = ["util", "buffer"] }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
hyper = { version = "0.14", features = ["full"] }
|
hyper = { version = "0.14", features = ["full"] }
|
||||||
|
217
README.md
Normal file
217
README.md
Normal file
@ -0,0 +1,217 @@
|
|||||||
|
# tower-web
|
||||||
|
|
||||||
|
This is *not* https://github.com/carllerche/tower-web even though the name is
|
||||||
|
the same. Its just a prototype of a minimal HTTP framework I've been toying
|
||||||
|
with.
|
||||||
|
|
||||||
|
# What is this?
|
||||||
|
|
||||||
|
## Goals
|
||||||
|
|
||||||
|
- As easy to use as tide. I don't really consider warp easy to use due to type
|
||||||
|
tricks it uses. `fn route() -> impl Filter<...>` also isn't very ergonomic.
|
||||||
|
Just `async fn(Request) -> Result<Response, Error>` would be nicer.
|
||||||
|
- Deep integration with Tower meaning you can
|
||||||
|
- Apply middleware to the entire application.
|
||||||
|
- Apply middleware to a single route.
|
||||||
|
- Apply middleware to subset of routes.
|
||||||
|
- Just focus on routing and generating responses. Tower can do the rest.
|
||||||
|
Want timeouts? Use `tower::timeout::Timeout`. Want logging? Use
|
||||||
|
`tower_http::trace::Trace`.
|
||||||
|
- Work with Tokio. tide is cool but requires async-std.
|
||||||
|
- Not macro based. Heavy macro based APIs can be very ergonomic but comes at a
|
||||||
|
complexity cost. Would like to see if I can design an API that is ergonomic
|
||||||
|
and doesn't require macros.
|
||||||
|
|
||||||
|
## Non-goals
|
||||||
|
|
||||||
|
- Runtime independent. If becoming runtime independent isn't too much then fine
|
||||||
|
but explicitly designing for runtime independence isn't a goal.
|
||||||
|
- Speed. As long as things are reasonably fast that is fine. For example using
|
||||||
|
async-trait for ergonomics is fine even though it comes at a cost.
|
||||||
|
|
||||||
|
# Example usage
|
||||||
|
|
||||||
|
Defining a single route looks like this:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
let app = tower_web::app().at("/").get(root);
|
||||||
|
|
||||||
|
async fn root(req: Request<Body>) -> Result<&'static str, Error> {
|
||||||
|
Ok("Hello, World!")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Adding more routes follows the same pattern:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
let app = tower_web::app()
|
||||||
|
.at("/")
|
||||||
|
.get(root)
|
||||||
|
.at("/users")
|
||||||
|
.get(users_index)
|
||||||
|
.post(users_create);
|
||||||
|
```
|
||||||
|
|
||||||
|
Handler functions are just async functions like:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
async fn handler(req: Request<Body>) -> Result<&'static str, Error> {
|
||||||
|
Ok("Hello, World!")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
They most take the request as the first argument but all arguments following
|
||||||
|
are called "extractors" and are used to extract data from the request (similar
|
||||||
|
to rocket but no macros):
|
||||||
|
|
||||||
|
```rust
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct UserPayload {
|
||||||
|
username: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct Pagination {
|
||||||
|
page: usize,
|
||||||
|
per_page: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn handler(
|
||||||
|
req: Request<Body>,
|
||||||
|
// deserialize response body with `serde_json` into a `UserPayload`
|
||||||
|
user: extract::Json<UserPayload>,
|
||||||
|
// deserialize query string into a `Pagination`
|
||||||
|
pagination: extract::Query<Pagination>,
|
||||||
|
) -> Result<&'static str, Error> {
|
||||||
|
let user: UserPayload = user.into_inner();
|
||||||
|
let pagination: Pagination = pagination.into_inner();
|
||||||
|
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
You can also get the raw response body:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
async fn handler(
|
||||||
|
req: Request<Body>,
|
||||||
|
// buffer the whole request body
|
||||||
|
body: Bytes,
|
||||||
|
) -> Result<&'static str, Error> {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Or limit the body size:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
async fn handler(
|
||||||
|
req: Request<Body>,
|
||||||
|
// max body size in bytes
|
||||||
|
body: extract::BytesMaxLength<1024>,
|
||||||
|
) -> Result<&'static str, Error> {
|
||||||
|
Ok("Hello, World!")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Anything that implements `FromRequest` can work as an extractor where
|
||||||
|
`FromRequest` is a simple async trait:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
#[async_trait]
|
||||||
|
pub trait FromRequest: Sized {
|
||||||
|
async fn from_request(req: &mut Request<Body>) -> Result<Self, Error>;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
You can also return different response types:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
async fn string_response(req: Request<Body>) -> Result<String, Error> {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
|
||||||
|
// gets `content-type: appliation/json`. `Json` can contain any `T: Serialize`
|
||||||
|
async fn json_response(req: Request<Body>) -> Result<response::Json<User>, Error> {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
|
||||||
|
// gets `content-type: text/html`. `Html` can contain any `T: Into<Bytes>`
|
||||||
|
async fn html_response(req: Request<Body>) -> Result<response::Html<String>, Error> {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
|
||||||
|
// or for full control
|
||||||
|
async fn response(req: Request<Body>) -> Result<Response<Body>, Error> {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
You can also apply Tower middleware to single routes:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
let app = tower_web::app()
|
||||||
|
.at("/")
|
||||||
|
.get(send_some_large_file.layer(tower_http::compression::CompressionLayer::new()))
|
||||||
|
```
|
||||||
|
|
||||||
|
Or to the whole app:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
let service = tower_web::app()
|
||||||
|
.at("/")
|
||||||
|
.get(root)
|
||||||
|
.into_service()
|
||||||
|
|
||||||
|
let app = ServiceBuilder::new()
|
||||||
|
.timeout(Duration::from_secs(30))
|
||||||
|
.layer(TraceLayer::new_for_http())
|
||||||
|
.layer(CompressionLayer::new())
|
||||||
|
.service(app);
|
||||||
|
```
|
||||||
|
|
||||||
|
And of course run it with Hyper:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() {
|
||||||
|
tracing_subscriber::fmt::init();
|
||||||
|
|
||||||
|
// build our application with some routes
|
||||||
|
let app = tower_web::app()
|
||||||
|
.at("/")
|
||||||
|
.get(handler)
|
||||||
|
// convert it into a `Service`
|
||||||
|
.into_service();
|
||||||
|
|
||||||
|
// add some middleware
|
||||||
|
let app = ServiceBuilder::new()
|
||||||
|
.layer(TraceLayer::new_for_http())
|
||||||
|
.service(app);
|
||||||
|
|
||||||
|
// 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();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
See the examples directory for more examples.
|
||||||
|
|
||||||
|
# TODO
|
||||||
|
|
||||||
|
- Error handling should probably be redone. Not quite sure if its bad the
|
||||||
|
`Error` is just an enum where everything is public.
|
||||||
|
- Audit which error codes we return for each kind of error. This will probably
|
||||||
|
be changed when error handling is re-done.
|
||||||
|
- Probably don't want to require `hyper::Body` for request bodies. Should
|
||||||
|
have our own so hyper isn't required.
|
||||||
|
- `RouteBuilder` should have an `async fn serve(self) -> Result<(),
|
||||||
|
hyper::Error>` for users who just wanna create a hyper server and not care
|
||||||
|
about the lower level details. Should be gated by a `hyper` feature.
|
||||||
|
- Each new route makes a new allocation for the response body, since `Or` needs
|
||||||
|
to unify the response body types. Would be nice to find a way to avoid that.
|
||||||
|
- It should be possible to package some routes together and apply a tower
|
||||||
|
middleware to that collection and then merge those routes into the app.
|
232
examples/lots_of_routes.rs
Normal file
232
examples/lots_of_routes.rs
Normal file
@ -0,0 +1,232 @@
|
|||||||
|
use http::Request;
|
||||||
|
use hyper::Server;
|
||||||
|
use std::net::SocketAddr;
|
||||||
|
use tower::make::Shared;
|
||||||
|
use tower_web::{body::Body, Error};
|
||||||
|
|
||||||
|
#[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<Body>) -> Result<&'static str, Error> {
|
||||||
|
Ok("Hello, World!")
|
||||||
|
}
|
@ -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>
|
impl<D, E> Default for BoxBody<D, E>
|
||||||
where
|
where
|
||||||
D: bytes::Buf + 'static,
|
D: bytes::Buf + 'static,
|
||||||
|
10
src/error.rs
10
src/error.rs
@ -49,6 +49,16 @@ pub enum Error {
|
|||||||
InvalidUtf8,
|
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 {
|
impl From<Infallible> for Error {
|
||||||
fn from(err: Infallible) -> Self {
|
fn from(err: Infallible) -> Self {
|
||||||
match err {}
|
match err {}
|
||||||
|
12
src/lib.rs
12
src/lib.rs
@ -1,6 +1,6 @@
|
|||||||
use self::{
|
use self::{
|
||||||
body::Body,
|
body::Body,
|
||||||
routing::{EmptyRouter, RouteAt},
|
routing::{AlwaysNotFound, RouteAt},
|
||||||
};
|
};
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
use futures_util::ready;
|
use futures_util::ready;
|
||||||
@ -26,15 +26,15 @@ mod tests;
|
|||||||
|
|
||||||
pub use self::error::Error;
|
pub use self::error::Error;
|
||||||
|
|
||||||
pub fn app() -> App<EmptyRouter> {
|
pub fn app() -> App<AlwaysNotFound> {
|
||||||
App {
|
App {
|
||||||
router: EmptyRouter(()),
|
service_tree: AlwaysNotFound(()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct App<R> {
|
pub struct App<R> {
|
||||||
router: R,
|
service_tree: R,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<R> App<R> {
|
impl<R> App<R> {
|
||||||
@ -79,7 +79,7 @@ where
|
|||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
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);
|
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)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
122
src/routing.rs
122
src/routing.rs
@ -10,16 +10,21 @@ use http::{Method, Request, Response, StatusCode};
|
|||||||
use pin_project::pin_project;
|
use pin_project::pin_project;
|
||||||
use std::{
|
use std::{
|
||||||
convert::Infallible,
|
convert::Infallible,
|
||||||
|
fmt,
|
||||||
future::Future,
|
future::Future,
|
||||||
pin::Pin,
|
pin::Pin,
|
||||||
task::{Context, Poll},
|
task::{Context, Poll},
|
||||||
};
|
};
|
||||||
use tower::{BoxError, Service};
|
use tower::{
|
||||||
|
buffer::{Buffer, BufferLayer},
|
||||||
|
util::BoxService,
|
||||||
|
BoxError, Service, ServiceBuilder,
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
#[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 Response = Response<Body>;
|
||||||
type Error = Infallible;
|
type Error = Infallible;
|
||||||
type Future = future::Ready<Result<Self::Response, Self::Error>>;
|
type Future = future::Ready<Result<Self::Response, Self::Error>>;
|
||||||
@ -42,14 +47,14 @@ pub struct RouteAt<R> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<R> 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
|
where
|
||||||
F: Handler<B, T>,
|
F: Handler<B, T>,
|
||||||
{
|
{
|
||||||
self.add_route(handler_fn, Method::GET)
|
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
|
where
|
||||||
S: Service<Request<Body>, Response = Response<B>> + Clone,
|
S: Service<Request<Body>, Response = Response<B>> + Clone,
|
||||||
S::Error: Into<BoxError>,
|
S::Error: Into<BoxError>,
|
||||||
@ -57,14 +62,14 @@ impl<R> RouteAt<R> {
|
|||||||
self.add_route_service(service, Method::GET)
|
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
|
where
|
||||||
F: Handler<B, T>,
|
F: Handler<B, T>,
|
||||||
{
|
{
|
||||||
self.add_route(handler_fn, Method::POST)
|
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
|
where
|
||||||
S: Service<Request<Body>, Response = Response<B>> + Clone,
|
S: Service<Request<Body>, Response = Response<B>> + Clone,
|
||||||
S::Error: Into<BoxError>,
|
S::Error: Into<BoxError>,
|
||||||
@ -76,24 +81,24 @@ impl<R> RouteAt<R> {
|
|||||||
self,
|
self,
|
||||||
handler: H,
|
handler: H,
|
||||||
method: Method,
|
method: Method,
|
||||||
) -> RouteBuilder<Route<HandlerSvc<H, B, T>, R>>
|
) -> RouteBuilder<Or<HandlerSvc<H, B, T>, R>>
|
||||||
where
|
where
|
||||||
H: Handler<B, T>,
|
H: Handler<B, T>,
|
||||||
{
|
{
|
||||||
self.add_route_service(HandlerSvc::new(handler), method)
|
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!(
|
assert!(
|
||||||
self.route_spec.starts_with(b"/"),
|
self.route_spec.starts_with(b"/"),
|
||||||
"route spec must start with a slash (`/`)"
|
"route spec must start with a slash (`/`)"
|
||||||
);
|
);
|
||||||
|
|
||||||
let new_app = App {
|
let new_app = App {
|
||||||
router: Route {
|
service_tree: Or {
|
||||||
service,
|
service,
|
||||||
route_spec: RouteSpec::new(method, self.route_spec.clone()),
|
route_spec: RouteSpec::new(method, self.route_spec.clone()),
|
||||||
fallback: self.app.router,
|
fallback: self.app.service_tree,
|
||||||
handler_ready: false,
|
handler_ready: false,
|
||||||
fallback_ready: false,
|
fallback_ready: false,
|
||||||
},
|
},
|
||||||
@ -128,14 +133,14 @@ impl<R> RouteBuilder<R> {
|
|||||||
self.app.at(route_spec)
|
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
|
where
|
||||||
F: Handler<B, T>,
|
F: Handler<B, T>,
|
||||||
{
|
{
|
||||||
self.app.at_bytes(self.route_spec).get(handler_fn)
|
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
|
where
|
||||||
S: Service<Request<Body>, Response = Response<B>> + Clone,
|
S: Service<Request<Body>, Response = Response<B>> + Clone,
|
||||||
S::Error: Into<BoxError>,
|
S::Error: Into<BoxError>,
|
||||||
@ -143,14 +148,14 @@ impl<R> RouteBuilder<R> {
|
|||||||
self.app.at_bytes(self.route_spec).get_service(service)
|
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
|
where
|
||||||
F: Handler<B, T>,
|
F: Handler<B, T>,
|
||||||
{
|
{
|
||||||
self.app.at_bytes(self.route_spec).post(handler_fn)
|
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
|
where
|
||||||
S: Service<Request<Body>, Response = Response<B>> + Clone,
|
S: Service<Request<Body>, Response = Response<B>> + Clone,
|
||||||
S::Error: Into<BoxError>,
|
S::Error: Into<BoxError>,
|
||||||
@ -164,9 +169,30 @@ impl<R> RouteBuilder<R> {
|
|||||||
poll_ready_error: None,
|
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,
|
service: H,
|
||||||
route_spec: RouteSpec,
|
route_spec: RouteSpec,
|
||||||
fallback: F,
|
fallback: F,
|
||||||
@ -174,7 +200,7 @@ pub struct Route<H, F> {
|
|||||||
fallback_ready: bool,
|
fallback_ready: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<H, F> Clone for Route<H, F>
|
impl<H, F> Clone for Or<H, F>
|
||||||
where
|
where
|
||||||
H: Clone,
|
H: Clone,
|
||||||
F: 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
|
where
|
||||||
H: Service<Request<Body>, Response = Response<HB>>,
|
H: Service<Request<Body>, Response = Response<HB>>,
|
||||||
H::Error: Into<Error>,
|
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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
#[allow(unused_imports)]
|
#[allow(unused_imports)]
|
||||||
|
30
src/tests.rs
30
src/tests.rs
@ -250,7 +250,35 @@ async fn extracting_url_params() {
|
|||||||
assert_eq!(res.status(), StatusCode::OK);
|
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.
|
/// Run a `tower::Service` in the background and get a URI for it.
|
||||||
pub async fn run_in_background<S, ResBody>(svc: S) -> SocketAddr
|
pub async fn run_in_background<S, ResBody>(svc: S) -> SocketAddr
|
||||||
|
Loading…
x
Reference in New Issue
Block a user