From 4aa43a2e48a98cfc7ca3bd832403e096013acc37 Mon Sep 17 00:00:00 2001 From: Bin Jin Date: Thu, 6 Jun 2024 22:15:50 +0800 Subject: [PATCH] waiProxy: Remove Content-Length for Encoded Responses This is a proper fix to #45. In previous merge request (#45), for HTTP/2 connections, Content-Length header is retained in order to avoid problems with Docker client rejecting chunked encoding responses from Docker registry. However, this approach failed when the upstream response was encoded (e.g. with 'Content-Encoding: gzip' in header). The retained 'Content-Length' header then mismatched the actual content size after decoding. To fix this, we now strip the Content-Length header if Content-Encoding header is present, along with our existing checks for cases where chunked encoding is not used (HTTP/2 and HEAD requests). --- ChangeLog.md | 4 ++++ Network/HTTP/ReverseProxy.hs | 10 ++++++---- http-reverse-proxy.cabal | 2 +- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/ChangeLog.md b/ChangeLog.md index aaf1d42..65c474d 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -1,4 +1,8 @@ +## 0.6.0.3 + +* Fix a regression introduced in 0.6.0.2: wrong 'Content-Length' header is preserved for responses with encoded content. [#47](https://github.com/fpco/http-reverse-proxy/pull/47) + ## 0.6.0.2 * Fix docker registry reverse proxying by preserving the 'Content-Length' response header to HTTP/2 and HEAD requests. [#45](https://github.com/fpco/http-reverse-proxy/pull/45) diff --git a/Network/HTTP/ReverseProxy.hs b/Network/HTTP/ReverseProxy.hs index ad82903..5d113d0 100644 --- a/Network/HTTP/ReverseProxy.hs +++ b/Network/HTTP/ReverseProxy.hs @@ -54,7 +54,7 @@ import Data.Functor.Identity (Identity (..)) import Data.IORef import Data.List.NonEmpty (NonEmpty (..)) import qualified Data.List.NonEmpty as NE -import Data.Maybe (fromMaybe, listToMaybe) +import Data.Maybe (fromMaybe, isNothing, listToMaybe) import Data.Monoid (mappend, mconcat, (<>)) import Data.Set (Set) import qualified Data.Set as Set @@ -426,12 +426,14 @@ waiProxyToSettings getDest wps' manager req0 sendResponse = do (awaitForever (\bs -> yield (Chunk $ fromByteString bs) >> yield Flush)) (wpsProcessBody wps req $ const () <$> res) src = bodyReaderSource $ HC.responseBody res - noChunked = HT.httpMajor (WAI.httpVersion req) >= 2 || WAI.requestMethod req == HT.methodHead + headers = HC.responseHeaders res + notEncoded = isNothing (lookup "content-encoding" headers) + notChunked = HT.httpMajor (WAI.httpVersion req) >= 2 || WAI.requestMethod req == HT.methodHead sendResponse $ WAI.responseStream (HC.responseStatus res) (filter (\(key, v) -> not (key `Set.member` strippedHeaders) || - key == "content-length" && (noChunked || v == "0")) - (HC.responseHeaders res)) + key == "content-length" && (notEncoded && notChunked || v == "0")) + headers) (\sendChunk flush -> runConduit $ src .| conduit .| CL.mapM_ (\mb -> case mb of Flush -> flush diff --git a/http-reverse-proxy.cabal b/http-reverse-proxy.cabal index 28952e0..2614837 100644 --- a/http-reverse-proxy.cabal +++ b/http-reverse-proxy.cabal @@ -1,5 +1,5 @@ name: http-reverse-proxy -version: 0.6.0.2 +version: 0.6.0.3 synopsis: Reverse proxy HTTP requests, either over raw sockets or with WAI description: Provides a simple means of reverse-proxying HTTP requests. The raw approach uses the same technique as leveraged by keter, whereas the WAI approach performs full request/response parsing via WAI and http-conduit. homepage: https://github.com/fpco/http-reverse-proxy