#wire-format #serialization #byte #format-byte

http_wire

Serialize HTTP requests and responses to wire format bytes

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

MIT/Apache

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_uninit is only available for FullRequest. FullResponse does not support it because the underlying httparse::Response lacks parse_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 chunked detection
  • Multi-value Transfer-Encoding headers (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: chunked support (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