星になれたなら

つみあげログです

nginx / ELB の 414 URI Too Long の対処

nginx や AWS の ELB を使っていると、 414 URI Too Long というエラーに遭遇することがあります。 このエラーの対処方法や切り分け方を以下に記します。

414 URI Too Long

このステータスコードは何なのか

414 というステータスコードは何を意味しているのでしょうか。

標準化団体のひとつである IETFRFC に以下のような記述があります。

The 414 (URI Too Long) status code indicates that the server is refusing to service the request because the request-target (Section 5.3 of [RFC7230]) is longer than the server is willing to interpret.

RFC 7231 - Hypertext Transfer Protocol (HTTP/1.1): Semantics and Content

request-target が、Web サーバーでは扱えないくらい長いことを示すためのステータスコードだ、ということです。

(RFC ではこのあと、このエラーが送出される典型例がぱらぱらと書いてありますが、読み飛ばして問題ありません。)

HTTP Request

まず、基本的な知識を押さえておきましょう。

HTTP は Hypertext Transfer Protocol と言われる通り、プロトコルの一種です。

このプロトコルで Request はどのように定義されているのでしょうか。

GET /where?q=now HTTP/1.1 # ... Request Target / Request Line
Host: www.example.org     # ... Request Headers
Authorization: Bearer xxx

hoge                      # ... Request Body

典型例はこの通りです。

HTTP では、TCP 1 でのコネクションを確立したのち、このようなテキストを送信します 2

一行目を含む、Request Body の開始を意味する空行までの行を Request Header と呼びます。 うち、一行目だけは特別に RFC では request-target 、俗に Request Line と呼んでいます。

ミドルウェア、インフラにおける実態

nginx

nginx においては Request Header の解釈時にバッファがあふれたときにエラーが送出されます。

(ref. https://github.com/nginx/nginx/blob/bfc5b35827903a3c543b58e4562db8b62021c164/src/http/ngx_http_request.c#L1187-L1205)

どのようなバッファでしょうか。

nginx は Request Header を読む際に二種類のバッファを用います。 Client Header BufferLarge Client Header Buffer です。

Nginx HTTP Server (邦題: マスタリング nginx) 3 を読んでみましょう。

client_header_buffer_size

This directive allows you to define the size of the buffer that Nginx allocates to request headers. Usually, 1k is enough. However, in some cases, the headers contain large chunks of cookie data or the request URI is lengthy. If that is the case, then Nginx allocates one or more larger buffers (the size of larger buffers is defined by the large_client_header_buffers directive).

client_header_buffer_size - Nginx HTTP Server - Fourth Edition [Book]

いわく Client Header Buffer というのは、Request Header を読むときのバッファです。 ソースコードを読むとわかるのですが、nginx は送られてきたテキストを一行ずつこのバッファにまるまる格納しようとします。 このバッファよりも Request Header の一行が大きかった場合、 Large Client Header Buffer に格納を試み、 それでも格納できなかった場合はエラーを返す、という処理になっています。

なお、Request Line を読んだときにバッファがあふれた場合は 414 が、 Request Line 以外の Request Header を読んだときにバッファがあふれた場合は 400 が返ります。

ref. http://nginx.org/en/docs/http/ngx_http_core_module.html#large_client_header_buffers

ELB

ALB と NLB とで扱いが異なります。

ALB に関しては Request Line 、それ以外の Request Header それぞれ、 Request Header 全体に対して最大バッファサイズが決まっています。 Request Header 全体に対して上限があるのが特徴で、 URI だけでなく他のヘッダ(巨大になりやすいものでいうと Cookie とか)まで含めて、バッファに収まるか気にする必要があります。

ref. https://docs.aws.amazon.com/elasticloadbalancing/latest/application/load-balancer-limits.html#HTTP_headers

このクォータは固定値で、調整ができません。

また、少なくとも Request Line が長すぎた場合は 414 が返却されます。 (ほかのヘッダが長すぎた場合に何が返るかは未検証です)

NLB に関しては記述がありません。 これは NLB が L4 の LB であり、HTTPが L7 のプロトコルであるため、上位レイヤーで何をやっているかに関心がないためでしょう。

ref. Quotas for your Network Load Balancers - Elastic Load Balancing

この記事でも NLB に関してはスコープ外とします。

対処方法

全体的な方針

Request Header の長すぎる行、特に 414 発生時は Request Line を短くできないか検討してください。

nginx の場合

large_client_header_buffers のバッファサイズに大きな値を指定し、 Large Client Header Buffer のサイズを大きくしてください 4

なお、 Client Header Buffer のサイズを大きくしてもバッファがあふれなくなりますが、 バッファサイズが大きすぎると Request Header が小さくても余分にメモリアロケートをすることになり、非効率的です。 小さなバッファで済むような処理は小さなバッファで済ませたほうがパフォーマンスの面では優れるので、 414 ないし 400 が送出されたときに Client Header Buffer のサイズを変更しないほうがよいでしょう。

ELB の場合

クォータが固定値なので、Request Header の長すぎる行を短くしてください。

まとめ

  • nginx / ELB には Request Header を解釈するバッファが存在する
    • バッファがあふれたときは、
      • Request Line の解釈であれば 414 が返却される
      • ↑以外の Request Header の解釈時であれば 400 が返却される(nginx / ALB で未検証)
  • 414 が送出された時は、
    • Request Line を短いものにできないか検討する
    • nginx を使っている場合は Large Client Header Buffer を大きくしてもよい

おわりに

蛇口のひねりかたに終始せず、蛇口の向こう側がどうなっているか知ることができた、それはとてもよかったのかなと思います。

今回は nginx に二種類のバッファがあることを突き止めました。 小さくて効率の良いバッファを使いながら、どのように大きな値を処理するか、という観点ではおもしろい実装なのかなと思います。 Cは読むの慣れてないので、完全に理解するまでに時間かかりそうですが……。

エンジニアリングは常々、現実の複雑性とその抽象化に向き合う営みなので、抽象化されているものから複雑性が顔を出したり、複雑性を抽象化したり、蛇口の向こう側を知ることが力になるのかな、と思っています。ドメイン知識、実装テクニック、ものの捉え方、といった形で。

参考文献


  1. HTTP/3 (HTTP over QUIC) では UDP を使います
  2. テキストを送るのは HTTP/1.1 のような素朴なプロトコルのみです。このとき、 telnet:80 に対してテキストを打っても HTTP としては正常なリクエストになります。HTTP over SSL/TSL(HTTPS)、HTTP/2、HTTP/3 ではより効率的・安全なデータの送受信を行うために、送受信する値や暗号化有無が異なります。
  3. nginx の公式ドキュメントを読んでもよいのですが、マスタリング nginx の方が体系知がない状態でもやさしい表現がなされているように感じます。
  4. バッファの数は任意です。大きなバッファを同時に多数要求するようなワークロードでは多めに用意しておくとうまく処理できるでしょう。