1use crate::{Method, Request, Response};
2use rama_core::error::BoxErrorExt as _;
3use rama_core::{
4 Service,
5 error::{BoxError, ErrorExt},
6 extensions::{Extension, Extensions, ExtensionsRef},
7};
8use rama_http_headers::authorization::Credentials;
9use rama_http_types::StreamingBody;
10use rama_net::uri::Uri;
11
12pub trait HttpClientExt: private::HttpClientExtSealed + Sized + Send + Sync + 'static {
16 type ExecuteResponse;
18 type ExecuteError;
20
21 fn get(&self, url: impl IntoUrl) -> RequestBuilder<'_, Self, Self::ExecuteResponse>;
29
30 fn post(&self, url: impl IntoUrl) -> RequestBuilder<'_, Self, Self::ExecuteResponse>;
38
39 fn put(&self, url: impl IntoUrl) -> RequestBuilder<'_, Self, Self::ExecuteResponse>;
47
48 fn patch(&self, url: impl IntoUrl) -> RequestBuilder<'_, Self, Self::ExecuteResponse>;
56
57 fn delete(&self, url: impl IntoUrl) -> RequestBuilder<'_, Self, Self::ExecuteResponse>;
65
66 fn head(&self, url: impl IntoUrl) -> RequestBuilder<'_, Self, Self::ExecuteResponse>;
72
73 fn connect(&self, url: impl IntoUrl) -> RequestBuilder<'_, Self, Self::ExecuteResponse>;
79
80 fn request(
93 &self,
94 method: Method,
95 url: impl IntoUrl,
96 ) -> RequestBuilder<'_, Self, Self::ExecuteResponse>;
97
98 fn build_from_request<Body: Into<crate::Body>>(
103 &self,
104 request: Request<Body>,
105 ) -> RequestBuilder<'_, Self, Self::ExecuteResponse>;
106
107 fn execute(
113 &self,
114
115 request: Request,
116 ) -> impl Future<Output = Result<Self::ExecuteResponse, Self::ExecuteError>>;
117}
118
119impl<S, Body> HttpClientExt for S
120where
121 S: Service<Request, Output = Response<Body>, Error: Into<BoxError>>,
122{
123 type ExecuteResponse = Response<Body>;
124 type ExecuteError = S::Error;
125
126 fn get(&self, url: impl IntoUrl) -> RequestBuilder<'_, Self, Self::ExecuteResponse> {
127 self.request(Method::GET, url)
128 }
129
130 fn post(&self, url: impl IntoUrl) -> RequestBuilder<'_, Self, Self::ExecuteResponse> {
131 self.request(Method::POST, url)
132 }
133
134 fn put(&self, url: impl IntoUrl) -> RequestBuilder<'_, Self, Self::ExecuteResponse> {
135 self.request(Method::PUT, url)
136 }
137
138 fn patch(&self, url: impl IntoUrl) -> RequestBuilder<'_, Self, Self::ExecuteResponse> {
139 self.request(Method::PATCH, url)
140 }
141
142 fn delete(&self, url: impl IntoUrl) -> RequestBuilder<'_, Self, Self::ExecuteResponse> {
143 self.request(Method::DELETE, url)
144 }
145
146 fn head(&self, url: impl IntoUrl) -> RequestBuilder<'_, Self, Self::ExecuteResponse> {
147 self.request(Method::HEAD, url)
148 }
149
150 fn connect(&self, url: impl IntoUrl) -> RequestBuilder<'_, Self, Self::ExecuteResponse> {
151 self.request(Method::CONNECT, url)
152 }
153
154 fn request(
155 &self,
156 method: Method,
157 url: impl IntoUrl,
158 ) -> RequestBuilder<'_, Self, Self::ExecuteResponse> {
159 let uri = match url.into_url() {
160 Ok(uri) => uri,
161 Err(err) => {
162 return RequestBuilder {
163 http_client_service: self,
164 state: RequestBuilderState::Error(err),
165 _phantom: std::marker::PhantomData,
166 };
167 }
168 };
169
170 let builder = crate::request::Builder::new().method(method).uri(uri);
171
172 RequestBuilder {
173 http_client_service: self,
174 state: RequestBuilderState::PreBody(builder),
175 _phantom: std::marker::PhantomData,
176 }
177 }
178
179 fn build_from_request<RequestBody: Into<crate::Body>>(
180 &self,
181 request: Request<RequestBody>,
182 ) -> RequestBuilder<'_, Self, Self::ExecuteResponse> {
183 RequestBuilder {
184 http_client_service: self,
185 state: RequestBuilderState::PostBody(request.map(Into::into)),
186 _phantom: std::marker::PhantomData,
187 }
188 }
189
190 fn execute(
191 &self,
192
193 request: Request,
194 ) -> impl Future<Output = Result<Self::ExecuteResponse, Self::ExecuteError>> {
195 Service::serve(self, request)
196 }
197}
198
199pub trait IntoUrl: private::IntoUrlSealed {}
205
206impl IntoUrl for Uri {}
207impl IntoUrl for &str {}
208impl IntoUrl for String {}
209impl IntoUrl for &String {}
210
211pub trait IntoHeaderName: private::IntoHeaderNameSealed {}
217
218impl IntoHeaderName for crate::HeaderName {}
219impl IntoHeaderName for Option<crate::HeaderName> {}
220impl IntoHeaderName for &str {}
221impl IntoHeaderName for String {}
222impl IntoHeaderName for &String {}
223impl IntoHeaderName for &[u8] {}
224
225pub trait IntoHeaderValue: private::IntoHeaderValueSealed {}
231
232impl IntoHeaderValue for crate::HeaderValue {}
233impl IntoHeaderValue for &str {}
234impl IntoHeaderValue for String {}
235impl IntoHeaderValue for &String {}
236impl IntoHeaderValue for &[u8] {}
237
238mod private {
239
240 use rama_http_types::HeaderName;
241 use rama_net::Protocol;
242
243 use super::*;
244
245 pub trait IntoUrlSealed {
246 fn into_url(self) -> Result<Uri, BoxError>;
247 }
248
249 impl IntoUrlSealed for Uri {
250 fn into_url(self) -> Result<Uri, BoxError> {
251 let protocol: Option<Protocol> = self.scheme().cloned();
252 match protocol {
253 Some(protocol) => {
254 if protocol.is_http() || protocol.is_ws() {
255 Ok(self)
256 } else {
257 Err(BoxError::from_static_str("unsupported protocol")
258 .context_field("protocol", protocol))
259 }
260 }
261 None => Err(BoxError::from_static_str("Missing scheme in URI")),
262 }
263 }
264 }
265
266 impl IntoUrlSealed for &str {
267 fn into_url(self) -> Result<Uri, BoxError> {
268 match self.parse::<Uri>() {
269 Ok(uri) => uri.into_url(),
270 Err(_) => {
271 Err(BoxError::from_static_str("invalid url").context_str_field("raw_str", self))
272 }
273 }
274 }
275 }
276
277 impl IntoUrlSealed for String {
278 #[inline(always)]
279 fn into_url(self) -> Result<Uri, BoxError> {
280 self.as_str().into_url()
281 }
282 }
283
284 impl IntoUrlSealed for &String {
285 #[inline(always)]
286 fn into_url(self) -> Result<Uri, BoxError> {
287 self.as_str().into_url()
288 }
289 }
290
291 pub trait IntoHeaderNameSealed {
292 fn into_header_name(self) -> Result<crate::HeaderName, BoxError>;
293 }
294
295 impl IntoHeaderNameSealed for HeaderName {
296 fn into_header_name(self) -> Result<crate::HeaderName, BoxError> {
297 Ok(self)
298 }
299 }
300
301 impl IntoHeaderNameSealed for Option<HeaderName> {
302 fn into_header_name(self) -> Result<crate::HeaderName, BoxError> {
303 match self {
304 Some(name) => Ok(name),
305 None => Err(BoxError::from_static_str("Header name is required")),
306 }
307 }
308 }
309
310 impl IntoHeaderNameSealed for &str {
311 fn into_header_name(self) -> Result<crate::HeaderName, BoxError> {
312 let name = self.parse::<crate::HeaderName>()?;
313 Ok(name)
314 }
315 }
316
317 impl IntoHeaderNameSealed for String {
318 #[inline(always)]
319 fn into_header_name(self) -> Result<crate::HeaderName, BoxError> {
320 self.as_str().into_header_name()
321 }
322 }
323
324 impl IntoHeaderNameSealed for &String {
325 #[inline(always)]
326 fn into_header_name(self) -> Result<crate::HeaderName, BoxError> {
327 self.as_str().into_header_name()
328 }
329 }
330
331 impl IntoHeaderNameSealed for &[u8] {
332 fn into_header_name(self) -> Result<crate::HeaderName, BoxError> {
333 let name = crate::HeaderName::from_bytes(self)?;
334 Ok(name)
335 }
336 }
337
338 pub trait IntoHeaderValueSealed {
339 fn into_header_value(self) -> Result<crate::HeaderValue, BoxError>;
340 }
341
342 impl IntoHeaderValueSealed for crate::HeaderValue {
343 fn into_header_value(self) -> Result<crate::HeaderValue, BoxError> {
344 Ok(self)
345 }
346 }
347
348 impl IntoHeaderValueSealed for &str {
349 fn into_header_value(self) -> Result<crate::HeaderValue, BoxError> {
350 let value = self.parse::<crate::HeaderValue>()?;
351 Ok(value)
352 }
353 }
354
355 impl IntoHeaderValueSealed for String {
356 fn into_header_value(self) -> Result<crate::HeaderValue, BoxError> {
357 self.as_str().into_header_value()
358 }
359 }
360
361 impl IntoHeaderValueSealed for &String {
362 #[inline(always)]
363 fn into_header_value(self) -> Result<crate::HeaderValue, BoxError> {
364 self.as_str().into_header_value()
365 }
366 }
367
368 impl IntoHeaderValueSealed for &[u8] {
369 fn into_header_value(self) -> Result<crate::HeaderValue, BoxError> {
370 let value = crate::HeaderValue::from_bytes(self)?;
371 Ok(value)
372 }
373 }
374
375 pub trait HttpClientExtSealed {}
376
377 impl<S, Body> HttpClientExtSealed for S where
378 S: Service<Request, Output = Response<Body>, Error: Into<BoxError>>
379 {
380 }
381}
382
383pub struct RequestBuilder<'a, S, Response> {
387 http_client_service: &'a S,
388 state: RequestBuilderState,
389 _phantom: std::marker::PhantomData<fn(Response) -> ()>,
390}
391
392impl<S, Response> std::fmt::Debug for RequestBuilder<'_, S, Response>
393where
394 S: std::fmt::Debug,
395{
396 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
397 f.debug_struct("RequestBuilder")
398 .field("http_client_service", &self.http_client_service)
399 .field("state", &self.state)
400 .finish()
401 }
402}
403
404#[derive(Debug)]
405enum RequestBuilderState {
406 PreBody(crate::request::Builder),
407 PostBody(crate::Request),
408 Error(BoxError),
409}
410
411impl<S, Body> RequestBuilder<'_, S, Response<Body>>
412where
413 S: Service<Request, Output = Response<Body>, Error: Into<BoxError>>,
414{
415 #[must_use]
417 pub fn header<K, V>(mut self, key: K, value: V) -> Self
418 where
419 K: IntoHeaderName,
420 V: IntoHeaderValue,
421 {
422 match self.state {
423 RequestBuilderState::PreBody(builder) => {
424 let key = match key.into_header_name() {
425 Ok(key) => key,
426 Err(err) => {
427 self.state = RequestBuilderState::Error(err);
428 return self;
429 }
430 };
431 let value = match value.into_header_value() {
432 Ok(value) => value,
433 Err(err) => {
434 self.state = RequestBuilderState::Error(err);
435 return self;
436 }
437 };
438 self.state = RequestBuilderState::PreBody(builder.header(key, value));
439 self
440 }
441 RequestBuilderState::PostBody(mut request) => {
442 let key = match key.into_header_name() {
443 Ok(key) => key,
444 Err(err) => {
445 self.state = RequestBuilderState::Error(err);
446 return self;
447 }
448 };
449 let value = match value.into_header_value() {
450 Ok(value) => value,
451 Err(err) => {
452 self.state = RequestBuilderState::Error(err);
453 return self;
454 }
455 };
456 request.headers_mut().append(key, value);
457 self.state = RequestBuilderState::PostBody(request);
458 self
459 }
460 RequestBuilderState::Error(err) => {
461 self.state = RequestBuilderState::Error(err);
462 self
463 }
464 }
465 }
466
467 #[must_use]
471 pub fn typed_header<H>(self, header: H) -> Self
472 where
473 H: crate::headers::HeaderEncode,
474 {
475 if let Some(value) = header.encode_to_value() {
476 self.header(H::name().clone(), value)
477 } else {
478 self
479 }
480 }
481
482 #[must_use]
486 pub fn headers(mut self, headers: crate::HeaderMap) -> Self {
487 for (key, value) in headers.into_iter() {
488 self = self.header(key, value);
489 }
490 self
491 }
492
493 #[must_use]
495 pub fn overwrite_header<K, V>(mut self, key: K, value: V) -> Self
496 where
497 K: IntoHeaderName,
498 V: IntoHeaderValue,
499 {
500 match self.state {
501 RequestBuilderState::PreBody(mut builder) => {
502 if let Some(headers) = builder.headers_mut() {
504 let key = match key.into_header_name() {
505 Ok(key) => key,
506 Err(err) => {
507 self.state = RequestBuilderState::Error(err);
508 return self;
509 }
510 };
511 let value = match value.into_header_value() {
512 Ok(value) => value,
513 Err(err) => {
514 self.state = RequestBuilderState::Error(err);
515 return self;
516 }
517 };
518 _ = headers.insert(key, value);
519 }
520
521 self.state = RequestBuilderState::PreBody(builder);
522 self
523 }
524 RequestBuilderState::PostBody(mut request) => {
525 let key = match key.into_header_name() {
526 Ok(key) => key,
527 Err(err) => {
528 self.state = RequestBuilderState::Error(err);
529 return self;
530 }
531 };
532 let value = match value.into_header_value() {
533 Ok(value) => value,
534 Err(err) => {
535 self.state = RequestBuilderState::Error(err);
536 return self;
537 }
538 };
539 _ = request.headers_mut().insert(key, value);
540 self.state = RequestBuilderState::PostBody(request);
541 self
542 }
543 RequestBuilderState::Error(err) => {
544 self.state = RequestBuilderState::Error(err);
545 self
546 }
547 }
548 }
549
550 #[must_use]
554 pub fn overwrite_typed_header<H>(self, header: H) -> Self
555 where
556 H: crate::headers::HeaderEncode,
557 {
558 if let Some(value) = header.encode_to_value() {
559 self.overwrite_header(H::name().clone(), value)
560 } else {
561 self
562 }
563 }
564
565 #[must_use]
567 pub fn auth(self, credentials: impl Credentials) -> Self {
568 let header = crate::headers::Authorization::new(credentials);
569 self.typed_header(header)
570 }
571
572 #[must_use]
574 pub fn extension<T>(mut self, extension: T) -> Self
575 where
576 T: Extension,
577 {
578 match self.state {
579 RequestBuilderState::PreBody(builder) => {
580 let builder = builder.extension(extension);
581 self.state = RequestBuilderState::PreBody(builder);
582 self
583 }
584 RequestBuilderState::PostBody(request) => {
585 request.extensions().insert(extension);
586 self.state = RequestBuilderState::PostBody(request);
587 self
588 }
589 state @ RequestBuilderState::Error(_) => {
590 self.state = state;
591 self
592 }
593 }
594 }
595
596 pub fn extensions(&self) -> Option<&Extensions> {
601 match &self.state {
602 RequestBuilderState::PreBody(builder) => builder.extensions(),
603 RequestBuilderState::PostBody(request) => Some(request.extensions()),
604 RequestBuilderState::Error(_) => None,
605 }
606 }
607
608 #[must_use]
618 pub fn body<T>(mut self, body: T) -> Self
619 where
620 T: TryInto<crate::Body, Error: Into<BoxError>>,
621 {
622 self.state = match self.state {
623 RequestBuilderState::PreBody(mut builder) => match body.try_into() {
624 Ok(body) => {
625 sync_content_length_pre_body(&mut builder, &body);
626 match builder.body(body) {
627 Ok(req) => RequestBuilderState::PostBody(req),
628 Err(err) => RequestBuilderState::Error(BoxError::from(err)),
629 }
630 }
631 Err(err) => RequestBuilderState::Error(err.into()),
632 },
633 RequestBuilderState::PostBody(mut req) => match body.try_into() {
634 Ok(body) => {
635 sync_content_length_post_body(&mut req, &body);
636 *req.body_mut() = body;
637 RequestBuilderState::PostBody(req)
638 }
639 Err(err) => RequestBuilderState::Error(BoxError::from(err.into())),
640 },
641 RequestBuilderState::Error(err) => RequestBuilderState::Error(err),
642 };
643 self
644 }
645
646 #[must_use]
654 pub fn form<T: serde::Serialize + ?Sized>(mut self, form: &T) -> Self {
655 self.state = match self.state {
656 RequestBuilderState::PreBody(mut builder) => match serde_html_form::to_string(form) {
657 Ok(body) => {
658 let len = body.len() as u64;
659 let builder = match builder.headers_mut() {
660 Some(headers) => {
661 if !headers.contains_key(crate::header::CONTENT_TYPE) {
662 headers.insert(
663 crate::header::CONTENT_TYPE,
664 crate::HeaderValue::from_static(
665 "application/x-www-form-urlencoded",
666 ),
667 );
668 }
669 headers.insert(
670 crate::header::CONTENT_LENGTH,
671 crate::HeaderValue::from(len),
672 );
673 builder
674 }
675 None => builder
676 .header(
677 crate::header::CONTENT_TYPE,
678 crate::HeaderValue::from_static(
679 "application/x-www-form-urlencoded",
680 ),
681 )
682 .header(crate::header::CONTENT_LENGTH, crate::HeaderValue::from(len)),
683 };
684 match builder.body(body.into()) {
685 Ok(req) => RequestBuilderState::PostBody(req),
686 Err(err) => RequestBuilderState::Error(BoxError::from(err)),
687 }
688 }
689 Err(err) => RequestBuilderState::Error(BoxError::from(err)),
690 },
691 RequestBuilderState::PostBody(mut req) => match serde_html_form::to_string(form) {
692 Ok(body) => {
693 let len = body.len() as u64;
694 if !req.headers().contains_key(crate::header::CONTENT_TYPE) {
695 req.headers_mut().insert(
696 crate::header::CONTENT_TYPE,
697 crate::HeaderValue::from_static("application/x-www-form-urlencoded"),
698 );
699 }
700 req.headers_mut()
701 .insert(crate::header::CONTENT_LENGTH, crate::HeaderValue::from(len));
702 *req.body_mut() = body.into();
703 RequestBuilderState::PostBody(req)
704 }
705 Err(err) => RequestBuilderState::Error(BoxError::from(err)),
706 },
707 RequestBuilderState::Error(err) => RequestBuilderState::Error(err),
708 };
709 self
710 }
711
712 #[must_use]
720 pub fn json<T: serde::Serialize + ?Sized>(mut self, json: &T) -> Self {
721 self.state = match self.state {
722 RequestBuilderState::PreBody(mut builder) => match serde_json::to_vec(json) {
723 Ok(body) => {
724 let len = body.len() as u64;
725 let builder = match builder.headers_mut() {
726 Some(headers) => {
727 if !headers.contains_key(crate::header::CONTENT_TYPE) {
728 headers.insert(
729 crate::header::CONTENT_TYPE,
730 crate::HeaderValue::from_static("application/json"),
731 );
732 }
733 headers.insert(
734 crate::header::CONTENT_LENGTH,
735 crate::HeaderValue::from(len),
736 );
737 builder
738 }
739 None => builder
740 .header(
741 crate::header::CONTENT_TYPE,
742 crate::HeaderValue::from_static("application/json"),
743 )
744 .header(crate::header::CONTENT_LENGTH, crate::HeaderValue::from(len)),
745 };
746 match builder.body(body.into()) {
747 Ok(req) => RequestBuilderState::PostBody(req),
748 Err(err) => RequestBuilderState::Error(BoxError::from(err)),
749 }
750 }
751 Err(err) => RequestBuilderState::Error(BoxError::from(err)),
752 },
753 RequestBuilderState::PostBody(mut req) => match serde_json::to_vec(json) {
754 Ok(body) => {
755 let len = body.len() as u64;
756 if !req.headers().contains_key(crate::header::CONTENT_TYPE) {
757 req.headers_mut().insert(
758 crate::header::CONTENT_TYPE,
759 crate::HeaderValue::from_static("application/json"),
760 );
761 }
762 req.headers_mut()
763 .insert(crate::header::CONTENT_LENGTH, crate::HeaderValue::from(len));
764 *req.body_mut() = body.into();
765 RequestBuilderState::PostBody(req)
766 }
767 Err(err) => RequestBuilderState::Error(BoxError::from(err)),
768 },
769 RequestBuilderState::Error(err) => RequestBuilderState::Error(err),
770 };
771 self
772 }
773
774 #[cfg(feature = "multipart")]
782 #[must_use]
783 pub fn multipart(mut self, form: super::multipart::Form) -> Self {
784 let content_type = form.content_type();
785 let content_length = form.content_length();
786 let body = form.into_body();
787 self.state = match self.state {
788 RequestBuilderState::PreBody(mut builder) => {
789 if let Some(headers) = builder.headers_mut() {
790 headers.insert(crate::header::CONTENT_TYPE, content_type);
791 sync_content_length_in_map(headers, content_length);
792 }
793 match builder.body(body) {
796 Ok(req) => RequestBuilderState::PostBody(req),
797 Err(err) => RequestBuilderState::Error(BoxError::from(err)),
798 }
799 }
800 RequestBuilderState::PostBody(mut req) => {
801 let headers = req.headers_mut();
802 headers.insert(crate::header::CONTENT_TYPE, content_type);
803 sync_content_length_in_map(headers, content_length);
804 *req.body_mut() = body;
805 RequestBuilderState::PostBody(req)
806 }
807 RequestBuilderState::Error(err) => RequestBuilderState::Error(err),
808 };
809 self
810 }
811
812 #[must_use]
820 pub fn octet_stream<T: Into<rama_core::bytes::Bytes>>(mut self, bytes: T) -> Self {
821 let bytes = bytes.into();
822 let len = bytes.len() as u64;
823 self.state = match self.state {
824 RequestBuilderState::PreBody(mut builder) => {
825 let builder = match builder.headers_mut() {
826 Some(headers) => {
827 if !headers.contains_key(crate::header::CONTENT_TYPE) {
828 headers.insert(
829 crate::header::CONTENT_TYPE,
830 crate::HeaderValue::from_static("application/octet-stream"),
831 );
832 }
833 headers
834 .insert(crate::header::CONTENT_LENGTH, crate::HeaderValue::from(len));
835 builder
836 }
837 None => builder
838 .header(
839 crate::header::CONTENT_TYPE,
840 crate::HeaderValue::from_static("application/octet-stream"),
841 )
842 .header(crate::header::CONTENT_LENGTH, crate::HeaderValue::from(len)),
843 };
844 match builder.body(bytes.into()) {
845 Ok(req) => RequestBuilderState::PostBody(req),
846 Err(err) => RequestBuilderState::Error(BoxError::from(err)),
847 }
848 }
849 RequestBuilderState::PostBody(mut req) => {
850 if !req.headers().contains_key(crate::header::CONTENT_TYPE) {
851 req.headers_mut().insert(
852 crate::header::CONTENT_TYPE,
853 crate::HeaderValue::from_static("application/octet-stream"),
854 );
855 }
856 req.headers_mut()
857 .insert(crate::header::CONTENT_LENGTH, crate::HeaderValue::from(len));
858 *req.body_mut() = bytes.into();
859 RequestBuilderState::PostBody(req)
860 }
861 RequestBuilderState::Error(err) => RequestBuilderState::Error(err),
862 };
863 self
864 }
865
866 #[must_use]
870 pub fn version(mut self, version: crate::Version) -> Self {
871 match self.state {
872 RequestBuilderState::PreBody(builder) => {
873 self.state = RequestBuilderState::PreBody(builder.version(version));
874 self
875 }
876 RequestBuilderState::PostBody(mut request) => {
877 *request.version_mut() = version;
878 self.state = RequestBuilderState::PostBody(request);
879 self
880 }
881 RequestBuilderState::Error(err) => {
882 self.state = RequestBuilderState::Error(err);
883 self
884 }
885 }
886 }
887
888 pub async fn try_into_request(self) -> Result<Request, BoxError> {
894 Ok(match self.state {
895 RequestBuilderState::PreBody(builder) => builder.body(crate::Body::empty())?,
896 RequestBuilderState::PostBody(request) => request,
897 RequestBuilderState::Error(err) => return Err(err),
898 })
899 }
900
901 pub async fn send(self) -> Result<Response<Body>, BoxError> {
907 let request = match self.state {
908 RequestBuilderState::PreBody(builder) => builder.body(crate::Body::empty())?,
909 RequestBuilderState::PostBody(request) => request,
910 RequestBuilderState::Error(err) => return Err(err),
911 };
912
913 let uri = request.uri().clone();
914 match self.http_client_service.serve(request).await {
915 Ok(response) => Ok(response),
916 Err(err) => Err(err.into().context(uri)),
917 }
918 }
919}
920
921fn sync_content_length_pre_body(builder: &mut crate::request::Builder, body: &crate::Body) {
928 let Some(headers) = builder.headers_mut() else {
929 return;
930 };
931 let exact = body.size_hint().exact();
932 sync_content_length_in_map(headers, exact);
933}
934
935fn sync_content_length_post_body<B>(req: &mut crate::Request<B>, body: &crate::Body) {
938 let exact = body.size_hint().exact();
939 sync_content_length_in_map(req.headers_mut(), exact);
940}
941
942fn sync_content_length_in_map(headers: &mut crate::HeaderMap, exact: Option<u64>) {
943 match exact {
944 Some(len) => {
945 headers.insert(crate::header::CONTENT_LENGTH, crate::HeaderValue::from(len));
946 }
947 None => {
948 headers.remove(crate::header::CONTENT_LENGTH);
949 }
950 }
951}
952
953#[cfg(test)]
954mod test {
955 use rama_http_types::StatusCode;
956
957 use super::*;
958 use crate::{
959 StreamingBody,
960 layer::{
961 required_header::AddRequiredRequestHeadersLayer,
962 retry::{ManagedPolicy, RetryLayer},
963 trace::TraceLayer,
964 },
965 service::web::response::IntoResponse,
966 };
967 use rama_core::{
968 layer::{Layer, MapResultLayer},
969 service::{BoxService, service_fn},
970 };
971 use rama_utils::backoff::ExponentialBackoff;
972 use std::convert::Infallible;
973
974 async fn fake_client_fn<Body>(request: Request<Body>) -> Result<Response, Infallible>
975 where
976 Body: StreamingBody<Data: Send + 'static, Error: Send + 'static> + Send + 'static,
977 {
978 let ua = request.headers().get(crate::header::USER_AGENT).unwrap();
979 assert_eq!(
980 ua.to_str().unwrap(),
981 format!("{}/{}", rama_utils::info::NAME, rama_utils::info::VERSION)
982 );
983
984 Ok(StatusCode::OK.into_response())
985 }
986
987 fn map_internal_client_error<E, Body>(
988 result: Result<Response<Body>, E>,
989 ) -> Result<Response, rama_core::error::BoxError>
990 where
991 E: Into<rama_core::error::BoxError>,
992 Body: StreamingBody<Data = rama_core::bytes::Bytes, Error: Into<BoxError>>
993 + Send
994 + Sync
995 + 'static,
996 {
997 match result {
998 Ok(response) => Ok(response.map(crate::Body::new)),
999 Err(err) => Err(err.into()),
1000 }
1001 }
1002
1003 type HttpClient = BoxService<Request, Response, BoxError>;
1004
1005 fn client() -> HttpClient {
1006 let builder = (
1007 MapResultLayer::new(map_internal_client_error),
1008 TraceLayer::new_for_http(),
1009 );
1010
1011 #[cfg(feature = "compression")]
1012 let builder = (
1013 builder,
1014 crate::layer::decompression::DecompressionLayer::new(),
1015 );
1016
1017 (
1018 builder,
1019 RetryLayer::new(ManagedPolicy::default().with_backoff(ExponentialBackoff::default())),
1020 AddRequiredRequestHeadersLayer::default(),
1021 )
1022 .into_layer(service_fn(fake_client_fn))
1023 .boxed()
1024 }
1025
1026 #[tokio::test]
1027 async fn test_client_happy_path() {
1028 let response = client().get("http://127.0.0.1:8080").send().await.unwrap();
1029 assert_eq!(response.status(), StatusCode::OK);
1030 }
1031
1032 async fn echo_headers_client_fn(request: Request<crate::Body>) -> Result<Response, Infallible> {
1035 let mut headers_lines = Vec::new();
1036 for (name, value) in request.headers() {
1037 headers_lines.push(format!(
1038 "{}: {}",
1039 name.as_str(),
1040 value.to_str().unwrap_or("<bin>")
1041 ));
1042 }
1043 let body = headers_lines.join("\n");
1044 Ok(crate::Response::new(crate::Body::from(body)))
1045 }
1046
1047 fn echo_client() -> HttpClient {
1048 let builder = (
1049 MapResultLayer::new(map_internal_client_error),
1050 TraceLayer::new_for_http(),
1051 );
1052 (builder,)
1053 .into_layer(service_fn(echo_headers_client_fn))
1054 .boxed()
1055 }
1056
1057 async fn dump_headers(resp: Response) -> String {
1058 use crate::body::util::BodyExt as _;
1059 let bytes = resp.into_body().collect().await.unwrap().to_bytes();
1060 String::from_utf8(bytes.to_vec()).unwrap()
1061 }
1062
1063 #[tokio::test]
1064 async fn test_form_helper_sets_content_length_and_overwrites_stale() {
1065 let resp = echo_client()
1066 .post("http://x/")
1067 .header(crate::header::CONTENT_LENGTH, "9999") .form(&[("k", "vvv")])
1069 .send()
1070 .await
1071 .unwrap();
1072 let dump = dump_headers(resp).await;
1073 assert!(
1075 dump.contains("content-length: 5"),
1076 "expected content-length: 5 in:\n{dump}"
1077 );
1078 assert!(
1079 dump.contains("content-type: application/x-www-form-urlencoded"),
1080 "missing CT in:\n{dump}"
1081 );
1082 }
1083
1084 #[tokio::test]
1085 async fn test_json_helper_sets_content_length_and_overwrites_stale() {
1086 let resp = echo_client()
1087 .post("http://x/")
1088 .header(crate::header::CONTENT_LENGTH, "9999")
1089 .json(&serde_json::json!({"a": 1}))
1090 .send()
1091 .await
1092 .unwrap();
1093 let dump = dump_headers(resp).await;
1094 assert!(
1096 dump.contains("content-length: 7"),
1097 "expected content-length: 7 in:\n{dump}"
1098 );
1099 assert!(dump.contains("content-type: application/json"));
1100 }
1101
1102 #[tokio::test]
1103 async fn test_octet_stream_helper_sets_content_length_and_overwrites_stale() {
1104 let resp = echo_client()
1105 .post("http://x/")
1106 .header(crate::header::CONTENT_LENGTH, "9999")
1107 .octet_stream(b"abcd".as_slice())
1108 .send()
1109 .await
1110 .unwrap();
1111 let dump = dump_headers(resp).await;
1112 assert!(
1113 dump.contains("content-length: 4"),
1114 "expected content-length: 4 in:\n{dump}"
1115 );
1116 assert!(dump.contains("content-type: application/octet-stream"));
1117 }
1118
1119 #[tokio::test]
1120 async fn test_body_helper_clears_content_length_for_streaming_body() {
1121 use rama_core::futures::stream;
1124 let stream = stream::iter([Ok::<_, BoxError>(rama_core::bytes::Bytes::from_static(
1125 b"chunk",
1126 ))]);
1127 let body = crate::Body::from_stream(stream);
1128
1129 let resp = echo_client()
1130 .post("http://x/")
1131 .header(crate::header::CONTENT_LENGTH, "9999")
1132 .body(body)
1133 .send()
1134 .await
1135 .unwrap();
1136 let dump = dump_headers(resp).await;
1137 assert!(
1138 !dump.to_ascii_lowercase().contains("content-length:"),
1139 "stale content-length leaked into:\n{dump}"
1140 );
1141 }
1142}