Skip to content

Commit a16ee6b

Browse files
committed
chore(volo-http): add test cases for generics and fix related issues
Signed-off-by: Yu Li <liyu.yukiteru@bytedance.com>
1 parent 7efebc1 commit a16ee6b

6 files changed

Lines changed: 263 additions & 15 deletions

File tree

volo-http/src/client/client_tests/http1_only.rs

Lines changed: 91 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,26 @@
88
// Find a website that support h2c.
99

1010
use std::{
11+
any::TypeId,
1112
collections::HashMap,
1213
future::Future,
1314
net::{IpAddr, Ipv4Addr, SocketAddr},
1415
time::Duration,
1516
};
1617

18+
use bytes::Bytes;
1719
use http::{header, status::StatusCode};
20+
use http_body_util::Full;
1821
use motore::service::Service;
1922
use volo::context::Context;
2023

21-
use super::{HttpBinResponse, HTTPBIN_GET, USER_AGENT_KEY, USER_AGENT_VAL};
24+
use super::{
25+
utils::{
26+
AutoBody, AutoBodyLayer, AutoFull, AutoFullLayer, DropBodyLayer, Nothing,
27+
RespBodyToFullLayer,
28+
},
29+
HttpBinResponse, HTTPBIN_GET, HTTPBIN_POST, USER_AGENT_KEY, USER_AGENT_VAL,
30+
};
2231
use crate::{
2332
body::{Body, BodyConversion},
2433
client::{
@@ -34,6 +43,87 @@ use crate::{
3443
utils::consts::HTTP_DEFAULT_PORT,
3544
};
3645

46+
#[tokio::test]
47+
async fn client_with_generics() {
48+
fn type_of<T: 'static>(_: &T) -> TypeId {
49+
TypeId::of::<T>()
50+
}
51+
52+
// Override default `ReqBody`, but the `ReqBody` is still implements `http_body::Body`
53+
{
54+
let client = Client::builder().build().unwrap();
55+
assert!(client
56+
.post(HTTPBIN_POST)
57+
.body(Full::new(Bytes::new()))
58+
.send()
59+
.await
60+
.is_ok());
61+
assert_eq!(TypeId::of::<Client<Full<Bytes>>>(), type_of(&client),);
62+
}
63+
// Override default `RespBody`, but the `RespBody` is still implements `http_body::Body`
64+
{
65+
let client = Client::builder()
66+
.layer_outer_front(RespBodyToFullLayer)
67+
.build()
68+
.unwrap();
69+
assert!(client.get(HTTPBIN_GET).send().await.is_ok());
70+
assert_eq!(TypeId::of::<Client<Body, Full<Bytes>>>(), type_of(&client),);
71+
}
72+
// Override default `ReqBody` through `Layer`. The `AutoBody` does not implement
73+
// `http_body::Body`, but the `AutoBodyLayer` will convert it to `volo_http::body::Body` and
74+
// use it.
75+
{
76+
let client = Client::builder()
77+
.layer_outer_front(AutoBodyLayer)
78+
.build()
79+
.unwrap();
80+
assert!(client
81+
.post(HTTPBIN_POST)
82+
.body(AutoBody)
83+
.send()
84+
.await
85+
.is_ok());
86+
assert_eq!(TypeId::of::<Client<AutoBody>>(), type_of(&client),);
87+
}
88+
// Override default `ReqBody` through `Layer`. The `AutoFull` does not implement
89+
// `http_body::Body`, but the `AutoFullLayer` will convert it to `Full<Bytes>` which implements
90+
// `http_body::Body` as its `InnerReqBody`.
91+
{
92+
let client = Client::builder()
93+
.layer_outer_front(AutoFullLayer)
94+
.build()
95+
.unwrap();
96+
assert!(client
97+
.post(HTTPBIN_POST)
98+
.body(AutoFull)
99+
.send()
100+
.await
101+
.is_ok());
102+
assert_eq!(TypeId::of::<Client<AutoFull>>(), type_of(&client),);
103+
}
104+
// Override default `RespBody` through `Layer`. The `RespBody` does not implement
105+
// `http_body::Body`, but the `DropBodyLayer` will drop `volo_http::body::Body` and put
106+
// `Nothing` to `Response`.
107+
{
108+
let client = Client::builder()
109+
.layer_outer_front(DropBodyLayer)
110+
.build()
111+
.unwrap();
112+
assert!(client.get(HTTPBIN_GET).send().await.is_ok());
113+
assert_eq!(TypeId::of::<Client<Body, Nothing>>(), type_of(&client),);
114+
}
115+
// Combine them
116+
{
117+
let client = Client::builder()
118+
.layer_outer_front(AutoFullLayer)
119+
.layer_outer_front(DropBodyLayer)
120+
.build()
121+
.unwrap();
122+
assert!(client.post(HTTPBIN_GET).body(AutoFull).send().await.is_ok());
123+
assert_eq!(TypeId::of::<Client<AutoFull, Nothing>>(), type_of(&client),);
124+
}
125+
}
126+
37127
#[cfg(feature = "json")]
38128
#[tokio::test]
39129
async fn simple_get() {

volo-http/src/client/client_tests/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,10 @@ use serde::Deserialize;
1010
mod http1_only;
1111
#[cfg(feature = "__tls")]
1212
mod tls;
13+
mod utils;
1314

1415
const HTTPBIN_GET: &str = "http://httpbin.org/get";
16+
const HTTPBIN_POST: &str = "http://httpbin.org/post";
1517
#[cfg(feature = "__tls")]
1618
const HTTPBIN_GET_HTTPS: &str = "https://httpbin.org/get";
1719
const USER_AGENT_KEY: &str = "User-Agent";
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
use bytes::Bytes;
2+
use http_body_util::{BodyExt, Full};
3+
use motore::{layer::Layer, service::Service};
4+
5+
use crate::{body::Body, request::Request, response::Response};
6+
7+
pub struct RespBodyToFullLayer;
8+
pub struct RespBodyToFullService<S>(S);
9+
10+
impl<S> Layer<S> for RespBodyToFullLayer {
11+
type Service = RespBodyToFullService<S>;
12+
13+
fn layer(self, inner: S) -> Self::Service {
14+
RespBodyToFullService(inner)
15+
}
16+
}
17+
18+
impl<Cx, ReqBody, RespBody, S> Service<Cx, Request<ReqBody>> for RespBodyToFullService<S>
19+
where
20+
Cx: Send,
21+
S: Service<Cx, Request<ReqBody>, Response = Response<RespBody>> + Sync,
22+
ReqBody: Send,
23+
RespBody: http_body::Body + Send,
24+
RespBody::Data: Send,
25+
RespBody::Error: std::fmt::Debug + Send,
26+
{
27+
type Response = Response<Full<Bytes>>;
28+
type Error = S::Error;
29+
30+
async fn call(
31+
&self,
32+
cx: &mut Cx,
33+
req: Request<ReqBody>,
34+
) -> Result<Self::Response, Self::Error> {
35+
let resp = self.0.call(cx, req).await?;
36+
let (parts, body) = resp.into_parts();
37+
let body = Full::new(BodyExt::collect(body).await.unwrap().to_bytes());
38+
let resp = Response::from_parts(parts, body);
39+
Ok(resp)
40+
}
41+
}
42+
43+
// For request, and for testing, it should never implement `http_body::Body`
44+
pub struct AutoBody;
45+
46+
pub struct AutoBodyLayer;
47+
pub struct AutoBodyService<S>(S);
48+
49+
impl<S> Layer<S> for AutoBodyLayer {
50+
type Service = AutoBodyService<S>;
51+
52+
fn layer(self, inner: S) -> Self::Service {
53+
AutoBodyService(inner)
54+
}
55+
}
56+
57+
impl<Cx, RespBody, S> Service<Cx, Request<AutoBody>> for AutoBodyService<S>
58+
where
59+
Cx: Send,
60+
S: Service<Cx, Request, Response = Response<RespBody>> + Sync,
61+
RespBody: Send,
62+
{
63+
type Response = S::Response;
64+
type Error = S::Error;
65+
66+
async fn call(
67+
&self,
68+
cx: &mut Cx,
69+
req: Request<AutoBody>,
70+
) -> Result<Self::Response, Self::Error> {
71+
let (parts, _) = req.into_parts();
72+
let body = Body::from("Hello, World");
73+
let req = Request::from_parts(parts, body);
74+
self.0.call(cx, req).await
75+
}
76+
}
77+
78+
// For request, and for testing, it should never implement `http_body::Body`
79+
pub struct AutoFull;
80+
81+
pub struct AutoFullLayer;
82+
pub struct AutoFullService<S>(S);
83+
84+
impl<S> Layer<S> for AutoFullLayer {
85+
type Service = AutoFullService<S>;
86+
87+
fn layer(self, inner: S) -> Self::Service {
88+
AutoFullService(inner)
89+
}
90+
}
91+
92+
impl<Cx, RespBody, S> Service<Cx, Request<AutoFull>> for AutoFullService<S>
93+
where
94+
Cx: Send,
95+
S: Service<Cx, Request<Full<Bytes>>, Response = Response<RespBody>> + Sync,
96+
RespBody: Send,
97+
{
98+
type Response = S::Response;
99+
type Error = S::Error;
100+
101+
async fn call(
102+
&self,
103+
cx: &mut Cx,
104+
req: Request<AutoFull>,
105+
) -> Result<Self::Response, Self::Error> {
106+
let (parts, _) = req.into_parts();
107+
let body = Full::new(Bytes::new());
108+
let req = Request::from_parts(parts, body);
109+
self.0.call(cx, req).await
110+
}
111+
}
112+
113+
// For response, and for testing, it should never implement `http_body::Body`
114+
pub struct Nothing;
115+
116+
pub struct DropBodyLayer;
117+
pub struct DropBodyService<S>(S);
118+
119+
impl<S> Layer<S> for DropBodyLayer {
120+
type Service = DropBodyService<S>;
121+
122+
fn layer(self, inner: S) -> Self::Service {
123+
DropBodyService(inner)
124+
}
125+
}
126+
127+
impl<Cx, ReqBody, RespBody, S> Service<Cx, Request<ReqBody>> for DropBodyService<S>
128+
where
129+
Cx: Send,
130+
S: Service<Cx, Request<ReqBody>, Response = Response<RespBody>> + Sync,
131+
ReqBody: Send,
132+
RespBody: Send,
133+
{
134+
type Response = Response<Nothing>;
135+
type Error = S::Error;
136+
137+
async fn call(
138+
&self,
139+
cx: &mut Cx,
140+
req: Request<ReqBody>,
141+
) -> Result<Self::Response, Self::Error> {
142+
let resp = self.0.call(cx, req).await?;
143+
let (parts, _) = resp.into_parts();
144+
let body = Nothing;
145+
let resp = Response::from_parts(parts, body);
146+
Ok(resp)
147+
}
148+
}

volo-http/src/client/mod.rs

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -656,24 +656,25 @@ impl<IL, OL, C, LB> ClientBuilder<IL, OL, C, LB> {
656656
/// - Transport through network or unix domain socket.
657657
///
658658
/// [`DnsResolver`]: crate::client::dns::DnsResolver
659-
pub fn build<ReqBody, RespBody>(mut self) -> Result<C::Target>
659+
pub fn build<InnerReqBody, OuterReqBody, RespBody>(mut self) -> Result<C::Target>
660660
where
661-
IL: Layer<ClientTransport<ReqBody>>,
661+
IL: Layer<ClientTransport<InnerReqBody>>,
662662
IL::Service: Send + Sync + 'static,
663663
LB: MkLbLayer,
664664
LB::Layer: Layer<IL::Service>,
665665
<LB::Layer as Layer<IL::Service>>::Service: Send + Sync,
666666
OL: Layer<<LB::Layer as Layer<IL::Service>>::Service>,
667667
OL::Service: Service<
668668
ClientContext,
669-
Request<ReqBody>,
669+
Request<OuterReqBody>,
670670
Response = Response<RespBody>,
671671
Error = ClientError,
672672
> + Send
673673
+ Sync
674674
+ 'static,
675-
C: MkClient<Client<ReqBody, RespBody>>,
676-
ReqBody: Send + 'static,
675+
C: MkClient<Client<OuterReqBody, RespBody>>,
676+
InnerReqBody: Send,
677+
OuterReqBody: Send + 'static,
677678
RespBody: Send,
678679
{
679680
let timeout_layer = Timeout;
@@ -697,24 +698,27 @@ impl<IL, OL, C, LB> ClientBuilder<IL, OL, C, LB> {
697698
/// default layers,
698699
///
699700
/// See [`ClientBuilder::build`] for more details.
700-
pub fn build_without_extra_layers<ReqBody, RespBody>(self) -> Result<C::Target>
701+
pub fn build_without_extra_layers<InnerReqBody, OuterReqBody, RespBody>(
702+
self,
703+
) -> Result<C::Target>
701704
where
702-
IL: Layer<ClientTransport<ReqBody>>,
705+
IL: Layer<ClientTransport<InnerReqBody>>,
703706
IL::Service: Send + Sync + 'static,
704707
LB: MkLbLayer,
705708
LB::Layer: Layer<IL::Service>,
706709
<LB::Layer as Layer<IL::Service>>::Service: Send + Sync,
707710
OL: Layer<<LB::Layer as Layer<IL::Service>>::Service>,
708711
OL::Service: Service<
709712
ClientContext,
710-
Request<ReqBody>,
713+
Request<OuterReqBody>,
711714
Response = Response<RespBody>,
712715
Error = ClientError,
713716
> + Send
714717
+ Sync
715718
+ 'static,
716-
C: MkClient<Client<ReqBody, RespBody>>,
717-
ReqBody: Send + 'static,
719+
C: MkClient<Client<OuterReqBody, RespBody>>,
720+
InnerReqBody: Send,
721+
OuterReqBody: Send + 'static,
718722
RespBody: Send,
719723
{
720724
self.status?;

volo-http/src/client/request_builder.rs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -394,10 +394,14 @@ impl<S, B> RequestBuilder<S, B> {
394394
}
395395

396396
/// Send the request and get the response.
397-
pub async fn send(mut self) -> Result<Response>
397+
pub async fn send<RespBody>(mut self) -> Result<Response<RespBody>>
398398
where
399-
S: OneShotService<ClientContext, Request<B>, Response = Response, Error = ClientError>
400-
+ Send
399+
S: OneShotService<
400+
ClientContext,
401+
Request<B>,
402+
Response = Response<RespBody>,
403+
Error = ClientError,
404+
> + Send
401405
+ Sync
402406
+ 'static,
403407
B: Send + 'static,

volo-http/src/client/transport/protocol.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ impl ClientTransportConfig {
6363
}
6464
}
6565

66-
pub struct ClientTransport<B> {
66+
pub struct ClientTransport<B = Body> {
6767
#[cfg(feature = "http1")]
6868
h1_client: conn::http1::Builder,
6969
#[cfg(feature = "http2")]

0 commit comments

Comments
 (0)