From 83edb643533de545bdb2fcd48fbcacc5c15da1b9 Mon Sep 17 00:00:00 2001 From: "lizheng.lz1998" Date: Thu, 31 Aug 2023 13:18:55 +0800 Subject: [PATCH] handler, extractor --- Cargo.lock | 1 + examples/src/http/http.rs | 13 ++++- volo-http/Cargo.toml | 1 + volo-http/src/extract.rs | 37 ++++++++++++++ volo-http/src/handler.rs | 102 ++++++++++++++++++++++++++++++++++++++ volo-http/src/lib.rs | 2 + 6 files changed, 155 insertions(+), 1 deletion(-) create mode 100644 volo-http/src/extract.rs create mode 100644 volo-http/src/handler.rs diff --git a/Cargo.lock b/Cargo.lock index afe4c15a..36672f23 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2691,6 +2691,7 @@ dependencies = [ name = "volo-http" version = "0.0.0" dependencies = [ + "async-trait", "bytes", "futures-util", "http", diff --git a/examples/src/http/http.rs b/examples/src/http/http.rs index b8c883f0..42d6f6d4 100644 --- a/examples/src/http/http.rs +++ b/examples/src/http/http.rs @@ -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, @@ -51,12 +52,22 @@ async fn json( Ok(Response::new(())) } +async fn test(u: Uri, m: Method) -> &'static str { + println!("{u:?}"); + println!("{m:?}"); + "test" +} + #[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)).build(), + ) .serve(SocketAddr::from(([127, 0, 0, 1], 3000))) .await .unwrap(); diff --git a/volo-http/Cargo.toml b/volo-http/Cargo.toml index 898f86fb..6f56dce4 100644 --- a/volo-http/Cargo.toml +++ b/volo-http/Cargo.toml @@ -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"] } diff --git a/volo-http/src/extract.rs b/volo-http/src/extract.rs new file mode 100644 index 00000000..7a5e56d3 --- /dev/null +++ b/volo-http/src/extract.rs @@ -0,0 +1,37 @@ +use http::{Method, Uri}; + +use crate::{response::RespBody, HttpContext}; +#[async_trait::async_trait] +pub trait FromContext: Sized { + type Rejection: Into; + async fn from_context(context: &mut HttpContext) -> Result; +} +#[async_trait::async_trait] +impl FromContext for Option +where + T: FromContext, +{ + type Rejection = &'static str; + + async fn from_context(context: &mut HttpContext) -> Result { + Ok(T::from_context(context).await.ok()) + } +} + +#[async_trait::async_trait] +impl FromContext for Uri { + type Rejection = String; + + async fn from_context(context: &mut HttpContext) -> Result { + Ok(context.uri.clone()) + } +} + +#[async_trait::async_trait] +impl FromContext for Method { + type Rejection = String; + + async fn from_context(context: &mut HttpContext) -> Result { + Ok(context.method.clone()) + } +} diff --git a/volo-http/src/handler.rs b/volo-http/src/handler.rs new file mode 100644 index 00000000..644e719d --- /dev/null +++ b/volo-http/src/handler.rs @@ -0,0 +1,102 @@ +use std::{future::Future, marker::PhantomData}; + +use http::{Response, StatusCode}; +use hyper::body::Incoming; + +use crate::{extract::FromContext, response::RespBody, HttpContext}; + +impl Clone for HandlerService +where + H: Clone, +{ + fn clone(&self) -> Self { + Self { + h: self.h.clone(), + _mark: PhantomData, + } + } +} +pub trait Handler<'r, T> { + type Future: Future + Send + 'r; + fn call(self, context: &'r mut HttpContext) -> Self::Future; +} + +impl<'r, F, Fut, T1, Res> Handler<'r, T1> for F +where + F: FnOnce(T1) -> Fut + Clone + Send + 'r, + Fut: Future + Send + 'r, + T1: FromContext + Send + 'r, + Res: Into, +{ + type Future = impl Future + Send + 'r; + + fn call(self, context: &'r mut HttpContext) -> Self::Future { + async move { + let t1 = match T1::from_context(context).await { + Ok(value) => value, + Err(rejection) => return rejection.into(), + }; + self(t1).await.into() + } + } +} + +impl<'r, F, Fut, T1, T2, Res> Handler<'r, (T1, T2)> for F +where + F: FnOnce(T1, T2) -> Fut + Clone + Send + 'r, + Fut: Future + Send, + T1: FromContext + Send + 'r, + T2: FromContext + Send + 'r, + Res: Into, +{ + type Future = impl Future + Send + 'r; + + fn call(self, context: &'r mut HttpContext) -> Self::Future { + async move { + let t1 = match T1::from_context(context).await { + Ok(value) => value, + Err(rejection) => return rejection.into(), + }; + let t2 = match T2::from_context(context).await { + Ok(value) => value, + Err(rejection) => return rejection.into(), + }; + self(t1, t2).await.into() + } + } +} +pub struct HandlerService { + h: H, + _mark: PhantomData, +} + +impl HandlerService { + pub fn new(h: H) -> Self { + Self { + h, + _mark: PhantomData, + } + } +} + +impl motore::Service for HandlerService +where + H: for<'r> Handler<'r, T> + Clone + Send + Sync, +{ + type Response = Response; + type Error = http::Error; + type Future<'cx> = impl Future> + 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 { + let resp = self.h.clone().call(cx).await; + Response::builder().status(StatusCode::OK).body(resp) + } + } +} diff --git a/volo-http/src/lib.rs b/volo-http/src/lib.rs index c076c34c..bd5d3551 100644 --- a/volo-http/src/lib.rs +++ b/volo-http/src/lib.rs @@ -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;