Skip to content

Commit 918368e

Browse files
bradfitzdmitshur
authored andcommitted
[release-branch.go1.12] net/http: prevent Transport from spamming stderr on server 408 reply
HTTP 408 responses now exist and are seen in the wild (e.g. from Google's GFE), so make Go's HTTP client not spam about them when seen. They're normal (now). Fixes #32367 Updates #32310 Change-Id: I558eb4654960c74cf20db1902ccaae13d03310f6 Reviewed-on: https://go-review.googlesource.com/c/go/+/179457 Run-TryBot: Brad Fitzpatrick <[email protected]> TryBot-Result: Gobot Gobot <[email protected]> Reviewed-by: Dmitri Shuralyov <[email protected]> (cherry picked from commit ba66d89) Reviewed-on: https://go-review.googlesource.com/c/go/+/181239 Run-TryBot: Dmitri Shuralyov <[email protected]> Reviewed-by: Brad Fitzpatrick <[email protected]>
1 parent 3b05c3c commit 918368e

File tree

3 files changed

+94
-1
lines changed

3 files changed

+94
-1
lines changed

src/net/http/export_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ var (
3333
ExportHttp2ConfigureServer = http2ConfigureServer
3434
Export_shouldCopyHeaderOnRedirect = shouldCopyHeaderOnRedirect
3535
Export_writeStatusLine = writeStatusLine
36+
Export_is408Message = is408Message
3637
)
3738

3839
const MaxWriteWaitBeforeConnReuse = maxWriteWaitBeforeConnReuse

src/net/http/transport.go

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1826,7 +1826,12 @@ func (pc *persistConn) readLoopPeekFailLocked(peekErr error) {
18261826
}
18271827
if n := pc.br.Buffered(); n > 0 {
18281828
buf, _ := pc.br.Peek(n)
1829-
log.Printf("Unsolicited response received on idle HTTP channel starting with %q; err=%v", buf, peekErr)
1829+
if is408Message(buf) {
1830+
pc.closeLocked(errServerClosedIdle)
1831+
return
1832+
} else {
1833+
log.Printf("Unsolicited response received on idle HTTP channel starting with %q; err=%v", buf, peekErr)
1834+
}
18301835
}
18311836
if peekErr == io.EOF {
18321837
// common case.
@@ -1836,6 +1841,19 @@ func (pc *persistConn) readLoopPeekFailLocked(peekErr error) {
18361841
}
18371842
}
18381843

1844+
// is408Message reports whether buf has the prefix of an
1845+
// HTTP 408 Request Timeout response.
1846+
// See golang.org/issue/32310.
1847+
func is408Message(buf []byte) bool {
1848+
if len(buf) < len("HTTP/1.x 408") {
1849+
return false
1850+
}
1851+
if string(buf[:7]) != "HTTP/1." {
1852+
return false
1853+
}
1854+
return string(buf[8:12]) == " 408"
1855+
}
1856+
18391857
// readResponse reads an HTTP response (or two, in the case of "Expect:
18401858
// 100-continue") from the server. It returns the final non-100 one.
18411859
// trace is optional.

src/net/http/transport_test.go

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5059,3 +5059,77 @@ func TestTransportRequestReplayable(t *testing.T) {
50595059
})
50605060
}
50615061
}
5062+
5063+
func TestIs408(t *testing.T) {
5064+
tests := []struct {
5065+
in string
5066+
want bool
5067+
}{
5068+
{"HTTP/1.0 408", true},
5069+
{"HTTP/1.1 408", true},
5070+
{"HTTP/1.8 408", true},
5071+
{"HTTP/2.0 408", false}, // maybe h2c would do this? but false for now.
5072+
{"HTTP/1.1 408 ", true},
5073+
{"HTTP/1.1 40", false},
5074+
{"http/1.0 408", false},
5075+
{"HTTP/1-1 408", false},
5076+
}
5077+
for _, tt := range tests {
5078+
if got := Export_is408Message([]byte(tt.in)); got != tt.want {
5079+
t.Errorf("is408Message(%q) = %v; want %v", tt.in, got, tt.want)
5080+
}
5081+
}
5082+
}
5083+
5084+
func TestTransportIgnores408(t *testing.T) {
5085+
// Not parallel. Relies on mutating the log package's global Output.
5086+
defer log.SetOutput(os.Stderr)
5087+
5088+
var logout bytes.Buffer
5089+
log.SetOutput(&logout)
5090+
5091+
defer afterTest(t)
5092+
const target = "backend:443"
5093+
5094+
cst := newClientServerTest(t, h1Mode, HandlerFunc(func(w ResponseWriter, r *Request) {
5095+
nc, _, err := w.(Hijacker).Hijack()
5096+
if err != nil {
5097+
t.Error(err)
5098+
return
5099+
}
5100+
defer nc.Close()
5101+
nc.Write([]byte("HTTP/1.1 200 OK\r\nContent-Length: 2\r\n\r\nok"))
5102+
nc.Write([]byte("HTTP/1.1 408 bye\r\n")) // changing 408 to 409 makes test fail
5103+
}))
5104+
defer cst.close()
5105+
req, err := NewRequest("GET", cst.ts.URL, nil)
5106+
if err != nil {
5107+
t.Fatal(err)
5108+
}
5109+
res, err := cst.c.Do(req)
5110+
if err != nil {
5111+
t.Fatal(err)
5112+
}
5113+
slurp, err := ioutil.ReadAll(res.Body)
5114+
if err != nil {
5115+
t.Fatal(err)
5116+
}
5117+
if err != nil {
5118+
t.Fatal(err)
5119+
}
5120+
if string(slurp) != "ok" {
5121+
t.Fatalf("got %q; want ok", slurp)
5122+
}
5123+
5124+
t0 := time.Now()
5125+
for i := 0; i < 50; i++ {
5126+
time.Sleep(time.Duration(i) * 5 * time.Millisecond)
5127+
if cst.tr.IdleConnKeyCountForTesting() == 0 {
5128+
if got := logout.String(); got != "" {
5129+
t.Fatalf("expected no log output; got: %s", got)
5130+
}
5131+
return
5132+
}
5133+
}
5134+
t.Fatalf("timeout after %v waiting for Transport connections to die off", time.Since(t0))
5135+
}

0 commit comments

Comments
 (0)