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::body::{ChunkBody, TextBody}; 15 use ylong_http::response::Response; 16 17 use super::{conn, Body, ConnPool, Connector, HttpBody, HttpConnector}; 18 use crate::async_impl::timeout::TimeoutFuture; 19 use crate::util::normalizer::{RequestFormatter, UriFormatter}; 20 use crate::util::proxy::Proxies; 21 use crate::util::redirect::TriggerKind; 22 use crate::util::{ClientConfig, ConnectorConfig, HttpConfig, HttpVersion, Redirect}; 23 #[cfg(feature = "http2")] 24 use crate::H2Config; 25 use crate::{sleep, timeout, ErrorKind, HttpClientError, Proxy, Request, Timeout, Uri}; 26 27 /// HTTP asynchronous client implementation. Users can use `async_impl::Client` 28 /// to send `Request` asynchronously. `async_impl::Client` depends on a 29 /// [`async_impl::Connector`] that can be customized by the user. 30 /// 31 /// [`async_impl::Connector`]: Connector 32 /// 33 /// # Examples 34 /// 35 /// ``` 36 /// use ylong_http_client::async_impl::Client; 37 /// use ylong_http_client::{EmptyBody, Request}; 38 /// 39 /// async fn async_client() { 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 wait for the `Response` to return asynchronously. 47 /// let response = client.request(request).await.unwrap(); 48 /// 49 /// // Gets the content of `Response`. 50 /// let status = response.status(); 51 /// } 52 /// ``` 53 pub struct Client<C: Connector> { 54 inner: ConnPool<C, C::Stream>, 55 client_config: ClientConfig, 56 http_config: HttpConfig, 57 } 58 59 impl Client<HttpConnector> { 60 /// Creates a new, default `AsyncClient`, which uses 61 /// [`async_impl::HttpConnector`]. 62 /// 63 /// [`async_impl::HttpConnector`]: HttpConnector 64 /// 65 /// # Examples 66 /// 67 /// ``` 68 /// use ylong_http_client::async_impl::Client; 69 /// 70 /// let client = Client::new(); 71 /// ``` new() -> Self72 pub fn new() -> Self { 73 Self::with_connector(HttpConnector::default()) 74 } 75 76 /// Creates a new, default [`async_impl::ClientBuilder`]. 77 /// 78 /// [`async_impl::ClientBuilder`]: ClientBuilder 79 /// 80 /// # Examples 81 /// 82 /// ``` 83 /// use ylong_http_client::async_impl::Client; 84 /// 85 /// let builder = Client::builder(); 86 /// ``` builder() -> ClientBuilder87 pub fn builder() -> ClientBuilder { 88 ClientBuilder::new() 89 } 90 } 91 92 impl<C: Connector> Client<C> { 93 /// Creates a new, default `AsyncClient` with a given connector. with_connector(connector: C) -> Self94 pub fn with_connector(connector: C) -> Self { 95 let http_config = HttpConfig::default(); 96 Self { 97 inner: ConnPool::new(http_config.clone(), connector), 98 client_config: ClientConfig::default(), 99 http_config, 100 } 101 } 102 103 /// Sends HTTP `Request` asynchronously. 104 /// 105 /// # Examples 106 /// 107 /// ``` 108 /// use ylong_http_client::async_impl::Client; 109 /// use ylong_http_client::{EmptyBody, Request}; 110 /// 111 /// async fn async_client() { 112 /// let client = Client::new(); 113 /// let response = client.request(Request::new(EmptyBody)).await; 114 /// } 115 /// ``` 116 // TODO: change result to `Response<HttpBody>` later. request<T: Body>( &self, request: Request<T>, ) -> Result<super::Response, HttpClientError>117 pub async fn request<T: Body>( 118 &self, 119 request: Request<T>, 120 ) -> Result<super::Response, HttpClientError> { 121 let (part, body) = request.into_parts(); 122 123 let content_length = part 124 .headers 125 .get("Content-Length") 126 .and_then(|v| v.to_str().ok()) 127 .and_then(|v| v.parse::<u64>().ok()) 128 .is_some(); 129 130 let transfer_encoding = part 131 .headers 132 .get("Transfer-Encoding") 133 .and_then(|v| v.to_str().ok()) 134 .map(|v| v.contains("chunked")) 135 .unwrap_or(false); 136 137 let response = match (content_length, transfer_encoding) { 138 (_, true) => { 139 let request = Request::from_raw_parts(part, ChunkBody::from_async_body(body)); 140 self.retry_send_request(request).await 141 } 142 (true, false) => { 143 let request = Request::from_raw_parts(part, TextBody::from_async_body(body)); 144 self.retry_send_request(request).await 145 } 146 (false, false) => { 147 let request = Request::from_raw_parts(part, body); 148 self.retry_send_request(request).await 149 } 150 }; 151 response.map(super::Response::new) 152 } 153 retry_send_request<T: Body>( &self, mut request: Request<T>, ) -> Result<Response<HttpBody>, HttpClientError>154 async fn retry_send_request<T: Body>( 155 &self, 156 mut request: Request<T>, 157 ) -> Result<Response<HttpBody>, HttpClientError> { 158 let mut retries = self.client_config.retry.times().unwrap_or(0); 159 loop { 160 let response = self.send_request_retryable(&mut request).await; 161 if response.is_ok() || retries == 0 { 162 return response; 163 } 164 retries -= 1; 165 } 166 } 167 send_request_retryable<T: Body>( &self, request: &mut Request<T>, ) -> Result<Response<HttpBody>, HttpClientError>168 async fn send_request_retryable<T: Body>( 169 &self, 170 request: &mut Request<T>, 171 ) -> Result<Response<HttpBody>, HttpClientError> { 172 let response = self 173 .send_request_with_uri(request.uri().clone(), request) 174 .await?; 175 self.redirect_request(response, request).await 176 } 177 redirect_request<T: Body>( &self, mut response: Response<HttpBody>, request: &mut Request<T>, ) -> Result<Response<HttpBody>, HttpClientError>178 async fn redirect_request<T: Body>( 179 &self, 180 mut response: Response<HttpBody>, 181 request: &mut Request<T>, 182 ) -> Result<Response<HttpBody>, HttpClientError> { 183 let mut redirected_list = vec![]; 184 let mut dst_uri = Uri::default(); 185 loop { 186 if Redirect::is_redirect(response.status().clone(), request) { 187 redirected_list.push(request.uri().clone()); 188 let trigger = Redirect::get_redirect( 189 &mut dst_uri, 190 &self.client_config.redirect, 191 &redirected_list, 192 &response, 193 request, 194 )?; 195 196 UriFormatter::new().format(&mut dst_uri)?; 197 let _ = request 198 .headers_mut() 199 .insert("Host", dst_uri.authority().unwrap().to_string().as_bytes()); 200 match trigger { 201 TriggerKind::NextLink => { 202 response = self.send_request_with_uri(dst_uri.clone(), request).await?; 203 continue; 204 } 205 TriggerKind::Stop => { 206 return Ok(response); 207 } 208 } 209 } else { 210 return Ok(response); 211 } 212 } 213 } 214 send_request_with_uri<T: Body>( &self, mut uri: Uri, request: &mut Request<T>, ) -> Result<Response<HttpBody>, HttpClientError>215 async fn send_request_with_uri<T: Body>( 216 &self, 217 mut uri: Uri, 218 request: &mut Request<T>, 219 ) -> Result<Response<HttpBody>, HttpClientError> { 220 UriFormatter::new().format(&mut uri)?; 221 RequestFormatter::new(request).normalize()?; 222 223 match self.http_config.version { 224 #[cfg(feature = "http2")] 225 HttpVersion::Http2PriorKnowledge => self.http2_request(uri, request).await, 226 HttpVersion::Http11 => { 227 let conn = if let Some(dur) = self.client_config.connect_timeout.inner() { 228 match timeout(dur, self.inner.connect_to(uri)).await { 229 Err(_elapsed) => { 230 return Err(HttpClientError::new_with_message( 231 ErrorKind::Timeout, 232 "Connect timeout", 233 )) 234 } 235 Ok(Ok(conn)) => conn, 236 Ok(Err(e)) => return Err(e), 237 } 238 } else { 239 self.inner.connect_to(uri).await? 240 }; 241 242 let mut retryable = Retryable::default(); 243 if let Some(timeout) = self.client_config.request_timeout.inner() { 244 TimeoutFuture { 245 timeout: Some(Box::pin(sleep(timeout))), 246 future: Box::pin(conn::request(conn, request, &mut retryable)), 247 } 248 .await 249 } else { 250 conn::request(conn, request, &mut retryable).await 251 } 252 } 253 } 254 } 255 256 #[cfg(feature = "http2")] http2_request<T: Body>( &self, uri: Uri, request: &mut Request<T>, ) -> Result<Response<HttpBody>, HttpClientError>257 async fn http2_request<T: Body>( 258 &self, 259 uri: Uri, 260 request: &mut Request<T>, 261 ) -> Result<Response<HttpBody>, HttpClientError> { 262 let mut retryable = Retryable::default(); 263 264 const RETRY: usize = 1; 265 let mut times = 0; 266 loop { 267 retryable.set_retry(false); 268 let conn = self.inner.connect_to(uri.clone()).await?; 269 let response = conn::request(conn, request, &mut retryable).await; 270 if retryable.retry() && times < RETRY { 271 times += 1; 272 continue; 273 } 274 return response; 275 } 276 } 277 } 278 279 impl Default for Client<HttpConnector> { default() -> Self280 fn default() -> Self { 281 Self::new() 282 } 283 } 284 285 #[derive(Default)] 286 pub(crate) struct Retryable { 287 #[cfg(feature = "http2")] 288 retry: bool, 289 } 290 291 #[cfg(feature = "http2")] 292 impl Retryable { set_retry(&mut self, retryable: bool)293 pub(crate) fn set_retry(&mut self, retryable: bool) { 294 self.retry = retryable 295 } 296 retry(&self) -> bool297 pub(crate) fn retry(&self) -> bool { 298 self.retry 299 } 300 } 301 302 /// A builder which is used to construct `async_impl::Client`. 303 /// 304 /// # Examples 305 /// 306 /// ``` 307 /// use ylong_http_client::async_impl::ClientBuilder; 308 /// 309 /// let client = ClientBuilder::new().build(); 310 /// ``` 311 pub struct ClientBuilder { 312 /// Options and flags that is related to `HTTP`. 313 http: HttpConfig, 314 315 /// Options and flags that is related to `Client`. 316 client: ClientConfig, 317 318 /// Options and flags that is related to `Proxy`. 319 proxies: Proxies, 320 321 /// Options and flags that is related to `TLS`. 322 #[cfg(feature = "__tls")] 323 tls: crate::util::TlsConfigBuilder, 324 } 325 326 impl ClientBuilder { 327 /// Creates a new, default `ClientBuilder`. 328 /// 329 /// # Examples 330 /// 331 /// ``` 332 /// use ylong_http_client::async_impl::ClientBuilder; 333 /// 334 /// let builder = ClientBuilder::new(); 335 /// ``` new() -> Self336 pub fn new() -> Self { 337 Self { 338 http: HttpConfig::default(), 339 client: ClientConfig::default(), 340 proxies: Proxies::default(), 341 342 #[cfg(feature = "__tls")] 343 tls: crate::util::TlsConfig::builder(), 344 } 345 } 346 347 /// Only use HTTP/1. 348 /// 349 /// # Examples 350 /// 351 /// ``` 352 /// use ylong_http_client::async_impl::ClientBuilder; 353 /// 354 /// let builder = ClientBuilder::new().http1_only(); 355 /// ``` http1_only(mut self) -> Self356 pub fn http1_only(mut self) -> Self { 357 self.http.version = HttpVersion::Http11; 358 self 359 } 360 361 /// Only use HTTP/2. 362 /// 363 /// # Examples 364 /// 365 /// ``` 366 /// use ylong_http_client::async_impl::ClientBuilder; 367 /// 368 /// let builder = ClientBuilder::new().http2_prior_knowledge(); 369 /// ``` 370 #[cfg(feature = "http2")] http2_prior_knowledge(mut self) -> Self371 pub fn http2_prior_knowledge(mut self) -> Self { 372 self.http.version = HttpVersion::Http2PriorKnowledge; 373 self 374 } 375 376 /// HTTP/2 settings. 377 /// 378 /// # Examples 379 /// 380 /// ``` 381 /// use ylong_http_client::async_impl::ClientBuilder; 382 /// use ylong_http_client::H2Config; 383 /// 384 /// let builder = ClientBuilder::new().http2_settings(H2Config::default()); 385 /// ``` 386 #[cfg(feature = "http2")] http2_settings(mut self, config: H2Config) -> Self387 pub fn http2_settings(mut self, config: H2Config) -> Self { 388 self.http.http2_config = config; 389 self 390 } 391 392 /// Enables a request timeout. 393 /// 394 /// The timeout is applied from when the request starts connection util the 395 /// response body has finished. 396 /// 397 /// # Examples 398 /// 399 /// ``` 400 /// use ylong_http_client::async_impl::ClientBuilder; 401 /// use ylong_http_client::Timeout; 402 /// 403 /// let builder = ClientBuilder::new().request_timeout(Timeout::none()); 404 /// ``` request_timeout(mut self, timeout: Timeout) -> Self405 pub fn request_timeout(mut self, timeout: Timeout) -> Self { 406 self.client.request_timeout = timeout; 407 self 408 } 409 410 /// Sets a timeout for only the connect phase of `Client`. 411 /// 412 /// Default is `Timeout::none()`. 413 /// 414 /// # Examples 415 /// 416 /// ``` 417 /// use ylong_http_client::async_impl::ClientBuilder; 418 /// use ylong_http_client::Timeout; 419 /// 420 /// let builder = ClientBuilder::new().connect_timeout(Timeout::none()); 421 /// ``` connect_timeout(mut self, timeout: Timeout) -> Self422 pub fn connect_timeout(mut self, timeout: Timeout) -> Self { 423 self.client.connect_timeout = timeout; 424 self 425 } 426 427 /// Sets a `Redirect` for this client. 428 /// 429 /// Default will follow redirects up to a maximum of 10. 430 /// 431 /// # Examples 432 /// 433 /// ``` 434 /// use ylong_http_client::async_impl::ClientBuilder; 435 /// use ylong_http_client::Redirect; 436 /// 437 /// let builder = ClientBuilder::new().redirect(Redirect::none()); 438 /// ``` redirect(mut self, redirect: Redirect) -> Self439 pub fn redirect(mut self, redirect: Redirect) -> Self { 440 self.client.redirect = redirect; 441 self 442 } 443 444 /// Adds a `Proxy` to the list of proxies the `Client` will use. 445 /// 446 /// # Examples 447 /// 448 /// ``` 449 /// # use ylong_http_client::async_impl::ClientBuilder; 450 /// # use ylong_http_client::{HttpClientError, Proxy}; 451 /// 452 /// # fn add_proxy() -> Result<(), HttpClientError> { 453 /// let builder = ClientBuilder::new().proxy(Proxy::http("http://www.example.com").build()?); 454 /// # Ok(()) 455 /// # } 456 /// ``` proxy(mut self, proxy: Proxy) -> Self457 pub fn proxy(mut self, proxy: Proxy) -> Self { 458 self.proxies.add_proxy(proxy.inner()); 459 self 460 } 461 462 /// Constructs a `Client` based on the given settings. 463 /// 464 /// # Examples 465 /// 466 /// ``` 467 /// use ylong_http_client::async_impl::ClientBuilder; 468 /// 469 /// let client = ClientBuilder::new().build(); 470 /// ``` build(self) -> Result<Client<HttpConnector>, HttpClientError>471 pub fn build(self) -> Result<Client<HttpConnector>, HttpClientError> { 472 let config = ConnectorConfig { 473 proxies: self.proxies, 474 #[cfg(feature = "__tls")] 475 tls: self.tls.build()?, 476 }; 477 478 let connector = HttpConnector::new(config); 479 480 Ok(Client { 481 inner: ConnPool::new(self.http.clone(), connector), 482 client_config: self.client, 483 http_config: self.http, 484 }) 485 } 486 } 487 488 #[cfg(feature = "__tls")] 489 impl ClientBuilder { 490 /// Sets the maximum allowed TLS version for connections. 491 /// 492 /// By default there's no maximum. 493 /// 494 /// # Examples 495 /// 496 /// ``` 497 /// use ylong_http_client::async_impl::ClientBuilder; 498 /// use ylong_http_client::TlsVersion; 499 /// 500 /// let builder = ClientBuilder::new().max_tls_version(TlsVersion::TLS_1_2); 501 /// ``` max_tls_version(mut self, version: crate::util::TlsVersion) -> Self502 pub fn max_tls_version(mut self, version: crate::util::TlsVersion) -> Self { 503 self.tls = self.tls.max_proto_version(version); 504 self 505 } 506 507 /// Sets the minimum required TLS version for connections. 508 /// 509 /// By default the TLS backend's own default is used. 510 /// 511 /// # Examples 512 /// 513 /// ``` 514 /// use ylong_http_client::async_impl::ClientBuilder; 515 /// use ylong_http_client::TlsVersion; 516 /// 517 /// let builder = ClientBuilder::new().min_tls_version(TlsVersion::TLS_1_2); 518 /// ``` min_tls_version(mut self, version: crate::util::TlsVersion) -> Self519 pub fn min_tls_version(mut self, version: crate::util::TlsVersion) -> Self { 520 self.tls = self.tls.min_proto_version(version); 521 self 522 } 523 524 /// Adds a custom root certificate. 525 /// 526 /// This can be used to connect to a server that has a self-signed. 527 /// certificate for example. 528 /// 529 /// # Examples 530 /// 531 /// ``` 532 /// use ylong_http_client::async_impl::ClientBuilder; 533 /// use ylong_http_client::Certificate; 534 /// 535 /// # fn set_cert(cert: Certificate) { 536 /// let builder = ClientBuilder::new().add_root_certificate(cert); 537 /// # } 538 /// ``` add_root_certificate(mut self, certs: crate::util::Certificate) -> Self539 pub fn add_root_certificate(mut self, certs: crate::util::Certificate) -> Self { 540 self.tls = self.tls.add_root_certificates(certs); 541 self 542 } 543 544 /// Loads trusted root certificates from a file. The file should contain a 545 /// sequence of PEM-formatted CA certificates. 546 /// 547 /// # Examples 548 /// 549 /// ``` 550 /// use ylong_http_client::async_impl::ClientBuilder; 551 /// 552 /// let builder = ClientBuilder::new().tls_ca_file("ca.crt"); 553 /// ``` tls_ca_file(mut self, path: &str) -> Self554 pub fn tls_ca_file(mut self, path: &str) -> Self { 555 self.tls = self.tls.ca_file(path); 556 self 557 } 558 559 /// Sets the list of supported ciphers for protocols before `TLSv1.3`. 560 /// 561 /// See [`ciphers`] for details on the format. 562 /// 563 /// [`ciphers`]: https://www.openssl.org/docs/man1.1.0/apps/ciphers.html 564 /// 565 /// # Examples 566 /// 567 /// ``` 568 /// use ylong_http_client::async_impl::ClientBuilder; 569 /// 570 /// let builder = ClientBuilder::new() 571 /// .tls_cipher_list("DEFAULT:!aNULL:!eNULL:!MD5:!3DES:!DES:!RC4:!IDEA:!SEED:!aDSS:!SRP:!PSK"); 572 /// ``` tls_cipher_list(mut self, list: &str) -> Self573 pub fn tls_cipher_list(mut self, list: &str) -> Self { 574 self.tls = self.tls.cipher_list(list); 575 self 576 } 577 578 /// Sets the list of supported ciphers for the `TLSv1.3` protocol. 579 /// 580 /// The format consists of TLSv1.3 cipher suite names separated by `:` 581 /// characters in order of preference. 582 /// 583 /// Requires `OpenSSL 1.1.1` or `LibreSSL 3.4.0` or newer. 584 /// 585 /// # Examples 586 /// 587 /// ``` 588 /// use ylong_http_client::async_impl::ClientBuilder; 589 /// 590 /// let builder = ClientBuilder::new().tls_cipher_suites( 591 /// "DEFAULT:!aNULL:!eNULL:!MD5:!3DES:!DES:!RC4:!IDEA:!SEED:!aDSS:!SRP:!PSK", 592 /// ); 593 /// ``` tls_cipher_suites(mut self, list: &str) -> Self594 pub fn tls_cipher_suites(mut self, list: &str) -> Self { 595 self.tls = self.tls.cipher_suites(list); 596 self 597 } 598 599 /// Controls the use of built-in system certificates during certificate 600 /// validation. Default to `true` -- uses built-in system certs. 601 /// 602 /// # Examples 603 /// 604 /// ``` 605 /// use ylong_http_client::async_impl::ClientBuilder; 606 /// 607 /// let builder = ClientBuilder::new().tls_built_in_root_certs(false); 608 /// ``` tls_built_in_root_certs(mut self, is_use: bool) -> Self609 pub fn tls_built_in_root_certs(mut self, is_use: bool) -> Self { 610 self.tls = self.tls.build_in_root_certs(is_use); 611 self 612 } 613 614 /// Controls the use of certificates verification. 615 /// 616 /// Defaults to `false` -- verify certificates. 617 /// 618 /// # Warning 619 /// 620 /// When sets `true`, any certificate for any site will be trusted for use. 621 /// 622 /// # Examples 623 /// 624 /// ``` 625 /// use ylong_http_client::async_impl::ClientBuilder; 626 /// 627 /// let builder = ClientBuilder::new().danger_accept_invalid_certs(true); 628 /// ``` danger_accept_invalid_certs(mut self, is_invalid: bool) -> Self629 pub fn danger_accept_invalid_certs(mut self, is_invalid: bool) -> Self { 630 self.tls = self.tls.danger_accept_invalid_certs(is_invalid); 631 self 632 } 633 634 /// Controls the use of hostname verification. 635 /// 636 /// Defaults to `false` -- verify hostname. 637 /// 638 /// # Warning 639 /// 640 /// When sets `true`, any valid certificate for any site will be trusted for 641 /// use from any other. 642 /// 643 /// # Examples 644 /// 645 /// ``` 646 /// use ylong_http_client::async_impl::ClientBuilder; 647 /// 648 /// let builder = ClientBuilder::new().danger_accept_invalid_hostnames(true); 649 /// ``` danger_accept_invalid_hostnames(mut self, is_invalid: bool) -> Self650 pub fn danger_accept_invalid_hostnames(mut self, is_invalid: bool) -> Self { 651 self.tls = self.tls.danger_accept_invalid_hostnames(is_invalid); 652 self 653 } 654 655 /// Controls the use of TLS server name indication. 656 /// 657 /// Defaults to `true` -- sets sni. 658 /// 659 /// # Examples 660 /// 661 /// ``` 662 /// use ylong_http_client::async_impl::ClientBuilder; 663 /// 664 /// let builder = ClientBuilder::new().tls_sni(true); 665 /// ``` tls_sni(mut self, is_set_sni: bool) -> Self666 pub fn tls_sni(mut self, is_set_sni: bool) -> Self { 667 self.tls = self.tls.sni(is_set_sni); 668 self 669 } 670 } 671 672 impl Default for ClientBuilder { default() -> Self673 fn default() -> Self { 674 Self::new() 675 } 676 } 677 678 #[cfg(test)] 679 mod ut_async_impl_client { 680 use crate::async_impl::Client; 681 682 /// UT test cases for `Client::builder`. 683 /// 684 /// # Brief 685 /// 1. Creates a ClientBuilder by calling `Client::Builder`. 686 /// 2. Calls `http_config`, `client_config`, `build` on the builder 687 /// respectively. 688 /// 3. Checks if the result is as expected. 689 #[test] ut_client_builder()690 fn ut_client_builder() { 691 let builder = Client::builder().http1_only().build(); 692 assert!(builder.is_ok()) 693 } 694 695 /// UT test cases for `ClientBuilder::default`. 696 /// 697 /// # Brief 698 /// 1. Creates a `ClientBuilder` by calling `ClientBuilder::default`. 699 /// 2. Calls `http_config`, `client_config`, `tls_config` and `build` 700 /// respectively. 701 /// 3. Checks if the result is as expected. 702 #[cfg(feature = "__tls")] 703 #[test] ut_client_builder_default()704 fn ut_client_builder_default() { 705 use crate::async_impl::ClientBuilder; 706 use crate::util::{Redirect, Timeout}; 707 708 let builder = ClientBuilder::default() 709 .redirect(Redirect::none()) 710 .connect_timeout(Timeout::from_secs(9)) 711 .build(); 712 assert!(builder.is_ok()) 713 } 714 } 715