hreq/lib.rs
1#![warn(clippy::all)]
2#![warn(missing_docs, missing_debug_implementations)]
3
4//! hreq is a user first async http client and server.
5//!
6//! ### Early days
7//!
8//! This library needs road testing. Bug reports and PRs are very welcome!
9//!
10//! ### Principles
11//!
12//! The principles of this library are:
13//!
14//! * User first API built on the http crate.
15//! * async (or blocking via minimal runtime).
16//! * Pure Rust.
17//!
18//! ```no_run
19//! use hreq::prelude::*;
20//!
21//! fn main() -> Result<(), hreq::Error> {
22//! // Use plain http API request builder with
23//! // trait extensions for extra convenience
24//! // in handling query parameters and other
25//! // request configurations.
26//! let response = Request::builder()
27//! .uri("https://myapi.acme.com/ingest")
28//! .query("api_key", "secret")
29//! .call().block()?;
30//!
31//! // More convenience on the http response.
32//! // Like shortcuts to read or parse
33//! // response headers.
34//! let x_req_id =
35//! response.header_as::<usize>("x-req-id")
36//! .unwrap();
37//!
38//! // A Body type with easy ways to
39//! // get the content.
40//! let mut body = response.into_body();
41//! let contents = body.read_to_string().block()?;
42//!
43//! assert_eq!(contents, "Hello world!");
44//!
45//! Ok(())
46//! }
47//! ```
48//! # User first
49//!
50//! _User first_ means that in situations where there are trade offs
51//! between ergonomics and performance, or ergonomics and correctness,
52//! extra weight will be put towards ergonomics. hreq does not attempt
53//! to win any performance or benchmark competitions at the same time
54//! as it should not be particularly slow or wasteful of system
55//! resources.
56//!
57//! # http crate
58//!
59//! Many rust http client/servers use some variant of the [http crate].
60//! It's often copied into the local source tree and extended from there.
61//!
62//! When writing a service that uses both a web server and
63//! client crate, one often ends up with similar, but not exactly the
64//! same versions of types like `http::Request` and `http::Response`.
65//!
66//! hreq works using extension traits only. It re-exports the http
67//! crate, but does not copy or modify it. It therefore adheres
68//! strictly to the exact API definition as set out by the
69//! http crate as well as avoids furthering the confusion of having
70//! multiple types with the same name.
71//!
72//! # Async and blocking
73//!
74//! Rust's async story is fantastic, but not every situation requires
75//! async. hreq "fakes" being a blocking library by default having a
76//! very minimal tokio runtime ([`rt-core`]) combined with a `.block()`
77//! call that is placed where we expect an `.await` in an async
78//! situation.
79//!
80//! # All examples using `.block()` can be `.await`
81//!
82//! It's anticipated hreq is often used in an async context, however
83//! rustdoc doesn't let us document the code that way. Everywhere
84//! the doc does `.block()`, you can switch that out for `.await`.
85//!
86//! ```
87//! use hreq::prelude::*;
88//!
89//! let res = Request::get("https://httpbin.org/get")
90//! .call().block(); // this can be .await in async
91//! ```
92//!
93//! ## Why?
94//!
95//! hreq is async through-and-through and ultimately relies on an
96//! async variant of [`TcpStream`] for it to function. Because the
97//! TCP socket is one of those things that is tightly coupled to
98//! the async event loop, `TcpStream` in turn needs to be provided
99//! by the runtime (tokio)
100//!
101//! There are talks of rust providing a simple single threaded
102//! executor as part of the std lib. This only solves half of the
103//! problem since `TcpStream` is coupled with the runtime.
104//!
105//! # Async runtime
106//!
107//! The async runtime is "pluggable" and comes in some different
108//! flavors.
109//!
110//! * `TokioSingle`. The default option. A minimal tokio `rt-core`
111//! which executes calls in one single thread. It does nothing
112//! until the current thread blocks on a future using `.block()`.
113//! * `TokioShared`. Picks up on a shared runtime by using a
114//! [`Handle`]. This runtime cannot use the `.block()` extension
115//! trait since that requires having a direct connection to the
116//! tokio [`Runtime`].
117//! * `TokioOwned`. Uses a preconfigured tokio [`Runtime`] that is
118//! "handed over" to hreq.
119//!
120//! How to configure the options is explained in [`AsyncRuntime`].
121//!
122//! ## Tokio only
123//!
124//! This project set out with the ambition to be runtime agnostic, specifically
125//! to also support async-std (and/or smol), however in practice that was not
126//! a viable route due to it taking too much work to maintain. Rust is likely
127//! to eventually provide a pluggable runtime mechanic, in which case this
128//! library will try to be agnostic again.
129//!
130//! # Agent, redirect and retries
131//!
132//! All calls in hreq goes through an [`Agent`]. The agent provides
133//! three main functions:
134//!
135//! * Retries
136//! * Connection pooling
137//! * Cookie handling
138//!
139//! However the simplest use of hreq creates a new agent for every call, which
140//! means connection pooling and cookie handling is only happening to a limited
141//! extent (when following redirects).
142//!
143//! ```
144//! use hreq::prelude::*;
145//!
146//! let res1 = Request::get("https://httpbin.org/get")
147//! .call().block(); // creates a new agent
148//!
149//! // this call doesn't reuse any cookies or connections.
150//! let res2 = Request::get("https://httpbin.org/get")
151//! .call().block(); // creates another new agent
152//! ```
153//!
154//! To use connection pooling and cookies between multiple calls, we need to
155//! create an agent.
156//!
157//! ```
158//! use hreq::prelude::*;
159//! use hreq::Agent;
160//!
161//! let mut agent = Agent::new();
162//!
163//! let req1 = Request::get("https://httpbin.org/get")
164//! .with_body(()).unwrap();
165//!
166//! let res1 = agent.send(req1).block();
167//!
168//! let req2 = Request::get("https://httpbin.org/get")
169//! .with_body(()).unwrap();
170//!
171//! // this call (tries to) reuse the connection in
172//! // req1 since we are using the same agent.
173//! let res2 = agent.send(req2).block();
174//! ```
175//!
176//! ## Retries
177//!
178//! The internet is a dangerous place and http requests fail all the time.
179//! hreq tries to be helpful and has a built in retries by default. However
180//! it will only retry when appropriate.
181//!
182//! * The default number of retries is 5 with a backoff going 125,
183//! 250, 500, 1000 milliseconds.
184//! * Only for idempotent methods: GET, HEAD, OPTIONS, TRACE, PUT and DELETE.
185//! * Only when the encountered error is retryable, such as BrokenPipe,
186//! ConnectionAborted, ConnectionReset, Interrupted.
187//!
188//! To disable retries, one must use a configured agent:
189//!
190//! ```
191//! use hreq::prelude::*;
192//! use hreq::Agent;
193//!
194//! let mut agent = Agent::new();
195//! agent.retries(0); // disable all retries
196//!
197//! let req = Request::get("https://httpbin.org/get")
198//! .with_body(()).unwrap();
199//!
200//! let res = agent.send(req).block();
201//! ```
202//!
203//! ## Redirects
204//!
205//! By default hreq follows up to 5 redirects. Redirects can be turned off
206//! by using an explicit agent in the same way as for retries.
207//!
208//! # Compression
209//!
210//! hreq supports content compression both for requests and responses. The
211//! feature is enabled by receving or setting the `content-encoding` header
212//! to `gzip`. Currently hreq only supports `gzip`.
213//!
214//! ## Example request with gzip body:
215//!
216//! ```
217//! use hreq::prelude::*;
218//!
219//! let res = Request::post("https://my-special-server/content")
220//! .header("content-encoding", "gzip") // enables gzip compression
221//! .send("request that is compressed".to_string()).block();
222//! ```
223//!
224//! The automatic compression and decompression can be turned off,
225//! see [`content_encode`] and [`content_decode`].
226//!
227//! # Charset
228//!
229//! Similarly to body compression hreq provides an automatic way of
230//! encoding and decoding text in request/response bodies. Rust uses
231//! utf-8 for `String` and assumes text bodies should be encoded as
232//! utf-8. Using the `content-type` we can change how hreq handles
233//! both requests and responses.
234//!
235//! ## Example sending an iso-8859-1 encoded body.
236//!
237//! ```no_run
238//! use hreq::prelude::*;
239//!
240//! // This is a &str in rust default utf-8
241//! let content = "Und in die Bäumen hängen Löwen und Bären";
242//!
243//! let req = Request::post("https://my-euro-server/")
244//! // This header converts the body to iso8859-1
245//! .header("content-type", "text/plain; charset=iso8859-1")
246//! .send(content).block();
247//! ```
248//!
249//! Receiving bodies of other charset is mostly transparent to the
250//! user. It will decode the body to utf-8 if a `content-type` header
251//! is present in the response.
252//!
253//! Only content types with a mime type `text/*` will be decoded.
254//!
255//! The charset encoding does not need to work only with utf-8. It
256//! can transcode between different encodings as appropriate. See
257//! [`charset_encode_source`] and [`charset_decode_target`].
258//!
259//! # Body size
260//!
261//! Depending on how a body is provided to a request hreq may or may
262//! not be able to know the total body size. For example, when the
263//! body provided as a string hreq will set the `content-size` header,
264//! and when the body is a `Reader`, hreq will not know the content
265//! size, but it can be set by the user.
266//!
267//! If the content size is not known for HTTP1.1, hreq is forced to
268//! use `transfer-encoding: chunked`. For HTTP2, this problem never
269//! arises.
270//!
271//! # JSON
272//!
273//! By default, hreq uses the [serde] crate to send and receive JSON
274//! encoded bodies. Because serde is so ubiquitous in Rust, this
275//! feature is enabled by default.
276//!
277//!
278//! ```
279//! use hreq::Body;
280//! use serde_derive::Serialize;
281//!
282//! #[derive(Serialize)]
283//! struct MyJsonThing {
284//! name: String,
285//! age: u8,
286//! }
287//!
288//! let json = MyJsonThing {
289//! name: "Karl Kajal".to_string(),
290//! age: 32,
291//! };
292//!
293//! let body = Body::from_json(&json);
294//! ```
295//!
296//! # Server
297//!
298//! hreq started as a client but now also got a simple server mechanism. It
299//! can route requests, use middleware, handle state and serve TLS.
300//!
301//! See the [`server module doc`] for more details.
302//!
303//! ```no_run
304//! # // ignore this example if not feature server
305//! # #[cfg(feature = "server")] {
306//! use hreq::prelude::*;
307//!
308//! async fn start_server() {
309//! let mut server = Server::new();
310//! server.at("/hello/:name").get(hello_there);
311//! let (shut, addr) = server.listen(0).await.expect("Failed to listen");
312//! println!("Listening to: {}", addr);
313//! shut.shutdown().await;
314//! }
315//!
316//! async fn hello_there(req: http::Request<Body>) -> String {
317//! let name = req.path_param("name").unwrap();
318//! format!("Hello there {}!\n", name)
319//! }
320//! # }
321//! ```
322//!
323//! # Capabilities
324//!
325//! * Async or blocking
326//! * Pure rust
327//! * HTTP/2 and HTTP/1.1
328//! * TLS (https)
329//! * Timeout for entire request and reading the response
330//! * Single threaded by default
331//! * Built as an extension to `http` crate.
332//! * Query parameter manipulation in request builder
333//! * Many ways to create a request body
334//! * Follow redirects
335//! * Retry on connection problems
336//! * HTTP/1.1 transfer-encoding chunked
337//! * Gzip encode/decode
338//! * Charset encode/decode
339//! * Connection pooling
340//! * JSON serialize/deserialize
341//! * Cookies
342//!
343//! [http crate]: https://crates.io/crates/http
344//! [`rt-core`]: https://docs.rs/tokio/latest/tokio/runtime/index.html#basic-scheduler
345//! [`TcpStream`]: https://doc.rust-lang.org/std/net/struct.TcpStream.html
346//! [`Handle`]: https://docs.rs/tokio/latest/tokio/runtime/struct.Handle.html
347//! [`Runtime`]: https://docs.rs/tokio/latest/tokio/runtime/struct.Runtime.html
348//! [`AsyncRuntime`]: https://docs.rs/hreq/latest/hreq/enum.AsyncRuntime.html
349//! [`Agent`]: https://docs.rs/hreq/latest/hreq/struct.Agent.html
350//! [Expect-100]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/100
351//! [`content_encode`]: https://docs.rs/hreq/latest/hreq/trait.RequestBuilderExt.html#tymethod.content_encode
352//! [`content_decode`]: https://docs.rs/hreq/latest/hreq/trait.RequestBuilderExt.html#tymethod.content_decode
353//! [`charset_encode_source`]: https://docs.rs/hreq/latest/hreq/trait.RequestBuilderExt.html#tymethod.charset_encode_source
354//! [`charset_decode_target`]: https://docs.rs/hreq/latest/hreq/trait.RequestBuilderExt.html#tymethod.charset_decode_target
355//! [serde]: https://crates.io/crates/serde
356//! [`server module doc`]: https://docs.rs/hreq/latest/hreq/server/index.html
357#[macro_use]
358extern crate log;
359
360mod async_impl;
361mod block_ext;
362mod body;
363mod body_codec;
364mod body_send;
365mod bw;
366mod charset;
367mod client;
368mod deadline;
369mod either;
370mod error;
371mod head_ext;
372mod params;
373mod proto;
374mod psl;
375mod res_ext;
376mod uninit;
377mod uri_ext;
378
379pub use client::{Agent, ResponseFuture};
380
381#[cfg(feature = "server")]
382pub mod server;
383
384#[cfg(feature = "tls")]
385mod tls;
386
387mod tokio_conv;
388
389#[doc(hidden)]
390pub const VERSION: &str = env!("CARGO_PKG_VERSION");
391
392use once_cell::sync::Lazy;
393
394// Can't have two slashes here apparently.
395// https://tools.ietf.org/html/rfc7230#section-3.2.6
396pub(crate) const AGENT_IDENT: Lazy<String> = Lazy::new(|| format!("hreq/{}", crate::VERSION));
397
398pub(crate) use futures_io::{AsyncBufRead, AsyncRead, AsyncSeek, AsyncWrite};
399
400pub use crate::async_impl::AsyncRuntime;
401pub use crate::block_ext::BlockExt;
402pub use crate::body::Body;
403pub use crate::client::RequestBuilderExt;
404pub use crate::client::RequestExt;
405pub use crate::error::Error;
406pub use crate::res_ext::ResponseExt;
407pub use http;
408
409pub mod cookie {
410 //! Re-export of the [cookie crate].
411 //!
412 //! [cookie crate]: https://docs.rs/cookie/latest/cookie/
413 pub use cookie::Cookie;
414}
415
416#[cfg(feature = "fuzz")]
417pub use crate::charset::CharCodec;
418
419pub mod prelude {
420 //! A "prelude" for users of the hreq crate.
421 //!
422 //! The idea is that by importing the entire contents of this module to get all the
423 //! essentials of the hreq crate.
424 //!
425 //! ```
426 //! # #![allow(warnings)]
427 //! use hreq::prelude::*;
428 //! ```
429
430 #[doc(no_inline)]
431 pub use crate::{BlockExt, Body, RequestBuilderExt, RequestExt, ResponseExt};
432
433 #[doc(no_inline)]
434 pub use http::{Request, Response};
435
436 #[cfg(feature = "server")]
437 #[doc(no_inline)]
438 pub use crate::server::{ResponseBuilderExt, Router, Server, ServerRequestExt};
439}
440
441pub(crate) trait Stream: AsyncRead + AsyncWrite + Unpin + Send + 'static {}
442impl<Z: AsyncRead + AsyncWrite + Unpin + Send + 'static> Stream for Z {}
443
444pub(crate) trait AsyncReadSeek: AsyncRead + AsyncSeek {}
445impl<Z: AsyncRead + AsyncSeek> AsyncReadSeek for Z {}
446
447#[cfg(test)]
448mod test {
449 /// Not actually a test, but ensure we uphold Send for our core objects.
450 #[allow(unused)]
451 async fn ensure_send_sync() {
452 use super::prelude::*;
453 fn ensure_send(_v: impl Send) {}
454 fn ensure_sync(_v: impl Sync) {}
455
456 let body = Body::empty();
457 ensure_send(body);
458 let body = Body::empty();
459 ensure_sync(body);
460
461 let req = http::Request::get("http://foo.com");
462 ensure_send(req);
463 let req = http::Request::get("http://foo.com");
464 ensure_sync(req);
465
466 let req_fut = http::Request::get("http://foo.com").call();
467 ensure_send(req_fut);
468 // RequestFuture is not Sync. A request being dispatched should
469 // be considered "owned" by a single thread.
470
471 let resp = http::Request::get("http://foo.com").call().await;
472 ensure_send(resp);
473 let resp = http::Request::get("http://foo.com").call().await;
474 ensure_sync(resp);
475 }
476}