Skip to content

Commit fac1eca

Browse files
tomchristiepgjones
authored andcommitted
Clean up public API for Headers data structure
1 parent 7da585b commit fac1eca

File tree

3 files changed

+49
-9
lines changed

3 files changed

+49
-9
lines changed

h11/_headers.py

Lines changed: 36 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -63,21 +63,49 @@
6363

6464

6565
class Headers:
66-
__slots__ = ['raw_items']
67-
68-
def __init__(self, raw_items):
69-
self.raw_items = raw_items
66+
"""
67+
A list-like interface that allows iterating over headers as byte-pairs
68+
of (lowercased-name, value).
69+
70+
Internally we actually store the representation as three-tuples,
71+
including both the raw original casing, in order to preserve casing
72+
over-the-wire, and the lowercased name, for case-insensitive comparisions.
73+
74+
r = Request(
75+
method="GET",
76+
target="/",
77+
headers=[("Host", "example.org"), ("Connection", "keep-alive")],
78+
http_version="1.1",
79+
)
80+
assert r.headers == [
81+
(b"host", b"example.org"),
82+
(b"connection", b"keep-alive")
83+
]
84+
assert r.headers.raw_items() == [
85+
(b"Host", b"example.org"),
86+
(b"Connection", b"keep-alive")
87+
]
88+
"""
89+
90+
def __init__(self, full_items):
91+
self._full_items = full_items
7092

7193
def __iter__(self):
72-
for _, name, value in self.raw_items:
94+
for _, name, value in self._full_items:
7395
yield name, value
7496

7597
def __bool__(self):
76-
return bool(self.raw_items)
98+
return bool(self._full_items)
7799

78100
def __eq__(self, other):
79101
return list(self) == list(other)
80102

103+
def __len__(self):
104+
return len(self._full_items)
105+
106+
def raw_items(self):
107+
return [(raw_name, value) for raw_name, _, value in self._full_items]
108+
81109

82110
def normalize_and_validate(headers, _parsed=False):
83111
new_headers = []
@@ -158,7 +186,7 @@ def get_comma_header(headers, name):
158186
# "100-continue". Splitting on commas is harmless. Case insensitive.
159187
#
160188
out = []
161-
for _, found_name, found_raw_value in headers.raw_items:
189+
for _, found_name, found_raw_value in headers._full_items:
162190
if found_name == name:
163191
found_raw_value = found_raw_value.lower()
164192
for found_split_value in found_raw_value.split(b","):
@@ -171,7 +199,7 @@ def get_comma_header(headers, name):
171199
def set_comma_header(headers, name, new_values):
172200
# The header name `name` is expected to be lower-case bytes.
173201
new_headers = []
174-
for found_raw_name, found_name, found_raw_value in headers.raw_items:
202+
for found_raw_name, found_name, found_raw_value in headers._full_items:
175203
if found_name != name:
176204
new_headers.append((found_raw_name, found_raw_value))
177205
for new_value in new_values:

h11/_writers.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ def write_headers(headers, write):
3838
# "Since the Host field-value is critical information for handling a
3939
# request, a user agent SHOULD generate Host as the first header field
4040
# following the request-line." - RFC 7230
41-
raw_items = headers.raw_items
41+
raw_items = headers._full_items
4242
for raw_name, name, value in raw_items:
4343
if name == b"host":
4444
write(bytesmod(b"%s: %s\r\n", (raw_name, value)))

h11/tests/test_events.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,3 +163,15 @@ def test_intenum_status_code():
163163
assert r.status_code == HTTPStatus.OK
164164
assert type(r.status_code) is not type(HTTPStatus.OK)
165165
assert type(r.status_code) is int
166+
167+
168+
def test_header_casing():
169+
r = Request(
170+
method="GET",
171+
target="/",
172+
headers=[("Host", "example.org"), ("Connection", "keep-alive")],
173+
http_version="1.1",
174+
)
175+
assert len(r.headers) == 2
176+
assert r.headers == [(b"host", b"example.org"), (b"connection", b"keep-alive")]
177+
assert r.headers.raw_items() == [(b"Host", b"example.org"), (b"Connection", b"keep-alive")]

0 commit comments

Comments
 (0)