Skip to main content

volo_http/client/
request_builder.rs

1//! Request builder for building a request and sending to server
2//!
3//! See [`RequestBuilder`] for more details.
4
5use std::{borrow::Cow, error::Error};
6
7use faststr::FastStr;
8use http::{
9    header::{HeaderMap, HeaderName, HeaderValue},
10    method::Method,
11    uri::{PathAndQuery, Scheme, Uri},
12    version::Version,
13};
14use motore::layer::Layer;
15use volo::{
16    client::{Apply, OneShotService, WithOptService},
17    net::Address,
18};
19
20use super::{CallOpt, insert_header, target::Target};
21use crate::{
22    body::Body,
23    context::ClientContext,
24    error::{
25        BoxError, ClientError,
26        client::{Result, builder_error},
27    },
28    request::Request,
29    response::Response,
30    utils::consts,
31};
32
33/// The builder for building a request.
34pub struct RequestBuilder<S, B = Body> {
35    inner: S,
36    target: Target,
37    version: Option<Version>,
38    request: Request<B>,
39    status: Result<()>,
40}
41
42impl<S> RequestBuilder<S> {
43    pub(super) fn new(inner: S) -> Self {
44        Self {
45            inner,
46            target: Default::default(),
47            version: None,
48            request: Request::default(),
49            status: Ok(()),
50        }
51    }
52
53    /// Set the request body.
54    pub fn data<D>(mut self, data: D) -> Self
55    where
56        D: TryInto<Body>,
57        D::Error: Error + Send + Sync + 'static,
58    {
59        if self.status.is_err() {
60            return self;
61        }
62
63        let body = match data.try_into() {
64            Ok(body) => body,
65            Err(err) => {
66                self.status = Err(builder_error(err));
67                return self;
68            }
69        };
70
71        let (parts, _) = self.request.into_parts();
72        self.request = Request::from_parts(parts, body);
73
74        self
75    }
76
77    /// Set the request body as json from object with [`Serialize`](serde::Serialize).
78    #[cfg(feature = "json")]
79    pub fn json<T>(mut self, json: &T) -> Self
80    where
81        T: serde::Serialize,
82    {
83        if self.status.is_err() {
84            return self;
85        }
86
87        let json = match crate::utils::json::serialize(json) {
88            Ok(json) => json,
89            Err(err) => {
90                self.status = Err(builder_error(err));
91                return self;
92            }
93        };
94
95        let (mut parts, _) = self.request.into_parts();
96        parts.headers.insert(
97            http::header::CONTENT_TYPE,
98            crate::utils::consts::APPLICATION_JSON,
99        );
100        self.request = Request::from_parts(parts, Body::from(json));
101
102        self
103    }
104
105    /// Set the request body as form from object with [`Serialize`](serde::Serialize).
106    #[cfg(feature = "form")]
107    pub fn form<T>(mut self, form: &T) -> Self
108    where
109        T: serde::Serialize,
110    {
111        if self.status.is_err() {
112            return self;
113        }
114
115        let form = match serde_urlencoded::to_string(form) {
116            Ok(form) => form,
117            Err(err) => {
118                self.status = Err(builder_error(err));
119                return self;
120            }
121        };
122
123        let (mut parts, _) = self.request.into_parts();
124        parts.headers.insert(
125            http::header::CONTENT_TYPE,
126            crate::utils::consts::APPLICATION_WWW_FORM_URLENCODED,
127        );
128        self.request = Request::from_parts(parts, Body::from(form));
129
130        self
131    }
132
133    /// Set the request body as `multipart/form-data` from a
134    /// [`Form`](crate::client::multipart::Form).
135    ///
136    /// This sets the `Content-Type` header to `multipart/form-data` with the boundary generated by
137    /// the form, and encodes all fields (including file/reader parts, which are streamed lazily)
138    /// into the request body.
139    #[cfg(feature = "multipart")]
140    pub fn multipart(mut self, form: crate::client::multipart::Form) -> Self {
141        if self.status.is_err() {
142            return self;
143        }
144
145        let content_type = form.content_type();
146        let (mut parts, _) = self.request.into_parts();
147        parts
148            .headers
149            .insert(http::header::CONTENT_TYPE, content_type);
150        self.request = Request::from_parts(parts, form.into_body());
151
152        self
153    }
154}
155
156impl<S, B> RequestBuilder<S, B> {
157    /// Set method for the request.
158    pub fn method(mut self, method: Method) -> Self {
159        *self.request.method_mut() = method;
160        self
161    }
162
163    /// Get a reference to method in the request.
164    pub fn method_ref(&self) -> &Method {
165        self.request.method()
166    }
167
168    /// Set uri for building request.
169    ///
170    /// The uri will be split into two parts scheme+host and path+query. The scheme and host can be
171    /// empty and it will be resolved as the target address. The path and query must exist and they
172    /// are used to build the request uri.
173    ///
174    /// Note that only path and query will be set to the request uri. For setting the full uri, use
175    /// `full_uri` instead.
176    pub fn uri<U>(mut self, uri: U) -> Self
177    where
178        U: TryInto<Uri>,
179        U::Error: Into<BoxError>,
180    {
181        if self.status.is_err() {
182            return self;
183        }
184        let uri = match uri.try_into() {
185            Ok(uri) => uri,
186            Err(err) => {
187                self.status = Err(builder_error(err));
188                return self;
189            }
190        };
191        if uri.host().is_some() {
192            match Target::from_uri(&uri) {
193                Ok(target) => self.target = target,
194                Err(err) => {
195                    self.status = Err(err);
196                    return self;
197                }
198            }
199        }
200        let rela_uri = uri
201            .path_and_query()
202            .map(PathAndQuery::to_owned)
203            .unwrap_or_else(|| PathAndQuery::from_static("/"))
204            .into();
205        *self.request.uri_mut() = rela_uri;
206
207        self
208    }
209
210    /// Set query for the uri in request from object with [`Serialize`](serde::Serialize).
211    #[cfg(feature = "query")]
212    pub fn set_query<T>(mut self, query: &T) -> Self
213    where
214        T: serde::Serialize,
215    {
216        if self.status.is_err() {
217            return self;
218        }
219        let query_str = match serde_urlencoded::to_string(query) {
220            Ok(query) => query,
221            Err(err) => {
222                self.status = Err(builder_error(err));
223                return self;
224            }
225        };
226
227        // We should keep path only without query
228        let path_str = self.request.uri().path();
229        let mut path = String::with_capacity(path_str.len() + 1 + query_str.len());
230        path.push_str(path_str);
231        path.push('?');
232        path.push_str(&query_str);
233        let Ok(uri) = Uri::from_maybe_shared(path) else {
234            // path part is from a valid uri, and the result of urlencoded must be valid.
235            unreachable!();
236        };
237
238        *self.request.uri_mut() = uri;
239
240        self
241    }
242
243    /// Get a reference to uri in the request.
244    pub fn uri_ref(&self) -> &Uri {
245        self.request.uri()
246    }
247
248    /// Set version of the HTTP request.
249    ///
250    /// If it is not set, the request will use HTTP/2 if it is enabled and supported by default.
251    pub fn version(mut self, version: Version) -> Self {
252        self.version = Some(version);
253        self
254    }
255
256    /// Get a reference to version in the request.
257    pub fn version_ref(&self) -> Option<Version> {
258        self.version
259    }
260
261    /// Insert a header into the request header map.
262    pub fn header<K, V>(mut self, key: K, value: V) -> Self
263    where
264        K: TryInto<HeaderName>,
265        K::Error: Error + Send + Sync + 'static,
266        V: TryInto<HeaderValue>,
267        V::Error: Error + Send + Sync + 'static,
268    {
269        if self.status.is_err() {
270            return self;
271        }
272
273        if let Err(err) = insert_header(self.request.headers_mut(), key, value) {
274            self.status = Err(err);
275        }
276
277        self
278    }
279
280    /// Get a reference to headers in the request.
281    pub fn headers(&self) -> &HeaderMap {
282        self.request.headers()
283    }
284
285    /// Get a mutable reference to headers in the request.
286    pub fn headers_mut(&mut self) -> &mut HeaderMap {
287        self.request.headers_mut()
288    }
289
290    /// Set target address for the request.
291    pub fn address<A>(mut self, address: A) -> Self
292    where
293        A: Into<Address>,
294    {
295        self.target = Target::from(address.into());
296        self
297    }
298
299    /// Set target host for the request.
300    ///
301    /// It uses http with port 80 by default.
302    ///
303    /// For setting scheme and port, use [`Self::with_scheme`] and [`Self::with_port`] after
304    /// specifying host.
305    pub fn host<H>(mut self, host: H) -> Self
306    where
307        H: Into<Cow<'static, str>>,
308    {
309        // SAFETY: using HTTP is safe
310        self.target = unsafe {
311            Target::new_host_unchecked(
312                Scheme::HTTP,
313                FastStr::from(host.into()),
314                consts::HTTP_DEFAULT_PORT,
315            )
316        };
317        self
318    }
319
320    /// Set scheme for target of the request.
321    pub fn with_scheme(mut self, scheme: Scheme) -> Self {
322        if self.status.is_err() {
323            return self;
324        }
325        if let Err(err) = self.target.set_scheme(scheme) {
326            self.status = Err(err);
327        }
328        self
329    }
330
331    /// Set port for target address of this request.
332    pub fn with_port(mut self, port: u16) -> Self {
333        if self.status.is_err() {
334            return self;
335        }
336        if let Err(err) = self.target.set_port(port) {
337            self.status = Err(err);
338        }
339        self
340    }
341
342    /// Get a reference to [`Target`].
343    pub fn target_ref(&self) -> &Target {
344        &self.target
345    }
346
347    /// Get a mutable reference to [`Target`].
348    pub fn target_mut(&mut self) -> &mut Target {
349        &mut self.target
350    }
351
352    /// Set a request body.
353    pub fn body<B2>(self, body: B2) -> RequestBuilder<S, B2> {
354        let (parts, _) = self.request.into_parts();
355        let request = Request::from_parts(parts, body);
356
357        RequestBuilder {
358            inner: self.inner,
359            target: self.target,
360            version: self.version,
361            request,
362            status: self.status,
363        }
364    }
365
366    /// Get a reference to body in the request.
367    pub fn body_ref(&self) -> &B {
368        self.request.body()
369    }
370
371    /// Add a new [`Layer`] to the front of request builder.
372    ///
373    /// Note that the [`Layer`] generated `Service` should be a [`OneShotService`].
374    pub fn layer<L>(self, layer: L) -> RequestBuilder<L::Service, B>
375    where
376        L: Layer<S>,
377    {
378        RequestBuilder {
379            inner: layer.layer(self.inner),
380            target: self.target,
381            version: self.version,
382            request: self.request,
383            status: self.status,
384        }
385    }
386
387    /// Apply a [`CallOpt`] to the request.
388    pub fn with_callopt(self, callopt: CallOpt) -> RequestBuilder<WithOptService<S, CallOpt>, B> {
389        self.layer(WithOptLayer::new(callopt))
390    }
391
392    fn set_version(&mut self) {
393        let ver = match self.version {
394            Some(ver) => ver,
395            None => {
396                // Use HTTP/1.1 by default
397                if cfg!(feature = "http1") {
398                    Version::HTTP_11
399                } else {
400                    Version::HTTP_2
401                }
402            }
403        };
404        *self.request.version_mut() = ver;
405    }
406
407    /// Send the request and get the response.
408    pub async fn send<RespBody>(mut self) -> Result<Response<RespBody>>
409    where
410        S: OneShotService<
411                ClientContext,
412                Request<B>,
413                Response = Response<RespBody>,
414                Error = ClientError,
415            > + Send
416            + Sync
417            + 'static,
418        B: Send + 'static,
419    {
420        self.set_version();
421        self.status?;
422
423        let mut cx = ClientContext::new();
424        self.target.apply(&mut cx)?;
425        self.inner.call(&mut cx, self.request).await
426    }
427}
428
429struct WithOptLayer {
430    opt: CallOpt,
431}
432
433impl WithOptLayer {
434    const fn new(opt: CallOpt) -> Self {
435        Self { opt }
436    }
437}
438
439impl<S> Layer<S> for WithOptLayer {
440    type Service = WithOptService<S, CallOpt>;
441
442    fn layer(self, inner: S) -> Self::Service {
443        WithOptService::new(inner, self.opt)
444    }
445}
446
447#[cfg(all(test, feature = "multipart"))]
448mod tests {
449    use std::future::Future;
450
451    use http::header::CONTENT_TYPE;
452    use motore::service::Service;
453
454    use super::*;
455    use crate::{
456        body::BodyConversion,
457        client::{Client, multipart::Form, test_helpers::MockTransport},
458    };
459
460    struct InspectMultipartRequest;
461
462    impl Service<ClientContext, Request> for InspectMultipartRequest {
463        type Response = Response;
464        type Error = ClientError;
465
466        fn call(
467            &self,
468            _: &mut ClientContext,
469            req: Request,
470        ) -> impl Future<Output = Result<Self::Response>> + Send {
471            async move {
472                assert_eq!(req.method(), Method::POST);
473                assert_eq!(req.uri(), "/upload");
474
475                let content_type = req
476                    .headers()
477                    .get(CONTENT_TYPE)
478                    .expect("multipart should set content-type")
479                    .to_str()
480                    .unwrap()
481                    .to_owned();
482                let boundary = content_type
483                    .strip_prefix("multipart/form-data; boundary=")
484                    .expect("content-type should include multipart boundary")
485                    .to_owned();
486
487                let (_, body) = req.into_parts();
488                let body = body.into_string().await.unwrap();
489                let expected = format!(
490                    "--{boundary}\r\nContent-Disposition: form-data; \
491                     name=\"field\"\r\n\r\nvalue\r\n--{boundary}--\r\n"
492                );
493                assert_eq!(body, expected);
494
495                Ok(Response::default())
496            }
497        }
498    }
499
500    #[tokio::test]
501    async fn multipart_sets_content_type_and_body() {
502        let client = Client::builder()
503            .mock(MockTransport::service(InspectMultipartRequest))
504            .unwrap();
505        let form = Form::new().text("field", "value");
506
507        client.post("/upload").multipart(form).send().await.unwrap();
508    }
509}