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 body

URI 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.