What are the trade-offs between hyper::server::conn::Http and Http2 for protocol-specific server configuration?
Http is a generic connection service that handles both HTTP/1 and HTTP/2 with automatic protocol detection, while Http2 is a specialized service builder that enforces HTTP/2-only connections with explicit configuration for HTTP/2-specific features like prior knowledge, H2C (HTTP/2 cleartext), and stream window sizes. The trade-off is flexibility versus control: Http provides automatic protocol negotiation with sensible defaults, while Http2 gives precise control over HTTP/2 behavior at the cost of HTTP/1 compatibility.
The Connection Service Architecture
use hyper::server::conn::{Http, Http2};
use hyper::{Request, Response, Body};
use tokio::net::TcpListener;
// Both Http and Http2 are "connection service builders"
// They configure how incoming connections are handled
async fn connection_architecture() {
let listener = TcpListener::bind("127.0.0.1:3000").await.unwrap();
let (stream, _addr) = listener.accept().await.unwrap();
// Http handles protocol detection
let http = Http::new();
// Inspects the connection and chooses HTTP/1 or HTTP/2
// Http2 enforces HTTP/2
let http2 = Http2::new();
// Requires HTTP/2, fails otherwise
}Both types are connection builders that configure how hyper handles incoming TCP connections.
Http: The Flexible Protocol Handler
use hyper::server::conn::Http;
use hyper::{Server, Request, Response, Body};
use tokio::net::TcpListener;
async fn http_basic() {
let listener = TcpListener::bind("127.0.0.1:3000").await.unwrap();
// Http::new() creates a connection service with defaults
let http = Http::new();
// It automatically detects HTTP/1 vs HTTP/2:
// - HTTP/1.1: Plain text requests
// - HTTP/2: Via ALPN negotiation (TLS) or HTTP/1 upgrade
// Serve with automatic protocol handling
let server = http.serve_connection(stream, service);
// Benefits:
// - Single code path for both protocols
// - Automatic ALPN negotiation with TLS
// - No need to know protocol in advance
}Http automatically handles both HTTP/1 and HTTP/2 based on client negotiation.
Http2: The Strict HTTP/2 Handler
use hyper::server::conn::Http2;
use hyper::{Server, Request, Response, Body};
use tokio::net::TcpListener;
async fn http2_basic() {
let listener = TcpListener::bind("127.0.0.1:3000").await.unwrap();
// Http2::new() creates a connection service for HTTP/2 only
let http2 = Http2::new();
// Enforces HTTP/2 on the connection
// Useful for:
// - HTTP/2 prior knowledge (no protocol negotiation)
// - HTTP/2 cleartext (h2c without TLS)
// - Strict HTTP/2-only servers
// Serve with HTTP/2 enforcement
let server = http2.serve_connection(stream, service);
// Benefits:
// - Explicit HTTP/2 configuration
// - Prior knowledge mode
// - h2c (HTTP/2 without TLS)
// - Fine-grained HTTP/2 settings
}Http2 enforces HTTP/2 and provides HTTP/2-specific configuration options.
Protocol Detection Behavior
use hyper::server::conn::{Http, Http2};
fn protocol_detection() {
// HTTP/1.1 over plain TCP
// Client sends: "GET / HTTP/1.1\r\n..."
// Http: Detects HTTP/1, handles accordingly
// Http2: Would reject or fail to parse
// HTTP/2 over TLS with ALPN
// Client sends TLS ClientHello with ALPN extension
// Server negotiates "h2" or "http/1.1"
// Http: Respects ALPN result
// Http2: Requires "h2" selection
// HTTP/2 prior knowledge (no negotiation)
// Client sends HTTP/2 connection preface
// Http: Can handle via http2_only flag
// Http2: Always expects this
// HTTP/2 cleartext (h2c)
// Client starts with HTTP/1 upgrade or preface
// Http: Can upgrade from HTTP/1 to HTTP/2
// Http2: Expects direct HTTP/2 preface
// Key difference:
// Http: Negotiates protocol
// Http2: Assumes HTTP/2
}Http negotiates; Http2 assumes HTTP/2 from the start.
Configuration Options Compared
use hyper::server::conn::{Http, Http2};
use std::time::Duration;
fn configuration_options() {
// Http configuration
let http = Http::new()
.http1_only(false) // Allow HTTP/2
.http1_keep_alive(true) // HTTP/1 keep-alive
.http1_title_case_headers(false)
.http1_preserve_header_case(false)
.http2_only(false) // Allow HTTP/1
.http2_initial_stream_window_size(65535)
.http2_initial_connection_window_size(65535)
.http2_adaptive_window(true) // Adaptive flow control
.http2_max_frame_size(16384);
// Http2 configuration (HTTP/2 specific)
let http2 = Http2::new()
.initial_stream_window_size(65535)
.initial_connection_window_size(65535)
.adaptive_window(true)
.max_frame_size(16384)
.max_concurrent_streams(100)
.max_send_buffer_size(1024 * 1024)
.enable_connect_protocol(false);
// Key differences:
// Http: Has http1_* and http2_* methods
// Http2: Only has HTTP/2-specific settings
// Http2-specific options not on Http:
// - enable_connect_protocol (extended CONNECT)
// - Some advanced HTTP/2 settings
}Http has mixed protocol options; Http2 focuses on HTTP/2-specific settings.
HTTP/2 Prior Knowledge
use hyper::server::conn::Http2;
use tokio::net::TcpListener;
// Prior knowledge: Both sides know HTTP/2 will be used
// No protocol negotiation needed
async fn prior_knowledge() {
let listener = TcpListener::bind("127.0.0.1:3000").await.unwrap();
let (stream, _addr) = listener.accept().await.unwrap();
// Client MUST send HTTP/2 connection preface immediately
// Server expects HTTP/2 from the start
let http2 = Http2::new();
// Use Http2 for:
// 1. Known HTTP/2-only clients
// 2. gRPC servers (gRPC requires HTTP/2)
// 3. Internal services with prior knowledge
// 4. HTTP/2 without TLS (h2c)
// With Http, you'd need:
// Http::new().http2_only(true)
// But Http2 is more explicit and cleaner
// Prior knowledge setup requires:
// - Client knows to send HTTP/2 preface
// - No TLS handshake with ALPN
// - No HTTP/1 fallback
}Http2 is designed for prior knowledge scenarios where HTTP/2 is guaranteed.
H2C: HTTP/2 Without TLS
use hyper::server::conn::Http2;
use tokio::net::TcpListener;
// h2c: HTTP/2 over cleartext TCP
// Useful for internal services, development, load balancers
async fn h2c_server() {
let listener = TcpListener::bind("127.0.0.1:3000").await.unwrap();
// For h2c, use Http2
// Client must either:
// 1. Send HTTP/2 connection preface directly
// 2. Start with HTTP/1 and upgrade (h2c upgrade)
let http2 = Http2::new();
// Some clients support h2c:
// - curl --http2-prior-knowledge
// - gRPC clients without TLS
// - Internal microservices
// Why use h2c?
// - Development without cert setup
// - Internal networks (already secure)
// - Behind TLS-terminating load balancers
// - Performance (no TLS overhead)
// Security consideration:
// - h2c sends unencrypted HTTP/2
// - Only use in trusted environments
}Http2 enables h2c servers without TLS overhead.
Window Size Configuration
use hyper::server::conn::{Http, Http2};
fn window_size_config() {
// HTTP/2 uses flow control with window sizes
// Stream window: Per-stream data buffering
// Connection window: Total connection buffering
// Smaller windows: Less memory, more flow control overhead
// Larger windows: More memory, less overhead
// Http configuration
let http = Http::new()
.http2_initial_stream_window_size(65535)
.http2_initial_connection_window_size(65535);
// Http2 configuration
let http2 = Http2::new()
.initial_stream_window_size(65535)
.initial_connection_window_size(65535);
// Adaptive window adjusts dynamically based on usage
let http2_adaptive = Http2::new()
.adaptive_window(true);
// Use cases:
// - Large files: Increase window size
// - Many streams: Decrease per-stream, increase connection
// - Memory-constrained: Smaller windows
}Window sizes control HTTP/2 flow control behavior, affecting memory and throughput.
Max Frame Size
use hyper::server::conn::{Http, Http2};
fn frame_size_config() {
// HTTP/2 splits data into frames (max 2^24 - 1 bytes)
// Smaller frames: More granular, more overhead
// Larger frames: Less overhead, more buffering
// Default frame size: 16384 bytes (2^14)
let http = Http::new()
.http2_max_frame_size(16384); // Default
let http2 = Http2::new()
.max_frame_size(16384);
// Valid range: 16384 to 16777215 (2^14 to 2^24 - 1)
// Larger frame size benefits:
// - Less per-frame overhead
// - Better for large payloads
// Smaller frame size benefits:
// - More responsive interleaving
// - Better for many concurrent streams
// Note: Setting too large may cause compatibility issues
// Most clients expect reasonable frame sizes
}Frame size affects HTTP/2's interleaving behavior and overhead.
Concurrent Streams
use hyper::server::conn::Http2;
fn concurrent_streams() {
// HTTP/2 allows multiple concurrent streams per connection
let http2 = Http2::new()
.max_concurrent_streams(100);
// Controls how many streams can be open simultaneously
// Lower limit:
// - Less resource usage per connection
// - Protection against stream exhaustion
// - Better isolation between clients
// Higher limit:
// - More parallelism per connection
// - Better for clients making many requests
// - More memory per connection
// Default is typically 200 (varies by implementation)
// Consider setting based on:
// - Expected requests per client
// - Server memory capacity
// - Client count
}max_concurrent_streams limits resource usage per connection.
Extended CONNECT Protocol
use hyper::server::conn::Http2;
fn extended_connect() {
// HTTP/2 CONNECT method can be extended for WebSocket
// RFC 8441 defines "Extended CONNECT Protocol"
let http2 = Http2::new()
.enable_connect_protocol(true);
// With extended CONNECT:
// - Client can upgrade to WebSocket over HTTP/2
// - Uses CONNECT with :protocol pseudo-header
// - More efficient than HTTP/1 WebSocket upgrade
// Use case:
// - WebSocket over HTTP/2
// - gRPC-Web with streaming
// - Custom protocols over HTTP/2
// Note: Requires client support for extended CONNECT
// Only available with Http2, not configurable via Http
}Extended CONNECT enables WebSocket over HTTP/2, available only with Http2.
Choosing Between Http and Http2
use hyper::server::conn::{Http, Http2};
fn choosing() {
// Use Http when:
// - Serving to browsers (may use HTTP/1 or HTTP/2)
// - TLS with ALPN negotiation
// - Mixed client support
// - Public internet services
let http = Http::new(); // Flexible, handles both
// Use Http2 when:
// - gRPC servers (require HTTP/2)
// - Prior knowledge setup
// - h2c (HTTP/2 without TLS)
// - Internal microservices with known protocol
// - Extended CONNECT for WebSocket over HTTP/2
let http2 = Http2::new(); // Strict HTTP/2
// Use Http::new().http2_only(true) when:
// - Want HTTP/2 enforcement with Http builder
// - But still need Http's other configuration
let http_strict = Http::new()
.http2_only(true);
// This is similar to Http2 but:
// - Still has http1_* config methods (ignored)
// - Different defaults
// - Different internal handling
}Choose Http for flexibility, Http2 for strict HTTP/2 control.
TLS Integration
use hyper::server::conn::{Http, Http2};
use tokio_rustls::TlsAcceptor;
use tokio::net::TcpListener;
async fn tls_integration() {
let listener = TcpListener::bind("127.0.0.1:3000").await.unwrap();
let tls_acceptor = create_tls_acceptor();
loop {
let (stream, _addr) = listener.accept().await.unwrap();
let tls_stream = tls_acceptor.accept(stream).await.unwrap();
// With TLS and ALPN, Http works well
// Client and server negotiate protocol during TLS handshake
let http = Http::new();
// http.serve_connection(tls_stream, service).await;
// Http2 can also be used with TLS
// But requires client to negotiate "h2" in ALPN
let http2 = Http2::new();
// http2.serve_connection(tls_stream, service).await;
}
}
fn create_tls_acceptor() -> tokio_rustls::TlsAcceptor {
// Configure TLS with ALPN
// ALPN offers: ["h2", "http/1.1"]
// Server selects based on client offer
// Implementation details omitted for brevity
unimplemented!()
}TLS with ALPN negotiation works naturally with Http; Http2 requires HTTP/2 ALPN selection.
Performance Considerations
use hyper::server::conn::{Http, Http2};
fn performance() {
// Http overhead:
// - Protocol detection on each connection
// - Additional branching in hot paths
// - But minimal in practice
// Http2 overhead:
// - No protocol detection
// - Direct HTTP/2 handling
// - Slightly cleaner code paths
// In practice:
// - Difference is minimal for most workloads
// - Protocol detection happens once per connection
// - HTTP/2's multiplexing benefits outweigh detection cost
// Memory considerations:
// - Both use similar HTTP/2 internals when HTTP/2
// - Window sizes affect buffering more than choice
// Choose based on requirements, not performance:
// - Need HTTP/1 support? Use Http
// - HTTP/2 only? Either works, Http2 is clearer
}Performance difference is minimal; choose based on requirements, not benchmarks.
Real-World Example: gRPC Server
use hyper::server::conn::Http2;
use tokio::net::TcpListener;
// gRPC requires HTTP/2
// Using Http2 is the natural choice
async fn grpc_server() {
let listener = TcpListener::bind("127.0.0.1:3000").await.unwrap();
let http2 = Http2::new()
.max_concurrent_streams(100) // Limit concurrent gRPC calls
.initial_stream_window_size(1024 * 1024) // 1MB per stream
.enable_connect_protocol(false); // gRPC doesn't need extended CONNECT
loop {
let (stream, _addr) = listener.accept().await.unwrap();
// gRPC client will send HTTP/2 preface
// Http2 expects this and handles it properly
// No need for protocol detection
// No HTTP/1 support needed
}
}gRPC servers naturally use Http2 since gRPC requires HTTP/2.
Real-World Example: Mixed Protocol Server
use hyper::server::conn::Http;
use tokio::net::TcpListener;
// Public internet server must handle both HTTP/1 and HTTP/2
async fn public_server() {
let listener = TcpListener::bind("127.0.0.1:3000").await.unwrap();
let http = Http::new()
.http1_keep_alive(true) // Enable keep-alive for HTTP/1
.http2_adaptive_window(true); // Adaptive flow control for HTTP/2
loop {
let (stream, _addr) = listener.accept().await.unwrap();
// With TLS + ALPN:
// - Browsers negotiate protocol
// - Http handles whatever client selects
// With plain TCP:
// - Http detects HTTP/1 vs HTTP/2 preface
// - Handles both transparently
}
}Public servers use Http for broad client compatibility.
Summary Table
fn summary_table() {
// | Aspect | Http | Http2 |
// |--------|------|-------|
// | Protocols | HTTP/1 and HTTP/2 | HTTP/2 only |
// | Protocol detection | Automatic | Prior knowledge |
// | h2c support | Via http2_only | Native |
// | HTTP/1 config | Yes | No |
// | Extended CONNECT | No | Yes (enable_connect_protocol) |
// | Use case | General purpose | HTTP/2 specific |
// | Feature | Http | Http2 |
// |---------|------|-------|
// | http1_only | Yes | No |
// | http2_only | Yes | Always |
// | ALPN negotiation | Yes | Requires h2 |
// | Prior knowledge | Partial | Full |
// | Max concurrent streams | Yes | Yes |
// | Window size config | Yes | Yes |
// | Adaptive window | Yes | Yes |
}Synthesis
Quick reference:
use hyper::server::conn::{Http, Http2};
fn quick_reference() {
// Http: Flexible, handles both protocols
let http = Http::new()
.http1_keep_alive(true)
.http2_adaptive_window(true);
// Use for: public servers, TLS with ALPN, mixed clients
// Http2: Strict HTTP/2
let http2 = Http2::new()
.max_concurrent_streams(100)
.initial_stream_window_size(65535)
.enable_connect_protocol(true);
// Use for: gRPC, prior knowledge, h2c, extended CONNECT
// Http::new().http2_only(true) is similar to Http2::new()
// But Http2 provides cleaner HTTP/2-only configuration
}Key insight: Http and Http2 represent different philosophies for HTTP connection handling. Http embraces flexibilityâit negotiates between HTTP/1 and HTTP/2 based on TLS ALPN or connection preface detection, providing a single configuration interface for both protocols. Http2 embraces specificityâit assumes HTTP/2 from the start, enabling prior knowledge setups, h2c (HTTP/2 cleartext), and HTTP/2-specific features like extended CONNECT. Choose Http for public-facing servers where client protocol diversity matters, or Http2 for controlled environments like gRPC services, internal microservices, or when you need explicit HTTP/2-only configuration. The performance difference is negligible; the choice is about API clarity and protocol support guarantees. Both use the same HTTP/2 implementation internally when serving HTTP/2, so window sizes, frame sizes, and other settings behave consistently across both.
