Loading page…
Rust walkthroughs
Loading page…
http::Request::new and Request::builder for constructing HTTP requests programmatically?http::Request::new creates a request directly from a body with default method (GET) and URI (/), requiring manual header insertion after construction, while Request::builder provides a fluent API for incrementally setting method, URI, headers, and version before constructing the final request. Request::new is a simple constructor best for minimal requests, whereas Request::builder is a builder pattern that enables method chaining and validates parts before construction. The builder returns a Result because it can fail (e.g., invalid header name), while new returns a Request directly because it only accepts already-valid components.
use http::{Request, Method, Uri, HeaderMap, HeaderName, HeaderValue};
fn basic_new() {
// Create request with just a body
let request: Request<String> = Request::new("Hello".to_string());
// Defaults:
// Method: GET
// URI: /
// Version: HTTP/1.1
// Headers: empty
assert_eq!(request.method(), Method::GET);
assert_eq!(request.uri(), "/");
assert_eq!(request.version(), http::Version::HTTP_11);
assert_eq!(request.headers().len(), 0);
}Request::new creates a request with minimal configuration and defaults.
use http::{Request, Method};
fn basic_builder() {
// Create request using builder
let request: Request<String> = Request::builder()
.method(Method::POST)
.uri("/api/users")
.header("Content-Type", "application/json")
.body(r#"{"name": "Alice"}"#.to_string())
.unwrap();
assert_eq!(request.method(), Method::POST);
assert_eq!(request.uri(), "/api/users");
assert_eq!(request.headers().get("Content-Type").unwrap(), "application/json");
}Request::builder() returns a Builder that chains method calls before finalizing with .body().
use http::{Request, Method, HeaderMap};
fn comparison() {
// Request::new: Simple, minimal
let simple: Request<()> = Request::new(());
// Only sets body, everything else is default
// Request::builder: Flexible, chainable
let built: Request<String> = Request::builder()
.method(Method::POST)
.uri("/api/data")
.header("Authorization", "Bearer token")
.header("Content-Type", "application/json")
.version(http::Version::HTTP_2)
.body("data".to_string())
.unwrap();
// Request::new requires post-hoc modification for anything but body
let mut manual: Request<()> = Request::new(());
manual.headers_mut().insert(
http::header::CONTENT_TYPE,
http::HeaderValue::from_static("text/plain"),
);
*manual.method_mut() = Method::POST;
}Request::new is simpler but requires mutation for configuration; builder sets everything in one chain.
use http::{Request, Method, Uri, HeaderMap, HeaderName, HeaderValue};
fn new_with_components() {
// Request::new takes just the body
// To set method/uri, modify after construction
let mut request: Request<String> = Request::new("body".to_string());
// Modify method
*request.method_mut() = Method::POST;
// Modify URI
*request.uri_mut() = Uri::from_static("/api/endpoint");
// Add headers
request.headers_mut().insert(
HeaderName::from_static("content-type"),
HeaderValue::from_static("application/json"),
);
// This approach requires:
// 1. Creating request
// 2. Mutating each component separately
// 3. No validation until headers are inserted
}Request::new requires explicit mutation to set non-default components.
use http::Request;
fn builder_validation() {
// Builder validates during construction
let valid = Request::builder()
.header("Content-Type", "application/json")
.body(())
.unwrap(); // Success
// Invalid header name causes error
let invalid = Request::builder()
.header("Invalid Header Name", "value") // Space not allowed
.body(());
assert!(invalid.is_err());
// Invalid URI causes error
let invalid_uri = Request::builder()
.uri("not a valid uri ::::")
.body(());
assert!(invalid_uri.is_err());
}Builder returns Result because it validates headers, URI, and other components during construction.
use http::Request;
fn new_never_fails() {
// Request::new always succeeds
// No validation on body content
let request: Request<String> = Request::new("anything".to_string());
// The body type can be anything
let unit_request: Request<()> = Request::new(());
let bytes_request: Request<Vec<u8>> = Request::new(vec
![0, 1, 2]);
let json_request: Request<String> = Request::new(r#"{"key": "value"}"#.to_string());
// No Result, no unwrap needed
// But also no headers, specific method, or custom URI
}Request::new never returns an error because it doesn't validate anything beyond accepting the body.
use http::{Request, HeaderMap, HeaderName, HeaderValue};
fn header_handling() {
// Request::new + manual headers
let mut request1: Request<()> = Request::new(());
request1.headers_mut().insert(
HeaderName::from_static("authorization"),
HeaderValue::from_static("Bearer token"),
);
// Requires HeaderName and HeaderValue types
// Request::builder with string headers
let request2: Request<()> = Request::builder()
.header("Authorization", "Bearer token")
.header("Content-Type", "application/json")
.body(())
.unwrap();
// Builder parses strings into HeaderName/HeaderValue
// Builder handles parsing errors
let invalid = Request::builder()
.header("Invalid Name", "value")
.body(());
assert!(invalid.is_err());
// Manual insertion would panic or require error handling
let mut request3: Request<()> = Request::new(());
// This would compile but panic at runtime with invalid header:
// request3.headers_mut().insert(
// HeaderName::from_static("Invalid Name"), // Panic!
// HeaderValue::from_static("value"),
// );
}Builder parses header strings and returns errors; Request::new requires typed headers.
use http::{Request, Method, Uri};
fn method_and_uri() {
// Request::new approach
let mut request1: Request<()> = Request::new(());
*request1.method_mut() = Method::POST;
*request1.uri_mut() = Uri::from_static("/api/users");
// Three separate operations
// Request::builder approach
let request2: Request<()> = Request::builder()
.method(Method::POST)
.uri("/api/users")
.body(())
.unwrap();
// Single chain, single operation
// Builder also accepts strings that are parsed
let request3: Request<()> = Request::builder()
.method("POST") // Parsed to Method
.uri("/api/users") // Parsed to Uri
.body(())
.unwrap();
// Builder validates method
let invalid = Request::builder()
.method("INVALID METHOD")
.body(());
assert!(invalid.is_err());
}Builder parses strings for method and URI; Request::new requires direct mutation.
use http::{Request, Version};
fn version_setting() {
// Request::new: defaults to HTTP/1.1
let request1: Request<()> = Request::new(());
assert_eq!(request1.version(), Version::HTTP_11);
// Modify version after construction
let mut request2: Request<()> = Request::new(());
*request2.version_mut() = Version::HTTP_2;
// Request::builder: set version in chain
let request3: Request<()> = Request::builder()
.version(Version::HTTP_2)
.body(())
.unwrap();
assert_eq!(request3.version(), Version::HTTP_2);
}Both require explicit setting for non-HTTP/1.1; builder does it inline.
use http::Request;
fn body_types() {
// Request::new: body type is explicit
let string_request: Request<String> = Request::new("body".to_string());
let vec_request: Request<Vec<u8>> = Request::new(vec
![0u8; 100]);
let unit_request: Request<()> = Request::new(());
// Body type inferred from new() argument
// Request::builder: body type from .body() argument
let string_request: Request<String> = Request::builder()
.body("body".to_string())
.unwrap();
let vec_request: Request<Vec<u8>> = Request::builder()
.body(vec
![0u8; 100])
.unwrap();
// Builder can be reused with different body types
let builder = Request::builder()
.method(Method::POST)
.uri("/api");
let req1: Request<String> = builder.clone().body("text".to_string()).unwrap();
let req2: Request<Vec<u8>> = builder.clone().body(vec
![0, 1, 2]).unwrap();
}Both approaches infer body type from the argument; builder allows template reuse.
use http::{Request, Method};
fn builder_reuse() {
// Create a builder template
let base_builder = Request::builder()
.method(Method::POST)
.header("Content-Type", "application/json")
.header("Authorization", "Bearer token");
// Reuse with different bodies
let request1: Request<String> = base_builder.clone()
.uri("/api/users")
.body(r#"{"name": "Alice"}"#.to_string())
.unwrap();
let request2: Request<String> = base_builder.clone()
.uri("/api/posts")
.body(r#"{"title": "Hello"}"#.to_string())
.unwrap();
// Both have same method and headers
assert_eq!(request1.method(), Method::POST);
assert_eq!(request2.method(), Method::POST);
assert_eq!(
request1.headers().get("Authorization"),
request2.headers().get("Authorization")
);
}Builder can be cloned and reused with different URIs or bodies.
use http::Request;
fn parts_access() {
// Both approaches produce the same Request type
let request1: Request<String> = Request::new("body".to_string());
let request2: Request<String> = Request::builder()
.body("body".to_string())
.unwrap();
// Both can be deconstructed into parts
let (parts1, body1) = request1.into_parts();
let (parts2, body2) = request2.into_parts();
// Parts include method, uri, version, headers
assert_eq!(parts1.method, http::Method::GET);
assert_eq!(parts2.method, http::Method::GET);
// Bodies are the same
assert_eq!(body1, "body");
assert_eq!(body2, "body");
}Both produce the same Request type with identical access to parts.
use http::{Request, header::{self, InvalidHeaderValue}};
fn error_handling() {
// Request::new: No errors during construction
// Errors come from header insertion
let mut request: Request<()> = Request::new(());
// HeaderValue::from_str can fail
match HeaderValue::from_str("invalid\nvalue") {
Ok(value) => {
request.headers_mut().insert(header::CONTENT_TYPE, value);
}
Err(e) => {
eprintln!("Invalid header value: {}", e);
}
}
// Request::builder: Errors during .body()
let result = Request::builder()
.header("Invalid\nHeader", "value")
.body(());
match result {
Ok(request) => {
// Use request
}
Err(e) => {
eprintln!("Failed to build request: {}", e);
}
}
// Builder collects all errors, returns on .body()
}Request::new requires error handling per header; builder collects errors at the end.
use http::Request;
fn simple_get() {
// Request::new is ideal for simple GET requests
// Default method is GET, default URI is /
let request: Request<()> = Request::new(());
// This is equivalent to:
// GET / HTTP/1.1
// (no headers)
// (no body)
// For a simple health check:
let health_check: Request<()> = Request::new(());
// Just use the request, all defaults are fine
// Builder would be overkill:
let same_request: Request<()> = Request::builder()
.body(())
.unwrap();
// More verbose for the same result
}Request::new is simpler for minimal requests with defaults.
use http::{Request, Method, header};
fn complex_api_request() {
// Builder is better for requests with many components
let request: Request<String> = Request::builder()
.method(Method::POST)
.uri("https://api.example.com/v1/users")
.version(http::Version::HTTP_2)
.header(header::CONTENT_TYPE, "application/json")
.header(header::AUTHORIZATION, "Bearer token123")
.header(header::ACCEPT, "application/json")
.header("X-Request-ID", "abc-123")
.header("X-API-Key", "secret-key")
.body(r#"{"name": "Alice", "email": "alice@example.com"}"#.to_string())
.unwrap();
// Compare to Request::new approach:
let mut request2: Request<String> = Request::new(
r#"{"name": "Alice", "email": "alice@example.com"}"#.to_string()
);
*request2.method_mut() = Method::POST;
*request2.uri_mut() = "https://api.example.com/v1/users".parse().unwrap();
*request2.version_mut() = http::Version::HTTP_2;
request2.headers_mut().insert(header::CONTENT_TYPE, "application/json".parse().unwrap());
request2.headers_mut().insert(header::AUTHORIZATION, "Bearer token123".parse().unwrap());
// ... more headers
}Builder is more ergonomic for complex requests with many headers.
use http::Request;
fn performance() {
// Request::new: Minimal allocation, direct construction
// Creates Request directly from body
// No intermediate builder state
let request: Request<()> = Request::new(());
// Fast path, minimal overhead
// Request::builder: Creates Builder, then builds Request
// Builder stores method, uri, headers, version
// .body() creates the final Request
let request: Request<()> = Request::builder()
.header("A", "1")
.header("B", "2")
.header("C", "3")
.body(())
.unwrap();
// Overhead:
// 1. Builder struct creation
// 2. Header parsing
// 3. Validation
// 4. Result unwrapping
// For hot paths with simple requests, Request::new is faster
// For complex requests, builder overhead is negligible
}Request::new has less overhead; builder has validation and parsing costs.
use http::Request;
fn memory_layout() {
// Request::new creates Request directly
// Request<T> contains:
// - Method (small, inline)
// - Uri (small struct, may have string allocation)
// - Version (enum, small)
// - HeaderMap (hashmap, allocation)
// - Extensions (small)
// - Body (T)
// Builder stores:
// - Option<Method>
// - Option<Uri>
// - Option<Version>
// - HeaderMap
// - Option<Extensions>
// Then .body() creates Request<T> from these
// Request::new is more direct:
// - Creates HeaderMap (empty)
// - Sets defaults
// - Returns Request
// Builder is indirect:
// - Creates Builder
// - Stores options
// - .body() creates Request from stored options
}Request::new is a direct constructor; builder has intermediate state.
use http::{Request, Method};
fn usage_guidelines() {
// Use Request::new when:
// 1. You only need to set the body
// 2. Defaults (GET, /, HTTP/1.1) are acceptable
// 3. You're in a hot path and need minimal overhead
// 4. You have pre-validated components
let simple: Request<()> = Request::new(());
let with_body: Request<String> = Request::new("data".to_string());
// Use Request::builder when:
// 1. You need to set method, URI, or headers
// 2. You want validation during construction
// 3. You want method chaining for readability
// 4. You're building requests from configuration
// 5. You want to reuse a template
let complex: Request<String> = Request::builder()
.method(Method::POST)
.uri("/api/data")
.header("Authorization", "Bearer token")
.body("payload".to_string())
.unwrap();
// Template reuse:
let template = Request::builder()
.method(Method::POST)
.header("Authorization", "Bearer token");
let req1 = template.clone().uri("/api/users").body("{}".to_string()).unwrap();
let req2 = template.clone().uri("/api/posts").body("{}".to_string()).unwrap();
}Choose based on complexity, validation needs, and reusability requirements.
Key differences:
| Aspect | Request::new | Request::builder |
|--------|---------------|-------------------|
| Configuration | Body only | Method, URI, headers, version |
| Defaults | GET, /, HTTP/1.1 | Same defaults |
| Error handling | No errors | Returns Result |
| Headers | Post-hoc insertion | Inline with builder |
| Validation | None | Validates headers, URI, method |
| Readability | Simple | Chainable, declarative |
| Performance | Minimal overhead | Builder overhead |
| Reusability | No template | Builder can be cloned |
When to use Request::new:
// Simple requests with defaults
let request: Request<()> = Request::new(());
// When you have pre-validated components
let mut request: Request<String> = Request::new(body);
*request.method_mut() = validated_method;
*request.uri_mut() = validated_uri;
// Hot paths where overhead matters
for item in items {
let request: Request<String> = Request::new(item);
// send request
}When to use Request::builder:
// Complex requests with multiple components
let request = Request::builder()
.method(Method::POST)
.uri("/api/users")
.header("Authorization", "Bearer token")
.header("Content-Type", "application/json")
.body(json_body)
.unwrap();
// Building from configuration
let request = Request::builder()
.method(config.method)
.uri(&config.uri)
.headers(config.headers)
.body(config.body)
.unwrap();
// Template reuse
let base = Request::builder().method(Method::POST);
for endpoint in endpoints {
let request = base.clone().uri(&endpoint).body(body.clone()).unwrap();
}Key insight: Request::new is a direct constructor that creates a request with just a body, suitable for simple cases or when you want maximum control and minimal overhead. Request::builder is a builder pattern that provides validation, method chaining, and template reuse, suitable for complex requests where readability and correctness matter more than raw performance. The builder returns a Result because it validates during construction, while new never fails because it accepts the body directly and uses defaults for everything else.