What is the relationship between hyper and http crates, and when would you use one over the other?

The http crate provides foundational HTTP types while hyper is a full HTTP client and server implementation built on those types. Understanding their relationship clarifies when to use each and how they fit together in the Rust HTTP ecosystem.

The http Crate: Foundation Types

The http crate provides core HTTP types with no I/O functionality:

use http::{Request, Response, Method, StatusCode, HeaderMap, Uri};
 
fn http_types() {
    // Create a request
    let mut request = Request::builder()
        .method(Method::GET)
        .uri("https://example.com/api")
        .header("Content-Type", "application/json")
        .body(())
        .unwrap();
    
    // Access request parts
    println!("Method: {}", request.method());
    println!("URI: {}", request.uri());
    println!("Headers: {:?}", request.headers());
    
    // Create a response
    let response = Response::builder()
        .status(StatusCode::OK)
        .header("Content-Type", "text/html")
        .body("<html>...</html>".to_string())
        .unwrap();
    
    println!("Status: {}", response.status());
}

These types represent HTTP messages without any network functionality.

What http Provides

use http::{
    Request, Response,
    Method, StatusCode, Version,
    HeaderMap, HeaderName, HeaderValue,
    Uri, Extensions,
};
 
fn http_components() {
    // Method enum
    let method = Method::GET;
    assert_eq!(method, Method::from_bytes(b"GET").unwrap());
    
    // Status codes
    let status = StatusCode::NOT_FOUND;
    assert_eq!(status.as_u16(), 404);
    assert_eq!(status.canonical_reason(), Some("Not Found"));
    
    // Header map with typed names
    let mut headers = HeaderMap::new();
    headers.insert(HeaderName::from_static("content-type"), 
                   HeaderValue::from_static("application/json"));
    
    // URI parsing
    let uri: Uri = "https://user:pass@example.com:8080/path?query#fragment"
        .parse().unwrap();
    assert_eq!(uri.scheme_str(), Some("https"));
    assert_eq!(uri.host(), Some("example.com"));
    
    // Extensions for arbitrary data
    let mut extensions = Extensions::new();
    extensions.insert(42i32);
    assert_eq!(extensions.get::<i32>(), Some(&42));
}

The http crate focuses on type-safe representations of HTTP concepts.

What http Does Not Provide

// The http crate does NOT provide:
// - Network I/O
// - Connection management
// - HTTP/1.x or HTTP/2 protocol implementation
// - Client or server functionality
// - TLS support
// - Request execution
 
// This won't compile - http can't send requests:
// let response = http_send(request);  // No such function
 
// http is purely about representing HTTP messages

The hyper Crate: HTTP Implementation

Hyper builds on http types and provides actual HTTP communication:

use hyper::{body::Bytes, client::HttpConnector, Body, Client, Request};
use http::Method;
 
// hyper uses http::Request and http::Response
async fn hyper_client() -> Result<(), Box<dyn std::error::Error>> {
    let client = Client::new();
    
    // Create request using http types
    let request = Request::builder()
        .method(Method::GET)
        .uri("http://httpbin.org/get")
        .body(Body::empty())?;
    
    // hyper executes the request
    let response = client.request(request).await?;
    
    println!("Status: {}", response.status());
    
    // hyper provides body handling
    let body_bytes = hyper::body::to_bytes(response.into_body()).await?;
    println!("Body: {}", String::from_utf8_lossy(&body_bytes));
    
    Ok(())
}

Hyper uses http::Request and http::Response but adds network functionality.

Hyper's Additional Features

use hyper::{
    server::Server,
    service::{make_service_fn, service_fn},
    Body, Response, StatusCode,
};
use std::convert::Infallible;
 
// Hyper provides server functionality
async fn hyper_server() {
    async fn handle(_: hyper::Request<Body>) -> Result<Response<Body>, Infallible> {
        Ok(Response::new(Body::from("Hello World")))
    }
    
    let make_svc = make_service_fn(|_conn| async {
        Ok::<_, Infallible>(service_fn(handle))
    });
    
    let addr = ([127, 0, 0, 1], 3000).into();
    let server = Server::bind(&addr).serve(make_svc);
    
    // server.await would run the server
}

Hyper provides client and server implementations, connection management, and protocol handling.

The Type Relationship

use http::{Request, Response, Method, StatusCode};
use hyper::Body;
 
