Hi Hank,

On Fri, Dec 19, 2025 at 08:55:43AM +0100, Hank Bowen wrote:
> Good morning,
> 
> I have a couple of questions regarding http-reuse and idle streams. In
> fact, I have sent a similar e-mail to this mailing list about relatively
> recently but I think that it might have been too long and by this
> discouraging to be read.
> 
> I will ask my questions in points as I think it will help readability:
> 
> 1. https://docs.haproxy.org/2.6/configuration.html#http-reuse states in the
> description of http-reuse that specific modes differ in how the first
> request of a session is handled.
> 
> What is session in this context?

Ah interesting. We're progressively trying to reduce the number of
occurrences of this "session" word which probably is one of those
with the most different definitions in the same area of computing.

Originally in haproxy a session was the entity which is instantiated
by an incoming TCP connection. It contains the client's source IP
address and some info. In the past, when keep-alive was not supported,
there was no distinction between a session and a stream, so all the
HTTP parsing was done on the "session" and the state was stored there,
thus there was a single request/response per session. That's why you
can see "sessions" in the backend stats by the way. It's the number
of times a backend was selected by a given session.

Later, in 1.4, we stated to support keep-alive, the session was in fact
basically reset to process another request, so it was possible to have
more than one request per session. It didn't change the backend stats
based on the definition above. Then in 1.5 with the arrival of SSL
support, we figured that the session was only completed when SSL was
finished, so the notion became a bit more complex to describe: the
session continues to be instantiated very early (to carry some basic
info such as the source address) but it's only usable when all handshakes
(including SSL) are done. Thus the "tcp-request session" rules apply
immediately after these handshakes and have access to what was negotiated
for SSL (e.g. certificates and SNI are known). And now the definition has
not changed, so the session remains directly tied to a client-initiated
incoming connection and is accessible from all the chain from the (front
facing connection to back facing one). It's very likely that some wording
remains in the doc mixing the two concepts of session and request, though
we've been careful to try to clarify everything. Of course, clarifying
when you know the difference always leaves some room for ambiguity.

> 2. We read there also that in case of safe mode that "The first
> request of a session is always sent over its own connection (...) This
> ensures that in case the server closes the connection when the request is
> being sent, the browser can decide to silently retry it"

Yes!

> How does the fact that the first request of a session is always sent over
> its own connections ensure that the browser can decide to silently retry it?

It's by virtue of reusing a connection: HTTP says that a client may reuse
a connection after the end of a request/response exchange to send a new
independent request and expect a response. However, by doing so the client
is willing to take the risk that the connection closes during or after the
emission of the request, because a close on inactivity delay was initiated
by the server or any other intermediary component before the client started
to send, and that client must be prepared to resend, assuming the request
was not processed. Thus a client sending a second request on a connection
is expected to always be able to retry if it gets no response (and there is
code in haproxy to detect the server close and forward just a close to the
client with no response to trigger its retry mechanism).

> Why wouldn't it be possible in case when that first request were dispatched
> over an already existing connection?

Because nothing guarantees that the client would retry. Some do, but it's
their choice.

You can even check that with curl by the way. Look:

  $ curl -v 0:8888/one
  *   Trying 0.0.0.0:8888...
  * connect to 0.0.0.0 port 8888 failed: Connection refused
  * Failed to connect to 0.0.0.0 port 8888 after 0 ms: Couldn't connect to 
server
  * Closing connection 0
  curl: (7) Failed to connect to 0.0.0.0 port 8888 after 0 ms: Couldn't connect 
to server

=> first request failed on connection error, no retry.

Let's try again with a server that listens to that port and closes after one
second:

  $ sleep 1 | ncat -lp8888

  $ curl -v 0:8888/one 
  *   Trying 0.0.0.0:8888...
  * Connected to 0.0.0.0 (127.0.0.1) port 8888 (#0)
  > GET /one HTTP/1.1
  > Host: 0.0.0.0:8888
  > User-Agent: curl/7.88.1-DEV
  > Accept: */*
  > 
  * Empty reply from server
  * Closing connection 0
  curl: (52) Empty reply from server

=> The request was properly sent, no response, it's a definitive error.

Now you bind a listener that responds in HTTP/1.1 and quickly closes
the connection after that respones, and you send two requests in a row:

  $ (printf "HTTP/1.1 200 OK\r\nContent-length: 0\r\n\r\n";sleep 1) | ncat 
-lp8888
  $ curl -v 0:8888/one 0:8888/two

=> ncat gets the two requests one after the other:
  GET /one HTTP/1.1
  Host: 0.0.0.0:8888
  User-Agent: curl/7.88.1-DEV
  Accept: */*
  
  GET /two HTTP/1.1
  Host: 0.0.0.0:8888
  User-Agent: curl/7.88.1-DEV
  Accept: */*

=> curl sees:
  *   Trying 0.0.0.0:8888...
  * Connected to 0.0.0.0 (127.0.0.1) port 8888 (#0)
  > GET /one HTTP/1.1
  > Host: 0.0.0.0:8888
  > User-Agent: curl/7.88.1-DEV
  > Accept: */*
  > 
  < HTTP/1.1 200 OK
  < Content-length: 0
  < 
  * Connection #0 to host 0.0.0.0 left intact
  * Found bundle for host: 0x55d7c8a6b720 [serially]
  * Can not multiplex, even if we wanted to
  * Re-using existing connection #0 with host 0.0.0.0
  > GET /two HTTP/1.1
  > Host: 0.0.0.0:8888
  > User-Agent: curl/7.88.1-DEV
  > Accept: */*
  > 
=>* Connection died, retrying a fresh connect (retry count: 1)
  * Closing connection 0
  * Issue another request to this URL: 'http://0.0.0.0:8888/two'
  * Hostname 0.0.0.0 was found in DNS cache
  *   Trying 0.0.0.0:8888...
  * connect to 0.0.0.0 port 8888 failed: Connection refused
  * Failed to connect to 0.0.0.0 port 8888 after 0 ms: Couldn't connect to 
server
  * Closing connection 1
  curl: (7) Failed to connect to 0.0.0.0 port 8888 after 0 ms: Couldn't connect 
to server

Have you seen "Connection died, retrying a fresh connect" above ?

> 3. After what time and by what criteria is a connection recognized idle in
> tcp and http mode?

It's not time, it's purely a protocol thing: once both a request and
response have been fully exchanged, by definition the connection is
idle again and reusable for a new request/response exchange.

What haproxy does in safe mode is that it considers that a client is
likely unable to retry the first failed request, so it will always deliver
the first request of a client's connection to a fresh connection on the
server side. But if it's a client's second connection, it will be sent
over any existing server connection.

A lot of users deploy with "http-reuse always" when they know that clients
are able to retry (e.g. typically applications, sometimes browsers), or
that the server-side connections are super reliable and the low failure
rate will be mostly covered by some clients, to reach a final rate which
is within the usual end-user experience. E.g. if you fail one request on
100 million, in the life of a user it's almost non-existing and can easily
be ignored by may sites. And for what matters we're using that on
haproxy.org as well for static files (i.e. everything except gitweb) and
for the instance that forwards to my home pages since it's not important
and failures are more likely caused by my internet access rather than
dropping connections.

Hoping this helps,
Willy


Reply via email to