Spring Boot な Web アプリケーションでエラーレスポンスを確実に JSON 形式で返却したい
極力手軽に実現する方法についてメモしています。
背景
Spring Boot を使った API サーバにおいて、存在しないパスへの HTTP リクエストを受け付けたときの 404 などエラーレスポンスを返す場合に 確実に JSON 形式でレスポンスを返す (すなわち、HTML を返却させない) ことを考えます。
Spring Boot は、Java の OkHttp や Python の Requests といった各種言語の HTTP クライアントライブラリや curl
コマンドからの HTTP リクエストであれば、素の設定のままでも以下のようにいい感じに JSON 形式のエラーレスポンスを返すことができます。
{
"timestamp": "2020-03-24T12:58:57.602+0000",
"status": 404,
"error": "Not Found",
"message": "No message available",
"path": "/no-such-path"
}
一方で Web ブラウザ、より正確には Accept
ヘッダに text/html
的な値が設定されている HTTP リクエスト の場合は、Spring Boot が 余計な 気を回すおかげで以下のような HTML レスポンスが返却されることになります。
<html>
<body>
<h1>Whitelabel Error Page</h1>
<p>This application has no explicit mapping for /error, so you are seeing this as a fallback.</p>
<div id='created'>Tue Mar 24 22:03:56 JST 2020</div>
<div>There was an unexpected error (type=Not Found, status=404).</div>
<div>No message available</div>
</body>
</html>
この HTML レスポンスは 然るべきエラービューが存在せずフォールバックが発生した場合にレンダリングされる ものなのですが、API サーバとしてはこれでも不都合はないにせよ、エラーレスポンスが JSON になりえないことがあるのは統一感がなく気持ちが悪いので、JSON に統一したいお気持ちがふつふつと湧いてきます。
そういうわけで、なるべく手間をかけずに簡単に、JSON 形式でエラーレスポンスを返却する方法を探ってみます。
エラーレスポンスを JSON で返すようにする簡単な方法
結論から言うと、Spring Boot には content negotiation の機能が備わっている ので、それを利用して常に Content-Type: application/json
な HTTP レスポンスを返却させるようにする、という手段で容易に実現できます。
「常に Content-Type: application/json
を返却させる」設定を実現するには、 WebMvcConfigurer
インタフェース を用います。
このインタフェースを実装したクラスにて configureContentNegotiation()
メソッド をオーバーライドすると ContentNegotiationConfigurer
オブジェクト が引数経由で得られるので、これに application/json
を常に返す ContentNegotiationStrategy
オブジェクトを strategies()
メソッド で設定することになります。
「application/json
を常に返す ContentNegotiationStrategy オブジェクト」は、FixedContentNegotiationStrategy
クラス のオブジェクトを生成すれば用意できます。
具体的には以下のコードのようになります。
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.web.accept.FixedContentNegotiationStrategy;
import org.springframework.web.servlet.config.annotation.*;
import java.util.List;
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
configurer.strategies(
List.of(new FixedContentNegotiationStrategy(MediaType.APPLICATION_JSON)));
// configurer.defaultContentType(MediaType.APPLICATION_JSON) だと、結局 Accept: text/html の場合に
// エラーレスポンスを HTML で返してしまうのでダメ
}
}
この方法であれば、Whitelabel やら ErrorController
やらをゴニョゴニョせずとも目的を果たすことができます。