Overview
HTTP/2 and HTTP/3 represent significant protocol evolution from HTTP/1.1, addressing performance bottlenecks that emerged as web applications became more complex. HTTP/2, published as RFC 7540 in 2015, introduced binary framing, multiplexing, and header compression while maintaining compatibility with HTTP/1.1 semantics. HTTP/3, standardized as RFC 9114 in 2022, builds on HTTP/2's improvements but replaces TCP with QUIC, a UDP-based transport protocol that eliminates head-of-line blocking at the transport layer.
The shift to HTTP/2 addressed fundamental limitations in HTTP/1.1, where browsers opened multiple TCP connections to achieve parallelism, leading to connection overhead and inefficient resource usage. HTTP/2 multiplexes multiple requests over a single connection, eliminating the need for connection pooling and domain sharding techniques that web developers previously relied on.
HTTP/3 tackles remaining performance issues in HTTP/2, particularly head-of-line blocking at the TCP layer. When a TCP packet is lost, all streams on that connection must wait for retransmission, even if their data has already arrived. By moving to QUIC, HTTP/3 implements stream-level flow control and independent loss recovery, ensuring packet loss affects only the specific stream that experienced it.
HTTP/1.1 Flow:
Request 1 ──────> Response 1
Request 2 ──────> Response 2 (waits for Request 1)
Request 3 ──────> Response 3 (waits for Request 2)
HTTP/2 Flow (single connection):
Request 1 ──┐
Request 2 ──┼──> Multiplexed ──> Responses 1,2,3 interleaved
Request 3 ──┘
HTTP/3 Flow (QUIC streams):
Request 1 ──> Stream 1 (independent recovery)
Request 2 ──> Stream 2 (independent recovery)
Request 3 ──> Stream 3 (independent recovery)
Both protocols maintain HTTP semantics, meaning request methods, status codes, and headers function identically to HTTP/1.1 from an application perspective. The differences lie in how data is framed, transmitted, and managed at the protocol layer.
Key Principles
Binary Framing Layer
HTTP/2 and HTTP/3 use binary framing instead of HTTP/1.1's text-based protocol. In HTTP/1.1, messages consist of readable text with headers separated by newlines and terminated by double newlines. Binary framing encodes messages into discrete frames with specific types and flags.
HTTP/2 defines frame types including HEADERS, DATA, SETTINGS, WINDOW_UPDATE, PRIORITY, RST_STREAM, PUSH_PROMISE, PING, and GOAWAY. Each frame contains a length field, frame type, flags, stream identifier, and payload. The stream identifier associates frames with specific request-response pairs.
HTTP/1.1 Message:
GET /api/users HTTP/1.1\r\n
Host: example.com\r\n
Accept: application/json\r\n
\r\n
HTTP/2 Frame Structure:
+-----------------------------------------------+
| Length (24) |
+---------------+---------------+---------------+
| Type (8) | Flags (8) |
+-+-------------+-------------------------------+
|R| Stream ID (31) |
+=+=============+===============================
| Payload |
+-----------------------------------------------+
Binary framing enables efficient parsing without complex string manipulation or vulnerability to ambiguous message formats. Parsers know exactly where each frame begins and ends based on length fields, eliminating delimiter scanning.
Stream Multiplexing
Multiplexing allows multiple concurrent exchanges on a single connection. Each HTTP request-response pair occurs on a distinct stream, identified by a stream ID. Streams are independent, meaning frames from different streams can be interleaved without blocking.
In HTTP/2, stream IDs are 31-bit integers. Client-initiated streams use odd numbers, server-initiated streams (server push) use even numbers. Stream 0 is reserved for connection-level control frames. Stream IDs must increase monotonically for each endpoint.
# Conceptual stream interleaving
# Stream 1: Request for /page.html
# Stream 3: Request for /style.css
# Stream 5: Request for /script.js
# Frames sent in this order:
HEADERS (stream 1) - /page.html request
HEADERS (stream 3) - /style.css request
HEADERS (stream 5) - /script.js request
DATA (stream 3) - CSS content chunk 1
DATA (stream 1) - HTML content chunk 1
DATA (stream 5) - JS content chunk 1
DATA (stream 3) - CSS content chunk 2
DATA (stream 1) - HTML content chunk 2
# Responses complete independently
Stream prioritization allows clients to indicate resource importance through dependency trees and weights. A stream can depend on another stream, and assigned weights determine bandwidth allocation among siblings. Browsers use prioritization to request critical resources like HTML and CSS before images or analytics scripts.
Header Compression
HPACK (HTTP/2) and QPACK (HTTP/3) compress headers to reduce overhead. HTTP headers are repetitive across requests - headers like User-Agent, Accept-Encoding, and Cookie often remain identical. HPACK uses static and dynamic tables to represent common headers with indexes rather than sending full header strings.
The static table contains 61 common header name-value pairs defined in the specification. The dynamic table stores headers seen during the connection, allowing subsequent references using small integers. Literal headers can use Huffman encoding for additional compression.
First Request:
:method: GET
:path: /index.html
:scheme: https
:authority: example.com
user-agent: Mozilla/5.0...
accept-encoding: gzip, deflate
Encoded with indexes from static table:
:method (2), :path (literal), :scheme (7), :authority (literal),
user-agent (literal, added to dynamic table index 62)
accept-encoding (16)
Second Request (same connection):
:method: GET
:path: /style.css
user-agent: Mozilla/5.0... (same as before)
Encoded:
:method (2), :path (literal), user-agent (62 from dynamic table)
QPACK extends HPACK for HTTP/3, addressing HTTP/2's head-of-line blocking in header decompression. HPACK requires strict ordering - if a frame with dynamic table updates is lost, subsequent frames cannot be decoded. QPACK uses separate unidirectional streams for dynamic table updates, allowing header blocks to reference future table entries with explicit acknowledgment tracking.
Connection Management
HTTP/2 uses a single TCP connection per origin. Connection preface begins with the string "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n" followed by a SETTINGS frame. This preface ensures clear HTTP/2 negotiation and prevents HTTP/1.1 intermediaries from misinterpreting the binary protocol.
HTTP/3 connections establish over QUIC, which provides built-in connection migration. When a client's network changes (mobile device switching from WiFi to cellular), the QUIC connection continues using connection IDs rather than 4-tuple (source IP, source port, destination IP, destination port) identification. This eliminates connection re-establishment delays.
Flow control operates at both connection and stream levels. WINDOW_UPDATE frames grant additional transmission credits. Initial window sizes are negotiated via SETTINGS frames. This prevents fast senders from overwhelming slow receivers and allows per-stream bandwidth allocation.
# HTTP/2 flow control example
# Initial window size: 65,535 bytes
# Stream 1 sends DATA frame: 20,000 bytes
# Stream 1 remaining window: 45,535 bytes
# Client sends WINDOW_UPDATE (stream 1): 20,000
# Stream 1 remaining window: 65,535 bytes
# Connection-level window also needs updates
# Both stream and connection windows must have available credits
QUIC Transport Protocol
HTTP/3 operates over QUIC, a multiplexed transport protocol built on UDP. QUIC combines transport and cryptographic handshakes, establishing connections faster than TCP+TLS. The initial handshake typically completes in one round trip for new connections and zero round trips for resumed connections with cached parameters.
QUIC implements streams natively at the transport layer. Each stream has independent flow control and reliability. Lost packets only affect streams carrying that data, unlike TCP where packet loss blocks all streams on the connection. QUIC retransmits lost data on a new packet number, simplifying loss detection and avoiding retransmission ambiguity present in TCP.
Connection migration allows clients to continue connections after network changes without application-level interruption. QUIC connections use connection IDs negotiated during handshake. When the client's IP address changes, it sends packets with the same connection ID from the new address, and the server migrates the connection.
QUIC Connection Establishment (1-RTT):
Client Server
| |
|-- Initial [ClientHello] ----------->|
| |
|<- Initial [ServerHello] |
| Handshake [Certificate] |
| 1-RTT [HTTP Response] -------------|
| |
|-- Handshake [Finished] ------------->|
| 1-RTT [HTTP Request] -------------->|
Compare to TCP+TLS (2-RTT):
Client Server
| |
|-- TCP SYN -----------------------> |
|<- TCP SYN-ACK -------------------| |
|-- TCP ACK -----------------------> |
|-- TLS ClientHello ---------------> |
|<- TLS ServerHello, Certificate --| |
|-- TLS Finished ------------------> |
|-- HTTP Request ------------------> |
Implementation Approaches
Protocol Negotiation
Servers must support protocol negotiation to enable HTTP/2 and HTTP/3 clients while maintaining backward compatibility. HTTP/2 negotiation uses Application-Layer Protocol Negotiation (ALPN), a TLS extension where clients indicate supported protocols during the handshake. Servers select a protocol and inform the client in the ServerHello message.
For unencrypted connections, HTTP/2 supports an upgrade mechanism where clients send HTTP/1.1 requests with an "Upgrade: h2c" header. The server responds with 101 Switching Protocols to accept or continues with HTTP/1.1. However, browsers only support HTTP/2 over TLS, making the upgrade mechanism primarily relevant for server-to-server communication.
HTTP/3 discovery typically uses Alternative Services (Alt-Svc). Servers advertise HTTP/3 availability through an Alt-Svc response header or ALTSVC frame in HTTP/2. Clients then establish a QUIC connection to the advertised endpoint while maintaining the existing HTTP/1.1 or HTTP/2 connection as a fallback.
HTTP/2 ALPN Negotiation:
ClientHello:
Supported protocols: h2, http/1.1
ServerHello:
Selected protocol: h2
HTTP/3 Discovery via Alt-Svc:
HTTP/1.1 Response:
Alt-Svc: h3=":443"; ma=3600
Client attempts QUIC connection to example.com:443
If successful, subsequent requests use HTTP/3
If QUIC blocked, falls back to HTTP/1.1 or HTTP/2
Server Push
HTTP/2 introduced server push, allowing servers to send resources before clients request them. Servers send PUSH_PROMISE frames to notify clients of pushed resources, followed by response headers and data on new streams. Clients can reject pushes with RST_STREAM if resources are already cached.
Server push aims to eliminate round trips for predictable resource dependencies. When serving HTML that references CSS and JavaScript files, servers can push those resources immediately rather than waiting for the client to parse HTML and request them. However, push requires careful cache awareness - pushing already-cached resources wastes bandwidth.
HTTP/3 initially included server push but removed it in RFC 9114 due to limited adoption and implementation complexity. Servers can achieve similar results using Link headers with rel=preload, informing browsers of critical resources without actually pushing data.
# HTTP/2 server push sequence
# Client requests /index.html
# Server sends:
HEADERS (stream 1) - response to /index.html request
PUSH_PROMISE (stream 1, promised stream 2) - /style.css
PUSH_PROMISE (stream 1, promised stream 4) - /script.js
DATA (stream 1) - HTML content
HEADERS (stream 2) - response headers for /style.css
DATA (stream 2) - CSS content
HEADERS (stream 4) - response headers for /script.js
DATA (stream 4) - JS content
# Client receives all resources without additional requests
# If client already had /style.css cached:
RST_STREAM (stream 2) - reject the push
Prioritization Strategies
HTTP/2 prioritization uses dependency trees where streams can depend on others. Each stream has a weight (1-256) determining bandwidth share among siblings. Browsers construct prioritization trees reflecting resource importance - critical rendering path resources receive higher priority.
Stream dependencies form a tree structure. When creating a stream, clients specify a parent stream ID and whether the new stream is exclusive. Exclusive dependencies insert the new stream between parent and children, making existing children depend on the new stream. This allows dynamic priority adjustments as new requests are made.
HTTP/3 abandoned HTTP/2's dependency-based prioritization due to implementation complexity and limited server support. RFC 9218 defines an extensible priority scheme using PRIORITY_UPDATE frames and Priority header fields. Clients indicate urgency (0-7) and incremental (boolean) parameters, providing simpler priority semantics.
HTTP/2 Priority Tree Example:
Stream 0 (connection)
|
Stream 1 (HTML) weight: 16
|
+----+----+
| |
Stream 3 Stream 5
(CSS) (JS)
weight:16 weight:16
Bandwidth allocation:
HTML receives 100% until complete
Then CSS and JS split equally (50% each)
Adding exclusive dependency:
Request Stream 7 (critical image)
exclusive dependency on Stream 1
New tree:
Stream 0
|
Stream 1
|
Stream 7
|
+----+----+
Stream 3 Stream 5
Connection Coalescing
HTTP/2 and HTTP/3 allow connection coalescing, where a single connection serves multiple origins sharing a certificate. When a client has a connection to example.com, it can reuse that connection for www.example.com if the server's certificate covers both domains and the server announces support via ORIGIN frames.
Connection coalescing reduces connection overhead when organizations serve multiple domains from the same infrastructure. Instead of establishing separate connections for cdn.example.com, api.example.com, and www.example.com, clients use one connection if the server's certificate and ORIGIN frames permit.
DNS resolution influences coalescing. Clients check that origins resolve to the same IP address before coalescing. This prevents connection hijacking where attackers redirect DNS to malicious servers presenting valid certificates for other domains.
# Connection coalescing scenario
# Client connects to https://example.com
# Server certificate covers: example.com, www.example.com, cdn.example.com
# Server sends ORIGIN frame:
ORIGIN
Origin: https://www.example.com
Origin: https://cdn.example.com
# Client can now send requests to all three origins on this connection
# Request to www.example.com/page.html → uses existing connection
# Request to cdn.example.com/image.png → uses existing connection
# Request to other.com → requires new connection
Performance Considerations
Latency Reduction
HTTP/2 reduces latency through multiplexing, eliminating the round-trip delays caused by serial HTTP/1.1 requests. In HTTP/1.1, browsers open multiple connections (typically 6 per origin) to achieve parallelism, but this approach has limits and creates connection management overhead. HTTP/2 allows unlimited concurrent streams on a single connection.
Header compression significantly reduces request and response sizes. Typical web pages include large Cookie headers and repetitive request headers across resources. HPACK compression achieves 70-85% header size reduction, particularly impactful on high-latency mobile networks where every byte transmitted adds measurable delay.
HTTP/3's QUIC foundation eliminates head-of-line blocking at the transport layer. In HTTP/2 over TCP, a single lost packet blocks all streams on that connection until the packet is retransmitted and acknowledged. With QUIC, lost packets only affect the specific stream, allowing other streams to continue delivering data.
# Latency comparison (100ms RTT network)
# HTTP/1.1 (6 connections, 10 resources)
# Resource 1-6: 100ms (first round trip)
# Resource 7-10: 200ms (wait for connection availability)
# Total: ~200ms for all resources
# HTTP/2 (1 connection, 10 resources)
# All resources: 100ms (single round trip)
# Total: ~100ms for all resources
# HTTP/3 with 1% packet loss
# HTTP/2: Lost packet blocks all streams
# Recovery time: 100ms (RTT for retransmit) affects all streams
# HTTP/3: Lost packet blocks only affected stream
# Other 9 streams continue without delay
Connection Establishment
QUIC's integrated cryptographic handshake reduces connection establishment time. TCP requires a 3-way handshake before TLS negotiation begins. TLS 1.2 needs two round trips, while TLS 1.3 optimizes to one round trip. QUIC combines transport and cryptographic handshakes into a single round trip for new connections.
Resumed connections achieve zero-round-trip (0-RTT) handshakes using pre-shared keys from previous connections. Clients send encrypted application data in the first packet, allowing immediate request transmission. This dramatically improves performance for repeat visitors, particularly on high-latency mobile networks.
HTTP/2 connection reuse amortizes establishment costs across multiple requests. Applications maintaining persistent connections see improved performance compared to HTTP/1.1's connection churn. Connection pooling becomes simpler since a single connection per origin suffices.
Connection Establishment Timing:
HTTP/1.1 + TLS 1.2: 3 RTTs
- TCP handshake: 1 RTT
- TLS handshake: 2 RTTs
HTTP/1.1 + TLS 1.3: 2 RTTs
- TCP handshake: 1 RTT
- TLS handshake: 1 RTT
HTTP/2 + TLS 1.3: 2 RTTs
(Same as HTTP/1.1 + TLS 1.3)
HTTP/3 (QUIC) new connection: 1 RTT
- Combined handshake: 1 RTT
HTTP/3 (QUIC) resumed: 0 RTT
- Encrypted data in first packet
Bandwidth Utilization
HTTP/2 improves bandwidth utilization through stream prioritization and flow control. Servers can allocate bandwidth according to resource priority, sending critical CSS before optional analytics. Flow control prevents buffer bloat where fast servers overwhelm slow clients.
Header compression reduces bandwidth consumption, particularly important for mobile connections with limited bandwidth and metered data. Compressing a 2KB header down to 300 bytes saves bandwidth across hundreds of requests per page load.
HTTP/3 handles loss recovery more efficiently than TCP. QUIC retransmits lost data with new packet numbers, eliminating retransmission ambiguity. When TCP retransmits, it cannot distinguish ACKs for original packets from ACKs for retransmissions, complicating congestion control. QUIC always knows which transmission was acknowledged.
# Bandwidth utilization example
# Page with 1 HTML (50KB), 5 CSS (100KB total), 20 images (2MB total)
# HTTP/1.1 strategy:
# Send all CSS first (critical rendering path)
# Then send images
# 6 parallel connections partially helps
# HTTP/2 strategy with prioritization:
# HTML: highest priority (16)
# CSS: high priority (12)
# Images: lower priority (8)
# Server allocates bandwidth proportionally
# Critical resources complete faster
# Flow control prevents issues:
# Slow client with 1Mbps connection
# Fast server with 1Gbps connection
# Without flow control: server buffers millions of bytes
# With flow control: server sends only what client can receive
Resource Overhead
HTTP/2 reduces connection overhead by using fewer TCP connections. Each TCP connection consumes memory for send/receive buffers, congestion control state, and connection tracking. Reducing from 6-8 connections per origin to 1 connection decreases memory consumption on both client and server.
Server push can waste bandwidth if pushed resources are already cached. Browsers cannot inform servers about cache state before receiving PUSH_PROMISE frames. Some implementations mitigate this by tracking pushed resources using cookies or cache digests, but these approaches add complexity.
QUIC requires more CPU for cryptographic operations since all packets are encrypted, not just the handshake. However, hardware acceleration for AES-GCM and ChaCha20-Poly1305 makes this overhead manageable on modern systems. The CPU cost is offset by reduced connection establishment overhead and improved loss recovery.
# Memory comparison (approximate)
# Per TCP connection: 100-200KB buffers + tracking data
# HTTP/1.1 approach:
# 6 connections to cdn.example.com
# 6 connections to api.example.com
# 6 connections to www.example.com
# Total: 18 connections = 1.8-3.6MB
# HTTP/2 approach (with coalescing):
# 1 connection serving all three origins
# Total: 1 connection = 100-200KB
# Savings: 1.7-3.4MB per client
Ruby Implementation
HTTP/2 Client Libraries
Ruby provides several HTTP/2 client libraries. The http-2 gem offers a pure Ruby implementation of the HTTP/2 protocol. The httpx gem supports both HTTP/2 and HTTP/3 with a focus on performance and modern Ruby features.
require 'http/2'
client = HTTP2::Client.new
client.on(:frame) do |bytes|
# Send binary frame data over socket
socket.write(bytes)
end
stream = client.new_stream
stream.on(:close) do
puts "Stream closed"
end
stream.on(:headers) do |headers|
puts "Response headers: #{headers}"
end
stream.on(:data) do |data|
puts "Response data: #{data}"
end
# Send request
headers = {
':scheme' => 'https',
':method' => 'GET',
':path' => '/api/users',
':authority' => 'example.com'
}
stream.headers(headers, end_stream: true)
# Receive frames from socket
loop do
data = socket.read(1024)
client << data
end
The httpx library provides a higher-level interface with automatic connection management:
require 'httpx'
# HTTP/2 request (automatically negotiates protocol)
response = HTTPX.get("https://example.com/api/data")
puts response.status
puts response.body.to_s
# Concurrent requests over single HTTP/2 connection
responses = HTTPX.get(
"https://example.com/api/users",
"https://example.com/api/posts",
"https://example.com/api/comments"
)
responses.each do |response|
puts "#{response.uri}: #{response.status}"
end
HTTP/3 Support
HTTP/3 support in Ruby is emerging. The httpx gem includes experimental HTTP/3 support built on the quic-go library through FFI bindings. Applications must explicitly enable HTTP/3 and handle fallback scenarios.
require 'httpx'
# Enable HTTP/3 with fallback
http = HTTPX.plugin(:retries).with(http3: true)
begin
response = http.get("https://example.com/api/data")
# Check negotiated protocol
if response.headers['alt-svc']
puts "Server supports HTTP/3: #{response.headers['alt-svc']}"
end
puts "Protocol used: #{response.version}"
puts "Response: #{response.body.to_s}"
rescue HTTPX::Error => e
# Handle connection failures, potentially retrying over HTTP/2
puts "Request failed: #{e.message}"
end
Server Implementation
Puma, a popular Ruby web server, supports HTTP/2 when running with TLS. Applications must configure TLS certificates and enable HTTP/2 in server settings. Puma handles protocol negotiation via ALPN automatically.
# config/puma.rb
port ENV.fetch('PORT', 3000)
# Enable TLS with HTTP/2
if ENV['ENABLE_HTTP2']
ssl_bind '0.0.0.0', '3001',
key: 'config/ssl/server.key',
cert: 'config/ssl/server.crt',
verify_mode: 'none'
# HTTP/2 enabled automatically with TLS
end
workers ENV.fetch('WORKERS', 4)
threads 1, 5
Rails applications work transparently over HTTP/2. The application code remains unchanged since HTTP/2 maintains HTTP semantics. Response headers and body generation work identically:
# app/controllers/api_controller.rb
class ApiController < ApplicationController
def index
# This code works identically over HTTP/1.1, HTTP/2, and HTTP/3
@users = User.includes(:posts).limit(20)
# Set cache headers for HTTP/2 push hints
response.headers['Link'] = '</assets/application.css>; rel=preload; as=style'
render json: @users
end
end
Connection Management
Ruby HTTP clients must manage connection pooling differently for HTTP/2. Traditional connection pools maintain multiple connections per host, but HTTP/2 requires only one connection. The httpx gem handles this automatically:
require 'httpx'
# Create session with connection reuse
session = HTTPX.plugin(:persistent)
# Multiple requests reuse same HTTP/2 connection
10.times do |i|
session.get("https://example.com/api/resource/#{i}") do |response|
puts "Request #{i}: #{response.status}"
end
end
# Explicitly close connections when done
session.close
Applications must handle connection failures gracefully. HTTP/2 connections can fail due to network issues, server restarts, or protocol errors. Retry logic should re-establish connections and resend failed requests:
require 'httpx'
def fetch_with_retry(url, max_retries: 3)
retries = 0
begin
HTTPX.get(url, timeout: { operation_timeout: 5 })
rescue HTTPX::Error => e
retries += 1
if retries <= max_retries
sleep(2 ** retries) # Exponential backoff
retry
else
raise
end
end
end
response = fetch_with_retry("https://example.com/api/data")
puts response.body.to_s
Header Manipulation
Ruby applications can set custom headers that influence HTTP/2 behavior. Server push hints use Link headers with rel=preload:
class ApplicationController < ActionController::Base
before_action :set_push_headers
private
def set_push_headers
# Hint server to push critical resources
push_headers = [
'</assets/application.css>; rel=preload; as=style',
'</assets/application.js>; rel=preload; as=script',
'</assets/logo.png>; rel=preload; as=image'
].join(', ')
response.headers['Link'] = push_headers
end
end
Setting Alt-Svc headers advertises HTTP/3 availability:
class ApplicationController < ActionController::Base
before_action :advertise_http3
private
def advertise_http3
if ENV['HTTP3_ENABLED']
# Advertise HTTP/3 on same host
response.headers['Alt-Svc'] = 'h3=":443"; ma=3600'
end
end
end
Performance Monitoring
Applications should monitor protocol usage and performance metrics. Logging the negotiated protocol helps track adoption:
require 'httpx'
class MetricsMiddleware
def initialize(app)
@app = app
end
def call(env)
start_time = Time.now
protocol = env['SERVER_PROTOCOL'] # "HTTP/2.0" or "HTTP/1.1"
status, headers, body = @app.call(env)
duration = Time.now - start_time
# Log metrics
Rails.logger.info({
protocol: protocol,
method: env['REQUEST_METHOD'],
path: env['PATH_INFO'],
duration: duration,
status: status
}.to_json)
[status, headers, body]
end
end
Tracking multiplexed stream counts in HTTP/2 connections:
require 'http/2'
class HTTP2ConnectionManager
attr_reader :active_streams
def initialize
@client = HTTP2::Client.new
@active_streams = 0
@client.on(:stream) do |stream|
@active_streams += 1
stream.on(:close) do
@active_streams -= 1
update_metrics
end
end
end
def new_request(headers)
stream = @client.new_stream
stream.headers(headers, end_stream: true)
stream
end
private
def update_metrics
# Report to monitoring system
Metrics.gauge('http2.active_streams', @active_streams)
end
end
Integration & Interoperability
Reverse Proxy Configuration
Applications commonly deploy behind reverse proxies that handle HTTP/2 and HTTP/3 termination. Nginx supports both protocols with appropriate configuration:
server {
listen 443 ssl http2;
listen 443 quic reuseport;
http2_max_concurrent_streams 128;
http2_max_field_size 16k;
http2_max_header_size 32k;
# HTTP/3 support
http3 on;
http3_max_concurrent_streams 128;
# Advertise HTTP/3
add_header Alt-Svc 'h3=":443"; ma=86400';
ssl_certificate /path/to/cert.pem;
ssl_certificate_key /path/to/key.pem;
ssl_protocols TLSv1.2 TLSv1.3;
location / {
proxy_pass http://ruby_backend;
proxy_http_version 1.1;
# Preserve original protocol information
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
upstream ruby_backend {
server 127.0.0.1:3000;
keepalive 32;
}
Ruby applications receive HTTP/1.1 requests from the proxy but can detect the original protocol through custom headers:
class ApplicationController < ActionController::Base
def client_protocol
# Nginx sets custom header indicating frontend protocol
request.headers['X-Protocol-Version'] || 'HTTP/1.1'
end
def using_http2?
client_protocol == 'HTTP/2.0'
end
def using_http3?
client_protocol == 'HTTP/3.0'
end
end
CDN Integration
Content Delivery Networks provide HTTP/2 and HTTP/3 support at edge locations. Applications must consider how protocol features interact with CDN caching:
class AssetsController < ApplicationController
def show
asset = Asset.find(params[:id])
# Set aggressive caching for CDN
expires_in 1.year, public: true
# Add push hints for HTTP/2
if using_http2?
related_assets = asset.related_assets.limit(5)
push_links = related_assets.map do |a|
"</assets/#{a.id}>; rel=preload; as=#{a.content_type}"
end
response.headers['Link'] = push_links.join(', ')
end
send_file asset.file_path,
type: asset.content_type,
disposition: 'inline'
end
end
Browser Compatibility
All modern browsers support HTTP/2. HTTP/3 support varies but is available in Chrome, Edge, Firefox, and Safari. Applications should provide fallback mechanisms:
class ApiController < ApplicationController
def index
# Detect protocol capabilities
protocol = request.headers['X-Protocol-Version']
# Adjust response based on protocol
case protocol
when 'HTTP/3.0', 'HTTP/2.0'
# Can rely on multiplexing, send all resources
render json: {
users: User.includes(:posts, :comments).all,
metadata: fetch_metadata
}
else
# HTTP/1.1, send minimal response
render json: {
users: User.limit(20).all,
metadata_url: api_metadata_path
}
end
end
end
Load Balancing Considerations
Load balancers must handle HTTP/2 and HTTP/3 connection persistence. Since HTTP/2 uses single connections with multiple streams, connection-based load balancing can create imbalanced load distribution:
# HAProxy configuration for HTTP/2
frontend https_frontend
bind *:443 ssl crt /path/to/cert.pem alpn h2,http/1.1
# Use request-based load balancing, not connection-based
default_backend ruby_servers
backend ruby_servers
balance roundrobin
# Enable HTTP/2 to backend if supported
server ruby1 127.0.0.1:3000 check
server ruby2 127.0.0.1:3001 check
server ruby3 127.0.0.1:3002 check
Applications can implement health checks that report stream load:
class HealthController < ApplicationController
def show
metrics = {
status: 'healthy',
active_connections: $connection_pool.active_connections,
http2_streams: $http2_manager&.active_streams || 0,
memory_usage: memory_usage_mb
}
render json: metrics
end
private
def memory_usage_mb
`ps -o rss= -p #{Process.pid}`.to_i / 1024
end
end
WebSocket Compatibility
WebSocket connections cannot multiplex over HTTP/2 or HTTP/3. The WebSocket protocol uses its own framing and operates independently. Applications using WebSocket maintain separate connections:
# Action Cable configuration
class Connection < ActionCable::Connection::Base
identified_by :current_user
def connect
# WebSocket connection established separately from HTTP/2
# Protocol: HTTP/1.1 → WebSocket upgrade
self.current_user = find_verified_user
end
private
def find_verified_user
verified_user = User.find_by(id: cookies.signed[:user_id])
reject_unauthorized_connection unless verified_user
verified_user
end
end
WebTransport, built on HTTP/3, provides an alternative to WebSocket with better multiplexing. However, Ruby support is currently limited.
Reference
Protocol Comparison
| Feature | HTTP/1.1 | HTTP/2 | HTTP/3 |
|---|---|---|---|
| Transport | TCP | TCP | QUIC (UDP) |
| Message Format | Text | Binary | Binary |
| Multiplexing | No | Yes | Yes |
| Header Compression | No | HPACK | QPACK |
| Server Push | No | Yes | No |
| Connection Per Origin | 6-8 | 1 | 1 |
| Head-of-Line Blocking | N/A | Transport Layer | None |
| Connection Migration | No | No | Yes |
| 0-RTT Handshake | No | No | Yes |
Frame Types (HTTP/2)
| Frame Type | Code | Purpose |
|---|---|---|
| DATA | 0x0 | Conveys request/response body data |
| HEADERS | 0x1 | Transmits header fields |
| PRIORITY | 0x2 | Specifies stream priority |
| RST_STREAM | 0x3 | Terminates a stream |
| SETTINGS | 0x4 | Negotiates connection parameters |
| PUSH_PROMISE | 0x5 | Notifies peer of pushed resources |
| PING | 0x6 | Measures RTT and check connection |
| GOAWAY | 0x7 | Initiates connection shutdown |
| WINDOW_UPDATE | 0x8 | Updates flow control window |
| CONTINUATION | 0x9 | Continues header block fragments |
SETTINGS Parameters
| Setting | ID | Default | Description |
|---|---|---|---|
| HEADER_TABLE_SIZE | 0x1 | 4096 | Maximum header compression table size |
| ENABLE_PUSH | 0x2 | 1 | Enables server push |
| MAX_CONCURRENT_STREAMS | 0x3 | unlimited | Maximum concurrent streams |
| INITIAL_WINDOW_SIZE | 0x4 | 65535 | Initial flow control window size |
| MAX_FRAME_SIZE | 0x5 | 16384 | Maximum frame payload size |
| MAX_HEADER_LIST_SIZE | 0x6 | unlimited | Maximum header list size |
Error Codes
| Error | Code | Description |
|---|---|---|
| NO_ERROR | 0x0 | Graceful shutdown |
| PROTOCOL_ERROR | 0x1 | Protocol violation detected |
| INTERNAL_ERROR | 0x2 | Internal implementation error |
| FLOW_CONTROL_ERROR | 0x3 | Flow control limits exceeded |
| SETTINGS_TIMEOUT | 0x4 | Settings ACK not received |
| STREAM_CLOSED | 0x5 | Frame on closed stream |
| FRAME_SIZE_ERROR | 0x6 | Frame size incorrect |
| REFUSED_STREAM | 0x7 | Stream not processed |
| CANCEL | 0x8 | Stream cancelled by peer |
| COMPRESSION_ERROR | 0x9 | Compression state error |
| CONNECT_ERROR | 0xa | TCP connection error |
| ENHANCE_YOUR_CALM | 0xb | Excessive resource usage |
| INADEQUATE_SECURITY | 0xc | TLS requirements not met |
| HTTP_1_1_REQUIRED | 0xd | HTTP/1.1 must be used |
Ruby HTTP/2 Client Configuration
require 'httpx'
# Comprehensive HTTP/2 client configuration
http = HTTPX.plugin(:persistent)
.plugin(:retries, max_retries: 3, retry_after: 1)
.plugin(:follow_redirects, max_redirects: 3)
.with(
http2_settings: {
max_concurrent_streams: 100,
initial_window_size: 65_535,
max_frame_size: 16_384
},
timeout: {
connect_timeout: 5,
operation_timeout: 30,
read_timeout: 10
}
)
ALPN Protocol Identifiers
| Protocol | Identifier | Description |
|---|---|---|
| HTTP/1.1 | http/1.1 | Plaintext HTTP/1.1 |
| HTTP/2 over TLS | h2 | Binary HTTP/2 with TLS |
| HTTP/2 cleartext | h2c | Binary HTTP/2 without TLS |
| HTTP/3 | h3 | HTTP/3 over QUIC |
Nginx HTTP/2 Directives
# Connection-level settings
http2_max_concurrent_streams 128;
http2_max_field_size 16k;
http2_max_header_size 32k;
http2_recv_buffer_size 256k;
# Push settings
http2_push /assets/application.css;
http2_push /assets/application.js;
http2_push_preload on;
# Timeouts
http2_idle_timeout 3m;
http2_recv_timeout 30s;
QUIC Connection ID Length
| Type | Length Range | Description |
|---|---|---|
| Short Header | 0-20 bytes | Connection IDs in short header packets |
| Long Header | 0-20 bytes | Connection IDs in long header packets |
| Initial | 8-20 bytes | Connection IDs in initial packets |
| Retry | 0-20 bytes | Server-chosen connection IDs |
HTTP/3 Frame Types
| Frame Type | Purpose |
|---|---|
| DATA | Application data |
| HEADERS | HTTP headers |
| CANCEL_PUSH | Cancel server push |
| SETTINGS | Connection configuration |
| PUSH_PROMISE | Announce pushed resource |
| GOAWAY | Graceful shutdown |
| MAX_PUSH_ID | Limit push stream IDs |
Performance Benchmarks
Typical latency improvements observed with HTTP/2 and HTTP/3 compared to HTTP/1.1:
| Scenario | HTTP/2 Improvement | HTTP/3 Improvement |
|---|---|---|
| High latency (200ms RTT) | 30-50% faster | 40-60% faster |
| Packet loss (1-2%) | 10-20% faster | 30-50% faster |
| Mobile connections | 20-40% faster | 35-55% faster |
| Concurrent requests (20+) | 40-60% faster | 45-65% faster |
| Frequent reconnections | 15-25% faster | 40-70% faster |