@@ -147,28 +147,41 @@ private HttpResponseMessage CreateResponseMessage(List<string> responseLines)
147147 }
148148 }
149149
150- // TODO: We'll need to refactor this in the future.
150+ // Response handling is based on headers and type. The implementation currently covers
151+ // four main cases:
151152 //
152- // Depending on the request and response (headers), we need to handle the response
153- // differently. We need to distinguish between four types of responses:
153+ // 1. Chunked transfer encoding (HTTP/1.1 `Transfer-Encoding: chunked`)
154+ // 2. HTTP responses with a `Content-Length` header
155+ // - For 101 Switching Protocols, `Content-Length` is ignored per RFC 9110
156+ // 3. Protocol upgrades (HTTP 101 + `Upgrade: tcp`)
157+ // - e.g., `/containers/{id}/attach` or `/exec/{id}/start`
158+ // 4. Streaming responses without connection upgrade headers
159+ // - e.g., `/containers/{id}/logs`
154160 //
155- // 1. Chunked transfer encoding
156- // 2. HTTP with a `Content-Length` header
157- // 3. Hijacked TCP connections (using the connection upgrade headers)
158- // - `/containers/{id}/attach`
159- // - `/exec/{id}/start`
160- // 4. Streams without the connection upgrade headers
161- // - `/containers/{id}/logs`
161+ // This separation ensures chunked framing, streaming, and upgraded connections are handled
162+ // correctly, while tolerating proxies that incorrectly send `Content-Length: 0` on upgrades.
163+
164+ var isSwitchingProtocols = response . StatusCode == HttpStatusCode . SwitchingProtocols ;
162165
163166 var isConnectionUpgrade = response . Headers . TryGetValues ( "Upgrade" , out var responseHeaderValues )
164- && responseHeaderValues . Any ( header => "tcp" . Equals ( header ) ) ;
167+ && responseHeaderValues . Any ( header => "tcp" . Equals ( header , StringComparison . OrdinalIgnoreCase ) ) ;
165168
166169 var isStream = content . Headers . TryGetValues ( "Content-Type" , out var contentHeaderValues )
167- && contentHeaderValues . Any ( header => DockerStreamHeaders . Contains ( header ) ) ;
170+ && contentHeaderValues . Any ( DockerStreamHeaders . Contains ) ;
168171
169- var isChunkedTransferEncoding = ( response . Headers . TransferEncodingChunked . GetValueOrDefault ( ) && ! isStream ) || ( isStream && ! isConnectionUpgrade ) ;
172+ // Treat the response as chunked for standard HTTP chunked or Docker raw-streams,
173+ // but not for upgraded connections.
174+ var isChunkedTransferEncoding = ( response . Headers . TransferEncodingChunked . GetValueOrDefault ( ) && ! isConnectionUpgrade )
175+ || ( ! isConnectionUpgrade && isStream ) ;
170176
171- content . ResolveResponseStream ( chunked : isChunkedTransferEncoding ) ;
177+ if ( isSwitchingProtocols && isConnectionUpgrade )
178+ {
179+ content . ResolveResponseStream ( false , true ) ;
180+ }
181+ else
182+ {
183+ content . ResolveResponseStream ( isChunkedTransferEncoding , isConnectionUpgrade ) ;
184+ }
172185
173186 return response ;
174187 }
0 commit comments