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 which make single request/response serialization impractical.
Usage
Add to your Cargo.toml:
[dependencies]
http_wire = "0.2"
Encoding (Serialization)
Use the WireEncode trait to serialize HTTP requests and responses to their wire format bytes.
Encoding Requests
use http_wire::WireEncode;
use http::Request;
use http_body_util::Empty;
use bytes::Bytes;
#[tokio::main]
async fn main() {
let request = Request::builder()
.method("GET")
.uri("/api/users")
.header("Host", "example.com")
.body(Empty::<Bytes>::new())
.unwrap();
let bytes = request.encode().await.unwrap();
}
Encoding Requests with Body
use http_wire::WireEncode;
use http::Request;
use http_body_util::Full;
use bytes::Bytes;
#[tokio::main]
async fn main() {
let body = r#"{"name":"John"}"#;
let request = Request::builder()
.method("POST")
.uri("/api/users")
.header("Host", "example.com")
.header("Content-Type", "application/json")
.body(Full::new(Bytes::from(body)))
.unwrap();
let bytes = request.encode().await.unwrap();
}
Encoding Responses
use http_wire::WireEncode;
use http::Response;
use http_body_util::Full;
use bytes::Bytes;
#[tokio::main]
async fn main() {
let response = Response::builder()
.status(200)
.header("Content-Type", "text/plain")
.body(Full::new(Bytes::from("Hello World")))
.unwrap();
let bytes = response.encode().await.unwrap();
}
Decoding (Parsing)
Use the WireDecode trait to parse raw HTTP bytes and determine message boundaries.
Parsing Request Length
Use RequestLength to determine the total length of an HTTP request in a byte buffer:
use http_wire::WireDecode;
use http_wire::request::RequestLength;
fn main() {
let raw = b"GET /api/users HTTP/1.1\r\nHost: example.com\r\n\r\n";
if let Some(length) = RequestLength::decode(raw) {
println!("Request is {} bytes", length);
let request_bytes = &raw[..length];
} else {
println!("Incomplete request");
}
}
Parsing Response Status and Length
Use ResponseStatusCode to get both the HTTP status code and total length of a response:
use http_wire::WireDecode;
use http_wire::response::ResponseStatusCode;
use http::StatusCode;
fn main() {
let raw = b"HTTP/1.1 200 OK\r\nContent-Length: 5\r\n\r\nhello";
if let Some((status, length)) = ResponseStatusCode::decode(raw) {
println!("Status: {}, Length: {} bytes", status, length);
assert_eq!(status, StatusCode::OK);
let response_bytes = &raw[..length];
} else {
println!("Incomplete response");
}
}
Handling Chunked Transfer Encoding
Both decoders fully support chunked transfer encoding:
use http_wire::WireDecode;
use http_wire::response::ResponseStatusCode;
fn main() {
let raw = b"HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n5\r\nhello\r\n0\r\n\r\n";
if let Some((status, length)) = ResponseStatusCode::decode(raw) {
println!("Chunked response complete: {} bytes", length);
}
}
Stream Parsing Example
Use decoders to handle streaming data:
use http_wire::WireDecode;
use http_wire::request::RequestLength;
fn parse_stream(buffer: &[u8]) -> Option<(&[u8], &[u8])> {
if let Some(length) = RequestLength::decode(buffer) {
let (request, remaining) = buffer.split_at(length);
Some((request, remaining))
} else {
None
}
}
Error Handling
use http_wire::{WireEncode, WireError};
#[tokio::main]
async fn main() -> Result<(), WireError> {
let request = http::Request::builder()
.uri("/")
.body(http_body_util::Empty::<bytes::Bytes>::new())
.unwrap();
let bytes = request.encode().await?;
println!("Serialized {} bytes", bytes.len());
Ok(())
}
WireError has three variants:
Connection - HTTP connection error (handshake or send failed)
Sync - Internal synchronization error
UnsupportedVersion - HTTP version not supported (only HTTP/1.0 and HTTP/1.1 are supported)
HTTP/2 Rejection
Attempting to encode HTTP/2 requests or responses will return an error:
use http_wire::{WireEncode, WireError};
#[tokio::main]
async fn main() {
let request = http::Request::builder()
.method("GET")
.uri("/")
.version(http::Version::HTTP_2)
.body(http_body_util::Empty::<bytes::Bytes>::new())
.unwrap();
let result = request.encode().await;
assert!(matches!(result, Err(WireError::UnsupportedVersion)));
}
Features
- Full support for chunked transfer encoding
- Handles requests and responses with or without bodies
- Proper handling of special status codes (1xx, 204, 304) that never have bodies
- Case-insensitive header parsing
- Zero-copy parsing for determining message boundaries
- HTTP/1.0 and HTTP/1.1 support
License
MIT OR Apache-2.0