Hyper

Low-level high-performance HTTP library for Rust. Complete HTTP/1.1, HTTP/2 support, async I/O optimization. Designed as foundation for other libraries and frameworks, adopted by many libraries like reqwest and warp. For developers prioritizing customizability and performance.

HTTP ClientRustAsynchronousPerformanceHTTP/2Secure

GitHub Overview

hyperium/hyper

An HTTP library for Rust

Stars15,387
Watchers174
Forks1,659
Created:August 30, 2014
Language:Rust
License:MIT License

Topics

httphyperrust

Star History

hyperium/hyper Star History
Data as of: 7/18/2025, 01:38 AM

Library

Hyper

Overview

Hyper is "a fast and correct HTTP library for Rust" developed as a low-level HTTP client and server library. Focusing on speed, correctness, and asynchronous design, it supports both HTTP/1 and HTTP/2. Designed not as a full-featured framework but as a building block for HTTP applications, it serves as the foundation for higher-level libraries (reqwest, axum, warp, etc.). Leveraging Rust's type system to ensure memory and thread safety, it achieves the highest performance with minimal overhead.

Details

Hyper 2025 edition has established itself as the de facto standard HTTP library in the Rust ecosystem. With a design centered on asynchronous I/O (async/await), it can efficiently handle high concurrent connections. Adopting a modular feature system, it selectively incorporates only necessary components to optimize compile time and binary size. Architecture based on the Service trait enables middleware patterns and customizable HTTP processing pipelines, supporting enterprise-level robust HTTP application construction.

Key Features

  • High-speed Performance: Optimized execution speed with minimal overhead
  • HTTP Specification Compliance: Accurate and strict implementation of HTTP/1.1 and HTTP/2
  • Async-first: Seamless integration with Rust's async/await ecosystem
  • Modular Design: Selective component inclusion via feature flags
  • Service trait: Flexible middleware patterns and request processing system
  • Memory Safety: Robust memory and thread safety through Rust's type system

Pros and Cons

Pros

  • Top-class HTTP performance realization in the Rust ecosystem
  • Robust and reliable communication through strict HTTP specification compliance
  • Optimized binary size through feature flag system
  • High scalability and efficient resource utilization through asynchronous processing
  • Flexible architecture and middleware support through Service trait
  • Compile-time error detection and safety guarantee through Rust type system

Cons

  • Advanced Rust and asynchronous programming knowledge required due to low-level library nature
  • Complex implementation needed for direct use (high-level libraries like reqwest recommended)
  • Combination with additional libraries necessary as it's not a full-featured framework
  • C FFI (Foreign Function Interface) is unstable and not covered by SemVer guarantee
  • High learning cost for beginners with technical documentation
  • Need to master unique Rust patterns when migrating from other languages

Reference Pages

Code Examples

Dependency Setup and Project Preparation

# Cargo.toml
[dependencies]
hyper = { version = "1", features = ["full"] }
tokio = { version = "1", features = ["full"] }
http-body-util = "0.1"
bytes = "1"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
# Project creation and dependency addition
cargo new hyper-client-example
cd hyper-client-example

# Dependency verification
cargo check

Basic HTTP Client Implementation

use hyper::{Client, Request, Body, Method, Uri};
use hyper::client::HttpConnector;
use http_body_util::Empty;
use tokio;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
    // HTTP client creation
    let client = Client::new();

    // Basic GET request
    let uri: Uri = "http://httpbin.org/get".parse()?;
    let response = client.get(uri).await?;
    
    println!("Status: {}", response.status());
    println!("Headers: {:#?}", response.headers());
    
    // Response body reading
    let body_bytes = hyper::body::to_bytes(response.into_body()).await?;
    let body_str = String::from_utf8(body_bytes.to_vec())?;
    println!("Body: {}", body_str);

    Ok(())
}

