How does http::header::HeaderMap::get_all handle multiple values for the same header name?

HeaderMap::get_all returns a GetAll iterator that yields all values associated with a header name, rather than just the first value that get returns, enabling proper handling of headers like Set-Cookie and Accept that can legitimately appear multiple times in an HTTP message. The http crate's HeaderMap is designed to store multiple values per header name because HTTP allows certain headers to be repeated—the get method returns only the first value for convenience, while get_all provides access to every value in insertion order. This distinction matters because combining multiple values into a single comma-separated string isn't always correct; some headers like Set-Cookie must remain separate, and get_all preserves this structure.

Understanding HTTP Headers with Multiple Values

use http::header::HeaderMap;
 
// HTTP headers can appear multiple times:
// Set-Cookie: session=abc123
// Set-Cookie: theme=dark
// Set-Cookie: lang=en
// 
// Or combined with commas:
// Accept: text/html, application/json, */*
//
// Some headers MUST stay separate (Set-Cookie)
// Some can be combined (Accept, Cache-Control)
 
fn main() {
    let mut headers = HeaderMap::new();
    
    // Multiple Set-Cookie values - must stay separate
    headers.insert("Set-Cookie", "session=abc123".parse().unwrap());
    headers.append("Set-Cookie", "theme=dark".parse().unwrap());
    headers.append("Set-Cookie", "lang=en".parse().unwrap());
    
    // HeaderMap stores all three values
    // How do we access them?
}

HTTP semantics allow multiple values for certain headers, requiring different access patterns.

The get Method Returns First Value Only

use http::header::{HeaderMap, HeaderValue};
 
fn get_vs_get_all() {
    let mut headers = HeaderMap::new();
    
    // Insert first value
    headers.insert("Set-Cookie", "session=abc123".parse().unwrap());
    // Append additional values
    headers.append("Set-Cookie", "theme=dark".parse().unwrap());
    headers.append("Set-Cookie", "lang=en".parse().unwrap());
    
    // get() returns only the first value
    let first = headers.get("Set-Cookie");
    // Returns: Some(&HeaderValue("session=abc123"))
    
    // Problem: We've lost the other two cookies!
    // get() only gives us the first one
    
    match first {
        Some(value) => {
            println!("First cookie: {}", value.to_str().unwrap());
            // Only sees: session=abc123
        }
        None => println!("No Set-Cookie header"),
    }
}

get is convenient for single-value headers but loses information for multi-value headers.

The get_all Method Returns All Values

use http::header::{HeaderMap, HeaderValue};
 
fn get_all_example() {
    let mut headers = HeaderMap::new();
    
    headers.insert("Set-Cookie", "session=abc123".parse().unwrap());
    headers.append("Set-Cookie", "theme=dark".parse().unwrap());
    headers.append("Set-Cookie", "lang=en".parse().unwrap());
    
    // get_all() returns an iterator over all values
    let all_cookies = headers.get_all("Set-Cookie");
    
    // GetAll implements Iterator
    for cookie in all_cookies {
        println!("Cookie: {}", cookie.to_str().unwrap());
    }
    // Output:
    // Cookie: session=abc123
    // Cookie: theme=dark
    // Cookie: lang=en
    
    // All three values are preserved and accessible
}

get_all provides complete access to all values for a header name.

The GetAll Type

use http::header::{HeaderMap, GetAll};
 
fn get_all_type() {
    let mut headers = HeaderMap::new();
    headers.append("Accept", "text/html".parse().unwrap());
    headers.append("Accept", "application/json".parse().unwrap());
    
    // get_all returns GetAll<'_, T>
    let all: GetAll<HeaderValue> = headers.get_all("Accept");
    
    // GetAll is an iterator
    // It yields &HeaderValue references
    
    // Collect into a Vec
    let values: Vec<&HeaderValue> = all.collect();
    assert_eq!(values.len(), 2);
    
    // Or iterate directly
    let all = headers.get_all("Accept");
    for value in all {
        println!("Accept: {}", value.to_str().unwrap());
    }
}

GetAll is an iterator type that yields references to all HeaderValue entries.

Iteration Order

use http::header::HeaderMap;
 
fn iteration_order() {
    let mut headers = HeaderMap::new();
    
    // Values are stored in insertion order
    headers.append("X-Custom", "first".parse().unwrap());
    headers.append("X-Custom", "second".parse().unwrap());
    headers.append("X-Custom", "third".parse().unwrap());
    
    // get_all returns values in insertion order
    let values: Vec<_> = headers.get_all("X-Custom")
        .into_iter()
        .map(|v| v.to_str().unwrap())
        .collect();
    
    assert_eq!(values, vec!["first", "second", "third"]);
    
    // Order is guaranteed to match insertion order
    // This is important for headers where order matters
}

