HTTP/2 is slowly becoming more and more widespread, and as it does, confusion gets to spread with it.

Recently a colleague at work asked why they were getting lowercase header names from their local development web server.

After a few hours of digging, it turns out that:

  1. This developer had recently upgraded their machine to Windows 10
  2. Windows 10 ships with IIS 10
  3. IIS 10 adds support for HTTP/2

HTTP/2 uses HPACK header compression (RFC 7541). Without getting into all the details, there are a few important points regarding headers:

  • HTTP/2 headers are typically sent over the wire either zero or one times. They can be sent more times, but as far as I know, this is unusual.
  • Common headers are defined in a static table (see RFC 7541 Appendix A), which is hardcoded into HTTP/2 servers and clients. For these headers, the header names are never transmitted. Instead, an index into this table is transmitted.
    • The HTTP method, path, status code, authority and scheme are now considered headers. Traditionally, this was a special first line of the request or response (e.g. GET /path/to/file HTTP/1.1 or HTTP/1.1 200 OK).
  • Other headers, including custom headers, can go into a first-in-first-out table. Whilst they remain in this dynamic table, the header names are not sent over the wire. Instead, an index into this table is transmitted.

With regards to the headers defined in the static table, these are defined in the specification as being in lowercase.

Furthermore, the HTTP/2 specification (RFC 7540) states:

Just as in HTTP/1.x, header field names are strings of ASCII characters that are compared in a case-insensitive fashion. However, header field names MUST be converted to lowercase prior to their encoding in HTTP/2. A request or response containing uppercase header field names MUST be treated as malformed -- RFC 7540, Hypertext Transfer Protocol Version 2 (HTTP/2), Section 8.1.2

So with the rollout of HTTP/2, all headers are now being converted to lowercase.

If you have any code that treats headers as case-sensitive, this code is wrong. This isn't new though. As the excerpt above hints at, RFC 2616 states:

Each header field consists of a name followed by a colon (":") and the field value. Field names are case-insensitive. -- RFC 2616, Hypertext Transfer Protocol -- HTTP/1.1, Section 4.2

HTTP header names have been case-insensitive since 1999.[1] If you have code anywhere that cares about case sensitivity of HTTP headers, go fix it.

With the rollout of HTTP/2 though, you're now more likely to encounter the case of the header keys not being what you expected it to be.

  1. Shout out to John McCaskey of Valve Corporation who first pointed this out to me a few years ago when I got mad that one of the Steam Web API headers was kebab-case instead of Train-Case. ↩︎