Skip to content

Commit

Permalink
handler, extractor (cloudwego#221)
Browse files Browse the repository at this point in the history
* handler, extractor
  • Loading branch information
lz1998 authored and bobozhengsir committed Oct 25, 2023
1 parent e06b9fc commit 2a27c9e
Show file tree
Hide file tree
Showing 8 changed files with 272 additions and 2 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

25 changes: 24 additions & 1 deletion examples/src/http/http.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
use std::{convert::Infallible, net::SocketAddr};

use bytes::Bytes;
use http::{Response, StatusCode};
use http::{Method, Response, StatusCode, Uri};
use hyper::body::Incoming;
use motore::service::service_fn;
use serde::{Deserialize, Serialize};
use volo_http::{
handler::HandlerService,
request::Json,
route::{Route, Router},
HttpContext,
Expand Down Expand Up @@ -51,12 +52,34 @@ async fn json(
Ok(Response::new(()))
}

async fn test(
u: Uri,
m: Method,
Json(request): Json<Person>,
) -> Result<&'static str, (StatusCode, &'static str)> {
println!("{u:?}");
println!("{m:?}");
println!("{request:?}");
if u.to_string().ends_with("a") {
Ok("a") // http://localhost:3000/test?a=a
} else {
Err((StatusCode::BAD_REQUEST, "b")) // http://localhost:3000/test?a=bb
}
}

#[tokio::main(flavor = "multi_thread")]
async fn main() {
Router::build()
.route("/", Route::builder().get(service_fn(hello)).build())
.route("/:echo", Route::builder().get(service_fn(echo)).build())
.route("/user", Route::builder().post(service_fn(json)).build())
.route(
"/test",
Route::builder()
.get(HandlerService::new(test))
.post(HandlerService::new(test))
.build(),
)
.serve(SocketAddr::from(([127, 0, 0, 1], 3000)))
.await
.unwrap();
Expand Down
1 change: 1 addition & 0 deletions volo-http/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ serde_json = "1"
thiserror.workspace = true
mime = "0.3"
serde = "1"
async-trait.workspace = true

[dev-dependencies]
serde = { version = "1", features = ["derive"] }
38 changes: 38 additions & 0 deletions volo-http/src/extract.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
use http::{Method, Response, Uri};

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

#[async_trait::async_trait]
pub trait FromContext: Sized {
type Rejection: IntoResponse;
async fn from_context(context: &HttpContext) -> Result<Self, Self::Rejection>;
}
#[async_trait::async_trait]
impl<T> FromContext for Option<T>
where
T: FromContext,
{
type Rejection = Response<()>; // Infallible

async fn from_context(context: &HttpContext) -> Result<Self, Self::Rejection> {
Ok(T::from_context(context).await.ok())
}
}

#[async_trait::async_trait]
impl FromContext for Uri {
type Rejection = Response<()>; // Infallible

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

#[async_trait::async_trait]
impl FromContext for Method {
type Rejection = Response<()>;

async fn from_context(context: &HttpContext) -> Result<Method, Self::Rejection> {
Ok(context.method.clone())
}
}
123 changes: 123 additions & 0 deletions volo-http/src/handler.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
use std::{future::Future, marker::PhantomData};

use http::Response;
use hyper::body::Incoming;

use crate::{
extract::FromContext,
request::FromRequest,
response::{IntoResponse, RespBody},
HttpContext,
};

impl<H, T> Clone for HandlerService<H, T>
where
H: Clone,
{
fn clone(&self) -> Self {
Self {
h: self.h.clone(),
_mark: PhantomData,
}
}
}
pub trait Handler<T> {
type Future<'r>: Future<Output = Response<RespBody>> + Send + 'r
where
Self: 'r;
fn call(self, context: &mut HttpContext, req: Incoming) -> Self::Future<'_>;
}

macro_rules! impl_handler {
(
[$($ty:ident),*], $last:ident
) => {
#[allow(non_snake_case, unused_mut, unused_variables)]
impl<F, Fut, $($ty,)* $last, Res> Handler<($($ty,)* $last,)> for F
where
F: FnOnce($($ty,)* $last) -> Fut + Clone + Send,
Fut: Future<Output = Res> + Send,
$( for<'r> $ty: FromContext + Send + 'r, )*
for<'r> $last: FromRequest + Send + 'r,
Res: IntoResponse,
{
type Future<'r> = impl Future<Output=Response<RespBody>> + Send + 'r
where Self: 'r;

fn call(self, context: &mut HttpContext, req: Incoming) -> Self::Future<'_> {
async move {
$(
let $ty = match $ty::from_context(context).await {
Ok(value) => value,
Err(rejection) => return rejection.into_response(),
};
)*
let $last = match $last::from(context, req).await {
Ok(value) => value,
Err(rejection) => return rejection,
};
self($($ty,)* $last).await.into_response()
}
}
}
};
}

impl_handler!([], T1);
impl_handler!([T1], T2);
impl_handler!([T1, T2], T3);
impl_handler!([T1, T2, T3], T4);
impl_handler!([T1, T2, T3, T4], T5);
impl_handler!([T1, T2, T3, T4, T5], T6);
impl_handler!([T1, T2, T3, T4, T5, T6], T7);
impl_handler!([T1, T2, T3, T4, T5, T6, T7], T8);
impl_handler!([T1, T2, T3, T4, T5, T6, T7, T8], T9);
impl_handler!([T1, T2, T3, T4, T5, T6, T7, T8, T9], T10);
impl_handler!([T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T11);
impl_handler!([T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11], T12);
impl_handler!([T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12], T13);
impl_handler!(
[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13],
T14
);
impl_handler!(
[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14],
T15
);
impl_handler!(
[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15],
T16
);

pub struct HandlerService<H, T> {
h: H,
_mark: PhantomData<fn(T)>,
}

impl<H, T> HandlerService<H, T> {
pub fn new(h: H) -> Self {
Self {
h,
_mark: PhantomData,
}
}
}

impl<H, T> motore::Service<HttpContext, Incoming> for HandlerService<H, T>
where
for<'r> H: Handler<T> + Clone + Send + Sync + 'r,
{
type Response = Response<RespBody>;
type Error = http::Error;
type Future<'cx> = impl Future<Output = Result<Self::Response, Self::Error>> + Send + 'cx
where
HttpContext: 'cx,
Self: 'cx;

fn call<'cx, 's>(&'s self, cx: &'cx mut HttpContext, req: Incoming) -> Self::Future<'cx>
where
's: 'cx,
{
async move { Ok(self.h.clone().call(cx, req).await) }
}
}
2 changes: 2 additions & 0 deletions volo-http/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
#![feature(impl_trait_in_assoc_type)]

pub(crate) mod dispatch;
pub mod extract;
pub mod handler;
pub mod layer;
pub mod param;
pub mod request;
Expand Down
24 changes: 23 additions & 1 deletion volo-http/src/request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,11 @@ use http_body_util::BodyExt;
use hyper::body::Incoming;
use serde::de::DeserializeOwned;

use crate::{response::RespBody, HttpContext};
use crate::{
extract::FromContext,
response::{IntoResponse, RespBody},
HttpContext,
};

pub trait FromRequest: Sized {
type FromFut<'cx>: Future<Output = Result<Self, Response<RespBody>>> + Send + 'cx
Expand All @@ -15,6 +19,24 @@ pub trait FromRequest: Sized {
fn from(cx: &HttpContext, body: Incoming) -> Self::FromFut<'_>;
}

impl<T> FromRequest for T
where
T: FromContext,
{
type FromFut<'cx> = impl Future<Output = Result<Self, Response<RespBody>>> + Send + 'cx
where
Self: 'cx;

fn from(cx: &HttpContext, _body: Incoming) -> Self::FromFut<'_> {
async move {
match T::from_context(cx).await {
Ok(value) => Ok(value),
Err(rejection) => Err(rejection.into_response()),
}
}
}
}

impl FromRequest for Incoming {
type FromFut<'cx> = impl Future<Output = Result<Self, Response<RespBody>>> + Send + 'cx
where
Expand Down
60 changes: 60 additions & 0 deletions volo-http/src/response.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use std::{
};

use futures_util::{ready, stream};
use http::{Response, StatusCode};
use http_body_util::{Full, StreamBody};
use hyper::body::{Body, Bytes, Frame};
use pin_project_lite::pin_project;
Expand Down Expand Up @@ -77,3 +78,62 @@ impl From<()> for RespBody {
}
}
}

pub trait IntoResponse {
fn into_response(self) -> Response<RespBody>;
}

impl<T> IntoResponse for Response<T>
where
T: Into<RespBody>,
{
fn into_response(self) -> Response<RespBody> {
let (parts, body) = self.into_parts();
Response::from_parts(parts, body.into())
}
}

impl<T> IntoResponse for T
where
T: Into<RespBody>,
{
fn into_response(self) -> Response<RespBody> {
Response::builder()
.status(StatusCode::OK)
.body(self.into())
.unwrap()
}
}

impl<R, E> IntoResponse for Result<R, E>
where
R: IntoResponse,
E: IntoResponse,
{
fn into_response(self) -> Response<RespBody> {
match self {
Ok(value) => value.into_response(),
Err(err) => err.into_response(),
}
}
}

impl<T> IntoResponse for (StatusCode, T)
where
T: IntoResponse,
{
fn into_response(self) -> Response<RespBody> {
let mut resp = self.1.into_response();
*resp.status_mut() = self.0;
resp
}
}

impl IntoResponse for StatusCode {
fn into_response(self) -> Response<RespBody> {
Response::builder()
.status(self)
.body(String::new().into())
.unwrap()
}
}

0 comments on commit 2a27c9e

Please sign in to comment.