Loading page…
Rust walkthroughs
Loading page…
Hyper is a fast, correct HTTP library for Rust that powers many web frameworks including Axum and Warp. It provides low-level HTTP/1 and HTTP/2 support with both client and server implementations. Understanding Hyper gives you fine-grained control over HTTP operations and is essential knowledge for Rust web development.
Core concepts:
Hyper's API is lower-level than frameworks like Axum, giving you direct control over every aspect of request handling.
# Cargo.toml
[dependencies]
hyper = { version = "1", features = ["full"] }
hyper-util = { version = "0.1", features = ["full"] }
tokio = { version = "1", features = ["full"] }
http-body-util = "0.1"use http_body_util::BodyExt;
use hyper::body::Bytes;
use hyper::server::conn::http1;
use hyper::service::service_fn;
use hyper::{Method, Request, Response, StatusCode};
use hyper_util::rt::TokioIo;
use std::net::SocketAddr;
use tokio::net::TcpListener;
// Type alias for our response body type
type GenericError = Box<dyn std::error::Error + Send + Sync>;
type Result<T> = std::result::Result<T, GenericError>;
async fn handle_request(req: Request<hyper::body::Incoming>) -> Result<Response<String>> {
match (req.method(), req.uri().path()) {
// Simple GET endpoint
(&Method::GET, "/") => Ok(Response::new("Welcome to Hyper!".to_string())),
// Health check endpoint
(&Method::GET, "/health") => {
Ok(Response::builder()
.status(StatusCode::OK)
.header("Content-Type", "application/json")
.body(r#"{ "status": "healthy" }"#.to_string())?)
}
// Echo endpoint - returns the request body
(&Method::POST, "/echo") => {
let body = req.collect().await?.to_bytes();
let body_str = String::from_utf8_lossy(&body);
Ok(Response::new(format!("Echo: {}", body_str)))
}
// Path parameters (manual parsing)
(&Method::GET, path) if path.starts_with("/users/") => {
let user_id = path.trim_start_matches("/users/");
Ok(Response::new(format!("User ID: {}", user_id)))
}
// 404 for unknown routes
_ => Ok(Response::builder()
.status(StatusCode::NOT_FOUND)
.body("Not Found".to_string())?),
}
}
#[tokio::main]
async fn main() -> Result<()> {
let addr: SocketAddr = "127.0.0.1:3000".parse()?;
let listener = TcpListener::bind(addr).await?;
println!("Server listening on http://{}", addr);
loop {
let (stream, remote_addr) = listener.accept().await?;
println!("New connection from {}", remote_addr);
let io = TokioIo::new(stream);
tokio::spawn(async move {
let service = service_fn(handle_request);
if let Err(e) = http1::Builder::new().serve_connection(io, service).await {
eprintln!("Error serving connection: {}", e);
}
});
}
}use http_body_util::BodyExt;
use hyper::server::conn::http1;
use hyper::service::service_fn;
use hyper::{Method, Request, Response, StatusCode};
use hyper_util::rt::TokioIo;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::net::SocketAddr;
use std::sync::Arc;
use tokio::net::TcpListener;
use tokio::sync::RwLock;
type Db = Arc<RwLock<HashMap<u64, User>>>;
#[derive(Debug, Serialize, Deserialize, Clone)]
struct User {
id: u64,
name: String,
email: String,
}
async fn handle_api(
req: Request<hyper::body::Incoming>,
db: Db,
) -> Result<Response<String>, Box<dyn std::error::Error + Send + Sync>> {
match (req.method(), req.uri().path()) {
// List all users
(&Method::GET, "/users") => {
let users = db.read().await;
let json = serde_json::to_string(&users.values().collect::<Vec<_>>())?;
Ok(Response::builder()
.header("Content-Type", "application/json")
.body(json)?)
}
// Create user
(&Method::POST, "/users") => {
let body = req.collect().await?.to_bytes();
let mut user: User = serde_json::from_slice(&body)?;
let mut db = db.write().await;
let id = db.keys().max().unwrap_or(&0) + 1;
user.id = id;
db.insert(id, user.clone());
let json = serde_json::to_string(&user)?;
Ok(Response::builder()
.status(StatusCode::CREATED)
.header("Content-Type", "application/json")
.body(json)?)
}
// Get user by ID
(&Method::GET, path) if path.starts_with("/users/") => {
let id: u64 = path.trim_start_matches("/users/").parse()?;
let db = db.read().await;
match db.get(&id) {
Some(user) => {
let json = serde_json::to_string(user)?;
Ok(Response::builder()
.header("Content-Type", "application/json")
.body(json)?)
}
None => Ok(Response::builder()
.status(StatusCode::NOT_FOUND)
.body("User not found".to_string())?),
}
}
_ => Ok(Response::builder()
.status(StatusCode::NOT_FOUND)
.body("Not Found".to_string())?),
}
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let db: Db = Arc::new(RwLock::new(HashMap::new()));
// Seed some data
db.write().await.insert(1, User {
id: 1,
name: "Alice".to_string(),
email: "alice@example.com".to_string(),
});
let addr: SocketAddr = "127.0.0.1:3000".parse()?;
let listener = TcpListener::bind(addr).await?;
println!("API server listening on http://{}", addr);
loop {
let (stream, _) = listener.accept().await?;
let io = TokioIo::new(stream);
let db = db.clone();
tokio::spawn(async move {
let service = service_fn(move |req| {
let db = db.clone();
async move { handle_api(req, db).await }
});
if let Err(e) = http1::Builder::new().serve_connection(io, service).await {
eprintln!("Error: {}", e);
}
});
}
}use hyper::service::service_fn;
use hyper::{Request, Response};
use std::time::Instant;
// Logging middleware
async fn log_request(
req: Request<hyper::body::Incoming>,
) -> Result<Response<String>, Box<dyn std::error::Error + Send + Sync>> {
let method = req.method().clone();
let path = req.uri().path().to_string();
let start = Instant::now();
println!("--> {} {}", method, path);
let response = handle_request(req).await;
let elapsed = start.elapsed();
println!("<-- {} {} ( {:?} )", method, path, elapsed);
response
}service_fn to wrap an async function as a request handler(method, path) to implement routing logicResponse::builder() for setting status codes and headers.collect().await?.to_bytes()Arc<RwLock<T>> or Arc<Mutex<T>>http1::Builder) from service logic