1 // Copyright (c) 2023 Huawei Device Co., Ltd.
2 // Licensed under the Apache License, Version 2.0 (the "License");
3 // you may not use this file except in compliance with the License.
4 // You may obtain a copy of the License at
5 //
6 //     http://www.apache.org/licenses/LICENSE-2.0
7 //
8 // Unless required by applicable law or agreed to in writing, software
9 // distributed under the License is distributed on an "AS IS" BASIS,
10 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 // See the License for the specific language governing permissions and
12 // limitations under the License.
13 
14 use ylong_http::request::uri::Uri;
15 
16 use super::{Body, Connector, HttpBody, HttpConnector, Request, Response};
17 use crate::error::HttpClientError;
18 use crate::sync_impl::conn;
19 use crate::sync_impl::pool::ConnPool;
20 use crate::util::config::{
21     ClientConfig, ConnectorConfig, HttpConfig, HttpVersion, Proxy, Redirect, Timeout,
22 };
23 use crate::util::normalizer::RequestFormatter;
24 use crate::util::proxy::Proxies;
25 use crate::util::redirect::{RedirectInfo, Trigger};
26 
27 /// HTTP synchronous client implementation. Users can use `Client` to
28 /// send `Request` synchronously. `Client` depends on a `Connector` that
29 /// can be customized by the user.
30 ///
31 /// # Examples
32 ///
33 /// ```no_run
34 /// use ylong_http_client::sync_impl::{Client, EmptyBody, Request};
35 ///
36 /// // Creates a new `Client`.
37 /// let client = Client::new();
38 ///
39 /// // Creates a new `Request`.
40 /// let request = Request::new(EmptyBody);
41 ///
42 /// // Sends `Request` and block waiting for `Response` to return.
43 /// let response = client.request(request).unwrap();
44 ///
45 /// // Gets the content of `Response`.
46 /// let status = response.status();
47 /// ```
48 pub struct Client<C: Connector> {
49     inner: ConnPool<C, C::Stream>,
50     config: ClientConfig,
51 }
52 
53 impl Client<HttpConnector> {
54     /// Creates a new, default `Client`, which uses
55     /// [`sync_impl::HttpConnector`].
56     ///
57     /// [`sync_impl::HttpConnector`]: HttpConnector
58     ///
59     /// # Examples
60     ///
61     /// ```
62     /// use ylong_http_client::sync_impl::Client;
63     ///
64     /// let client = Client::new();
65     /// ```
newnull66     pub fn new() -> Self {
67         Self::with_connector(HttpConnector::default())
68     }
69 
70     /// Creates a new, default [`sync_impl::ClientBuilder`].
71     ///
72     /// [`sync_impl::ClientBuilder`]: ClientBuilder
73     ///
74     /// # Examples
75     ///
76     /// ```
77     /// use ylong_http_client::sync_impl::Client;
78     ///
79     /// let builder = Client::builder();
80     /// ```
buildernull81     pub fn builder() -> ClientBuilder {
82         ClientBuilder::new()
83     }
84 }
85 
86 impl<C: Connector> Client<C> {
87     /// Creates a new, default `Client` with a given connector.
with_connectornull88     pub fn with_connector(connector: C) -> Self {
89         Self {
90             inner: ConnPool::new(connector),
91             config: ClientConfig::new(),
92         }
93     }
94 
95     /// Sends HTTP Request synchronously. This method will block the current
96     /// thread until a `Response` is obtained or an error occurs.
97     ///
98     /// # Examples
99     ///
100     /// ```no_run
101     /// use ylong_http_client::sync_impl::{Client, EmptyBody, Request};
102     ///
103     /// let client = Client::new();
104     /// let response = client.request(Request::new(EmptyBody));
105     /// ```
requestnull106     pub fn request<T: Body>(
107         &self,
108         mut request: Request<T>,
109     ) -> Result<Response<HttpBody>, HttpClientError> {
110         RequestFormatter::new(&mut request).format()?;
111         self.retry_send_request(request)
112     }
113 
retry_send_requestnull114     fn retry_send_request<T: Body>(
115         &self,
116         mut request: Request<T>,
117     ) -> Result<Response<HttpBody>, HttpClientError> {
118         let mut retries = self.config.retry.times().unwrap_or(0);
119         loop {
120             let response = self.send_request_retryable(&mut request);
121             if response.is_ok() || retries == 0 {
122                 return response;
123             }
124             retries -= 1;
125         }
126     }
127 
send_request_retryablenull128     fn send_request_retryable<T: Body>(
129         &self,
130         request: &mut Request<T>,
131     ) -> Result<Response<HttpBody>, HttpClientError> {
132         let response = self.send_request_with_uri(request.uri().clone(), request)?;
133         self.redirect_request(response, request)
134     }
135 
redirect_requestnull136     fn redirect_request<T: Body>(
137         &self,
138         response: Response<HttpBody>,
139         request: &mut Request<T>,
140     ) -> Result<Response<HttpBody>, HttpClientError> {
141         let mut response = response;
142         let mut info = RedirectInfo::new();
143         loop {
144             match self
145                 .config
146                 .redirect
147                 .inner()
148                 .redirect(request, &mut response, &mut info)?
149             {
150                 Trigger::NextLink => {
151                     RequestFormatter::new(request).format()?;
152                     response = self.send_request_with_uri(request.uri().clone(), request)?;
153                 }
154                 Trigger::Stop => return Ok(response),
155             }
156         }
157     }
158 
send_request_with_urinull159     fn send_request_with_uri<T: Body>(
160         &self,
161         uri: Uri,
162         request: &mut Request<T>,
163     ) -> Result<Response<HttpBody>, HttpClientError> {
164         conn::request(self.inner.connect_to(uri)?, request)
165     }
166 }
167 
168 impl Default for Client<HttpConnector> {
defaultnull169     fn default() -> Self {
170         Self::new()
171     }
172 }
173 
174 /// A builder which is used to construct `sync_impl::Client`.
175 ///
176 /// # Examples
177 ///
178 /// ```
179 /// use ylong_http_client::sync_impl::ClientBuilder;
180 ///
181 /// let client = ClientBuilder::new().build();
182 /// ```
183 pub struct ClientBuilder {
184     /// Options and flags that is related to `HTTP`.
185     http: HttpConfig,
186 
187     /// Options and flags that is related to `Client`.
188     client: ClientConfig,
189 
190     /// Options and flags that is related to `Proxy`.
191     proxies: Proxies,
192 
193     /// Options and flags that is related to `TLS`.
194     #[cfg(feature = "__tls")]
195     tls: crate::util::TlsConfigBuilder,
196 }
197 
198 impl ClientBuilder {
199     /// Creates a new, default `ClientBuilder`.
200     ///
201     /// # Examples
202     ///
203     /// ```
204     /// use ylong_http_client::sync_impl::ClientBuilder;
205     ///
206     /// let builder = ClientBuilder::new();
207     /// ```
newnull208     pub fn new() -> Self {
209         Self {
210             http: HttpConfig::default(),
211             client: ClientConfig::default(),
212             proxies: Proxies::default(),
213 
214             #[cfg(feature = "__tls")]
215             tls: crate::util::TlsConfig::builder(),
216         }
217     }
218 
219     /// Only use HTTP/1.
220     ///
221     /// # Examples
222     ///
223     /// ```
224     /// use ylong_http_client::sync_impl::ClientBuilder;
225     ///
226     /// let builder = ClientBuilder::new().http1_only();
227     /// ```
http1_onlynull228     pub fn http1_only(mut self) -> Self {
229         self.http.version = HttpVersion::Http1;
230         self
231     }
232 
233     /// Enables a request timeout.
234     ///
235     /// The timeout is applied from when the request starts connection util the
236     /// response body has finished.
237     ///
238     /// # Examples
239     ///
240     /// ```
241     /// use ylong_http_client::sync_impl::ClientBuilder;
242     /// use ylong_http_client::Timeout;
243     ///
244     /// let builder = ClientBuilder::new().request_timeout(Timeout::none());
245     /// ```
request_timeoutnull246     pub fn request_timeout(mut self, timeout: Timeout) -> Self {
247         self.client.request_timeout = timeout;
248         self
249     }
250 
251     /// Sets a timeout for only the connect phase of `Client`.
252     ///
253     /// Default is `Timeout::none()`.
254     ///
255     /// # Examples
256     ///
257     /// ```
258     /// use ylong_http_client::sync_impl::ClientBuilder;
259     /// use ylong_http_client::Timeout;
260     ///
261     /// let builder = ClientBuilder::new().connect_timeout(Timeout::none());
262     /// ```
connect_timeoutnull263     pub fn connect_timeout(mut self, timeout: Timeout) -> Self {
264         self.client.connect_timeout = timeout;
265         self
266     }
267 
268     /// Sets a `Redirect` for this client.
269     ///
270     /// Default will follow redirects up to a maximum of 10.
271     ///
272     /// # Examples
273     ///
274     /// ```
275     /// use ylong_http_client::sync_impl::ClientBuilder;
276     /// use ylong_http_client::Redirect;
277     ///
278     /// let builder = ClientBuilder::new().redirect(Redirect::none());
279     /// ```
redirectnull280     pub fn redirect(mut self, redirect: Redirect) -> Self {
281         self.client.redirect = redirect;
282         self
283     }
284 
285     /// Adds a `Proxy` to the list of proxies the `Client` will use.
286     ///
287     /// # Examples
288     ///
289     /// ```
290     /// # use ylong_http_client::sync_impl::ClientBuilder;
291     /// # use ylong_http_client::{HttpClientError, Proxy};
292     ///
293     /// # fn add_proxy() -> Result<(), HttpClientError> {
294     /// let builder = ClientBuilder::new().proxy(Proxy::http("http://www.example.com").build()?);
295     /// # Ok(())
296     /// # }
proxynull297     pub fn proxy(mut self, proxy: Proxy) -> Self {
298         self.proxies.add_proxy(proxy.inner());
299         self
300     }
301 
302     /// Constructs a `Client` based on the given settings.
303     ///
304     /// # Examples
305     ///
306     /// ```
307     /// use ylong_http_client::sync_impl::ClientBuilder;
308     ///
309     /// let client = ClientBuilder::new().build();
310     /// ```
buildnull311     pub fn build(self) -> Result<Client<HttpConnector>, HttpClientError> {
312         let config = ConnectorConfig {
313             proxies: self.proxies,
314             #[cfg(feature = "__tls")]
315             tls: self.tls.build()?,
316         };
317 
318         let connector = HttpConnector::new(config);
319 
320         Ok(Client {
321             inner: ConnPool::new(connector),
322             config: self.client,
323         })
324     }
325 }
326 
327 #[cfg(feature = "__tls")]
328 impl ClientBuilder {
329     /// Sets the maximum allowed TLS version for connections.
330     ///
331     /// By default there's no maximum.
332     ///
333     /// # Examples
334     ///
335     /// ```
336     /// use ylong_http_client::sync_impl::ClientBuilder;
337     /// use ylong_http_client::TlsVersion;
338     ///
339     /// let builder = ClientBuilder::new().max_tls_version(TlsVersion::TLS_1_2);
340     /// ```
max_tls_versionnull341     pub fn max_tls_version(mut self, version: crate::util::TlsVersion) -> Self {
342         self.tls = self.tls.max_proto_version(version);
343         self
344     }
345 
346     /// Sets the minimum required TLS version for connections.
347     ///
348     /// By default the TLS backend's own default is used.
349     ///
350     /// # Examples
351     ///
352     /// ```
353     /// use ylong_http_client::sync_impl::ClientBuilder;
354     /// use ylong_http_client::TlsVersion;
355     ///
356     /// let builder = ClientBuilder::new().min_tls_version(TlsVersion::TLS_1_2);
357     /// ```
min_tls_versionnull358     pub fn min_tls_version(mut self, version: crate::util::TlsVersion) -> Self {
359         self.tls = self.tls.min_proto_version(version);
360         self
361     }
362 
363     /// Adds a custom root certificate.
364     ///
365     /// This can be used to connect to a server that has a self-signed.
366     /// certificate for example.
367     ///
368     /// # Examples
369     ///
370     /// ```
371     /// use ylong_http_client::sync_impl::ClientBuilder;
372     /// use ylong_http_client::Certificate;
373     ///
374     /// # fn set_cert(cert: Certificate) {
375     /// let builder = ClientBuilder::new().add_root_certificate(cert);
376     /// # }
377     /// ```
add_root_certificatenull378     pub fn add_root_certificate(mut self, certs: crate::util::Certificate) -> Self {
379         use crate::c_openssl::adapter::CertificateList;
380 
381         match certs.into_inner() {
382             CertificateList::CertList(c) => {
383                 self.tls = self.tls.add_root_certificates(c);
384             }
385             #[cfg(feature = "c_openssl_3_0")]
386             CertificateList::PathList(p) => {
387                 self.tls = self.tls.add_path_certificates(p);
388             }
389         }
390         self
391     }
392 
393     /// Loads trusted root certificates from a file. The file should contain a
394     /// sequence of PEM-formatted CA certificates.
395     ///
396     /// # Examples
397     ///
398     /// ```
399     /// use ylong_http_client::sync_impl::ClientBuilder;
400     ///
401     /// let builder = ClientBuilder::new().tls_ca_file("ca.crt");
402     /// ```
tls_ca_filenull403     pub fn tls_ca_file(mut self, path: &str) -> Self {
404         self.tls = self.tls.ca_file(path);
405         self
406     }
407 
408     /// Sets the list of supported ciphers for protocols before `TLSv1.3`.
409     ///
410     /// See [`ciphers`] for details on the format.
411     ///
412     /// [`ciphers`]: https://www.openssl.org/docs/man1.1.0/apps/ciphers.html
413     ///
414     /// # Examples
415     ///
416     /// ```
417     /// use ylong_http_client::sync_impl::ClientBuilder;
418     ///
419     /// let builder = ClientBuilder::new()
420     ///     .tls_cipher_list("DEFAULT:!aNULL:!eNULL:!MD5:!3DES:!DES:!RC4:!IDEA:!SEED:!aDSS:!SRP:!PSK");
421     /// ```
tls_cipher_listnull422     pub fn tls_cipher_list(mut self, list: &str) -> Self {
423         self.tls = self.tls.cipher_list(list);
424         self
425     }
426 
427     /// Controls the use of built-in system certificates during certificate
428     /// validation. Default to `true` -- uses built-in system certs.
429     ///
430     /// # Examples
431     ///
432     /// ```
433     /// use ylong_http_client::sync_impl::ClientBuilder;
434     ///
435     /// let builder = ClientBuilder::new().tls_built_in_root_certs(false);
436     /// ```
tls_built_in_root_certsnull437     pub fn tls_built_in_root_certs(mut self, is_use: bool) -> Self {
438         self.tls = self.tls.build_in_root_certs(is_use);
439         self
440     }
441 
442     /// Controls the use of certificates verification.
443     ///
444     /// Defaults to `false` -- verify certificates.
445     ///
446     /// # Warning
447     ///
448     /// When sets `true`, any certificate for any site will be trusted for use.
449     ///
450     /// # Examples
451     ///
452     /// ```
453     /// use ylong_http_client::sync_impl::ClientBuilder;
454     ///
455     /// let builder = ClientBuilder::new().danger_accept_invalid_certs(true);
456     /// ```
danger_accept_invalid_certsnull457     pub fn danger_accept_invalid_certs(mut self, is_invalid: bool) -> Self {
458         self.tls = self.tls.danger_accept_invalid_certs(is_invalid);
459         self
460     }
461 
462     /// Controls the use of hostname verification.
463     ///
464     /// Defaults to `false` -- verify hostname.
465     ///
466     /// # Warning
467     ///
468     /// When sets `true`, any valid certificate for any site will be trusted for
469     /// use from any other.
470     ///
471     /// # Examples
472     ///
473     /// ```
474     /// use ylong_http_client::sync_impl::ClientBuilder;
475     ///
476     /// let builder = ClientBuilder::new().danger_accept_invalid_hostnames(true);
477     /// ```
danger_accept_invalid_hostnamesnull478     pub fn danger_accept_invalid_hostnames(mut self, is_invalid: bool) -> Self {
479         self.tls = self.tls.danger_accept_invalid_hostnames(is_invalid);
480         self
481     }
482 
483     /// Controls the use of TLS server name indication.
484     ///
485     /// Defaults to `true` -- sets sni.
486     ///
487     /// # Examples
488     ///
489     /// ```
490     /// use ylong_http_client::sync_impl::ClientBuilder;
491     ///
492     /// let builder = ClientBuilder::new().tls_sni(true);
493     /// ```
tls_sninull494     pub fn tls_sni(mut self, is_set_sni: bool) -> Self {
495         self.tls = self.tls.sni(is_set_sni);
496         self
497     }
498 }
499 
500 impl Default for ClientBuilder {
defaultnull501     fn default() -> Self {
502         Self::new()
503     }
504 }
505 
506 #[cfg(test)]
507 mod ut_syn_client {
508     use ylong_http::body::TextBody;
509     use ylong_http::request::uri::Uri;
510     use ylong_http::request::Request;
511 
512     use crate::sync_impl::Client;
513 
514     /// UT test cases for `Client::request`.
515     ///
516     /// # Brief
517     /// 1. Creates a `Client` by calling `Client::new`.
518     /// 2. Calls `request`.
519     /// 3. Checks if the result is error.
520     #[test]
ut_request_client_errnull521     fn ut_request_client_err() {
522         let client = Client::new();
523         let reader = "Hello World";
524         let body = TextBody::from_bytes(reader.as_bytes());
525         let mut req = Request::new(body);
526         let request_uri = req.uri_mut();
527         *request_uri = Uri::from_bytes(b"http://_:80").unwrap();
528         let response = client.request(req);
529         assert!(response.is_err())
530     }
531 
532     /// UT test cases for `Client::new`.
533     ///
534     /// # Brief
535     /// 1. Creates a `Client` by calling `Client::new`.
536     /// 2. Calls `request`.
537     /// 3. Checks if the result is correct.
538     #[test]
ut_client_newnull539     fn ut_client_new() {
540         let _ = Client::default();
541         let _ = Client::new();
542     }
543 
544     /// UT test cases for `Client::builder`.
545     ///
546     /// # Brief
547     /// 1. Creates a `Client` by calling `Client::builder`.
548     /// 2. Calls `http_config`, `client_config`, `tls_config` and `build`
549     ///    respectively.
550     /// 3. Checks if the result is correct.
551     #[cfg(feature = "__tls")]
552     #[test]
ut_client_buildernull553     fn ut_client_builder() {
554         let builder = Client::builder().build();
555         assert!(builder.is_ok());
556     }
557 }
558