こんにちは。 エキサイト株式会社の三浦です。
Spring BootにはExceptionControllerというものがあり、それを使うことでControllerが投げる例外の種類によってリクエスト元に返すステータスコードやレスポンスを制御することが出来ます。
このExceptionControllerにはカスタムのものを設定することもできるのですが、特定の条件下ではカスタムのExceptionControllerをうまく使えないときがあります。
今回は、メディアタイプに関連してカスタムのExceptionControllerが当たらない場合について説明します。
ExceptionController
Spring BootにはExceptionControllerというものがあります。
これにはデフォルトのものも存在するのですが、以下のようにカスタムで設定することも出来ます。
@RestControllerAdvice public class ExceptionController { @ExceptionHandler({NotFoundException.class}) // NotFoundException は、独自の例外用クラス @ResponseStatus(HttpStatus.NOT_FOUND) public Map<String, String> handleNotFoundException(Exception exception) { return Map.of("error", Optional.ofNullable(exception.getMessage()).orElse("")); } @ExceptionHandler({BadRequestException.class}) // BadRequestException は、独自の例外用クラス @ResponseStatus(HttpStatus.BAD_REQUEST) public Map<String, String> handleBadRequestException(Exception exception) { return Map.of("error", Optional.ofNullable(exception.getMessage()).orElse("")); } @ExceptionHandler() @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) public Map<String, String> handleException(Exception exception) { return Map.of("error", Optional.ofNullable(exception.getMessage()).orElse("")); } }
このように設定すると、
- NotFoundException というクラスが例外として投げられたら、リクエスト元に
HttpStatus.NOT_FOUND
(404エラー)を返す - BadRequestExceptionというクラスが例外として投げられたら、リクエスト元に
HttpStatus.BAD_REQUEST
(400エラー)を返す - それ以外の例外が投げられたら、リクエスト元に
HttpStatus.INTERNAL_SERVER_ERROR
(500エラー)を返す
というような挙動となります。
実際に、今回の設定で BadRequestException を投げると、以下のようなエラーが返ってきます。
既存・独自両方の例外クラスを設定でき、非常に便利な機能と言えるでしょう。
Acceptするメディアタイプによっては、ExceptionControllerが当たらない
さて、もう一回、全く同じURL・クエリパラメータで投げてみましょう。
なにやら先程よりログの量が増えていますが、少なくとも BadRequestException が投げられたことはわかります。
レスポンスはどうでしょうか。
なんと、500エラーになっています。
実は、URL・クエリパラメータは変えなかったのですが、それ以外で変更した部分がありました。
それはリクエストヘッダーの Accept
です。
Accept
を text/plain
のみに制限した結果、レスポンスが変化したのです。
改めて、先程のログを良く見てみましょう。
BadRequestException の後に、 ExceptionHandlerExceptionResolver
や HttpMediaTypeNotAcceptableException
というものが続いています。
今回のレスポンスはJSON形式で返ってくるので、 Accept
では application/json
が許容されている必要があったのですが、 text/plain
に限定したことによって「メディアタイプが許容されてない」というエラー( HttpMediaTypeNotAcceptableException
)が起きてしまったのです。
一見すると「この場合は、どのエラーにも引っかからなかったパターンである 500エラー
を返す挙動になりそう」に見えますが、このケースではどうやらカスタムのExceptionControllerではなくデフォルトのExceptionControllerが使用されることになってしまうようで、レスポンスのステータスコードが想定外のものとなってしまったのでした。
まとめ
多くの場合カスタムなExceptionControllerはとても有用ですが、今回のメディアタイプ非許容など、特定の条件下だと自動的にデフォルトのExceptionControllerが使われてしまうケースも存在します。
メディアタイプ非許容に関しては、例えば外部公開のAPIに対してbot等がアクセスする、などで発生する場面もあるでしょう。
「自動的にデフォルトのExceptionControllerが使われる」場合があることを知らないと、謎のステータスコードの発生に対する調査はかなり大変なはずです。
この記事が、少しでもそういった調査の助けになれば幸いです。