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 // TODO: Adapter, remove this later. 16 use ylong_http::response::Response; 17 18 use super::{Body, Connector, HttpBody, HttpConnector}; 19 use crate::error::HttpClientError; 20 use crate::sync_impl::conn; 21 use crate::sync_impl::pool::ConnPool; 22 use crate::util::normalizer::RequestFormatter; 23 use crate::util::proxy::Proxies; 24 use crate::util::redirect::TriggerKind; 25 use crate::util::{ 26 ClientConfig, ConnectorConfig, HttpConfig, HttpVersion, Proxy, Redirect, Timeout, 27 }; 28 use crate::Request; 29 30 /// HTTP synchronous client implementation. Users can use `Client` to 31 /// send `Request` synchronously. `Client` depends on a `Connector` that 32 /// can be customized by the user. 33 /// 34 /// # Examples 35 /// 36 /// ```no_run 37 /// use ylong_http_client::sync_impl::Client; 38 /// use ylong_http_client::{EmptyBody, Request}; 39 /// 40 /// // Creates a new `Client`. 41 /// let client = Client::new(); 42 /// 43 /// // Creates a new `Request`. 44 /// let request = Request::new(EmptyBody); 45 /// 46 /// // Sends `Request` and block waiting for `Response` to return. 47 /// let response = client.request(request).unwrap(); 48 /// 49 /// // Gets the content of `Response`. 50 /// let status = response.status(); 51 /// ``` 52 pub struct Client<C: Connector> { 53 inner: ConnPool<C, C::Stream>, 54 client_config: ClientConfig, 55 } 56 57 impl Client<HttpConnector> { 58 /// Creates a new, default `Client`, which uses 59 /// [`sync_impl::HttpConnector`]. 60 /// 61 /// [`sync_impl::HttpConnector`]: HttpConnector 62 /// 63 /// # Examples 64 /// 65 /// ``` 66 /// use ylong_http_client::sync_impl::Client; 67 /// 68 /// let client = Client::new(); 69 /// ``` new() -> Self70 pub fn new() -> Self { 71 Self::with_connector(HttpConnector::default()) 72 } 73 74 /// Creates a new, default [`sync_impl::ClientBuilder`]. 75 /// 76 /// [`sync_impl::ClientBuilder`]: ClientBuilder 77 /// 78 /// # Examples 79 /// 80 /// ``` 81 /// use ylong_http_client::sync_impl::Client; 82 /// 83 /// let builder = Client::builder(); 84 /// ``` builder() -> ClientBuilder85 pub fn builder() -> ClientBuilder { 86 ClientBuilder::new() 87 } 88 } 89 90 impl<C: Connector> Client<C> { 91 /// Creates a new, default `Client` with a given connector. with_connector(connector: C) -> Self92 pub fn with_connector(connector: C) -> Self { 93 Self { 94 inner: ConnPool::new(connector), 95 client_config: ClientConfig::new(), 96 } 97 } 98 99 /// Sends HTTP Request synchronously. This method will block the current 100 /// thread until a `Response` is obtained or an error occurs. 101 /// 102 /// # Examples 103 /// 104 /// ```no_run 105 /// use ylong_http_client::sync_impl::Client; 106 /// use ylong_http_client::{EmptyBody, Request}; 107 /// 108 /// let client = Client::new(); 109 /// let response = client.request(Request::new(EmptyBody)); 110 /// ``` request<T: Body>( &self, mut request: Request<T>, ) -> Result<Response<HttpBody>, HttpClientError>111 pub fn request<T: Body>( 112 &self, 113 mut request: Request<T>, 114 ) -> Result<Response<HttpBody>, HttpClientError> { 115 RequestFormatter::new(&mut request).normalize()?; 116 self.retry_send_request(request) 117 } 118 retry_send_request<T: Body>( &self, mut request: Request<T>, ) -> Result<Response<HttpBody>, HttpClientError>119 fn retry_send_request<T: Body>( 120 &self, 121 mut request: Request<T>, 122 ) -> Result<Response<HttpBody>, HttpClientError> { 123 let mut retries = self.client_config.retry.times().unwrap_or(0); 124 loop { 125 let response = self.send_request_retryable(&mut request); 126 if response.is_ok() || retries == 0 { 127 return response; 128 } 129 retries -= 1; 130 } 131 } 132 send_request_retryable<T: Body>( &self, request: &mut Request<T>, ) -> Result<Response<HttpBody>, HttpClientError>133 fn send_request_retryable<T: Body>( 134 &self, 135 request: &mut Request<T>, 136 ) -> Result<Response<HttpBody>, HttpClientError> { 137 let response = self.send_request_with_uri(request.uri().clone(), request)?; 138 self.redirect_request(response, request) 139 } 140 redirect_request<T: Body>( &self, mut response: Response<HttpBody>, request: &mut Request<T>, ) -> Result<Response<HttpBody>, HttpClientError>141 fn redirect_request<T: Body>( 142 &self, 143 mut response: Response<HttpBody>, 144 request: &mut Request<T>, 145 ) -> Result<Response<HttpBody>, HttpClientError> { 146 let mut redirected_list = vec![]; 147 let mut dst_uri = Uri::default(); 148 loop { 149 if Redirect::is_redirect(response.status().clone(), request) { 150 redirected_list.push(request.uri().clone()); 151 let trigger = Redirect::get_redirect( 152 &mut dst_uri, 153 &self.client_config.redirect, 154 &redirected_list, 155 &response, 156 request, 157 )?; 158 159 match trigger { 160 TriggerKind::NextLink => { 161 response = conn::request(self.inner.connect_to(dst_uri.clone())?, request)?; 162 continue; 163 } 164 TriggerKind::Stop => { 165 return Ok(response); 166 } 167 } 168 } else { 169 return Ok(response); 170 } 171 } 172 } 173 send_request_with_uri<T: Body>( &self, uri: Uri, request: &mut Request<T>, ) -> Result<Response<HttpBody>, HttpClientError>174 fn send_request_with_uri<T: Body>( 175 &self, 176 uri: Uri, 177 request: &mut Request<T>, 178 ) -> Result<Response<HttpBody>, HttpClientError> { 179 conn::request(self.inner.connect_to(uri)?, request) 180 } 181 } 182 183 impl Default for Client<HttpConnector> { default() -> Self184 fn default() -> Self { 185 Self::new() 186 } 187 } 188 189 /// A builder which is used to construct `sync_impl::Client`. 190 /// 191 /// # Examples 192 /// 193 /// ``` 194 /// use ylong_http_client::sync_impl::ClientBuilder; 195 /// 196 /// let client = ClientBuilder::new().build(); 197 /// ``` 198 pub struct ClientBuilder { 199 /// Options and flags that is related to `HTTP`. 200 http: HttpConfig, 201 202 /// Options and flags that is related to `Client`. 203 client: ClientConfig, 204 205 /// Options and flags that is related to `Proxy`. 206 proxies: Proxies, 207 208 /// Options and flags that is related to `TLS`. 209 #[cfg(feature = "__tls")] 210 tls: crate::util::TlsConfigBuilder, 211 } 212 213 impl ClientBuilder { 214 /// Creates a new, default `ClientBuilder`. 215 /// 216 /// # Examples 217 /// 218 /// ``` 219 /// use ylong_http_client::sync_impl::ClientBuilder; 220 /// 221 /// let builder = ClientBuilder::new(); 222 /// ``` new() -> Self223 pub fn new() -> Self { 224 Self { 225 http: HttpConfig::default(), 226 client: ClientConfig::default(), 227 proxies: Proxies::default(), 228 229 #[cfg(feature = "__tls")] 230 tls: crate::util::TlsConfig::builder(), 231 } 232 } 233 234 /// Only use HTTP/1. 235 /// 236 /// # Examples 237 /// 238 /// ``` 239 /// use ylong_http_client::sync_impl::ClientBuilder; 240 /// 241 /// let builder = ClientBuilder::new().http1_only(); 242 /// ``` http1_only(mut self) -> Self243 pub fn http1_only(mut self) -> Self { 244 self.http.version = HttpVersion::Http11; 245 self 246 } 247 248 /// Enables a request timeout. 249 /// 250 /// The timeout is applied from when the request starts connection util the 251 /// response body has finished. 252 /// 253 /// # Examples 254 /// 255 /// ``` 256 /// use ylong_http_client::sync_impl::ClientBuilder; 257 /// use ylong_http_client::Timeout; 258 /// 259 /// let builder = ClientBuilder::new().request_timeout(Timeout::none()); 260 /// ``` request_timeout(mut self, timeout: Timeout) -> Self261 pub fn request_timeout(mut self, timeout: Timeout) -> Self { 262 self.client.request_timeout = timeout; 263 self 264 } 265 266 /// Sets a timeout for only the connect phase of `Client`. 267 /// 268 /// Default is `Timeout::none()`. 269 /// 270 /// # Examples 271 /// 272 /// ``` 273 /// use ylong_http_client::sync_impl::ClientBuilder; 274 /// use ylong_http_client::Timeout; 275 /// 276 /// let builder = ClientBuilder::new().connect_timeout(Timeout::none()); 277 /// ``` connect_timeout(mut self, timeout: Timeout) -> Self278 pub fn connect_timeout(mut self, timeout: Timeout) -> Self { 279 self.client.connect_timeout = timeout; 280 self 281 } 282 283 /// Sets a `Redirect` for this client. 284 /// 285 /// Default will follow redirects up to a maximum of 10. 286 /// 287 /// # Examples 288 /// 289 /// ``` 290 /// use ylong_http_client::sync_impl::ClientBuilder; 291 /// use ylong_http_client::Redirect; 292 /// 293 /// let builder = ClientBuilder::new().redirect(Redirect::none()); 294 /// ``` redirect(mut self, redirect: Redirect) -> Self295 pub fn redirect(mut self, redirect: Redirect) -> Self { 296 self.client.redirect = redirect; 297 self 298 } 299 300 /// Adds a `Proxy` to the list of proxies the `Client` will use. 301 /// 302 /// # Examples 303 /// 304 /// ``` 305 /// # use ylong_http_client::sync_impl::ClientBuilder; 306 /// # use ylong_http_client::{HttpClientError, Proxy}; 307 /// 308 /// # fn add_proxy() -> Result<(), HttpClientError> { 309 /// let builder = ClientBuilder::new().proxy(Proxy::http("http://www.example.com").build()?); 310 /// # Ok(()) 311 /// # } proxy(mut self, proxy: Proxy) -> Self312 pub fn proxy(mut self, proxy: Proxy) -> Self { 313 self.proxies.add_proxy(proxy.inner()); 314 self 315 } 316 317 /// Constructs a `Client` based on the given settings. 318 /// 319 /// # Examples 320 /// 321 /// ``` 322 /// use ylong_http_client::sync_impl::ClientBuilder; 323 /// 324 /// let client = ClientBuilder::new().build(); 325 /// ``` build(self) -> Result<Client<HttpConnector>, HttpClientError>326 pub fn build(self) -> Result<Client<HttpConnector>, HttpClientError> { 327 let config = ConnectorConfig { 328 proxies: self.proxies, 329 #[cfg(feature = "__tls")] 330 tls: self.tls.build()?, 331 }; 332 333 let connector = HttpConnector::new(config); 334 335 Ok(Client { 336 inner: ConnPool::new(connector), 337 client_config: self.client, 338 }) 339 } 340 } 341 342 #[cfg(feature = "__tls")] 343 impl ClientBuilder { 344 /// Sets the maximum allowed TLS version for connections. 345 /// 346 /// By default there's no maximum. 347 /// 348 /// # Examples 349 /// 350 /// ``` 351 /// use ylong_http_client::sync_impl::ClientBuilder; 352 /// use ylong_http_client::TlsVersion; 353 /// 354 /// let builder = ClientBuilder::new().max_tls_version(TlsVersion::TLS_1_2); 355 /// ``` max_tls_version(mut self, version: crate::util::TlsVersion) -> Self356 pub fn max_tls_version(mut self, version: crate::util::TlsVersion) -> Self { 357 self.tls = self.tls.max_proto_version(version); 358 self 359 } 360 361 /// Sets the minimum required TLS version for connections. 362 /// 363 /// By default the TLS backend's own default is used. 364 /// 365 /// # Examples 366 /// 367 /// ``` 368 /// use ylong_http_client::sync_impl::ClientBuilder; 369 /// use ylong_http_client::TlsVersion; 370 /// 371 /// let builder = ClientBuilder::new().min_tls_version(TlsVersion::TLS_1_2); 372 /// ``` min_tls_version(mut self, version: crate::util::TlsVersion) -> Self373 pub fn min_tls_version(mut self, version: crate::util::TlsVersion) -> Self { 374 self.tls = self.tls.min_proto_version(version); 375 self 376 } 377 378 /// Adds a custom root certificate. 379 /// 380 /// This can be used to connect to a server that has a self-signed. 381 /// certificate for example. 382 /// 383 /// # Examples 384 /// 385 /// ``` 386 /// use ylong_http_client::sync_impl::ClientBuilder; 387 /// use ylong_http_client::Certificate; 388 /// 389 /// # fn set_cert(cert: Certificate) { 390 /// let builder = ClientBuilder::new().add_root_certificate(cert); 391 /// # } 392 /// ``` add_root_certificate(mut self, certs: crate::util::Certificate) -> Self393 pub fn add_root_certificate(mut self, certs: crate::util::Certificate) -> Self { 394 self.tls = self.tls.add_root_certificates(certs); 395 self 396 } 397 398 /// Loads trusted root certificates from a file. The file should contain a 399 /// sequence of PEM-formatted CA certificates. 400 /// 401 /// # Examples 402 /// 403 /// ``` 404 /// use ylong_http_client::sync_impl::ClientBuilder; 405 /// 406 /// let builder = ClientBuilder::new().tls_ca_file("ca.crt"); 407 /// ``` tls_ca_file(mut self, path: &str) -> Self408 pub fn tls_ca_file(mut self, path: &str) -> Self { 409 self.tls = self.tls.ca_file(path); 410 self 411 } 412 413 /// Sets the list of supported ciphers for protocols before `TLSv1.3`. 414 /// 415 /// See [`ciphers`] for details on the format. 416 /// 417 /// [`ciphers`]: https://www.openssl.org/docs/man1.1.0/apps/ciphers.html 418 /// 419 /// # Examples 420 /// 421 /// ``` 422 /// use ylong_http_client::sync_impl::ClientBuilder; 423 /// 424 /// let builder = ClientBuilder::new() 425 /// .tls_cipher_list("DEFAULT:!aNULL:!eNULL:!MD5:!3DES:!DES:!RC4:!IDEA:!SEED:!aDSS:!SRP:!PSK"); 426 /// ``` tls_cipher_list(mut self, list: &str) -> Self427 pub fn tls_cipher_list(mut self, list: &str) -> Self { 428 self.tls = self.tls.cipher_list(list); 429 self 430 } 431 432 /// Sets the list of supported ciphers for the `TLSv1.3` protocol. 433 /// 434 /// The format consists of TLSv1.3 cipher suite names separated by `:` 435 /// characters in order of preference. 436 /// 437 /// Requires `OpenSSL 1.1.1` or `LibreSSL 3.4.0` or newer. 438 /// 439 /// # Examples 440 /// 441 /// ``` 442 /// use ylong_http_client::sync_impl::ClientBuilder; 443 /// 444 /// let builder = ClientBuilder::new().tls_cipher_suites( 445 /// "DEFAULT:!aNULL:!eNULL:!MD5:!3DES:!DES:!RC4:!IDEA:!SEED:!aDSS:!SRP:!PSK", 446 /// ); 447 /// ``` tls_cipher_suites(mut self, list: &str) -> Self448 pub fn tls_cipher_suites(mut self, list: &str) -> Self { 449 self.tls = self.tls.cipher_suites(list); 450 self 451 } 452 453 /// Controls the use of built-in system certificates during certificate 454 /// validation. Default to `true` -- uses built-in system certs. 455 /// 456 /// # Examples 457 /// 458 /// ``` 459 /// use ylong_http_client::sync_impl::ClientBuilder; 460 /// 461 /// let builder = ClientBuilder::new().tls_built_in_root_certs(false); 462 /// ``` tls_built_in_root_certs(mut self, is_use: bool) -> Self463 pub fn tls_built_in_root_certs(mut self, is_use: bool) -> Self { 464 self.tls = self.tls.build_in_root_certs(is_use); 465 self 466 } 467 468 /// Controls the use of certificates verification. 469 /// 470 /// Defaults to `false` -- verify certificates. 471 /// 472 /// # Warning 473 /// 474 /// When sets `true`, any certificate for any site will be trusted for use. 475 /// 476 /// # Examples 477 /// 478 /// ``` 479 /// use ylong_http_client::sync_impl::ClientBuilder; 480 /// 481 /// let builder = ClientBuilder::new().danger_accept_invalid_certs(true); 482 /// ``` danger_accept_invalid_certs(mut self, is_invalid: bool) -> Self483 pub fn danger_accept_invalid_certs(mut self, is_invalid: bool) -> Self { 484 self.tls = self.tls.danger_accept_invalid_certs(is_invalid); 485 self 486 } 487 488 /// Controls the use of hostname verification. 489 /// 490 /// Defaults to `false` -- verify hostname. 491 /// 492 /// # Warning 493 /// 494 /// When sets `true`, any valid certificate for any site will be trusted for 495 /// use from any other. 496 /// 497 /// # Examples 498 /// 499 /// ``` 500 /// use ylong_http_client::sync_impl::ClientBuilder; 501 /// 502 /// let builder = ClientBuilder::new().danger_accept_invalid_hostnames(true); 503 /// ``` danger_accept_invalid_hostnames(mut self, is_invalid: bool) -> Self504 pub fn danger_accept_invalid_hostnames(mut self, is_invalid: bool) -> Self { 505 self.tls = self.tls.danger_accept_invalid_hostnames(is_invalid); 506 self 507 } 508 509 /// Controls the use of TLS server name indication. 510 /// 511 /// Defaults to `true` -- sets sni. 512 /// 513 /// # Examples 514 /// 515 /// ``` 516 /// use ylong_http_client::sync_impl::ClientBuilder; 517 /// 518 /// let builder = ClientBuilder::new().tls_sni(true); 519 /// ``` tls_sni(mut self, is_set_sni: bool) -> Self520 pub fn tls_sni(mut self, is_set_sni: bool) -> Self { 521 self.tls = self.tls.sni(is_set_sni); 522 self 523 } 524 } 525 526 impl Default for ClientBuilder { default() -> Self527 fn default() -> Self { 528 Self::new() 529 } 530 } 531 532 #[cfg(test)] 533 mod ut_syn_client { 534 use ylong_http::body::TextBody; 535 use ylong_http::request::uri::Uri; 536 use ylong_http::request::Request; 537 538 use crate::sync_impl::Client; 539 540 /// UT test cases for `Client::request`. 541 /// 542 /// # Brief 543 /// 1. Creates a `Client` by calling `Client::new`. 544 /// 2. Calls `request`. 545 /// 3. Checks if the result is error. 546 #[test] ut_request_client_err()547 fn ut_request_client_err() { 548 let client = Client::new(); 549 let reader = "Hello World"; 550 let body = TextBody::from_bytes(reader.as_bytes()); 551 let mut req = Request::new(body); 552 let request_uri = req.uri_mut(); 553 *request_uri = Uri::from_bytes(b"http://_:80").unwrap(); 554 let response = client.request(req); 555 assert!(response.is_err()) 556 } 557 558 /// UT test cases for `Client::new`. 559 /// 560 /// # Brief 561 /// 1. Creates a `Client` by calling `Client::new`. 562 /// 2. Calls `request`. 563 /// 3. Checks if the result is correct. 564 #[test] ut_client_new()565 fn ut_client_new() { 566 let _ = Client::default(); 567 let _ = Client::new(); 568 } 569 570 /// UT test cases for `Client::builder`. 571 /// 572 /// # Brief 573 /// 1. Creates a `Client` by calling `Client::builder`. 574 /// 2. Calls `http_config`, `client_config`, `tls_config` and `build` 575 /// respectively. 576 /// 3. Checks if the result is correct. 577 #[cfg(feature = "__tls")] 578 #[test] ut_client_builder()579 fn ut_client_builder() { 580 let builder = Client::builder().build(); 581 assert!(builder.is_ok()); 582 } 583 } 584