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