Skip to content

reverse_proxy: h2 websocket stuck if client needs to send the first message and encode is enabled #6733

@WeidiDeng

Description

@WeidiDeng

With this merged, I noticed some of my websockets failed to connect while others succeed.

I disabled encode and found out now these requests connect successfully.

The problem is that client requests contain the header accept-encoding: gzip, deflate, br, zstd, which is wrapped

for _, encName := range AcceptedEncodings(r, enc.Prefer) {
if _, ok := enc.writerPools[encName]; !ok {
continue // encoding not offered
}
w = enc.openResponseWriter(encName, w)
defer w.(*responseWriter).Close()

This response writer will write informational header immediately

if 100 <= status && status <= 199 {
rw.ResponseWriter.WriteHeader(status)
}

During websocket upgrade, for http1, the upgrade will write 101 status,

rw.WriteHeader(res.StatusCode)

which will be flushed to the client immediately.

However, for h2, although FlushError is called

flushErr := http.NewResponseController(rw).Flush()

response code is 200 same as any other status that indicates success. Because we want to determine if the response can be encoded, this code is stored and flushed later if there is any data from the upstream and we can be sure whether the response can be encoded

func (rw *responseWriter) FlushError() error {
if !rw.wroteHeader {
// flushing the underlying ResponseWriter will write header and status code,
// but we need to delay that until we can determine if we must encode and
// therefore add the Content-Encoding header; this happens in the first call
// to rw.Write (see bug in #4314)
return nil
}

If the websocket transport a protocol that server sends response first, it is OK as that will cause the status and data to be sent. But if the client sends data first, it won't happen because the response will never be sent, and client thinks the handshake didn't finish.

For now, I created a matcher to ignore encoding for h2 websocket upgrade

@not_h2_ws not {
    header :protocol *
    method CONNECT
    protocol http/2
}
encode @not_h2_ws zstd gzip

But I think an elegant solution should be implemented.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bug 🐞Something isn't workinghelp wanted 🆘Extra attention is needed

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions