Repository: go
Issue: net/http: ReadTimeout is not honored when ReadHeaderTimeout > ReadTimeout
Labels: NeedsInvestigation

<!-- Please answer these questions before submitting your issue. Thanks! -->

### What version of Go are you using (`go version`)?

<pre>
$ go version
go version go1.13.4 linux/amd64
</pre>

### Does this issue reproduce with the latest release?

It should.

### What operating system and processor architecture are you using (`go env`)?

<details><summary><code>go env</code> Output</summary><br><pre>
$ go env
GO111MODULE=""
GOARCH="amd64"
GOBIN=""
GOCACHE="/root/.cache/go-build"
GOENV="/root/.config/go/env"
GOEXE=""
GOFLAGS=""
GOHOSTARCH="amd64"
GOHOSTOS="linux"
GONOPROXY=""
GONOSUMDB=""
GOOS="linux"
GOPATH="/go"
GOPRIVATE=""
GOPROXY="https://proxy.golang.org,direct"
GOROOT="/usr/local/go"
GOSUMDB="sum.golang.org"
GOTMPDIR=""
GOTOOLDIR="/usr/local/go/pkg/tool/linux_amd64"
GCCGO="gccgo"
AR="ar"
CC="gcc"
CXX="g++"
CGO_ENABLED="1"
GOMOD=""
CGO_CFLAGS="-g -O2"
CGO_CPPFLAGS=""
CGO_CXXFLAGS="-g -O2"
CGO_FFLAGS="-g -O2"
CGO_LDFLAGS="-g -O2"
PKG_CONFIG="pkg-config"
GOGCCFLAGS="-fPIC -m64 -pthread -fmessage-length=0 -fdebug-prefix-map=/tmp/go-build860032074=/tmp/go-build -gno-record-gcc-switches"
</pre></details>

### What did you do?

The following simple program runs a Hello World HTTP server with 5 second ReadTimeout and 10 second ReadHeaderTimeout.
```
package main

import (
	"fmt"
	"net/http"
	"time"
)

func main() {
	http.HandleFunc("/", HelloServer)
	server := &http.Server{
		Addr:              ":1234",
		ReadHeaderTimeout: 10 * time.Second,
		ReadTimeout:       5 * time.Second,
	}
	server.ListenAndServe()
}

func HelloServer(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "Hello, %s!", r.URL.Path[1:])
}
```

Start the server.  Now, test the timeouts by connecting as a client as follows, where `nc` is the netcat program.  The client initiates a connection but never sends any bytes containing request headers.
```
jamesjohnston-mac:website jamesjohnston$ time nc localhost 1234

real	0m10.019s
user	0m0.005s
sys	0m0.010s
```

### What did you expect to see?

The connection should time out after 5 seconds.  The documentation for ReadTimeout states the following:

```
	// ReadTimeout is the maximum duration for reading the entire
	// request, including the body.
```

Since headers are part of the request, we'd also expect this timeout to apply to headers as well.

### What did you see instead?

The connection timed out after 10 seconds, not 5 seconds.

NOTE:  If we instead did an HTTP POST with a request body included, and then sent the full request (headers+body) in the time between 5 and 10 seconds (with a flush on network connection after sending all headers and before the body), then we would find that the headers would be successfully received by server but then the body would instantly time out when trying to read it.  I struggle to see how this would be of any use in the real world, especially seeing as how ServeHTTP cannot adjust read timeouts/deadlines - i.e. lengthen them to something that isn't timing out immediately. (see https://github.com/golang/go/issues/16100 )

### Suggested action items

I do not know what the actual intended behavior the maintainers of the http package wish to have.  It seems to me there are two options to resolve this contradiction:

- Update documentation to reflect actual behavior.  For example, update comment on ReadTimeout to look something like:
```
	// ReadTimeout is the maximum duration for reading the entire
	// request, including the body.  As an exception, if
	// ReadHeaderTimeout > ReadTimeout, then ReadHeaderTimeout will
	// apply for reading the header portion of the request, but then the
	// request body will immediately time out when attempting to read it if
	// the ReadTimeout deadline has already elapsed.
```
- Update code to match current documented behavior.  For example, update https://github.com/golang/go/blob/440f7d64048cd94cba669e16fe92137ce6b84073/src/net/http/server.go#L946 to look something like:
```
	t0 := time.Now()
	if d := c.server.readHeaderTimeout(); d != 0 {
		hdrDeadline = t0.Add(d)
	}
	if d := c.server.ReadTimeout; d != 0 {
		wholeReqDeadline = t0.Add(d)
	}
	// New: Enforce hdrDeadline <= wholeReqDeadline
	// (Not shown: logic to deal with infinite ReadHeaderTimeout and/or ReadTimeout
	if wholeReqDeadline.Before(hdrDeadline) {
		hdrDeadline = wholeReqDeadline
	}
```