-
-
Notifications
You must be signed in to change notification settings - Fork 4.7k
Description
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
caddy/modules/caddyhttp/encode/encode.go
Lines 155 to 160 in 9c0c71e
| 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
caddy/modules/caddyhttp/encode/encode.go
Lines 250 to 252 in 9c0c71e
| 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
caddy/modules/caddyhttp/encode/encode.go
Lines 262 to 269 in 9c0c71e
| 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.