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 /// ``` new() -> Self66 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 /// ``` builder() -> ClientBuilder81 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_connector(connector: C) -> Self88 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 /// ``` request<T: Body>( &self, mut request: Request<T>, ) -> Result<Response<HttpBody>, HttpClientError>106 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_request<T: Body>( &self, mut request: Request<T>, ) -> Result<Response<HttpBody>, HttpClientError>114 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_retryable<T: Body>( &self, request: &mut Request<T>, ) -> Result<Response<HttpBody>, HttpClientError>128 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_request<T: Body>( &self, response: Response<HttpBody>, request: &mut Request<T>, ) -> Result<Response<HttpBody>, HttpClientError>136 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_uri<T: Body>( &self, uri: Uri, request: &mut Request<T>, ) -> Result<Response<HttpBody>, HttpClientError>159 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> { default() -> Self169 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 /// ``` new() -> Self208 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_only(mut self) -> Self228 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_timeout(mut self, timeout: Timeout) -> Self246 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_timeout(mut self, timeout: Timeout) -> Self263 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 /// ``` redirect(mut self, redirect: Redirect) -> Self280 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 /// # } proxy(mut self, proxy: Proxy) -> Self297 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 /// ``` build(self) -> Result<Client<HttpConnector>, HttpClientError>311 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_version(mut self, version: crate::util::TlsVersion) -> Self341 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_version(mut self, version: crate::util::TlsVersion) -> Self358 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_certificate(mut self, certs: crate::util::Certificate) -> Self378 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_file(mut self, path: &str) -> Self403 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_list(mut self, list: &str) -> Self422 pub fn tls_cipher_list(mut self, list: &str) -> Self { 423 self.tls = self.tls.cipher_list(list); 424 self 425 } 426 427 /// Sets the list of supported ciphers for the `TLSv1.3` protocol. 428 /// 429 /// The format consists of TLSv1.3 cipher suite names separated by `:` 430 /// characters in order of preference. 431 /// 432 /// Requires `OpenSSL 1.1.1` or `LibreSSL 3.4.0` or newer. 433 /// 434 /// # Examples 435 /// 436 /// ``` 437 /// use ylong_http_client::sync_impl::ClientBuilder; 438 /// 439 /// let builder = ClientBuilder::new().tls_cipher_suites( 440 /// "DEFAULT:!aNULL:!eNULL:!MD5:!3DES:!DES:!RC4:!IDEA:!SEED:!aDSS:!SRP:!PSK", 441 /// ); 442 /// ``` tls_cipher_suites(mut self, list: &str) -> Self443 pub fn tls_cipher_suites(mut self, list: &str) -> Self { 444 self.tls = self.tls.cipher_suites(list); 445 self 446 } 447 448 /// Controls the use of built-in system certificates during certificate 449 /// validation. Default to `true` -- uses built-in system certs. 450 /// 451 /// # Examples 452 /// 453 /// ``` 454 /// use ylong_http_client::sync_impl::ClientBuilder; 455 /// 456 /// let builder = ClientBuilder::new().tls_built_in_root_certs(false); 457 /// ``` tls_built_in_root_certs(mut self, is_use: bool) -> Self458 pub fn tls_built_in_root_certs(mut self, is_use: bool) -> Self { 459 self.tls = self.tls.build_in_root_certs(is_use); 460 self 461 } 462 463 /// Controls the use of certificates verification. 464 /// 465 /// Defaults to `false` -- verify certificates. 466 /// 467 /// # Warning 468 /// 469 /// When sets `true`, any certificate for any site will be trusted for use. 470 /// 471 /// # Examples 472 /// 473 /// ``` 474 /// use ylong_http_client::sync_impl::ClientBuilder; 475 /// 476 /// let builder = ClientBuilder::new().danger_accept_invalid_certs(true); 477 /// ``` danger_accept_invalid_certs(mut self, is_invalid: bool) -> Self478 pub fn danger_accept_invalid_certs(mut self, is_invalid: bool) -> Self { 479 self.tls = self.tls.danger_accept_invalid_certs(is_invalid); 480 self 481 } 482 483 /// Controls the use of hostname verification. 484 /// 485 /// Defaults to `false` -- verify hostname. 486 /// 487 /// # Warning 488 /// 489 /// When sets `true`, any valid certificate for any site will be trusted for 490 /// use from any other. 491 /// 492 /// # Examples 493 /// 494 /// ``` 495 /// use ylong_http_client::sync_impl::ClientBuilder; 496 /// 497 /// let builder = ClientBuilder::new().danger_accept_invalid_hostnames(true); 498 /// ``` danger_accept_invalid_hostnames(mut self, is_invalid: bool) -> Self499 pub fn danger_accept_invalid_hostnames(mut self, is_invalid: bool) -> Self { 500 self.tls = self.tls.danger_accept_invalid_hostnames(is_invalid); 501 self 502 } 503 504 /// Controls the use of TLS server name indication. 505 /// 506 /// Defaults to `true` -- sets sni. 507 /// 508 /// # Examples 509 /// 510 /// ``` 511 /// use ylong_http_client::sync_impl::ClientBuilder; 512 /// 513 /// let builder = ClientBuilder::new().tls_sni(true); 514 /// ``` tls_sni(mut self, is_set_sni: bool) -> Self515 pub fn tls_sni(mut self, is_set_sni: bool) -> Self { 516 self.tls = self.tls.sni(is_set_sni); 517 self 518 } 519 } 520 521 impl Default for ClientBuilder { default() -> Self522 fn default() -> Self { 523 Self::new() 524 } 525 } 526 527 #[cfg(test)] 528 mod ut_syn_client { 529 use ylong_http::body::TextBody; 530 use ylong_http::request::uri::Uri; 531 use ylong_http::request::Request; 532 533 use crate::sync_impl::Client; 534 535 /// UT test cases for `Client::request`. 536 /// 537 /// # Brief 538 /// 1. Creates a `Client` by calling `Client::new`. 539 /// 2. Calls `request`. 540 /// 3. Checks if the result is error. 541 #[test] ut_request_client_err()542 fn ut_request_client_err() { 543 let client = Client::new(); 544 let reader = "Hello World"; 545 let body = TextBody::from_bytes(reader.as_bytes()); 546 let mut req = Request::new(body); 547 let request_uri = req.uri_mut(); 548 *request_uri = Uri::from_bytes(b"http://_:80").unwrap(); 549 let response = client.request(req); 550 assert!(response.is_err()) 551 } 552 553 /// UT test cases for `Client::new`. 554 /// 555 /// # Brief 556 /// 1. Creates a `Client` by calling `Client::new`. 557 /// 2. Calls `request`. 558 /// 3. Checks if the result is correct. 559 #[test] ut_client_new()560 fn ut_client_new() { 561 let _ = Client::default(); 562 let _ = Client::new(); 563 } 564 565 /// UT test cases for `Client::builder`. 566 /// 567 /// # Brief 568 /// 1. Creates a `Client` by calling `Client::builder`. 569 /// 2. Calls `http_config`, `client_config`, `tls_config` and `build` 570 /// respectively. 571 /// 3. Checks if the result is correct. 572 #[cfg(feature = "__tls")] 573 #[test] ut_client_builder()574 fn ut_client_builder() { 575 let builder = Client::builder().build(); 576 assert!(builder.is_ok()); 577 } 578 } 579