fn type_relationship() {
    // http::Request with a body type
    let http_request: Request<()> = Request::builder()
        .method(Method::GET)
        .uri("/path")
        .body(())
        .unwrap();
    
    // hyper::Request is http::Request<hyper::Body>
    let hyper_request: Request<Body> = Request::builder()
        .method(Method::GET)
        .uri("/path")
        .body(Body::empty())
        .unwrap();
    
    // They share the same underlying type
    // Request<B> where B is the body type
    
    // Similarly for responses
    let http_response: Response<String> = Response::new("body".to_string());
    let hyper_response: Response<Body> = Response::new(Body::from("body"));
}

Hyper uses the generic http::Request<B> and http::Response<B> with its own Body type.

When to Use http Directly

use http::{Request, Response, StatusCode, HeaderMap};
 
// Use http when you only need to represent or manipulate HTTP types
// without actually sending/receiving over a network
 
// 1. Library code that works with HTTP types generically
fn validate_request<B>(request: &Request<B>) -> Result<(), String> {
    if request.method() != http::Method::GET && request.method() != http::Method::POST {
        return Err("Only GET and POST allowed".to_string());
    }
    
    if let Some(content_type) = request.headers().get("content-type") {
        if !content_type.to_str().unwrap_or("").starts_with("application/json") {
            return Err("Content-Type must be JSON".to_string());
        }
    }
    
    Ok(())
}
 
// 2. Building middleware that transforms requests/responses
fn add_header<B>(mut request: Request<B>) -> Request<B> {
    request.headers_mut().insert(
        http::header::USER_AGENT,
        http::HeaderValue::from_static("my-app/1.0"),
    );
    request
}
 
// 3. Testing HTTP handling logic without network I/O
#[cfg(test)]
mod tests {
    use super::*;
    
    #[test]
    fn test_validation() {
        let request = Request::builder()
            .method("DELETE")
            .uri("/test")
            .body(())
            .unwrap();
        
        assert!(validate_request(&request).is_err());
    }
}

When to Use hyper

use hyper::{Body, Client, Request, Server, service::{service_fn, make_service_fn}};
 
// Use hyper when you need actual HTTP communication
 
// 1. HTTP client
async fn fetch_url(url: &str) -> Result<String, Box<dyn std::error::Error>> {
    let client = Client::new();
    let response = client.get(url.parse()?).await?;
    let body = hyper::body::to_bytes(response.into_body()).await?;
    Ok(String::from_utf8_lossy(&body).to_string())
}
 
// 2. HTTP server
async fn run_server() {
    let service = service_fn(|_req| async {
        Ok::<_, hyper::Error>(hyper::Response::new(Body::from("OK")))
    });
    // Server::bind(&addr).serve(make_service).await
}
 
// 3. Connection-level control
async fn connection_control() {
    // Hyper provides access to connection details
    let client: Client<hyper::client::HttpConnector> = Client::new();
    
    // Configure timeouts, pooling, etc.
    // let client = Client::builder()
    //     .pool_max_idle_per_host(10)
    //     .build::<_, Body>(connector);
}

The Crate Dependency Graph

Application Code
       |
       v
     hyper  (HTTP implementation, I/O)
       |
       v
     http   (HTTP types, no I/O)
       |
       v
    std lib (collections, etc.)

Hyper depends on http, but http has no dependencies on hyper or I/O.

Using Both Together

use http::{Request, Response, Method, StatusCode, HeaderMap};
use hyper::{Body, Client};
 
