---
title: How to use http crate in Rust HTTP types
summary: The `http` crate provides the foundational, platform-agnostic types (like `Request`, `Response`, `HeaderMap`, and `Method`) used by the Rust ecosystem, but it does not include an HTTP client or server implementation itself.
lang: en
slug: how-to-use-http-crate-in-rust-http-types
published: 2026-04-17T16:43:47+00:00
updated: 2026-04-25T22:00:13+00:00
canonical: https://www.rustfaq.org/en/how-to-use-http-crate-in-rust-http-types/
---

# How to use http crate in Rust HTTP types



## When libraries need a common language

You are writing a logging middleware for a web server. You want to capture the incoming request, print the headers, and pass the request along to the handler. You reach for your server library, but you realize the request type is buried inside the library's namespace. You want to write a unit test that constructs a request without spinning up a server, but the library's types are hard to build manually. You also want to share a request object between a custom retry mechanism and the actual sender, but the types don't match.

The ecosystem solved this with the `http` crate. It defines the standard data structures for HTTP messages. Every major client and server library uses these types. `reqwest` uses them. `hyper` uses them. `actix-web` uses them. The `http` crate is the lingua franca. It gives you a shared vocabulary so you can pass data between libraries, write tests, and build middleware without locking yourself into one implementation.

## The standard envelope

The `http` crate provides `Request`, `Response`, `HeaderMap`, `Method`, `Uri`, `StatusCode`, and `Version`. It does not send or receive data. It does not open sockets. It does not resolve DNS. It does not implement HTTP/2 framing. It is purely the data model.

Think of the `http` crate as the standard envelope format. It defines what an envelope looks like, where the stamp goes, how to write the address, and what counts as valid ink. It does not deliver the mail. For delivery, you need a postal service. In Rust, `reqwest` is the postal service for clients. `hyper` is the postal service for servers. The `http` crate gives you the envelope; the other crates handle the logistics.

This separation is deliberate. It keeps the data structures stable and lightweight. It allows libraries to evolve their networking code without breaking the types you rely on. It also means you can construct and manipulate HTTP messages in memory without touching the network at all.

## Building a request

The standard way to create an `http::Request` is the builder pattern. You start with `Request::builder()`, chain methods to set the parts, and call `body()` to produce the final request. The builder returns a `Result` because you can forget required fields like the URI.

```rust
use http::{Request, Response, Method, StatusCode, HeaderMap, HeaderValue};

fn build_example() -> Result<(), http::Error> {
    // Builder accumulates state. body() consumes the builder and returns Result.
    let req = Request::builder()
        .method(Method::POST)
        .uri("https://api.example.com/upload")
        .header("Content-Type", "application/json")
        .body(vec![0u8; 1024])?;

    // Headers are stored in a HeaderMap. Access returns Option<&HeaderValue>.
    if let Some(ct) = req.headers().get("Content-Type") {
        println!("Type: {}", ct.to_str().unwrap());
    }

    // Response builder follows the same pattern.
    let res = Response::builder()
        .status(StatusCode::OK)
        .body("Done")?;

    Ok(())
}
```

The builder enforces correctness. If you omit `.uri()`, the `body()` call returns an error. The builder also validates headers as you add them. Invalid header values are rejected immediately. This catches mistakes at construction time rather than letting them propagate to the network layer.

The `body` method is generic. The type parameter `B` in `Request<B>` can be anything. In the example, the body is `Vec<u8>`. It could be a `String`, a custom struct, or a stream type. The `http` crate does not care what the body contains. It just holds it. This flexibility is why the crate works everywhere. Libraries can choose the body type that fits their needs.

Don't try to reuse the builder. The `body()` method consumes it. If you need multiple requests, create multiple builders.

## Inside the builder

The builder pattern in `http` is designed for safety and ergonomics. Methods like `method()`, `uri()`, and `header()` return the builder itself, allowing chaining. The `body()` method is the terminator. It takes ownership of the body value and the builder, validates the state, and returns `Result<Request<B>, Error>`.

The error type is `http::Error`. It covers missing fields, invalid headers, and invalid URIs. The error message tells you exactly what went wrong. If you forget the URI, you get a clear message. If you pass a header value with invalid bytes, you get a validation error.

