[go: up one dir, main page]

ureq/
lib.rs

1//!<div align="center">
2//!  <!-- Version -->
3//!  <a href="https://crates.io/crates/ureq">
4//!    <img src="https://img.shields.io/crates/v/ureq.svg?style=flat-square"
5//!    alt="Crates.io version" />
6//!  </a>
7//!  <!-- Docs -->
8//!  <a href="https://docs.rs/ureq">
9//!    <img src="https://img.shields.io/badge/docs-latest-blue.svg?style=flat-square"
10//!      alt="docs.rs docs" />
11//!  </a>
12//!  <!-- Downloads -->
13//!  <a href="https://crates.io/crates/ureq">
14//!    <img src="https://img.shields.io/crates/d/ureq.svg?style=flat-square"
15//!      alt="Crates.io downloads" />
16//!  </a>
17//!</div>
18//!
19//! A simple, safe HTTP client.
20//!
21//! Ureq's first priority is being easy for you to use. It's great for
22//! anyone who wants a low-overhead HTTP client that just gets the job done. Works
23//! very well with HTTP APIs. Its features include cookies, JSON, HTTP proxies,
24//! HTTPS, charset decoding, and is based on the API of the `http` crate.
25//!
26//! Ureq is in pure Rust for safety and ease of understanding. It avoids using
27//! `unsafe` directly. It uses blocking I/O instead of async I/O, because that keeps
28//! the API simple and keeps dependencies to a minimum. For TLS, ureq uses
29//! rustls or native-tls.
30//!
31//! See the [changelog] for details of recent releases.
32//!
33//! [changelog]: https://github.com/algesten/ureq/blob/main/CHANGELOG.md
34//!
35//! # Usage
36//!
37//! In its simplest form, ureq looks like this:
38//!
39//! ```rust
40//! let body: String = ureq::get("http://example.com")
41//!     .header("Example-Header", "header value")
42//!     .call()?
43//!     .body_mut()
44//!     .read_to_string()?;
45//! # Ok::<(), ureq::Error>(())
46//! ```
47//!
48//! For more involved tasks, you'll want to create an [`Agent`]. An Agent
49//! holds a connection pool for reuse, and a cookie store if you use the
50//! **cookies** feature. An Agent can be cheaply cloned due to internal
51//! [`Arc`] and all clones of an Agent share state among each other. Creating
52//! an Agent also allows setting options like the TLS configuration.
53//!
54//! ```rust
55//! # fn no_run() -> Result<(), ureq::Error> {
56//! use ureq::Agent;
57//! use std::time::Duration;
58//!
59//! let mut config = Agent::config_builder()
60//!     .timeout_global(Some(Duration::from_secs(5)))
61//!     .build();
62//!
63//! let agent: Agent = config.into();
64//!
65//! let body: String = agent.get("http://example.com/page")
66//!     .call()?
67//!     .body_mut()
68//!     .read_to_string()?;
69//!
70//! // Reuses the connection from previous request.
71//! let response: String = agent.put("http://example.com/upload")
72//!     .header("Authorization", "example-token")
73//!     .send("some body data")?
74//!     .body_mut()
75//!     .read_to_string()?;
76//! # Ok(())}
77//! ```
78//!
79//! ## JSON
80//!
81//! Ureq supports sending and receiving json, if you enable the **json** feature:
82//!
83//! ```rust
84//! # #[cfg(feature = "json")]
85//! # fn no_run() -> Result<(), ureq::Error> {
86//! use serde::{Serialize, Deserialize};
87//!
88//! #[derive(Serialize)]
89//! struct MySendBody {
90//!    thing: String,
91//! }
92//!
93//! #[derive(Deserialize)]
94//! struct MyRecvBody {
95//!    other: String,
96//! }
97//!
98//! let send_body = MySendBody { thing: "yo".to_string() };
99//!
100//! // Requires the `json` feature enabled.
101//! let recv_body = ureq::post("http://example.com/post/ingest")
102//!     .header("X-My-Header", "Secret")
103//!     .send_json(&send_body)?
104//!     .body_mut()
105//!     .read_json::<MyRecvBody>()?;
106//! # Ok(())}
107//! ```
108//!
109//! ## Error handling
110//!
111//! ureq returns errors via `Result<T, ureq::Error>`. That includes I/O errors,
112//! protocol errors. By default, also HTTP status code errors (when the
113//! server responded 4xx or 5xx) results in [`Error`].
114//!
115//! This behavior can be turned off via [`http_status_as_error()`]
116//!
117//! ```rust
118//! use ureq::Error;
119//!
120//! # fn no_run() -> Result<(), ureq::Error> {
121//! match ureq::get("http://mypage.example.com/").call() {
122//!     Ok(response) => { /* it worked */},
123//!     Err(Error::StatusCode(code)) => {
124//!         /* the server returned an unexpected status
125//!            code (such as 400, 500 etc) */
126//!     }
127//!     Err(_) => { /* some kind of io/transport/etc error */ }
128//! }
129//! # Ok(())}
130//! ```
131//!
132//! # Features
133//!
134//! To enable a minimal dependency tree, some features are off by default.
135//! You can control them when including ureq as a dependency.
136//!
137//! `ureq = { version = "3", features = ["socks-proxy", "charset"] }`
138//!
139//! The default enabled features are: **rustls** and **gzip**.
140//!
141//! * **rustls** enables the rustls TLS implementation. This is the default for the the crate level
142//!   convenience calls (`ureq::get` etc). It currently uses `ring` as the TLS provider.
143//! * **native-tls** enables the native tls backend for TLS. Due to the risk of diamond dependencies
144//!   accidentally switching on an unwanted TLS implementation, `native-tls` is never picked up as
145//!   a default or used by the crate level convenience calls (`ureq::get` etc) – it must be configured
146//!   on the agent
147//! * **platform-verifier** enables verifying the server certificates using a method native to the
148//!   platform ureq is executing on. See [rustls-platform-verifier] crate
149//! * **socks-proxy** enables proxy config using the `socks4://`, `socks4a://`, `socks5://`
150//!   and `socks://` (equal to `socks5://`) prefix
151//! * **cookies** enables cookies
152//! * **gzip** enables requests of gzip-compressed responses and decompresses them
153//! * **brotli** enables requests brotli-compressed responses and decompresses them
154//! * **charset** enables interpreting the charset part of the Content-Type header
155//!   (e.g.  `Content-Type: text/plain; charset=iso-8859-1`). Without this, the
156//!   library defaults to Rust's built in `utf-8`
157//! * **json** enables JSON sending and receiving via serde_json
158//! * **multipart** enables multipart/form-data sending via [`unversioned::multipart`]
159//!
160//! ### Unstable
161//!
162//! These features are unstable and might change in a minor version.
163//!
164//! * **rustls-no-provider** Enables rustls, but does not enable any [`CryptoProvider`] such as `ring`.
165//!   Providers other than the default (currently `ring`) are never picked up from feature flags alone.
166//!   It must be configured on the agent.
167//!
168//! * **vendored** compiles and statically links to a copy of non-Rust vendors (e.g. OpenSSL from `native-tls`)
169//!
170//! # TLS (https)
171//!
172//! ## rustls
173//!
174//! By default, ureq uses [`rustls` crate] with the `ring` cryptographic provider.
175//! As of Sep 2024, the `ring` provider has a higher chance of compiling successfully. If the user
176//! installs another process [default provider], that choice is respected.
177//!
178//! ureq does not guarantee to default to ring indefinitely. `rustls` as a feature flag will always
179//! work, but the specific crypto backend might change in a minor version.
180//!
181//! ```
182//! # #[cfg(feature = "rustls")]
183//! # {
184//! // This uses rustls
185//! ureq::get("https://www.google.com/").call().unwrap();
186//! # } Ok::<_, ureq::Error>(())
187//! ```
188//!
189//! ### rustls without ring
190//!
191//! ureq never changes TLS backend from feature flags alone. It is possible to compile ureq
192//! without ring, but it requires specific feature flags and configuring the [`Agent`].
193//!
194//! Since rustls is not semver 1.x, this requires non-semver-guaranteed API. I.e. ureq might
195//! change this behavior without a major version bump.
196//!
197//! Read more at [`TlsConfigBuilder::unversioned_rustls_crypto_provider`][crate::tls::TlsConfigBuilder::unversioned_rustls_crypto_provider].
198//!
199//! ## native-tls
200//!
201//! As an alternative, ureq ships with [`native-tls`] as a TLS provider. This must be
202//! enabled using the **native-tls** feature. Due to the risk of diamond dependencies
203//! accidentally switching on an unwanted TLS implementation, `native-tls` is never picked
204//! up as a default or used by the crate level convenience calls (`ureq::get` etc) – it
205//! must be configured on the agent.
206//!
207//! ```
208//! # #[cfg(feature = "native-tls")]
209//! # {
210//! use ureq::config::Config;
211//! use ureq::tls::{TlsConfig, TlsProvider};
212//!
213//! let mut config = Config::builder()
214//!     .tls_config(
215//!         TlsConfig::builder()
216//!             // requires the native-tls feature
217//!             .provider(TlsProvider::NativeTls)
218//!             .build()
219//!     )
220//!     .build();
221//!
222//! let agent = config.new_agent();
223//!
224//! agent.get("https://www.google.com/").call().unwrap();
225//! # } Ok::<_, ureq::Error>(())
226//! ```
227//!
228//! ## Root certificates
229//!
230//! ### webpki-roots
231//!
232//! By default, ureq uses Mozilla's root certificates via the [webpki-roots] crate. This is a static
233//! bundle of root certificates that do not update automatically. It also circumvents whatever root
234//! certificates are installed on the host running ureq, which might be a good or a bad thing depending
235//! on your perspective. There is also no mechanism for [SCT], [CRL]s or other revocations.
236//! To maintain a "fresh" list of root certs, you need to bump the ureq dependency from time to time.
237//!
238//! The main reason for chosing this as the default is to minimize the number of dependencies. More
239//! details about this decision can be found at [PR 818].
240//!
241//! If your use case for ureq is talking to a limited number of servers with high trust, the
242//! default setting is likely sufficient. If you use ureq with a high number of servers, or servers
243//! you don't trust, we recommend using the platform verifier (see below).
244//!
245//! ### platform-verifier
246//!
247//! The [rustls-platform-verifier] crate provides access to natively checking the certificate via your OS.
248//! To use this verifier, you need to enable it using feature flag **platform-verifier** as well as
249//! configure an agent to use it.
250//!
251//! ```
252//! # #[cfg(all(feature = "rustls", feature="platform-verifier"))]
253//! # {
254//! use ureq::Agent;
255//! use ureq::tls::{TlsConfig, RootCerts};
256//!
257//! let agent = Agent::config_builder()
258//!     .tls_config(
259//!         TlsConfig::builder()
260//!             .root_certs(RootCerts::PlatformVerifier)
261//!             .build()
262//!     )
263//!     .build()
264//!     .new_agent();
265//!
266//! let response = agent.get("https://httpbin.org/get").call()?;
267//! # } Ok::<_, ureq::Error>(())
268//! ```
269//!
270//! Setting `RootCerts::PlatformVerifier` together with `TlsProvider::NativeTls` means
271//! also native-tls will use the OS roots instead of [webpki-roots] crate. Whether that
272//! results in a config that has CRLs and revocations is up to whatever native-tls links to.
273//!
274//! # JSON
275//!
276//! By enabling the **json** feature, the library supports serde json.
277//!
278//! This is enabled by default.
279//!
280//! * [`request.send_json()`] send body as json.
281//! * [`body.read_json()`] transform response to json.
282//!
283//! # Sending body data
284//!
285//! HTTP/1.1 has two ways of transfering body data. Either of a known size with
286//! the `Content-Length` HTTP header, or unknown size with the
287//! `Transfer-Encoding: chunked` header. ureq supports both and will use the
288//! appropriate method depending on which body is being sent.
289//!
290//! ureq has a [`AsSendBody`] trait that is implemented for many well known types
291//! of data that we might want to send. The request body can thus be anything
292//! from a `String` to a `File`, see below.
293//!
294//! ## Content-Length
295//!
296//! The library will send a `Content-Length` header on requests with bodies of
297//! known size, in other words, if the body to send is one of:
298//!
299//! * `&[u8]`
300//! * `&[u8; N]`
301//! * `&str`
302//! * `String`
303//! * `&String`
304//! * `Vec<u8>`
305//! * `&Vec<u8>)`
306//! * [`SendBody::from_json()`] (implicitly via [`request.send_json()`])
307//!
308//! ## Transfer-Encoding: chunked
309//!
310//! ureq will send a `Transfer-Encoding: chunked` header on requests where the body
311//! is of unknown size. The body is automatically converted to an [`std::io::Read`]
312//! when the type is one of:
313//!
314//! * `File`
315//! * `&File`
316//! * `TcpStream`
317//! * `&TcpStream`
318//! * `Stdin`
319//! * `UnixStream` (not on windows)
320//!
321//! ### From readers
322//!
323//! The chunked method also applies for bodies constructed via:
324//!
325//! * [`SendBody::from_reader()`]
326//! * [`SendBody::from_owned_reader()`]
327//!
328//! ## Proxying a response body
329//!
330//! As a special case, when ureq sends a [`Body`] from a previous http call, the
331//! use of `Content-Length` or `chunked` depends on situation. For input such as
332//! gzip decoding (**gzip** feature) or charset transformation (**charset** feature),
333//! the output body might not match the input, which means ureq is forced to use
334//! the `chunked` method.
335//!
336//! * `Response<Body>`
337//!
338//! ## Sending form data
339//!
340//! [`request.send_form()`] provides a way to send `application/x-www-form-urlencoded`
341//! encoded data. The key/values provided will be URL encoded.
342//!
343//! ## Overriding
344//!
345//! If you set your own Content-Length or Transfer-Encoding header before
346//! sending the body, ureq will respect that header by not overriding it,
347//! and by encoding the body or not, as indicated by the headers you set.
348//!
349//! ```
350//! let resp = ureq::put("https://httpbin.org/put")
351//!     .header("Transfer-Encoding", "chunked")
352//!     .send("Hello world")?;
353//! # Ok::<_, ureq::Error>(())
354//! ```
355//!
356//! # Character encoding
357//!
358//! By enabling the **charset** feature, the library supports receiving other
359//! character sets than `utf-8`.
360//!
361//! For [`Body::read_to_string()`] we read the header like:
362//!
363//! `Content-Type: text/plain; charset=iso-8859-1`
364//!
365//! and if it contains a charset specification, we try to decode the body using that
366//! encoding. In the absence of, or failing to interpret the charset, we fall back on `utf-8`.
367//!
368//! Currently ureq does not provide a way to encode when sending request bodies.
369//!
370//! ## Lossy utf-8
371//!
372//! When reading text bodies (with a `Content-Type` starting `text/` as in `text/plain`,
373//! `text/html`, etc), ureq can ensure the body is possible to read as a `String` also if
374//! it contains characters that are not valid for utf-8. Invalid characters are replaced
375//! with a question mark `?` (NOT the utf-8 replacement character).
376//!
377//! For [`Body::read_to_string()`] this is turned on by default, but it can be disabled
378//! and conversely for [`Body::as_reader()`] it is not enabled, but can be.
379//!
380//! To precisely configure the behavior use [`Body::with_config()`].
381//!
382//! # Proxying
383//!
384//! ureq supports two kinds of proxies,  [`HTTP`] ([`CONNECT`]), [`SOCKS4`]/[`SOCKS5`],
385//! the former is always available while the latter must be enabled using the feature
386//! **socks-proxy**.
387//!
388//! Proxies settings are configured on an [`Agent`]. All request sent through the agent will be proxied.
389//!
390//! ## Environment Variables
391//!
392//! ureq automatically reads proxy configuration from environment variables when creating
393//! a default [`Agent`]. Proxy variables are checked in order: `ALL_PROXY`, `HTTPS_PROXY`,
394//! then `HTTP_PROXY` (with lowercase variants).
395//!
396//! `NO_PROXY` specifies hosts that bypass the proxy, supporting exact hosts, wildcard
397//! suffixes (`*.example.com`), dot suffixes (`.example.com`), and match-all (`*`).
398//!
399//! ## Example using HTTP
400//!
401//! ```rust
402//! use ureq::{Agent, Proxy};
403//! # fn no_run() -> std::result::Result<(), ureq::Error> {
404//! // Configure an http connect proxy.
405//! let proxy = Proxy::new("http://user:password@cool.proxy:9090")?;
406//! let agent: Agent = Agent::config_builder()
407//!     .proxy(Some(proxy))
408//!     .build()
409//!     .into();
410//!
411//! // This is proxied.
412//! let resp = agent.get("http://cool.server").call()?;
413//! # Ok(())}
414//! # fn main() {}
415//! ```
416//!
417//! ## Example using SOCKS5
418//!
419//! ```rust
420//! use ureq::{Agent, Proxy};
421//! # #[cfg(feature = "socks-proxy")]
422//! # fn no_run() -> std::result::Result<(), ureq::Error> {
423//! // Configure a SOCKS proxy.
424//! let proxy = Proxy::new("socks5://user:password@cool.proxy:9090")?;
425//! let agent: Agent = Agent::config_builder()
426//!     .proxy(Some(proxy))
427//!     .build()
428//!     .into();
429//!
430//! // This is proxied.
431//! let resp = agent.get("http://cool.server").call()?;
432//! # Ok(())}
433//! ```
434//!
435//! # Log levels
436//!
437//! ureq uses the log crate. These are the definitions of the log levels, however we
438//! do not guarantee anything for dependencies such as `http` and `rustls`.
439//!
440//! * `ERROR` - nothing
441//! * `WARN` - if we detect a user configuration problem.
442//! * `INFO` - nothing
443//! * `DEBUG` - uri, state changes, transport, resolver and selected request/response headers
444//! * `TRACE` - wire level debug. NOT REDACTED!
445//!
446//! The request/response headers on DEBUG levels are allow-listed to only include headers that
447//! are considered safe. The code has the [allow list](https://github.com/algesten/ureq/blob/81127cfc38516903330dc1b9c618122372f8dc29/src/util.rs#L184-L198).
448//!
449//! # Versioning
450//!
451//! ## Semver and `unversioned`
452//!
453//! ureq follows semver. From ureq 3.x we strive to have a much closer adherence to semver than 2.x.
454//! The main mistake in 2.x was to re-export crates that were not yet semver 1.0. In ureq 3.x TLS and
455//! cookie configuration is shimmed using our own types.
456//!
457//! ureq 3.x is trying out two new traits that had no equivalent in 2.x, [`Transport`] and [`Resolver`].
458//! These allow the user write their own bespoke transports and (DNS name) resolver. The API:s for
459//! these parts are not yet solidified. They live under the [`unversioned`] module, and do not
460//! follow semver. See module doc for more info.
461//!
462//! ## Breaking changes in dependencies
463//!
464//! ureq relies on non-semver 1.x crates such as `rustls` and `native-tls`. Some scenarios, such
465//! as configuring `rustls` to not use `ring`, a user of ureq might need to interact with these
466//! crates directly instead of going via ureq's provided API.
467//!
468//! Such changes can break when ureq updates dependencies. This is not considered a breaking change
469//! for ureq and will not be reflected by a major version bump.
470//!
471//! We strive to mark ureq's API with the word "unversioned" to identify places where this risk arises.
472//!
473//! ## Minimum Supported Rust Version (MSRV)
474//!
475//! From time to time we will need to update our minimum supported Rust version (MSRV). This is not
476//! something we do lightly; our ambition is to be as conservative with MSRV as possible.
477//!
478//! * For some dependencies, we will opt for pinning the version of the dep instead
479//!   of bumping our MSRV.
480//! * For important dependencies, like the TLS libraries, we cannot hold back our MSRV if they change.
481//! * We do not consider MSRV changes to be breaking for the purposes of semver.
482//! * We will not make MSRV changes in patch releases.
483//! * MSRV changes will get their own minor release, and not be co-mingled with other changes.
484//!
485//! [`HTTP`]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Proxy_servers_and_tunneling#http_tunneling
486//! [`CONNECT`]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/CONNECT
487//! [`SOCKS4`]: https://en.wikipedia.org/wiki/SOCKS#SOCKS4
488//! [`SOCKS5`]: https://en.wikipedia.org/wiki/SOCKS#SOCKS5
489//! [`rustls` crate]: https://crates.io/crates/rustls
490//! [default provider]: https://docs.rs/rustls/latest/rustls/crypto/struct.CryptoProvider.html#method.install_default
491//! [`native-tls`]: https://crates.io/crates/native-tls
492//! [rustls-platform-verifier]: https://crates.io/crates/rustls-platform-verifier
493//! [webpki-roots]: https://crates.io/crates/webpki-roots
494//! [`Arc`]: https://doc.rust-lang.org/std/sync/struct.Arc.html
495//! [`Agent`]: https://docs.rs/ureq/latest/ureq/struct.Agent.html
496//! [`Error`]: https://docs.rs/ureq/latest/ureq/enum.Error.html
497//! [`http_status_as_error()`]: https://docs.rs/ureq/latest/ureq/config/struct.ConfigBuilder.html#method.http_status_as_error
498//! [SCT]: https://en.wikipedia.org/wiki/Certificate_Transparency
499//! [CRL]: https://en.wikipedia.org/wiki/Certificate_revocation_list
500//! [PR 818]: https://github.com/algesten/ureq/pull/818
501//! [`request.send_json()`]: https://docs.rs/ureq/latest/ureq/struct.RequestBuilder.html#method.send_json
502//! [`body.read_json()`]: https://docs.rs/ureq/latest/ureq/struct.Body.html#method.read_json
503//! [`AsSendBody`]: https://docs.rs/ureq/latest/ureq/trait.AsSendBody.html
504//! [`SendBody::from_json()`]: https://docs.rs/ureq/latest/ureq/struct.SendBody.html#method.from_json
505//! [`std::io::Read`]: https://doc.rust-lang.org/std/io/trait.Read.html
506//! [`SendBody::from_reader()`]: https://docs.rs/ureq/latest/ureq/struct.SendBody.html#method.from_reader
507//! [`SendBody::from_owned_reader()`]: https://docs.rs/ureq/latest/ureq/struct.SendBody.html#method.from_owned_reader
508//! [`Body`]: https://docs.rs/ureq/latest/ureq/struct.Body.html
509//! [`request.send_form()`]: https://docs.rs/ureq/latest/ureq/struct.RequestBuilder.html#method.send_form
510//! [`Body::read_to_string()`]: https://docs.rs/ureq/latest/ureq/struct.Body.html#method.read_to_string
511//! [`Body::as_reader()`]: https://docs.rs/ureq/latest/ureq/struct.Body.html#method.as_reader
512//! [`Body::with_config()`]: https://docs.rs/ureq/latest/ureq/struct.Body.html#method.with_config
513//! [`Transport`]: https://docs.rs/ureq/latest/ureq/unversioned/transport/trait.Transport.html
514//! [`Resolver`]: https://docs.rs/ureq/latest/ureq/unversioned/resolver/trait.Resolver.html
515//! [`unversioned`]: https://docs.rs/ureq/latest/ureq/unversioned/index.html
516//! [`CryptoProvider`]: https://docs.rs/rustls/latest/rustls/crypto/struct.CryptoProvider.html
517//! [`unversioned::multipart`]: https://docs.rs/ureq/latest/ureq/unversioned/multipart/index.html
518
519#![forbid(unsafe_code)]
520#![warn(clippy::all)]
521#![deny(missing_docs)]
522// I don't think elided lifetimes help in understanding the code.
523#![allow(clippy::needless_lifetimes)]
524// Since you can't use inlined args for all cases, using it means the
525// code will have a mix of inlined and not inlined. Code should be
526// uniform, thus this lint is misguided.
527#![allow(clippy::uninlined_format_args)]
528#![allow(mismatched_lifetime_syntaxes)]
529
530#[macro_use]
531extern crate log;
532
533use std::convert::TryFrom;
534
535/// Re-exported http-crate.
536pub use ureq_proto::http;
537
538pub use body::{Body, BodyBuilder, BodyReader, BodyWithConfig};
539use http::Method;
540use http::{Request, Response, Uri};
541pub use proxy::{Proxy, ProxyBuilder, ProxyProtocol};
542pub use request::RequestBuilder;
543use request::{WithBody, WithoutBody};
544pub use request_ext::RequestExt;
545pub use response::ResponseExt;
546pub use send_body::AsSendBody;
547
548mod agent;
549mod body;
550pub mod config;
551mod error;
552mod pool;
553mod proxy;
554mod query;
555mod request;
556mod response;
557mod run;
558mod send_body;
559mod timings;
560mod util;
561
562pub mod unversioned;
563use unversioned::resolver;
564use unversioned::transport;
565
566pub mod middleware;
567
568#[cfg(feature = "_tls")]
569pub mod tls;
570
571#[cfg(feature = "cookies")]
572mod cookies;
573mod request_ext;
574
575#[cfg(feature = "cookies")]
576pub use cookies::{Cookie, CookieJar};
577
578pub use agent::Agent;
579pub use error::Error;
580pub use send_body::SendBody;
581pub use timings::Timeout;
582
583/// Typestate variables.
584pub mod typestate {
585    pub use super::request::WithBody;
586    pub use super::request::WithoutBody;
587
588    pub use super::config::typestate::AgentScope;
589    pub use super::config::typestate::HttpCrateScope;
590    pub use super::config::typestate::RequestScope;
591}
592
593/// Run a [`http::Request<impl AsSendBody>`].
594pub fn run(request: Request<impl AsSendBody>) -> Result<Response<Body>, Error> {
595    let agent = Agent::new_with_defaults();
596    agent.run(request)
597}
598
599/// A new [Agent] with default configuration
600///
601/// Agents are used to hold configuration and keep state between requests.
602pub fn agent() -> Agent {
603    Agent::new_with_defaults()
604}
605
606/// Make a GET request.
607///
608/// Run on a use-once [`Agent`].
609#[must_use]
610pub fn get<T>(uri: T) -> RequestBuilder<WithoutBody>
611where
612    Uri: TryFrom<T>,
613    <Uri as TryFrom<T>>::Error: Into<http::Error>,
614{
615    RequestBuilder::<WithoutBody>::new(Agent::new_with_defaults(), Method::GET, uri)
616}
617
618/// Make a POST request.
619///
620/// Run on a use-once [`Agent`].
621#[must_use]
622pub fn post<T>(uri: T) -> RequestBuilder<WithBody>
623where
624    Uri: TryFrom<T>,
625    <Uri as TryFrom<T>>::Error: Into<http::Error>,
626{
627    RequestBuilder::<WithBody>::new(Agent::new_with_defaults(), Method::POST, uri)
628}
629
630/// Make a PUT request.
631///
632/// Run on a use-once [`Agent`].
633#[must_use]
634pub fn put<T>(uri: T) -> RequestBuilder<WithBody>
635where
636    Uri: TryFrom<T>,
637    <Uri as TryFrom<T>>::Error: Into<http::Error>,
638{
639    RequestBuilder::<WithBody>::new(Agent::new_with_defaults(), Method::PUT, uri)
640}
641
642/// Make a DELETE request.
643///
644/// Run on a use-once [`Agent`].
645#[must_use]
646pub fn delete<T>(uri: T) -> RequestBuilder<WithoutBody>
647where
648    Uri: TryFrom<T>,
649    <Uri as TryFrom<T>>::Error: Into<http::Error>,
650{
651    RequestBuilder::<WithoutBody>::new(Agent::new_with_defaults(), Method::DELETE, uri)
652}
653
654/// Make a HEAD request.
655///
656/// Run on a use-once [`Agent`].
657#[must_use]
658pub fn head<T>(uri: T) -> RequestBuilder<WithoutBody>
659where
660    Uri: TryFrom<T>,
661    <Uri as TryFrom<T>>::Error: Into<http::Error>,
662{
663    RequestBuilder::<WithoutBody>::new(Agent::new_with_defaults(), Method::HEAD, uri)
664}
665
666/// Make an OPTIONS request.
667///
668/// Run on a use-once [`Agent`].
669#[must_use]
670pub fn options<T>(uri: T) -> RequestBuilder<WithoutBody>
671where
672    Uri: TryFrom<T>,
673    <Uri as TryFrom<T>>::Error: Into<http::Error>,
674{
675    RequestBuilder::<WithoutBody>::new(Agent::new_with_defaults(), Method::OPTIONS, uri)
676}
677
678/// Make a CONNECT request.
679///
680/// Run on a use-once [`Agent`].
681#[must_use]
682pub fn connect<T>(uri: T) -> RequestBuilder<WithoutBody>
683where
684    Uri: TryFrom<T>,
685    <Uri as TryFrom<T>>::Error: Into<http::Error>,
686{
687    RequestBuilder::<WithoutBody>::new(Agent::new_with_defaults(), Method::CONNECT, uri)
688}
689
690/// Make a PATCH request.
691///
692/// Run on a use-once [`Agent`].
693#[must_use]
694pub fn patch<T>(uri: T) -> RequestBuilder<WithBody>
695where
696    Uri: TryFrom<T>,
697    <Uri as TryFrom<T>>::Error: Into<http::Error>,
698{
699    RequestBuilder::<WithBody>::new(Agent::new_with_defaults(), Method::PATCH, uri)
700}
701
702/// Make a TRACE request.
703///
704/// Run on a use-once [`Agent`].
705#[must_use]
706pub fn trace<T>(uri: T) -> RequestBuilder<WithoutBody>
707where
708    Uri: TryFrom<T>,
709    <Uri as TryFrom<T>>::Error: Into<http::Error>,
710{
711    RequestBuilder::<WithoutBody>::new(Agent::new_with_defaults(), Method::TRACE, uri)
712}
713
714#[cfg(test)]
715pub(crate) mod test {
716    use std::{io, sync::OnceLock};
717
718    use assert_no_alloc::AllocDisabler;
719    use config::{Config, ConfigBuilder};
720    use typestate::AgentScope;
721
722    use super::*;
723
724    #[global_allocator]
725    // Some tests checks that we are not allocating
726    static A: AllocDisabler = AllocDisabler;
727
728    pub fn init_test_log() {
729        static INIT_LOG: OnceLock<()> = OnceLock::new();
730        INIT_LOG.get_or_init(env_logger::init);
731    }
732
733    #[test]
734    fn connect_http_google() {
735        init_test_log();
736        let agent = Agent::new_with_defaults();
737
738        let res = agent.get("http://www.google.com/").call().unwrap();
739        assert_eq!(
740            "text/html;charset=ISO-8859-1",
741            res.headers()
742                .get("content-type")
743                .unwrap()
744                .to_str()
745                .unwrap()
746                .replace("; ", ";")
747        );
748        assert_eq!(res.body().mime_type(), Some("text/html"));
749    }
750
751    #[test]
752    #[cfg(feature = "rustls")]
753    fn connect_https_google_rustls() {
754        init_test_log();
755        use config::Config;
756
757        use crate::tls::{TlsConfig, TlsProvider};
758
759        let agent: Agent = Config::builder()
760            .tls_config(TlsConfig::builder().provider(TlsProvider::Rustls).build())
761            .build()
762            .into();
763
764        let res = agent.get("https://www.google.com/").call().unwrap();
765        assert_eq!(
766            "text/html;charset=ISO-8859-1",
767            res.headers()
768                .get("content-type")
769                .unwrap()
770                .to_str()
771                .unwrap()
772                .replace("; ", ";")
773        );
774        assert_eq!(res.body().mime_type(), Some("text/html"));
775    }
776
777    #[test]
778    #[cfg(feature = "native-tls")]
779    fn connect_https_google_native_tls_simple() {
780        init_test_log();
781        use config::Config;
782
783        use crate::tls::{TlsConfig, TlsProvider};
784
785        let agent: Agent = Config::builder()
786            .tls_config(
787                TlsConfig::builder()
788                    .provider(TlsProvider::NativeTls)
789                    .build(),
790            )
791            .build()
792            .into();
793
794        let mut res = agent.get("https://www.google.com/").call().unwrap();
795
796        assert_eq!(
797            "text/html;charset=ISO-8859-1",
798            res.headers()
799                .get("content-type")
800                .unwrap()
801                .to_str()
802                .unwrap()
803                .replace("; ", ";")
804        );
805        assert_eq!(res.body().mime_type(), Some("text/html"));
806        res.body_mut().read_to_string().unwrap();
807    }
808
809    #[test]
810    #[cfg(feature = "rustls")]
811    fn connect_https_google_rustls_webpki() {
812        init_test_log();
813        use crate::tls::{RootCerts, TlsConfig, TlsProvider};
814        use config::Config;
815
816        let agent: Agent = Config::builder()
817            .tls_config(
818                TlsConfig::builder()
819                    .provider(TlsProvider::Rustls)
820                    .root_certs(RootCerts::WebPki)
821                    .build(),
822            )
823            .build()
824            .into();
825
826        agent.get("https://www.google.com/").call().unwrap();
827    }
828
829    #[test]
830    #[cfg(feature = "native-tls")]
831    fn connect_https_google_native_tls_webpki() {
832        init_test_log();
833        use crate::tls::{RootCerts, TlsConfig, TlsProvider};
834        use config::Config;
835
836        let agent: Agent = Config::builder()
837            .tls_config(
838                TlsConfig::builder()
839                    .provider(TlsProvider::NativeTls)
840                    .root_certs(RootCerts::WebPki)
841                    .build(),
842            )
843            .build()
844            .into();
845
846        agent.get("https://www.google.com/").call().unwrap();
847    }
848
849    #[test]
850    #[cfg(feature = "rustls")]
851    fn connect_https_google_noverif() {
852        init_test_log();
853        use crate::tls::{TlsConfig, TlsProvider};
854
855        let agent: Agent = Config::builder()
856            .tls_config(
857                TlsConfig::builder()
858                    .provider(TlsProvider::Rustls)
859                    .disable_verification(true)
860                    .build(),
861            )
862            .build()
863            .into();
864
865        let res = agent.get("https://www.google.com/").call().unwrap();
866        assert_eq!(
867            "text/html;charset=ISO-8859-1",
868            res.headers()
869                .get("content-type")
870                .unwrap()
871                .to_str()
872                .unwrap()
873                .replace("; ", ";")
874        );
875        assert_eq!(res.body().mime_type(), Some("text/html"));
876    }
877
878    #[test]
879    fn simple_put_content_len() {
880        init_test_log();
881        let mut res = put("http://httpbin.org/put").send(&[0_u8; 100]).unwrap();
882        res.body_mut().read_to_string().unwrap();
883    }
884
885    #[test]
886    fn simple_put_chunked() {
887        init_test_log();
888        let mut res = put("http://httpbin.org/put")
889            // override default behavior
890            .header("transfer-encoding", "chunked")
891            .send(&[0_u8; 100])
892            .unwrap();
893        res.body_mut().read_to_string().unwrap();
894    }
895
896    #[test]
897    fn simple_get() {
898        init_test_log();
899        let mut res = get("http://httpbin.org/get").call().unwrap();
900        res.body_mut().read_to_string().unwrap();
901    }
902
903    #[test]
904    fn query_no_slash() {
905        init_test_log();
906        let mut res = get("http://httpbin.org?query=foo").call().unwrap();
907        res.body_mut().read_to_string().unwrap();
908    }
909
910    #[test]
911    fn simple_head() {
912        init_test_log();
913        let mut res = head("http://httpbin.org/get").call().unwrap();
914        res.body_mut().read_to_string().unwrap();
915    }
916
917    #[test]
918    fn redirect_no_follow() {
919        init_test_log();
920        let agent: Agent = Config::builder().max_redirects(0).build().into();
921        let mut res = agent
922            .get("http://httpbin.org/redirect-to?url=%2Fget")
923            .call()
924            .unwrap();
925        let txt = res.body_mut().read_to_string().unwrap();
926        #[cfg(feature = "_test")]
927        assert_eq!(txt, "You've been redirected");
928        #[cfg(not(feature = "_test"))]
929        assert_eq!(txt, "");
930    }
931
932    #[test]
933    fn test_send_form_content_type() {
934        init_test_log();
935
936        // These tests verify that the methods work correctly with the test infrastructure
937        // The actual Content-Type verification happens at the transport level
938        let form_data = [("key1", "value1"), ("key2", "value2")];
939        let mut res = post("http://httpbin.org/post")
940            .header("x-verify-content-type", "application/x-www-form-urlencoded")
941            .send_form(form_data)
942            .unwrap();
943
944        let _txt = res.body_mut().read_to_string().unwrap();
945    }
946
947    #[test]
948    #[cfg(feature = "json")]
949    fn test_send_json_content_type() {
950        use serde_json::json;
951
952        init_test_log();
953
954        let data = json!({"key": "value"});
955        let mut res = post("http://httpbin.org/post")
956            .header("x-verify-content-type", "application/json; charset=utf-8")
957            .send_json(&data)
958            .unwrap();
959
960        let _txt = res.body_mut().read_to_string().unwrap();
961    }
962
963    #[test]
964    #[cfg(feature = "multipart")]
965    fn test_send_multipart_content_type() {
966        use crate::unversioned::multipart::Form;
967
968        init_test_log();
969
970        let form = Form::new()
971            .text("field1", "value1")
972            .text("field2", "value2");
973
974        let mut res = post("http://httpbin.org/post")
975            .header("x-verify-content-type", "multipart/form-data")
976            .send(form)
977            .unwrap();
978
979        let _txt = res.body_mut().read_to_string().unwrap();
980    }
981
982    #[test]
983    fn redirect_max_with_error() {
984        init_test_log();
985        let agent: Agent = Config::builder().max_redirects(3).build().into();
986        let res = agent
987            .get(
988                "http://httpbin.org/redirect-to?url=%2Fredirect-to%3F\
989                url%3D%2Fredirect-to%3Furl%3D%252Fredirect-to%253Furl%253D",
990            )
991            .call();
992        let err = res.unwrap_err();
993        assert_eq!(err.to_string(), "too many redirects");
994    }
995
996    #[test]
997    fn redirect_max_without_error() {
998        init_test_log();
999        let agent: Agent = Config::builder()
1000            .max_redirects(3)
1001            .max_redirects_will_error(false)
1002            .build()
1003            .into();
1004        let res = agent
1005            .get(
1006                "http://httpbin.org/redirect-to?url=%2Fredirect-to%3F\
1007                url%3D%2Fredirect-to%3Furl%3D%252Fredirect-to%253Furl%253D",
1008            )
1009            .call()
1010            .unwrap();
1011        assert_eq!(res.status(), 302);
1012    }
1013
1014    #[test]
1015    fn redirect_follow() {
1016        init_test_log();
1017        let res = get("http://httpbin.org/redirect-to?url=%2Fget")
1018            .call()
1019            .unwrap();
1020        let response_uri = res.get_uri();
1021        assert_eq!(response_uri.path(), "/get")
1022    }
1023
1024    #[test]
1025    fn redirect_history_none() {
1026        init_test_log();
1027        let res = get("http://httpbin.org/redirect-to?url=%2Fget")
1028            .call()
1029            .unwrap();
1030        let redirect_history = res.get_redirect_history();
1031        assert_eq!(redirect_history, None)
1032    }
1033
1034    #[test]
1035    fn redirect_history_some() {
1036        init_test_log();
1037        let agent: Agent = Config::builder()
1038            .max_redirects(3)
1039            .max_redirects_will_error(false)
1040            .save_redirect_history(true)
1041            .build()
1042            .into();
1043        let res = agent
1044            .get("http://httpbin.org/redirect-to?url=%2Fget")
1045            .call()
1046            .unwrap();
1047        let redirect_history = res.get_redirect_history();
1048        assert_eq!(
1049            redirect_history,
1050            Some(
1051                vec![
1052                    "http://httpbin.org/redirect-to?url=%2Fget".parse().unwrap(),
1053                    "http://httpbin.org/get".parse().unwrap()
1054                ]
1055                .as_ref()
1056            )
1057        );
1058        let res = agent
1059            .get(
1060                "http://httpbin.org/redirect-to?url=%2Fredirect-to%3F\
1061                url%3D%2Fredirect-to%3Furl%3D%252Fredirect-to%253Furl%253D",
1062            )
1063            .call()
1064            .unwrap();
1065        let redirect_history = res.get_redirect_history();
1066        assert_eq!(
1067            redirect_history,
1068            Some(vec![
1069                "http://httpbin.org/redirect-to?url=%2Fredirect-to%3Furl%3D%2Fredirect-to%3Furl%3D%252Fredirect-to%253Furl%253D".parse().unwrap(),
1070                "http://httpbin.org/redirect-to?url=/redirect-to?url=%2Fredirect-to%3Furl%3D".parse().unwrap(),
1071                "http://httpbin.org/redirect-to?url=/redirect-to?url=".parse().unwrap(),
1072                "http://httpbin.org/redirect-to?url=".parse().unwrap(),
1073            ].as_ref())
1074        );
1075        let res = agent.get("https://www.google.com/").call().unwrap();
1076        let redirect_history = res.get_redirect_history();
1077        assert_eq!(
1078            redirect_history,
1079            Some(vec!["https://www.google.com/".parse().unwrap()].as_ref())
1080        );
1081    }
1082
1083    #[test]
1084    fn connect_https_invalid_name() {
1085        let result = get("https://example.com{REQUEST_URI}/").call();
1086        let err = result.unwrap_err();
1087        assert!(matches!(err, Error::Http(_)));
1088        assert_eq!(err.to_string(), "http: invalid uri character");
1089    }
1090
1091    #[test]
1092    fn post_big_body_chunked() {
1093        init_test_log();
1094        // https://github.com/algesten/ureq/issues/879
1095        let mut data = io::Cursor::new(vec![42; 153_600]);
1096        post("http://httpbin.org/post")
1097            .content_type("application/octet-stream")
1098            .send(SendBody::from_reader(&mut data))
1099            .expect("to send correctly");
1100    }
1101
1102    #[test]
1103    #[cfg(not(feature = "_test"))]
1104    fn post_array_body_sends_content_length() {
1105        init_test_log();
1106        let mut response = post("http://httpbin.org/post")
1107            .content_type("application/octet-stream")
1108            .send(vec![42; 123])
1109            .expect("to send correctly");
1110
1111        let ret = response.body_mut().read_to_string().unwrap();
1112        assert!(ret.contains("\"Content-Length\": \"123\""));
1113    }
1114
1115    #[test]
1116    #[cfg(not(feature = "_test"))]
1117    fn post_file_sends_file_length() {
1118        init_test_log();
1119
1120        let file = std::fs::File::open("LICENSE-MIT").unwrap();
1121
1122        let mut response = post("http://httpbin.org/post")
1123            .content_type("application/octet-stream")
1124            .send(file)
1125            .expect("to send correctly");
1126
1127        let ret = response.body_mut().read_to_string().unwrap();
1128        assert!(ret.contains("\"Content-Length\": \"1072\""));
1129    }
1130
1131    #[test]
1132    #[cfg(not(feature = "_test"))]
1133    fn username_password_from_uri() {
1134        init_test_log();
1135        let mut res = get("https://martin:secret@httpbin.org/get").call().unwrap();
1136        let body = res.body_mut().read_to_string().unwrap();
1137        assert!(body.contains("Basic bWFydGluOnNlY3JldA=="));
1138    }
1139
1140    #[test]
1141    #[cfg(all(feature = "cookies", feature = "_test"))]
1142    fn store_response_cookies() {
1143        let agent = Agent::new_with_defaults();
1144        let _ = agent.get("https://www.google.com").call().unwrap();
1145
1146        let mut all: Vec<_> = agent
1147            .cookie_jar_lock()
1148            .iter()
1149            .map(|c| c.name().to_string())
1150            .collect();
1151
1152        all.sort();
1153
1154        assert_eq!(all, ["AEC", "__Secure-ENID"])
1155    }
1156
1157    #[test]
1158    #[cfg(all(feature = "cookies", feature = "_test"))]
1159    fn send_request_cookies() {
1160        init_test_log();
1161
1162        let agent = Agent::new_with_defaults();
1163        let uri = Uri::from_static("http://cookie.test/cookie-test");
1164        let uri2 = Uri::from_static("http://cookie2.test/cookie-test");
1165
1166        let mut jar = agent.cookie_jar_lock();
1167        jar.insert(Cookie::parse("a=1", &uri).unwrap(), &uri)
1168            .unwrap();
1169        jar.insert(Cookie::parse("b=2", &uri).unwrap(), &uri)
1170            .unwrap();
1171        jar.insert(Cookie::parse("c=3", &uri2).unwrap(), &uri2)
1172            .unwrap();
1173
1174        jar.release();
1175
1176        let _ = agent.get("http://cookie.test/cookie-test").call().unwrap();
1177    }
1178
1179    #[test]
1180    #[cfg(all(feature = "_test", not(feature = "cookies")))]
1181    fn partial_redirect_when_following() {
1182        init_test_log();
1183        // this should work because we follow the redirect and go to /get
1184        get("http://my-host.com/partial-redirect").call().unwrap();
1185    }
1186
1187    #[test]
1188    #[cfg(feature = "_test")]
1189    fn partial_redirect_when_not_following() {
1190        init_test_log();
1191        // this should fail because we are not following redirects, and the
1192        // response is partial before the server is hanging up
1193        get("http://my-host.com/partial-redirect")
1194            .config()
1195            .max_redirects(0)
1196            .build()
1197            .call()
1198            .unwrap_err();
1199    }
1200
1201    #[test]
1202    #[cfg(feature = "_test")]
1203    fn http_connect_proxy() {
1204        init_test_log();
1205
1206        let proxy = Proxy::new("http://my_proxy:1234/connect-proxy").unwrap();
1207
1208        let agent = Agent::config_builder()
1209            .proxy(Some(proxy))
1210            .build()
1211            .new_agent();
1212
1213        let mut res = agent.get("http://httpbin.org/get").call().unwrap();
1214        res.body_mut().read_to_string().unwrap();
1215    }
1216
1217    #[test]
1218    fn ensure_reasonable_stack_sizes() {
1219        macro_rules! ensure {
1220            ($type:ty, $size:tt) => {
1221                let sz = std::mem::size_of::<$type>();
1222                // println!("{}: {}", stringify!($type), sz);
1223                assert!(
1224                    sz <= $size,
1225                    "Stack size of {} is too big {} > {}",
1226                    stringify!($type),
1227                    sz,
1228                    $size
1229                );
1230            };
1231        }
1232
1233        ensure!(RequestBuilder<WithoutBody>, 400); // 304
1234        ensure!(Agent, 100); // 32
1235        ensure!(Config, 400); // 320
1236        ensure!(ConfigBuilder<AgentScope>, 400); // 320
1237        ensure!(Response<Body>, 250); // 136
1238        ensure!(Body, 50); // 24
1239    }
1240
1241    #[test]
1242    #[cfg(feature = "_test")]
1243    fn limit_max_response_header_size() {
1244        init_test_log();
1245        let err = get("http://httpbin.org/get")
1246            .config()
1247            .max_response_header_size(5)
1248            .build()
1249            .call()
1250            .unwrap_err();
1251        assert!(matches!(err, Error::LargeResponseHeader(65, 5)));
1252    }
1253
1254    #[test]
1255    #[cfg(feature = "_test")]
1256    fn propfind_with_body() {
1257        init_test_log();
1258
1259        // https://github.com/algesten/ureq/issues/1034
1260        let request = http::Request::builder()
1261            .method("PROPFIND")
1262            .uri("https://www.google.com/")
1263            .body("Some really cool body")
1264            .unwrap();
1265
1266        let _ = Agent::config_builder()
1267            .allow_non_standard_methods(true)
1268            .build()
1269            .new_agent()
1270            .run(request)
1271            .unwrap();
1272    }
1273
1274    #[test]
1275    #[cfg(feature = "_test")]
1276    fn non_standard_method() {
1277        init_test_log();
1278        let method = Method::from_bytes(b"FNORD").unwrap();
1279
1280        let req = Request::builder()
1281            .method(method)
1282            .uri("http://httpbin.org/fnord")
1283            .body(())
1284            .unwrap();
1285
1286        let agent = Agent::new_with_defaults();
1287
1288        let req = agent
1289            .configure_request(req)
1290            .allow_non_standard_methods(true)
1291            .build();
1292
1293        agent.run(req).unwrap();
1294    }
1295
1296    #[test]
1297    #[cfg(feature = "_test")]
1298    fn chunk_abort() {
1299        init_test_log();
1300        let mut res = get("http://my-fine-server/1chunk-abort").call().unwrap();
1301        let body = res.body_mut().read_to_string().unwrap();
1302        assert_eq!(body, "OK");
1303        let mut res = get("http://my-fine-server/2chunk-abort").call().unwrap();
1304        let body = res.body_mut().read_to_string().unwrap();
1305        assert_eq!(body, "OK");
1306        let mut res = get("http://my-fine-server/3chunk-abort").call().unwrap();
1307        let body = res.body_mut().read_to_string().unwrap();
1308        assert_eq!(body, "OK");
1309        let mut res = get("http://my-fine-server/4chunk-abort").call().unwrap();
1310        let body = res.body_mut().read_to_string().unwrap();
1311        assert_eq!(body, "OK");
1312    }
1313
1314    // This doesn't need to run, just compile.
1315    fn _ensure_send_sync() {
1316        fn is_send(_t: impl Send) {}
1317        fn is_sync(_t: impl Sync) {}
1318
1319        // Agent
1320        is_send(Agent::new_with_defaults());
1321        is_sync(Agent::new_with_defaults());
1322
1323        // ResponseBuilder
1324        is_send(get("https://example.test"));
1325        is_sync(get("https://example.test"));
1326
1327        let data = vec![0_u8, 1, 2, 3, 4];
1328
1329        // Response<Body> via ResponseBuilder
1330        is_send(post("https://example.test").send(&data));
1331        is_sync(post("https://example.test").send(&data));
1332
1333        // Request<impl AsBody>
1334        is_send(Request::post("https://yaz").body(&data).unwrap());
1335        is_sync(Request::post("https://yaz").body(&data).unwrap());
1336
1337        // Response<Body> via Agent::run
1338        is_send(run(Request::post("https://yaz").body(&data).unwrap()));
1339        is_sync(run(Request::post("https://yaz").body(&data).unwrap()));
1340
1341        // Response<BodyReader<'a>>
1342        let mut response = post("https://yaz").send(&data).unwrap();
1343        let shared_reader = response.body_mut().as_reader();
1344        is_send(shared_reader);
1345        let shared_reader = response.body_mut().as_reader();
1346        is_sync(shared_reader);
1347
1348        // Response<BodyReader<'static>>
1349        let response = post("https://yaz").send(&data).unwrap();
1350        let owned_reader = response.into_parts().1.into_reader();
1351        is_send(owned_reader);
1352        let response = post("https://yaz").send(&data).unwrap();
1353        let owned_reader = response.into_parts().1.into_reader();
1354        is_sync(owned_reader);
1355
1356        let err = Error::HostNotFound;
1357        is_send(err);
1358        let err = Error::HostNotFound;
1359        is_sync(err);
1360    }
1361}