What is the purpose of http::request::Builder::uri for constructing HTTP requests programmatically?
The http::request::Builder::uri method sets the request target (URI) on an HTTP request being constructed through the builder pattern. This method accepts any type that can be converted to Uri—strings, Uri instances, or Parts—and returns the builder for method chaining. The URI specifies the request target: the path, query string, host, and scheme that together tell the server what resource to act upon. Using the builder pattern separates request construction from request execution, allowing URI configuration to be composed with headers, method, and body before the request is finalized.
Basic URI Construction
use http::request::Builder;
fn basic_uri() -> http::Result<http::Request<()>> {
let request = Builder::new()
.uri("https://example.com/api/users")
.body(())?;
Ok(request)
}The uri method accepts a string that gets parsed into a Uri.
Builder Pattern Chaining
use http::{request::Builder, Method};
fn builder_chaining() -> http::Result<http::Request<String>> {
let request = Builder::new()
.method(Method::POST)
.uri("https://api.example.com/users")
.header("Content-Type", "application/json")
.body(r#"{"name": "Alice"}"#.to_string())?;
Ok(request)
}The builder pattern allows composing URI with method, headers, and body.
URI Components
use http::Uri;
fn uri_components() {
let uri: Uri = "https://user:pass@example.com:8080/path?query=value#fragment".parse().unwrap();
// URI components:
println!("Scheme: {:?}", uri.scheme()); // Some("https")
println!("Authority: {:?}", uri.authority()); // Some("user:pass@example.com:8080")
println!("Host: {:?}", uri.host()); // Some("example.com")
println!("Port: {:?}", uri.port_u16()); // Some(8080)
println!("Path: {:?}", uri.path()); // "/path"
println!("Query: {:?}", uri.query()); // Some("query=value")
println!("Fragment: {:?}", uri.fragment()); // Some("fragment")
}A URI contains scheme, authority, path, query, and fragment components.
Setting URI from Parts
use http::{request::Builder, Uri, uri::Parts};
fn uri_from_parts() -> http::Result<http::Request<()>> {
let mut parts = Parts::default();
parts.scheme = Some(http::uri::Scheme::HTTP);
parts.authority = Some("api.example.com".parse().unwrap());
parts.path_and_query = Some("/users?page=1".parse().unwrap());
let uri = Uri::from_parts(parts)?;
let request = Builder::new()
.uri(uri)
.body(())?;
Ok(request)
}Building URIs from parts allows precise control over each component.
Relative vs Absolute URIs
use http::request::Builder;
fn relative_uri() -> http::Result<http::Request<()>> {
// Relative URI (path only)
let request = Builder::new()
.uri("/api/users?id=123")
.body(())?;
// The host must be set separately via Host header
// or through the authority in an absolute URI
Ok(request)
}
fn absolute_uri() -> http::Result<http::Request<()>> {
// Absolute URI includes scheme and authority
let request = Builder::new()
.uri("https://api.example.com/users?id=123")
.body(())?;
// Host is derived from the URI
Ok(request)
}Relative URIs need a host header; absolute URIs include the host.
Query String Construction
use http::request::Builder;
fn with_query_string() -> http::Result<http::Request<()>> {
let request = Builder::new()
.uri("/search?q=rust&category=libraries&page=1")
.body(())?;
// Query string is part of the URI
let uri = request.uri();
println!("Path: {}", uri.path()); // "/search"
println!("Query: {:?}", uri.query()); // Some("q=rust&category=libraries&page=1")
Ok(request)
}Query strings are part of the path-and-query component.
Dynamic URI Construction
use http::request::Builder;
fn dynamic_uri(user_id: u64, action: &str) -> http::Result<http::Request<()>> {
let uri = format!("https://api.example.com/users/{}/{}", user_id, action);
let request = Builder::new()
.uri(&uri)
.body(())?;
Ok(request)
}
fn build_request() -> http::Result<()> {
let request = dynamic_uri(42, "posts")?;
println!("URI: {}", request.uri()); // /users/42/posts
Ok(())
}Dynamic URIs can be constructed using format strings.
URI Encoding
use http::request::Builder;
fn encoded_uri() -> http::Result<http::Request<()>> {
// Special characters must be percent-encoded
let search = "hello world";
let encoded = urlencoding::encode(search);
let uri = format!("/search?q={}", encoded);
let request = Builder::new()
.uri(&uri)
.body(())?;
// URI: /search?q=hello%20world
Ok(request)
}Special characters in URIs must be percent-encoded.
Method and URI Combination
use http::{request::Builder, Method};
fn restful_requests() -> http::Result<()> {
// GET request
let get = Builder::new()
.method(Method::GET)
.uri("/api/users")
.body(())?;
// POST request
let post = Builder::new()
.method(Method::POST)
.uri("/api/users")
.body(r#"{"name": "Bob"}"#)?;
// PUT request
let put = Builder::new()
.method(Method::PUT)
.uri("/api/users/42")
.body(r#"{"name": "Robert"}"#)?;
// DELETE request
let delete = Builder::new()
.method(Method::DELETE)
.uri("/api/users/42")
.body(())?;
Ok(())
}URI varies by HTTP method: GET and DELETE use paths; POST and PUT include bodies.
Host Header Derivation
use http::request::Builder;
fn host_from_uri() -> http::Result<http::Request<()>> {
// When using absolute URI
let request = Builder::new()
.uri("https://api.example.com:8080/users")
.body(())?;
// Host can be extracted from URI authority
if let Some(host) = request.uri().host() {
println!("Host: {}", host);
}
// For relative URIs, Host header must be set explicitly
let request2 = Builder::new()
.uri("/users")
.header("Host", "api.example.com")
.body(())?;
Ok(request)
}Absolute URIs provide host information; relative URIs need Host header.
URI Parsing
use http::Uri;
fn parse_uri() {
let uri: Uri = "https://example.com/path?query=value".parse().unwrap();
// URI parsing extracts all components
assert_eq!(uri.scheme_str(), Some("https"));
assert_eq!(uri.host(), Some("example.com"));
assert_eq!(uri.path(), "/path");
assert_eq!(uri.query(), Some("query=value"));
}URIs are parsed and validated when set via uri().
Path and Query Separation
use http::request::Builder;
fn path_and_query() -> http::Result<http::Request<()>> {
// Path and query combined
let request = Builder::new()
.uri("/users?active=true")
.body(())?;
let uri = request.uri();
println!("Path: {}", uri.path()); // /users
println!("Query: {:?}", uri.query()); // Some("active=true")
// Access query string parameters
if let Some(query) = uri.query() {
for pair in query.split('&') {
if let Some((key, value)) = pair.split_once('=') {
println!("{} = {}", key, value);
}
}
}
Ok(request)
}The path and query are accessible separately from the URI.
Reconstructing URIs
use http::request::Builder;
fn reconstruct_uri() -> http::Result<()> {
let request = Builder::new()
.uri("https://api.example.com:8080/v1/users?page=2&limit=50")
.body(())?;
let uri = request.uri();
// Reconstruct parts
let scheme = uri.scheme_str().unwrap_or("http");
let host = uri.host().unwrap_or("localhost");
let port = uri.port_u16().map(|p| format!(":{}", p)).unwrap_or_default();
let path = uri.path();
let query = uri.query().map(|q| format!("?{}", q)).unwrap_or_default();
let full_url = format!("{}://{}{}{}{}", scheme, host, port, path, query);
println!("Full URL: {}", full_url);
Ok(())
}URI components can be extracted and reconstructed.
Error Handling
use http::request::Builder;
fn invalid_uri_handling() {
// Invalid URI characters
let result = Builder::new()
.uri("/path with spaces") // Spaces are invalid
.body(());
match result {
Ok(_) => println!("Request created"),
Err(e) => println!("Error: {}", e),
}
// Must encode: /path%20with%20spaces
let valid = Builder::new()
.uri("/path%20with%20spaces")
.body(()); // This works
}Invalid URIs cause errors; special characters must be encoded.
Builder Reuse
use http::request::Builder;
fn builder_reuse() -> http::Result<()> {
// Create base builder
let base = Builder::new()
.header("User-Agent", "MyApp/1.0");
// Clone and modify for different endpoints
let users_request = base.clone()
.uri("/api/users")
.body(())?;
let posts_request = base.clone()
.uri("/api/posts")
.body(())?;
// Note: Builder doesn't implement Copy, only Clone
Ok(())
}Builders can be cloned for reuse with different URIs.
URI Modification
use http::{Uri, uri::Parts};
fn modify_uri() -> http::Result<http::Request<()>> {
let original: Uri = "https://example.com/api/v1/users".parse()?;
// Extract parts
let mut parts: Parts = original.into_parts();
// Modify path
parts.path_and_query = Some("/api/v2/users".parse()?);
// Rebuild URI
let new_uri = Uri::from_parts(parts)?;
let request = http::Request::builder()
.uri(new_uri)
.body(())?;
println!("New URI: {}", request.uri());
Ok(request)
}URIs can be modified by extracting and rebuilding parts.
Integration with Hyper
use http::request::Builder;
// When using with hyper or other HTTP libraries
// the URI is used directly in the request
fn hyper_style_request() -> http::Result<http::Request<String>> {
let request = Builder::new()
.method("POST")
.uri("https://httpbin.org/post")
.header("Content-Type", "application/json")
.body(r#"{"test": "data"}"#.to_string())?;
// This request can be sent via hyper::Client
// The URI determines the destination
Ok(request)
}The http crate integrates with hyper and other HTTP libraries.
Real-World Example: API Client Builder
use http::{request::Builder, Method, Uri};
struct ApiClient {
base_url: String,
default_headers: Vec<(String, String)>,
}
impl ApiClient {
fn new(base_url: impl Into<String>) -> Self {
Self {
base_url: base_url.into(),
default_headers: Vec::new(),
}
}
fn with_header(mut self, key: &str, value: &str) -> Self {
self.default_headers.push((key.to_string(), value.to_string()));
self
}
fn build_request(
&self,
method: Method,
path: &str,
query: Option<&str>,
) -> http::Result<http::Request<()>> {
let uri = match query {
Some(q) => format!("{}{}?{}", self.base_url, path, q),
None => format!("{}{}", self.base_url, path),
};
let mut builder = Builder::new()
.method(method)
.uri(&uri);
for (key, value) in &self.default_headers {
builder = builder.header(key, value);
}
builder.body(())
}
fn get(&self, path: &str) -> http::Result<http::Request<()>> {
self.build_request(Method::GET, path, None)
}
fn get_with_query(&self, path: &str, query: &str) -> http::Result<http::Request<()>> {
self.build_request(Method::GET, path, Some(query))
}
}
fn api_client_example() -> http::Result<()> {
let client = ApiClient::new("https://api.example.com")
.with_header("Authorization", "Bearer token")
.with_header("Accept", "application/json");
let request = client.get_with_query("/users", "active=true")?;
println!("URI: {}", request.uri());
Ok(())
}API clients compose base URLs with paths and query strings.
Real-World Example: Pagination
use http::request::Builder;
fn paginated_request(base: &str, endpoint: &str, page: u32, per_page: u32) -> http::Result<http::Request<()>> {
let uri = format!("{}{}?page={}&per_page={}", base, endpoint, page, per_page);
Builder::new()
.uri(&uri)
.body(())
}
fn pagination_example() -> http::Result<()> {
let base = "https://api.example.com";
for page in 1..=3 {
let request = paginated_request(base, "/posts", page, 10)?;
println!("Page {}: {}", page, request.uri());
}
Ok(())
}Pagination uses query parameters in the URI.
Real-World Example: REST Resource Paths
use http::{request::Builder, Method};
struct ResourcePaths;
impl ResourcePaths {
fn users() -> &'static str {
"/users"
}
fn user(id: u64) -> String {
format!("/users/{}", id)
}
fn user_posts(user_id: u64) -> String {
format!("/users/{}/posts", user_id)
}
fn user_post(user_id: u64, post_id: u64) -> String {
format!("/users/{}/posts/{}", user_id, post_id)
}
}
fn rest_example() -> http::Result<()> {
let base = "https://api.example.com";
// List users
let list = Builder::new()
.method(Method::GET)
.uri(&format!("{}{}", base, ResourcePaths::users()))
.body(())?;
// Get specific user
let get = Builder::new()
.method(Method::GET)
.uri(&format!("{}{}", base, ResourcePaths::user(42)))
.body(())?;
// Get user's posts
let posts = Builder::new()
.method(Method::GET)
.uri(&format!("{}{}", base, ResourcePaths::user_posts(42)))
.body(())?;
Ok(())
}REST APIs use URI paths to represent resource hierarchies.
Synthesis
URI components:
| Component | Example | Purpose |
|---|---|---|
| Scheme | https |
Protocol |
| Authority | user:pass@host:port |
Destination |
| Host | api.example.com |
Server address |
| Port | 8080 |
Server port |
| Path | /users/42 |
Resource path |
| Query | ?page=1&limit=10 |
Parameters |
| Fragment | #section |
Page anchor |
Builder chain order:
Builder::new()
.method(Method::POST) // HTTP method
.uri("/api/users") // Request target
.header("Content-Type", "application/json") // Headers
.body("{}") // Request bodyURI types:
| Type | Example | Use Case |
|---|---|---|
| Absolute | https://api.example.com/users |
Full URL with authority |
| Relative | /users?id=42 |
Path with Host header |
| Asterisk | * |
OPTIONS requests |
Key insight: http::request::Builder::uri sets the request target through the builder pattern, accepting any type convertible to Uri (strings, Uri instances, or Parts). The URI specifies where the request should be sent: scheme, host, port, path, and query string. When using absolute URIs, the host is derived from the URI; when using relative URIs, the Host header must be set separately. The builder pattern allows URI configuration to compose with method, headers, and body, creating requests declaratively before execution. For API clients, the URI typically combines a base URL with endpoint paths and query parameters, enabling reusable request construction patterns that separate URL building from request handling.