The `Method` type is not just a string. It is a structured type with known variants like `GET`, `POST`, `PUT`, `DELETE`. You can also create custom methods, but the standard ones are preferred. The `StatusCode` type works similarly. It provides constants like `StatusCode::OK` and `StatusCode::NOT_FOUND`, plus methods to convert to and from integers.

The `Uri` type represents the request URI. It is immutable once constructed. You cannot modify a `Uri` in place. If you need to change the path or query, you build a new `Uri`. This immutability allows URIs to be shared safely across threads and borrowed without cloning the underlying string.

Treat the builder as a one-time factory. Build, validate, and move on.

## HeaderMap and the header rules

Headers are stored in a `HeaderMap`. This is not a `HashMap<String, String>`. It is a specialized structure optimized for HTTP. Keys are `HeaderName` types. Values are `HeaderValue` types. The map is case-insensitive for keys. `Content-Type`, `content-type`, and `CONTENT-TYPE` all refer to the same header.

```rust
use http::{HeaderMap, HeaderValue, header::CONTENT_TYPE};

fn header_rules() -> Result<(), http::Error> {
    let mut headers = HeaderMap::new();

    // insert replaces existing values. Returns the old value if present.
    headers.insert(CONTENT_TYPE, HeaderValue::from_static("text/plain"));

    // append adds a second value. Some headers allow multiple values.
    headers.append("Accept", HeaderValue::from_static("text/html"));
    headers.append("Accept", HeaderValue::from_static("application/json"));

    // get returns the first value. get_all returns an iterator over all values.
    let accepts = headers.get_all("Accept");
    for accept in accepts {
        println!("Accept: {}", accept.to_str().unwrap());
    }

    Ok(())
}
```

The distinction between `insert` and `append` matters. `insert` replaces any existing value for that key. `append` adds a new value, allowing multiple entries for the same header. This is essential for headers like `Set-Cookie`, `Accept`, and `Vary`, where multiple values are valid and meaningful.

`HeaderValue` validates content. Not every byte sequence is a valid header value. The HTTP spec restricts headers to ASCII characters with some exceptions. `HeaderValue::from_bytes` checks these rules and returns an error if the value is invalid. This prevents malformed headers from leaking into your application.

Convention aside: use the constants from `http::header` like `CONTENT_TYPE` instead of string literals. The constants are `HeaderName` types, so they are validated at compile time and avoid typos. Writing `"Content-Type"` works, but `CONTENT_TYPE` is safer and faster.

Iteration order in `HeaderMap` is not guaranteed. Do not rely on the order of headers. If order matters, collect into a vector and sort.

## The generic body

The body type parameter `B` in `Request<B>` and `Response<B>` is the source of most confusion for newcomers. The `http` crate does not define what a body is. It leaves that to the consumer. This design allows the crate to support bytes, strings, streams, and future types without imposing a single model.

In a simple client, the body might be `Vec<u8>` for binary data or `String` for text. In a streaming server, the body might be a type that implements a stream trait, yielding chunks of data over time. The `http` crate does not care. It just holds the body.

This generic approach means you cannot mix body types freely. A `Request<Vec<u8>>` is not the same as a `Request<String>`. If a function expects `Request<Vec<u8>>`, you cannot pass `Request<String>` even if the string can be converted to bytes. You must convert the body type explicitly.

The compiler will reject mismatched types with `E0308` (mismatched types). This error often appears when you try to pass a request with one body type to a function expecting another. The fix is to convert the body before constructing the request, or to use a generic function that accepts any body type.

Pick the body type that matches your data flow. If you have bytes, use `Vec<u8>`. If you have a string, use `String`. If you are streaming, use a stream type. Do not fight the generic.

## Extensions: the shared backpack

`Request` and `Response` have an `Extensions` map. This is a type-safe key-value store that holds arbitrary data. Libraries use extensions to attach metadata to requests and responses without breaking the API.

For example, a server library might store the original connection information in extensions. A client library might store the retry count. Middleware might store authentication results. Extensions allow the ecosystem to share data without adding fields to the core types.

