Skip to content

Commit 06655f7

Browse files
authored
fix Transfer-Encoding behavior in input for SimpleRequestsClient (#39)
1 parent 0071a79 commit 06655f7

3 files changed

Lines changed: 83 additions & 1 deletion

File tree

rolo/client.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,10 @@ def request(self, request: Request, server: str | None = None) -> Response:
128128
if not request.headers.get("accept-encoding"):
129129
headers["accept-encoding"] = urllib3.util.SKIP_HEADER
130130

131+
# we need to remove `Transfer-Encoding` because it is a hop-by-hop header, and most of the `Request` objects
132+
# passed are coming from a webserver, which already decoded the request
133+
headers.pop("Transfer-Encoding", None)
134+
131135
response = self.session.request(
132136
method=request.method,
133137
# use raw base url to preserve path url encoding

tests/test_client.py

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,3 +58,57 @@ def multi_values_handler(_request: WerkzeugRequest) -> Response:
5858
with SimpleRequestsClient() as client:
5959
response = client.request(request, url)
6060
assert response.headers.getlist("Set-Cookie") == ["value1", "value2"]
61+
62+
def test_chunked_encoded_request(self, httpserver: HTTPServer):
63+
# because SimpleRequestsClient mostly forwards an existing incoming request, and it uses `restore_payload`
64+
# this means that any `Transfer-Encoding` headers must be stripped before sending
65+
66+
def transfer_encoded_handler(_request: WerkzeugRequest) -> Response:
67+
return Response(response=_request.data)
68+
69+
httpserver.expect_request("/").respond_with_handler(transfer_encoded_handler)
70+
71+
url = httpserver.url_for("/")
72+
body = b"hello world"
73+
request = Request(
74+
path="/",
75+
method="POST",
76+
body=body,
77+
headers={
78+
"Transfer-Encoding": "chunked",
79+
"Content-Length": str(len(body)),
80+
},
81+
)
82+
83+
with SimpleRequestsClient() as client:
84+
response = client.request(request, url)
85+
86+
assert response.data == body
87+
88+
def test_gzip_encoded_request(self, httpserver: HTTPServer):
89+
def transfer_encoded_handler(_request: WerkzeugRequest) -> Response:
90+
return Response(response=_request.data)
91+
92+
httpserver.expect_request("/").respond_with_handler(transfer_encoded_handler)
93+
94+
url = httpserver.url_for("/")
95+
raw_body = b"hello world"
96+
request = Request(
97+
path="/",
98+
method="POST",
99+
# we need to use the raw body here, because in real world use case, the webserver would have read and
100+
# decoded the payload
101+
body=raw_body,
102+
headers={
103+
"Transfer-Encoding": "gzip",
104+
"Content-Length": str(len(raw_body)),
105+
},
106+
)
107+
108+
with SimpleRequestsClient() as client:
109+
response = client.request(request, url)
110+
111+
assert response.data == raw_body
112+
# we're making sure we're not passing the `Transfer-Encoding` gzip along, as we're read the body and sent it
113+
# decoded over the wire
114+
assert "Transfer-Encoding" not in httpserver.log[0][0].headers

tests/test_proxy.py

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,9 @@ def _handler(request: WerkzeugRequest):
183183
}
184184

185185
@pytest.mark.parametrize("chunked", [True, False])
186-
def test_proxy_handler_transfer_encoding(self, router_server, httpserver: HTTPServer, chunked):
186+
def test_proxy_handler_transfer_encoding_output(
187+
self, router_server, httpserver: HTTPServer, chunked
188+
):
187189
router, proxy = router_server
188190
backend = httpserver
189191
body = "enough-for-content-length"
@@ -328,6 +330,28 @@ def _handler(_request: Request) -> Response:
328330

329331
assert response.data == body
330332

333+
def test_proxy_handler_transfer_encoding_input(self, router_server, httpserver: HTTPServer):
334+
router, proxy = router_server
335+
backend = httpserver
336+
337+
def _handler(_request: WerkzeugRequest):
338+
# the handler/httpserver fixture will automatically decode the request. if the request was malformed, it
339+
# would fail
340+
return Response(_request.data)
341+
342+
backend.expect_request("").respond_with_handler(_handler)
343+
344+
router.add("/", ProxyHandler(backend.url_for("/")))
345+
346+
def gen():
347+
yield b"fizz\n"
348+
yield b"buzz\n"
349+
yield b"done"
350+
351+
response = requests.post(proxy.url, data=gen())
352+
353+
assert response.text == "fizz\nbuzz\ndone"
354+
331355

332356
@pytest.mark.parametrize("consume_data", [True, False])
333357
def test_forward_files_and_form_data_proxy_consumes_data(

0 commit comments

Comments
 (0)