[kamal-proxy] 不正なChunked Encodingリクエストを400エラーとして適切に処理
Context
HTTPリクエストのChunked Transfer Encodingが不正な形式の場合、kamal-proxyはこれまでサーバー側のエラー(500 Internal Server Error)として処理していました。しかし、これは本質的にクライアント側の問題であり、誤ったステータスコードを返していました。この変更により、不正なチャンク形式を検出した際に、より適切な400 Bad Requestを返すようになります。
Technical Detail
エラー判定ロジックの実装
新しく追加された errors.go では、Go標準ライブラリのchunked encoding実装が返す特定のエラーメッセージを文字列マッチングで判定します。
func isChunkedEncodingError(err error) bool {
if err == nil {
return false
}
switch err.Error() {
case "invalid byte in chunk length",
"http chunk length too large",
"malformed chunked encoding",
"trailer header without chunked transfer encoding",
"too many trailers":
return true
}
return false
}
Go標準ライブラリは errors.New() でプレーンなエラーを返すため、型アサーションではなく文字列マッチングを使用する必要があります。
リクエストバッファリング時のエラーハンドリング
request_buffer_middleware.go では、エラーの種類に応じて適切なHTTPステータスコードを返すよう修正されました。
if errors.Is(err, ErrMaximumSizeExceeded) {
SetErrorResponse(w, r, http.StatusRequestEntityTooLarge, nil)
} else if isChunkedEncodingError(err) {
slog.Info("Malformed chunked request", "path", r.URL.Path, "error", err)
SetErrorResponse(w, r, http.StatusBadRequest, nil)
} else {
slog.Error("Error buffering request", "path", r.URL.Path, "error", err)
SetErrorResponse(w, r, http.StatusInternalServerError, nil)
}
この変更により、以下のエラー分類が可能になります:
- リクエストサイズ超過 → 413 Request Entity Too Large
- Chunked Encoding不正 → 400 Bad Request
- その他のサーバーエラー → 500 Internal Server Error
プロキシレベルでのエラーハンドリング
target.go でも同様の判定ロジックが追加され、バッファリングを使用しない場合でも不正なChunked Encodingを適切に処理できるようになりました。
if isChunkedEncodingError(err) {
slog.Info("Malformed request", "target", t.Address(), "path", r.URL.Path, "error", err)
SetErrorResponse(w, r, http.StatusBadRequest, nil)
return
}
テストケースの追加
不正なChunked Encodingリクエストを再現するため、生のTCPコネクションを使用したテストが追加されました。
rawRequest := "POST / HTTP/1.1\r\n" +
"Host: example.com\r\n" +
"Transfer-Encoding: chunked\r\n" +
"\r\n" +
"ZZ\r\n" // 不正なチャンクサイズ(16進数でない)
_, err = conn.Write([]byte(rawRequest))
require.NoError(t, err)
resp, err := http.ReadResponse(bufio.NewReader(conn), nil)
require.NoError(t, err)
assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
このテストでは、チャンクサイズに16進数ではない "ZZ" を指定することで、意図的にエラーを発生させています。
まとめ
この変更により、kamal-proxyはHTTPプロトコルの規約に則った適切なエラーレスポンスを返すようになりました。クライアント起因のエラー(不正なChunked Encoding)とサーバー起因のエラーを明確に区別することで、デバッグがより容易になり、クライアント側でも問題の特定が迅速に行えるようになります。