Go's httputil.ReverseProxy doesn't work for WebSocket traffic. The issues with it are detailed here. It looks like the fix/feature may be released in Go 1.12 (see "added this to the Go1.12 milestone" here).
As the fix/feature is not out yet, I am trying to write my own reverse proxy to solve the problem until it is released. On my server, I wrote a simple gorilla/websocket handler that upgrades the connection and then waits for a message from the client.
On my reverse proxy server, I wrote a reverse proxy handler based on the Go 1.12 change submission here, especially the additions in reverseproxy.go. Here is the core of my reverse proxy handler, in relevant part:
outreq := r.WithContext(ctx)
if r.ContentLength == 0 {
outreq.Body = nil
}
p.director(outreq)
outreq.Header = copyHeaders(browserHeaders)
setForwardHeader(r.RemoteAddr, outreq)
log.Infof("outreq URL is %+v", outreq.URL)
transport := http.DefaultTransport
res, err := transport.RoundTrip(outreq)
if err != nil {
log.Errorf("RoundTrip(outreq) failed: %s", err)
}
log.Infof("Server response was %+v", res)
if res.StatusCode != http.StatusSwitchingProtocols {
log.Errorf("Did not receive HTTP status 101 switchingn protocols: %d
", res.StatusCode)
return
}
if !hasUpgradeHeaders(res.Header) {
log.Error("request does not contain upgrade headers")
return
}
backConn, ok := res.Body.(io.ReadWriteCloser)
if !ok {
log.Errorf("internal error: 101 switching protocols response with non-writable body: %v (%T)", res.Body, res.Body)
return
}
When I try to connect a websocket from my browser, I get as far as that last if statement. The type assertion of the response body to type io.ReadWriteCloser
failed. When I log the response I see this:
Server response was &{Status:101 Switching Protocols StatusCode:101 Proto:HTTP/1.1
ProtoMajor:1 ProtoMinor:1 Header:map[Upgrade:[websocket] Connection:[Upgrade]
Sec-Websocket-Accept:[B1fWETPTtNo0bAHFWkFn2lAm9iw=]] Body:{} ContentLength:0
TransferEncoding:[] Close:false Uncompressed:false
Trailer:map[] Request:0xc000112100 TLS:<nil>}
The error log output is this:
ERRO[0012] internal error: 101 switching protocols response with non-writable body: {} (http.noBody)
Why am I getting http.noBody
in the response body rather than something implementing the io.ReadWriteCloser
interface? How can I resolve the issue so that I can proceed to hijack the connection and facilitate communication between the browser and the server?
Update
I think I found the answer to the "why" question. The latest release of Go does not include this line in persistConn's readLoop function in transport.go in the net/http package:
if resp.isProtocolSwitch() {
resp.Body = newReadWriteCloserBody(pc.br, pc.conn)
}
Compare to current here.
Any ideas for how to resolve?