Values are yielded in insertion order, preserving the original header sequence.

Insert vs Append

use http::header::HeaderMap;
 
fn insert_vs_append() {
    let mut headers = HeaderMap::new();
    
    // insert() replaces all existing values
    headers.insert("Set-Cookie", "session=abc".parse().unwrap());
    headers.insert("Set-Cookie", "theme=dark".parse().unwrap());
    
    // Only one value remains - insert replaced the previous
    let count = headers.get_all("Set-Cookie").count();
    assert_eq!(count, 1);
    // get_all yields: "theme=dark"
    
    // append() adds to existing values
    let mut headers = HeaderMap::new();
    headers.append("Set-Cookie", "session=abc".parse().unwrap());
    headers.append("Set-Cookie", "theme=dark".parse().unwrap());
    headers.append("Set-Cookie", "lang=en".parse().unwrap());
    
    // All three values preserved
    let count = headers.get_all("Set-Cookie").count();
    assert_eq!(count, 3);
    
    // get_all yields all three in order
}

insert replaces all values; append adds to existing values, which is why get_all is needed.

Common Multi-Value Headers

use http::header::{HeaderMap, HeaderValue};
 
fn common_multi_value_headers() {
    let mut headers = HeaderMap::new();
    
    // Set-Cookie: Each value is a separate cookie
    // MUST stay separate, not combined
    headers.append("Set-Cookie", "session=abc123; Path=/".parse().unwrap());
    headers.append("Set-Cookie", "preferences=dark; Path=/prefs".parse().unwrap());
    
    // Accept-Encoding: Can be combined or separate
    headers.append("Accept-Encoding", "gzip".parse().unwrap());
    headers.append("Accept-Encoding", "deflate".parse().unwrap());
    // Or combined: "gzip, deflate"
    
    // Accept: Multiple media types
    headers.append("Accept", "text/html".parse().unwrap());
    headers.append("Accept", "application/json".parse().unwrap());
    
    // Cache-Control: Multiple directives
    headers.append("Cache-Control", "no-cache".parse().unwrap());
    headers.append("Cache-Control", "no-store".parse().unwrap());
    
    // Access-Control-Allow-Origin in CORS responses
    // Typically single value, but can have multiple
    
    // Via: Proxy information, multiple hops
    headers.append("Via", "1.1 proxy1.example.com".parse().unwrap());
    headers.append("Via", "1.1 proxy2.example.com".parse().unwrap());
}

Headers like Set-Cookie and Accept commonly have multiple values with different combination rules.

Combining Values for Single-Value Headers

use http::header::{HeaderMap, HeaderValue};
 
fn combining_values() {
    let mut headers = HeaderMap::new();
    
    // Some headers can be combined with commas
    // Accept: text/html, application/json, */*
    
    // Approach 1: Single combined value
    headers.insert("Accept", "text/html, application/json, */*".parse().unwrap());
    
    // Approach 2: Multiple separate values
    headers.clear();
    headers.append("Accept", "text/html".parse().unwrap());
    headers.append("Accept", "application/json".parse().unwrap());
    headers.append("Accept", "*/*".parse().unwrap());
    
    // Both are valid HTTP
    // get_all supports both approaches
    
    // For single-value combined form:
    let combined = headers.get("Accept");
    // Returns: "text/html, application/json, */*"
    
    // For multi-value separate form:
    for value in headers.get_all("Accept") {
        // Each value separately
    }
}

Some headers can be combined with commas; others must stay separate.

Set-Cookie Must Stay Separate

use http::header::{HeaderMap, HeaderValue};
 
fn set_cookie_separate() {
    // Set-Cookie is special: CANNOT be combined
    // Each Set-Cookie header creates one cookie
    // Combining with commas would be incorrect
    
    let mut headers = HeaderMap::new();
    
    // Correct: Separate Set-Cookie headers
    headers.append("Set-Cookie", "session=abc123; HttpOnly".parse().unwrap());
    headers.append("Set-Cookie", "theme=dark; Path=/theme".parse().unwrap());
    
    // Using get_all to retrieve all cookies:
    let cookies: Vec<_> = headers.get_all("Set-Cookie")
        .into_iter()
        .map(|v| v.to_str().unwrap())
        .collect();
    
    // Each cookie is separate:
    // ["session=abc123; HttpOnly", "theme=dark; Path=/theme"]
    
    // Incorrect would be combining:
    // "session=abc123; HttpOnly, theme=dark; Path=/theme"
    // This would be interpreted as a single malformed cookie
}

