Skip to content
This repository was archived by the owner on Mar 23, 2026. It is now read-only.

Commit 64a4012

Browse files
authored
allow http.Request object to retain dashes and key casing (#5969)
1 parent 6dd426e commit 64a4012

File tree

2 files changed

+34
-7
lines changed

2 files changed

+34
-7
lines changed

localstack/http/request.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -147,9 +147,16 @@ def __init__(
147147

148148
super(Request, self).__init__(environ)
149149

150+
# restore originally passed headers:
150151
# werkzeug normally provides read-only access to headers set in the WSGIEnvironment through the EnvironHeaders
151-
# class, this makes them mutable.
152-
self.headers = Headers(self.headers)
152+
# class, here we make them mutable again. moreover, WSGI header encoding conflicts with RFC2616. see this github
153+
# issue for a discussion: https://github.com/pallets/werkzeug/issues/940
154+
headers = Headers(headers)
155+
# these two headers are treated separately in the WSGI environment, so we extract them if necessary
156+
for h in ["content-length", "content-type"]:
157+
if h not in headers and h in self.headers:
158+
headers[h] = self.headers[h]
159+
self.headers = headers
153160

154161

155162
def get_raw_path(request) -> str:

tests/unit/http/test_request.py

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -104,12 +104,16 @@ def validate(*args, **kwargs):
104104
validate(headers={"Content-Type": "text/xml", "x-amz-target": "foobar"}, body=b"")
105105

106106

107-
def test_get_content_length():
108-
request = Request("GET", "/", body="foobar", headers={"Content-Length": "7"})
109-
assert request.content_length == 7 # checking that the value from headers take precedence
110-
107+
def test_content_length_is_set_automatically():
108+
# checking that the value is calculated automatically
111109
request = Request("GET", "/", body="foobar")
112-
assert request.content_length == 6 # checking that the value is calculated
110+
assert request.content_length == 6
111+
112+
113+
def test_content_length_is_overwritten():
114+
# checking that the value passed from headers take precedence
115+
request = Request("GET", "/", body="foobar", headers={"Content-Length": "7"})
116+
assert request.content_length == 7
113117

114118

115119
def test_get_custom_headers():
@@ -131,3 +135,19 @@ def test_get_raw_path_with_query():
131135
assert request.path == "/foo/bar/ed"
132136
assert request.environ["RAW_URI"] == "/foo%2Fbar/ed?fizz=buzz"
133137
assert get_raw_path(request) == "/foo%2Fbar/ed"
138+
139+
140+
def test_headers_retain_dashes():
141+
request = Request("GET", "/foo/bar/ed", {"X-Amz-Meta--foo_bar-ed": "foobar"})
142+
assert "x-amz-meta--foo_bar-ed" in request.headers
143+
assert request.headers["x-amz-meta--foo_bar-ed"] == "foobar"
144+
145+
146+
def test_headers_retain_case():
147+
request = Request("GET", "/foo/bar/ed", {"X-Amz-Meta--FOO_BaR-ed": "foobar"})
148+
keys = list(request.headers.keys())
149+
for k in keys:
150+
if k.lower().startswith("x-amz-meta"):
151+
assert k == "X-Amz-Meta--FOO_BaR-ed"
152+
return
153+
pytest.fail(f"key not in header keys {keys}")

0 commit comments

Comments
 (0)