mirror of https://github.com/golang/go.git
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.
This commit is contained in:
parent
3a6cd4c72d
commit
dc0de83201
|
|
@ -352,10 +352,16 @@ func (r *Response) bodyIsWritable() bool {
|
|||
return ok
|
||||
}
|
||||
|
||||
// isProtocolSwitch reports whether r is a response to a successful
|
||||
// protocol upgrade.
|
||||
// isProtocolSwitch reports whether the response code and header
|
||||
// indicate a successful protocol upgrade response.
|
||||
func (r *Response) isProtocolSwitch() bool {
|
||||
return r.StatusCode == StatusSwitchingProtocols &&
|
||||
r.Header.Get("Upgrade") != "" &&
|
||||
httpguts.HeaderValuesContainsToken(r.Header["Connection"], "Upgrade")
|
||||
return isProtocolSwitchResponse(r.StatusCode, r.Header)
|
||||
}
|
||||
|
||||
// isProtocolSwitchResponse reports whether the response code and
|
||||
// response header indicate a successful protocol upgrade response.
|
||||
func isProtocolSwitchResponse(code int, h Header) bool {
|
||||
return code == StatusSwitchingProtocols &&
|
||||
h.Get("Upgrade") != "" &&
|
||||
httpguts.HeaderValuesContainsToken(h["Connection"], "Upgrade")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6443,3 +6443,56 @@ func BenchmarkResponseStatusLine(b *testing.B) {
|
|||
}
|
||||
})
|
||||
}
|
||||
func TestDisableKeepAliveUpgrade(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("skipping in short mode")
|
||||
}
|
||||
|
||||
setParallel(t)
|
||||
defer afterTest(t)
|
||||
|
||||
s := httptest.NewUnstartedServer(HandlerFunc(func(w ResponseWriter, r *Request) {
|
||||
w.Header().Set("Connection", "Upgrade")
|
||||
w.Header().Set("Upgrade", "someProto")
|
||||
w.WriteHeader(StatusSwitchingProtocols)
|
||||
c, _, err := w.(Hijacker).Hijack()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer c.Close()
|
||||
|
||||
io.Copy(c, c)
|
||||
}))
|
||||
s.Config.SetKeepAlivesEnabled(false)
|
||||
s.Start()
|
||||
defer s.Close()
|
||||
|
||||
cl := s.Client()
|
||||
cl.Transport.(*Transport).DisableKeepAlives = true
|
||||
|
||||
resp, err := cl.Get(s.URL)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to perform request: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
rwc, ok := resp.Body.(io.ReadWriteCloser)
|
||||
if !ok {
|
||||
t.Fatalf("Response.Body is not a io.ReadWriteCloser: %T", resp.Body)
|
||||
}
|
||||
|
||||
_, err = rwc.Write([]byte("hello"))
|
||||
if err != nil {
|
||||
t.Fatalf("failed to write to body: %v", err)
|
||||
}
|
||||
|
||||
b := make([]byte, 5)
|
||||
_, err = io.ReadFull(rwc, b)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to read from body: %v", err)
|
||||
}
|
||||
|
||||
if string(b) != "hello" {
|
||||
t.Fatalf("unexpected value read from body:\ngot: %q\nwant: %q", b, "hello")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1448,7 +1448,13 @@ func (cw *chunkWriter) writeHeader(p []byte) {
|
|||
return
|
||||
}
|
||||
|
||||
if w.closeAfterReply && (!keepAlivesEnabled || !hasToken(cw.header.get("Connection"), "close")) {
|
||||
// Only override the Connection header if it is not a successful
|
||||
// protocol switch response and if KeepAlives are not enabled.
|
||||
// See https://golang.org/issue/36381.
|
||||
delConnectionHeader := w.closeAfterReply &&
|
||||
(!keepAlivesEnabled || !hasToken(cw.header.get("Connection"), "close")) &&
|
||||
!isProtocolSwitchResponse(w.status, header)
|
||||
if delConnectionHeader {
|
||||
delHeader("Connection")
|
||||
if w.req.ProtoAtLeast(1, 1) {
|
||||
setHeader.connection = "close"
|
||||
|
|
|
|||
Loading…
Reference in New Issue