// Custom request builder
async fn custom_get_request() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
    let client = Client::new();
    
    let request = Request::builder()
        .method(Method::GET)
        .uri("https://api.example.com/users")
        .header("User-Agent", "hyper-client/1.0")
        .header("Accept", "application/json")
        .body(Empty::<bytes::Bytes>::new())?;
    
    let response = client.request(request).await?;
    
    if response.status().is_success() {
        let body = hyper::body::to_bytes(response.into_body()).await?;
        println!("Success: {}", String::from_utf8_lossy(&body));
    } else {
        println!("Error: {}", response.status());
    }
    
    Ok(())
}

POST, PUT, DELETE Requests and JSON Handling

use hyper::{Client, Request, Body, Method};
use http_body_util::Full;
use bytes::Bytes;
use serde::{Deserialize, Serialize};

#[derive(Serialize, Deserialize, Debug)]
struct User {
    id: Option<u32>,
    name: String,
    email: String,
    age: u32,
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
    let client = Client::new();

    // POST request (JSON sending)
    let new_user = User {
        id: None,
        name: "John Doe".to_string(),
        email: "[email protected]".to_string(),
        age: 30,
    };

    let json_body = serde_json::to_string(&new_user)?;
    
    let request = Request::builder()
        .method(Method::POST)
        .uri("https://api.example.com/users")
        .header("Content-Type", "application/json")
        .header("Authorization", "Bearer your-token")
        .body(Full::new(Bytes::from(json_body)))?;

    let response = client.request(request).await?;
    
    if response.status().is_success() {
        let body = hyper::body::to_bytes(response.into_body()).await?;
        let created_user: User = serde_json::from_slice(&body)?;
        println!("User created successfully: {:?}", created_user);
    } else {
        println!("Error: {}", response.status());
    }

    // PUT request (data update)
    let updated_user = User {
        id: Some(1),
        name: "Jane Doe".to_string(),
        email: "[email protected]".to_string(),
        age: 31,
    };

    let json_body = serde_json::to_string(&updated_user)?;
    
    let put_request = Request::builder()
        .method(Method::PUT)
        .uri("https://api.example.com/users/1")
        .header("Content-Type", "application/json")
        .header("Authorization", "Bearer your-token")
        .body(Full::new(Bytes::from(json_body)))?;

    let put_response = client.request(put_request).await?;
    println!("Update status: {}", put_response.status());

    // DELETE request
    let delete_request = Request::builder()
        .method(Method::DELETE)
        .uri("https://api.example.com/users/1")
        .header("Authorization", "Bearer your-token")
        .body(Empty::<Bytes>::new())?;

    let delete_response = client.request(delete_request).await?;
    
    if delete_response.status().is_success() {
        println!("User deleted successfully");
    }

    Ok(())
}

Advanced Configuration (Timeout, Authentication, Custom Headers)

use hyper::{Client, Request, Method, Uri};
use hyper::client::HttpConnector;
use hyper_tls::HttpsConnector;
use http_body_util::Empty;
use bytes::Bytes;
use std::time::Duration;
use tower::{ServiceBuilder, timeout::TimeoutLayer};
use tower_http::classify::StatusInRangeAsFailures;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
    // HTTPS-enabled client creation
    let https = HttpsConnector::new();
    let client = Client::builder().build::<_, hyper::Body>(https);

    // Custom headers and timeout configuration
    let request = Request::builder()
        .method(Method::GET)
        .uri("https://secure-api.example.com/data")
        .header("User-Agent", "MyApp/1.0 (Rust Hyper)")
        .header("Accept", "application/json")
        .header("Accept-Language", "en-US,ja-JP")
        .header("X-API-Version", "v2")
        .header("X-Request-ID", "req-12345")
        .header("Authorization", "Bearer your-jwt-token")
        .body(Empty::<Bytes>::new())?;

    // Request execution with timeout
    let response = tokio::time::timeout(
        Duration::from_secs(30),
        client.request(request)
    ).await??;

    println!("Status: {}", response.status());
    println!("Headers: {:#?}", response.headers());

    // Basic authentication example
    let basic_auth_request = Request::builder()
        .method(Method::GET)
        .uri("https://api.example.com/private")
        .header("Authorization", "Basic dXNlcm5hbWU6cGFzc3dvcmQ=") // base64(username:password)
        .body(Empty::<Bytes>::new())?;

    let auth_response = client.request(basic_auth_request).await?;
    println!("Authentication status: {}", auth_response.status());

    Ok(())
}

