http_wire

Crates.iohttp_wire
lib.rshttp_wire
version0.4.0
created_at2025-12-28 09:07:53.195739+00
updated_at2026-01-24 12:05:40.965454+00
descriptionSerialize HTTP/1.1 requests and responses to wire format bytes
homepage
repositoryhttps://github.com/awgn/http_wire
max_upload_size
id2008465
size66,781
Nicola Bonelli (awgn)

documentation

README

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)

The library provides two ways to encode HTTP messages:

  1. Sync encoding with WireEncode - works in synchronous code without requiring an async runtime (Recommended for most use cases)
  2. Async encoding with WireEncodeAsync - requires an async runtime (Tokio)

Synchronous Encoding (Recommended for most use cases)

Use WireEncode to encode HTTP messages in synchronous contexts without needing to set up an async runtime:

use http_wire::WireEncode;
use http::Request;
use http_body_util::Full;
use bytes::Bytes;

fn main() {
    let request = Request::builder()
        .method("POST")
        .uri("/api/users")
        .header("Host", "example.com")
        .header("Content-Type", "application/json")
        .body(Full::new(Bytes::from(r#"{"name":"John"}"#)))
        .unwrap();

    let bytes = request.encode().unwrap();
    // bytes contains the full HTTP/1.1 request with body
}

This works for both requests and responses:

use http_wire::WireEncode;
use http::Response;
use http_body_util::Full;
use bytes::Bytes;

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().unwrap();
}

Note: WireEncode creates a minimal single-threaded Tokio runtime internally and blocks until encoding completes. This provides the convenience of synchronous code while still leveraging Hyper's correct HTTP serialization.

Async Encoding

For code that's already running in an async context, you can use WireEncodeAsync:

Async Encoding Requests

use http_wire::WireEncodeAsync;
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_async().await.unwrap();
    // bytes contains: "GET /api/users HTTP/1.1\r\nhost: example.com\r\n\r\n"
}

Encoding Requests with Body

use http_wire::WireEncodeAsync;
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_async().await.unwrap();
    // bytes contains the full HTTP/1.1 request with body
}

Encoding Responses

use http_wire::WireEncodeAsync;
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_async().await.unwrap();
    // bytes contains: "HTTP/1.1 200 OK\r\ncontent-type: text/plain\r\n..."
}

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, including its body, 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);
        // Use the length to slice the buffer if there's more data
        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);
        // Use the length to extract just the response
        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])> {
    // Try to parse a complete request
    if let Some(length) = RequestLength::decode(buffer) {
        // Split buffer into complete request and remaining data
        let (request, remaining) = buffer.split_at(length);
        Some((request, remaining))
    } else {
        // Need more data
        None
    }
}

Error Handling

Both sync and async encoding use the same error types:

// Sync version (recommended)
use http_wire::{WireEncode, WireError};

fn main() -> 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(())
}
// Async version
use http_wire::{WireEncodeAsync, 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_async().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)

Features

  • Full support for chunked transfer encoding
  • Handles requests and responses with or without 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

Commit count: 18

cargo fmt