Skip to content

Commit 716912d

Browse files
committed
net/http: allow upgrading non keepalive connections
Fixes #36381 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 commit ensures net/http's Server does not override the connection header for successful protocol switch responses.
1 parent 3a6cd4c commit 716912d

File tree

3 files changed

+65
-7
lines changed

3 files changed

+65
-7
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 header
362+
// 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: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -251,7 +251,9 @@ func TestHostHandlers(t *testing.T) {
251251
for _, h := range handlers {
252252
mux.Handle(h.pattern, stringHandler(h.msg))
253253
}
254-
ts := httptest.NewServer(mux)
254+
ts := httptest.NewUnstartedServer(mux)
255+
ts.Config.SetKeepAlivesEnabled(false)
256+
ts.Start()
255257
defer ts.Close()
256258

257259
conn, err := net.Dial("tcp", ts.Listener.Addr().String())
@@ -6443,3 +6445,50 @@ func BenchmarkResponseStatusLine(b *testing.B) {
64436445
}
64446446
})
64456447
}
6448+
func TestDisableKeepAliveUpgrade(t *testing.T) {
6449+
if testing.Short() {
6450+
t.Skip("skipping in short mode")
6451+
}
6452+
6453+
setParallel(t)
6454+
defer afterTest(t)
6455+
6456+
s := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) {
6457+
c, _, err := w.(Hijacker).Hijack()
6458+
if err != nil {
6459+
return
6460+
}
6461+
defer c.Close()
6462+
6463+
io.Copy(c, c)
6464+
}))
6465+
defer s.Close()
6466+
6467+
s.Client().Transport.(*Transport).DisableKeepAlives = true
6468+
6469+
resp, err := s.Client().Get("/")
6470+
if err != nil {
6471+
t.Fatalf("failed to perform request: %v", err)
6472+
}
6473+
defer resp.Body.Close()
6474+
6475+
rwc, ok := resp.Body.(io.ReadWriteCloser)
6476+
if !ok {
6477+
t.Fatalf("body is not a io.ReadWriteCloser: %T", resp.Body)
6478+
}
6479+
6480+
_, err = rwc.Write([]byte("hello"))
6481+
if err != nil {
6482+
t.Fatalf("failed to write to body: %v", err)
6483+
}
6484+
6485+
b := make([]byte, 5)
6486+
_, err = io.ReadFull(rwc, b)
6487+
if err != nil {
6488+
t.Fatalf("failed to read from body: %v", err)
6489+
}
6490+
6491+
if string(b) != "hello" {
6492+
t.Fatalf("unexpected value read from body: %q", b)
6493+
}
6494+
}

src/net/http/server.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1290,7 +1290,10 @@ func (cw *chunkWriter) writeHeader(p []byte) {
12901290
if !connectionHeaderSet {
12911291
setHeader.connection = "keep-alive"
12921292
}
1293-
} else if !w.req.ProtoAtLeast(1, 1) || w.wantsClose {
1293+
} else if !w.req.ProtoAtLeast(1, 1) ||
1294+
// Only close if the request indicates the client wants to close
1295+
// and we are not upgrading the connection.
1296+
(w.wantsClose && !isProtocolSwitchResponse(w.status, header)) {
12941297
w.closeAfterReply = true
12951298
}
12961299

0 commit comments

Comments
 (0)