Loading page…
Rust walkthroughs
Loading page…
reqwest::ClientBuilder::http2_initial_stream_window_size affect HTTP/2 flow control behavior?The http2_initial_stream_window_size setting in reqwest::ClientBuilder configures the initial window size for HTTP/2 stream-level flow control, determining how many bytes the client is willing to accept on a single stream before the server must wait for window updates. HTTP/2 flow control prevents fast senders from overwhelming slow receivers—each stream and the connection have separate windows that shrink as data arrives and grow when the receiver sends window update frames. A larger window size allows more data in flight per stream, improving throughput for large transfers but potentially increasing memory usage for buffering. Understanding this setting is essential for tuning HTTP/2 clients handling large responses or many concurrent streams.
use reqwest::Client;
fn basic_client() -> Result<Client, reqwest::Error> {
let client = Client::builder()
.http2_initial_stream_window_size(2 << 20) // 2MB per stream
.build()?;
Ok(client)
}The initial stream window size tells the server how much data it can send before pausing for acknowledgment.
use reqwest::Client;
fn default_window() {
// Default stream window: 65,535 bytes (2^16 - 1)
// Default connection window: 65,535 bytes
// This is small for large file transfers
// Server must pause frequently to wait for window updates
}The HTTP/2 specification sets a default of 65,535 bytes, which may limit throughput.
use reqwest::Client;
async fn throughput_comparison() -> Result<(), Box<dyn std::error::Error>> {
// Small window: more window updates, lower throughput on high-latency connections
let small_window_client = Client::builder()
.http2_initial_stream_window_size(65535) // ~64KB
.build()?;
// Large window: fewer window updates, higher throughput potential
let large_window_client = Client::builder()
.http2_initial_stream_window_size(16 << 20) // 16MB
.build()?;
// With small window and high latency:
// - Client sends WINDOW_UPDATE after 64KB
// - Server waits for WINDOW_UPDATE before sending more
// - Round trip time (RTT) limits throughput
// With large window:
// - More data in flight before waiting
// - Better utilization of available bandwidth
Ok(())
}Larger windows allow more data in flight, reducing the impact of network latency.
use reqwest::Client;
fn stream_vs_connection() -> Result<Client, reqwest::Error> {
let client = Client::builder()
// Stream window: per-stream limit
.http2_initial_stream_window_size(2 << 20) // 2MB per stream
// Connection window: shared across all streams
.http2_initial_connection_window_size(16 << 20) // 16MB total
.build()?;
// Each stream can send up to 2MB
// All streams combined share the 16MB connection window
Ok(client)
}Stream windows are per-stream; connection windows are shared across all streams.
use reqwest::Client;
fn memory_consideration() -> Result<Client, reqwest::Error> {
// Larger window = more potential memory for buffering
// If client is slow to consume data, buffers fill up
let client = Client::builder()
.http2_initial_stream_window_size(4 << 20) // 4MB per stream
.build()?;
// With 10 concurrent streams, potential buffer usage: ~40MB
// If memory is constrained:
let constrained_client = Client::builder()
.http2_initial_stream_window_size(512 << 10) // 512KB per stream
.build()?;
Ok(client)
}Larger windows require more memory for buffering incoming data.
use reqwest::Client;
fn high_latency_optimization() -> Result<Client, reqwest::Error> {
// For high-latency connections (satellite, cross-continental)
// Bandwidth-Delay Product (BDP) = bandwidth * RTT
// Example: 100 Mbps link, 200ms RTT
// BDP = 100 Mbps * 0.2s = 20 Mb = 2.5 MB
// Window should be at least BDP for full utilization
let client = Client::builder()
.http2_initial_stream_window_size(4 << 20) // 4MB, exceeds BDP
.build()?;
// This allows server to send continuously without waiting
// for WINDOW_UPDATE frames over the high-latency link
Ok(client)
}Window size should exceed the bandwidth-delay product for optimal throughput.
use reqwest::Client;
fn concurrent_streams() -> Result<Client, reqwest::Error> {
// Connection window is shared across all streams
// If each stream has 2MB window and 20 streams active
// Total potential data in flight: 40MB
let client = Client::builder()
.http2_initial_stream_window_size(2 << 20) // 2MB per stream
.http2_initial_connection_window_size(40 << 20) // 40MB total
.http2_max_concurrent_streams(20) // Limit concurrent streams
.build()?;
// Matching connection window to (stream window * max streams)
// ensures all streams can use their full window
Ok(client)
}Connection window should accommodate all concurrent streams' windows.
use reqwest::Client;
async fn download_large_file() -> Result<(), Box<dyn std::error::Error>> {
// For large file downloads, larger windows help
let client = Client::builder()
.http2_initial_stream_window_size(16 << 20) // 16MB window
.build()?;
let response = client
.get("https://example.com/large-file.zip")
.send()
.await?;
// With larger window, server can send more data before
// waiting for WINDOW_UPDATE
//
// Process response body...
let bytes = response.bytes().await?;
Ok(())
}Large file downloads benefit from larger stream windows.
use reqwest::Client;
use tokio::io::AsyncReadExt;
async fn stream_response() -> Result<(), Box<dyn std::error::Error>> {
// For streaming, window size affects buffering
let client = Client::builder()
.http2_initial_stream_window_size(1 << 20) // 1MB
.build()?;
let mut response = client
.get("https://example.com/stream")
.send()
.await?;
// If consumer is slow, window fills up
// Server must wait for WINDOW_UPDATE
// Read in chunks to manage flow
let mut buffer = [0u8; 8192];
while let Some(chunk) = response.chunk().await? {
// Process chunk
// WINDOW_UPDATE sent after consuming data
}
Ok(())
}Streaming responses benefit from balanced window sizes.
use reqwest::Client;
fn flow_control_sequence() {
// 1. Client advertises initial window size (e.g., 2MB)
// 2. Server sends data on stream
// 3. Client's window decreases as data arrives
// 4. When window reaches ~50%, client may send WINDOW_UPDATE
// 5. Window increments by the WINDOW_UPDATE value
// 6. Server can send more data
// WINDOW_UPDATE is sent automatically by reqwest when:
// - Data is consumed from the response body
// - Window falls below threshold
// If client doesn't consume data:
// - Window shrinks to 0
// - Server must stop sending
// - Potential deadlock if client is waiting
// Larger initial window = more buffer space before blocking
}Window updates are automatic when response body data is consumed.
use reqwest::Client;
fn window_limits() -> Result<Client, reqwest::Error> {
// HTTP/2 spec: window size is 31-bit unsigned integer
// Maximum: 2^31 - 1 = 2,147,483,647 bytes (~2GB)
// Practical limits based on memory:
// - 65KB default is minimum useful
// - 1-16MB common for high-throughput
// - >16MB rarely beneficial (memory pressure)
let client = Client::builder()
.http2_initial_stream_window_size(16 << 20) // 16MB is practical max
.build()?;
Ok(client)
}Window sizes above a few megabytes have diminishing returns.
use reqwest::Client;
async fn window_update_timing() -> Result<(), Box<dyn std::error::Error>> {
let client = Client::builder()
.http2_initial_stream_window_size(1 << 20) // 1MB
.build()?;
let response = client
.get("https://example.com/data")
.send()
.await?;
// Window starts at 1MB
// Reading 100KB:
let mut buf = [0u8; 102400];
// response.body()...
// After consuming 100KB:
// - Window decreased by 100KB
// - WINDOW_UPDATE sent if below threshold
// - Window restored toward initial size
// If reading is slow:
// - Window may exhaust
// - Server pauses sending
Ok(())
}Window updates occur when response body is consumed.
use reqwest::Client;
async fn compare_window_sizes() -> Result<(), Box<dyn std::error::Error>> {
// Scenario: Download 100MB file over 100ms RTT connection
// Small window (64KB):
// - Server sends 64KB, waits ~100ms for WINDOW_UPDATE
// - ~640KB/s throughput max
// - Many round trips required
// Large window (16MB):
// - Server can send entire file without waiting
// - Full bandwidth utilization
// - One round trip for acknowledgment at end
// Window size should be: data_rate * RTT
// For 100Mbps = 12.5MB/s, RTT = 0.1s
// Ideal window: 12.5MB/s * 0.1s = 1.25MB
let client = Client::builder()
.http2_initial_stream_window_size(2 << 20) // 2MB
.build()?;
Ok(())
}Calculate optimal window size from bandwidth and round-trip time.
use reqwest::Client;
struct ApiClient {
client: Client,
}
impl ApiClient {
fn new() -> Result<Self, reqwest::Error> {
// Balanced window for API calls
// APIs typically return JSON (small responses)
// Multiple concurrent requests common
let client = Client::builder()
.http2_initial_stream_window_size(512 << 10) // 512KB per stream
.http2_initial_connection_window_size(10 << 20) // 10MB total
.build()?;
Ok(Self { client })
}
async fn fetch_user(&self, id: u64) -> Result<User, reqwest::Error> {
self.client
.get(&format!("https://api.example.com/users/{}", id))
.send()
.await?
.json()
.await
}
async fn fetch_all_users(&self, ids: &[u64]) -> Result<Vec<User>, reqwest::Error> {
// Multiple concurrent requests share connection window
let futures: Vec<_> = ids
.iter()
.map(|id| self.fetch_user(*id))
.collect();
// Process concurrently...
todo!()
}
}API clients with many small requests can use smaller windows.
use reqwest::Client;
struct DownloadService {
client: Client,
}
impl DownloadService {
fn new() -> Result<Self, reqwest::Error> {
// Large windows for file downloads
let client = Client::builder()
.http2_initial_stream_window_size(16 << 20) // 16MB per stream
.http2_initial_connection_window_size(128 << 20) // 128MB total
.build()?;
Ok(Self { client })
}
async fn download_file(&self, url: &str) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
let response = self.client
.get(url)
.send()
.await?;
let bytes = response.bytes().await?;
Ok(bytes.to_vec())
}
async fn download_parallel(&self, urls: &[&str]) -> Result<Vec<Vec<u8>>, Box<dyn std::error::Error>> {
// Multiple large downloads in parallel
let futures: Vec<_> = urls
.iter()
.map(|url| self.download_file(url))
.collect();
// Connection window (128MB) shared across downloads
// Stream window (16MB) per download
todo!()
}
}File download services benefit from larger windows for throughput.
use reqwest::Client;
use futures::StreamExt;
struct StreamConsumer {
client: Client,
}
impl StreamConsumer {
fn new() -> Result<Self, reqwest::Error> {
// Moderate window for streaming
// Balance between throughput and memory
let client = Client::builder()
.http2_initial_stream_window_size(1 << 20) // 1MB
.build()?;
Ok(Self { client })
}
async fn consume_stream(&self) -> Result<(), Box<dyn std::error::Error>> {
let response = self.client
.get("https://example.com/stream")
.send()
.await?;
// Process stream chunks as they arrive
// Window updates sent as we consume data
let mut stream = response.bytes_stream();
while let Some(chunk) = stream.next().await {
let data = chunk?;
// Process data
// As we consume, window updates are sent
// Keeping flow control active
}
Ok(())
}
}Streaming applications balance window size with memory constraints.
Window size behavior:
| Window Size | Effect | |-------------|--------| | 64KB (default) | More WINDOW_UPDATE frames, lower throughput on high-RTT | | 1-4MB | Good balance for most workloads | | 16MB+ | High throughput for large files, high memory usage |
Stream vs Connection windows:
| Aspect | Stream Window | Connection Window |
|--------|---------------|-------------------|
| Scope | Per stream | Shared across all streams |
| Config | http2_initial_stream_window_size | http2_initial_connection_window_size |
| Effect | Limits single stream throughput | Limits total throughput |
Key relationships:
| Setting | Relationship | |---------|-------------| | Stream window | Per-stream data limit | | Connection window | ≥ stream window × concurrent streams | | BDP | bandwidth × RTT, should be ≤ window size |
Key insight: http2_initial_stream_window_size controls HTTP/2 flow control at the stream level—larger windows allow more data in flight per stream before the server must pause for window updates, improving throughput on high-latency connections. The optimal window size exceeds the bandwidth-delay product (bandwidth × RTT) to keep the connection saturated. However, larger windows increase memory usage for buffering when the client is slow to consume data. For many small API requests, the default or modest increases (512KB-1MB) are sufficient; for large file downloads or high-throughput streaming, windows of 4-16MB improve performance. The connection window should be sized to accommodate all concurrent streams (stream_window × max_concurrent_streams). Window updates are sent automatically when response body data is consumed, so applications that process data promptly maintain good flow control.