ureq/
lib.rs

1#![forbid(unsafe_code)]
2#![warn(clippy::all)]
3// new is just more readable than ..Default::default().
4#![allow(clippy::new_without_default)]
5// the matches! macro is obscure and not widely known.
6#![allow(clippy::match_like_matches_macro)]
7// we're not changing public api due to a lint.
8#![allow(clippy::upper_case_acronyms)]
9#![allow(clippy::result_large_err)]
10#![allow(clippy::only_used_in_recursion)]
11// println!("{var}") doesn't allow even the simplest expressions for var,
12// such as "{foo.var}" – hence this lint forces us to have inconsistent
13// formatting args. I prefer a lint that forbid "{var}".
14#![allow(clippy::uninlined_format_args)]
15// if we want a range, we will make a range.
16#![allow(clippy::manual_range_patterns)]
17#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))]
18
19//!<div align="center">
20//!  <!-- Version -->
21//!  <a href="https://crates.io/crates/ureq">
22//!    <img src="https://img.shields.io/crates/v/ureq.svg?style=flat-square"
23//!    alt="Crates.io version" />
24//!  </a>
25//!  <!-- Docs -->
26//!  <a href="https://docs.rs/ureq">
27//!    <img src="https://img.shields.io/badge/docs-latest-blue.svg?style=flat-square"
28//!      alt="docs.rs docs" />
29//!  </a>
30//!  <!-- Downloads -->
31//!  <a href="https://crates.io/crates/ureq">
32//!    <img src="https://img.shields.io/crates/d/ureq.svg?style=flat-square"
33//!      alt="Crates.io downloads" />
34//!  </a>
35//!</div>
36//!
37//! A simple, safe HTTP client.
38//!
39//! > [!NOTE]
40//! > * 2.12.x is MSRV 1.71
41//! > * 2.11.x is MSRV 1.67
42//! >
43//! > For both these lines, we will release patch version pinning dependencies as needed to
44//! > retain the MSRV. If we are bumping MSRV, that will require a minor version bump.
45//!
46//! > [!NOTE]
47//! > ureq version 2.11.0 was forced to bump MSRV from 1.63 -> 1.67. The problem is that the
48//! > `time` crate 0.3.20, the last 1.63 compatible version, stopped compiling with Rust
49//! > [1.80 and above](https://github.com/algesten/ureq/pull/878#issuecomment-2503176155).
50//! > To release a 2.x version that is possible to compile on the latest Rust we were
51//! > forced to bump MSRV.
52//!
53//! Ureq's first priority is being easy for you to use. It's great for
54//! anyone who wants a low-overhead HTTP client that just gets the job done. Works
55//! very well with HTTP APIs. Its features include cookies, JSON, HTTP proxies,
56//! HTTPS, interoperability with the `http` crate, and charset decoding.
57//!
58//! Ureq is in pure Rust for safety and ease of understanding. It avoids using
59//! `unsafe` directly. It [uses blocking I/O][blocking] instead of async I/O, because that keeps
60//! the API simple and keeps dependencies to a minimum. For TLS, ureq uses
61//! [rustls or native-tls](#https--tls--ssl).
62//!
63//! See the [changelog] for details of recent releases.
64//!
65//! [blocking]: #blocking-io-for-simplicity
66//! [changelog]: https://github.com/algesten/ureq/blob/main/CHANGELOG.md
67//!
68//!
69//! ## Usage
70//!
71//! In its simplest form, ureq looks like this:
72//!
73//! ```rust
74//! fn main() -> Result<(), ureq::Error> {
75//! # ureq::is_test(true);
76//!     let body: String = ureq::get("http://example.com")
77//!         .set("Example-Header", "header value")
78//!         .call()?
79//!         .into_string()?;
80//!     Ok(())
81//! }
82//! ```
83//!
84//! For more involved tasks, you'll want to create an [Agent]. An Agent
85//! holds a connection pool for reuse, and a cookie store if you use the
86//! "cookies" feature. An Agent can be cheaply cloned due to an internal
87//! [Arc](std::sync::Arc) and all clones of an Agent share state among each other. Creating
88//! an Agent also allows setting options like the TLS configuration.
89//!
90//! ```no_run
91//! # fn main() -> std::result::Result<(), ureq::Error> {
92//! # ureq::is_test(true);
93//!   use ureq::{Agent, AgentBuilder};
94//!   use std::time::Duration;
95//!
96//!   let agent: Agent = ureq::AgentBuilder::new()
97//!       .timeout_read(Duration::from_secs(5))
98//!       .timeout_write(Duration::from_secs(5))
99//!       .build();
100//!   let body: String = agent.get("http://example.com/page")
101//!       .call()?
102//!       .into_string()?;
103//!
104//!   // Reuses the connection from previous request.
105//!   let response: String = agent.put("http://example.com/upload")
106//!       .set("Authorization", "example-token")
107//!       .call()?
108//!       .into_string()?;
109//! # Ok(())
110//! # }
111//! ```
112//!
113//! Ureq supports sending and receiving json, if you enable the "json" feature:
114//!
115//! ```rust
116//! # #[cfg(feature = "json")]
117//! # fn main() -> std::result::Result<(), ureq::Error> {
118//! # ureq::is_test(true);
119//!   // Requires the `json` feature enabled.
120//!   let resp: String = ureq::post("http://myapi.example.com/post/ingest")
121//!       .set("X-My-Header", "Secret")
122//!       .send_json(ureq::json!({
123//!           "name": "martin",
124//!           "rust": true
125//!       }))?
126//!       .into_string()?;
127//! # Ok(())
128//! # }
129//! # #[cfg(not(feature = "json"))]
130//! # fn main() {}
131//! ```
132//!
133//! ## Error handling
134//!
135//! ureq returns errors via `Result<T, ureq::Error>`. That includes I/O errors,
136//! protocol errors, and status code errors (when the server responded 4xx or
137//! 5xx)
138//!
139//! ```rust
140//! use ureq::Error;
141//!
142//! # fn req() {
143//! match ureq::get("http://mypage.example.com/").call() {
144//!     Ok(response) => { /* it worked */},
145//!     Err(Error::Status(code, response)) => {
146//!         /* the server returned an unexpected status
147//!            code (such as 400, 500 etc) */
148//!     }
149//!     Err(_) => { /* some kind of io/transport error */ }
150//! }
151//! # }
152//! # fn main() {}
153//! ```
154//!
155//! More details on the [Error] type.
156//!
157//! ## Features
158//!
159//! To enable a minimal dependency tree, some features are off by default.
160//! You can control them when including ureq as a dependency.
161//!
162//! `ureq = { version = "*", features = ["json", "charset"] }`
163//!
164//! * `tls` enables https. This is enabled by default.
165//! * `native-certs` makes the default TLS implementation use the OS' trust store (see TLS doc below).
166//! * `cookies` enables cookies.
167//! * `json` enables [Response::into_json()] and [Request::send_json()] via serde_json.
168//! * `charset` enables interpreting the charset part of the Content-Type header
169//!    (e.g.  `Content-Type: text/plain; charset=iso-8859-1`). Without this, the
170//!    library defaults to Rust's built in `utf-8`.
171//! * `socks-proxy` enables proxy config using the `socks4://`, `socks4a://`, `socks5://` and `socks://` (equal to `socks5://`) prefix.
172//! * `native-tls` enables an adapter so you can pass a `native_tls::TlsConnector` instance
173//!   to `AgentBuilder::tls_connector`. Due to the risk of diamond dependencies accidentally switching on an unwanted
174//!   TLS implementation, `native-tls` is never picked up as a default or used by the crate level
175//!   convenience calls (`ureq::get` etc) – it must be configured on the agent. The `native-certs` feature
176//!   does nothing for `native-tls`.
177//! * `gzip` enables requests of gzip-compressed responses and decompresses them. This is enabled by default.
178//! * `brotli` enables requests brotli-compressed responses and decompresses them.
179//! * `http-interop` enables conversion methods to and from `http::Response` and `http::request::Builder` (v0.2).
180//! * `http` enables conversion methods to and from `http::Response` and `http::request::Builder` (v1.0).
181//!
182//! # Plain requests
183//!
184//! Most standard methods (GET, POST, PUT etc), are supported as functions from the
185//! top of the library ([get()], [post()], [put()], etc).
186//!
187//! These top level http method functions create a [Request] instance
188//! which follows a build pattern. The builders are finished using:
189//!
190//! * [`.call()`][Request::call()] without a request body.
191//! * [`.send()`][Request::send()] with a request body as [Read][std::io::Read] (chunked encoding support for non-known sized readers).
192//! * [`.send_string()`][Request::send_string()] body as string.
193//! * [`.send_bytes()`][Request::send_bytes()] body as bytes.
194//! * [`.send_form()`][Request::send_form()] key-value pairs as application/x-www-form-urlencoded.
195//!
196//! # JSON
197//!
198//! By enabling the `ureq = { version = "*", features = ["json"] }` feature,
199//! the library supports serde json.
200//!
201//! * [`request.send_json()`][Request::send_json()] send body as serde json.
202//! * [`response.into_json()`][Response::into_json()] transform response to json.
203//!
204//! # Content-Length and Transfer-Encoding
205//!
206//! The library will send a Content-Length header on requests with bodies of
207//! known size, in other words, those sent with
208//! [`.send_string()`][Request::send_string()],
209//! [`.send_bytes()`][Request::send_bytes()],
210//! [`.send_form()`][Request::send_form()], or
211//! [`.send_json()`][Request::send_json()]. If you send a
212//! request body with [`.send()`][Request::send()],
213//! which takes a [Read][std::io::Read] of unknown size, ureq will send Transfer-Encoding:
214//! chunked, and encode the body accordingly. Bodyless requests
215//! (GETs and HEADs) are sent with [`.call()`][Request::call()]
216//! and ureq adds neither a Content-Length nor a Transfer-Encoding header.
217//!
218//! If you set your own Content-Length or Transfer-Encoding header before
219//! sending the body, ureq will respect that header by not overriding it,
220//! and by encoding the body or not, as indicated by the headers you set.
221//!
222//! ```
223//! let resp = ureq::post("http://my-server.com/ingest")
224//!     .set("Transfer-Encoding", "chunked")
225//!     .send_string("Hello world");
226//! ```
227//!
228//! # Character encoding
229//!
230//! By enabling the `ureq = { version = "*", features = ["charset"] }` feature,
231//! the library supports sending/receiving other character sets than `utf-8`.
232//!
233//! For [`response.into_string()`][Response::into_string()] we read the
234//! header `Content-Type: text/plain; charset=iso-8859-1` and if it contains a charset
235//! specification, we try to decode the body using that encoding. In the absence of, or failing
236//! to interpret the charset, we fall back on `utf-8`.
237//!
238//! Similarly when using [`request.send_string()`][Request::send_string()],
239//! we first check if the user has set a `; charset=<whatwg charset>` and attempt
240//! to encode the request body using that.
241//!
242//!
243//! # Proxying
244//!
245//! ureq supports two kinds of proxies,  [`HTTP`] ([`CONNECT`]), [`SOCKS4`] and [`SOCKS5`],
246//! the former is always available while the latter must be enabled using the feature
247//! `ureq = { version = "*", features = ["socks-proxy"] }`.
248//!
249//! Proxies settings are configured on an [Agent] (using [AgentBuilder]). All request sent
250//! through the agent will be proxied.
251//!
252//! ## Example using HTTP
253//!
254//! ```rust
255//! fn proxy_example_1() -> std::result::Result<(), ureq::Error> {
256//!     // Configure an http connect proxy. Notice we could have used
257//!     // the http:// prefix here (it's optional).
258//!     let proxy = ureq::Proxy::new("user:password@cool.proxy:9090")?;
259//!     let agent = ureq::AgentBuilder::new()
260//!         .proxy(proxy)
261//!         .build();
262//!
263//!     // This is proxied.
264//!     let resp = agent.get("http://cool.server").call()?;
265//!     Ok(())
266//! }
267//! # fn main() {}
268//! ```
269//!
270//! ## Example using SOCKS5
271//!
272//! ```rust
273//! # #[cfg(feature = "socks-proxy")]
274//! fn proxy_example_2() -> std::result::Result<(), ureq::Error> {
275//!     // Configure a SOCKS proxy.
276//!     let proxy = ureq::Proxy::new("socks5://user:password@cool.proxy:9090")?;
277//!     let agent = ureq::AgentBuilder::new()
278//!         .proxy(proxy)
279//!         .build();
280//!
281//!     // This is proxied.
282//!     let resp = agent.get("http://cool.server").call()?;
283//!     Ok(())
284//! }
285//! # fn main() {}
286//! ```
287//!
288//! # HTTPS / TLS / SSL
289//!
290//! On platforms that support rustls, ureq uses rustls. On other platforms, native-tls can
291//! be manually configured using [`AgentBuilder::tls_connector`].
292//!
293//! You might want to use native-tls if you need to interoperate with servers that
294//! only support less-secure TLS configurations (rustls doesn't support TLS 1.0 and 1.1, for
295//! instance). You might also want to use it if you need to validate certificates for IP addresses,
296//! which are not currently supported in rustls.
297//!
298//! Here's an example of constructing an Agent that uses native-tls. It requires the
299//! "native-tls" feature to be enabled.
300//!
301//! ```no_run
302//! # #[cfg(feature = "native-tls")]
303//! # fn build() -> std::result::Result<(), Box<dyn std::error::Error>> {
304//! # ureq::is_test(true);
305//!   use std::sync::Arc;
306//!   use ureq::Agent;
307//!
308//!   let agent = ureq::AgentBuilder::new()
309//!       .tls_connector(Arc::new(native_tls::TlsConnector::new()?))
310//!       .build();
311//! # Ok(())
312//! # }
313//! # fn main() {}
314//! ```
315//!
316//! ## Trusted Roots
317//!
318//! When you use rustls (`tls` feature), ureq defaults to trusting
319//! [webpki-roots](https://docs.rs/webpki-roots/), a
320//! copy of the Mozilla Root program that is bundled into your program (and so won't update if your
321//! program isn't updated). You can alternately configure
322//! [rustls-native-certs](https://docs.rs/rustls-native-certs/) which extracts the roots from your
323//! OS' trust store. That means it will update when your OS is updated, and also that it will
324//! include locally installed roots.
325//!
326//! When you use `native-tls`, ureq will use your OS' certificate verifier and root store.
327//!
328//! # Blocking I/O for simplicity
329//!
330//! Ureq uses blocking I/O rather than Rust's newer [asynchronous (async) I/O][async]. Async I/O
331//! allows serving many concurrent requests without high costs in memory and OS threads. But
332//! it comes at a cost in complexity. Async programs need to pull in a runtime (usually
333//! [async-std] or [tokio]). They also need async variants of any method that might block, and of
334//! [any method that might call another method that might block][what-color]. That means async
335//! programs usually have a lot of dependencies - which adds to compile times, and increases
336//! risk.
337//!
338//! The costs of async are worth paying, if you're writing an HTTP server that must serve
339//! many many clients with minimal overhead. However, for HTTP _clients_, we believe that the
340//! cost is usually not worth paying. The low-cost alternative to async I/O is blocking I/O,
341//! which has a different price: it requires an OS thread per concurrent request. However,
342//! that price is usually not high: most HTTP clients make requests sequentially, or with
343//! low concurrency.
344//!
345//! That's why ureq uses blocking I/O and plans to stay that way. Other HTTP clients offer both
346//! an async API and a blocking API, but we want to offer a blocking API without pulling in all
347//! the dependencies required by an async API.
348//!
349//! [async]: https://rust-lang.github.io/async-book/01_getting_started/02_why_async.html
350//! [async-std]: https://github.com/async-rs/async-std#async-std
351//! [tokio]: https://github.com/tokio-rs/tokio#tokio
352//! [what-color]: https://journal.stuffwithstuff.com/2015/02/01/what-color-is-your-function/
353//! [`HTTP`]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Proxy_servers_and_tunneling#http_tunneling
354//! [`CONNECT`]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/CONNECT
355//! [`SOCKS4`]: https://en.wikipedia.org/wiki/SOCKS#SOCKS4
356//! [`SOCKS5`]: https://en.wikipedia.org/wiki/SOCKS#SOCKS5
357//!
358//! ------------------------------------------------------------------------------
359//!
360//! Ureq is inspired by other great HTTP clients like
361//! [superagent](http://visionmedia.github.io/superagent/) and
362//! [the fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API).
363//!
364//! If ureq is not what you're looking for, check out these other Rust HTTP clients:
365//! [surf](https://crates.io/crates/surf), [reqwest](https://crates.io/crates/reqwest),
366//! [isahc](https://crates.io/crates/isahc), [attohttpc](https://crates.io/crates/attohttpc),
367//! [actix-web](https://crates.io/crates/actix-web), and [hyper](https://crates.io/crates/hyper).
368//!
369
370/// Re-exported rustls crate
371///
372/// Use this re-export to always get a compatible version of `ClientConfig`.
373#[cfg(feature = "tls")]
374pub use rustls;
375
376/// Re-exported native-tls crate
377///
378/// Use this re-export to always get a compatible version of `TlsConnector`.
379#[cfg(feature = "native-tls")]
380pub use native_tls;
381
382mod agent;
383mod body;
384mod chunked;
385mod error;
386mod header;
387mod middleware;
388mod pool;
389mod proxy;
390mod request;
391mod resolve;
392mod response;
393mod stream;
394mod unit;
395
396// rustls is our default tls engine. If the feature is on, it will be
397// used for the shortcut calls the top of the crate (`ureq::get` etc).
398#[cfg(feature = "tls")]
399mod rtls;
400
401// native-tls is a feature that must be configured via the AgentBuilder.
402// it is never picked up as a default (and never used by `ureq::get` etc).
403#[cfg(feature = "native-tls")]
404mod ntls;
405
406// If we have rustls compiled, that is the default.
407#[cfg(feature = "tls")]
408pub(crate) fn default_tls_config() -> std::sync::Arc<dyn TlsConnector> {
409    rtls::default_tls_config()
410}
411
412// Without rustls compiled, we just fail on https when using the shortcut
413// calls at the top of the crate (`ureq::get` etc).
414#[cfg(not(feature = "tls"))]
415pub(crate) fn default_tls_config() -> std::sync::Arc<dyn TlsConnector> {
416    use std::sync::Arc;
417
418    struct NoTlsConfig;
419
420    impl TlsConnector for NoTlsConfig {
421        fn connect(
422            &self,
423            _dns_name: &str,
424            _io: Box<dyn ReadWrite>,
425        ) -> Result<Box<dyn ReadWrite>, crate::error::Error> {
426            Err(ErrorKind::UnknownScheme
427                .msg("cannot make HTTPS request because no TLS backend is configured"))
428        }
429    }
430
431    Arc::new(NoTlsConfig)
432}
433
434#[cfg(feature = "cookies")]
435mod cookies;
436
437#[cfg(feature = "json")]
438pub use serde_json::json;
439use url::Url;
440
441#[cfg(test)]
442mod test;
443#[doc(hidden)]
444mod testserver;
445
446#[cfg(feature = "http-interop")]
447// 0.2 version dependency (deprecated)
448mod http_interop;
449
450#[cfg(feature = "http-crate")]
451// 1.0 version dependency.
452mod http_crate;
453
454pub use crate::agent::Agent;
455pub use crate::agent::AgentBuilder;
456pub use crate::agent::RedirectAuthHeaders;
457pub use crate::error::{Error, ErrorKind, OrAnyStatus, Transport};
458pub use crate::middleware::{Middleware, MiddlewareNext};
459pub use crate::proxy::Proxy;
460pub use crate::request::{Request, RequestUrl};
461pub use crate::resolve::Resolver;
462pub use crate::response::Response;
463pub use crate::stream::{ReadWrite, TlsConnector};
464
465// re-export
466#[cfg(feature = "cookies")]
467pub use cookie::Cookie;
468
469#[cfg(feature = "json")]
470pub use {serde, serde_json};
471
472#[cfg(feature = "json")]
473#[deprecated(note = "use ureq::serde_json::Map instead")]
474pub type SerdeMap<K, V> = serde_json::Map<K, V>;
475
476#[cfg(feature = "json")]
477#[deprecated(note = "use ureq::serde_json::Value instead")]
478pub type SerdeValue = serde_json::Value;
479
480#[cfg(feature = "json")]
481#[deprecated(note = "use ureq::serde_json::to_value instead")]
482pub fn serde_to_value<T: serde::Serialize>(
483    value: T,
484) -> std::result::Result<serde_json::Value, serde_json::Error> {
485    serde_json::to_value(value)
486}
487
488use once_cell::sync::Lazy;
489use std::sync::atomic::{AtomicBool, Ordering};
490
491/// Creates an [AgentBuilder].
492pub fn builder() -> AgentBuilder {
493    AgentBuilder::new()
494}
495
496// is_test returns false so long as it has only ever been called with false.
497// If it has ever been called with true, it will always return true after that.
498// This is a public but hidden function used to allow doctests to use the test_agent.
499// Note that we use this approach for doctests rather the #[cfg(test)], because
500// doctests are run against a copy of the crate build without cfg(test) set.
501// We also can't use #[cfg(doctest)] to do this, because cfg(doctest) is only set
502// when collecting doctests, not when building the crate.
503#[doc(hidden)]
504pub fn is_test(is: bool) -> bool {
505    static IS_TEST: Lazy<AtomicBool> = Lazy::new(|| AtomicBool::new(false));
506    if is {
507        IS_TEST.store(true, Ordering::SeqCst);
508    }
509    IS_TEST.load(Ordering::SeqCst)
510}
511
512/// Agents are used to hold configuration and keep state between requests.
513pub fn agent() -> Agent {
514    #[cfg(not(test))]
515    if is_test(false) {
516        testserver::test_agent()
517    } else {
518        AgentBuilder::new().build()
519    }
520    #[cfg(test)]
521    testserver::test_agent()
522}
523
524/// Make a request with the HTTP verb as a parameter.
525///
526/// This allows making requests with verbs that don't have a dedicated
527/// method.
528///
529/// If you've got an already-parsed [`Url`], try [`request_url()`].
530///
531/// ```
532/// # fn main() -> Result<(), ureq::Error> {
533/// # ureq::is_test(true);
534/// let resp: ureq::Response = ureq::request("OPTIONS", "http://example.com/")
535///     .call()?;
536/// # Ok(())
537/// # }
538/// ```
539pub fn request(method: &str, path: &str) -> Request {
540    agent().request(method, path)
541}
542/// Make a request using an already-parsed [Url].
543///
544/// This is useful if you've got a parsed [`Url`] from some other source, or if
545/// you want to parse the URL and then modify it before making the request.
546/// If you'd just like to pass a [`String`] or a [`&str`], try [`request()`].
547///
548/// ```
549/// # fn main() -> Result<(), ureq::Error> {
550/// # ureq::is_test(true);
551/// use url::Url;
552/// let agent = ureq::agent();
553///
554/// let mut url: Url = "http://example.com/some-page".parse()?;
555/// url.set_path("/get/robots.txt");
556/// let resp: ureq::Response = ureq::request_url("GET", &url)
557///     .call()?;
558/// # Ok(())
559/// # }
560/// ```
561pub fn request_url(method: &str, url: &Url) -> Request {
562    agent().request_url(method, url)
563}
564
565/// Make a GET request.
566pub fn get(path: &str) -> Request {
567    request("GET", path)
568}
569
570/// Make a HEAD request.
571pub fn head(path: &str) -> Request {
572    request("HEAD", path)
573}
574
575/// Make a PATCH request.
576pub fn patch(path: &str) -> Request {
577    request("PATCH", path)
578}
579
580/// Make a POST request.
581pub fn post(path: &str) -> Request {
582    request("POST", path)
583}
584
585/// Make a PUT request.
586pub fn put(path: &str) -> Request {
587    request("PUT", path)
588}
589
590/// Make a DELETE request.
591pub fn delete(path: &str) -> Request {
592    request("DELETE", path)
593}
594
595#[cfg(test)]
596mod tests {
597    use super::*;
598
599    #[test]
600    fn connect_http_google() {
601        let agent = Agent::new();
602
603        let resp = agent.get("http://www.google.com/").call().unwrap();
604        assert_eq!(
605            "text/html;charset=ISO-8859-1",
606            resp.header("content-type").unwrap().replace("; ", ";")
607        );
608        assert_eq!("text/html", resp.content_type());
609    }
610
611    #[test]
612    #[cfg(feature = "tls")]
613    fn connect_https_google_rustls() {
614        let agent = Agent::new();
615
616        let resp = agent.get("https://www.google.com/").call().unwrap();
617        assert_eq!(
618            "text/html;charset=ISO-8859-1",
619            resp.header("content-type").unwrap().replace("; ", ";")
620        );
621        assert_eq!("text/html", resp.content_type());
622    }
623
624    #[test]
625    #[cfg(feature = "native-tls")]
626    fn connect_https_google_native_tls() {
627        use std::sync::Arc;
628
629        let tls_config = native_tls::TlsConnector::new().unwrap();
630        let agent = builder().tls_connector(Arc::new(tls_config)).build();
631
632        let resp = agent.get("https://www.google.com/").call().unwrap();
633        assert_eq!(
634            "text/html;charset=ISO-8859-1",
635            resp.header("content-type").unwrap().replace("; ", ";")
636        );
637        assert_eq!("text/html", resp.content_type());
638    }
639
640    #[test]
641    fn connect_https_invalid_name() {
642        let result = get("https://example.com{REQUEST_URI}/").call();
643        let e = ErrorKind::Dns;
644        assert_eq!(result.unwrap_err().kind(), e);
645    }
646}