Loading page…
Rust walkthroughs
Loading page…
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 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.
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.
// 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 messagesHyper 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.
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.
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.
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());
}
}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);
}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.
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(())
}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;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())
}
}
}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",
}
}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())
}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| 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 | | ✓ |
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:
Use hyper when:
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.