django / asgiref

ASGI specification and utilities
https://asgi.readthedocs.io/en/latest/
BSD 3-Clause "New" or "Revised" License
1.47k stars 209 forks source link

Documentation suggestion - Cookie header handling for HTTP/2 #420

Closed LucidDan closed 11 months ago

LucidDan commented 11 months ago

While working on an issue with Django and a new ASGI server (Granian), I came across an issue with the Cookie header and HTTP/2. At first I thought Granian had a bug in it, as its quite new, but after investigating, I realised Django's ASGI request handler was just not built to support HTTP/2, and was erroneously mishandling the multiple Cookie headers it received. It took me a while to figure this out as I had to dig into RFCs to get the answer. Working around it was as simple as disabling Granian's HTTP/2 support...but in the process I found a reference in the ASGI spec.

The asgiref docs currently say this (https://asgi.readthedocs.io/en/latest/specs/www.html#http):

Multiple header fields with the same name are complex in HTTP. RFC 7230 states that for any header field that can appear multiple times, it is exactly equivalent to sending that header field only once with all the values joined by commas.

However, RFC 7230 and RFC 6265 make it clear that this rule does not apply to the various headers used by HTTP cookies (Cookie and Set-Cookie). The Cookie header must only be sent once by a user-agent, but the Set-Cookie header may appear repeatedly and cannot be joined by commas. The ASGI design decision is to transport both request and response headers as lists of 2-element [name, value] lists and preserve headers exactly as they were provided.

I think with HTTP/2, this is now a bit unintentionally confusing, because the HTTP/2 RFC says the following (in https://www.rfc-editor.org/rfc/rfc9113#name-compressing-the-cookie-head)

To allow for better compression efficiency, the Cookie header field MAY be split into separate header fields, each with one or more cookie-pairs. If there are multiple Cookie header fields after decompression, these MUST be concatenated into a single octet string using the two-octet delimiter of 0x3b, 0x20 (the ASCII string "; ") before being passed into a non-HTTP/2 context, such as an HTTP/1.1 connection, or a generic HTTP server application.

While I can see how some might interpret an ASGI application as a "generic HTTP server application", the fact that we pass through the http protocol suggests that we're not protocol-agnostic, and the docs above also say "ASGI design decision is to ... preserve headers exactly as they were provided" - it seems it is acceptable for an ASGI server to pass Cookie headers through without concatenation, and applications should be prepared for that if they support HTTP/2.

I think it might be worth rephrasing the documentation to make mention of the additional RFC and the change of behaviour in HTTP/2.

PR on the way with a suggested re-wording.

LucidDan commented 11 months ago

Suggested re-wording #421 ...although happy to get suggestions as it is hard to explain the vagaries of RFCs in a succinct way!

andrewgodwin commented 11 months ago

Ah, the fun of cookie headers! I think what you've suggested in the PR makes sense; while in a future spec we might want to force servers to re-concatenate cookie headers if multiple arrive over HTTP/2, that'd be a breaking change, and the existing behaviour is probably fine anyway given there's other HTTP/2 facets we expose.