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