Set-Cookie must remain separate; combining values would break cookie parsing.

Working with GetAll Iterator

use http::header::{HeaderMap, HeaderValue};
 
fn working_with_getall() {
    let mut headers = HeaderMap::new();
    headers.append("Accept", "text/html".parse().unwrap());
    headers.append("Accept", "application/json".parse().unwrap());
    headers.append("Accept", "*/*".parse().unwrap());
    
    // Iterate directly
    println!("Accept values:");
    for value in headers.get_all("Accept") {
        println!("  {}", value.to_str().unwrap());
    }
    
    // Collect into Vec
    let accepts: Vec<_> = headers.get_all("Accept")
        .into_iter()
        .collect();
    
    // Check count
    let count = headers.get_all("Accept").count();
    assert_eq!(count, 3);
    
    // Find specific value
    let has_json = headers.get_all("Accept")
        .into_iter()
        .any(|v| v.to_str().map(|s| s.contains("json")).unwrap_or(false));
    assert!(has_json);
    
    // Get first and rest
    let mut iter = headers.get_all("Accept").into_iter();
    let first = iter.next();
    let rest: Vec<_> = iter.collect();
}

GetAll implements Iterator, enabling standard iterator methods.

Converting to String

use http::header::{HeaderMap, HeaderValue};
 
fn to_string_handling() {
    let mut headers = HeaderMap::new();
    headers.append("X-Custom", "value1".parse().unwrap());
    headers.append("X-Custom", "value2".parse().unwrap());
    
    // get_all returns GetAll<HeaderValue>
    // Each HeaderValue may not be valid UTF-8
    
    // Safe conversion with to_str()
    for value in headers.get_all("X-Custom") {
        match value.to_str() {
            Ok(s) => println!("Value: {}", s),
            Err(_) => println!("Value contains non-UTF-8"),
        }
    }
    
    // Collect all as strings
    let strings: Vec<_> = headers.get_all("X-Custom")
        .into_iter()
        .filter_map(|v| v.to_str().ok())
        .collect();
    
    // Combine with commas (for headers that support it)
    let combined = headers.get_all("X-Custom")
        .into_iter()
        .filter_map(|v| v.to_str().ok())
        .collect::<Vec<_>>()
        .join(", ");
}

HeaderValue::to_str() handles potential non-UTF-8 values.

Checking for Multiple Values

use http::header::HeaderMap;
 
fn checking_multiple() {
    let mut headers = HeaderMap::new();
    headers.append("X-Single", "value".parse().unwrap());
    
    headers.append("X-Multi", "one".parse().unwrap());
    headers.append("X-Multi", "two".parse().unwrap());
    
    // Check if header has multiple values
    let multi_count = headers.get_all("X-Multi").count();
    if multi_count > 1 {
        println!("X-Multi has {} values", multi_count);
    }
    
    // Single-value header still works with get_all
    let single_count = headers.get_all("X-Single").count();
    assert_eq!(single_count, 1);
    
    // But get() is simpler for single values
    if let Some(value) = headers.get("X-Single") {
        println!("X-Single: {}", value.to_str().unwrap());
    }
}

Use get_all().count() to check for multiple values; use get for single-value convenience.

Performance Considerations

use http::header::HeaderMap;
 
fn performance() {
    let mut headers = HeaderMap::new();
    
    // HeaderMap uses a hash map internally
    // - O(1) average lookup for get()
    // - O(n) for get_all() where n is number of values
    
    // For single-value headers:
    // - get() is most efficient (returns first value)
    // - No iterator allocation
    
    // For multi-value headers:
    // - get_all() creates an iterator
    // - Iterator yields references, no copying
    
    // Memory:
    // - Values are stored in a vector per header name
    // - get_all() iterator is cheap to create
    
    // Recommendation:
    // - Use get() when you know there's only one value
    // - Use get_all() when multiple values are possible
    // - get_all() is still efficient for single values
    
    // Example:
    headers.insert("Content-Type", "application/json".parse().unwrap());
    
    // Single value, get() is slightly more direct
    let content_type = headers.get("Content-Type");
    
    // But get_all() works fine too
    let content_type = headers.get_all("Content-Type").next();
}

