Skip to content

encode/httpx#1214 prefer sending outbound cookies separately to improve headers compression #1275

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged

Conversation

cdeler
Copy link
Contributor

@cdeler cdeler commented Dec 28, 2022

According to the rfc7540#section-8.1.2.5,

To allow for better compression efficiency, the Cookie header field
MAY be split into separate header fields

The h2 framework is used by the httpx client, and some users require to have an ability to send cookies via separate headers to follow http2 practice. Or at least to make this option configurable (more: encode/httpx#1214).

As h2 decides how to properly send data over the transport, h2 seems to be a right place to add an ability to split the cookie headers. Also the opposite functionality is added to the inbound cookies processing (ref).

Notes:

I have concerns about my PR, may be you can help me with suggestings:

  • I used the "server" connection in the client connections tests. I don't know if it's a right way to do
  • may be it makes sense to hide this feature under a config? If so, do you have suggestions where to add it?

if isinstance(header, HeaderTuple):
yield header.__class__(header[0], cookie_val)
else:
yield header[0], cookie_val
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At this point, what are the trade-offs to simply always create HeaderTuple's?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we use the same approach in

h2/src/h2/utilities.py

Lines 549 to 552 in c7f967d

if isinstance(header, HeaderTuple):
yield header.__class__(header[0].strip(), header[1].strip())
else:
yield (header[0].strip(), header[1].strip())
, I don't mind either way.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure about that, but I see that all outbound cookies are handled using this pattern: isinstance(header, HeaderTuple): ...

h2/src/h2/utilities.py

Lines 534 to 538 in 63b6b97

for header in headers:
if isinstance(header, HeaderTuple):
yield header.__class__(header[0].lower(), header[1])
else:
yield (header[0].lower(), header[1])

h2/src/h2/utilities.py

Lines 548 to 552 in 63b6b97

for header in headers:
if isinstance(header, HeaderTuple):
yield header.__class__(header[0].strip(), header[1].strip())
else:
yield (header[0].strip(), header[1].strip())

Also I'm not sure about using NeverIndexedHeaderTuple (e.g. how it can negatively affect compression)

@sethmlarson
Copy link
Member

Question for some context: if a client sends Cookie headers that are already split does h2 combine them somewhere or will the split header fields be encoded as expected?

@Kriechi
Copy link
Member

Kriechi commented Dec 28, 2022

@sethmlarson I asked myself the same thing, and found this:

h2/src/h2/utilities.py

Lines 583 to 603 in c7f967d

def _combine_cookie_fields(headers, hdr_validation_flags):
"""
RFC 7540 § 8.1.2.5 allows HTTP/2 clients to split the Cookie header field,
which must normally appear only once, into multiple fields for better
compression. However, they MUST be joined back up again when received.
This normalization step applies that transform. The side-effect is that
all cookie fields now appear *last* in the header block.
"""
# There is a problem here about header indexing. Specifically, it's
# possible that all these cookies are sent with different header indexing
# values. At this point it shouldn't matter too much, so we apply our own
# logic and make them never-indexed.
cookies = []
for header in headers:
if header[0] == b'cookie':
cookies.append(header[1])
else:
yield header
if cookies:
cookie_val = b'; '.join(cookies)
yield NeverIndexedHeaderTuple(b'cookie', cookie_val)

@cdeler cdeler requested a review from Kriechi January 2, 2023 05:22
@Kriechi
Copy link
Member

Kriechi commented Jan 2, 2023

as a first step to a review, I fixed our project CI (which was a challenge by itself).
#1276

I don't see any obvious issues with the code itself - though I still need time to think and read through the spec and implications. Happy to hear other thoughts in the mean time!

@cdeler you said:

some users require to have an ability
and
to add an ability to split

I don't see an "option" in your PR - its "always on"?
Or do you think this should be the new one-and-only way to handle cookies?

@cdeler cdeler force-pushed the cdeler-split-outbound-cookie-headers branch from 48f26a2 to 65b719c Compare January 3, 2023 06:52
@cdeler cdeler force-pushed the cdeler-split-outbound-cookie-headers branch from 65b719c to a48a2fe Compare January 7, 2023 05:29
@cdeler
Copy link
Contributor Author

cdeler commented Jan 7, 2023

Hello @Kriechi

Or do you think this should be the new one-and-only way to handle cookies?

I don't think so (because RFC says "may") :-) , so that I updated the PR adding split_outbound_cookies flag to the h2.config.H2Configuration (False by default)

@Kriechi
Copy link
Member

Kriechi commented Jan 7, 2023

LGTM!

@Kriechi Kriechi merged commit 0c15f1f into python-hyper:master Jan 11, 2023
@cdeler cdeler deleted the cdeler-split-outbound-cookie-headers branch January 11, 2023 21:42
@kuntaoKZ
Copy link

Hello,
seems this change was not covered in 4.1.0 version?

Kriechi added a commit to Kriechi/hyper-h2 that referenced this pull request Dec 21, 2024
Kriechi added a commit to Kriechi/hyper-h2 that referenced this pull request Jan 22, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants