12 releases (5 breaking)
Uses new Rust 2024
| 0.7.0 | Feb 21, 2026 |
|---|---|
| 0.5.1 | Jan 29, 2026 |
| 0.4.0 | Jan 24, 2026 |
| 0.3.0 | Jan 24, 2026 |
| 0.1.1 | Dec 28, 2025 |
#885 in Network programming
Used in 2 crates
65KB
831 lines
http_wire
A Rust library to serialize and parse HTTP/1.x requests and responses to/from their wire format (raw bytes).
Note: This crate only supports HTTP/1.0 and HTTP/1.1. HTTP/2 is not supported due to its binary framing, HPACK header compression, and multiplexed nature.
Usage
Add to your Cargo.toml:
[dependencies]
http_wire = "0.7"
Encoding
Use the WireEncode trait to serialize http::Request and http::Response values to their wire-format bytes.
Encoding is performed via direct serialization — no async runtime, no Tokio, no HTTP pipeline. The request/response line, headers, and body are written sequentially into a pre-allocated buffer. The body is collected using futures::executor::block_on, which requires no runtime for in-memory body types such as Full<Bytes> and Empty<Bytes>.
Encoding a request
use http_wire::WireEncode;
use http::Request;
use http_body_util::Empty;
use bytes::Bytes;
let request = Request::builder()
.method("GET")
.uri("/api/users")
.header("Host", "example.com")
.header("Accept", "application/json")
.body(Empty::<Bytes>::new())
.unwrap();
let bytes = request.encode().unwrap();
// b"GET /api/users HTTP/1.1\r\nhost: example.com\r\naccept: application/json\r\n\r\n"
Encoding a request with body
use http_wire::WireEncode;
use http::Request;
use http_body_util::Full;
use bytes::Bytes;
let body = r#"{"name":"Alice"}"#;
let request = Request::builder()
.method("POST")
.uri("/api/users")
.header("Host", "example.com")
.header("Content-Type", "application/json")
.header("Content-Length", body.len().to_string())
.body(Full::new(Bytes::from(body)))
.unwrap();
let bytes = request.encode().unwrap();
Encoding a response
use http_wire::WireEncode;
use http::Response;
use http_body_util::Full;
use bytes::Bytes;
let response = Response::builder()
.status(200)
.header("Content-Type", "application/json")
.body(Full::new(Bytes::from(r#"{"status":"ok"}"#)))
.unwrap();
let bytes = response.encode().unwrap();
// b"HTTP/1.1 200 OK\r\ncontent-type: application/json\r\n\r\n{\"status\":\"ok\"}"
Decoding
Use the WireDecode trait with FullRequest or FullResponse to parse raw bytes and determine complete message boundaries.
Decoding a request
use http_wire::WireDecode;
use http_wire::request::FullRequest;
let raw = b"GET /api/users HTTP/1.1\r\nHost: example.com\r\n\r\n";
let mut headers = [httparse::EMPTY_HEADER; 16];
let (request, total_len) = FullRequest::decode(raw, &mut headers).unwrap();
assert_eq!(request.head.method, Some("GET"));
assert_eq!(request.head.path, Some("/api/users"));
assert_eq!(total_len, raw.len());
Decoding a request (optimised — uninitialized headers)
For performance-critical code, FullRequest supports skipping the header buffer initialisation:
use http_wire::WireDecode;
use http_wire::request::FullRequest;
use std::mem::MaybeUninit;
let raw = b"GET /api/users HTTP/1.1\r\nHost: example.com\r\n\r\n";
let mut headers = [const { MaybeUninit::uninit() }; 16];
let (request, total_len) = FullRequest::decode_uninit(raw, &mut headers).unwrap();
assert_eq!(request.head.method, Some("GET"));
decode_uninitis only available forFullRequest.FullResponsedoes not support it because the underlyinghttparse::Responselacksparse_with_uninit_headers.
Decoding a response
use http_wire::WireDecode;
use http_wire::response::FullResponse;
let raw = b"HTTP/1.1 200 OK\r\nContent-Length: 5\r\n\r\nhello";
let mut headers = [httparse::EMPTY_HEADER; 16];
let (response, total_len) = FullResponse::decode(raw, &mut headers).unwrap();
assert_eq!(response.head.code, Some(200));
assert_eq!(response.body, b"hello");
assert_eq!(total_len, raw.len());
Handling incomplete messages
Both decoders return structured errors rather than panicking on partial data:
use http_wire::{WireDecode, WireError};
use http_wire::request::FullRequest;
// Incomplete headers
let raw = b"GET /api/users HTTP/1.1\r\nHost: example.com\r\n";
let mut headers = [httparse::EMPTY_HEADER; 16];
assert!(matches!(
FullRequest::decode(raw, &mut headers),
Err(WireError::PartialHead)
));
// Headers complete but body truncated
let raw = b"POST / HTTP/1.1\r\nContent-Length: 100\r\n\r\nshort";
let mut headers = [httparse::EMPTY_HEADER; 16];
assert!(matches!(
FullRequest::decode(raw, &mut headers),
Err(WireError::IncompleteBody(_))
));
Stream parsing
Decoders return the exact byte length of the complete message, making it straightforward to split a streaming buffer:
use http_wire::WireDecode;
use http_wire::request::FullRequest;
fn split_first_request<'h, 'b>(
buf: &'b [u8],
headers: &'h mut [httparse::Header<'b>],
) -> Option<(&'b [u8], &'b [u8])> {
let (_, len) = FullRequest::decode(buf, headers).ok()?;
Some(buf.split_at(len))
}
Transfer encodings
Both Content-Length and Transfer-Encoding: chunked are fully supported, including:
- Multiple chunks
- Chunk extensions (ignored)
- Trailer headers
- Case-insensitive
chunkeddetection - Multi-value
Transfer-Encodingheaders (e.g.gzip, chunked)
Status codes that never carry a body (1xx, 204, 304) are handled automatically by FullResponse.
Error handling
use http_wire::{WireEncode, WireError};
fn serialize() -> Result<(), WireError> {
let request = http::Request::builder()
.uri("/")
.body(http_body_util::Empty::<bytes::Bytes>::new())
.unwrap();
let bytes = request.encode()?;
println!("Serialized {} bytes", bytes.len());
Ok(())
}
WireError variants:
| Variant | When |
|---|---|
Connection |
Body collection failed during encoding |
UnsupportedVersion |
HTTP version is not 1.0 or 1.1 |
PartialHead |
Headers section is incomplete |
IncompleteBody(n) |
Body is n bytes shorter than Content-Length |
InvalidChunkedBody |
Chunked encoding is malformed or incomplete |
HttparseError |
Header parsing failed (invalid characters, etc.) |
Features
- Direct serialization — no Tokio runtime required for encoding
- Zero-copy decoding — parsed fields borrow directly from the input buffer
- Full
Transfer-Encoding: chunkedsupport (encode and decode) - Case-insensitive header parsing
- HTTP/1.0 and HTTP/1.1 support
- Uninitialized header buffer optimisation for request decoding
License
MIT OR Apache-2.0
Dependencies
~1.4–2.2MB
~41K SLoC