Skip to content

Commit c7f967d

Browse files
committed
encode/httpx#1214 prefer sending outbound cookies separately to improve headers compression
1 parent 63b6b97 commit c7f967d

File tree

2 files changed

+67
-0
lines changed

2 files changed

+67
-0
lines changed

src/h2/utilities.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -603,6 +603,30 @@ def _combine_cookie_fields(headers, hdr_validation_flags):
603603
yield NeverIndexedHeaderTuple(b'cookie', cookie_val)
604604

605605

606+
def _split_outbound_cookie_fields(headers, hdr_validation_flags):
607+
"""
608+
RFC 7540 § 8.1.2.5 allows for better compression efficiency,
609+
to split the Cookie header field into separate header fields
610+
611+
We want to do it for outbound requests, as we are doing for
612+
inbound
613+
"""
614+
for header in headers:
615+
if header[0] in (b'cookie', 'cookie'):
616+
needle = b'; ' if isinstance(header[0], bytes) else '; '
617+
618+
if needle in header[1]:
619+
for cookie_val in header[1].split(needle):
620+
if isinstance(header, HeaderTuple):
621+
yield header.__class__(header[0], cookie_val)
622+
else:
623+
yield header[0], cookie_val
624+
else:
625+
yield header
626+
else:
627+
yield header
628+
629+
606630
def normalize_outbound_headers(headers, hdr_validation_flags):
607631
"""
608632
Normalizes a header sequence that we are about to send.
@@ -611,6 +635,7 @@ def normalize_outbound_headers(headers, hdr_validation_flags):
611635
:param hdr_validation_flags: An instance of HeaderValidationFlags.
612636
"""
613637
headers = _lowercase_header_names(headers, hdr_validation_flags)
638+
headers = _split_outbound_cookie_fields(headers, hdr_validation_flags)
614639
headers = _strip_surrounding_whitespace(headers, hdr_validation_flags)
615640
headers = _strip_connection_headers(headers, hdr_validation_flags)
616641
headers = _secure_headers(headers, hdr_validation_flags)

test/test_basic_logic.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
import hyperframe
1111
import pytest
12+
from hpack import HeaderTuple
1213

1314
import h2.config
1415
import h2.connection
@@ -789,6 +790,47 @@ def test_headers_are_lowercase(self, frame_factory):
789790

790791
assert c.data_to_send() == expected_frame.serialize()
791792

793+
def test_outbound_cookie_headers_are_split(self):
794+
"""
795+
We should split outbound cookie headers according to
796+
RFC 7540 - 8.1.2.5
797+
"""
798+
cookie_headers = [
799+
HeaderTuple('cookie',
800+
'username=John Doe; expires=Thu, 18 Dec 2013 12:00:00 UTC'),
801+
('cookie', 'path=1'),
802+
('cookie', 'test1=val1; test2=val2')
803+
]
804+
805+
expected_cookie_headers = [
806+
HeaderTuple('cookie', 'username=John Doe'),
807+
HeaderTuple('cookie', 'expires=Thu, 18 Dec 2013 12:00:00 UTC'),
808+
('cookie', 'path=1'),
809+
('cookie', 'test1=val1'),
810+
('cookie', 'test2=val2'),
811+
]
812+
813+
client_config = h2.config.H2Configuration(
814+
client_side=True,
815+
header_encoding='utf-8'
816+
)
817+
server_config = h2.config.H2Configuration(
818+
client_side=False,
819+
normalize_inbound_headers=False,
820+
header_encoding='utf-8'
821+
)
822+
client = h2.connection.H2Connection(config=client_config)
823+
server = h2.connection.H2Connection(config=server_config)
824+
825+
client.initiate_connection()
826+
client.send_headers(1, self.example_request_headers + cookie_headers, end_stream=True)
827+
828+
e = server.receive_data(client.data_to_send())
829+
830+
cookie_fields = [(n, v) for n, v in e[1].headers if n == 'cookie']
831+
832+
assert cookie_fields == expected_cookie_headers
833+
792834
@given(frame_size=integers(min_value=2**14, max_value=(2**24 - 1)))
793835
@settings(suppress_health_check=[HealthCheck.function_scoped_fixture])
794836
def test_changing_max_frame_size(self, frame_factory, frame_size):

0 commit comments

Comments
 (0)