// Custom connector and proxy configuration
use hyper::client::connect::dns::GaiResolver;
use hyper::client::connect::HttpConnector;

async fn custom_connector_example() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
    // Custom DNS resolver configuration
    let mut connector = HttpConnector::new_with_resolver(GaiResolver::new());
    connector.set_connect_timeout(Some(Duration::from_secs(5)));
    connector.set_happy_eyeballs_timeout(Some(Duration::from_millis(300)));
    connector.enforce_http(false); // Allow HTTPS

    let client = Client::builder()
        .http2_only(false) // Allow HTTP/1.1
        .build(connector);

    let response = client.get("http://example.com".parse()?).await?;
    println!("Custom connector response: {}", response.status());

    Ok(())
}

Error Handling and Retry Functionality

use hyper::{Client, StatusCode};
use http_body_util::Empty;
use bytes::Bytes;
use std::time::Duration;
use tokio::time::{sleep, timeout};

#[derive(Debug)]
enum HttpError {
    RequestFailed(hyper::Error),
    Timeout,
    HttpStatus(StatusCode),
    SerializationError(serde_json::Error),
}

impl From<hyper::Error> for HttpError {
    fn from(err: hyper::Error) -> Self {
        HttpError::RequestFailed(err)
    }
}

impl From<serde_json::Error> for HttpError {
    fn from(err: serde_json::Error) -> Self {
        HttpError::SerializationError(err)
    }
}

// HTTP client with retry functionality
struct RetryableClient {
    client: Client<hyper_tls::HttpsConnector<hyper::client::HttpConnector>>,
    max_retries: usize,
    base_delay: Duration,
}

impl RetryableClient {
    fn new() -> Self {
        let https = hyper_tls::HttpsConnector::new();
        let client = Client::builder().build::<_, hyper::Body>(https);
        
        Self {
            client,
            max_retries: 3,
            base_delay: Duration::from_millis(500),
        }
    }

    async fn get_with_retry(&self, uri: hyper::Uri) -> Result<String, HttpError> {
        let mut last_error = None;

        for attempt in 0..=self.max_retries {
            match self.attempt_request(uri.clone()).await {
                Ok(response) => return Ok(response),
                Err(e) => {
                    last_error = Some(e);
                    
                    if attempt < self.max_retries {
                        let delay = self.base_delay * 2_u32.pow(attempt as u32);
                        println!("Attempt {} failed. Retrying in {}ms...", attempt + 1, delay.as_millis());
                        sleep(delay).await;
                    }
                }
            }
        }

        Err(last_error.unwrap())
    }

    async fn attempt_request(&self, uri: hyper::Uri) -> Result<String, HttpError> {
        let request = hyper::Request::builder()
            .method(hyper::Method::GET)
            .uri(uri)
            .header("User-Agent", "RetryableClient/1.0")
            .body(Empty::<Bytes>::new())
            .map_err(|e| HttpError::RequestFailed(hyper::Error::from(e)))?;

        // Request with timeout
        let response = timeout(
            Duration::from_secs(10),
            self.client.request(request)
        ).await.map_err(|_| HttpError::Timeout)??;

        // Status code check
        let status = response.status();
        if !status.is_success() {
            return Err(HttpError::HttpStatus(status));
        }

        // Response reading
        let body_bytes = hyper::body::to_bytes(response.into_body()).await?;
        let body_str = String::from_utf8(body_bytes.to_vec())
            .map_err(|_| HttpError::RequestFailed(
                hyper::Error::from(std::io::Error::new(
                    std::io::ErrorKind::InvalidData,
                    "Invalid UTF-8"
                ))
            ))?;

        Ok(body_str)
    }
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
    let client = RetryableClient::new();
    
