From HTTP/1.0 to HTTP/3, WebSockets, and Beyond
GET /index.html HTTP/1.0
[connection closes]
GET /style.css HTTP/1.0
[new connection, new handshake]
GET /index.html HTTP/1.1
Host: example.com
Connection: keep-alive
GET /style.css HTTP/1.1
[same connection reused]
Problem: Head-of-line blocking - responses must be in order
One TCP connection:
Stream 1: /index.html
Stream 2: /style.css (parallel!)
Stream 3: /script.js (parallel!)
Still has TCP-level head-of-line blocking
UDP-based QUIC:
- 0-RTT connection resumption
- Stream-level error recovery
- Connection migration (IP change)
| Feature | HTTP/1.1 | HTTP/2 | HTTP/3 |
|---|---|---|---|
| Protocol | Text over TCP | Binary over TCP | Binary over QUIC (UDP) |
| Connections | 6 parallel per domain | 1 connection, multiplexed | 1 QUIC connection |
| Head-of-line blocking | Yes (HTTP level) | No (HTTP), Yes (TCP) | No |
| Header compression | No | HPACK | QPACK |
| Server push | No | Yes | Yes (rarely used) |
| TLS | Optional | De facto required | Built-in (TLS 1.3) |
| Connection Setup | TCP 3-way + TLS (2-3 RTT) | TCP 3-way + TLS (2-3 RTT) | 0-1 RTT |
POST /api/users HTTP/1.1 ← Request Line
Host: api.example.com ← Headers
Content-Type: application/json
Authorization: Bearer eyJhbGc...
Content-Length: 58
User-Agent: Mozilla/5.0
Accept: application/json
Connection: keep-alive
← Blank line
{"name": "Alice", "email": "alice@example.com"} ← Body
| Method | Purpose | Idempotent | Safe | Cacheable |
|---|---|---|---|---|
| GET | Retrieve resource | Yes | Yes | Yes |
| POST | Create resource | No | No | Rarely |
| PUT | Replace resource | Yes | No | No |
| PATCH | Partial update | No | No | No |
| DELETE | Remove resource | Yes | No | No |
| HEAD | GET without body (headers only) | Yes | Yes | Yes |
| OPTIONS | Get allowed methods (CORS preflight) | Yes | Yes | No |
Idempotent: Multiple identical requests have same effect as one
Safe: Read-only, doesn't modify server state
Example: DELETE /users/123 is idempotent - deleting same user 5 times = same result (user deleted)
HTTP/1.1 200 OK ← Status Line
Date: Mon, 01 Jan 2024 12:00:00 GMT ← Headers
Server: nginx/1.18.0
Content-Type: application/json; charset=utf-8
Content-Length: 134
Cache-Control: max-age=3600
Set-Cookie: session=abc123; HttpOnly; Secure
Access-Control-Allow-Origin: *
← Blank line
{"id": 42, "name": "Alice", ...} ← Body
| Code | Meaning | Use Case |
|---|---|---|
| 1xx - Informational | ||
| 100 | Continue | Client should continue with request body |
| 101 | Switching Protocols | Upgrade to WebSocket |
| 2xx - Success | ||
| 200 | OK | Request succeeded |
| 201 | Created | Resource created (POST, PUT) |
| 204 | No Content | Success, but no body (DELETE) |
| 3xx - Redirection | ||
| 301 | Moved Permanently | Resource permanently moved, update bookmarks |
| 302 | Found | Temporary redirect |
| 304 | Not Modified | Use cached version (conditional GET) |
| 4xx - Client Error | ||
| 400 | Bad Request | Malformed syntax, invalid JSON |
| 401 | Unauthorized | Authentication required or failed |
| 403 | Forbidden | Authenticated but not authorized |
| 404 | Not Found | Resource doesn't exist |
| 429 | Too Many Requests | Rate limit exceeded |
| 5xx - Server Error | ||
| 500 | Internal Server Error | Generic server error |
| 502 | Bad Gateway | Invalid response from upstream server |
| 503 | Service Unavailable | Server overloaded or down |
| 504 | Gateway Timeout | Upstream server timeout |
| Header | Purpose | Example |
|---|---|---|
| Host | Domain name (required in HTTP/1.1) | Host: api.example.com |
| User-Agent | Client software identifier | User-Agent: Mozilla/5.0 ... |
| Accept | Acceptable response formats | Accept: application/json |
| Authorization | Authentication credentials | Authorization: Bearer token123 |
| Content-Type | Request body format | Content-Type: application/json |
| Cookie | Send cookies to server | Cookie: session=abc123 |
| If-None-Match | Conditional request (ETag) | If-None-Match: "abc123" |
| If-Modified-Since | Conditional request (timestamp) | If-Modified-Since: Mon, 01 Jan 2024 |
| Origin | CORS - request origin | Origin: https://example.com |
| Header | Purpose | Example |
|---|---|---|
| Content-Type | Response body format | Content-Type: text/html; charset=utf-8 |
| Content-Length | Body size in bytes | Content-Length: 1234 |
| Set-Cookie | Set cookie in client | Set-Cookie: session=abc; HttpOnly |
| Location | Redirect target (3xx status) | Location: /new-url |
| ETag | Resource version identifier | ETag: "33a64df551425fcc55e" |
| Last-Modified | Resource modification time | Last-Modified: Mon, 01 Jan 2024 |
| Directive | Meaning |
|---|---|
no-cache |
Cache, but revalidate with server before using |
no-store |
Don't cache at all (sensitive data) |
max-age=3600 |
Cache for 3600 seconds (1 hour) |
public |
Can be cached by any cache (CDN, browser) |
private |
Only browser cache, not shared caches |
must-revalidate |
Must check with server when stale |
immutable |
Never changes (perfect for hashed assets) |
# Static assets (versioned filenames)
Cache-Control: public, max-age=31536000, immutable
# Cache for 1 year, never revalidate
# API responses
Cache-Control: private, max-age=300
# Cache in browser for 5 minutes
# Sensitive data
Cache-Control: no-store
# Don't cache
# Initial request
GET /api/users/123 HTTP/1.1
# Response
HTTP/1.1 200 OK
ETag: "v1.23.45"
Last-Modified: Mon, 01 Jan 2024 12:00:00 GMT
{ "id": 123, "name": "Alice" }
# Subsequent request
GET /api/users/123 HTTP/1.1
If-None-Match: "v1.23.45"
# Response (resource unchanged)
HTTP/1.1 304 Not Modified
ETag: "v1.23.45"
# No body - use cached version
| Header | Purpose | Example |
|---|---|---|
| Strict-Transport-Security (HSTS) | Force HTTPS for specified time | Strict-Transport-Security: max-age=31536000; includeSubDomains |
| Content-Security-Policy (CSP) | Prevent XSS by restricting resource sources | Content-Security-Policy: default-src 'self'; script-src 'self' cdn.example.com |
| X-Frame-Options | Prevent clickjacking (iframe embedding) | X-Frame-Options: DENY or SAMEORIGIN |
| X-Content-Type-Options | Prevent MIME-type sniffing | X-Content-Type-Options: nosniff |
| Referrer-Policy | Control Referer header information | Referrer-Policy: strict-origin-when-cross-origin |
| Permissions-Policy | Control browser features (camera, geolocation) | Permissions-Policy: geolocation=(), camera=() |
# Preflight request (OPTIONS)
OPTIONS /api/data HTTP/1.1
Origin: https://example.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: Content-Type
# Preflight response
HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://example.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Max-Age: 86400 # Cache preflight for 24 hours
# Actual request
POST /api/data HTTP/1.1
Origin: https://example.com
Content-Type: application/json
# Actual response
HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://example.com
Access-Control-Allow-Credentials: true
Client Server
| |
| 1. ClientHello |
| - TLS versions supported |
| - Cipher suites supported |
| - Random number |
|---------------------------------------->|
| |
| 2. ServerHello |
| - TLS version |
| - Cipher suite |
| - Random number |
| 3. Certificate |
| - Server cert |
| 4. ServerHelloDone |
|<----------------------------------------|
| |
| 5. ClientKeyExchange |
| - Pre-master secret (encrypted) |
| 6. ChangeCipherSpec |
| 7. Finished (encrypted) |
|---------------------------------------->|
| |
| 8. ChangeCipherSpec |
| 9. Finished (encrypted) |
|<----------------------------------------|
| |
| Application Data (encrypted) |
|<--------------------------------------->|
2 Round Trips (2-RTT) before application data can be sent
Client Server
| |
| ClientHello |
| + Key Share (DH public key) |
|---------------------------------------->|
| |
| ServerHello |
| + Key Share |
| + Certificate |
| + Finished |
|<----------------------------------------|
| Application Data (encrypted) |
|<--------------------------------------->|
1 Round Trip (1-RTT) - much faster!
0-RTT resumption: Reconnecting clients can send encrypted data immediately
Client repeatedly requests updates
Client → Server: GET /updates
Server → Client: {"new": false}
[wait 5 seconds]
Client → Server: GET /updates
Server → Client: {"new": false}
[wait 5 seconds]
Client → Server: GET /updates
Server → Client: {"new": true, "data": ...}
Pros: Simple, works everywhere
Cons: Wasteful, high latency
Server holds request until data available
Client → Server: GET /updates
[server waits...]
[new data arrives]
Server → Client: {"data": ...}
Client → Server: GET /updates [immediately]
[server waits...]
Pros: Lower latency than polling
Cons: Ties up connections, complex
Server pushes updates over persistent connection
Client → Server: GET /events
Accept: text/event-stream
Server → Client: (connection stays open)
data: {"message": "Hello"}
data: {"message": "Update 1"}
data: {"message": "Update 2"}
Pros: Simple, auto-reconnect, event IDs
Cons: One-way only (server→client)
const eventSource = new EventSource('/events');
eventSource.onmessage = (event) => {
console.log('New message:', event.data);
};
eventSource.onerror = () => {
console.log('Connection lost, reconnecting...');
};
Full-duplex communication (bidirectional)
# Initial handshake (HTTP upgrade)
Client → Server:
GET /chat HTTP/1.1
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: x3JJHMbDL1EzLkh9...
Server → Client:
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm...
# Now bidirectional frames
Client ↔ Server:
[binary frame: user typing...]
[text frame: "Hello"]
[binary frame: audio data...]
Pros: Full duplex, low overhead, binary support
Cons: More complex, needs special server support
const ws = new WebSocket('wss://example.com/chat');
ws.onopen = () => {
ws.send(JSON.stringify({ type: 'join', room: 'lobby' }));
};
ws.onmessage = (event) => {
const message = JSON.parse(event.data);
console.log('Received:', message);
};
ws.send(JSON.stringify({ type: 'message', text: 'Hello!' }));
| Technique | Best For |
|---|---|
| Polling | Infrequent updates, simple requirements |
| Long Polling | Legacy browser support, moderate real-time needs |
| SSE | One-way updates (server→client): notifications, live feeds, stock tickers |
| WebSockets | Bidirectional real-time: chat, gaming, collaborative editing, live trading |
| Algorithm | Compression Ratio | Speed | Browser Support |
|---|---|---|---|
| gzip | Good (60-80%) | Fast | All browsers |
| Brotli | Better (70-85%) | Slower compression, fast decompression | Modern browsers (95%+) |
| Deflate | Similar to gzip | Fast | All browsers (rarely used) |
# Request
GET /script.js HTTP/1.1
Accept-Encoding: gzip, deflate, br
# Response
HTTP/1.1 200 OK
Content-Encoding: br
Content-Type: application/javascript
Content-Length: 15234 # Compressed size
[compressed data]
Connection 1: /index.html ████████
Connection 2: /style.css ██████
Connection 3: /script.js ████
Connection 4: /image1.png ██████████
Connection 5: /image2.png ████████
Connection 6: /image3.png ██████
# Max 6 parallel, then queue
Connection 1: /image4.png ██████
[waiting...]
One TCP Connection:
Stream 1: /index.html ████████
Stream 2: /style.css ██████
Stream 3: /script.js ████
Stream 4: /image1.png ██████████
Stream 5: /image2.png ████████
Stream 6: /image3.png ██████
Stream 7: /image4.png ██████
...unlimited streams...
Benefits:
Server proactively sends resources before client requests them
# Client requests HTML
GET /index.html HTTP/2
# Server pushes CSS and JS before client parses HTML
PUSH_PROMISE: /style.css
PUSH_PROMISE: /script.js
# Server sends responses
Stream 1: /index.html → <html><link href="/style.css">...
Stream 2: /style.css → body { ... } [pushed]
Stream 3: /script.js → console.log(...) [pushed]
Pros: Eliminates round trips for critical resources
Cons: Can push unnecessary resources, cache issues
Reality: Not widely used, being removed from Chrome
Problem with TCP: Head-of-line blocking at TCP level
HTTP/2 over TCP:
Stream 1: ████ [packet lost!] [blocked]
Stream 2: ████ [waiting...] [blocked]
Stream 3: ████ [waiting...] [blocked]
# All streams blocked until lost packet retransmitted
HTTP/3 over QUIC (UDP):
Stream 1: ████ [packet lost!] [stream 1 blocked]
Stream 2: ████████ [continues!]
Stream 3: ████████ [continues!]
# Only affected stream blocked