空チャンクサイズに対するバリデーション追加で500エラーを防止
Pumaでは、チャンク化されたリクエストのサイズが空文字列になると例外が発生し、結果としてクライアントに 500 Internal Server Error が返されていました。本 PR はそのケースを検出し、適切なエラーメッセージで 400 系レスポンスに置き換えることで、サーバーの安定性を向上させます。
背景
Issue #3951 で報告された問題は、トレーラが本文から遅延して送られた際に Puma のチャンクサイズ解析が失敗し、ArgumentError がスローされて 500 応答になる点です。特に空のチャンクサイズ("\r\n\r\n" のようにサイズ部が欠落)に対して to_i が期待外れの引数エラーを引き起こしていました。空サイズは HTTP/1.1 の仕様上不正であり、サーバーは 400 Bad Request を返すべきです。
技術的な変更
lib/puma/client.rb では CHUNK_SIZE_INVALID 正規表現を削除し、代わりに CHUNK_SIZE_VALID を導入しました。新しい正規表現は \A\h+\z とし、16 進数文字のみで構成されるサイズ文字列を許容します。
- CHUNK_SIZE_INVALID = /[^\h]/.freeze
+ CHUNK_SIZE_VALID = /\A\h+\z/.freeze
decode_chunk メソッドのバリデーションロジックを変更し、サイズ文字列が正規表現にマッチしない場合に例外を投げます。空文字列または nil の場合は別メッセージ "Chunk size cannot be empty or nil" を使用し、具体的な原因を伝えます。
- if CHUNK_SIZE_INVALID.match? chunk_hex
- raise HttpParserError, "Invalid chunk size: '#{chunk_hex}'"
+ unless CHUNK_SIZE_VALID.match? chunk_hex
+ err_msg = chunk_hex && !chunk_hex.empty? ?
+ "Invalid chunk size: '#{chunk_hex}'" :
+ "Chunk size cannot be empty or nil"
+ raise HttpParserError, err_msg
end
テストコード test/test_request_single.rb に空チャンクサイズ用のケースを追加し、期待するエラーメッセージが返ることを検証しています。
+ def test_chunked_size_empty
+ te = 'Transfer-Encoding: chunked'
+ chunked = "\r\n\r\n"
+
+ assert_invalid "#{GET_PREFIX}#{te}\r\n\r\n#{chunked}",
+ "Chunk size cannot be empty or nil"
+ end
設計判断
バリデーションの実装では、否定的チェック(unless match?)を選択し、CHUNK_SIZE_VALID が真であることを前提にコードを簡潔に保ちました。空サイズ専用のエラーメッセージを追加したことで、デバッグ情報が明確になり、クライアント側でも原因が把握しやすくなります。定数名を VALID 系に変更した点は、意味論的に「有効かどうか」を直接表現し、将来的な拡張や他モジュールでの再利用を容易にする設計です。
まとめ
この変更により、Puma は空のチャンクサイズを検出して HttpParserError をスローし、結果として 400 系エラーが返されます。例外が 500 へ昇格するケースが排除されたことで、サーバーの耐障害性が向上し、Issue #3951 の最初の課題が解決されました。