Skip to content

Commit

Permalink
feat(volo-http): make handler more powerful
Browse files Browse the repository at this point in the history
With this commit, there is no need to keep `HttpContext` in handler
function.  Like axum, anything with `FromContext` or `FromRequest`
trait can be used as arguments of a handler.

Signed-off-by: Yu Li <liyu.yukiteru@bytedance.com>
  • Loading branch information
wfly1998 committed Nov 24, 2023
1 parent cd2be09 commit bef4e18
Show file tree
Hide file tree
Showing 5 changed files with 71 additions and 42 deletions.
54 changes: 17 additions & 37 deletions examples/src/http/http.rs
Original file line number Diff line number Diff line change
@@ -1,46 +1,36 @@
use std::{convert::Infallible, net::SocketAddr};
use std::net::SocketAddr;

use bytes::Bytes;
use http::{Method, Response, StatusCode, Uri};
use hyper::body::Incoming;
use motore::{service::service_fn, timeout::TimeoutLayer};
use serde::{Deserialize, Serialize};
use http::{Method, StatusCode, Uri};
use motore::timeout::TimeoutLayer;
use serde::Deserialize;
use volo_http::{
handler::HandlerService,
param::Params,
request::Json,
route::{MethodRouter, Router},
route::{get, post, MethodRouter, Router},
server::Server,
HttpContext,
};

async fn hello(
_cx: &mut HttpContext,
_request: Incoming,
) -> Result<Response<&'static str>, Infallible> {
Ok(Response::new("hello, world\n"))
async fn hello() -> &'static str {
"hello, world\n"
}

async fn echo(cx: &mut HttpContext, _request: Incoming) -> Result<Response<Bytes>, Infallible> {
if let Some(echo) = cx.params.get("echo") {
return Ok(Response::new(echo.clone()));
async fn echo(params: Params) -> Result<Bytes, StatusCode> {
if let Some(echo) = params.get("echo") {
return Ok(echo.clone());
}
Ok(Response::builder()
.status(StatusCode::BAD_REQUEST)
.body(Bytes::new())
.unwrap())
Err(StatusCode::BAD_REQUEST)
}

#[derive(Serialize, Deserialize, Debug)]
#[derive(Deserialize, Debug)]
struct Person {
name: String,
age: u8,
phones: Vec<String>,
}

async fn json(
_cx: &mut HttpContext,
Json(request): Json<Person>,
) -> Result<Response<()>, Infallible> {
async fn json(Json(request): Json<Person>) {
let first_phone = request
.phones
.get(0)
Expand All @@ -50,7 +40,6 @@ async fn json(
"{} is {} years old, {}'s first phone number is {}",
request.name, request.age, request.name, first_phone
);
Ok(Response::new(()))
}

async fn test(
Expand All @@ -73,19 +62,10 @@ async fn main() {
let app = Router::new()
.route(
"/",
MethodRouter::builder()
.get(service_fn(hello))
.build()
.layer(TimeoutLayer::new(Some(std::time::Duration::from_secs(1)))),
)
.route(
"/:echo",
MethodRouter::builder().get(service_fn(echo)).build(),
)
.route(
"/user",
MethodRouter::builder().post(service_fn(json)).build(),
get(hello).layer(TimeoutLayer::new(Some(std::time::Duration::from_secs(1)))),
)
.route("/:echo", get(echo))
.route("/user", post(json))
.route(
"/test",
MethodRouter::builder()
Expand Down
14 changes: 12 additions & 2 deletions volo-http/src/extract.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use futures_util::Future;
use http::{Method, Response, Uri};

use crate::{response::IntoResponse, HttpContext};
use crate::{response::IntoResponse, HttpContext, Params};

pub trait FromContext: Sized {
type Rejection: IntoResponse;
Expand Down Expand Up @@ -34,9 +34,19 @@ impl FromContext for Uri {
}

impl FromContext for Method {
type Rejection = Response<()>;
type Rejection = Response<()>; // Infallible

async fn from_context(context: &HttpContext) -> Result<Method, Self::Rejection> {
Ok(context.method.clone())
}
}

impl FromContext for Params {
type Rejection = Response<()>; // Infallible

fn from_context(
context: &HttpContext,
) -> impl Future<Output = Result<Params, Self::Rejection>> + Send {
async move { Ok(context.params.clone()) }
}
}
12 changes: 12 additions & 0 deletions volo-http/src/handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ where
}
}
}

pub trait Handler<T> {
fn call(
self,
Expand All @@ -29,6 +30,17 @@ pub trait Handler<T> {
) -> impl Future<Output = Response<RespBody>> + Send;
}

impl<F, Fut, Res> Handler<()> for F
where
F: FnOnce() -> Fut + Clone + Send,
Fut: Future<Output = Res> + Send,
Res: IntoResponse,
{
async fn call(self, _context: &mut HttpContext, _req: Incoming) -> Response<RespBody> {
self().await.into_response()
}
}

macro_rules! impl_handler {
(
[$($ty:ident),*], $last:ident
Expand Down
1 change: 1 addition & 0 deletions volo-http/src/param.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use std::slice::Iter;

use bytes::{BufMut, Bytes, BytesMut};

#[derive(Clone)]
pub struct Params {
pub(crate) inner: Vec<(Bytes, Bytes)>,
}
Expand Down
32 changes: 29 additions & 3 deletions volo-http/src/route.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,11 @@ use hyper::body::{Bytes, Incoming};
use motore::{layer::Layer, Service};

use crate::{
dispatch::DispatchService, request::FromRequest, response::RespBody, DynError, HttpContext,
dispatch::DispatchService,
handler::{Handler, HandlerService},
request::FromRequest,
response::RespBody,
DynError, HttpContext,
};

// The `matchit::Router` cannot be converted to `Iterator`, so using
Expand Down Expand Up @@ -209,11 +213,17 @@ impl Service<HttpContext, Incoming> for MethodRouter {
}
}

macro_rules! for_all_methods {
($name:ident) => {
$name!(options, get, post, put, delete, head, trace, connect, patch);
};
}

pub struct MethodRouterBuilder {
route: MethodRouter,
}

macro_rules! impl_method_register {
macro_rules! impl_method_register_for_builder {
($( $method:ident ),*) => {
$(
pub fn $method<S, IB, OB>(mut self, handler: S) -> Self
Expand All @@ -235,7 +245,7 @@ macro_rules! impl_method_register {
}

impl MethodRouterBuilder {
impl_method_register!(options, get, post, put, delete, head, trace, connect, patch);
for_all_methods!(impl_method_register_for_builder);

pub fn build(self) -> MethodRouter {
self.route
Expand Down Expand Up @@ -284,3 +294,19 @@ impl Service<HttpContext, Incoming> for Route {
self.0.call(cx, req).await
}
}

macro_rules! impl_method_register {
($( $method:ident ),*) => {
$(
pub fn $method<H, T>(h: H) -> MethodRouter
where
for<'a> H: Handler<T> + Clone + Send + Sync + 'a,
for<'a> T: 'a,
{
MethodRouter::builder().$method(HandlerService::new(h)).build()
}
)+
};
}

for_all_methods!(impl_method_register);

0 comments on commit bef4e18

Please sign in to comment.