Skip to content

Commit ac1f13e

Browse files
authored
fix twisted multi-value header handling (#35)
1 parent 231d767 commit ac1f13e

2 files changed

Lines changed: 35 additions & 2 deletions

File tree

rolo/serving/twisted.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -221,8 +221,9 @@ def writeHeaders(
221221
else:
222222
# newer twisted versions instead pass the headers object
223223
for name, values in headers.getAllRawHeaders():
224-
line = name + b": " + b",".join(values) + b"\r\n"
225-
headerSequence.append(line)
224+
for value in values:
225+
line = name + b": " + value + b"\r\n"
226+
headerSequence.append(line)
226227

227228
headerSequence.append(b"\r\n")
228229
self.transport.writeSequence(headerSequence)

tests/gateway/test_headers.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1+
import http.client
12
import json
3+
from collections import defaultdict
24

35
import pytest
46
import requests
@@ -14,6 +16,8 @@ def handler(chain: HandlerChain, context: RequestContext, response: Response):
1416
response.mimetype = "application/json"
1517
response.headers["X-fOO_bar"] = "FooBar"
1618
response.headers["content-md5"] = "af5e58f9a7c4682e1b410f2e9392a539"
19+
response.headers.add("multi-value", "value1")
20+
response.headers.add("multi-value", "value2")
1721
return response
1822

1923
gateway = Gateway(request_handlers=[handler])
@@ -39,3 +43,31 @@ def handler(chain: HandlerChain, context: RequestContext, response: Response):
3943
assert "X-fOO_bar" in response_headers
4044
# even though it's a standard header, it should be in the original case
4145
assert "content-md5" in response_headers
46+
assert response_headers["multi-value"] == "value1, value2"
47+
48+
49+
@pytest.mark.parametrize("serve_gateway", ["asgi", "twisted"], indirect=True)
50+
def test_multivalue_header_handling(serve_gateway):
51+
def handler(chain: HandlerChain, context: RequestContext, response: Response):
52+
response.data = json.dumps({"headers": dict(context.request.headers)})
53+
response.mimetype = "application/json"
54+
response.headers.add("multi-value", "value1")
55+
response.headers.add("multi-value", "value2")
56+
return response
57+
58+
gateway = Gateway(request_handlers=[handler])
59+
60+
srv = serve_gateway(gateway)
61+
62+
# we need to use a low level HTTP client because `requests` does some header manipulation and concatenation which
63+
# obscures the behavior
64+
conn = http.client.HTTPConnection(host="127.0.0.1", port=srv.port)
65+
66+
conn.request("GET", url="/hello")
67+
response = conn.getresponse()
68+
response_headers = defaultdict(list)
69+
70+
for k, v in response.headers.items():
71+
response_headers[k].append(v)
72+
73+
assert response_headers["multi-value"] == ["value1", "value2"]

0 commit comments

Comments
 (0)