From f0056d7055195be8611f627de2aabcb535d62ec1 Mon Sep 17 00:00:00 2001 From: Yukiteru Li Date: Fri, 1 Dec 2023 11:20:54 +0800 Subject: [PATCH] feat(volo-http): support extract in timeout handler (#268) * feat(volo-http): support extract in timeout handler * chore(volo-http): re-export common types from other crates Signed-off-by: Yu Li --- Cargo.lock | 3 -- examples/Cargo.toml | 3 -- examples/src/http/simple.rs | 21 +++--------- volo-http/src/extract.rs | 12 +++++++ volo-http/src/handler.rs | 67 +++++++++++++++++++++++-------------- volo-http/src/layer.rs | 44 ++++++++++++++---------- volo-http/src/lib.rs | 11 ++++-- volo-http/src/macros.rs | 46 +++++++++++++++++++++++++ 8 files changed, 139 insertions(+), 68 deletions(-) create mode 100644 volo-http/src/macros.rs diff --git a/Cargo.lock b/Cargo.lock index f4f4c96a..9efc3a38 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -529,9 +529,6 @@ dependencies = [ "anyhow", "async-stream", "bytes", - "http 1.0.0", - "http-body-util", - "hyper 1.0.1", "lazy_static", "metainfo", "motore", diff --git a/examples/Cargo.toml b/examples/Cargo.toml index 4779866c..9c77ce2b 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -81,9 +81,6 @@ path = "src/http/simple.rs" anyhow.workspace = true async-stream.workspace = true bytes.workspace = true -http = "1" -http-body-util = "0.1" -hyper = { version = "1", features = ["server", "http1", "http2"] } lazy_static.workspace = true metainfo.workspace = true motore.workspace = true diff --git a/examples/src/http/simple.rs b/examples/src/http/simple.rs index 577cc265..85ecf7c3 100644 --- a/examples/src/http/simple.rs +++ b/examples/src/http/simple.rs @@ -1,16 +1,10 @@ use std::{net::SocketAddr, time::Duration}; -use bytes::Bytes; -use http::{Method, Response, StatusCode, Uri}; -use http_body_util::Full; use serde::Deserialize; use volo_http::{ layer::TimeoutLayer, - param::Params, - request::Json, route::{get, post, MethodRouter, Router}, - server::Server, - HttpContext, + Address, Bytes, Json, Method, Params, Server, StatusCode, Uri, }; async fn hello() -> &'static str { @@ -57,8 +51,8 @@ async fn timeout_test() { tokio::time::sleep(Duration::from_secs(5)).await } -fn timeout_handler(ctx: &HttpContext) -> StatusCode { - tracing::info!("Timeout on `{}`, peer: {}", ctx.uri, ctx.peer); +fn timeout_handler(uri: Uri, peer: Address) -> StatusCode { + tracing::info!("Timeout on `{}`, peer: {}", uri, peer); StatusCode::INTERNAL_SERVER_ERROR } @@ -70,16 +64,11 @@ async fn main() { tracing::subscriber::set_global_default(subscriber).expect("setting default subscriber failed"); - let timeout_response = Response::builder() - .status(StatusCode::INTERNAL_SERVER_ERROR) - .body(Full::new(Bytes::new())) - .unwrap(); - let app = Router::new() .route( "/", - get(hello).layer(TimeoutLayer::new(Duration::from_secs(1), move |_| { - timeout_response + get(hello).layer(TimeoutLayer::new(Duration::from_secs(1), || { + StatusCode::INTERNAL_SERVER_ERROR })), ) .route("/:echo", get(echo)) diff --git a/volo-http/src/extract.rs b/volo-http/src/extract.rs index 3bc4c182..cbce3f04 100644 --- a/volo-http/src/extract.rs +++ b/volo-http/src/extract.rs @@ -1,5 +1,6 @@ use futures_util::Future; use http::{Method, Response, Uri}; +use volo::net::Address; use crate::{response::IntoResponse, HttpContext, Params, State}; @@ -23,6 +24,17 @@ where } } +impl FromContext for Address +where + S: Send + Sync, +{ + type Rejection = Response<()>; // Infallible + + async fn from_context(context: &HttpContext, _state: &S) -> Result { + Ok(context.peer.clone()) + } +} + impl FromContext for Uri where S: Send + Sync, diff --git a/volo-http/src/handler.rs b/volo-http/src/handler.rs index 8cf5a9be..0d8fa852 100644 --- a/volo-http/src/handler.rs +++ b/volo-http/src/handler.rs @@ -6,6 +6,7 @@ use motore::Service; use crate::{ extract::FromContext, + macros::{all_the_tuples, all_the_tuples_no_last_special_case}, request::FromRequest, response::{IntoResponse, RespBody}, DynError, DynService, HttpContext, @@ -79,31 +80,7 @@ macro_rules! impl_handler { }; } -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 -); +all_the_tuples!(impl_handler); // Use an extra trait with less generic types for hiding the type of handler pub struct DynHandler(Box>); @@ -272,3 +249,43 @@ where Ok(self.handler.clone().call(cx, req, &self.state).await) } } + +pub trait HandlerWithoutRequest: Sized { + fn call(self, context: &HttpContext) -> impl Future> + Send; +} + +impl HandlerWithoutRequest<()> for F +where + F: FnOnce() -> Res + Clone + Send, + Res: IntoResponse, +{ + async fn call(self, _context: &HttpContext) -> Response { + self().into_response() + } +} + +macro_rules! impl_handler_without_request { + ( + $($ty:ident),* $(,)? + ) => { + #[allow(non_snake_case, unused_mut, unused_variables)] + impl HandlerWithoutRequest<($($ty,)*)> for F + where + F: FnOnce($($ty,)*) -> Res + Clone + Send, + Res: IntoResponse, + $( for<'r> $ty: FromContext<()> + Send + 'r, )* + { + async fn call(self, context: &HttpContext) -> Response { + $( + let $ty = match $ty::from_context(context, &()).await { + Ok(value) => value, + Err(rejection) => return rejection.into_response(), + }; + )* + self($($ty,)*).into_response() + } + } + }; +} + +all_the_tuples_no_last_special_case!(impl_handler_without_request); diff --git a/volo-http/src/layer.rs b/volo-http/src/layer.rs index 43097f0f..93b3f199 100644 --- a/volo-http/src/layer.rs +++ b/volo-http/src/layer.rs @@ -1,4 +1,4 @@ -use std::time::Duration; +use std::{marker::PhantomData, time::Duration}; use http::{Method, Request, Response, StatusCode}; use http_body_util::Full; @@ -6,6 +6,7 @@ use hyper::body::{Bytes, Incoming}; use motore::{layer::Layer, service::Service}; use crate::{ + handler::HandlerWithoutRequest, response::{IntoResponse, RespBody}, HttpContext, }; @@ -93,50 +94,57 @@ where } #[derive(Clone)] -pub struct TimeoutLayer { +pub struct TimeoutLayer { duration: Duration, - handler: F, + handler: H, + _marker: PhantomData, } -impl TimeoutLayer { - pub fn new(duration: Duration, handler: F) -> Self +impl TimeoutLayer { + pub fn new(duration: Duration, handler: H) -> Self where - F: FnOnce(&HttpContext) -> T + Clone + Sync, - T: IntoResponse, + H: HandlerWithoutRequest + Clone + Send + Sync + 'static, { - Self { duration, handler } + Self { + duration, + handler, + _marker: PhantomData, + } } } -impl Layer for TimeoutLayer +impl Layer for TimeoutLayer where S: Service> + Send + Sync + 'static, - F: FnOnce(&HttpContext) -> T + Clone + Sync, - T: IntoResponse, + H: HandlerWithoutRequest + Clone + Send + Sync + 'static, + T: Sync, { - type Service = Timeout; + type Service = Timeout; fn layer(self, inner: S) -> Self::Service { Timeout { service: inner, duration: self.duration, handler: self.handler, + _marker: PhantomData, } } } #[derive(Clone)] -pub struct Timeout { +pub struct Timeout { service: S, duration: Duration, - handler: F, + handler: H, + _marker: PhantomData, } -impl Service for Timeout +impl Service for Timeout where S: Service> + Send + Sync + 'static, - F: FnOnce(&HttpContext) -> T + Clone + Sync, - T: IntoResponse, + S::Error: Send, + H: HandlerWithoutRequest + Clone + Send + Sync + 'static, + T: Sync, { type Response = S::Response; @@ -153,7 +161,7 @@ where tokio::select! { resp = fut_service => resp, _ = fut_timeout => { - Ok((self.handler.clone())(cx).into_response()) + Ok(self.handler.clone().call(cx).await.into_response()) }, } } diff --git a/volo-http/src/lib.rs b/volo-http/src/lib.rs index fa62b57e..68735579 100644 --- a/volo-http/src/lib.rs +++ b/volo-http/src/lib.rs @@ -7,10 +7,15 @@ pub mod response; pub mod route; pub mod server; -use http::{Extensions, HeaderMap, HeaderValue, Method, Uri, Version}; +mod macros; + +pub use bytes::Bytes; +use http::{Extensions, HeaderMap, HeaderValue, Version}; +pub use http::{Method, StatusCode, Uri}; use hyper::{body::Incoming, Response}; -use param::Params; -use volo::net::Address; +pub use volo::net::Address; + +pub use crate::{param::Params, request::Json, server::Server}; mod private { #[derive(Debug, Clone, Copy)] diff --git a/volo-http/src/macros.rs b/volo-http/src/macros.rs new file mode 100644 index 00000000..b4b20e96 --- /dev/null +++ b/volo-http/src/macros.rs @@ -0,0 +1,46 @@ +#[rustfmt::skip] +macro_rules! all_the_tuples { + ($name:ident) => { + $name!([], T1); + $name!([T1], T2); + $name!([T1, T2], T3); + $name!([T1, T2, T3], T4); + $name!([T1, T2, T3, T4], T5); + $name!([T1, T2, T3, T4, T5], T6); + $name!([T1, T2, T3, T4, T5, T6], T7); + $name!([T1, T2, T3, T4, T5, T6, T7], T8); + $name!([T1, T2, T3, T4, T5, T6, T7, T8], T9); + $name!([T1, T2, T3, T4, T5, T6, T7, T8, T9], T10); + $name!([T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T11); + $name!([T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11], T12); + $name!([T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12], T13); + $name!([T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13], T14); + $name!([T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14], T15); + $name!([T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15], T16); + }; +} + +#[rustfmt::skip] +macro_rules! all_the_tuples_no_last_special_case { + ($name:ident) => { + $name!(T1); + $name!(T1, T2); + $name!(T1, T2, T3); + $name!(T1, T2, T3, T4); + $name!(T1, T2, T3, T4, T5); + $name!(T1, T2, T3, T4, T5, T6); + $name!(T1, T2, T3, T4, T5, T6, T7); + $name!(T1, T2, T3, T4, T5, T6, T7, T8); + $name!(T1, T2, T3, T4, T5, T6, T7, T8, T9); + $name!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10); + $name!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11); + $name!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12); + $name!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13); + $name!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14); + $name!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15); + $name!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16); + }; +} + +pub(crate) use all_the_tuples; +pub(crate) use all_the_tuples_no_last_special_case;