isahc/config/mod.rs
1//! Definition of all client and request configuration options.
2//!
3//! Individual options are separated out into multiple types. Each type acts
4//! both as a "field name" and the value of that option.
5
6// Options are implemented as structs of various kinds that can be "applied" to
7// a curl easy handle. This helps to reduce code duplication as there are many
8// supported options, and also helps avoid having a massive function that does
9// all the configuring.
10//
11// When adding new config options, remember to add methods for setting the
12// option both in HttpClientBuilder and RequestBuilderExt. In addition, be sure
13// to update the client code to apply the option when configuring an easy
14// handle.
15
16use self::{proxy::Proxy, request::SetOpt};
17use crate::{
18 auth::{Authentication, Credentials},
19 is_http_version_supported,
20};
21use curl::easy::Easy2;
22use std::{net::IpAddr, time::Duration};
23
24pub(crate) mod client;
25pub(crate) mod dial;
26pub(crate) mod dns;
27pub(crate) mod proxy;
28pub(crate) mod redirect;
29pub(crate) mod request;
30pub(crate) mod ssl;
31
32pub use dial::{Dialer, DialerParseError};
33pub use dns::{DnsCache, ResolveMap};
34pub use redirect::RedirectPolicy;
35pub use ssl::{CaCertificate, ClientCertificate, PrivateKey, SslOption};
36
37/// Provides additional methods when building a request for configuring various
38/// execution-related options on how the request should be sent.
39///
40/// This trait can be used to either configure requests individually by invoking
41/// them on an [`http::request::Builder`], or to configure the default settings
42/// for an [`HttpClient`](crate::HttpClient) by invoking them on an
43/// [`HttpClientBuilder`](crate::HttpClientBuilder).
44///
45/// This trait is sealed and cannot be implemented for types outside of Isahc.
46pub trait Configurable: request::WithRequestConfig {
47 /// Specify a maximum amount of time that a complete request/response cycle
48 /// is allowed to take before being aborted. This includes DNS resolution,
49 /// connecting to the server, writing the request, and reading the response.
50 ///
51 /// Since response bodies are streamed, you will likely receive a
52 /// [`Response`](crate::http::Response) before the response body stream has
53 /// been fully consumed. This means that the configured timeout will still
54 /// be active for that request, and if it expires, further attempts to read
55 /// from the stream will return a [`TimedOut`](std::io::ErrorKind::TimedOut)
56 /// I/O error.
57 ///
58 /// This also means that if you receive a response with a body but do not
59 /// immediately start reading from it, then the timeout timer will still be
60 /// active and may expire before you even attempt to read the body. Keep
61 /// this in mind when consuming responses and consider handling the response
62 /// body right after you receive it if you are using this option.
63 ///
64 /// If not set, no timeout will be enforced.
65 ///
66 /// # Examples
67 ///
68 /// ```no_run
69 /// use isahc::{prelude::*, Request};
70 /// use std::time::Duration;
71 ///
72 /// // This page is too slow and won't respond in time.
73 /// let response = Request::get("https://httpbin.org/delay/10")
74 /// .timeout(Duration::from_secs(5))
75 /// .body(())?
76 /// .send()
77 /// .expect_err("page should time out");
78 /// # Ok::<(), isahc::Error>(())
79 /// ```
80 #[must_use = "builders have no effect if unused"]
81 fn timeout(self, timeout: Duration) -> Self {
82 self.with_config(move |config| {
83 config.timeout = Some(timeout);
84 })
85 }
86
87 /// Set a timeout for establishing connections to a host.
88 ///
89 /// If not set, a default connect timeout of 300 seconds will be used.
90 #[must_use = "builders have no effect if unused"]
91 fn connect_timeout(self, timeout: Duration) -> Self {
92 self.with_config(move |config| {
93 config.connect_timeout = Some(timeout);
94 })
95 }
96
97 /// Specify a maximum amount of time where transfer rate can go below
98 /// a minimum speed limit. `low_speed` is that limit in bytes/s.
99 ///
100 /// If not set, no low speed limits are imposed.
101 #[must_use = "builders have no effect if unused"]
102 fn low_speed_timeout(self, low_speed: u32, timeout: Duration) -> Self {
103 self.with_config(move |config| {
104 config.low_speed_timeout = Some((low_speed, timeout));
105 })
106 }
107
108 /// Configure how the use of HTTP versions should be negotiated with the
109 /// server.
110 ///
111 /// The default is [`VersionNegotiation::latest_compatible`].
112 ///
113 /// # Examples
114 ///
115 /// ```
116 /// use isahc::{
117 /// config::VersionNegotiation,
118 /// prelude::*,
119 /// HttpClient,
120 /// };
121 ///
122 /// // Never use anything newer than HTTP/1.x for this client.
123 /// let http11_client = HttpClient::builder()
124 /// .version_negotiation(VersionNegotiation::http11())
125 /// .build()?;
126 ///
127 /// // HTTP/2 with prior knowledge.
128 /// let http2_client = HttpClient::builder()
129 /// .version_negotiation(VersionNegotiation::http2())
130 /// .build()?;
131 /// # Ok::<(), isahc::Error>(())
132 /// ```
133 #[must_use = "builders have no effect if unused"]
134 fn version_negotiation(self, negotiation: VersionNegotiation) -> Self {
135 self.with_config(move |config| {
136 config.version_negotiation = Some(negotiation);
137 })
138 }
139
140 /// Set a policy for automatically following server redirects.
141 ///
142 /// The default is to not follow redirects.
143 ///
144 /// # Examples
145 ///
146 /// ```no_run
147 /// use isahc::{config::RedirectPolicy, prelude::*, Request};
148 ///
149 /// // This URL redirects us to where we want to go.
150 /// let response = Request::get("https://httpbin.org/redirect/1")
151 /// .redirect_policy(RedirectPolicy::Follow)
152 /// .body(())?
153 /// .send()?;
154 ///
155 /// // This URL redirects too much!
156 /// let error = Request::get("https://httpbin.org/redirect/10")
157 /// .redirect_policy(RedirectPolicy::Limit(5))
158 /// .body(())?
159 /// .send()
160 /// .expect_err("too many redirects");
161 /// # Ok::<(), isahc::Error>(())
162 /// ```
163 #[must_use = "builders have no effect if unused"]
164 fn redirect_policy(self, policy: RedirectPolicy) -> Self {
165 self.with_config(move |config| {
166 config.redirect_policy = Some(policy);
167 })
168 }
169
170 /// Update the `Referer` header automatically when following redirects.
171 #[must_use = "builders have no effect if unused"]
172 fn auto_referer(self) -> Self {
173 self.with_config(move |config| {
174 config.auto_referer = Some(true);
175 })
176 }
177
178 /// Set a cookie jar to use to accept, store, and supply cookies for
179 /// incoming responses and outgoing requests.
180 ///
181 /// A cookie jar can be shared across multiple requests or with an entire
182 /// client, allowing cookies to be persisted across multiple requests.
183 ///
184 /// # Availability
185 ///
186 /// This method is only available when the [`cookies`](index.html#cookies)
187 /// feature is enabled.
188 #[cfg(feature = "cookies")]
189 #[must_use = "builders have no effect if unused"]
190 fn cookie_jar(self, cookie_jar: crate::cookies::CookieJar) -> Self;
191
192 /// Enable or disable automatic decompression of the response body for
193 /// various compression algorithms as returned by the server in the
194 /// [`Content-Encoding`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Encoding)
195 /// response header.
196 ///
197 /// If set to true (the default), Isahc will automatically and transparently
198 /// decode the HTTP response body for known and available compression
199 /// algorithms. If the server returns a response with an unknown or
200 /// unavailable encoding, Isahc will return an
201 /// [`InvalidContentEncoding`](crate::error::ErrorKind::InvalidContentEncoding)
202 /// error.
203 ///
204 /// If you do not specify a specific value for the
205 /// [`Accept-Encoding`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-Encoding)
206 /// header, Isahc will set one for you automatically based on this option.
207 #[must_use = "builders have no effect if unused"]
208 fn automatic_decompression(self, decompress: bool) -> Self {
209 self.with_config(move |config| {
210 config.automatic_decompression = Some(decompress);
211 })
212 }
213
214 /// Configure the use of the `Expect` request header when sending request
215 /// bodies with HTTP/1.1.
216 ///
217 /// By default, when sending requests containing a body of large or unknown
218 /// length over HTTP/1.1, Isahc will send the request headers first without
219 /// the body and wait for the server to respond with a 100 (Continue) status
220 /// code, as defined by [RFC 7231, Section
221 /// 5.1.1](https://datatracker.ietf.org/doc/html/rfc7231#section-5.1.1).
222 /// This gives the opportunity for the server to reject the response without
223 /// needing to first transmit the request body over the network, if the body
224 /// contents are not necessary for the server to determine an appropriate
225 /// response.
226 ///
227 /// For servers that do not support this behavior and instead simply wait
228 /// for the request body without responding with a 100 (Continue), there is
229 /// a limited timeout before the response body is sent anyway without
230 /// confirmation. The default timeout is 1 second, but this can be
231 /// configured.
232 ///
233 /// The `Expect` behavior can also be disabled entirely.
234 ///
235 /// This configuration only takes effect when using HTTP/1.1.
236 ///
237 /// # Examples
238 ///
239 /// ```
240 /// use std::time::Duration;
241 /// use isahc::{
242 /// config::ExpectContinue,
243 /// prelude::*,
244 /// HttpClient,
245 /// };
246 ///
247 /// // Use the default behavior (enabled).
248 /// let client = HttpClient::builder()
249 /// .expect_continue(ExpectContinue::enabled())
250 /// // or equivalently...
251 /// .expect_continue(ExpectContinue::default())
252 /// // or equivalently...
253 /// .expect_continue(true)
254 /// .build()?;
255 ///
256 /// // Customize the timeout if the server doesn't respond with a 100
257 /// // (Continue) status.
258 /// let client = HttpClient::builder()
259 /// .expect_continue(ExpectContinue::timeout(Duration::from_millis(200)))
260 /// // or equivalently...
261 /// .expect_continue(Duration::from_millis(200))
262 /// .build()?;
263 ///
264 /// // Disable the Expect header entirely.
265 /// let client = HttpClient::builder()
266 /// .expect_continue(ExpectContinue::disabled())
267 /// // or equivalently...
268 /// .expect_continue(false)
269 /// .build()?;
270 /// # Ok::<(), isahc::Error>(())
271 /// ```
272 #[must_use = "builders have no effect if unused"]
273 fn expect_continue<T>(self, expect: T) -> Self
274 where
275 T: Into<ExpectContinue>,
276 {
277 self.with_config(move |config| {
278 config.expect_continue = Some(expect.into());
279 })
280 }
281
282 /// Set one or more default HTTP authentication methods to attempt to use
283 /// when authenticating with the server.
284 ///
285 /// Depending on the authentication schemes enabled, you will also need to
286 /// set credentials to use for authentication using
287 /// [`Configurable::credentials`].
288 ///
289 /// # Examples
290 ///
291 /// ```
292 /// use isahc::{
293 /// auth::{Authentication, Credentials},
294 /// prelude::*,
295 /// HttpClient,
296 /// };
297 ///
298 /// let client = HttpClient::builder()
299 /// .authentication(Authentication::basic() | Authentication::digest())
300 /// .credentials(Credentials::new("clark", "qwerty"))
301 /// .build()?;
302 /// # Ok::<(), isahc::Error>(())
303 /// ```
304 #[must_use = "builders have no effect if unused"]
305 fn authentication(self, authentication: Authentication) -> Self {
306 self.with_config(move |config| {
307 config.authentication = Some(authentication);
308 })
309 }
310
311 /// Set the credentials to use for HTTP authentication.
312 ///
313 /// This setting will do nothing unless you also set one or more
314 /// authentication methods using [`Configurable::authentication`].
315 #[must_use = "builders have no effect if unused"]
316 fn credentials(self, credentials: Credentials) -> Self {
317 self.with_config(move |config| {
318 config.credentials = Some(credentials);
319 })
320 }
321
322 /// Enable TCP keepalive with a given probe interval.
323 #[must_use = "builders have no effect if unused"]
324 fn tcp_keepalive(self, interval: Duration) -> Self {
325 self.with_config(move |config| {
326 config.tcp_keepalive = Some(interval);
327 })
328 }
329
330 /// Enables the `TCP_NODELAY` option on connect.
331 #[must_use = "builders have no effect if unused"]
332 fn tcp_nodelay(self) -> Self {
333 self.with_config(move |config| {
334 config.tcp_nodelay = Some(true);
335 })
336 }
337
338 /// Bind local socket connections to a particular network interface.
339 ///
340 /// # Examples
341 ///
342 /// Bind to an IP address.
343 ///
344 /// ```
345 /// use isahc::{
346 /// prelude::*,
347 /// config::NetworkInterface,
348 /// HttpClient,
349 /// Request,
350 /// };
351 /// use std::net::IpAddr;
352 ///
353 /// // Bind to an IP address.
354 /// let client = HttpClient::builder()
355 /// .interface(IpAddr::from([192, 168, 1, 2]))
356 /// .build()?;
357 ///
358 /// // Bind to an interface by name (not supported on Windows).
359 /// # #[cfg(unix)]
360 /// let client = HttpClient::builder()
361 /// .interface(NetworkInterface::name("eth0"))
362 /// .build()?;
363 ///
364 /// // Reset to using whatever interface the TCP stack finds suitable (the
365 /// // default).
366 /// let request = Request::get("https://example.org")
367 /// .interface(NetworkInterface::any())
368 /// .body(())?;
369 /// # Ok::<(), isahc::Error>(())
370 /// ```
371 #[must_use = "builders have no effect if unused"]
372 fn interface<I>(self, interface: I) -> Self
373 where
374 I: Into<NetworkInterface>,
375 {
376 self.with_config(move |config| {
377 config.interface = Some(interface.into());
378 })
379 }
380
381 /// Select a specific IP version when resolving hostnames. If a given
382 /// hostname does not resolve to an IP address of the desired version, then
383 /// the request will fail with a connection error.
384 ///
385 /// This does not affect requests with an explicit IP address as the host.
386 ///
387 /// The default is [`IpVersion::Any`].
388 #[must_use = "builders have no effect if unused"]
389 fn ip_version(self, version: IpVersion) -> Self {
390 self.with_config(move |config| {
391 config.ip_version = Some(version);
392 })
393 }
394
395 /// Specify a socket to connect to instead of the using the host and port
396 /// defined in the request URI.
397 ///
398 /// # Examples
399 ///
400 /// Connecting to a Unix socket:
401 ///
402 /// ```
403 /// use isahc::{
404 /// config::Dialer,
405 /// prelude::*,
406 /// Request,
407 /// };
408 ///
409 /// # #[cfg(unix)]
410 /// let request = Request::get("http://localhost/containers")
411 /// .dial(Dialer::unix_socket("/path/to/my.sock"))
412 /// .body(())?;
413 /// # Ok::<(), isahc::Error>(())
414 /// ```
415 ///
416 /// Connecting to a specific Internet socket address:
417 ///
418 /// ```
419 /// use isahc::{
420 /// config::Dialer,
421 /// prelude::*,
422 /// Request,
423 /// };
424 /// use std::net::Ipv4Addr;
425 ///
426 /// let request = Request::get("http://exmaple.org")
427 /// // Actually issue the request to localhost on port 8080. The host
428 /// // header will remain unchanged.
429 /// .dial(Dialer::ip_socket((Ipv4Addr::LOCALHOST, 8080)))
430 /// .body(())?;
431 /// # Ok::<(), isahc::Error>(())
432 /// ```
433 #[must_use = "builders have no effect if unused"]
434 fn dial<D>(self, dialer: D) -> Self
435 where
436 D: Into<Dialer>,
437 {
438 self.with_config(move |config| {
439 config.dial = Some(dialer.into());
440 })
441 }
442
443 /// Set a proxy to use for requests.
444 ///
445 /// The proxy protocol is specified by the URI scheme.
446 ///
447 /// - **`http`**: Proxy. Default when no scheme is specified.
448 /// - **`https`**: HTTPS Proxy. (Added in 7.52.0 for OpenSSL, GnuTLS and
449 /// NSS)
450 /// - **`socks4`**: SOCKS4 Proxy.
451 /// - **`socks4a`**: SOCKS4a Proxy. Proxy resolves URL hostname.
452 /// - **`socks5`**: SOCKS5 Proxy.
453 /// - **`socks5h`**: SOCKS5 Proxy. Proxy resolves URL hostname.
454 ///
455 /// By default no proxy will be used, unless one is specified in either the
456 /// `http_proxy` or `https_proxy` environment variables.
457 ///
458 /// Setting to `None` explicitly disables the use of a proxy.
459 ///
460 /// # Examples
461 ///
462 /// Using `http://proxy:80` as a proxy:
463 ///
464 /// ```
465 /// use isahc::{prelude::*, HttpClient};
466 ///
467 /// let client = HttpClient::builder()
468 /// .proxy(Some("http://proxy:80".parse()?))
469 /// .build()?;
470 /// # Ok::<(), Box<dyn std::error::Error>>(())
471 /// ```
472 ///
473 /// Explicitly disable the use of a proxy:
474 ///
475 /// ```
476 /// use isahc::{prelude::*, HttpClient};
477 ///
478 /// let client = HttpClient::builder()
479 /// .proxy(None)
480 /// .build()?;
481 /// # Ok::<(), Box<dyn std::error::Error>>(())
482 /// ```
483 #[must_use = "builders have no effect if unused"]
484 fn proxy(self, proxy: impl Into<Option<http::Uri>>) -> Self {
485 self.with_config(move |config| {
486 config.proxy = Some(proxy.into());
487 })
488 }
489
490 /// Disable proxy usage for the provided list of hosts.
491 ///
492 /// # Examples
493 ///
494 /// ```
495 /// use isahc::{prelude::*, HttpClient};
496 ///
497 /// let client = HttpClient::builder()
498 /// // Disable proxy for specified hosts.
499 /// .proxy_blacklist(vec!["a.com", "b.org"])
500 /// .build()?;
501 /// # Ok::<(), isahc::Error>(())
502 /// ```
503 #[must_use = "builders have no effect if unused"]
504 fn proxy_blacklist<I, T>(self, hosts: I) -> Self
505 where
506 I: IntoIterator<Item = T>,
507 T: Into<String>,
508 {
509 self.with_config(move |config| {
510 config.proxy_blacklist = Some(hosts.into_iter().map(T::into).collect());
511 })
512 }
513
514 /// Set one or more HTTP authentication methods to attempt to use when
515 /// authenticating with a proxy.
516 ///
517 /// Depending on the authentication schemes enabled, you will also need to
518 /// set credentials to use for authentication using
519 /// [`Configurable::proxy_credentials`].
520 ///
521 /// # Examples
522 ///
523 /// ```
524 /// use isahc::{
525 /// auth::{Authentication, Credentials},
526 /// prelude::*,
527 /// HttpClient,
528 /// };
529 ///
530 /// let client = HttpClient::builder()
531 /// .proxy("http://proxy:80".parse::<http::Uri>()?)
532 /// .proxy_authentication(Authentication::basic())
533 /// .proxy_credentials(Credentials::new("clark", "qwerty"))
534 /// .build()?;
535 /// # Ok::<(), Box<dyn std::error::Error>>(())
536 /// ```
537 #[must_use = "builders have no effect if unused"]
538 fn proxy_authentication(self, authentication: Authentication) -> Self {
539 self.with_config(move |config| {
540 config.proxy_authentication = Some(Proxy(authentication));
541 })
542 }
543
544 /// Set the credentials to use for proxy authentication.
545 ///
546 /// This setting will do nothing unless you also set one or more proxy
547 /// authentication methods using
548 /// [`Configurable::proxy_authentication`].
549 #[must_use = "builders have no effect if unused"]
550 fn proxy_credentials(self, credentials: Credentials) -> Self {
551 self.with_config(move |config| {
552 config.proxy_credentials = Some(Proxy(credentials));
553 })
554 }
555
556 /// Set a maximum upload speed for the request body, in bytes per second.
557 ///
558 /// The default is unlimited.
559 #[must_use = "builders have no effect if unused"]
560 fn max_upload_speed(self, max: u64) -> Self {
561 self.with_config(move |config| {
562 config.max_upload_speed = Some(max);
563 })
564 }
565
566 /// Set a maximum download speed for the response body, in bytes per second.
567 ///
568 /// The default is unlimited.
569 #[must_use = "builders have no effect if unused"]
570 fn max_download_speed(self, max: u64) -> Self {
571 self.with_config(move |config| {
572 config.max_download_speed = Some(max);
573 })
574 }
575
576 /// Set a custom SSL/TLS client certificate to use for client connections.
577 ///
578 /// If a format is not supported by the underlying SSL/TLS engine, an error
579 /// will be returned when attempting to send a request using the offending
580 /// certificate.
581 ///
582 /// The default value is none.
583 ///
584 /// # Examples
585 ///
586 /// ```no_run
587 /// use isahc::{
588 /// config::{ClientCertificate, PrivateKey},
589 /// prelude::*,
590 /// Request,
591 /// };
592 ///
593 /// let response = Request::get("localhost:3999")
594 /// .ssl_client_certificate(ClientCertificate::pem_file(
595 /// "client.pem",
596 /// PrivateKey::pem_file("key.pem", String::from("secret")),
597 /// ))
598 /// .body(())?
599 /// .send()?;
600 /// # Ok::<(), isahc::Error>(())
601 /// ```
602 ///
603 /// ```
604 /// use isahc::{
605 /// config::{ClientCertificate, PrivateKey},
606 /// prelude::*,
607 /// HttpClient,
608 /// };
609 ///
610 /// let client = HttpClient::builder()
611 /// .ssl_client_certificate(ClientCertificate::pem_file(
612 /// "client.pem",
613 /// PrivateKey::pem_file("key.pem", String::from("secret")),
614 /// ))
615 /// .build()?;
616 /// # Ok::<(), isahc::Error>(())
617 /// ```
618 #[must_use = "builders have no effect if unused"]
619 fn ssl_client_certificate(self, certificate: ClientCertificate) -> Self {
620 self.with_config(move |config| {
621 config.ssl_client_certificate = Some(certificate);
622 })
623 }
624
625 /// Set a custom SSL/TLS CA certificate bundle to use for client
626 /// connections.
627 ///
628 /// The default value is none.
629 ///
630 /// # Notes
631 ///
632 /// On Windows it may be necessary to combine this with
633 /// [`SslOption::DANGER_ACCEPT_REVOKED_CERTS`] in order to work depending on
634 /// the contents of your CA bundle.
635 ///
636 /// # Examples
637 ///
638 /// ```
639 /// use isahc::{config::CaCertificate, prelude::*, HttpClient};
640 ///
641 /// let client = HttpClient::builder()
642 /// .ssl_ca_certificate(CaCertificate::file("ca.pem"))
643 /// .build()?;
644 /// # Ok::<(), isahc::Error>(())
645 /// ```
646 #[must_use = "builders have no effect if unused"]
647 fn ssl_ca_certificate(self, certificate: CaCertificate) -> Self {
648 self.with_config(move |config| {
649 config.ssl_ca_certificate = Some(certificate);
650 })
651 }
652
653 /// Set a list of ciphers to use for SSL/TLS connections.
654 ///
655 /// The list of valid cipher names is dependent on the underlying SSL/TLS
656 /// engine in use. You can find an up-to-date list of potential cipher names
657 /// at <https://curl.haxx.se/docs/ssl-ciphers.html>.
658 ///
659 /// The default is unset and will result in the system defaults being used.
660 #[must_use = "builders have no effect if unused"]
661 fn ssl_ciphers<I, T>(self, ciphers: I) -> Self
662 where
663 I: IntoIterator<Item = T>,
664 T: Into<String>,
665 {
666 self.with_config(move |config| {
667 config.ssl_ciphers = Some(ciphers.into_iter().map(T::into).collect());
668 })
669 }
670
671 /// Set various options for this request that control SSL/TLS behavior.
672 ///
673 /// Most options are for disabling security checks that introduce security
674 /// risks, but may be required as a last resort. Note that the most secure
675 /// options are already the default and do not need to be specified.
676 ///
677 /// The default value is [`SslOption::NONE`].
678 ///
679 /// # Warning
680 ///
681 /// You should think very carefully before using this method. Using *any*
682 /// options that alter how certificates are validated can introduce
683 /// significant security vulnerabilities.
684 ///
685 /// # Examples
686 ///
687 /// ```no_run
688 /// use isahc::{config::SslOption, prelude::*, Request};
689 ///
690 /// let response = Request::get("https://badssl.com")
691 /// .ssl_options(SslOption::DANGER_ACCEPT_INVALID_CERTS | SslOption::DANGER_ACCEPT_REVOKED_CERTS)
692 /// .body(())?
693 /// .send()?;
694 /// # Ok::<(), isahc::Error>(())
695 /// ```
696 ///
697 /// ```
698 /// use isahc::{config::SslOption, prelude::*, HttpClient};
699 ///
700 /// let client = HttpClient::builder()
701 /// .ssl_options(SslOption::DANGER_ACCEPT_INVALID_CERTS | SslOption::DANGER_ACCEPT_REVOKED_CERTS)
702 /// .build()?;
703 /// # Ok::<(), isahc::Error>(())
704 /// ```
705 #[must_use = "builders have no effect if unused"]
706 fn ssl_options(self, options: SslOption) -> Self {
707 self.with_config(move |config| {
708 config.ssl_options = Some(options);
709 })
710 }
711
712 /// Enable or disable sending HTTP header names in Title-Case instead of
713 /// lowercase form.
714 ///
715 /// This option only affects user-supplied headers and does not affect
716 /// low-level headers that are automatically supplied for HTTP protocol
717 /// details, such as `Connection` and `Host` (unless you override such a
718 /// header yourself).
719 ///
720 /// This option has no effect when using HTTP/2 or newer where headers are
721 /// required to be lowercase.
722 #[must_use = "builders have no effect if unused"]
723 fn title_case_headers(self, enable: bool) -> Self {
724 self.with_config(move |config| {
725 config.title_case_headers = Some(enable);
726 })
727 }
728
729 /// Enable or disable comprehensive per-request metrics collection.
730 ///
731 /// When enabled, detailed timing metrics will be tracked while a request is
732 /// in progress, such as bytes sent and received, estimated size, DNS lookup
733 /// time, etc. For a complete list of the available metrics that can be
734 /// inspected, see the [`Metrics`](crate::Metrics) documentation.
735 ///
736 /// When enabled, to access a view of the current metrics values you can use
737 /// [`ResponseExt::metrics`](crate::ResponseExt::metrics).
738 ///
739 /// While effort is taken to optimize hot code in metrics collection, it is
740 /// likely that enabling it will have a small effect on overall throughput.
741 /// Disabling metrics may be necessary for absolute peak performance.
742 ///
743 /// By default metrics are disabled.
744 #[must_use = "builders have no effect if unused"]
745 fn metrics(self, enable: bool) -> Self {
746 self.with_config(move |config| {
747 config.enable_metrics = Some(enable);
748 })
749 }
750}
751
752/// A strategy for selecting what HTTP versions should be used when
753/// communicating with a server.
754///
755/// You can set a version negotiation strategy on a given request or on a client
756/// with [`Configurable::version_negotiation`].
757///
758/// Attempting to use an HTTP version without client-side support at runtime
759/// will result in an error. For example, using the system libcurl on an old
760/// machine may not have an HTTP/2 implementation. Using static linking and the
761/// [`http2`](../index.html#http2) crate feature can help guarantee that HTTP/2
762/// will be available to use.
763#[derive(Clone, Debug)]
764pub struct VersionNegotiation(VersionNegotiationInner);
765
766#[derive(Clone, Copy, Debug)]
767enum VersionNegotiationInner {
768 LatestCompatible,
769 Strict(curl::easy::HttpVersion),
770}
771
772impl Default for VersionNegotiation {
773 fn default() -> Self {
774 Self::latest_compatible()
775 }
776}
777
778impl VersionNegotiation {
779 /// Always prefer the latest supported version announced by the server,
780 /// falling back to older versions if not explicitly listed as supported.
781 /// This is the default.
782 ///
783 /// Secure connections will begin with a TLS handshake, after which the
784 /// highest supported HTTP version listed by the server via ALPN will be
785 /// used. Once connected, additional upgrades to newer versions may also
786 /// occur if the server lists support for it. In the future, headers such as
787 /// `Alt-Svc` will be used.
788 ///
789 /// Insecure connections always use HTTP/1.x since there is no standard
790 /// mechanism for a server to declare support for insecure HTTP versions,
791 /// and only HTTP/0.9, HTTP/1.x, and HTTP/2 support insecure transfers.
792 pub const fn latest_compatible() -> Self {
793 Self(VersionNegotiationInner::LatestCompatible)
794 }
795
796 /// Connect via HTTP/1.0 and do not attempt to use a higher version.
797 pub const fn http10() -> Self {
798 Self(VersionNegotiationInner::Strict(
799 curl::easy::HttpVersion::V10,
800 ))
801 }
802
803 /// Connect via HTTP/1.1 and do not attempt to use a higher version.
804 pub const fn http11() -> Self {
805 Self(VersionNegotiationInner::Strict(
806 curl::easy::HttpVersion::V11,
807 ))
808 }
809
810 /// Connect via HTTP/2. Failure to connect will not fall back to old
811 /// versions, unless HTTP/1.1 is negotiated via TLS ALPN before the session
812 /// begins.
813 ///
814 /// If HTTP/2 support is not compiled in, then using this strategy will
815 /// always result in an error.
816 ///
817 /// This strategy is often referred to as [HTTP/2 with Prior
818 /// Knowledge](https://http2.github.io/http2-spec/#known-http).
819 pub const fn http2() -> Self {
820 Self(VersionNegotiationInner::Strict(
821 curl::easy::HttpVersion::V2PriorKnowledge,
822 ))
823 }
824
825 /// Connect via HTTP/3. Failure to connect will not fall back to old
826 /// versions.
827 pub const fn http3() -> Self {
828 Self(VersionNegotiationInner::Strict(curl::easy::HttpVersion::V3))
829 }
830}
831
832impl SetOpt for VersionNegotiation {
833 fn set_opt<H>(&self, easy: &mut Easy2<H>) -> Result<(), curl::Error> {
834 match self.0 {
835 VersionNegotiationInner::LatestCompatible => {
836 // If HTTP/2 support is available, this basically the most
837 // lenient way of using it. Alt-Svc is used to upgrade to newer
838 // versions, and old versions are used if the server doesn't
839 // list HTTP/2 via ALPN.
840 //
841 // If HTTP/2 is not available, leaving it the default setting is
842 // the ideal behavior.
843 if is_http_version_supported(http::Version::HTTP_2) {
844 easy.http_version(curl::easy::HttpVersion::V2TLS)
845 } else {
846 Ok(())
847 }
848 }
849 VersionNegotiationInner::Strict(version) => easy.http_version(version),
850 }
851 }
852}
853
854/// Used to configure which local addresses or interfaces should be used to send
855/// network traffic from.
856///
857/// Note that this type is "lazy" in the sense that errors are not returned if
858/// the given interfaces are not checked for validity until you actually attempt
859/// to use it in a network request.
860#[derive(Clone, Debug)]
861pub struct NetworkInterface {
862 /// Interface in verbose curl format.
863 interface: Option<String>,
864}
865
866impl NetworkInterface {
867 /// Bind to whatever the networking stack finds suitable. This is the
868 /// default behavior.
869 pub fn any() -> Self {
870 Self {
871 interface: None,
872 }
873 }
874
875 /// Bind to the interface with the given name (such as `eth0`). This method
876 /// is not available on Windows as it does not really have names for network
877 /// devices.
878 ///
879 /// # Examples
880 ///
881 /// ```
882 /// # use isahc::config::NetworkInterface;
883 /// let loopback = NetworkInterface::name("lo");
884 /// let wifi = NetworkInterface::name("wlan0");
885 /// ```
886 #[cfg(unix)]
887 pub fn name(name: impl AsRef<str>) -> Self {
888 Self {
889 interface: Some(format!("if!{}", name.as_ref())),
890 }
891 }
892
893 /// Bind to the given local host or address. This can either be a host name
894 /// or an IP address.
895 ///
896 /// # Examples
897 ///
898 /// ```
899 /// # use isahc::config::NetworkInterface;
900 /// let local = NetworkInterface::host("server.local");
901 /// let addr = NetworkInterface::host("192.168.1.2");
902 /// ```
903 pub fn host(host: impl AsRef<str>) -> Self {
904 Self {
905 interface: Some(format!("host!{}", host.as_ref())),
906 }
907 }
908}
909
910impl Default for NetworkInterface {
911 fn default() -> Self {
912 Self::any()
913 }
914}
915
916impl From<IpAddr> for NetworkInterface {
917 fn from(ip: IpAddr) -> Self {
918 Self {
919 interface: Some(format!("host!{}", ip)),
920 }
921 }
922}
923
924impl SetOpt for NetworkInterface {
925 fn set_opt<H>(&self, easy: &mut Easy2<H>) -> Result<(), curl::Error> {
926 #[allow(unsafe_code)]
927 match self.interface.as_ref() {
928 Some(interface) => easy.interface(interface),
929
930 // Use raw FFI because safe wrapper doesn't let us set to null.
931 None => unsafe {
932 match curl_sys::curl_easy_setopt(easy.raw(), curl_sys::CURLOPT_INTERFACE, 0) {
933 curl_sys::CURLE_OK => Ok(()),
934 code => Err(curl::Error::new(code)),
935 }
936 },
937 }
938 }
939}
940
941/// Supported IP versions that can be used.
942#[derive(Clone, Debug)]
943pub enum IpVersion {
944 /// Use IPv4 addresses only. IPv6 addresses will be ignored.
945 V4,
946
947 /// Use IPv6 addresses only. IPv4 addresses will be ignored.
948 V6,
949
950 /// Use either IPv4 or IPv6 addresses. By default IPv6 addresses are
951 /// preferred if available, otherwise an IPv4 address will be used. IPv6
952 /// addresses are tried first by following the recommendations of [RFC
953 /// 6555 "Happy Eyeballs"](https://tools.ietf.org/html/rfc6555).
954 Any,
955}
956
957impl Default for IpVersion {
958 fn default() -> Self {
959 Self::Any
960 }
961}
962
963impl SetOpt for IpVersion {
964 fn set_opt<H>(&self, easy: &mut Easy2<H>) -> Result<(), curl::Error> {
965 easy.ip_resolve(match &self {
966 IpVersion::V4 => curl::easy::IpResolve::V4,
967 IpVersion::V6 => curl::easy::IpResolve::V6,
968 IpVersion::Any => curl::easy::IpResolve::Any,
969 })
970 }
971}
972
973/// Controls the use of the `Expect` request header when sending request bodies
974/// with HTTP/1.1.
975///
976/// By default, when sending requests containing a body of large or unknown
977/// length over HTTP/1.1, Isahc will send the request headers first without the
978/// body and wait for the server to respond with a 100 (Continue) status code,
979/// as defined by [RFC 7231, Section
980/// 5.1.1](https://datatracker.ietf.org/doc/html/rfc7231#section-5.1.1). This
981/// gives the opportunity for the server to reject the response without needing
982/// to first transmit the request body over the network, if the body contents
983/// are not necessary for the server to determine an appropriate response.
984///
985/// For servers that do not support this behavior and instead simply wait for
986/// the request body without responding with a 100 (Continue), there is a
987/// limited timeout before the response body is sent anyway without
988/// confirmation. The default timeout is 1 second, but this can be configured.
989///
990/// The `Expect` behavior can also be disabled entirely.
991///
992/// This configuration only takes effect when using HTTP/1.1.
993#[derive(Clone, Debug)]
994pub struct ExpectContinue {
995 timeout: Option<Duration>,
996}
997
998impl ExpectContinue {
999 /// Enable the use of `Expect` and wait for a 100 (Continue) response with a
1000 /// default timeout before sending a request body.
1001 pub const fn enabled() -> Self {
1002 Self::timeout(Duration::from_secs(1))
1003 }
1004
1005 /// Enable the use of `Expect` and wait for a 100 (Continue) response, up to
1006 /// the given timeout, before sending a request body.
1007 pub const fn timeout(timeout: Duration) -> Self {
1008 Self {
1009 timeout: Some(timeout),
1010 }
1011 }
1012
1013 /// Disable the use and handling of the `Expect` request header.
1014 pub const fn disabled() -> Self {
1015 Self {
1016 timeout: None,
1017 }
1018 }
1019
1020 pub(crate) fn is_disabled(&self) -> bool {
1021 self.timeout.is_none()
1022 }
1023}
1024
1025impl Default for ExpectContinue {
1026 fn default() -> Self {
1027 Self::enabled()
1028 }
1029}
1030
1031impl From<bool> for ExpectContinue {
1032 fn from(value: bool) -> Self {
1033 if value {
1034 Self::enabled()
1035 } else {
1036 Self::disabled()
1037 }
1038 }
1039}
1040
1041impl From<Duration> for ExpectContinue {
1042 fn from(value: Duration) -> Self {
1043 Self::timeout(value)
1044 }
1045}
1046
1047impl SetOpt for ExpectContinue {
1048 fn set_opt<H>(&self, easy: &mut Easy2<H>) -> Result<(), curl::Error> {
1049 if let Some(timeout) = self.timeout {
1050 easy.expect_100_timeout(timeout)
1051 } else {
1052 Ok(())
1053 }
1054 }
1055}