HTTP/1.1 and the End of One-Request Connections
Follow the documented shift from HTTP/1.0's connection model to HTTP/1.1 persistence, message framing, and cautious retry semantics.
The early Web’s protocol did not begin with today’s assumptions about a reusable connection. HTTP/0.9 carried a simple request and raw response; HTTP/1.0 introduced versioned, MIME-like messages with metadata and methods. Yet the original HTTP/1.0 common-use specification did not make persistent connections the default. Fetching a page with many resources could therefore establish many separate TCP connections. HTTP/1.1’s persistence was a protocol-design response to that cost, but it also required explicit message boundaries and careful recovery from connection closure.
RFC 1945, published in May 1996, described common HTTP/1.0 practice and explicitly classified itself as Informational, not an Internet Standard. It was a snapshot of what implementations largely agreed on, not proof that every product labeled “HTTP/1.0” behaved identically. The document’s request/response model describes a client establishing a connection, sending a request line and headers, and receiving a response. It also documents caching headers and intermediaries, showing that HTTP/1.0 was already more than a bare file-transfer mechanism.
Why a reusable connection changed the contract
RFC 2068, the January 1997 HTTP/1.1 specification, argued that HTTP/1.0 did not adequately address hierarchical proxies, caching, persistent connections, and virtual hosts. Its solution was not simply “leave the socket open.” On a connection carrying multiple messages, the receiver must know where one response ends before reading the next. RFC 2068 therefore requires self-defined message lengths for persistence, using framing rules rather than treating connection close as the only delimiter.
HTTP/1.1 made persistence the default unless a Connection: close token signaled otherwise. It also allowed a client to pipeline requests, while requiring the server to return responses in request order. Pipelining was a permitted optimization, not the defining feature of persistence. One can reuse a connection sequentially without sending several unanswered requests at once.
This distinction illuminates an enduring design tension: fewer handshakes can reduce latency and server load, but a long-lived stream needs unambiguous framing and a policy for failures partway through a sequence. An intermediary cannot safely pretend all old peers understand the new lifetime rules.
The proxy and retry problem
The HTTP/1.1 specification discusses faulty experimental HTTP/1.0 Keep-Alive interactions with proxies. A proxy that does not understand hop-by-hop Connection signaling might forward a header incorrectly, leaving participants with incompatible expectations about when a response ends. The historical lesson is not that every HTTP/1.0 persistent connection was impossible, but that the extension was not a reliable default across arbitrary intermediaries.
Either side can close an apparently idle persistent connection while the other side is beginning another request. RFC 2068 consequently permits automatic retry of an aborted request only when its method is idempotent; other methods require a human decision rather than blind replay. That boundary remains a useful way to think about retry behavior: a transport failure does not reveal whether the server already performed an operation. A POST that timed out may have succeeded, failed, or committed before its response was lost.
Read the documents as stages, not final rules
RFC 2068 was superseded by later HTTP specifications, so it is a historical source for the design transition, not a current implementation manual. Likewise, RFC 1945 is valuable for understanding contemporaneous HTTP/1.0 practice rather than for retroactively assigning one behavior to every early server. The two texts make the progression clear: richer messages enabled the Web’s expansion, while persistence forced framing, intermediary, and retry rules to become more explicit.
HTTP/1.1 did not end connection-management problems. It did establish that connection reuse is a protocol contract shared by client, server, and intermediaries - not a local socket optimization. That contract is the durable historical insight behind the change from one-request connections.
A small wire-level comparison
Consider two image requests on a single connection to an HTTP/1.1 origin server. The client sends a first request and receives a status line, headers, a defined body length, and the body. It can then issue the second request on the same connection. The server can distinguish the second request only because the first message has an unambiguous ending. If the first response has neither a trustworthy length nor valid chunked framing, connection close may be the only reliable boundary; the socket cannot then carry a further response.
That example explains why persistence and framing are inseparable. It also explains a class of proxy bugs: if two peers disagree about where a message ends, a reused connection can misassign bytes from one message to the next. The historical RFCs should be read with this relationship in mind, not as a performance feature bolted onto unchanged HTTP/1.0 messages.
The Connection header carries hop-by-hop control, not a property to forward blindly across every intermediary. RFC 2068’s account of old Keep-Alive proxy failures is a concrete instance: a proxy that failed to consume a connection-specific field could lead the next peer to hold a connection open while the proxy expected closure to delimit the message. A correct intermediary must understand the connection on each side independently. This is why protocol-version negotiation and intermediary behavior were part of HTTP/1.1’s design case.
Test failure semantics separately from speed
Suppose a client sends two requests sequentially over one persistent connection and the server closes it while the second request is in flight. The client knows the transport ended, but not necessarily whether the server acted on the request. A safe retry decision depends on method semantics. For an idempotent request, repeating the request is intended to have the same effect as one execution; for a non-idempotent action, automatic replay can duplicate a change. The RFC’s retry constraint is therefore a correctness rule, not merely a conservative performance setting.
For a historical protocol trace, record the request and response version, Connection fields, the framing headers, and whether closure occurred before or after a complete response. Do not infer persistence solely from the existence of a TCP socket that stayed open briefly. Likewise, do not infer that pipelining was used merely because multiple requests shared a connection. A sequential trace and a pipelined trace impose different outstanding-request states on the client and server.
RFC 2068 later gave way to revised specifications, so modern troubleshooting should use current HTTP standards. But its text remains direct evidence for what its designers were trying to repair: connection churn, ambiguous behavior through proxies, and failure handling once a connection could outlive a single exchange. Those constraints explain both the appeal and the complexity of HTTP/1.1 persistence.
Related:
- From SPDY to HTTP/2: How Web Transport Was Reframed
- MIME: Teaching Internet Mail to Carry More Than Plain Text
Sources: