1use base64::Engine;
11use bytes::Bytes;
12use http::{Method, Uri};
13use serde::Serialize;
14use std::collections::HashMap;
15use std::sync::Arc;
16use std::time::Duration;
17use tokio::sync::RwLock;
18use tokio::time::timeout as tokio_timeout;
19use url::Url;
20
21use crate::cookie::CookieJar;
22use crate::error::{Error, Result};
23use crate::fingerprint::{http2::Http2Settings, FingerprintProfile};
24use crate::headers::Headers;
25use crate::pool::alt_svc::AltSvcCache;
26use crate::pool::multiplexer::{ConnectionPool, PoolKey};
27use crate::request::{Body, IntoUrl, RedirectPolicy, Request};
28use crate::response::Response;
29use crate::timeouts::Timeouts;
30use crate::transport::connector::{BoringConnector, MaybeHttpsStream};
31use crate::transport::h1::H1Connection;
32use crate::transport::h2::{H2Connection, H2PooledConnection, PseudoHeaderOrder};
33use crate::transport::h3::H3Client;
34use crate::version::HttpVersion;
35
36#[derive(Clone)]
45pub struct Client {
46 connector: BoringConnector,
47 insecure_connector: BoringConnector,
49 h3_client: H3Client,
50 alt_svc_cache: Arc<AltSvcCache>,
51 h2_pool: Arc<RwLock<HashMap<PoolKey, H2PooledConnection>>>,
53 h1_pool: Arc<ConnectionPool>,
55 http2_settings: Http2Settings,
56 pseudo_order: PseudoHeaderOrder,
57 default_version: HttpVersion,
58 timeouts: Timeouts,
60 h3_upgrade_enabled: bool,
62 http2_prior_knowledge: bool,
64 danger_accept_invalid_certs: bool,
66 localhost_allows_invalid_certs: bool,
68 default_headers: Headers,
70 redirect_policy: RedirectPolicy,
72 cookie_store: Option<Arc<RwLock<CookieJar>>>,
74}
75
76pub struct RequestBuilder<'a> {
78 client: &'a Client,
79 url: Option<Url>,
80 method: Method,
81 headers: Headers,
82 body: Body,
83 version: Option<HttpVersion>,
84 timeout: Option<Duration>,
85 error: Option<Error>,
86}
87
88pub struct ClientBuilder {
90 fingerprint: FingerprintProfile,
91 http2_settings: Option<Http2Settings>,
92 pseudo_order: PseudoHeaderOrder,
93 timeouts: Timeouts,
94 prefer_http2: bool,
95 h3_upgrade_enabled: bool,
96 http2_prior_knowledge: bool,
97 root_certs: Vec<Vec<u8>>,
98 use_platform_roots: bool,
100 danger_accept_invalid_certs: bool,
102 localhost_allows_invalid_certs: bool,
104 default_headers: Headers,
106 redirect_policy: RedirectPolicy,
108 cookie_store: Option<Arc<RwLock<CookieJar>>>,
110}
111
112impl Client {
113 pub fn new() -> Result<Self> {
115 ClientBuilder::new().build()
116 }
117
118 pub fn builder() -> ClientBuilder {
120 ClientBuilder::new()
121 }
122
123 pub fn get(&self, url: impl IntoUrl) -> RequestBuilder<'_> {
125 RequestBuilder::new(self, Method::GET, url)
126 }
127
128 pub fn post(&self, url: impl IntoUrl) -> RequestBuilder<'_> {
130 RequestBuilder::new(self, Method::POST, url)
131 }
132
133 pub fn put(&self, url: impl IntoUrl) -> RequestBuilder<'_> {
135 RequestBuilder::new(self, Method::PUT, url)
136 }
137
138 pub fn delete(&self, url: impl IntoUrl) -> RequestBuilder<'_> {
140 RequestBuilder::new(self, Method::DELETE, url)
141 }
142
143 pub fn head(&self, url: impl IntoUrl) -> RequestBuilder<'_> {
145 RequestBuilder::new(self, Method::HEAD, url)
146 }
147
148 pub fn patch(&self, url: impl IntoUrl) -> RequestBuilder<'_> {
150 RequestBuilder::new(self, Method::PATCH, url)
151 }
152
153 pub fn request(&self, method: Method, url: impl IntoUrl) -> RequestBuilder<'_> {
155 RequestBuilder::new(self, method, url)
156 }
157
158 pub fn alt_svc_cache(&self) -> &Arc<AltSvcCache> {
160 &self.alt_svc_cache
161 }
162
163 fn is_localhost(host: &str) -> bool {
165 host == "localhost" || host == "127.0.0.1" || host == "::1"
166 }
167
168 fn connector_for_uri(&self, uri: &Uri) -> &BoringConnector {
170 if self.danger_accept_invalid_certs {
172 return &self.insecure_connector;
173 }
174
175 if self.localhost_allows_invalid_certs {
177 if let Some(host) = uri.host() {
178 if Self::is_localhost(host) {
179 return &self.insecure_connector;
180 }
181 }
182 }
183
184 &self.connector
185 }
186}
187
188impl<'a> RequestBuilder<'a> {
189 fn new(client: &'a Client, method: Method, url: impl IntoUrl) -> Self {
190 let mut error = None;
191 let url = match url.into_url() {
192 Ok(url) => Some(url),
193 Err(err) => {
194 error = Some(err);
195 None
196 }
197 };
198
199 Self {
200 client,
201 url,
202 method,
203 headers: client.default_headers.clone(),
204 body: Body::Empty,
205 version: None,
206 timeout: None,
207 error,
208 }
209 }
210
211 fn set_error(&mut self, error: Error) {
212 if self.error.is_none() {
213 self.error = Some(error);
214 }
215 }
216
217 fn ensure_content_type(&mut self, value: &str) {
218 if !self.headers.contains("content-type") {
219 self.headers.insert("Content-Type", value.to_string());
220 }
221 }
222
223 pub fn header(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
225 self.headers.insert(key, value);
226 self
227 }
228
229 pub fn header_append(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
231 self.headers.append(key, value);
232 self
233 }
234
235 pub fn headers(mut self, headers: impl Into<Headers>) -> Self {
237 self.headers = headers.into();
238 self
239 }
240
241 pub fn body(mut self, body: impl Into<Body>) -> Self {
243 self.body = body.into();
244 self
245 }
246
247 pub fn query<T: Serialize + ?Sized>(mut self, query: &T) -> Self {
249 if self.error.is_some() {
250 return self;
251 }
252
253 let url = match self.url.as_mut() {
254 Some(url) => url,
255 None => return self,
256 };
257
258 match serde_urlencoded::to_string(query) {
259 Ok(encoded) => {
260 if !encoded.is_empty() {
261 let merged = match url.query() {
262 Some(existing) if !existing.is_empty() => {
263 format!("{}&{}", existing, encoded)
264 }
265 _ => encoded,
266 };
267 url.set_query(Some(&merged));
268 }
269 }
270 Err(err) => self.set_error(err.into()),
271 }
272
273 self
274 }
275
276 pub fn json<T: Serialize + ?Sized>(mut self, json: &T) -> Self {
278 if self.error.is_some() {
279 return self;
280 }
281
282 match serde_json::to_vec(json) {
283 Ok(bytes) => {
284 self.body = Body::Json(bytes);
285 self.ensure_content_type("application/json");
286 }
287 Err(err) => self.set_error(err.into()),
288 }
289
290 self
291 }
292
293 pub fn form<T: Serialize + ?Sized>(mut self, form: &T) -> Self {
295 if self.error.is_some() {
296 return self;
297 }
298
299 match serde_urlencoded::to_string(form) {
300 Ok(encoded) => {
301 self.body = Body::Form(encoded);
302 self.ensure_content_type("application/x-www-form-urlencoded");
303 }
304 Err(err) => self.set_error(err.into()),
305 }
306
307 self
308 }
309
310 pub fn bearer_auth(mut self, token: impl AsRef<str>) -> Self {
312 self.headers
313 .insert("Authorization", format!("Bearer {}", token.as_ref()));
314 self
315 }
316
317 pub fn basic_auth<P: AsRef<str>>(
319 mut self,
320 username: impl AsRef<str>,
321 password: Option<P>,
322 ) -> Self {
323 let creds = match password {
324 Some(p) => format!("{}:{}", username.as_ref(), p.as_ref()),
325 None => format!("{}:", username.as_ref()),
326 };
327 let encoded = base64::engine::general_purpose::STANDARD.encode(creds.as_bytes());
328 self.headers
329 .insert("Authorization", format!("Basic {}", encoded));
330 self
331 }
332
333 pub fn timeout(mut self, timeout: Duration) -> Self {
335 self.timeout = Some(timeout);
336 self
337 }
338
339 pub fn version(mut self, version: HttpVersion) -> Self {
341 self.version = Some(version);
342 self
343 }
344
345 pub fn build(self) -> Result<Request> {
347 if let Some(error) = self.error {
348 return Err(error);
349 }
350
351 let url = self.url.ok_or_else(|| Error::missing("url"))?;
352
353 Ok(Request {
354 method: self.method,
355 url,
356 headers: self.headers,
357 body: self.body,
358 version: self.version,
359 timeout: self.timeout,
360 })
361 }
362
363 pub async fn send(self) -> Result<Response> {
365 let client = self.client.clone();
366 let request = self.build()?;
367 client.execute(request).await
368 }
369
370 pub async fn send_streaming(
374 self,
375 ) -> Result<(
376 Response,
377 tokio::sync::mpsc::Receiver<std::result::Result<Bytes, crate::transport::h2::H2Error>>,
378 )> {
379 let client = self.client.clone();
380 let request = self.build()?;
381 let mut timeouts = client.timeouts.clone();
382 if let Some(total) = request.timeout {
383 timeouts.total = Some(total);
384 }
385 let mut headers = request.headers.clone();
386
387 if let Some(jar) = &client.cookie_store {
388 if !headers.contains("cookie") {
389 if let Some(cookie_header) =
390 jar.read().await.build_cookie_header(request.url.as_str())
391 {
392 headers.insert("Cookie", cookie_header);
393 }
394 }
395 }
396
397 let version = request.version.unwrap_or(client.default_version);
398
399 if !matches!(version, HttpVersion::Http2 | HttpVersion::Auto) {
401 return Err(Error::HttpProtocol(
402 "Streaming only supported for HTTP/2".into(),
403 ));
404 }
405
406 let uri: Uri = request
408 .url
409 .as_str()
410 .parse()
411 .map_err(|e| Error::HttpProtocol(format!("Invalid URI: {}", e)))?;
412
413 let connector = client.connector_for_uri(&uri);
416 let connect_fut = connector.connect(&uri);
417 let stream = if let Some(connect_timeout) = timeouts.connect {
418 tokio_timeout(connect_timeout, connect_fut)
419 .await
420 .map_err(|_| Error::ConnectTimeout(connect_timeout))??
421 } else {
422 connect_fut.await?
423 };
424
425 let alpn = stream.alpn_protocol();
427 if !alpn.is_h2() {
428 return Err(Error::HttpProtocol(format!(
429 "Expected h2 ALPN, got {:?}",
430 alpn
431 )));
432 }
433
434 let h2_connect_fut =
436 H2Connection::connect(stream, client.http2_settings.clone(), client.pseudo_order);
437 let mut h2_conn = if let Some(connect_timeout) = timeouts.connect {
438 tokio_timeout(connect_timeout, h2_connect_fut)
439 .await
440 .map_err(|_| Error::ConnectTimeout(connect_timeout))??
441 } else {
442 h2_connect_fut.await?
443 };
444
445 let mut path = request.url.path().to_string();
447 if path.is_empty() {
448 path = "/".to_string();
449 }
450 if let Some(query) = request.url.query() {
451 path.push('?');
452 path.push_str(query);
453 }
454
455 let host = request.url.host_str().unwrap_or("localhost");
456 let authority = if let Some(port) = request.url.port_or_known_default() {
457 if port == 443 {
458 host.to_string()
459 } else {
460 format!("{}:{}", host, port)
461 }
462 } else {
463 host.to_string()
464 };
465
466 let full_uri = format!("https://{}{}", authority, path);
468 let mut request_builder = http::Request::builder()
469 .method(request.method.clone())
470 .uri(&full_uri);
471
472 for (key, value) in headers.iter() {
474 request_builder = request_builder.header(key, value);
475 }
476
477 let body = request.body.clone().into_bytes()?;
478 let http_request = request_builder
479 .body(body)
480 .map_err(|e| Error::HttpProtocol(format!("Failed to build request: {}", e)))?;
481
482 let send_fut = h2_conn.send_request_streaming(http_request);
484 let (response, rx) = if let Some(ttfb_timeout) = timeouts.ttfb {
485 tokio_timeout(ttfb_timeout, send_fut)
486 .await
487 .map_err(|_| Error::TtfbTimeout(ttfb_timeout))??
488 } else {
489 send_fut.await?
490 };
491
492 tokio::spawn(async move {
494 loop {
495 match h2_conn.read_streaming_frames().await {
496 Ok(true) => continue,
497 Ok(false) => break,
498 Err(e) => {
499 tracing::debug!("Streaming read error: {}", e);
500 break;
501 }
502 }
503 }
504 });
505
506 let status = response.status().as_u16();
508 let headers = response
509 .headers()
510 .iter()
511 .map(|(k, v)| (k.as_str().to_string(), v.to_str().unwrap_or("").to_string()))
512 .collect::<Vec<(String, String)>>();
513
514 let our_response = crate::response::Response::new(
515 status,
516 Headers::from(headers),
517 Bytes::new(), "HTTP/2".to_string(),
519 );
520
521 let request_url = request.url.clone();
522 let our_response = our_response.with_url(request_url.clone());
523
524 if let Some(jar) = &client.cookie_store {
525 jar.write()
526 .await
527 .store_from_headers(our_response.headers(), request_url.as_str());
528 }
529
530 Ok((our_response, rx))
531 }
532}
533
534impl Client {
535 pub async fn execute(&self, mut request: Request) -> Result<Response> {
537 let policy = self.redirect_policy.clone();
538 let mut redirects = 0u32;
539
540 loop {
541 let mut headers = request.headers.clone();
542 let cookie_injected = self.apply_cookie_header(&request, &mut headers).await;
543 request.headers = headers;
544
545 let mut timeouts = self.timeouts.clone();
546 if let Some(total) = request.timeout {
547 timeouts.total = Some(total);
548 }
549
550 let response = self.execute_once(&request, &timeouts).await?;
551
552 self.store_cookies(&response, &request.url).await;
553
554 if matches!(policy, RedirectPolicy::None) || !response.is_redirect() {
555 return Ok(response);
556 }
557
558 let location = match response.redirect_url() {
559 Some(value) => value,
560 None => return Ok(response),
561 };
562
563 if let RedirectPolicy::Limited(limit) = policy {
564 if redirects >= limit {
565 return Err(Error::RedirectLimit { count: limit });
566 }
567 }
568
569 let next_url = request.url.join(location).map_err(Error::from)?;
570 let mut next_request = self.redirect_request(&request, &response, next_url);
571
572 if cookie_injected {
573 next_request.headers.remove("cookie");
574 }
575
576 request = next_request;
577 redirects += 1;
578 }
579 }
580
581 async fn execute_once(&self, request: &Request, timeouts: &Timeouts) -> Result<Response> {
582 let version = request.version.unwrap_or(self.default_version);
583
584 if matches!(version, HttpVersion::Http3Only) {
586 return self
587 .send_h3_for_url(request, request.url.clone(), timeouts)
588 .await;
589 }
590
591 if matches!(version, HttpVersion::Http3) {
593 match self
594 .send_h3_for_url(request, request.url.clone(), timeouts)
595 .await
596 {
597 Ok(response) => return Ok(response),
598 Err(e) => {
599 tracing::debug!("HTTP/3 failed, falling back to HTTP/1.1 or HTTP/2: {}", e);
600 }
602 }
603 }
604
605 if matches!(version, HttpVersion::Auto) && self.h3_upgrade_enabled {
607 let origin = Self::origin_for_url(&request.url);
608 if let Some(alt_svc) = self.alt_svc_cache.get_h3_alternative(&origin).await {
609 tracing::debug!(
610 "Alt-Svc indicates HTTP/3 support for {}, attempting upgrade",
611 origin
612 );
613
614 let mut h3_url = request.url.clone();
615 let _ = h3_url.set_scheme("https");
616 if let Some(ref host) = alt_svc.host {
617 h3_url
618 .set_host(Some(host))
619 .map_err(|_| Error::HttpProtocol("Invalid Alt-Svc host".into()))?;
620 }
621 let _ = h3_url.set_port(Some(alt_svc.port));
622
623 match self
624 .send_h3_for_url(request, h3_url.clone(), timeouts)
625 .await
626 {
627 Ok(response) => return Ok(response.with_url(h3_url)),
628 Err(e) => {
629 tracing::debug!("HTTP/3 upgrade failed, using HTTP/1.1 or HTTP/2: {}", e);
630 }
632 }
633 }
634 }
635
636 self.send_h1_h2(request, version, timeouts).await
638 }
639
640 async fn send_h3_for_url(
641 &self,
642 request: &Request,
643 url: Url,
644 timeouts: &Timeouts,
645 ) -> Result<Response> {
646 let body = if request.body.is_empty() {
647 None
648 } else {
649 Some(request.body.clone().into_bytes()?.to_vec())
650 };
651
652 let fut = self.h3_client.send_request(
653 url.as_str(),
654 request.method.as_str(),
655 request.headers.to_vec(),
656 body,
657 );
658
659 let response = if let Some(total_timeout) = timeouts.total {
661 tokio_timeout(total_timeout, fut)
662 .await
663 .map_err(|_| Error::TotalTimeout(total_timeout))??
664 } else {
665 fut.await?
666 };
667
668 Ok(response.with_url(url))
669 }
670
671 async fn send_h1_h2(
672 &self,
673 request: &Request,
674 version: HttpVersion,
675 timeouts: &Timeouts,
676 ) -> Result<Response> {
677 let request_url = request.url.clone();
679
680 let uri: Uri = request
682 .url
683 .as_str()
684 .parse()
685 .map_err(|e| Error::HttpProtocol(format!("Invalid URI: {}", e)))?;
686
687 let prefer_http2 = match version {
689 HttpVersion::Http1_1 => false,
690 HttpVersion::Http2 => true,
691 HttpVersion::Http3 | HttpVersion::Http3Only => {
692 return Err(Error::HttpProtocol("HTTP/3 should use send_h3".into()));
693 }
694 HttpVersion::Auto => matches!(self.default_version, HttpVersion::Http2),
695 };
696
697 let h3_upgrade_enabled = self.h3_upgrade_enabled;
699 let alt_svc_cache = self.alt_svc_cache.clone();
700 let origin = Self::origin_for_url(&request.url);
701
702 let headers_vec = request.headers.to_vec();
703 let body_bytes = if request.body.is_empty() {
704 None
705 } else {
706 Some(request.body.clone().into_bytes()?)
707 };
708
709 if prefer_http2 {
711 let pool_key = Self::make_pool_key(&uri);
712
713 let pooled = {
715 let pool = self.h2_pool.read().await;
716 pool.get(&pool_key).cloned()
717 };
718
719 if let Some(conn) = pooled {
720 let result = conn
722 .send_request(
723 request.method.clone(),
724 &uri,
725 headers_vec.clone(),
726 body_bytes.clone(),
727 )
728 .await;
729
730 match result {
731 Ok(response) => {
732 if h3_upgrade_enabled {
734 if let Some(alt_svc) = response.get_header("alt-svc") {
735 alt_svc_cache.parse_and_store(&origin, alt_svc).await;
736 }
737 }
738 return Ok(response.with_url(request_url));
739 }
740 Err(e) => {
741 tracing::debug!("Pooled HTTP/2 connection failed, creating new: {}", e);
743 let mut pool = self.h2_pool.write().await;
744 pool.remove(&pool_key);
745 }
746 }
747 }
748
749 let connector = self.connector_for_uri(&uri);
752 let connect_fut = connector.connect(&uri);
753 let stream = if let Some(connect_timeout) = timeouts.connect {
754 tokio_timeout(connect_timeout, connect_fut)
755 .await
756 .map_err(|_| Error::ConnectTimeout(connect_timeout))??
757 } else {
758 connect_fut.await?
759 };
760
761 let use_http2 = if self.http2_prior_knowledge && !stream.alpn_protocol().is_h2() {
763 true
765 } else if let MaybeHttpsStream::Https(ref ssl_stream) = stream {
766 ssl_stream.ssl().selected_alpn_protocol() == Some(b"h2")
767 } else {
768 false
769 };
770
771 if use_http2 {
772 let h2_conn =
774 H2Connection::connect(stream, self.http2_settings.clone(), self.pseudo_order)
775 .await?;
776 let pooled_conn = H2PooledConnection::new(h2_conn);
777
778 {
780 let mut pool = self.h2_pool.write().await;
781 pool.insert(pool_key, pooled_conn.clone());
782 }
783
784 let fut = pooled_conn.send_request(
786 request.method.clone(),
787 &uri,
788 headers_vec.clone(),
789 body_bytes.clone(),
790 );
791
792 let response = if let Some(ttfb_timeout) = timeouts.ttfb {
793 tokio_timeout(ttfb_timeout, fut)
794 .await
795 .map_err(|_| Error::TtfbTimeout(ttfb_timeout))?
796 } else {
797 fut.await
798 }?;
799
800 if h3_upgrade_enabled {
802 if let Some(alt_svc) = response.get_header("alt-svc") {
803 alt_svc_cache.parse_and_store(&origin, alt_svc).await;
804 }
805 }
806
807 return Ok(response.with_url(request_url));
808 }
809 }
811
812 let pool_key = Self::make_pool_key(&uri);
814
815 let mut stream_opt = self.h1_pool.get_h1(&pool_key).await;
817 let mut used_pooled = stream_opt.is_some();
818
819 let mut stream = if let Some(pooled_stream) = stream_opt.take() {
821 tracing::debug!("H1: Reusing pooled connection for {:?}", pool_key);
822 pooled_stream
823 } else {
824 tracing::debug!("H1: Creating new connection for {:?}", pool_key);
825 let connector = self.connector_for_uri(&uri);
827 let connect_fut = connector.connect(&uri);
828 if let Some(connect_timeout) = timeouts.connect {
829 tokio_timeout(connect_timeout, connect_fut)
830 .await
831 .map_err(|_| Error::ConnectTimeout(connect_timeout))??
832 } else {
833 connect_fut.await?
834 }
835 };
836
837 let server_wants_h2 = if let MaybeHttpsStream::Https(ref ssl_stream) = stream {
840 ssl_stream.ssl().selected_alpn_protocol() == Some(b"h2")
841 } else {
842 false
843 };
844
845 let response = if server_wants_h2 {
846 tracing::debug!("Server selected h2 via ALPN, upgrading to HTTP/2");
848
849 let h2_conn =
850 H2Connection::connect(stream, self.http2_settings.clone(), self.pseudo_order)
851 .await?;
852 let pooled_conn = H2PooledConnection::new(h2_conn);
853
854 {
856 let mut pool = self.h2_pool.write().await;
857 pool.insert(pool_key, pooled_conn.clone());
858 }
859
860 let fut = pooled_conn.send_request(
862 request.method.clone(),
863 &uri,
864 headers_vec.clone(),
865 body_bytes.clone(),
866 );
867
868 if let Some(ttfb_timeout) = timeouts.ttfb {
869 tokio_timeout(ttfb_timeout, fut)
870 .await
871 .map_err(|_| Error::TtfbTimeout(ttfb_timeout))?
872 } else {
873 fut.await
874 }?
875 } else {
876 let result = loop {
880 let stream_for_request = stream;
881 let fut = Self::do_send_http1(
882 stream_for_request,
883 request.method.clone(),
884 &uri,
885 headers_vec.clone(),
886 body_bytes.clone(),
887 );
888
889 let request_result = if let Some(ttfb_timeout) = timeouts.ttfb {
891 tokio_timeout(ttfb_timeout, fut)
892 .await
893 .map_err(|_| Error::TtfbTimeout(ttfb_timeout))?
894 } else {
895 fut.await
896 };
897
898 match request_result {
899 Ok((resp, returned_stream)) => {
900 self.h1_pool.put_h1(pool_key.clone(), returned_stream).await;
902 break Ok(resp);
903 }
904 Err(e) => {
905 if used_pooled {
907 tracing::debug!(
908 "H1: Pooled connection failed for {:?}, creating new: {}",
909 pool_key,
910 e
911 );
912 let connector = self.connector_for_uri(&uri);
914 let connect_fut = connector.connect(&uri);
915 stream = if let Some(connect_timeout) = timeouts.connect {
916 tokio_timeout(connect_timeout, connect_fut)
917 .await
918 .map_err(|_| Error::ConnectTimeout(connect_timeout))??
919 } else {
920 connect_fut.await?
921 };
922 used_pooled = false; continue;
924 } else {
925 tracing::debug!(
927 "H1: Request failed for {:?}, discarding connection: {}",
928 pool_key,
929 e
930 );
931 break Err(e);
932 }
933 }
934 }
935 };
936
937 result?
938 };
939
940 if h3_upgrade_enabled {
942 if let Some(alt_svc) = response.get_header("alt-svc") {
943 alt_svc_cache.parse_and_store(&origin, alt_svc).await;
944 }
945 }
946
947 Ok(response.with_url(request_url))
948 }
949
950 fn redirect_request(&self, request: &Request, response: &Response, next_url: Url) -> Request {
951 let status = response.status().as_u16();
952 let mut method = request.method.clone();
953 let mut body = request.body.clone();
954 let mut headers = request.headers.clone();
955
956 let should_switch = status == 303
957 || ((status == 301 || status == 302) && !matches!(method, Method::GET | Method::HEAD));
958
959 if should_switch {
960 method = Method::GET;
961 body = Body::Empty;
962 headers.remove("content-length");
963 headers.remove("content-type");
964 }
965
966 if Self::is_cross_origin(&request.url, &next_url) {
967 headers.remove("authorization");
968 }
969
970 Request {
971 method,
972 url: next_url,
973 headers,
974 body,
975 version: request.version,
976 timeout: request.timeout,
977 }
978 }
979
980 async fn apply_cookie_header(&self, request: &Request, headers: &mut Headers) -> bool {
981 if let Some(jar) = &self.cookie_store {
982 if !headers.contains("cookie") {
983 if let Some(cookie_header) =
984 jar.read().await.build_cookie_header(request.url.as_str())
985 {
986 headers.insert("Cookie", cookie_header);
987 return true;
988 }
989 }
990 }
991 false
992 }
993
994 async fn store_cookies(&self, response: &Response, url: &Url) {
995 if let Some(jar) = &self.cookie_store {
996 jar.write()
997 .await
998 .store_from_headers(response.headers(), url.as_str());
999 }
1000 }
1001
1002 fn make_pool_key(uri: &Uri) -> PoolKey {
1004 let host = uri.host().unwrap_or("localhost").to_string();
1005 let is_https = uri.scheme_str() == Some("https");
1006 let port = uri.port_u16().unwrap_or(if is_https { 443 } else { 80 });
1007 PoolKey::new(host, port, is_https)
1008 }
1009
1010 async fn do_send_http1(
1011 stream: MaybeHttpsStream,
1012 method: Method,
1013 uri: &Uri,
1014 headers: Vec<(String, String)>,
1015 body: Option<Bytes>,
1016 ) -> Result<(Response, MaybeHttpsStream)> {
1017 let mut conn = H1Connection::new(stream);
1018 let response = conn.send_request(method, uri, headers, body).await?;
1019 let stream = conn.into_inner();
1020 Ok((response, stream))
1021 }
1022
1023 fn origin_for_url(url: &Url) -> String {
1025 let scheme = url.scheme();
1026 let host = url.host_str().unwrap_or("localhost");
1027 let port = url
1028 .port_or_known_default()
1029 .unwrap_or(if scheme == "https" { 443 } else { 80 });
1030
1031 if (scheme == "https" && port == 443) || (scheme == "http" && port == 80) {
1032 format!("{}://{}", scheme, host)
1033 } else {
1034 format!("{}://{}:{}", scheme, host, port)
1035 }
1036 }
1037
1038 fn is_cross_origin(a: &Url, b: &Url) -> bool {
1039 a.scheme() != b.scheme()
1040 || a.host_str() != b.host_str()
1041 || a.port_or_known_default() != b.port_or_known_default()
1042 }
1043}
1044
1045impl ClientBuilder {
1046 pub fn new() -> Self {
1055 Self {
1056 fingerprint: FingerprintProfile::default(),
1057 http2_settings: None,
1058 pseudo_order: PseudoHeaderOrder::Chrome,
1059 timeouts: Timeouts::default(),
1060 prefer_http2: true, h3_upgrade_enabled: true, http2_prior_knowledge: false,
1063 root_certs: Vec::new(),
1064 use_platform_roots: false,
1065 danger_accept_invalid_certs: false,
1066 localhost_allows_invalid_certs: true, default_headers: Headers::new(),
1068 redirect_policy: RedirectPolicy::None,
1069 cookie_store: None,
1070 }
1071 }
1072
1073 pub fn fingerprint(mut self, fingerprint: FingerprintProfile) -> Self {
1075 self.fingerprint = fingerprint;
1076 self
1077 }
1078
1079 pub fn http2_settings(mut self, settings: Http2Settings) -> Self {
1081 self.http2_settings = Some(settings);
1082 self
1083 }
1084
1085 pub fn pseudo_order(mut self, order: PseudoHeaderOrder) -> Self {
1087 self.pseudo_order = order;
1088 self
1089 }
1090
1091 pub fn timeouts(mut self, timeouts: Timeouts) -> Self {
1095 self.timeouts = timeouts;
1096 self
1097 }
1098
1099 pub fn api_timeouts(mut self) -> Self {
1103 self.timeouts = Timeouts::api_defaults();
1104 self
1105 }
1106
1107 pub fn streaming_timeouts(mut self) -> Self {
1112 self.timeouts = Timeouts::streaming_defaults();
1113 self
1114 }
1115
1116 #[deprecated(
1121 since = "1.0.2",
1122 note = "Use `timeouts()` or `total_timeout()` instead"
1123 )]
1124 pub fn timeout(mut self, timeout: Duration) -> Self {
1125 self.timeouts.total = Some(timeout);
1126 self
1127 }
1128
1129 pub fn total_timeout(mut self, timeout: Duration) -> Self {
1131 self.timeouts.total = Some(timeout);
1132 self
1133 }
1134
1135 pub fn connect_timeout(mut self, timeout: Duration) -> Self {
1137 self.timeouts.connect = Some(timeout);
1138 self
1139 }
1140
1141 pub fn ttfb_timeout(mut self, timeout: Duration) -> Self {
1143 self.timeouts.ttfb = Some(timeout);
1144 self
1145 }
1146
1147 pub fn read_timeout(mut self, timeout: Duration) -> Self {
1149 self.timeouts.read_idle = Some(timeout);
1150 self
1151 }
1152
1153 pub fn write_timeout(mut self, timeout: Duration) -> Self {
1155 self.timeouts.write_idle = Some(timeout);
1156 self
1157 }
1158
1159 pub fn pool_acquire_timeout(mut self, timeout: Duration) -> Self {
1161 self.timeouts.pool_acquire = Some(timeout);
1162 self
1163 }
1164
1165 pub fn default_headers(mut self, headers: impl Into<Headers>) -> Self {
1167 self.default_headers = headers.into();
1168 self
1169 }
1170
1171 pub fn default_header(mut self, name: impl Into<String>, value: impl Into<String>) -> Self {
1173 self.default_headers.insert(name, value);
1174 self
1175 }
1176
1177 pub fn user_agent(mut self, value: impl Into<String>) -> Self {
1179 self.default_headers.insert("User-Agent", value.into());
1180 self
1181 }
1182
1183 pub fn redirect_policy(mut self, policy: RedirectPolicy) -> Self {
1185 self.redirect_policy = policy;
1186 self
1187 }
1188
1189 pub fn cookie_store(mut self, enabled: bool) -> Self {
1191 if enabled {
1192 self.cookie_store = Some(Arc::new(RwLock::new(CookieJar::new())));
1193 } else {
1194 self.cookie_store = None;
1195 }
1196 self
1197 }
1198
1199 pub fn cookie_jar(mut self, jar: Arc<RwLock<CookieJar>>) -> Self {
1201 self.cookie_store = Some(jar);
1202 self
1203 }
1204
1205 pub fn prefer_http2(mut self, prefer: bool) -> Self {
1207 self.prefer_http2 = prefer;
1208 self
1209 }
1210
1211 pub fn h3_upgrade(mut self, enabled: bool) -> Self {
1218 self.h3_upgrade_enabled = enabled;
1219 self
1220 }
1221
1222 pub fn http2_prior_knowledge(mut self, enabled: bool) -> Self {
1225 self.http2_prior_knowledge = enabled;
1226 if enabled {
1228 self.prefer_http2 = true;
1229 }
1230 self
1231 }
1232
1233 pub fn add_root_certificate(mut self, cert: Vec<u8>) -> Self {
1235 self.root_certs.push(cert);
1236 self
1237 }
1238
1239 pub fn with_platform_roots(mut self, enabled: bool) -> Self {
1250 self.use_platform_roots = enabled;
1251 self
1252 }
1253
1254 pub fn danger_accept_invalid_certs(mut self, accept: bool) -> Self {
1260 self.danger_accept_invalid_certs = accept;
1261 self
1262 }
1263
1264 pub fn localhost_allows_invalid_certs(mut self, allow: bool) -> Self {
1272 self.localhost_allows_invalid_certs = allow;
1273 self
1274 }
1275
1276 pub fn build(self) -> Result<Client> {
1278 let tls_fingerprint = self.fingerprint.tls_fingerprint();
1280 let mut connector = BoringConnector::with_fingerprint(tls_fingerprint.clone())
1281 .with_root_certificates(self.root_certs.clone())
1282 .with_platform_roots(self.use_platform_roots);
1283
1284 if self.danger_accept_invalid_certs {
1286 connector = connector.danger_accept_invalid_certs(true);
1287 }
1288
1289 let insecure_connector = BoringConnector::with_fingerprint(tls_fingerprint.clone())
1291 .with_root_certificates(self.root_certs)
1292 .with_platform_roots(self.use_platform_roots)
1293 .danger_accept_invalid_certs(true);
1294
1295 let h3_client = H3Client::with_fingerprint(tls_fingerprint);
1297
1298 let http2_settings = self.http2_settings.unwrap_or_default();
1300
1301 let default_version = if self.prefer_http2 {
1303 HttpVersion::Http2
1304 } else {
1305 HttpVersion::Http1_1
1306 };
1307
1308 Ok(Client {
1309 connector,
1310 insecure_connector,
1311 h3_client,
1312 alt_svc_cache: Arc::new(AltSvcCache::new()),
1313 h2_pool: Arc::new(RwLock::new(HashMap::new())),
1314 h1_pool: Arc::new(ConnectionPool::new()),
1315 http2_settings,
1316 pseudo_order: self.pseudo_order,
1317 default_version,
1318 timeouts: self.timeouts,
1319 h3_upgrade_enabled: self.h3_upgrade_enabled,
1320 http2_prior_knowledge: self.http2_prior_knowledge,
1321 danger_accept_invalid_certs: self.danger_accept_invalid_certs,
1322 localhost_allows_invalid_certs: self.localhost_allows_invalid_certs,
1323 default_headers: self.default_headers,
1324 redirect_policy: self.redirect_policy,
1325 cookie_store: self.cookie_store,
1326 })
1327 }
1328}
1329
1330impl Default for ClientBuilder {
1331 fn default() -> Self {
1332 Self::new()
1333 }
1334}
1335
1336impl Default for AltSvcCache {
1337 fn default() -> Self {
1338 Self::new()
1339 }
1340}