What is the difference between hyper::Request::new and Request::builder for constructing HTTP requests?
Request::new creates a request with a body using default values for all other components, returning a complete Request immediately, while Request::builder returns a Builder that allows configuring individual request components (method, URI, headers, version) before constructing the final request, providing a fluent API for complex request construction. The choice between them depends on whether you need the simplest possible request construction or fine-grained control over request properties.
Basic Request Construction
use http_body_util::Full;
use hyper::body::Bytes;
use hyper::Request;
fn basic_construction() {
// Request::new: Create with just a body
let body = Full::new(Bytes::from("hello world"));
let request: Request<Full<Bytes>> = Request::new(body);
// Default values:
// - Method: GET
// - URI: / (root path)
// - Version: HTTP/1.1
// - Headers: empty
println!("Method: {}", request.method()); // GET
println!("URI: {}", request.uri()); // /
println!("Version: {:?}", request.version()); // HTTP/1.1
}Request::new is the simplest way to create a request with default settings.
Using the Builder Pattern
use http_body_util::Full;
use hyper::body::Bytes;
use hyper::Request;
fn builder_construction() {
// Request::builder: Configure before building
let request: Request<Full<Bytes>> = Request::builder()
.method("POST")
.uri("/api/users")
.header("Content-Type", "application/json")
.version(hyper::Version::HTTP_2)
.body(Full::new(Bytes::from(r#"{"name": "Alice"}"#)))
.unwrap();
println!("Method: {}", request.method()); // POST
println!("URI: {}", request.uri()); // /api/users
println!("Version: {:?}", request.version()); // HTTP/2
}Request::builder provides a fluent API for configuring individual components.
Method Configuration
use http_body_util::Full;
use hyper::body::Bytes;
use hyper::{Method, Request};
fn method_configuration() {
// Request::new: Always GET
let get_request: Request<Full<Bytes>> = Request::new(Full::new(Bytes::new()));
assert_eq!(*get_request.method(), Method::GET);
// Request::builder: Set any method
let post_request: Request<Full<Bytes>> = Request::builder()
.method(Method::POST)
.body(Full::new(Bytes::from("data")))
.unwrap();
assert_eq!(*post_request.method(), Method::POST);
let custom_request: Request<Full<Bytes>> = Request::builder()
.method("CUSTOM")
.body(Full::new(Bytes::new()))
.unwrap();
assert_eq!(custom_request.method(), "CUSTOM");
// Common methods
let _get = Request::builder().method(Method::GET).body(Full::new(Bytes::new())).unwrap();
let _post = Request::builder().method(Method::POST).body(Full::new(Bytes::new())).unwrap();
let _put = Request::builder().method(Method::PUT).body(Full::new(Bytes::new())).unwrap();
let _delete = Request::builder().method(Method::DELETE).body(Full::new(Bytes::new())).unwrap();
}The builder allows setting any HTTP method; Request::new defaults to GET.
URI Configuration
use http_body_util::Full;
use hyper::body::Bytes;
use hyper::Request;
use std::str::FromStr;
fn uri_configuration() {
// Request::new: Always "/"
let default_request: Request<Full<Bytes>> = Request::new(Full::new(Bytes::new()));
assert_eq!(default_request.uri(), "/");
// Request::builder: Set any URI
let request: Request<Full<Bytes>> = Request::builder()
.uri("/api/v1/users/123")
.body(Full::new(Bytes::new()))
.unwrap();
assert_eq!(request.uri(), "/api/v1/users/123");
// Full URLs
let request: Request<Full<Bytes>> = Request::builder()
.uri("https://example.com/api/data?key=value")
.body(Full::new(Bytes::new()))
.unwrap();
// URI components are parsed
let uri = request.uri();
println!("Scheme: {:?}", uri.scheme()); // Some("https")
println!("Host: {:?}", uri.host()); // Some("example.com")
println!("Path: {:?}", uri.path()); // "/api/data"
println!("Query: {:?}", uri.query()); // Some("key=value")
}Request::new defaults to "/" URI; builder allows any URI.
Header Configuration
use http_body_util::Full;
use hyper::body::Bytes;
use hyper::Request;
fn header_configuration() {
// Request::new: No headers
let no_headers: Request<Full<Bytes>> = Request::new(Full::new(Bytes::new()));
assert!(no_headers.headers().is_empty());
// Request::builder: Add headers fluently
let request: Request<Full<Bytes>> = Request::builder()
.header("Content-Type", "application/json")
.header("Authorization", "Bearer token123")
.header("Accept", "application/json")
.header("User-Agent", "MyApp/1.0")
.body(Full::new(Bytes::from(r#"{"data": "value"}"#)))
.unwrap();
// Access headers
let headers = request.headers();
assert_eq!(headers.get("Content-Type").unwrap(), "application/json");
assert_eq!(headers.get("Authorization").unwrap(), "Bearer token123");
// Multiple values for same header
let request: Request<Full<Bytes>> = Request::builder()
.header("Accept", "application/json")
.header("Accept", "text/html") // Adds second value
.body(Full::new(Bytes::new()))
.unwrap();
// Headers are append by default
let accept_values: Vec<_> = request.headers().get_all("Accept").iter().collect();
assert_eq!(accept_values.len(), 2);
}Request::new creates no headers; builder allows adding headers fluently.
HTTP Version Configuration
use http_body_util::Full;
use hyper::body::Bytes;
use hyper::{Request, Version};
fn version_configuration() {
// Request::new: HTTP/1.1
let request: Request<Full<Bytes>> = Request::new(Full::new(Bytes::new()));
assert_eq!(request.version(), Version::HTTP_11);
// Request::builder: Choose version
let http1_request: Request<Full<Bytes>> = Request::builder()
.version(Version::HTTP_11)
.body(Full::new(Bytes::new()))
.unwrap();
let http2_request: Request<Full<Bytes>> = Request::builder()
.version(Version::HTTP_2)
.body(Full::new(Bytes::new()))
.unwrap();
// Note: HTTP version is often determined by the client/connection
// This setting is for constructing the request object
}Request::new uses HTTP/1.1; builder allows specifying the version.
Body Configuration
use http_body_util::{Empty, Full};
use hyper::body::Bytes;
use hyper::Request;
fn body_configuration() {
// Request::new: Body is the only parameter
let body = Full::new(Bytes::from("request body"));
let request: Request<Full<Bytes>> = Request::new(body);
// Simple, direct body construction
// Request::builder: Body via .body()
let request: Request<Full<Bytes>> = Request::builder()
.method("POST")
.uri("/upload")
.body(Full::new(Bytes::from("file contents")))
.unwrap();
// Empty body
let request: Request<Empty<Bytes>> = Request::builder()
.method("GET")
.uri("/api/data")
.body(Empty::new())
.unwrap();
// Both approaches require a body
// Request::new makes body the sole parameter
// Request::builder makes body the final .body() call
}Both methods require a body; Request::new takes it as the parameter, builder uses .body().
Error Handling
use http_body_util::Full;
use hyper::body::Bytes;
use hyper::Request;
fn error_handling() {
// Request::new: Infallible, returns Request directly
let request: Request<Full<Bytes>> = Request::new(Full::new(Bytes::new()));
// Cannot fail, always returns valid Request
// Request::builder: Returns Result
let result: Result<Request<Full<Bytes>>, _> = Request::builder()
.method("INVALID METHOD") // Invalid method
.body(Full::new(Bytes::new()));
match result {
Ok(request) => println!("Created: {:?}", request.method()),
Err(e) => println!("Error: {}", e), // Invalid method name
}
// Common error cases:
// - Invalid method name
// - Invalid URI
// - Invalid header name or value
// - Missing required components
// Using unwrap for quick examples
let request: Request<Full<Bytes>> = Request::builder()
.method("POST")
.uri("/api")
.body(Full::new(Bytes::new()))
.expect("valid request");
}Request::new cannot fail; builder returns Result for validation errors.
When to Use Request::new
use http_body_util::Full;
use hyper::body::Bytes;
use hyper::Request;
fn when_to_use_new() {
// Simple GET requests with body
let request: Request<Full<Bytes>> = Request::new(Full::new(Bytes::new()));
// Default values are acceptable
// - GET method
// - "/" URI
// - No headers
// - HTTP/1.1
// Quick tests or simple clients
fn simple_client() -> Request<Full<Bytes>> {
Request::new(Full::new(Bytes::from("data")))
}
// When you only care about the body
fn body_only_request(data: Bytes) -> Request<Full<Bytes>> {
Request::new(Full::new(data))
}
// Simple cases where builder would be verbose
// Request::new(body) vs Request::builder().body(body).unwrap()
}Use Request::new for simple requests with acceptable defaults.
When to Use Request::builder
use http_body_util::Full;
use hyper::body::Bytes;
use hyper::{Method, Request, Version};
fn when_to_use_builder() {
// Non-GET methods
let post_request: Request<Full<Bytes>> = Request::builder()
.method(Method::POST)
.body(Full::new(Bytes::from("data")))
.unwrap();
// Custom URIs
let request: Request<Full<Bytes>> = Request::builder()
.uri("/api/v1/users/123/profile")
.body(Full::new(Bytes::new()))
.unwrap();
// Headers required
let request: Request<Full<Bytes>> = Request::builder()
.method(Method::POST)
.uri("/api/data")
.header("Content-Type", "application/json")
.header("Authorization", "Bearer token")
.body(Full::new(Bytes::from(r#"{"key": "value"}"#)))
.unwrap();
// HTTP/2
let request: Request<Full<Bytes>> = Request::builder()
.version(Version::HTTP_2)
.body(Full::new(Bytes::new()))
.unwrap();
// Complex requests requiring multiple configuration steps
}Use Request::builder when you need to configure multiple components.
Incremental Construction
use http_body_util::Full;
use hyper::body::Bytes;
use hyper::{Method, Request};
fn incremental_construction() {
// Builder can be stored and reused
let base_builder = Request::builder()
.method(Method::POST)
.header("Authorization", "Bearer token123");
// Clone and extend for different requests
let users_request: Request<Full<Bytes>> = base_builder
.clone()
.uri("/api/users")
.body(Full::new(Bytes::from(r#"{"name": "Alice"}"#)))
.unwrap();
let posts_request: Request<Full<Bytes>> = base_builder
.clone()
.uri("/api/posts")
.body(Full::new(Bytes::from(r#"{"title": "Hello"}"#)))
.unwrap();
// Builder allows building multiple similar requests
}
fn conditional_headers(content_type: Option<&str>) -> Request<Full<Bytes>> {
let mut builder = Request::builder()
.method(Method::POST)
.uri("/api/data");
// Conditionally add headers
if let Some(ct) = content_type {
builder = builder.header("Content-Type", ct);
}
builder.body(Full::new(Bytes::from("data"))).unwrap()
}Builder can be stored, cloned, and conditionally configured.
Reusable Request Templates
use http_body_util::Full;
use hyper::body::Bytes;
use hyper::{Method, Request};
struct ApiClient {
base_url: String,
auth_token: String,
}
impl ApiClient {
fn create_request(&self, method: Method, path: &str, body: Bytes) -> Request<Full<Bytes>> {
// Template with common headers
Request::builder()
.method(method)
.uri(format!("{}/{}", self.base_url, path))
.header("Authorization", format!("Bearer {}", self.auth_token))
.header("Accept", "application/json")
.header("User-Agent", "MyApiClient/1.0")
.body(Full::new(body))
.unwrap()
}
fn get(&self, path: &str) -> Request<Full<Bytes>> {
self.create_request(Method::GET, path, Bytes::new())
}
fn post(&self, path: &str, body: Bytes) -> Request<Full<Bytes>> {
self.create_request(Method::POST, path, body)
}
}
fn client_usage() {
let client = ApiClient {
base_url: "https://api.example.com".to_string(),
auth_token: "secret".to_string(),
};
let get_request = client.get("users");
let post_request = client.post("users", Bytes::from(r#"{"name": "Bob"}"#));
}Builder pattern enables creating reusable request templates.
Modifying Existing Requests
use http_body_util::Full;
use hyper::body::Bytes;
use hyper::{Method, Request};
fn modify_requests() {
// Request::new creates a complete Request
let mut request: Request<Full<Bytes>> = Request::new(Full::new(Bytes::new()));
// Modify after creation
*request.method_mut() = Method::POST;
*request.uri_mut() = "/api/data".parse().unwrap();
request.headers_mut().insert("Content-Type", "application/json".parse().unwrap());
// This works but is less ergonomic than builder
// Builder creates from scratch
let request: Request<Full<Bytes>> = Request::builder()
.method(Method::POST)
.uri("/api/data")
.header("Content-Type", "application/json")
.body(Full::new(Bytes::new()))
.unwrap();
}Both approaches allow modifying the request, but builder is more ergonomic.
Complete Example: API Request
use http_body_util::Full;
use hyper::body::Bytes;
use hyper::{Method, Request, Version};
fn api_request_example() {
// Simple GET: Request::new is sufficient
let get_request: Request<Full<Bytes>> = Request::new(Full::new(Bytes::new()));
// Complex API request: Builder is appropriate
let api_request: Request<Full<Bytes>> = Request::builder()
.method(Method::POST)
.uri("https://api.example.com/v1/users")
.version(Version::HTTP_11)
.header("Content-Type", "application/json")
.header("Authorization", "Bearer abc123")
.header("Accept", "application/json")
.header("X-Request-ID", "unique-id-123")
.body(Full::new(Bytes::from(
r#"{"name": "Alice", "email": "alice@example.com"}"#
)))
.unwrap();
// Builder makes complex request construction readable
// Each component is clearly specified
}Complex requests benefit from builder's fluent API.
Comparison Summary
use http_body_util::Full;
use hyper::body::Bytes;
use hyper::{Method, Request};
fn comparison() {
// Request::new
// - Takes body as parameter
// - Returns Request directly (no Result)
// - Default: GET, "/", HTTP/1.1, no headers
// - Simple, minimal
// - Use for simple GET requests
let simple: Request<Full<Bytes>> = Request::new(Full::new(Bytes::new()));
// Request::builder
// - Returns Builder, then .body() returns Result
// - Configure method, URI, headers, version
// - Fluent API
// - Can fail with invalid configuration
// - Use for complex requests
let complex: Request<Full<Bytes>> = Request::builder()
.method(Method::POST)
.uri("/api/data")
.header("Content-Type", "application/json")
.body(Full::new(Bytes::from("data")))
.unwrap();
// Lines of code comparison:
// Request::new: 1 line
// Request::builder: N lines for N components
// When defaults are acceptable:
// Request::new(body) is simpler
// When you need configuration:
// Request::builder().method(...).uri(...).header(...).body(...)
}Synthesis
Quick reference:
| Aspect | Request::new |
Request::builder |
|---|---|---|
| Body | Parameter | .body() method |
| Method | GET (default) | Configurable |
| URI | "/" (default) | Configurable |
| Headers | Empty | Configurable |
| Version | HTTP/1.1 | Configurable |
| Return type | Request |
Result<Request, Error> |
| Use case | Simple requests | Complex requests |
When to use each:
use http_body_util::Full;
use hyper::body::Bytes;
use hyper::{Method, Request};
fn usage_guide() {
// Use Request::new when:
// - GET request with no special configuration
// - Default values are acceptable
// - Quick tests or prototypes
// - Simplicity matters
let simple: Request<Full<Bytes>> = Request::new(Full::new(Bytes::new()));
// Use Request::builder when:
// - Non-GET method required
// - Custom URI needed
// - Headers must be set
// - HTTP version matters
// - Building multiple similar requests
let complex: Request<Full<Bytes>> = Request::builder()
.method(Method::POST)
.uri("/api/endpoint")
.header("Authorization", "Bearer token")
.body(Full::new(Bytes::from("data")))
.unwrap();
}Key insight: Request::new and Request::builder represent two approaches to request construction. Request::new(body) is the simplest possible constructionâpass a body, get a request with sensible defaults (GET method, "/" URI, HTTP/1.1, no headers). It cannot fail and returns a complete Request directly. Request::builder() returns a Builder that provides a fluent API for configuring individual request components. Each configuration method (method(), uri(), header(), version()) returns the builder for chaining, and the final .body() call returns Result<Request, Error> because the configuration might be invalid. Use Request::new for simple GET requests or when defaults are acceptable. Use Request::builder for POST/PUT/DELETE methods, custom URIs, required headers, or when building request templates. The builder pattern enables incremental constructionâyou can store a partially-configured builder, clone it, and create multiple requests with common settings. Request::new is for simplicity; Request::builder is for control and flexibility.