async fn combined_usage() -> Result<(), Box<dyn std::error::Error>> {
    // Build request with http types
    let request = Request::builder()
        .method(Method::POST)
        .uri("https://api.example.com/data")
        .header("Authorization", "Bearer token")
        .header("Content-Type", "application/json")
        .body(Body::from(r#"{"key": "value"}"#))?;
    
    // Validate with http-focused code
    validate_request_headers(request.headers())?;
    
    // Execute with hyper
    let client = Client::new();
    let response = client.request(request).await?;
    
    // Process response with http types
    let status = response.status();
    let headers = response.headers().clone();
    
    if status.is_success() {
        let body = hyper::body::to_bytes(response.into_body()).await?;
        process_response(status, headers, &body)?;
    }
    
    Ok(())
}
 
fn validate_request_headers(headers: &HeaderMap) -> Result<(), String> {
    if !headers.contains_key("authorization") {
        return Err("Missing authorization".to_string());
    }
    Ok(())
}
 
fn process_response(status: StatusCode, headers: HeaderMap, body: &[u8]) -> Result<(), String> {
    // Pure http type processing
    println!("Status: {}", status);
    Ok(())
}

Building Higher-Level Libraries

The http crate enables writing code that works with any HTTP implementation:

use http::{Request, Response, StatusCode};
use async_trait::async_trait;
 
// Define a trait using http types
#[async_trait]
trait HttpClient: Clone + Send + Sync + 'static {
    async fn execute<B: Into<reqwest::Body> + Send>(
        &self,
        request: Request<B>,
    ) -> Result<Response<reqwest::Body>, HttpError>;
}
 
// Implementation for reqwest
#[derive(Clone)]
struct ReqwestClient {
    client: reqwest::Client,
}
 
#[async_trait]
impl HttpClient for ReqwestClient {
    async fn execute<B: Into<reqwest::Body> + Send>(
        &self,
        request: Request<B>,
    ) -> Result<Response<reqwest::Body>, HttpError> {
        // Convert http::Request to reqwest::Request
        // Execute
        // Convert reqwest::Response to http::Response
        unimplemented!()
    }
}
 
// Implementation for hyper
#[derive(Clone)]
struct HyperClient {
    client: hyper::Client<hyper::client::HttpConnector>,
}
 
#[async_trait]
impl HttpClient for HyperClient {
    async fn execute<B: Into<hyper::Body> + Send>(
        &self,
        request: Request<B>,
    ) -> Result<Response<hyper::Body>, HttpError> {
        // Use hyper directly
        unimplemented!()
    }
}
 
#[derive(Debug)]
struct HttpError;

Common Patterns

Pattern 1: Middleware

use http::{Request, Response};
 
// Middleware works with http types, not hyper specifically
trait Middleware<B> {
    fn handle(&self, request: Request<B>) -> Result<Request<B>, Response<B>>;
}
 
struct AuthMiddleware;
 
impl<B> Middleware<B> for AuthMiddleware {
    fn handle(&self, request: Request<B>) -> Result<Request<B>, Response<B>> {
        if request.headers().contains_key("authorization") {
            Ok(request)
        } else {
            Err(Response::builder()
                .status(http::StatusCode::UNAUTHORIZED)
                .body(/* ... */)
                .unwrap())
        }
    }
}

Pattern 2: Routing

use http::{Method, Request};
 
// Routing logic uses http types
fn route<B>(request: &Request<B>) -> &'static str {
    match (request.method(), request.uri().path()) {
        (&Method::GET, "/") => "home",
        (&Method::GET, "/users") => "list_users",
        (&Method::POST, "/users") => "create_user",
        (&Method::GET, path) if path.starts_with("/users/") => "get_user",
        _ => "not_found",
    }
}

Pattern 3: Testing

use http::{Request, Response, StatusCode};
 
// Test handlers without hyper
#[test]
fn test_handler() {
    let request = Request::builder()
        .method(Method::GET)
        .uri("/users")
        .body(())
        .unwrap();
    
    let response = handle_request(request);
    
    assert_eq!(response.status(), StatusCode::OK);
}
 
fn handle_request<B>(request: Request<B>) -> Response<String> {
    Response::new("response".to_string())
}

Frameworks Built on These Crates

Many frameworks layer on top of hyper and http:

// Tower services use http types
// use tower::Service;
 
// Axum handlers work with http types
// use axum::extract::Request;
 
// Warp filters work with http types  
// use warp::Filter;
 
// All use http::Request and http::Response as the common interface

Choosing Between Them

Scenario Use http Use hyper
Manipulate HTTP types āœ“
Validate requests/responses āœ“
Write middleware āœ“
Test HTTP handling āœ“
Make HTTP requests āœ“
Serve HTTP requests āœ“
Manage connections āœ“
Configure timeouts āœ“

Synthesis

The http crate provides the vocabulary for HTTP—types like Request, Response, Method, StatusCode, and HeaderMap. It has no I/O and can be used in any context where you need to represent HTTP concepts.

The hyper crate provides the implementation—actually sending requests, accepting connections, parsing the HTTP protocol on the wire. It uses http types as its interface.

Use http directly when:

  • Writing libraries that need HTTP types without I/O
  • Building middleware or request/response transformers
  • Writing tests for HTTP handling logic
  • Creating generic HTTP-related traits or interfaces

Use hyper when:

  • You need to make HTTP requests over the network
  • You need to serve HTTP requests
  • You need connection management, timeouts, or protocol configuration
  • You're building a higher-level HTTP framework

The separation allows http to be a lightweight, widely-used foundation while hyper provides the heavy lifting for actual network communication. This is why most HTTP-related Rust code uses types from http even when hyper is doing the actual work.