```rust
use http::Request;

struct AuthInfo {
    user_id: u64,
    roles: Vec<String>,
}

fn use_extensions() {
    let mut req = Request::builder()
        .uri("https://api.example.com/secure")
        .body(())
        .unwrap();

    // Insert custom data into extensions.
    req.extensions_mut().insert(AuthInfo {
        user_id: 42,
        roles: vec!["admin".to_string()],
    });

    // Retrieve data from extensions.
    if let Some(auth) = req.extensions().get::<AuthInfo>() {
        println!("User: {}", auth.user_id);
    }
}
```

Extensions are stored as `dyn Any`. You must specify the type when inserting and retrieving. This provides type safety. If you try to get a type that was not inserted, you get `None`.

Treat extensions as the ecosystem's shared backpack. Check for existing keys before adding your own. Use well-known types when possible to avoid collisions.

## Real-world interop

The power of the `http` crate shines when you integrate with libraries. `reqwest` and `hyper` both use `http` types internally. You can convert between library types and `http` types to inspect or modify messages.

```rust
use http::Request;
use reqwest::Client;

async fn inspect_with_reqwest() -> Result<(), Box<dyn std::error::Error>> {
    let client = Client::new();

    // Build a reqwest request.
    let reqwest_req = client.post("https://api.example.com")
        .json(&serde_json::json!({"key": "value"}))
        .build()?;

    // Convert to http::Request to inspect.
    // try_into consumes the reqwest request and returns http::Request<Vec<u8>>.
    let http_req: Request<Vec<u8>> = reqwest_req.try_into()?;

    println!("Method: {}", http_req.method());
    println!("URI: {}", http_req.uri());

    // You cannot send http_req directly.
    // reqwest owns the transport logic. http::Request is just data.
    Ok(())
}
```

The `try_into()` call converts the `reqwest::Request` into an `http::Request<Vec<u8>>`. This consumes the `reqwest` request. You cannot send the `http` request directly. You need the `reqwest` client to handle the network. This pattern is useful for logging, validation, or middleware that needs to peek at the raw message.

Some libraries support converting back. `reqwest` allows you to build a request from an `http::Request` using `Client::from_request()`. This lets you construct a standard request and send it with `reqwest`.

The `http` crate gives you the vocabulary. You still need a mouth to speak.

## Pitfalls and compiler errors

The `http` crate is simple, but it has traps.

The crate does not send data. You cannot call `req.send()`. If you try, the compiler rejects you with `E0599` (no function named `send` found). You must use a client or server library to transmit the request.

The `Uri` type is immutable. You cannot modify a URI after construction. If you need to change the path, build a new `Uri`. Attempting to mutate a URI results in a borrow checker error.

`HeaderValue` validation is strict. `HeaderValue::from_bytes` fails on invalid bytes. If you construct a header value from untrusted input, handle the error. Ignoring the error with `unwrap()` can crash your application.

Body types are rigid. `Request<Vec<u8>>` is not `Request<String>`. The compiler rejects mismatches with `E0308`. Convert body types explicitly before passing them to functions.

`HeaderMap` iteration order is undefined. Do not rely on the order of headers. If you need deterministic order, collect and sort.

Extensions require type matching. `get::<T>()` returns `None` if the type does not match. Check for `None` before using the value.

Don't fight the compiler here. The errors are protecting you from malformed HTTP and type mismatches.

## Decision matrix

Use `http` when you need a standard data structure to pass between libraries or store HTTP data in memory. Use `reqwest` when you need to send requests over the network with a high-level API. Use `hyper` when you are building a server or need low-level control over the connection. Use `url` when you need to parse query strings or manipulate URL components, since `http::Uri` is minimal and immutable. Use `httparse` when you need to parse raw bytes from a socket without the overhead of full types.

The `http` crate is the foundation. Build on it, but reach for the right tool for the job.

## Where to go next

- [Using reqwest for HTTP clients](/en/using-reqwest-for-http-clients/)
- [Building servers with hyper](/en/building-servers-with-hyper/)
- [Parsing URLs with the url crate](/en/parsing-urls-with-the-url-crate/)