    match client.get_with_retry("https://httpbin.org/get".parse()?).await {
        Ok(response) => println!("Success: {}", response),
        Err(HttpError::RequestFailed(e)) => println!("Request error: {}", e),
        Err(HttpError::Timeout) => println!("Timeout error"),
        Err(HttpError::HttpStatus(status)) => println!("HTTP error: {}", status),
        Err(HttpError::SerializationError(e)) => println!("Serialization error: {}", e),
    }

    Ok(())
}

Concurrent Processing and Streaming

use hyper::{Client, Uri};
use http_body_util::Empty;
use bytes::Bytes;
use futures::future::join_all;
use tokio::task::JoinHandle;
use std::time::Instant;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
    let client = Client::new();

    // Parallel fetching of multiple URLs
    let urls = vec![
        "http://httpbin.org/delay/1",
        "http://httpbin.org/delay/2", 
        "http://httpbin.org/delay/1",
        "http://httpbin.org/status/200",
    ];

    let start = Instant::now();
    
    // Execute parallel requests
    let handles: Vec<JoinHandle<Result<String, Box<dyn std::error::Error + Send + Sync>>>> = urls
        .into_iter()
        .map(|url| {
            let client = client.clone();
            tokio::spawn(async move {
                let uri: Uri = url.parse()?;
                let response = client.get(uri).await?;
                let body = hyper::body::to_bytes(response.into_body()).await?;
                Ok(format!("URL: {} - Status: {}", url, response.status()))
            })
        })
        .collect();

    // Wait for all requests to complete
    let results = join_all(handles).await;
    
    println!("Parallel requests completed in: {:?}", start.elapsed());
    
    for (i, result) in results.into_iter().enumerate() {
        match result {
            Ok(Ok(response)) => println!("Request {}: {}", i + 1, response),
            Ok(Err(e)) => println!("Request {} error: {}", i + 1, e),
            Err(e) => println!("Task {} error: {}", i + 1, e),
        }
    }

    Ok(())
}

// Streaming processing example
use hyper::body::{Body, Frame};
use futures::stream::StreamExt;

async fn streaming_example() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
    let client = Client::new();
    
    let response = client.get("http://httpbin.org/stream/10".parse()?).await?;
    
    if response.status().is_success() {
        let mut body = response.into_body();
        let mut total_bytes = 0;
        
        // Read data in streaming fashion
        while let Some(frame) = body.frame().await {
            match frame? {
                Frame::Data(chunk) => {
                    total_bytes += chunk.len();
                    println!("Received chunk: {} bytes (total: {} bytes)", chunk.len(), total_bytes);
                    
                    // Process chunk data
                    let chunk_str = String::from_utf8_lossy(&chunk);
                    println!("Data: {}", chunk_str.trim());
                }
                Frame::Trailers(_trailers) => {
                    println!("Trailers received");
                }
            }
        }
        
        println!("Streaming completed: total received {} bytes", total_bytes);
    }

    Ok(())
}

Production Configuration and Monitoring

use hyper::{Client, Request, Method};
use hyper_tls::HttpsConnector;
use http_body_util::Full;
use bytes::Bytes;
use std::sync::Arc;
use std::sync::atomic::{AtomicU64, Ordering};
use std::time::{Duration, Instant};
use tokio::time::timeout;

// Metrics collection
#[derive(Debug, Default)]
struct HttpMetrics {
    total_requests: AtomicU64,
    successful_requests: AtomicU64,
    failed_requests: AtomicU64,
    total_duration: AtomicU64, // milliseconds
}

impl HttpMetrics {
    fn record_request(&self, success: bool, duration: Duration) {
        self.total_requests.fetch_add(1, Ordering::Relaxed);
        self.total_duration.fetch_add(duration.as_millis() as u64, Ordering::Relaxed);
        
        if success {
            self.successful_requests.fetch_add(1, Ordering::Relaxed);
        } else {
            self.failed_requests.fetch_add(1, Ordering::Relaxed);
        }
    }
    
