[kamal-proxy] 不正なChunked Encodingリクエストを400エラーとして適切に処理

basecamp/kamal-proxy

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)とサーバー起因のエラーを明確に区別することで、デバッグがより容易になり、クライアント側でも問題の特定が迅速に行えるようになります。

記事メタデータ

Generated by:
Claude Sonnet 4.5 for DiffDaily

この記事はAIによって自動生成されています。内容の正確性については、必ずソースコードやPRを確認してください。

品質レビュー結果

Review Status:
承認済み
Review Count:
1回
Reviewed by:
Gemini 2.5 Pro for DiffDaily

Review Criteria:

ガイドライン準拠 ✓ PASS

記事構成とDiffDaily Styleへの準拠状況

記事構成の3要素(Title, Context, Technical Detail)が明確に記載されています。コードブロック前後の空行やファイル名付きハイライトなど、カスタムMarkdown構文も正しく使用されており、可読性が高いです。対象読者であるエンジニアに適した技術レベルで書かれています。

  • 記事構成(Title、Context、Technical Detail)
  • DiffDaily Styleガイド準拠
  • カスタムMarkdown活用
  • 対象読者への適合性
技術的整合性 ✓ PASS

技術的な正確性と表現の適切性

記事で引用されているコードスニペットは、PRの目的である「不正なChunked Encodingリクエストに対する400エラー返却」を達成するための妥当な変更です。Goの標準ライブラリのエラーハンドリングの特性(文字列マッチングの必要性)に関する説明も技術的に正確です。

  • 技術用語の正確性
  • コード例の正確性
  • 説明の技術的正確性
PR内容との整合性 ✓ PASS

元のPR情報との一致度

記事の主張はすべてPRのタイトル('Report malformed chunked requests as 400, not 500')と、記事内で示されたコード変更によって裏付けられています。PR情報にない憶測や根拠のない主張(ハルシネーション)は見られませんでした。

  • タイトル・説明の一致
  • Diff内容の正確な反映
  • 推測の排除