✅ 現象の整理
項目 | 内容 |
---|---|
連携元 |
Accept-Encoding: gzip を付けて HTTP リクエスト送信。つまり「gzipでもいいよ」と宣言。 |
連携先(本来のサーバ) | gzip 圧縮されたレスポンスを返し、Content-Encoding: gzip をヘッダーに付ける。OK。 |
Proxy | ヘッダー Content-Encoding: gzip はそのまま残して中身(body)も gzip 圧縮のまま通している。 |
あなたのアプリ |
WebClient + reactor-netty を使って受信し、MDC に body を put (この時点で文字化け)。最終的にそのまま連携元に返却するとエラー発生。 |
❗ 問題点
-
WebClient が自動で gzip 解凍していない(通常は
reactor-netty
が自動解凍するが、Content-Encoding
に応じた処理が一部の構成でスキップされることがある)。 - 文字化けの原因は gzip 圧縮されたバイナリを文字列として扱ったため。
-
MDC に入れた body が読めないのは、gzip 解凍前の生データを
String
として無理に扱ったため。 - 連携元が gzip を受け入れると言ったのに、gzip のまま返してしまった。ただし、その内容が正しく解凍可能な gzip でなければ連携元側でもエラーになります。
✅ 誰に責任がある?
当事者 | 判断 |
---|---|
連携元 |
Accept-Encoding: gzip を送るのは HTTP/1.1 標準仕様。責任なし。 |
連携先 |
Content-Encoding: gzip を付けて返しているのは正しい。問題なし。 |
Proxy | 通常、gzip 圧縮レスポンスを中継するならそのままにするのが正しい。削除してはいけない。なので問題なし。 |
あなたのアプリ(中継サービス) | |
👉 ここで解凍されずに文字列化・MDC格納・レスポンス返却したのが直接的な原因です。 |
✅ 正しい対処・設計
1. WebClient で受け取ったときに gzip 解凍されているか確認
webClient.get()
.uri("...")
.acceptEncoding("gzip") // 自分からも gzip OK と言っている?
.retrieve()
.bodyToMono(byte[].class) // byte配列で受け取る
.map(bytes -> encodingUtils.autoDecode(bytes, StandardCharsets.UTF_8))
2. MDC に入れる前に文字列化(必要なら解凍)
String decodedBody = encodingUtils.autoDecode(bodyBytes, StandardCharsets.UTF_8);
MDC.put("body", decodedBody);
3. レスポンス返却時には圧縮し直すか、非圧縮で返す
-
連携元が
Accept-Encoding: gzip
を指定しているなら gzip で返してもいいが、- 中継処理内で一度解凍した場合、再圧縮しないとそのままでは返せない!
-
逆に、圧縮レスポンスを再構成しないなら
Content-Encoding
を削除する必要あり。
✅ 最終的な判断
連携元が
Accept-Encoding: gzip
を送っているのは正しい。問題はあなたのアプリ側で "gzip圧縮されたbodyを文字列として誤って扱った"、"再圧縮せずにそのまま返した" ことにある。
✅ 推奨方針(簡易)
- WebClient の受信時点で
byte[]
で受け取り、gzipか確認して手動解凍。 - MDCやログ、body表示には 文字列化された内容を使う(生バイナリはNG)。
- 再送信するときは、解凍済みのものを非圧縮で送るか、明示的に再 gzip 圧縮して
Content-Encoding: gzip
を付けて返す。