HTTP/2 Header Frames

I have recently come across a GitHub issue 'Allow the encoder to split headers across frames' in the dotnet/aspnetcore repository, that made me look into and understand some of the details how HTTP/2 encodes headers.

In this post I will summarize my key findings on how ASP.NET Core writes these headers today. In this post I use version ASP.NET Core 8 with .NET 8. My findings are based on the current state of the source code and the corresponding RFC7540.

HTTP/2

Here are my key learnings:

  • RFC7540 describes the HTTP/2 protocol's related details.

  • In HTTP/2 a request-response pair is serialized in a stream.

  • A stream consists of message frames.

  • Frames have a type, frame header, a given size and corresponding data. Frames are associated with a given stream with the stream ID.

  • HTTP/2 requests start with HEADER frame. A header frame may be followed by CONTINUATION frames containing further headers.

  • The HTTP/2 request headers HPack encoded and split into HEADER and CONTINUATION frames.

  • ASP.NET Core's HPackEncoder is in namespace System.Net.Http.HPack, and is source shared with the .NET runtime, because the type is internal.

  • HPACK uses a static and a dynamic table to encode/decode header fields. The static table contains the predefined header fields. The dynamic table is an amendable table to reference repeated non-predefined header fields. The size of the dynamic table is strictly bounded. The default Header Table size is 4096, but this size does not correspond to frame size.

  • In ASP.NET Core's Kestrel HPackHeaderWriter static class writes the headers.

    • HPackHeaderWriter iterates over the headers/trailers (except for the response status header which is written separately) using the Http2FrameWriter types.

    • Writes each encoded header to the buffer of the current frame.

    • When the buffer is full the remainder of the headers are written into the following frame.

  • Http2FrameWriter creates the buffer which it passes as a Span<byte> to the header writer. The default size of the buffer is 16K, but it can be updated via Kestrel's option by setting the Http2Limits's MaxFrameSize property. Clients can also influence the frame size by sending a value in the SETTING frame. As long as the value is between the min and max allowed frame size of the server, Kestrel will use the requested frame size.

  • While the current ASP.NET Core implementation tries to write each header in its entirety to the HEADER buffer, the protocol itself does not specify such requirements. In fact, HPACK encoded headers data can be split up at an arbitrary position into consecutive CONTINUATION frames.