HTTP

Intro

HTTP (Hypertext Transfer Protocol) is a stateless request-response protocol that carries nearly all web and API traffic in modern systems. You reach for it every time a client talks to a server over the network — browser page loads, REST APIs, webhooks, health checks, file downloads. Understanding HTTP mechanics — connection lifecycle, caching semantics, status codes, and TLS — directly affects the latency, reliability, and security of every service you build. In .NET, HTTP is the transport underneath HttpClient, ASP.NET Core, and gRPC (over HTTP/2), making it foundational knowledge for senior .NET work.

Request-Response Lifecycle

An HTTP/1.1 exchange follows this sequence:

sequenceDiagram
  participant Client
  participant Server

  Client->>Server: TCP handshake SYN SYN-ACK ACK
  Client->>Server: TLS handshake if HTTPS
  Client->>Server: HTTP request method path headers body
  Server->>Client: HTTP response status headers body
  Note over Client,Server: Connection stays open via keep-alive
  Client->>Server: Next request reuses connection

Persistent Connections

HTTP/1.0 opened a new TCP connection per request — catastrophically expensive. HTTP/1.1 made persistent connections the default: the same TCP socket is reused for multiple request-response cycles. The Connection: keep-alive header controls idle timeout and max requests. Idle connections still consume server-side file descriptors and memory, so servers close them after a configurable timeout.

Head-of-Line Blocking

HTTP/1.1 processes requests sequentially on each connection: the response to request #1 must complete before request #2 begins. If request #1 triggers a slow database query, requests #2 and #3 wait behind it. Browsers work around this by opening up to 6 parallel TCP connections per origin, but this wastes resources and still hits limits. HTTP 2 solves this with stream multiplexing.

Pipelining was an HTTP/1.1 attempt to fix HOL blocking by sending multiple requests without waiting for responses. It failed in practice because responses still had to arrive in order, and buggy intermediary proxies mishandled pipelined responses. Browsers disabled it by default.

HTTPS and TLS

HTTPS is HTTP over TLS. It provides confidentiality (encryption), integrity (tamper detection), and server authentication via certificates. Plain HTTP has no protection against eavesdropping or manipulation.

TLS 1.2 Handshake (2 RTTs)

Client → Server: ClientHello (supported ciphers, random)
Server → Client: ServerHello, Certificate, ServerHelloDone
Client → Server: ClientKeyExchange, ChangeCipherSpec, Finished
Server → Client: ChangeCipherSpec, Finished
→ Application data begins (2 RTTs after TCP handshake)

The client validates the certificate chain: leaf cert → intermediate CA(s) → trusted root CA. Each cert is verified for signature validity, expiry, revocation status (OCSP or CRL), and hostname match against the SAN (Subject Alternative Name) field.

TLS 1.3 (1 RTT)

TLS 1.3 (RFC 8446) reduced the handshake to 1 RTT and eliminated weak primitives:

.NET TLS Guidance

Use SslProtocols.None to defer TLS version selection to the OS — never hardcode SslProtocols.Tls12. The legacy SslProtocols.Default maps to SSL3 + TLS 1.0, which is insecure. ServicePointManager.SecurityProtocol is process-global and affects all HttpClient instances — a footgun in shared libraries that silently downgrades TLS for the entire process.

Methods and Semantics

Method Semantics Idempotent Safe
GET Read resource representation Yes Yes
HEAD Same as GET but response has no body Yes Yes
POST Create resource or trigger processing No No
PUT Replace target resource entirely Yes No
PATCH Partial modification Depends on implementation No
DELETE Remove target resource Yes No
OPTIONS Describe communication options and CORS preflight Yes Yes

Safe means the request does not change server state. Idempotent means repeating the same request produces the same resulting state — not the same response, but the same server-side outcome. Clients can safely auto-retry idempotent methods on ambiguous network failure, which is why PUT and DELETE are preferable over POST when the operation allows it.

Status Codes

Commonly Misused Codes

Code Correct Use Common Mistake
200 vs 204 204 for success with no body (DELETE, PUT acknowledgment) Returning 200 with empty body wastes a Content-Length round-trip
201 Created Resource created; include Location header pointing to new resource Using 200 for POST that creates a resource
301 vs 308 308 preserves HTTP method on redirect Using 301 for non-GET redirects; browsers may change POST to GET
400 vs 422 400 for malformed syntax; 422 for valid syntax but invalid semantics Using 400 for all validation errors
401 vs 403 401 = not authenticated (must include WWW-Authenticate); 403 = authenticated but not authorized Confusing authentication with authorization
409 Conflict Optimistic concurrency failure, duplicate resource Using 400 for state conflicts
429 Too Many Requests Rate limited; include Retry-After header Using 503 for rate limiting
502 vs 503 502 = upstream returned invalid response; 503 = this server is overloaded or in maintenance Treating them interchangeably; monitoring tools distinguish them for different alerting

Caching

HTTP caching prevents redundant data transfer by storing responses and reusing them for matching requests.

Cache-Control

Cache-Control: max-age=3600, must-revalidate, public

Key directives:

Heuristic caching trap: if no Cache-Control is set, RFC 9111 allows caches to apply heuristic freshness (~10% of Last-Modified age). Always set explicit Cache-Control headers — especially on API responses.

Conditional Requests and ETag

# Server sends ETag with original response
ETag: "33a64df5"

# Client revalidates later
If-None-Match: "33a64df5"

# Server responds 304 if unchanged — no body transferred
HTTP/1.1 304 Not Modified

ETags are preferred over Last-Modified because Last-Modified has only 1-second granularity and distributed servers struggle to synchronize file timestamps. If-None-Match takes precedence over If-Modified-Since when both are present (RFC 9110).

Vary Header

Vary: Accept-Encoding tells caches to key on both URL and the listed request headers. Without it, a CDN might serve a gzip-compressed response to a client that only accepts brotli. Vary: User-Agent is a cache-killing anti-pattern — thousands of User-Agent variants effectively disable caching.

Pitfalls

1) HttpClient Socket Exhaustion in .NET

2) DNS Staleness with Singleton HttpClient

3) Heuristic Caching Surprises

Tradeoffs

Aspect HTTP/1.1 HTTP/2 HTTP/3 via QUIC
Multiplexing No; one request per connection at a time Yes; many streams per TCP connection Yes; streams per QUIC connection
Head-of-line blocking Application-level and TCP-level TCP-level only; a single lost packet stalls all streams None; streams are independent at transport level
Header compression None HPACK QPACK
Transport TCP TCP UDP with QUIC
Adoption Universal Widespread Growing

Decision rule: HTTP/1.1 for maximum compatibility and simplicity. Upgrade to HTTP/2 when multiplexing and header compression matter (most modern services benefit). HTTP/3 for latency-sensitive, loss-prone networks like mobile clients and global CDN edges.

Questions


Whats next