get is slightly more efficient for single values; get_all is efficient and correct for all cases.

HeaderName vs &str Lookup

use http::header::{HeaderMap, HeaderName};
 
fn typed_lookup() {
    let mut headers = HeaderMap::new();
    headers.append("Content-Type", "application/json".parse().unwrap());
    
    // Lookup with &str (convenient, parses each time)
    let values = headers.get_all("Content-Type");
    
    // Lookup with HeaderName (pre-parsed, more efficient for repeated use)
    let content_type: HeaderName = "Content-Type".parse().unwrap();
    let values = headers.get_all(&content_type);
    
    // Standard headers have constants
    use http::header::CONTENT_TYPE;
    let values = headers.get_all(CONTENT_TYPE);
    
    // For repeated lookups, HeaderName is more efficient
    // For one-time lookups, &str is convenient
}

Use HeaderName for repeated lookups; &str is convenient for one-time access.

Real-World Example: Cookie Handling

use http::header::HeaderMap;
 
fn handle_cookies() {
    let mut response_headers = HeaderMap::new();
    
    // Server sends multiple Set-Cookie headers
    response_headers.append("Set-Cookie", 
        "session_id=abc123; Path=/; HttpOnly".parse().unwrap());
    response_headers.append("Set-Cookie", 
        "user_prefs=dark_theme; Path=/prefs; Max-Age=3600".parse().unwrap());
    response_headers.append("Set-Cookie", 
        "tracking_id=xyz789; Domain=.example.com".parse().unwrap());
    
    // Client must handle each cookie separately
    for cookie_header in response_headers.get_all("Set-Cookie") {
        let cookie_str = cookie_header.to_str().unwrap();
        
        // Parse each cookie
        let parts: Vec<&str> = cookie_str.split(';').collect();
        let name_value: Vec<&str> = parts[0].split('=').collect();
        
        let name = name_value[0].trim();
        let value = name_value.get(1).map(|s| s.trim()).unwrap_or("");
        
        println!("Setting cookie '{}' = '{}'", name, value);
        
        // Extract attributes like Path, HttpOnly, etc.
        for attr in &parts[1..] {
            println!("  Attribute: {}", attr.trim());
        }
    }
    
    // Output:
    // Setting cookie 'session_id' = 'abc123'
    //   Attribute: Path=/
    //   Attribute: HttpOnly
    // ... etc
}

get_all is essential for properly handling Set-Cookie headers.

Synthesis

When to use get vs get_all:

// Use get() when:
// 1. Header should have only one value (Content-Type, Content-Length)
// 2. You only need the first value
// 3. Convenience outweighs correctness
 
let content_type = headers.get("Content-Type");
 
// Use get_all() when:
// 1. Header can have multiple values (Set-Cookie, Accept)
// 2. You need all values
// 3. Correctness is important
// 4. You're unsure about header behavior
 
for cookie in headers.get_all("Set-Cookie") {
    // Handle each cookie
}

Implementation details:

// HeaderMap stores values as:
// HashMap<HeaderName, Vec<HeaderValue>>
// 
// - Each header name maps to a Vec of values
// - insert() replaces the Vec
// - append() pushes to the Vec
// 
// get() returns: Vec[0].as_ref() (first value)
// get_all() returns: Iterator over Vec
 
// This is why:
// - get() is O(1) for lookup, returns reference to first
// - get_all() is O(1) for iterator creation, O(n) for iteration

Key distinction:

// get(): Returns Option<&HeaderValue>
// - Only first value
// - Returns None if header doesn't exist
// - Use for single-value headers
 
// get_all(): Returns GetAll<HeaderValue>
// - Iterator over all values
// - Returns empty iterator if header doesn't exist
// - Use for multi-value headers
 
// GetAll is an iterator, not Option
// - Can iterate directly even if no values
// - Use .next() to get first value
// - Use .count() to check for multiple values

Key insight: HeaderMap::get_all returns a GetAll iterator that provides access to all values for a header name, preserving insertion order and enabling proper handling of multi-value HTTP headers. This is essential for headers like Set-Cookie where each value must remain separate—combining them with commas would be semantically incorrect. The get method returns only the first value, which is convenient for single-value headers like Content-Type but loses information for multi-value headers. The get_all approach also correctly handles headers that could be either combined (comma-separated in one header line) or separate (multiple header lines): both HTTP representations yield the same values through get_all. When working with headers that might have multiple values, always use get_all to ensure you process all values correctly; use get only when the HTTP specification guarantees a single value or when you explicitly want only the first value.