isahc/
client.rs

1//! The HTTP client implementation.
2
3use crate::{
4    agent::{self, AgentBuilder},
5    body::{AsyncBody, Body},
6    config::{
7        client::ClientConfig,
8        request::{RequestConfig, SetOpt, WithRequestConfig},
9        *,
10    },
11    default_headers::DefaultHeadersInterceptor,
12    error::{Error, ErrorKind},
13    handler::{RequestHandler, ResponseBodyReader},
14    headers::HasHeaders,
15    interceptor::{self, Interceptor, InterceptorObj},
16    parsing::header_to_curl_string,
17};
18use futures_lite::{
19    future::{block_on, try_zip},
20    io::AsyncRead,
21};
22use http::{
23    header::{HeaderMap, HeaderName, HeaderValue},
24    Request,
25    Response,
26};
27use once_cell::sync::Lazy;
28use std::{
29    convert::TryFrom,
30    fmt,
31    future::Future,
32    io,
33    pin::Pin,
34    sync::Arc,
35    task::{Context, Poll},
36    time::Duration,
37};
38use tracing_futures::Instrument;
39
40static USER_AGENT: Lazy<String> = Lazy::new(|| {
41    format!(
42        "curl/{} isahc/{}",
43        curl::Version::get().version(),
44        env!("CARGO_PKG_VERSION")
45    )
46});
47
48/// An HTTP client builder, capable of creating custom [`HttpClient`] instances
49/// with customized behavior.
50///
51/// Any option that can be configured per-request can also be configured on a
52/// client builder as a default setting. Request configuration is provided by
53/// the [`Configurable`] trait, which is also available in the
54/// [`prelude`](crate::prelude) module.
55///
56/// # Examples
57///
58/// ```
59/// use isahc::{
60///     config::{RedirectPolicy, VersionNegotiation},
61///     prelude::*,
62///     HttpClient,
63/// };
64/// use std::time::Duration;
65///
66/// let client = HttpClient::builder()
67///     .timeout(Duration::from_secs(60))
68///     .redirect_policy(RedirectPolicy::Limit(10))
69///     .version_negotiation(VersionNegotiation::http2())
70///     .build()?;
71/// # Ok::<(), isahc::Error>(())
72/// ```
73#[must_use = "builders have no effect if unused"]
74pub struct HttpClientBuilder {
75    agent_builder: AgentBuilder,
76    client_config: ClientConfig,
77    request_config: RequestConfig,
78    interceptors: Vec<InterceptorObj>,
79    default_headers: HeaderMap<HeaderValue>,
80    error: Option<Error>,
81
82    #[cfg(feature = "cookies")]
83    cookie_jar: Option<crate::cookies::CookieJar>,
84}
85
86impl Default for HttpClientBuilder {
87    fn default() -> Self {
88        Self::new()
89    }
90}
91
92impl HttpClientBuilder {
93    /// Create a new builder for building a custom client. All configuration
94    /// will start out with the default values.
95    ///
96    /// This is equivalent to the [`Default`] implementation.
97    pub fn new() -> Self {
98        Self {
99            agent_builder: AgentBuilder::default(),
100            client_config: ClientConfig::default(),
101            request_config: RequestConfig::client_defaults(),
102            interceptors: vec![
103                // Add redirect support. Note that this is _always_ the first,
104                // and thus the outermost, interceptor. Also note that this does
105                // not enable redirect following, it just implements support for
106                // it, if a request asks for it.
107                InterceptorObj::new(crate::redirect::RedirectInterceptor),
108            ],
109            default_headers: HeaderMap::new(),
110            error: None,
111
112            #[cfg(feature = "cookies")]
113            cookie_jar: None,
114        }
115    }
116
117    /// Enable persistent cookie handling for all requests using this client
118    /// using a shared cookie jar.
119    ///
120    /// # Examples
121    ///
122    /// ```no_run
123    /// use isahc::{prelude::*, HttpClient};
124    ///
125    /// // Create a client with a cookie jar.
126    /// let client = HttpClient::builder()
127    ///     .cookies()
128    ///     .build()?;
129    ///
130    /// // Make a request that sets a cookie.
131    /// let uri = "http://httpbin.org/cookies/set?foo=bar".parse()?;
132    /// client.get(&uri)?;
133    ///
134    /// // Get the cookie from the cookie jar.
135    /// let cookie = client.cookie_jar()
136    ///     .unwrap()
137    ///     .get_by_name(&uri, "foo")
138    ///     .unwrap();
139    /// assert_eq!(cookie, "bar");
140    ///
141    /// # Ok::<(), Box<dyn std::error::Error>>(())
142    /// ```
143    ///
144    /// # Availability
145    ///
146    /// This method is only available when the [`cookies`](index.html#cookies)
147    /// feature is enabled.
148    #[cfg(feature = "cookies")]
149    pub fn cookies(self) -> Self {
150        // Note: this method is now essentially the same as setting a default
151        // cookie jar, but remains for backwards compatibility.
152        self.cookie_jar(Default::default())
153    }
154
155    /// Add a request interceptor to the client.
156    ///
157    /// # Availability
158    ///
159    /// This method is only available when the
160    /// [`unstable-interceptors`](index.html#unstable-interceptors) feature is
161    /// enabled.
162    #[cfg(feature = "unstable-interceptors")]
163    #[inline]
164    pub fn interceptor(self, interceptor: impl Interceptor + 'static) -> Self {
165        self.interceptor_impl(interceptor)
166    }
167
168    #[allow(unused)]
169    pub(crate) fn interceptor_impl(mut self, interceptor: impl Interceptor + 'static) -> Self {
170        self.interceptors.push(InterceptorObj::new(interceptor));
171        self
172    }
173
174    /// Set a maximum number of simultaneous connections that this client is
175    /// allowed to keep open at one time.
176    ///
177    /// If set to a value greater than zero, no more than `max` connections will
178    /// be opened at one time. If executing a new request would require opening
179    /// a new connection, then the request will stay in a "pending" state until
180    /// an existing connection can be used or an active request completes and
181    /// can be closed, making room for a new connection.
182    ///
183    /// Setting this value to `0` disables the limit entirely.
184    ///
185    /// This is an effective way of limiting the number of sockets or file
186    /// descriptors that this client will open, though note that the client may
187    /// use file descriptors for purposes other than just HTTP connections.
188    ///
189    /// By default this value is `0` and no limit is enforced.
190    ///
191    /// To apply a limit per-host, see
192    /// [`HttpClientBuilder::max_connections_per_host`].
193    pub fn max_connections(mut self, max: usize) -> Self {
194        self.agent_builder = self.agent_builder.max_connections(max);
195        self
196    }
197
198    /// Set a maximum number of simultaneous connections that this client is
199    /// allowed to keep open to individual hosts at one time.
200    ///
201    /// If set to a value greater than zero, no more than `max` connections will
202    /// be opened to a single host at one time. If executing a new request would
203    /// require opening a new connection, then the request will stay in a
204    /// "pending" state until an existing connection can be used or an active
205    /// request completes and can be closed, making room for a new connection.
206    ///
207    /// Setting this value to `0` disables the limit entirely. By default this
208    /// value is `0` and no limit is enforced.
209    ///
210    /// To set a global limit across all hosts, see
211    /// [`HttpClientBuilder::max_connections`].
212    pub fn max_connections_per_host(mut self, max: usize) -> Self {
213        self.agent_builder = self.agent_builder.max_connections_per_host(max);
214        self
215    }
216
217    /// Set the size of the connection cache.
218    ///
219    /// After requests are completed, if the underlying connection is reusable,
220    /// it is added to the connection cache to be reused to reduce latency for
221    /// future requests.
222    ///
223    /// Setting the size to `0` disables connection caching for all requests
224    /// using this client.
225    ///
226    /// By default this value is unspecified. A reasonable default size will be
227    /// chosen.
228    pub fn connection_cache_size(mut self, size: usize) -> Self {
229        self.agent_builder = self.agent_builder.connection_cache_size(size);
230        self.client_config.close_connections = size == 0;
231        self
232    }
233
234    /// Set the maximum time-to-live (TTL) for connections to remain in the
235    /// connection cache.
236    ///
237    /// After requests are completed, if the underlying connection is
238    /// reusable, it is added to the connection cache to be reused to reduce
239    /// latency for future requests. This option controls how long such
240    /// connections should be still considered valid before being discarded.
241    ///
242    /// Old connections have a high risk of not working any more and thus
243    /// attempting to use them wastes time if the server has disconnected.
244    ///
245    /// The default TTL is 118 seconds.
246    pub fn connection_cache_ttl(mut self, ttl: Duration) -> Self {
247        self.client_config.connection_cache_ttl = Some(ttl);
248        self
249    }
250
251    /// Configure DNS caching.
252    ///
253    /// By default, DNS entries are cached by the client executing the request
254    /// and are used until the entry expires. Calling this method allows you to
255    /// change the entry timeout duration or disable caching completely.
256    ///
257    /// Note that DNS entry TTLs are not respected, regardless of this setting.
258    ///
259    /// By default caching is enabled with a 60 second timeout.
260    ///
261    /// # Examples
262    ///
263    /// ```
264    /// use isahc::{config::*, prelude::*, HttpClient};
265    /// use std::time::Duration;
266    ///
267    /// let client = HttpClient::builder()
268    ///     // Cache entries for 10 seconds.
269    ///     .dns_cache(Duration::from_secs(10))
270    ///     // Cache entries forever.
271    ///     .dns_cache(DnsCache::Forever)
272    ///     // Don't cache anything.
273    ///     .dns_cache(DnsCache::Disable)
274    ///     .build()?;
275    /// # Ok::<(), isahc::Error>(())
276    /// ```
277    pub fn dns_cache<C>(mut self, cache: C) -> Self
278    where
279        C: Into<DnsCache>,
280    {
281        // This option is technically supported per-request by curl, but we only
282        // expose it on the client. Since the DNS cache is shared between all
283        // requests, exposing this option per-request would actually cause the
284        // timeout to alternate values for every request with a different
285        // timeout, resulting in some confusing (but valid) behavior.
286        self.client_config.dns_cache = Some(cache.into());
287        self
288    }
289
290    /// Set a mapping of DNS resolve overrides.
291    ///
292    /// Entries in the given map will be used first before using the default DNS
293    /// resolver for host+port pairs.
294    ///
295    /// Note that DNS resolving is only performed when establishing a new
296    ///
297    /// # Examples
298    ///
299    /// ```
300    /// use isahc::{config::ResolveMap, prelude::*, HttpClient};
301    /// use std::net::IpAddr;
302    ///
303    /// let client = HttpClient::builder()
304    ///     .dns_resolve(ResolveMap::new()
305    ///         // Send requests for example.org on port 80 to 127.0.0.1.
306    ///         .add("example.org", 80, [127, 0, 0, 1]))
307    ///     .build()?;
308    /// # Ok::<(), Box<dyn std::error::Error>>(())
309    /// ```
310    pub fn dns_resolve(mut self, map: ResolveMap) -> Self {
311        // Similar to the dns_cache option, this operation actually affects all
312        // requests in a multi handle so we do not expose it per-request to
313        // avoid confusing behavior.
314        self.client_config.dns_resolve = Some(map);
315        self
316    }
317
318    /// Add a default header to be passed with every request.
319    ///
320    /// If a default header value is already defined for the given key, then a
321    /// second header value will be appended to the list and multiple header
322    /// values will be included in the request.
323    ///
324    /// If any values are defined for this header key on an outgoing request,
325    /// they will override any default header values.
326    ///
327    /// If the header key or value are malformed, [`HttpClientBuilder::build`]
328    /// will return an error.
329    ///
330    /// # Examples
331    ///
332    /// ```
333    /// use isahc::{prelude::*, HttpClient};
334    ///
335    /// let client = HttpClient::builder()
336    ///     .default_header("some-header", "some-value")
337    ///     .build()?;
338    /// # Ok::<(), Box<dyn std::error::Error>>(())
339    /// ```
340    pub fn default_header<K, V>(mut self, key: K, value: V) -> Self
341    where
342        HeaderName: TryFrom<K>,
343        <HeaderName as TryFrom<K>>::Error: Into<http::Error>,
344        HeaderValue: TryFrom<V>,
345        <HeaderValue as TryFrom<V>>::Error: Into<http::Error>,
346    {
347        match HeaderName::try_from(key) {
348            Ok(key) => match HeaderValue::try_from(value) {
349                Ok(value) => {
350                    self.default_headers.append(key, value);
351                }
352                Err(e) => {
353                    self.error = Some(Error::new(ErrorKind::ClientInitialization, e.into()));
354                }
355            },
356            Err(e) => {
357                self.error = Some(Error::new(ErrorKind::ClientInitialization, e.into()));
358            }
359        }
360        self
361    }
362
363    /// Set the default headers to include in every request, replacing any
364    /// previously set default headers.
365    ///
366    /// Headers defined on an individual request always override headers in the
367    /// default map.
368    ///
369    /// If any header keys or values are malformed, [`HttpClientBuilder::build`]
370    /// will return an error.
371    ///
372    /// # Examples
373    ///
374    /// Set default headers from a slice:
375    ///
376    /// ```
377    /// use isahc::{prelude::*, HttpClient};
378    ///
379    /// let mut builder = HttpClient::builder()
380    ///     .default_headers(&[
381    ///         ("some-header", "value1"),
382    ///         ("some-header", "value2"),
383    ///         ("some-other-header", "some-other-value"),
384    ///     ])
385    ///     .build()?;
386    /// # Ok::<(), Box<dyn std::error::Error>>(())
387    /// ```
388    ///
389    /// Using an existing header map:
390    ///
391    /// ```
392    /// use isahc::{prelude::*, HttpClient};
393    ///
394    /// let mut headers = http::HeaderMap::new();
395    /// headers.append("some-header".parse::<http::header::HeaderName>()?, "some-value".parse()?);
396    ///
397    /// let mut builder = HttpClient::builder()
398    ///     .default_headers(&headers)
399    ///     .build()?;
400    /// # Ok::<(), Box<dyn std::error::Error>>(())
401    /// ```
402    ///
403    /// Using a hashmap:
404    ///
405    /// ```
406    /// use isahc::{prelude::*, HttpClient};
407    /// use std::collections::HashMap;
408    ///
409    /// let mut headers = HashMap::new();
410    /// headers.insert("some-header", "some-value");
411    ///
412    /// let mut builder = HttpClient::builder()
413    ///     .default_headers(headers)
414    ///     .build()?;
415    /// # Ok::<(), Box<dyn std::error::Error>>(())
416    /// ```
417    pub fn default_headers<K, V, I, P>(mut self, headers: I) -> Self
418    where
419        HeaderName: TryFrom<K>,
420        <HeaderName as TryFrom<K>>::Error: Into<http::Error>,
421        HeaderValue: TryFrom<V>,
422        <HeaderValue as TryFrom<V>>::Error: Into<http::Error>,
423        I: IntoIterator<Item = P>,
424        P: HeaderPair<K, V>,
425    {
426        self.default_headers.clear();
427
428        for (key, value) in headers.into_iter().map(HeaderPair::pair) {
429            self = self.default_header(key, value);
430        }
431
432        self
433    }
434
435    /// Build an [`HttpClient`] using the configured options.
436    ///
437    /// If the client fails to initialize, an error will be returned.
438    #[allow(unused_mut)]
439    pub fn build(mut self) -> Result<HttpClient, Error> {
440        if let Some(err) = self.error {
441            return Err(err);
442        }
443
444        // Add cookie interceptor if enabled.
445        #[cfg(feature = "cookies")]
446        {
447            let jar = self.cookie_jar.clone();
448            self = self.interceptor_impl(crate::cookies::interceptor::CookieInterceptor::new(jar));
449        }
450
451        // Add default header interceptor if any default headers were specified.
452        if !self.default_headers.is_empty() {
453            let default_headers = std::mem::take(&mut self.default_headers);
454            self = self.interceptor_impl(DefaultHeadersInterceptor::from(default_headers));
455        }
456
457        #[cfg(not(feature = "cookies"))]
458        let inner = Inner {
459            agent: self
460                .agent_builder
461                .spawn()
462                .map_err(|e| Error::new(ErrorKind::ClientInitialization, e))?,
463            client_config: self.client_config,
464            request_config: self.request_config,
465            interceptors: self.interceptors,
466        };
467
468        #[cfg(feature = "cookies")]
469        let inner = Inner {
470            agent: self
471                .agent_builder
472                .spawn()
473                .map_err(|e| Error::new(ErrorKind::ClientInitialization, e))?,
474            client_config: self.client_config,
475            request_config: self.request_config,
476            interceptors: self.interceptors,
477            cookie_jar: self.cookie_jar,
478        };
479
480        Ok(HttpClient {
481            inner: Arc::new(inner),
482        })
483    }
484}
485
486impl Configurable for HttpClientBuilder {
487    #[cfg(feature = "cookies")]
488    fn cookie_jar(mut self, cookie_jar: crate::cookies::CookieJar) -> Self {
489        self.cookie_jar = Some(cookie_jar);
490        self
491    }
492}
493
494impl WithRequestConfig for HttpClientBuilder {
495    #[inline]
496    fn with_config(mut self, f: impl FnOnce(&mut RequestConfig)) -> Self {
497        f(&mut self.request_config);
498        self
499    }
500}
501
502impl fmt::Debug for HttpClientBuilder {
503    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
504        f.debug_struct("HttpClientBuilder").finish()
505    }
506}
507
508/// Helper trait for defining key-value pair types that can be dereferenced into
509/// a tuple from a reference.
510///
511/// This trait is sealed and cannot be implemented for types outside of Isahc.
512pub trait HeaderPair<K, V> {
513    fn pair(self) -> (K, V);
514}
515
516impl<K, V> HeaderPair<K, V> for (K, V) {
517    fn pair(self) -> (K, V) {
518        self
519    }
520}
521
522impl<'a, K: Copy, V: Copy> HeaderPair<K, V> for &'a (K, V) {
523    fn pair(self) -> (K, V) {
524        (self.0, self.1)
525    }
526}
527
528/// An HTTP client for making requests.
529///
530/// An [`HttpClient`] instance acts as a session for executing one or more HTTP
531/// requests, and also allows you to set common protocol settings that should be
532/// applied to all requests made with the client.
533///
534/// [`HttpClient`] is entirely thread-safe, and implements both [`Send`] and
535/// [`Sync`]. You are free to create clients outside the context of the "main"
536/// thread, or move them between threads. You can even invoke many requests
537/// simultaneously from multiple threads, since doing so doesn't need a mutable
538/// reference to the client. This is fairly cheap to do as well, since
539/// internally requests use lock-free message passing to get things going.
540///
541/// The client maintains a connection pool internally and is not cheap to
542/// create, so we recommend creating a client once and re-using it throughout
543/// your code. Creating a new client for every request would decrease
544/// performance significantly, and might cause errors to occur under high
545/// workloads, caused by creating too many system resources like sockets or
546/// threads.
547///
548/// It is not universally true that you should use exactly one client instance
549/// in an application. All HTTP requests made with the same client will share
550/// any session-wide state, like cookies or persistent connections. It may be
551/// the case that it is better to create separate clients for separate areas of
552/// an application if they have separate concerns or are making calls to
553/// different servers. If you are creating an API client library, that might be
554/// a good place to maintain your own internal client.
555///
556/// # Examples
557///
558/// ```no_run
559/// use isahc::{prelude::*, HttpClient};
560///
561/// // Create a new client using reasonable defaults.
562/// let client = HttpClient::new()?;
563///
564/// // Make some requests.
565/// let mut response = client.get("https://example.org")?;
566/// assert!(response.status().is_success());
567///
568/// println!("Response:\n{}", response.text()?);
569/// # Ok::<(), isahc::Error>(())
570/// ```
571///
572/// Customizing the client configuration:
573///
574/// ```no_run
575/// use isahc::{
576///     config::{RedirectPolicy, VersionNegotiation},
577///     prelude::*,
578///     HttpClient,
579/// };
580/// use std::time::Duration;
581///
582/// let client = HttpClient::builder()
583///     .version_negotiation(VersionNegotiation::http11())
584///     .redirect_policy(RedirectPolicy::Limit(10))
585///     .timeout(Duration::from_secs(20))
586///     // May return an error if there's something wrong with our configuration
587///     // or if the client failed to start up.
588///     .build()?;
589///
590/// let response = client.get("https://example.org")?;
591/// assert!(response.status().is_success());
592/// # Ok::<(), isahc::Error>(())
593/// ```
594///
595/// See the documentation on [`HttpClientBuilder`] for a comprehensive look at
596/// what can be configured.
597#[derive(Clone)]
598pub struct HttpClient {
599    inner: Arc<Inner>,
600}
601
602struct Inner {
603    /// This is how we talk to our background agent thread.
604    agent: agent::Handle,
605
606    /// Client-wide request configuration.
607    client_config: ClientConfig,
608
609    /// Default request configuration to use if not specified in a request.
610    request_config: RequestConfig,
611
612    /// Registered interceptors that requests should pass through.
613    interceptors: Vec<InterceptorObj>,
614
615    /// Configured cookie jar, if any.
616    #[cfg(feature = "cookies")]
617    cookie_jar: Option<crate::cookies::CookieJar>,
618}
619
620impl HttpClient {
621    /// Create a new HTTP client using the default configuration.
622    ///
623    /// If the client fails to initialize, an error will be returned.
624    pub fn new() -> Result<Self, Error> {
625        HttpClientBuilder::default().build()
626    }
627
628    /// Get a reference to a global client instance.
629    ///
630    /// TODO: Stabilize.
631    pub(crate) fn shared() -> &'static Self {
632        static SHARED: Lazy<HttpClient> =
633            Lazy::new(|| HttpClient::new().expect("shared client failed to initialize"));
634
635        &SHARED
636    }
637
638    /// Create a new [`HttpClientBuilder`] for building a custom client.
639    pub fn builder() -> HttpClientBuilder {
640        HttpClientBuilder::default()
641    }
642
643    /// Get the configured cookie jar for this HTTP client, if any.
644    ///
645    /// # Availability
646    ///
647    /// This method is only available when the [`cookies`](index.html#cookies)
648    /// feature is enabled.
649    #[cfg(feature = "cookies")]
650    pub fn cookie_jar(&self) -> Option<&crate::cookies::CookieJar> {
651        self.inner.cookie_jar.as_ref()
652    }
653
654    /// Send a GET request to the given URI.
655    ///
656    /// To customize the request further, see [`HttpClient::send`]. To execute
657    /// the request asynchronously, see [`HttpClient::get_async`].
658    ///
659    /// # Examples
660    ///
661    /// ```no_run
662    /// use isahc::{prelude::*, HttpClient};
663    ///
664    /// let client = HttpClient::new()?;
665    /// let mut response = client.get("https://example.org")?;
666    /// println!("{}", response.text()?);
667    /// # Ok::<(), isahc::Error>(())
668    /// ```
669    #[inline]
670    pub fn get<U>(&self, uri: U) -> Result<Response<Body>, Error>
671    where
672        http::Uri: TryFrom<U>,
673        <http::Uri as TryFrom<U>>::Error: Into<http::Error>,
674    {
675        match http::Request::get(uri).body(()) {
676            Ok(request) => self.send(request),
677            Err(e) => Err(Error::from_any(e)),
678        }
679    }
680
681    /// Send a GET request to the given URI asynchronously.
682    ///
683    /// To customize the request further, see [`HttpClient::send_async`]. To
684    /// execute the request synchronously, see [`HttpClient::get`].
685    pub fn get_async<U>(&self, uri: U) -> ResponseFuture<'_>
686    where
687        http::Uri: TryFrom<U>,
688        <http::Uri as TryFrom<U>>::Error: Into<http::Error>,
689    {
690        match http::Request::get(uri).body(()) {
691            Ok(request) => self.send_async(request),
692            Err(e) => ResponseFuture::error(Error::from_any(e)),
693        }
694    }
695
696    /// Send a HEAD request to the given URI.
697    ///
698    /// To customize the request further, see [`HttpClient::send`]. To execute
699    /// the request asynchronously, see [`HttpClient::head_async`].
700    ///
701    /// # Examples
702    ///
703    /// ```no_run
704    /// use isahc::{prelude::*, HttpClient};
705    ///
706    /// let client = HttpClient::new()?;
707    /// let response = client.head("https://example.org")?;
708    /// println!("Page size: {:?}", response.headers()["content-length"]);
709    /// # Ok::<(), isahc::Error>(())
710    /// ```
711    #[inline]
712    pub fn head<U>(&self, uri: U) -> Result<Response<Body>, Error>
713    where
714        http::Uri: TryFrom<U>,
715        <http::Uri as TryFrom<U>>::Error: Into<http::Error>,
716    {
717        match http::Request::head(uri).body(()) {
718            Ok(request) => self.send(request),
719            Err(e) => Err(Error::from_any(e)),
720        }
721    }
722
723    /// Send a HEAD request to the given URI asynchronously.
724    ///
725    /// To customize the request further, see [`HttpClient::send_async`]. To
726    /// execute the request synchronously, see [`HttpClient::head`].
727    pub fn head_async<U>(&self, uri: U) -> ResponseFuture<'_>
728    where
729        http::Uri: TryFrom<U>,
730        <http::Uri as TryFrom<U>>::Error: Into<http::Error>,
731    {
732        match http::Request::head(uri).body(()) {
733            Ok(request) => self.send_async(request),
734            Err(e) => ResponseFuture::error(Error::from_any(e)),
735        }
736    }
737
738    /// Send a POST request to the given URI with a given request body.
739    ///
740    /// To customize the request further, see [`HttpClient::send`]. To execute
741    /// the request asynchronously, see [`HttpClient::post_async`].
742    ///
743    /// # Examples
744    ///
745    /// ```no_run
746    /// use isahc::{prelude::*, HttpClient};
747    ///
748    /// let client = HttpClient::new()?;
749    ///
750    /// let response = client.post("https://httpbin.org/post", r#"{
751    ///     "speed": "fast",
752    ///     "cool_name": true
753    /// }"#)?;
754    /// # Ok::<(), isahc::Error>(())
755    #[inline]
756    pub fn post<U, B>(&self, uri: U, body: B) -> Result<Response<Body>, Error>
757    where
758        http::Uri: TryFrom<U>,
759        <http::Uri as TryFrom<U>>::Error: Into<http::Error>,
760        B: Into<Body>,
761    {
762        match http::Request::post(uri).body(body) {
763            Ok(request) => self.send(request),
764            Err(e) => Err(Error::from_any(e)),
765        }
766    }
767
768    /// Send a POST request to the given URI asynchronously with a given request
769    /// body.
770    ///
771    /// To customize the request further, see [`HttpClient::send_async`]. To
772    /// execute the request synchronously, see [`HttpClient::post`].
773    pub fn post_async<U, B>(&self, uri: U, body: B) -> ResponseFuture<'_>
774    where
775        http::Uri: TryFrom<U>,
776        <http::Uri as TryFrom<U>>::Error: Into<http::Error>,
777        B: Into<AsyncBody>,
778    {
779        match http::Request::post(uri).body(body) {
780            Ok(request) => self.send_async(request),
781            Err(e) => ResponseFuture::error(Error::from_any(e)),
782        }
783    }
784
785    /// Send a PUT request to the given URI with a given request body.
786    ///
787    /// To customize the request further, see [`HttpClient::send`]. To execute
788    /// the request asynchronously, see [`HttpClient::put_async`].
789    ///
790    /// # Examples
791    ///
792    /// ```no_run
793    /// use isahc::{prelude::*, HttpClient};
794    ///
795    /// let client = HttpClient::new()?;
796    ///
797    /// let response = client.put("https://httpbin.org/put", r#"{
798    ///     "speed": "fast",
799    ///     "cool_name": true
800    /// }"#)?;
801    /// # Ok::<(), isahc::Error>(())
802    /// ```
803    #[inline]
804    pub fn put<U, B>(&self, uri: U, body: B) -> Result<Response<Body>, Error>
805    where
806        http::Uri: TryFrom<U>,
807        <http::Uri as TryFrom<U>>::Error: Into<http::Error>,
808        B: Into<Body>,
809    {
810        match http::Request::put(uri).body(body) {
811            Ok(request) => self.send(request),
812            Err(e) => Err(Error::from_any(e)),
813        }
814    }
815
816    /// Send a PUT request to the given URI asynchronously with a given request
817    /// body.
818    ///
819    /// To customize the request further, see [`HttpClient::send_async`]. To
820    /// execute the request synchronously, see [`HttpClient::put`].
821    pub fn put_async<U, B>(&self, uri: U, body: B) -> ResponseFuture<'_>
822    where
823        http::Uri: TryFrom<U>,
824        <http::Uri as TryFrom<U>>::Error: Into<http::Error>,
825        B: Into<AsyncBody>,
826    {
827        match http::Request::put(uri).body(body) {
828            Ok(request) => self.send_async(request),
829            Err(e) => ResponseFuture::error(Error::from_any(e)),
830        }
831    }
832
833    /// Send a DELETE request to the given URI.
834    ///
835    /// To customize the request further, see [`HttpClient::send`]. To execute
836    /// the request asynchronously, see [`HttpClient::delete_async`].
837    #[inline]
838    pub fn delete<U>(&self, uri: U) -> Result<Response<Body>, Error>
839    where
840        http::Uri: TryFrom<U>,
841        <http::Uri as TryFrom<U>>::Error: Into<http::Error>,
842    {
843        match http::Request::delete(uri).body(()) {
844            Ok(request) => self.send(request),
845            Err(e) => Err(Error::from_any(e)),
846        }
847    }
848
849    /// Send a DELETE request to the given URI asynchronously.
850    ///
851    /// To customize the request further, see [`HttpClient::send_async`]. To
852    /// execute the request synchronously, see [`HttpClient::delete`].
853    pub fn delete_async<U>(&self, uri: U) -> ResponseFuture<'_>
854    where
855        http::Uri: TryFrom<U>,
856        <http::Uri as TryFrom<U>>::Error: Into<http::Error>,
857    {
858        match http::Request::delete(uri).body(()) {
859            Ok(request) => self.send_async(request),
860            Err(e) => ResponseFuture::error(Error::from_any(e)),
861        }
862    }
863
864    /// Send an HTTP request and return the HTTP response.
865    ///
866    /// Upon success, will return a [`Response`] containing the status code,
867    /// response headers, and response body from the server. The [`Response`] is
868    /// returned as soon as the HTTP response headers are received; the
869    /// connection will remain open to stream the response body in real time.
870    /// Dropping the response body without fully consuming it will close the
871    /// connection early without downloading the rest of the response body.
872    ///
873    /// The response body is provided as a stream that may only be consumed
874    /// once. If you need to inspect the response body more than once, you will
875    /// have to either read it into memory or write it to a file.
876    ///
877    /// The response body is not a direct stream from the server, but uses its
878    /// own buffering mechanisms internally for performance. It is therefore
879    /// undesirable to wrap the body in additional buffering readers.
880    ///
881    /// _Note that the actual underlying socket connection isn't necessarily
882    /// closed on drop. It may remain open to be reused if pipelining is being
883    /// used, the connection is configured as `keep-alive`, and so on._
884    ///
885    /// This client's configuration can be overridden for this request by
886    /// configuring the request using methods provided by the [`Configurable`]
887    /// trait.
888    ///
889    /// To execute a request asynchronously, see [`HttpClient::send_async`].
890    ///
891    /// # Examples
892    ///
893    /// ```no_run
894    /// use isahc::{prelude::*, HttpClient, Request};
895    ///
896    /// let client = HttpClient::new()?;
897    ///
898    /// let request = Request::post("https://httpbin.org/post")
899    ///     .header("Content-Type", "application/json")
900    ///     .body(r#"{
901    ///         "speed": "fast",
902    ///         "cool_name": true
903    ///     }"#)?;
904    ///
905    /// let response = client.send(request)?;
906    /// assert!(response.status().is_success());
907    /// # Ok::<(), isahc::Error>(())
908    /// ```
909    pub fn send<B>(&self, request: Request<B>) -> Result<Response<Body>, Error>
910    where
911        B: Into<Body>,
912    {
913        let span = tracing::debug_span!(
914            "send",
915            method = ?request.method(),
916            uri = ?request.uri(),
917        );
918
919        let mut writer_maybe = None;
920
921        let request = request.map(|body| {
922            let (async_body, writer) = body.into().into_async();
923            writer_maybe = writer;
924            async_body
925        });
926
927        let response = block_on(
928            async move {
929                // Instead of simply blocking the current thread until the response
930                // is received, we can use the current thread to read from the
931                // request body synchronously while concurrently waiting for the
932                // response.
933                if let Some(mut writer) = writer_maybe {
934                    // Note that the `send_async` future is given first; this
935                    // ensures that it is polled first and thus the request is
936                    // initiated before we attempt to write the request body.
937                    let (response, _) = try_zip(self.send_async_inner(request), async move {
938                        writer.write().await.map_err(Error::from)
939                    })
940                    .await?;
941
942                    Ok(response)
943                } else {
944                    self.send_async_inner(request).await
945                }
946            }
947            .instrument(span),
948        )?;
949
950        Ok(response.map(|body| body.into_sync()))
951    }
952
953    /// Send an HTTP request and return the HTTP response asynchronously.
954    ///
955    /// Upon success, will return a [`Response`] containing the status code,
956    /// response headers, and response body from the server. The [`Response`] is
957    /// returned as soon as the HTTP response headers are received; the
958    /// connection will remain open to stream the response body in real time.
959    /// Dropping the response body without fully consuming it will close the
960    /// connection early without downloading the rest of the response body.
961    ///
962    /// The response body is provided as a stream that may only be consumed
963    /// once. If you need to inspect the response body more than once, you will
964    /// have to either read it into memory or write it to a file.
965    ///
966    /// The response body is not a direct stream from the server, but uses its
967    /// own buffering mechanisms internally for performance. It is therefore
968    /// undesirable to wrap the body in additional buffering readers.
969    ///
970    /// _Note that the actual underlying socket connection isn't necessarily
971    /// closed on drop. It may remain open to be reused if pipelining is being
972    /// used, the connection is configured as `keep-alive`, and so on._
973    ///
974    /// This client's configuration can be overridden for this request by
975    /// configuring the request using methods provided by the [`Configurable`]
976    /// trait.
977    ///
978    /// To execute a request synchronously, see [`HttpClient::send`].
979    ///
980    /// # Examples
981    ///
982    /// ```no_run
983    /// # async fn run() -> Result<(), isahc::Error> {
984    /// use isahc::{prelude::*, HttpClient, Request};
985    ///
986    /// let client = HttpClient::new()?;
987    ///
988    /// let request = Request::post("https://httpbin.org/post")
989    ///     .header("Content-Type", "application/json")
990    ///     .body(r#"{
991    ///         "speed": "fast",
992    ///         "cool_name": true
993    ///     }"#)?;
994    ///
995    /// let response = client.send_async(request).await?;
996    /// assert!(response.status().is_success());
997    /// # Ok(()) }
998    /// ```
999    #[inline]
1000    pub fn send_async<B>(&self, request: Request<B>) -> ResponseFuture<'_>
1001    where
1002        B: Into<AsyncBody>,
1003    {
1004        let span = tracing::debug_span!(
1005            "send_async",
1006            method = ?request.method(),
1007            uri = ?request.uri(),
1008        );
1009
1010        ResponseFuture::new(
1011            self.send_async_inner(request.map(Into::into))
1012                .instrument(span),
1013        )
1014    }
1015
1016    /// Actually send the request. All the public methods go through here.
1017    async fn send_async_inner(
1018        &self,
1019        mut request: Request<AsyncBody>,
1020    ) -> Result<Response<AsyncBody>, Error> {
1021        // Populate request config, creating if necessary.
1022        if let Some(config) = request.extensions_mut().get_mut::<RequestConfig>() {
1023            // Merge request configuration with defaults.
1024            config.merge(&self.inner.request_config);
1025        } else {
1026            request
1027                .extensions_mut()
1028                .insert(self.inner.request_config.clone());
1029        }
1030
1031        let ctx = interceptor::Context {
1032            invoker: Arc::new(self),
1033            interceptors: &self.inner.interceptors,
1034        };
1035
1036        ctx.send(request).await
1037    }
1038
1039    fn create_easy_handle(
1040        &self,
1041        mut request: Request<AsyncBody>,
1042    ) -> Result<
1043        (
1044            curl::easy::Easy2<RequestHandler>,
1045            impl Future<Output = Result<Response<ResponseBodyReader>, Error>>,
1046        ),
1047        curl::Error,
1048    > {
1049        // Prepare the request plumbing.
1050        let body = std::mem::take(request.body_mut());
1051        let has_body = !body.is_empty();
1052        let body_length = body.len();
1053        let (handler, future) = RequestHandler::new(body);
1054
1055        let mut easy = curl::easy::Easy2::new(handler);
1056
1057        // Set whether curl should generate verbose debug data for us to log.
1058        easy.verbose(easy.get_ref().is_debug_enabled())?;
1059
1060        // Disable connection reuse logs if connection cache is disabled.
1061        if self.inner.client_config.close_connections {
1062            easy.get_mut().disable_connection_reuse_log = true;
1063        }
1064
1065        easy.signal(false)?;
1066
1067        let request_config = request
1068            .extensions()
1069            .get::<RequestConfig>()
1070            .unwrap();
1071
1072        request_config.set_opt(&mut easy)?;
1073        self.inner.client_config.set_opt(&mut easy)?;
1074
1075        // Check if we need to disable the Expect header.
1076        let disable_expect_header = request_config.expect_continue
1077            .as_ref()
1078            .map(|x| x.is_disabled())
1079            .unwrap_or_default();
1080
1081        // Set the HTTP method to use. Curl ties in behavior with the request
1082        // method, so we need to configure this carefully.
1083        #[allow(indirect_structural_match)]
1084        match (request.method(), has_body) {
1085            // Normal GET request.
1086            (&http::Method::GET, false) => {
1087                easy.get(true)?;
1088            }
1089            // Normal HEAD request.
1090            (&http::Method::HEAD, false) => {
1091                easy.nobody(true)?;
1092            }
1093            // POST requests have special redirect behavior.
1094            (&http::Method::POST, _) => {
1095                easy.post(true)?;
1096            }
1097            // Normal PUT request.
1098            (&http::Method::PUT, _) => {
1099                easy.upload(true)?;
1100            }
1101            // Default case is to either treat request like a GET or PUT.
1102            (method, has_body) => {
1103                easy.upload(has_body)?;
1104                easy.custom_request(method.as_str())?;
1105            }
1106        }
1107
1108        easy.url(&uri_to_string(request.uri()))?;
1109
1110        // If the request has a body, then we either need to tell curl how large
1111        // the body is if we know it, or tell curl to use chunked encoding. If
1112        // we do neither, curl will simply not send the body without warning.
1113        if has_body {
1114            // Use length given in Content-Length header, or the size defined by
1115            // the body itself.
1116            let body_length = request
1117                .headers()
1118                .get("Content-Length")
1119                .and_then(|value| value.to_str().ok())
1120                .and_then(|value| value.parse().ok())
1121                .or(body_length);
1122
1123            if let Some(len) = body_length {
1124                if request.method() == http::Method::POST {
1125                    easy.post_field_size(len)?;
1126                } else {
1127                    easy.in_filesize(len)?;
1128                }
1129            } else {
1130                // Set the Transfer-Encoding header to instruct curl to use
1131                // chunked encoding. Replaces any existing values that may be
1132                // incorrect.
1133                request.headers_mut().insert(
1134                    "Transfer-Encoding",
1135                    http::header::HeaderValue::from_static("chunked"),
1136                );
1137            }
1138        }
1139
1140        // Generate a header list for curl.
1141        let mut headers = curl::easy::List::new();
1142
1143        let title_case = request
1144            .extensions()
1145            .get::<RequestConfig>()
1146            .unwrap()
1147            .title_case_headers
1148            .unwrap_or(false);
1149
1150        for (name, value) in request.headers().iter() {
1151            headers.append(&header_to_curl_string(name, value, title_case))?;
1152        }
1153
1154        if disable_expect_header {
1155            headers.append("Expect:")?;
1156        }
1157
1158        easy.http_headers(headers)?;
1159
1160        Ok((easy, future))
1161    }
1162}
1163
1164impl crate::interceptor::Invoke for &HttpClient {
1165    fn invoke(
1166        &self,
1167        mut request: Request<AsyncBody>,
1168    ) -> crate::interceptor::InterceptorFuture<'_, Error> {
1169        Box::pin(async move {
1170            let is_head_request = request.method() == http::Method::HEAD;
1171
1172            // Set default user agent if not specified.
1173            request
1174                .headers_mut()
1175                .entry(http::header::USER_AGENT)
1176                .or_insert(USER_AGENT.parse().unwrap());
1177
1178            // Check if automatic decompression is enabled; we'll need to know
1179            // this later after the response is sent.
1180            let is_automatic_decompression = request
1181                .extensions()
1182                .get::<RequestConfig>()
1183                .unwrap()
1184                .automatic_decompression
1185                .unwrap_or(false);
1186
1187            // Create and configure a curl easy handle to fulfil the request.
1188            let (easy, future) = self.create_easy_handle(request).map_err(Error::from_any)?;
1189
1190            // Send the request to the agent to be executed.
1191            self.inner.agent.submit_request(easy)?;
1192
1193            // Await for the response headers.
1194            let response = future.await?;
1195
1196            // If a Content-Length header is present, include that information in
1197            // the body as well.
1198            let body_len = response.content_length().filter(|_| {
1199                // If automatic decompression is enabled, and will likely be
1200                // selected, then the value of Content-Length does not indicate
1201                // the uncompressed body length and merely the compressed data
1202                // length. If it looks like we are in this scenario then we
1203                // ignore the Content-Length, since it can only cause confusion
1204                // when included with the body.
1205                if is_automatic_decompression {
1206                    if let Some(value) = response.headers().get(http::header::CONTENT_ENCODING) {
1207                        if value != "identity" {
1208                            return false;
1209                        }
1210                    }
1211                }
1212
1213                true
1214            });
1215
1216            // Convert the reader into an opaque Body.
1217            Ok(response.map(|reader| {
1218                if is_head_request {
1219                    AsyncBody::empty()
1220                } else {
1221                    let body = ResponseBody {
1222                        inner: reader,
1223                        // Extend the lifetime of the agent by including a reference
1224                        // to its handle in the response body.
1225                        _client: (*self).clone(),
1226                    };
1227
1228                    if let Some(len) = body_len {
1229                        AsyncBody::from_reader_sized(body, len)
1230                    } else {
1231                        AsyncBody::from_reader(body)
1232                    }
1233                }
1234            }))
1235        })
1236    }
1237}
1238
1239impl fmt::Debug for HttpClient {
1240    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1241        f.debug_struct("HttpClient").finish()
1242    }
1243}
1244
1245/// A future for a request being executed.
1246#[must_use = "futures do nothing unless you `.await` or poll them"]
1247pub struct ResponseFuture<'c>(Pin<Box<dyn Future<Output = <Self as Future>::Output> + 'c + Send>>);
1248
1249impl<'c> ResponseFuture<'c> {
1250    fn new<F>(future: F) -> Self
1251    where
1252        F: Future<Output = <Self as Future>::Output> + Send + 'c,
1253    {
1254        ResponseFuture(Box::pin(future))
1255    }
1256
1257    fn error(error: Error) -> Self {
1258        Self::new(async move { Err(error) })
1259    }
1260}
1261
1262impl Future for ResponseFuture<'_> {
1263    type Output = Result<Response<AsyncBody>, Error>;
1264
1265    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
1266        self.0.as_mut().poll(cx)
1267    }
1268}
1269
1270impl<'c> fmt::Debug for ResponseFuture<'c> {
1271    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1272        f.debug_struct("ResponseFuture").finish()
1273    }
1274}
1275
1276/// Response body stream. Holds a reference to the agent to ensure it is kept
1277/// alive until at least this transfer is complete.
1278struct ResponseBody {
1279    inner: ResponseBodyReader,
1280    _client: HttpClient,
1281}
1282
1283impl AsyncRead for ResponseBody {
1284    fn poll_read(
1285        mut self: Pin<&mut Self>,
1286        cx: &mut Context<'_>,
1287        buf: &mut [u8],
1288    ) -> Poll<io::Result<usize>> {
1289        let inner = Pin::new(&mut self.inner);
1290        inner.poll_read(cx, buf)
1291    }
1292}
1293
1294/// Convert a URI to a string. This implementation is a bit faster than the
1295/// `Display` implementation that avoids the `std::fmt` machinery.
1296fn uri_to_string(uri: &http::Uri) -> String {
1297    let mut s = String::new();
1298
1299    if let Some(scheme) = uri.scheme() {
1300        s.push_str(scheme.as_str());
1301        s.push_str("://");
1302    }
1303
1304    if let Some(authority) = uri.authority() {
1305        s.push_str(authority.as_str());
1306    }
1307
1308    s.push_str(uri.path());
1309
1310    if let Some(query) = uri.query() {
1311        s.push('?');
1312        s.push_str(query);
1313    }
1314
1315    s
1316}
1317
1318#[cfg(test)]
1319mod tests {
1320    use super::*;
1321
1322    static_assertions::assert_impl_all!(HttpClient: Send, Sync);
1323    static_assertions::assert_impl_all!(HttpClientBuilder: Send);
1324
1325    #[test]
1326    fn test_default_header() {
1327        let client = HttpClientBuilder::new()
1328            .default_header("some-key", "some-value")
1329            .build();
1330        match client {
1331            Ok(_) => assert!(true),
1332            Err(_) => assert!(false),
1333        }
1334    }
1335
1336    #[test]
1337    fn test_default_headers_mut() {
1338        let mut builder = HttpClientBuilder::new().default_header("some-key", "some-value");
1339        let headers_map = &mut builder.default_headers;
1340        assert!(headers_map.len() == 1);
1341
1342        let mut builder = HttpClientBuilder::new()
1343            .default_header("some-key", "some-value1")
1344            .default_header("some-key", "some-value2");
1345        let headers_map = &mut builder.default_headers;
1346
1347        assert!(headers_map.len() == 2);
1348
1349        let mut builder = HttpClientBuilder::new();
1350        let header_map = &mut builder.default_headers;
1351        assert!(header_map.is_empty())
1352    }
1353}