1use std::{
6 cell::RefCell,
7 error::Error,
8 future::Future,
9 sync::{Arc, LazyLock},
10 time::Duration,
11};
12
13use http::{
14 header::{HeaderMap, HeaderName, HeaderValue},
15 method::Method,
16 uri::Uri,
17};
18use metainfo::{METAINFO, MetaInfo};
19use motore::{
20 layer::{Identity, Layer, Stack},
21 service::{BoxService, Service},
22};
23use paste::paste;
24use volo::{
25 client::{MkClient, OneShotService},
26 context::Context,
27 loadbalance::MkLbLayer,
28 net::dial::{DefaultMakeTransport, MakeTransport},
29};
30
31use self::{
32 layer::{
33 Timeout,
34 header::{Host, UserAgent},
35 },
36 loadbalance::{DefaultLb, LbConfig},
37 transport::{
38 pool,
39 protocol::{ClientConfig, ClientTransport, ClientTransportConfig},
40 },
41};
42use crate::{
43 body::Body,
44 context::ClientContext,
45 error::{
46 BoxError, ClientError,
47 client::{Result, builder_error},
48 },
49 request::Request,
50 response::Response,
51};
52
53mod callopt;
54#[cfg(test)]
55mod client_tests;
56#[cfg(feature = "cookie")]
57pub mod cookie;
58pub mod dns;
59pub mod layer;
60pub mod loadbalance;
61#[cfg(feature = "multipart")]
62pub mod multipart;
63mod request_builder;
64pub mod sse;
65pub mod target;
66#[cfg(test)]
67pub mod test_helpers;
68pub mod transport;
69mod utils;
70
71pub use self::{
72 callopt::CallOpt, request_builder::RequestBuilder, target::Target, transport::protocol,
73};
74
75#[doc(hidden)]
76pub mod prelude {
77 pub use super::{Client, ClientBuilder};
78}
79
80pub struct ClientBuilder<IL = Identity, OL = Identity, C = DefaultMkClient, LB = DefaultLb> {
82 http_config: ClientConfig,
83 client_config: ClientTransportConfig,
84 pool_config: pool::Config,
85 connector: DefaultMakeTransport,
86 timeout: Option<Duration>,
87 user_agent: Option<HeaderValue>,
88 host_mode: Host,
89 headers: HeaderMap,
90 inner_layer: IL,
91 outer_layer: OL,
92 mk_client: C,
93 mk_lb: LB,
94 status: Result<()>,
95 #[cfg(feature = "__tls")]
96 tls_config: Option<volo::net::tls::TlsConnector>,
97}
98
99impl ClientBuilder<Identity, Identity, DefaultMkClient, DefaultLb> {
100 pub fn new() -> Self {
102 Self {
103 http_config: Default::default(),
104 client_config: Default::default(),
105 pool_config: pool::Config::default(),
106 connector: Default::default(),
107 timeout: None,
108 user_agent: None,
109 host_mode: Host::Auto,
110 headers: Default::default(),
111 inner_layer: Identity::new(),
112 outer_layer: Identity::new(),
113 mk_client: DefaultMkClient,
114 mk_lb: Default::default(),
115 status: Ok(()),
116 #[cfg(feature = "__tls")]
117 tls_config: None,
118 }
119 }
120}
121
122impl Default for ClientBuilder<Identity, Identity, DefaultMkClient, DefaultLb> {
123 fn default() -> Self {
124 Self::new()
125 }
126}
127
128impl<IL, OL, C, LB, DISC> ClientBuilder<IL, OL, C, LbConfig<LB, DISC>> {
129 pub fn load_balance<NLB>(
131 self,
132 load_balance: NLB,
133 ) -> ClientBuilder<IL, OL, C, LbConfig<NLB, DISC>> {
134 ClientBuilder {
135 http_config: self.http_config,
136 client_config: self.client_config,
137 pool_config: self.pool_config,
138 connector: self.connector,
139 timeout: self.timeout,
140 user_agent: self.user_agent,
141 host_mode: self.host_mode,
142 headers: self.headers,
143 inner_layer: self.inner_layer,
144 outer_layer: self.outer_layer,
145 mk_client: self.mk_client,
146 mk_lb: self.mk_lb.load_balance(load_balance),
147 status: self.status,
148 #[cfg(feature = "__tls")]
149 tls_config: self.tls_config,
150 }
151 }
152
153 pub fn discover<NDISC>(self, discover: NDISC) -> ClientBuilder<IL, OL, C, LbConfig<LB, NDISC>> {
155 ClientBuilder {
156 http_config: self.http_config,
157 client_config: self.client_config,
158 pool_config: self.pool_config,
159 connector: self.connector,
160 timeout: self.timeout,
161 user_agent: self.user_agent,
162 host_mode: self.host_mode,
163 headers: self.headers,
164 inner_layer: self.inner_layer,
165 outer_layer: self.outer_layer,
166 mk_client: self.mk_client,
167 mk_lb: self.mk_lb.discover(discover),
168 status: self.status,
169 #[cfg(feature = "__tls")]
170 tls_config: self.tls_config,
171 }
172 }
173}
174
175impl<IL, OL, C, LB> ClientBuilder<IL, OL, C, LB> {
176 #[doc(hidden)]
178 pub fn client_maker<C2>(self, new_mk_client: C2) -> ClientBuilder<IL, OL, C2, LB> {
179 ClientBuilder {
180 http_config: self.http_config,
181 client_config: self.client_config,
182 pool_config: self.pool_config,
183 connector: self.connector,
184 timeout: self.timeout,
185 user_agent: self.user_agent,
186 host_mode: self.host_mode,
187 headers: self.headers,
188 inner_layer: self.inner_layer,
189 outer_layer: self.outer_layer,
190 mk_client: new_mk_client,
191 mk_lb: self.mk_lb,
192 status: self.status,
193 #[cfg(feature = "__tls")]
194 tls_config: self.tls_config,
195 }
196 }
197
198 pub fn layer_inner<Inner>(self, layer: Inner) -> ClientBuilder<Stack<Inner, IL>, OL, C, LB> {
212 ClientBuilder {
213 http_config: self.http_config,
214 client_config: self.client_config,
215 pool_config: self.pool_config,
216 connector: self.connector,
217 timeout: self.timeout,
218 user_agent: self.user_agent,
219 host_mode: self.host_mode,
220 headers: self.headers,
221 inner_layer: Stack::new(layer, self.inner_layer),
222 outer_layer: self.outer_layer,
223 mk_client: self.mk_client,
224 mk_lb: self.mk_lb,
225 status: self.status,
226 #[cfg(feature = "__tls")]
227 tls_config: self.tls_config,
228 }
229 }
230
231 pub fn layer_inner_front<Inner>(
245 self,
246 layer: Inner,
247 ) -> ClientBuilder<Stack<IL, Inner>, OL, C, LB> {
248 ClientBuilder {
249 http_config: self.http_config,
250 client_config: self.client_config,
251 pool_config: self.pool_config,
252 connector: self.connector,
253 timeout: self.timeout,
254 user_agent: self.user_agent,
255 host_mode: self.host_mode,
256 headers: self.headers,
257 inner_layer: Stack::new(self.inner_layer, layer),
258 outer_layer: self.outer_layer,
259 mk_client: self.mk_client,
260 mk_lb: self.mk_lb,
261 status: self.status,
262 #[cfg(feature = "__tls")]
263 tls_config: self.tls_config,
264 }
265 }
266
267 pub fn layer_outer<Outer>(self, layer: Outer) -> ClientBuilder<IL, Stack<Outer, OL>, C, LB> {
281 ClientBuilder {
282 http_config: self.http_config,
283 client_config: self.client_config,
284 pool_config: self.pool_config,
285 connector: self.connector,
286 timeout: self.timeout,
287 user_agent: self.user_agent,
288 host_mode: self.host_mode,
289 headers: self.headers,
290 inner_layer: self.inner_layer,
291 outer_layer: Stack::new(layer, self.outer_layer),
292 mk_client: self.mk_client,
293 mk_lb: self.mk_lb,
294 status: self.status,
295 #[cfg(feature = "__tls")]
296 tls_config: self.tls_config,
297 }
298 }
299
300 pub fn layer_outer_front<Outer>(
314 self,
315 layer: Outer,
316 ) -> ClientBuilder<IL, Stack<OL, Outer>, C, LB> {
317 ClientBuilder {
318 http_config: self.http_config,
319 client_config: self.client_config,
320 pool_config: self.pool_config,
321 connector: self.connector,
322 timeout: self.timeout,
323 user_agent: self.user_agent,
324 host_mode: self.host_mode,
325 headers: self.headers,
326 inner_layer: self.inner_layer,
327 outer_layer: Stack::new(self.outer_layer, layer),
328 mk_client: self.mk_client,
329 mk_lb: self.mk_lb,
330 status: self.status,
331 #[cfg(feature = "__tls")]
332 tls_config: self.tls_config,
333 }
334 }
335
336 pub fn follow_redirects(
342 self,
343 max_redirects: usize,
344 ) -> ClientBuilder<IL, Stack<OL, layer::FollowRedirect>, C, LB> {
345 self.layer_outer_front(layer::FollowRedirect::new(max_redirects))
346 }
347
348 pub fn follow_redirects_when<P>(
354 self,
355 max_redirects: usize,
356 predicate: P,
357 ) -> ClientBuilder<IL, Stack<OL, layer::FollowRedirect<P>>, C, LB>
358 where
359 P: layer::RedirectPredicate,
360 {
361 self.layer_outer_front(layer::FollowRedirect::new(max_redirects).when(predicate))
362 }
363
364 pub fn mk_load_balance<NLB>(self, mk_load_balance: NLB) -> ClientBuilder<IL, OL, C, NLB> {
366 ClientBuilder {
367 http_config: self.http_config,
368 client_config: self.client_config,
369 pool_config: self.pool_config,
370 connector: self.connector,
371 timeout: self.timeout,
372 user_agent: self.user_agent,
373 host_mode: self.host_mode,
374 headers: self.headers,
375 inner_layer: self.inner_layer,
376 outer_layer: self.outer_layer,
377 mk_client: self.mk_client,
378 mk_lb: mk_load_balance,
379 status: self.status,
380 #[cfg(feature = "__tls")]
381 tls_config: self.tls_config,
382 }
383 }
384
385 pub fn header<K, V>(&mut self, key: K, value: V) -> &mut Self
387 where
388 K: TryInto<HeaderName>,
389 K::Error: Error + Send + Sync + 'static,
390 V: TryInto<HeaderValue>,
391 V::Error: Error + Send + Sync + 'static,
392 {
393 if self.status.is_err() {
394 return self;
395 }
396
397 if let Err(err) = insert_header(&mut self.headers, key, value) {
398 self.status = Err(err);
399 }
400 self
401 }
402
403 #[cfg(feature = "__tls")]
405 #[cfg_attr(docsrs, doc(cfg(any(feature = "rustls", feature = "native-tls"))))]
406 pub fn set_tls_config<T>(&mut self, tls_config: T) -> &mut Self
407 where
408 T: Into<volo::net::tls::TlsConnector>,
409 {
410 self.tls_config = Some(Into::into(tls_config));
411 self
412 }
413
414 pub fn headers(&self) -> &HeaderMap {
416 &self.headers
417 }
418
419 pub fn headers_mut(&mut self) -> &mut HeaderMap {
421 &mut self.headers
422 }
423
424 #[deprecated(
429 since = "0.4.0",
430 note = "`set_title_case_headers` has been removed into `http1_config`"
431 )]
432 #[cfg(feature = "http1")]
433 pub fn set_title_case_headers(&mut self, title_case_headers: bool) -> &mut Self {
434 self.http_config
435 .h1
436 .set_title_case_headers(title_case_headers);
437 self
438 }
439
440 #[deprecated(
454 since = "0.4.0",
455 note = "`set_max_headers` has been removed into `http1_config`"
456 )]
457 #[cfg(feature = "http1")]
458 pub fn set_max_headers(&mut self, max_headers: usize) -> &mut Self {
459 self.http_config.h1.set_max_headers(max_headers);
460 self
461 }
462
463 #[cfg(feature = "http1")]
465 pub fn http1_config(&mut self) -> &mut self::transport::http1::Config {
466 &mut self.http_config.h1
467 }
468
469 #[cfg(feature = "http2")]
471 pub fn http2_config(&mut self) -> &mut self::transport::http2::Config {
472 &mut self.http_config.h2
473 }
474
475 #[doc(hidden)]
477 pub fn stat_enable(&mut self, enable: bool) -> &mut Self {
478 self.client_config.stat_enable = enable;
479 self
480 }
481
482 #[cfg(feature = "__tls")]
486 #[cfg_attr(docsrs, doc(cfg(any(feature = "rustls", feature = "native-tls"))))]
487 pub fn disable_tls(&mut self, disable: bool) -> &mut Self {
488 self.client_config.disable_tls = disable;
489 self
490 }
491
492 pub fn set_pool_idle_timeout(&mut self, timeout: Duration) -> &mut Self {
498 self.pool_config.idle_timeout = timeout;
499 self
500 }
501
502 pub fn set_max_idle_per_host(&mut self, num: usize) -> &mut Self {
509 self.pool_config.max_idle_per_host = num;
510 self
511 }
512
513 pub fn set_connect_timeout(&mut self, timeout: Duration) -> &mut Self {
515 self.connector.set_connect_timeout(Some(timeout));
516 self
517 }
518
519 pub fn set_read_timeout(&mut self, timeout: Duration) -> &mut Self {
521 self.connector.set_read_timeout(Some(timeout));
522 self
523 }
524
525 pub fn set_write_timeout(&mut self, timeout: Duration) -> &mut Self {
527 self.connector.set_write_timeout(Some(timeout));
528 self
529 }
530
531 pub fn set_request_timeout(&mut self, timeout: Duration) -> &mut Self {
533 self.timeout = Some(timeout);
534 self
535 }
536
537 pub fn user_agent<V>(&mut self, val: V) -> &mut Self
542 where
543 V: TryInto<HeaderValue>,
544 V::Error: Error + Send + Sync + 'static,
545 {
546 if self.status.is_err() {
547 return self;
548 }
549 match val.try_into() {
550 Ok(val) => self.user_agent = Some(val),
551 Err(err) => self.status = Err(builder_error(err)),
552 }
553 self
554 }
555
556 pub fn host_mode(&mut self, mode: Host) -> &mut Self {
566 self.host_mode = mode;
567 self
568 }
569
570 pub fn build<InnerReqBody, OuterReqBody, RespBody>(mut self) -> Result<C::Target>
593 where
594 IL: Layer<ClientTransport<InnerReqBody>>,
595 IL::Service: Send + Sync + 'static,
596 LB: MkLbLayer,
597 LB::Layer: Layer<IL::Service>,
598 <LB::Layer as Layer<IL::Service>>::Service: Send + Sync,
599 OL: Layer<<LB::Layer as Layer<IL::Service>>::Service>,
600 OL::Service: Service<
601 ClientContext,
602 Request<OuterReqBody>,
603 Response = Response<RespBody>,
604 Error = ClientError,
605 > + Send
606 + Sync
607 + 'static,
608 C: MkClient<Client<OuterReqBody, RespBody>>,
609 InnerReqBody: Send,
610 OuterReqBody: Send + 'static,
611 RespBody: Send,
612 {
613 let timeout_layer = Timeout;
614 let host_layer = self.host_mode.clone();
615 let ua_layer = match self.user_agent.take() {
616 Some(ua) => UserAgent::new(ua),
617 None => UserAgent::auto(),
618 };
619 self.layer_outer_front(ua_layer)
620 .layer_outer_front(host_layer)
621 .layer_outer_front(timeout_layer)
622 .build_without_extra_layers()
623 }
624
625 pub fn build_without_extra_layers<InnerReqBody, OuterReqBody, RespBody>(
632 self,
633 ) -> Result<C::Target>
634 where
635 IL: Layer<ClientTransport<InnerReqBody>>,
636 IL::Service: Send + Sync + 'static,
637 LB: MkLbLayer,
638 LB::Layer: Layer<IL::Service>,
639 <LB::Layer as Layer<IL::Service>>::Service: Send + Sync,
640 OL: Layer<<LB::Layer as Layer<IL::Service>>::Service>,
641 OL::Service: Service<
642 ClientContext,
643 Request<OuterReqBody>,
644 Response = Response<RespBody>,
645 Error = ClientError,
646 > + Send
647 + Sync
648 + 'static,
649 C: MkClient<Client<OuterReqBody, RespBody>>,
650 InnerReqBody: Send,
651 OuterReqBody: Send + 'static,
652 RespBody: Send,
653 {
654 self.status?;
655
656 let transport = ClientTransport::new(
657 self.http_config,
658 self.client_config,
659 self.pool_config,
660 #[cfg(feature = "__tls")]
661 self.tls_config,
662 );
663 let service = self
664 .outer_layer
665 .layer(self.mk_lb.make().layer(self.inner_layer.layer(transport)));
666 let service = BoxService::new(service);
667
668 let client_inner = ClientInner {
669 service,
670 timeout: self.timeout,
671 headers: self.headers,
672 };
673 let client = Client {
674 inner: Arc::new(client_inner),
675 };
676 Ok(self.mk_client.mk_client(client))
677 }
678}
679
680fn insert_header<K, V>(headers: &mut HeaderMap, key: K, value: V) -> Result<()>
681where
682 K: TryInto<HeaderName>,
683 K::Error: Error + Send + Sync + 'static,
684 V: TryInto<HeaderValue>,
685 V::Error: Error + Send + Sync + 'static,
686{
687 headers.insert(
688 key.try_into().map_err(builder_error)?,
689 value.try_into().map_err(builder_error)?,
690 );
691 Ok(())
692}
693
694struct ClientInner<ReqBody, RespBody> {
695 service: BoxService<ClientContext, Request<ReqBody>, Response<RespBody>, ClientError>,
696 timeout: Option<Duration>,
697 headers: HeaderMap,
698}
699
700pub struct Client<ReqBody = Body, RespBody = Body> {
721 inner: Arc<ClientInner<ReqBody, RespBody>>,
722}
723
724impl Default for Client {
725 fn default() -> Self {
726 ClientBuilder::default().build().unwrap()
727 }
728}
729
730impl<ReqBody, RespBody> Clone for Client<ReqBody, RespBody> {
731 fn clone(&self) -> Self {
732 Self {
733 inner: Arc::clone(&self.inner),
734 }
735 }
736}
737
738macro_rules! method_requests {
739 ($method:ident) => {
740 paste! {
741 #[doc = concat!("Create a request with `", stringify!([<$method:upper>]) ,"` method and the given `uri`.")]
742 pub fn [<$method:lower>]<U>(&self, uri: U) -> RequestBuilder<Self>
743 where
744 U: TryInto<Uri>,
745 U::Error: Into<BoxError>,
746 {
747 self.request(Method::[<$method:upper>], uri)
748 }
749 }
750 };
751}
752
753impl Client {
754 pub fn builder() -> ClientBuilder<Identity, Identity, DefaultMkClient, DefaultLb> {
756 ClientBuilder::new()
757 }
758}
759
760impl<ReqBody, RespBody> Client<ReqBody, RespBody> {
761 pub fn request_builder(&self) -> RequestBuilder<Self> {
763 RequestBuilder::new(self.clone())
764 }
765
766 pub fn request<U>(&self, method: Method, uri: U) -> RequestBuilder<Self>
768 where
769 U: TryInto<Uri>,
770 U::Error: Into<BoxError>,
771 {
772 RequestBuilder::new(self.clone()).method(method).uri(uri)
773 }
774
775 method_requests!(options);
776 method_requests!(get);
777 method_requests!(post);
778 method_requests!(put);
779 method_requests!(delete);
780 method_requests!(head);
781 method_requests!(trace);
782 method_requests!(connect);
783 method_requests!(patch);
784}
785
786impl<ReqBody, RespBody> OneShotService<ClientContext, Request<ReqBody>>
787 for Client<ReqBody, RespBody>
788where
789 ReqBody: Send,
790{
791 type Response = Response<RespBody>;
792 type Error = ClientError;
793
794 async fn call(
795 self,
796 cx: &mut ClientContext,
797 mut req: Request<ReqBody>,
798 ) -> Result<Self::Response, Self::Error> {
799 #[cfg(feature = "__tls")]
800 crate::client::layer::utils::update_request_extension(req.extensions_mut(), cx.target());
801
802 {
804 let config = cx.rpc_info_mut().config_mut();
805 if config.timeout().is_none() {
807 config.set_timeout(self.inner.timeout);
808 }
809 }
810
811 req.headers_mut().extend(self.inner.headers.clone());
813
814 let has_metainfo = METAINFO.try_with(|_| {}).is_ok();
816
817 let fut = self.inner.service.call(cx, req);
818
819 if has_metainfo {
820 fut.await
821 } else {
822 METAINFO.scope(RefCell::new(MetaInfo::default()), fut).await
823 }
824 }
825}
826
827impl<ReqBody, RespBody> Service<ClientContext, Request<ReqBody>> for Client<ReqBody, RespBody>
828where
829 ReqBody: Send,
830{
831 type Response = Response<RespBody>;
832 type Error = ClientError;
833
834 fn call(
835 &self,
836 cx: &mut ClientContext,
837 req: Request<ReqBody>,
838 ) -> impl Future<Output = Result<Self::Response, Self::Error>> + Send {
839 OneShotService::call(self.clone(), cx, req)
840 }
841}
842
843pub struct DefaultMkClient;
845
846impl<C> MkClient<C> for DefaultMkClient {
847 type Target = C;
848
849 fn mk_client(&self, service: C) -> Self::Target {
850 service
851 }
852}
853
854static CLIENT: LazyLock<Client> = LazyLock::new(Default::default);
855
856pub async fn get<U>(uri: U) -> Result<Response>
858where
859 U: TryInto<Uri>,
860 U::Error: Into<BoxError>,
861{
862 CLIENT.get(uri).send().await
863}