How do I build HTTP types with the http crate in Rust?
Walkthrough
The http crate provides core HTTP types that form the foundation for many Rust web libraries like hyper, reqwest, axum, and others. It defines strongly-typed representations of HTTP requests, responses, headers, method, status codes, and URIs. The crate is designed to be a shared foundation that different HTTP libraries can build upon, ensuring interoperability. Understanding http is essential for working with web services in Rust at a lower level or building your own HTTP abstractions.
Key concepts:
- Request — represents an HTTP request with body type T
- Response — represents an HTTP response with body type T
- Method — HTTP methods (GET, POST, PUT, DELETE, etc.)
- StatusCode — HTTP status codes with semantic meaning
- HeaderMap — type-safe header storage
Code Example
# Cargo.toml
[dependencies]
http = "1.0"use http::{Request, Response, Method, StatusCode};
fn main() {
let request = Request::builder()
.method(Method::GET)
.uri("https://example.com/api/users")
.header("Content-Type", "application/json")
.body(()).unwrap();
println!("Request: {} {}", request.method(), request.uri());
}Building Requests
use http::{Request, Method};
fn main() {
// Simple request with builder
let request = Request::builder()
.method(Method::GET)
.uri("/users/123")
.body(())
.unwrap();
println!("Method: {}", request.method());
println!("URI: {}", request.uri());
// POST request with body
let post_request = Request::builder()
.method(Method::POST)
.uri("/api/users")
.header("Content-Type", "application/json")
.body(r#"{"name":"Alice"}"#.to_string())
.unwrap();
println!("Body: {}", post_request.body());
}Request Parts and Body
use http::Request;
fn main() {
let request = Request::builder()
.method("GET")
.uri("https://example.com/search?q=rust")
.header("Accept", "application/json")
.header("User-Agent", "MyApp/1.0")
.body("request body".to_string())
.unwrap();
// Decompose into parts and body
let (parts, body) = request.into_parts();
println!("Method: {:?}", parts.method);
println!("URI: {:?}", parts.uri);
println!("Version: {:?}", parts.version);
println!("Headers: {:?}", parts.headers);
println!("Body: {}", body);
}Building Responses
use http::{Response, StatusCode};
fn main() {
// Simple response
let response = Response::builder()
.status(StatusCode::OK)
.header("Content-Type", "text/plain")
.body("Hello, World!")
.unwrap();
println!("Status: {}", response.status());
println!("Body: {:?}", response.body());
// Response with different status
let not_found = Response::builder()
.status(StatusCode::NOT_FOUND)
.header("Content-Type", "application/json")
.body(r#"{"error":"Not found"}"#)
.unwrap();
println!("Status: {}", not_found.status());
}HTTP Methods
use http::Method;
fn main() {
// Common methods
let get = Method::GET;
let post = Method::POST;
let put = Method::PUT;
let delete = Method::DELETE;
let patch = Method::PATCH;
let head = Method::HEAD;
let options = Method::OPTIONS;
let connect = Method::CONNECT;
let trace = Method::TRACE;
println!("GET: {}", get);
println!("POST: {}", post);
// Custom method
let custom = Method::from_bytes(b"CUSTOM").unwrap();
println!("Custom: {}", custom);
// Compare methods
if get == Method::GET {
println!("This is a GET request");
}
// Method properties
println!("GET is safe: {}", get.is_safe());
println!("POST is safe: {}", post.is_safe());
println!("GET is idempotent: {}", get.is_idempotent());
}Status Codes
use http::StatusCode;
fn main() {
// Common status codes
let ok = StatusCode::OK;
let created = StatusCode::CREATED;
let bad_request = StatusCode::BAD_REQUEST;
let not_found = StatusCode::NOT_FOUND;
let server_error = StatusCode::INTERNAL_SERVER_ERROR;
println!("OK: {} - {}", ok.as_u16(), ok.canonical_reason().unwrap());
// Status code categories
for code in [200, 201, 301, 400, 404, 500] {
let status = StatusCode::from_u16(code).unwrap();
let category = if status.is_informational() {
"Informational"
} else if status.is_success() {
"Success"
} else if status.is_redirection() {
"Redirection"
} else if status.is_client_error() {
"Client Error"
} else if status.is_server_error() {
"Server Error"
} else {
"Unknown"
};
println!("{}: {} - {:?}", code, category, status.canonical_reason());
}
}Working with Headers
use http::{HeaderMap, HeaderName, HeaderValue};
fn main() {
let mut headers = HeaderMap::new();
// Insert headers
headers.insert("content-type", "application/json".parse().unwrap());
headers.insert("authorization", "Bearer token123".parse().unwrap());
// Append header (allows multiple values)
headers.append("set-cookie", "session=abc".parse().unwrap());
headers.append("set-cookie", "theme=dark".parse().unwrap());
// Get single header
if let Some(content_type) = headers.get("content-type") {
println!("Content-Type: {:?}", content_type);
}
// Get all values for a header
for cookie in headers.get_all("set-cookie").iter() {
println!("Cookie: {:?}", cookie);
}
// Check existence
println!("Has authorization: {}", headers.contains_key("authorization"));
// Remove header
headers.remove("authorization");
// Iterate all headers
for (name, value) in &headers {
println!("{}: {:?}", name, value);
}
}Typed Header Names
use http::{header, HeaderMap};
fn main() {
let mut headers = HeaderMap::new();
// Use standard header names
headers.insert(header::CONTENT_TYPE, "text/html".parse().unwrap());
headers.insert(header::ACCEPT, "application/json".parse().unwrap());
headers.insert(header::USER_AGENT, "MyApp/1.0".parse().unwrap());
headers.insert(header::HOST, "example.com".parse().unwrap());
headers.insert(header::AUTHORIZATION, "Bearer token".parse().unwrap());
// Access using typed names
if let Some(ct) = headers.get(header::CONTENT_TYPE) {
println!("Content-Type: {:?}", ct);
}
// Common headers available in header module
let _headers: &[&str] = &[
"accept",
"accept-encoding",
"accept-language",
"cache-control",
"content-length",
"content-type",
"cookie",
"date",
"etag",
"host",
"if-modified-since",
"location",
"server",
"set-cookie",
"user-agent",
];
}URI Parsing
use http::Uri;
fn main() {
let uri: Uri = "https://example.com:8080/api/users?page=1&limit=10#section".parse().unwrap();
// URI components
println!("Scheme: {:?}", uri.scheme());
println!("Authority: {:?}", uri.authority());
println!("Path: {:?}", uri.path());
println!("Query: {:?}", uri.query());
println!("Fragment: {:?}", uri.fragment());
// Build URI from parts
let uri = Uri::builder()
.scheme("https")
.authority("api.example.com")
.path_and_query("/v1/users?active=true")
.build()
.unwrap();
println!("Built URI: {}", uri);
}Authority and Path
use http::uri::{Authority, PathAndQuery, Uri};
fn main() {
let uri: Uri = "https://user:pass@example.com:443/api/v1/users?limit=5".parse().unwrap();
if let Some(auth) = uri.authority() {
println!("Host: {:?}", auth.host());
println!("Port: {:?}", auth.port());
println!("Full authority: {}", auth);
}
if let Some(path) = uri.path_and_query() {
println!("Path: {:?}", path.path());
println!("Query: {:?}", path.query());
}
// Build authority
let authority: Authority = "api.example.com:8080".parse().unwrap();
println!("Authority: {}", authority);
// Build path and query
let pq: PathAndQuery = "/search?q=rust&sort=recent".parse().unwrap();
println!("Path+Query: {}", pq);
}Request Builder Patterns
use http::{Request, Method, HeaderMap};
fn main() {
// Method 1: Builder pattern
let request1 = Request::builder()
.method(Method::POST)
.uri("/api/users")
.header("Content-Type", "application/json")
.body(r#"{"name":"Alice"}"
#.to_string())
.unwrap();
// Method 2: Create from parts
let mut builder = Request::builder()
.method(Method::GET)
.uri("/api/users");
builder = builder.header("Accept", "application/json");
builder = builder.header("User-Agent", "MyApp");
let request2 = builder.body(()).unwrap();
// Method 3: Modify existing request
let mut request = Request::new("body");
*request.method_mut() = Method::PUT;
*request.uri_mut() = "/update".parse().unwrap();
request.headers_mut().insert("X-Custom", "value".parse().unwrap());
println!("Request: {} {}", request.method(), request.uri());
}Response Builder Patterns
use http::{Response, StatusCode, header};
fn main() {
// JSON response
let json_response = Response::builder()
.status(StatusCode::OK)
.header(header::CONTENT_TYPE, "application/json")
.body(r#"{"status":"ok"}"
#)
.unwrap();
// Error response
let error_response = Response::builder()
.status(StatusCode::BAD_REQUEST)
.header(header::CONTENT_TYPE, "application/json")
.body(r#"{"error":"Invalid input"}"
#)
.unwrap();
// Redirect response
let redirect = Response::builder()
.status(StatusCode::FOUND)
.header(header::LOCATION, "/new-location")
.body(())
.unwrap();
// Create response and modify
let mut response = Response::new("body");
*response.status_mut() = StatusCode::CREATED;
response.headers_mut().insert(
header::CONTENT_TYPE,
"text/plain".parse().unwrap(),
);
}Extensions for Request/Response State
use http::{Request, Response, Extensions};
use std::any::Any;
#[derive(Debug, Clone)]
struct RequestContext {
request_id: String,
user_id: Option<String>,
}
fn main() {
let mut request = Request::new(());
// Add typed extension
request.extensions_mut().insert(RequestContext {
request_id: "req-123".to_string(),
user_id: Some("user-456".to_string()),
});
// Retrieve extension
if let Some(ctx) = request.extensions().get::<RequestContext>() {
println!("Request ID: {}", ctx.request_id);
}
// Extensions are type-indexed
request.extensions_mut().insert(42i32);
request.extensions_mut().insert("hello".to_string());
println!("Int: {:?}", request.extensions().get::<i32>());
println!("String: {:?}", request.extensions().get::<String>());
}Version
use http::{Version, Request};
fn main() {
let mut request = Request::new(());
// Set HTTP version
*request.version_mut() = Version::HTTP_11;
println!("Version: {:?}", request.version());
// Available versions
println!("HTTP/0.9: {:?}", Version::HTTP_09);
println!("HTTP/1.0: {:?}", Version::HTTP_10);
println!("HTTP/1.1: {:?}", Version::HTTP_11);
println!("HTTP/2.0: {:?}", Version::HTTP_2);
println!("HTTP/3.0: {:?}", Version::HTTP_3);
}Converting Between Request and Response
use http::{Request, Response, StatusCode};
fn handle_request(req: Request<String>) -> Response<String> {
println!("Handling {} {}", req.method(), req.uri());
Response::builder()
.status(StatusCode::OK)
.header("Content-Type", "application/json")
.body(format!("{{\"path\": \"{}\"}}", req.uri().path()))
.unwrap()
}
fn main() {
let request = Request::builder()
.method("GET")
.uri("/api/users")
.body(String::new())
.unwrap();
let response = handle_request(request);
println!("Response: {} - {}", response.status(), response.body());
}Error Handling
use http::{Request, Method, StatusCode};
fn main() {
// Invalid method
let result = "INVALID METHOD".parse::<Method>();
match result {
Ok(method) => println!("Method: {}", method),
Err(e) => println!("Invalid method: {}", e),
}
// Invalid status code
let result = StatusCode::from_u16(999);
match result {
Ok(status) => println!("Status: {}", status),
Err(e) => println!("Invalid status: {}", e),
}
// Invalid URI
let result = "not a valid uri".parse::<http::Uri>();
match result {
Ok(uri) => println!("URI: {}", uri),
Err(e) => println!("Invalid URI: {}", e),
}
}Generic Body Types
use http::{Request, Response};
// Different body types
fn process_requests() {
// Empty body
let empty: Request<()> = Request::builder()
.uri("/health")
.body(())
.unwrap();
// String body
let string: Request<String> = Request::builder()
.uri("/api")
.body("text body".to_string())
.unwrap();
// Bytes body
let bytes: Request<Vec<u8>> = Request::builder()
.uri("/upload")
.body(vec![0, 1, 2, 3, 4])
.unwrap();
// Custom body type
#[derive(Debug)]
struct JsonBody(serde_json::Value);
let json = Request::builder()
.uri("/api/json")
.body(JsonBody(serde_json::json!({"key": "value"})))
.unwrap();
}
fn main() {
process_requests();
}Middleware Pattern
use http::{Request, Response, header};
fn logging_middleware<T>(request: &Request<T>) {
println!("[{}] {}", request.method(), request.uri());
for (name, value) in request.headers() {
println!(" {}: {:?}", name, value);
}
}
fn auth_middleware<T>(request: &Request<T>) -> Result<(), String> {
match request.headers().get(header::AUTHORIZATION) {
Some(auth) => {
println!("Auth header present: {:?}", auth);
Ok(())
}
None => Err("Missing authorization header".to_string()),
}
}
fn add_response_headers<B>(response: &mut Response<B>) {
response.headers_mut().insert(
"X-Request-Id",
"req-123".parse().unwrap(),
);
response.headers_mut().insert(
"X-Response-Time",
"42ms".parse().unwrap(),
);
}
fn main() {
let request = Request::builder()
.method("GET")
.uri("/api/users")
.header("Authorization", "Bearer token")
.body(())
.unwrap();
logging_middleware(&request);
if auth_middleware(&request).is_ok() {
let mut response = Response::new("OK");
add_response_headers(&mut response);
println!("Response headers: {:?}", response.headers());
}
}Real-World Example: API Client
use http::{Request, Response, Method, StatusCode, header};
use std::collections::HashMap;
struct ApiClient {
base_url: String,
default_headers: HashMap<String, String>,
}
impl ApiClient {
fn new(base_url: &str) -> Self {
Self {
base_url: base_url.to_string(),
default_headers: HashMap::new(),
}
}
fn with_header(mut self, key: &str, value: &str) -> Self {
self.default_headers.insert(key.to_string(), value.to_string());
self
}
fn build_request(&self, method: Method, path: &str, body: Option<&str>) -> Request<String> {
let uri = format!("{}{}", self.base_url, path);
let mut builder = Request::builder()
.method(method)
.uri(&uri);
// Add default headers
for (key, value) in &self.default_headers {
builder = builder.header(key, value);
}
builder.body(body.unwrap_or("").to_string()).unwrap()
}
fn get(&self, path: &str) -> Request<String> {
self.build_request(Method::GET, path, None)
}
fn post(&self, path: &str, body: &str) -> Request<String> {
self.build_request(Method::POST, path, Some(body))
}
}
fn main() {
let client = ApiClient::new("https://api.example.com")
.with_header("Accept", "application/json")
.with_header("User-Agent", "MyApp/1.0");
let get_req = client.get("/users");
println!("GET {}", get_req.uri());
let post_req = client.post("/users", r#"{"name":"Alice"}"
#);
println!("POST {} with body: {}", post_req.uri(), post_req.body());
}Real-World Example: HTTP Router
use http::{Request, Response, Method, StatusCode};
type Handler = fn(&Request<String>) -> Response<String>;
struct Router {
routes: Vec<(Method, String, Handler)>,
}
impl Router {
fn new() -> Self {
Self { routes: Vec::new() }
}
fn route(mut self, method: Method, path: &str, handler: Handler) -> Self {
self.routes.push((method, path.to_string(), handler));
self
}
fn handle(&self, request: &Request<String>) -> Response<String> {
for (method, path, handler) in &self.routes {
if request.method() == method && request.uri().path() == path {
return handler(request);
}
}
Response::builder()
.status(StatusCode::NOT_FOUND)
.body("Not Found".to_string())
.unwrap()
}
}
fn get_users(_req: &Request<String>) -> Response<String> {
Response::builder()
.status(StatusCode::OK)
.body(r#"[{"id":1,"name":"Alice"}]"#.to_string())
.unwrap()
}
fn create_user(req: &Request<String>) -> Response<String> {
println!("Creating user with: {}", req.body());
Response::builder()
.status(StatusCode::CREATED)
.body(r#"{"id":2,"name":"Bob"}"
#.to_string())
.unwrap()
}
fn main() {
let router = Router::new()
.route(Method::GET, "/users", get_users)
.route(Method::POST, "/users", create_user);
let request = Request::builder()
.method(Method::GET)
.uri("/users")
.body(String::new())
.unwrap();
let response = router.handle(&request);
println!("Response: {} - {}", response.status(), response.body());
}Summary
Request<T>andResponse<T>are generic over body type T- Use
Request::builder()andResponse::builder()for fluent construction Methodprovides type-safe HTTP methods withGET,POST, etc.StatusCodeoffers all HTTP status codes with semantic methodsHeaderMapstores headers with efficient lookup and multiple values- Use
headermodule for standard header names likeCONTENT_TYPE Uriparses URIs into components: scheme, authority, path, queryExtensionsallows attaching typed data to requests/responsesinto_parts()andfrom_parts()decompose and reconstruct messages- The crate is designed for interoperability between HTTP libraries
- Perfect for: building HTTP clients/servers, middleware, API clients, routing
