Skip to main content

http_net/
lib.rs

1//! # http_net
2//!
3//! An HTTP/1.1 engine that does no I/O itself. Feed it the bytes a socket
4//! delivered; it hands back requests (or responses) as borrowed views of
5//! those bytes, decodes their bodies, and serializes what you send back.
6//! The caller owns sockets, threads, and timers, so the same core runs
7//! under a blocking loop, an event loop, or a test that never opens a
8//! socket.
9//!
10//! Parsing follows RFC 9112 strictly. Messages that recipients could
11//! disagree about are refused rather than repaired: conflicting
12//! `Content-Length` / `Transfer-Encoding`, duplicate `Content-Length`,
13//! obsolete line folding, bare CR or LF, whitespace before a colon, and
14//! control characters in field values each have their own [`HttpError`].
15//! Every buffer is bounded by [`Limits`], and each limit is checked before
16//! the bytes it governs are buffered.
17//!
18//! ## Tier 1: serve and fetch (blocking, `std`)
19//!
20//! ```
21//! # #[cfg(feature = "std")]
22//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
23//! use http_net::{blocking, Response};
24//!
25//! let server = blocking::Server::bind("127.0.0.1:0")?;
26//! let addr = server.local_addr();
27//! let stop = server.shutdown_handle();
28//! let t = std::thread::spawn(move || server.serve(|req, _body| Response::text(200, req.path())));
29//!
30//! let resp = blocking::get(&format!("http://{addr}/hi"))?;
31//! assert_eq!(resp.text(), Some("/hi"));
32//!
33//! stop.shutdown();
34//! t.join().unwrap()?;
35//! # Ok(())
36//! # }
37//! # #[cfg(not(feature = "std"))]
38//! # fn main() {}
39//! ```
40//!
41//! ## Tier 2: the sans-IO connection
42//!
43//! ```
44//! use http_net::{Response, ServerConn, ServerEvent};
45//!
46//! let mut conn = ServerConn::new();
47//! let mut out = Vec::new();
48//! let _ = conn.feed(b"GET /status HTTP/1.1\r\nHost: localhost\r\n\r\n");
49//! loop {
50//!     match conn.poll()? {
51//!         ServerEvent::Request(req) => assert_eq!(req.path(), "/status"),
52//!         ServerEvent::Body(_) => {}
53//!         ServerEvent::RequestEnd => conn.respond(&Response::text(200, "up"), &mut out)?,
54//!         _ => break, // NeedInput: read more from the socket
55//!     }
56//! }
57//! assert!(out.starts_with(b"HTTP/1.1 200 OK\r\n"));
58//! # Ok::<(), http_net::HttpError>(())
59//! ```
60//!
61//! [`ClientConn`] is the mirror image for the client side, and the
62//! `blocking` module shows how either is driven over `std::net`.
63//!
64//! ## Protocol upgrades
65//!
66//! A request with `Connection: upgrade` and an `Upgrade` field offering a
67//! protocol other than `h2c` reports [`RequestHead::is_upgrade`]. Answering
68//! it with [`Response::switching_protocols`] naming an offered protocol
69//! moves the connection to [`ServerEvent::Upgraded`], after which
70//! [`ServerConn::remaining_input`] holds any bytes of the new protocol that
71//! arrived with the request. This is the hand-off point for a WebSocket
72//! engine such as `ws-net`.
73//!
74//! ## Feature flags
75//!
76//! | Feature | Default | Description |
77//! |---|---|---|
78//! | `std` | yes | The `blocking` adapter over `std::net`. Without it the crate is `no_std` and needs only `alloc` (built in CI for `thumbv7em-none-eabihf`). |
79//!
80//! ## Credentials in `Debug` output
81//!
82//! Every `Debug` implementation in the crate prints the value of a
83//! credential field as `<redacted N bytes>`: `authorization`,
84//! `proxy-authorization`, `cookie`, `set-cookie`, and any field whose name
85//! contains `auth`, `cookie`, `token`, `secret`, `key`, `session`,
86//! `password`, `passwd`, `csrf`, `xsrf`, `signature`, or `credential`
87//! (`x-api-key`, `x-auth-token`). A request target's query prints as its
88//! length. So logging a request with `{:?}` does not leak a session cookie,
89//! a bearer token, or a signed URL. Other field values print as received,
90//! and the accessors return the real values.
91
92#![cfg_attr(not(feature = "std"), no_std)]
93#![cfg_attr(docsrs, feature(doc_cfg))]
94#![forbid(unsafe_code)]
95#![deny(missing_docs)]
96#![deny(unused_must_use)]
97#![deny(unused_results)]
98#![deny(clippy::unwrap_used)]
99#![deny(clippy::expect_used)]
100#![deny(clippy::todo)]
101#![deny(clippy::unimplemented)]
102#![deny(clippy::print_stdout)]
103#![deny(clippy::print_stderr)]
104#![deny(clippy::dbg_macro)]
105#![deny(clippy::unreachable)]
106#![cfg_attr(test, allow(clippy::unwrap_used, clippy::expect_used))]
107
108extern crate alloc;
109
110mod body;
111mod buffer;
112mod chunked;
113mod client;
114mod date;
115mod error;
116mod forward;
117mod head;
118mod limits;
119mod message;
120mod method;
121mod preface;
122mod raw;
123mod redact;
124mod scan;
125mod server;
126pub mod status;
127mod write;
128
129#[cfg(feature = "std")]
130#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
131pub mod blocking;
132
133pub use chunked::{ChunkStep, ChunkedDecoder};
134pub use client::{ClientConn, ClientEvent, ClientLeniency};
135pub use date::{DateCache, HttpDate};
136pub use error::HttpError;
137pub use forward::is_hop_by_hop;
138pub use head::{
139    BodyFraming, FieldSpan, HeaderValues, Headers, ListElements, RequestHead, ResponseHead, Span,
140    TargetForm, Version,
141};
142pub use limits::Limits;
143pub use message::{Request, Response};
144pub use method::{Method, MethodSet};
145pub use preface::is_h2_preface;
146pub use raw::{
147    HeadFacts, OwnedRequestHead, Parsed, ParsedRequest, ParsedResponse, RawHead,
148    parse_request_head, parse_response_head,
149};
150pub use server::{Progress, ServerConn, ServerEvent};
151pub use status::{StatusClass, reason_phrase, status_allows_body, status_class};
152pub use write::{BodyPlan, OutgoingBody, Output, ResponseHeadRef};
153
154/// The crate version, as published (`CARGO_PKG_VERSION`).
155///
156/// # Examples
157///
158/// ```
159/// assert_eq!(http_net::VERSION.split('.').count(), 3);
160/// ```
161pub const VERSION: &str = env!("CARGO_PKG_VERSION");
162
163// The README's and the API reference's examples run as doctests, so their
164// claims stay true.
165#[cfg(all(doctest, feature = "std"))]
166#[doc = include_str!("../README.md")]
167struct ReadmeDoctests;
168
169#[cfg(all(doctest, feature = "std"))]
170#[doc = include_str!("../docs/API.md")]
171struct ApiDoctests;
172
173#[cfg(all(doctest, feature = "std"))]
174#[doc = include_str!("../docs/MIGRATION.md")]
175struct MigrationDoctests;
176
177#[cfg(test)]
178mod tests {
179    #[test]
180    fn test_public_types_are_send_and_sync() {
181        fn assert<T: Send + Sync>() {}
182        assert::<crate::ServerConn>();
183        assert::<crate::ServerEvent<'_>>();
184        assert::<crate::ClientConn>();
185        assert::<crate::ClientLeniency>();
186        assert::<crate::ClientEvent<'_>>();
187        assert::<crate::Response>();
188        assert::<crate::Request>();
189        assert::<crate::RequestHead<'_>>();
190        assert::<crate::ResponseHead<'_>>();
191        assert::<crate::Headers<'_>>();
192        assert::<crate::HeaderValues<'_, '_>>();
193        assert::<crate::HttpError>();
194        assert::<crate::Limits>();
195        assert::<crate::Method>();
196        assert::<crate::MethodSet>();
197        assert::<crate::Version>();
198        assert::<crate::BodyFraming>();
199        assert::<crate::TargetForm>();
200        assert::<crate::StatusClass>();
201        #[cfg(feature = "std")]
202        {
203            assert::<crate::blocking::Server>();
204            assert::<crate::blocking::ServerConfig>();
205            assert::<crate::blocking::ShutdownHandle>();
206            assert::<crate::blocking::Connection>();
207            assert::<crate::blocking::Incoming<'_>>();
208            assert::<crate::blocking::Upgraded>();
209            assert::<crate::blocking::Client>();
210            assert::<crate::blocking::ClientResponse>();
211            assert::<crate::blocking::Error>();
212        }
213    }
214
215    #[test]
216    fn test_version_matches_the_manifest() {
217        assert_eq!(crate::VERSION, env!("CARGO_PKG_VERSION"));
218    }
219}