現象
- Node.js(v12.3.1)で立てたWebサーバにアクセスすると、時折HTTPリクエストに失敗する
- Cookieを削除したり、ブラウザを再起動すると治ることもあるが、根本的な原因がわからない
サンプルコード
const http = require('http');
const server = http.createServer((req, res) => {
res.writeHead(200, {'Content-Type': 'text/plain'});
res.end('Hello World');
});
server.listen(8080);
原因
-
Node.jsの最大HTTPリクエストヘッダサイズのデフォルト値である8kBを越えるHTTPリクエストヘッダサイズを送信していたことが原因だった
-
Node.jsは、2018/11にDoS攻撃の脆弱性対応として、デフォルトのHTTPリクエストヘッダの最大サイズを変更前の80kBから8kB(8192Bytes)に変更する修正が加えられた
-
デフォルトでは、HTTPリクエストヘッダのサイズが8kBを越えるとソケットが強制破棄されて「431 Request Header Fields Too Large」を返す
$ npm start
> sample-nodejs-header-overflow@1.0.0 start /../../../sample-nodejs-header-overflow
> node index.js
// curlで8kB以上のHTTPリクエストを送信
ErrorCode: HPE_HEADER_OVERFLOW
BytesParsed: 8559
// curlで8kB以上のHTTPリクエストを送信
ErrorCode: HPE_HEADER_OVERFLOW
BytesParsed: 8559
// curlで8kB以上のHTTPリクエストを送信
ErrorCode: HPE_HEADER_OVERFLOW
BytesParsed: 9085
対策
- HTTPリクエストヘッダのサイズが超えた場合に起こるclientErrorイベントを補足して、ソケットが強制的に破棄されないようにエラーハンドリングを行う
const http = require('http');
const server = http.createServer((req, res) => {
res.writeHead(200, {'Content-Type': 'text/plain'});
res.end('Hello World');
});
server.on('clientError', (err, socket) => {
console.log('ErrorCode: ', err.code);
console.log('BytesParsed: ', err.bytesParsed);
socket.end('HTTP/1.1 400 Bad Request\r\n\r\n');
});
server.listen(8080);
- アプリケーション起動時に「--max-http-header-size」という起動オプションを設定して、Node.jsが受け取る最大のHTTPリクエストヘッダサイズを増やす
$ node --max-http-header-size=16384 index.js
おわりに
今回リクエストヘッダのサイズが8kBを越えた主な原因は、多数のCookieを使ってWebサーバにアクセスしていたことでした。
仕様上Cookieの数が多くなり、HTTPリクエストのサイズに不安がある場合は、エラーハンドリングを正しく実装して、起動オプションでNode.jsが受け取るHTTPリクエストサイズの最大値を上げておくと良いかと思います。