    fn print_stats(&self) {
        let total = self.total_requests.load(Ordering::Relaxed);
        let success = self.successful_requests.load(Ordering::Relaxed);
        let failed = self.failed_requests.load(Ordering::Relaxed);
        let total_duration = self.total_duration.load(Ordering::Relaxed);
        
        if total > 0 {
            let avg_duration = total_duration as f64 / total as f64;
            let success_rate = (success as f64 / total as f64) * 100.0;
            
            println!("HTTP Metrics:");
            println!("  Total requests: {}", total);
            println!("  Successful: {} ({:.1}%)", success, success_rate);
            println!("  Failed: {}", failed);
            println!("  Average response time: {:.1}ms", avg_duration);
        }
    }
}

// Production HTTP client
struct ProductionHttpClient {
    client: Client<HttpsConnector<hyper::client::HttpConnector>>,
    metrics: Arc<HttpMetrics>,
    timeout: Duration,
}

impl ProductionHttpClient {
    fn new() -> Self {
        // TLS configuration and connection settings
        let https = HttpsConnector::new();
        
        let client = Client::builder()
            .pool_idle_timeout(Duration::from_secs(60))
            .pool_max_idle_per_host(10)
            .http2_only(false)
            .build(https);

        Self {
            client,
            metrics: Arc::new(HttpMetrics::default()),
            timeout: Duration::from_secs(30),
        }
    }

    async fn post_json<T: serde::Serialize>(
        &self,
        url: &str,
        payload: &T,
        auth_token: &str,
    ) -> Result<String, Box<dyn std::error::Error + Send + Sync>> {
        let start = Instant::now();
        let mut success = false;

        let result = async {
            let json_body = serde_json::to_string(payload)?;
            
            let request = Request::builder()
                .method(Method::POST)
                .uri(url)
                .header("Content-Type", "application/json")
                .header("Accept", "application/json")
                .header("Authorization", format!("Bearer {}", auth_token))
                .header("User-Agent", "ProductionClient/1.0")
                .header("X-Request-ID", uuid::Uuid::new_v4().to_string())
                .body(Full::new(Bytes::from(json_body)))?;

            let response = timeout(self.timeout, self.client.request(request)).await??;
            
            if response.status().is_success() {
                success = true;
                let body = hyper::body::to_bytes(response.into_body()).await?;
                Ok(String::from_utf8(body.to_vec())?)
            } else {
                Err(format!("HTTP Error: {}", response.status()).into())
            }
        }.await;

        // Record metrics
        self.metrics.record_request(success, start.elapsed());
        
        result
    }

    fn get_metrics(&self) -> Arc<HttpMetrics> {
        self.metrics.clone()
    }
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
    let client = ProductionHttpClient::new();
    
    // Sample payload
    use serde_json::json;
    let payload = json!({
        "name": "John Doe",
        "email": "[email protected]",
        "department": "engineering"
    });

    // Execute API call
    match client.post_json(
        "https://api.example.com/users",
        &payload,
        "your-auth-token"
    ).await {
        Ok(response) => println!("API success: {}", response),
        Err(e) => println!("API error: {}", e),
    }

    // Display metrics
    client.get_metrics().print_stats();

    Ok(())
}

// Health check functionality
impl ProductionHttpClient {
    async fn health_check(&self, endpoints: Vec<&str>) -> Vec<(String, bool, Duration)> {
        let handles = endpoints.into_iter().map(|endpoint| {
            let client = self.client.clone();
            let endpoint = endpoint.to_string();
            
            tokio::spawn(async move {
                let start = Instant::now();
                let uri: hyper::Uri = endpoint.parse().unwrap();
                
                let success = match timeout(Duration::from_secs(5), client.get(uri)).await {
                    Ok(Ok(response)) => response.status().is_success(),
                    _ => false,
                };
                
                (endpoint, success, start.elapsed())
            })
        });

        let results = futures::future::join_all(handles).await;
        results.into_iter().map(|r| r.unwrap()).collect()
    }
}