You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
When constructing a http.Server with a value provided for ReadHeaderTimeout, an open connection will be closed by the server if it has remained idle for that timeout. This occurs if a value is not provided for IdleTimeout. For example, the following server will close any open connections if they have remained idle for 100 ms:
The timer for the header read timeout should be started when a request begins (i.e., when the first byte of a new request is received). Technically the timeout should probably only start after the first line of the request has been read (e.g. GET / HTTP/1.1) as that line is not part of the headers, but...whatever. I'm not going to nitpick over that.
What did you see instead?
The timeout considers time the connection has spent idle as part of the timeout duration. This means that the timeout, which is supposed to apply only to reading headers, also applies to all the time that has elapsed since the end of the previous request, even if that time was not spent reading headers.
Surprisingly, setting an IdleTimeout on the server prevents this behavior.
Why this occurs
This occurs because the accept loop for HTTP 1.1 connections in the serve function, here:
The readRequest function begins by immediately setting a deadline for reading from the connection, based on the values of Server.ReadHeaderTimeout and Server.ReadTimeout:
// RFC 7230 section 3 tolerance for old buggy clients.
peek, _:=c.bufr.Peek(4) // ReadRequest will get err below
c.bufr.Discard(numLeadingCRorLF(peek))
}
req, err:=readRequest(c.bufr)
In the accept loop in serve, when a previous request completes, the read deadline is reset and the loop wraps around to the beginning, where it calls readRequest again. But, resetting the read deadline here doesn't actually matter here, because readRequest will immediately set a deadline based on the value of ReadHeaderTimeout:
This means that time spent waiting for the initial read is limited by a deadline that's set immediately upon completion of the previous request.
Note that this does not apply when there's a value provided for Server.IdleTimeout, since in that case, the server will attempt to peek from the connection first, in order to wait for it to become readable. That's limited by a deadline set based on the idle timeout. However, if the connection becomes readable before the idle timeout elapses, and a new request begins, that request's ReadHeaderTimeout will (correctly) only apply to reading the new request, and not to time spent waiting for the connection to become readable.
The text was updated successfully, but these errors were encountered:
Consistently wait for idle connections to become readable before
starting the ReadHeaderTimeout timer. Previously, connections with no
idle timeout skipped directly to reading headers, so the
ReadHeaderTimeout also included time spent idle.
Fixesgolang#54784
Change-Id: Ifba48f16c9a747647086ea9da6b6dcd54c4672e7
What version of Go are you using (
go version
)?Does this issue reproduce with the latest release?
I have no idea. Which one is the latest release?
What operating system and processor architecture are you using (
go env
)?go env
OutputWhat did you do?
When constructing a
http.Server
with a value provided forReadHeaderTimeout
, an open connection will be closed by the server if it has remained idle for that timeout. This occurs if a value is not provided forIdleTimeout
. For example, the following server will close any open connections if they have remained idle for 100 ms:What did you expect to see?
The timer for the header read timeout should be started when a request begins (i.e., when the first byte of a new request is received). Technically the timeout should probably only start after the first line of the request has been read (e.g.
GET / HTTP/1.1
) as that line is not part of the headers, but...whatever. I'm not going to nitpick over that.What did you see instead?
The timeout considers time the connection has spent idle as part of the timeout duration. This means that the timeout, which is supposed to apply only to reading headers, also applies to all the time that has elapsed since the end of the previous request, even if that time was not spent reading headers.
Surprisingly, setting an
IdleTimeout
on the server prevents this behavior.Why this occurs
This occurs because the accept loop for HTTP 1.1 connections in the
serve
function, here:go/src/net/http/server.go
Lines 1903 to 2010 in bd56cb9
begins with a call to
readRequest
.The
readRequest
function begins by immediately setting a deadline for reading from the connection, based on the values ofServer.ReadHeaderTimeout
andServer.ReadTimeout
:go/src/net/http/server.go
Lines 958 to 969 in bd56cb9
It then tries to actually read from the connection:
go/src/net/http/server.go
Lines 977 to 982 in bd56cb9
In the accept loop in
serve
, when a previous request completes, the read deadline is reset and the loop wraps around to the beginning, where it callsreadRequest
again. But, resetting the read deadline here doesn't actually matter here, becausereadRequest
will immediately set a deadline based on the value ofReadHeaderTimeout
:go/src/net/http/server.go
Lines 2003 to 2009 in bd56cb9
This means that time spent waiting for the initial read is limited by a deadline that's set immediately upon completion of the previous request.
Note that this does not apply when there's a value provided for
Server.IdleTimeout
, since in that case, the server will attempt to peek from the connection first, in order to wait for it to become readable. That's limited by a deadline set based on the idle timeout. However, if the connection becomes readable before the idle timeout elapses, and a new request begins, that request'sReadHeaderTimeout
will (correctly) only apply to reading the new request, and not to time spent waiting for the connection to become readable.The text was updated successfully, but these errors were encountered: