Skip to content

Commit 50b16f9

Browse files
nhooyrbradfitz
authored andcommitted
net/http: allow upgrading non keepalive connections
If one was using http.Transport with DisableKeepAlives and trying to upgrade a connection against net/http's Server, the Server would not allow a "Connection: Upgrade" header to be written and instead override it to "Connection: Close" which would break the handshake. This change ensures net/http's Server does not override the connection header for successful protocol switch responses. Fixes #36381. Change-Id: I882aad8539e6c87ff5f37c20e20b3a7fa1a30357 GitHub-Last-Rev: dc0de83 GitHub-Pull-Request: #36382 Reviewed-on: https://go-review.googlesource.com/c/go/+/213277 Trust: Emmanuel Odeke <[email protected]> Trust: Brad Fitzpatrick <[email protected]> Reviewed-by: Brad Fitzpatrick <[email protected]>
1 parent 212d385 commit 50b16f9

File tree

3 files changed

+71
-6
lines changed

3 files changed

+71
-6
lines changed

src/net/http/response.go

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -352,10 +352,16 @@ func (r *Response) bodyIsWritable() bool {
352352
return ok
353353
}
354354

355-
// isProtocolSwitch reports whether r is a response to a successful
356-
// protocol upgrade.
355+
// isProtocolSwitch reports whether the response code and header
356+
// indicate a successful protocol upgrade response.
357357
func (r *Response) isProtocolSwitch() bool {
358-
return r.StatusCode == StatusSwitchingProtocols &&
359-
r.Header.Get("Upgrade") != "" &&
360-
httpguts.HeaderValuesContainsToken(r.Header["Connection"], "Upgrade")
358+
return isProtocolSwitchResponse(r.StatusCode, r.Header)
359+
}
360+
361+
// isProtocolSwitchResponse reports whether the response code and
362+
// response header indicate a successful protocol upgrade response.
363+
func isProtocolSwitchResponse(code int, h Header) bool {
364+
return code == StatusSwitchingProtocols &&
365+
h.Get("Upgrade") != "" &&
366+
httpguts.HeaderValuesContainsToken(h["Connection"], "Upgrade")
361367
}

src/net/http/serve_test.go

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6448,3 +6448,56 @@ func BenchmarkResponseStatusLine(b *testing.B) {
64486448
}
64496449
})
64506450
}
6451+
func TestDisableKeepAliveUpgrade(t *testing.T) {
6452+
if testing.Short() {
6453+
t.Skip("skipping in short mode")
6454+
}
6455+
6456+
setParallel(t)
6457+
defer afterTest(t)
6458+
6459+
s := httptest.NewUnstartedServer(HandlerFunc(func(w ResponseWriter, r *Request) {
6460+
w.Header().Set("Connection", "Upgrade")
6461+
w.Header().Set("Upgrade", "someProto")
6462+
w.WriteHeader(StatusSwitchingProtocols)
6463+
c, _, err := w.(Hijacker).Hijack()
6464+
if err != nil {
6465+
return
6466+
}
6467+
defer c.Close()
6468+
6469+
io.Copy(c, c)
6470+
}))
6471+
s.Config.SetKeepAlivesEnabled(false)
6472+
s.Start()
6473+
defer s.Close()
6474+
6475+
cl := s.Client()
6476+
cl.Transport.(*Transport).DisableKeepAlives = true
6477+
6478+
resp, err := cl.Get(s.URL)
6479+
if err != nil {
6480+
t.Fatalf("failed to perform request: %v", err)
6481+
}
6482+
defer resp.Body.Close()
6483+
6484+
rwc, ok := resp.Body.(io.ReadWriteCloser)
6485+
if !ok {
6486+
t.Fatalf("Response.Body is not a io.ReadWriteCloser: %T", resp.Body)
6487+
}
6488+
6489+
_, err = rwc.Write([]byte("hello"))
6490+
if err != nil {
6491+
t.Fatalf("failed to write to body: %v", err)
6492+
}
6493+
6494+
b := make([]byte, 5)
6495+
_, err = io.ReadFull(rwc, b)
6496+
if err != nil {
6497+
t.Fatalf("failed to read from body: %v", err)
6498+
}
6499+
6500+
if string(b) != "hello" {
6501+
t.Fatalf("unexpected value read from body:\ngot: %q\nwant: %q", b, "hello")
6502+
}
6503+
}

src/net/http/server.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1468,7 +1468,13 @@ func (cw *chunkWriter) writeHeader(p []byte) {
14681468
return
14691469
}
14701470

1471-
if w.closeAfterReply && (!keepAlivesEnabled || !hasToken(cw.header.get("Connection"), "close")) {
1471+
// Only override the Connection header if it is not a successful
1472+
// protocol switch response and if KeepAlives are not enabled.
1473+
// See https://golang.org/issue/36381.
1474+
delConnectionHeader := w.closeAfterReply &&
1475+
(!keepAlivesEnabled || !hasToken(cw.header.get("Connection"), "close")) &&
1476+
!isProtocolSwitchResponse(w.status, header)
1477+
if delConnectionHeader {
14721478
delHeader("Connection")
14731479
if w.req.ProtoAtLeast(1, 1) {
14741480
setHeader.connection = "close"

0 commit comments

Comments
 (0)