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.
GitHub Overview
hyperium/hyper
An HTTP library for Rust
Topics
Star History
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()
}
}