Spring Boot の Whitelabel Error Page と JSON レスポンス

Spring Boot のデフォルトの状態では、404 Not Found 等が発生した際に Whitelabel Error Page というエラーページが表示される。

```
Whitelabel Error Page

This application has no explicit mapping for /error, so you are seeing this as a fallback.

Mon Nov 18 22:16:56 JST 2019
There was an unexpected error (type=Not Found, status=404).
No message available
```

curl で同等の動きを確認するには -H オプションでリクエストヘッダに accept: text/html を指定する。

```
$ curl -H "accept: text/html" http://localhost:8080/
<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'>Mon Nov 18 22:18:01 JST 2019</div><div>There was an unexpected error (type=Not Found, status=404).</div><div>No message available</div></body></html>
```

-H オプションをつけない場合は JSON フォーマットのデータが返ってくる。

```
$ curl http://localhost:8080/
{"timestamp":"2019-11-18T13:19:03.320+0000","status":404,"error":"Not Found","message":"No message available","path":"/"}
```

Spring Boot のドキュメントには、マシンクライアントでは JSON レスポンスを生成し、ブラウザクライアントには HTML 形式で表示する whitelabel エラービューを生成すると書いてある。

[Spring Boot Reference Documentation](https://docs.spring.io/spring-boot/docs/2.2.1.RELEASE/reference/htmlsingle/#boot-features-error-handling)

> For machine clients, it produces a JSON response with details of the error, the HTTP status, and the exception message. For browser clients, there is a “whitelabel” error view that renders the same data in HTML format (to customize it, add a View that resolves to error).

Spring Boot でエラーページを処理しているのは BasicErrorController クラス。

BasicErrorController クラスの errorHtml メソッドには、@RequestMapping アノテーションで produces = MediaType.TEXT_HTML_VALUE が指定されている。

そのためリクエストの Accept ヘッダに text/html が含まれる場合にこの errorHtml メソッドが呼び出される。
また Accept ヘッダに text/html が含まれないリクエストの場合は error メソッドが呼び出される。

errorHtml メソッドは HTML のレスポンスを返し、 error メソッドは JSON のレスポンスを返している。

以下に該当箇所のソースコードを示す。

[spring\-boot/BasicErrorController\.java at v2\.2\.1\.RELEASE · spring\-projects/spring\-boot · GitHub](https://github.com/spring-projects/spring-boot/blob/v2.2.1.RELEASE/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/error/BasicErrorController.java)

```java
@RequestMapping(produces = MediaType.TEXT_HTML_VALUE)
public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
  HttpStatus status = getStatus(request);
  Map<String, Object> model = Collections
      .unmodifiableMap(getErrorAttributes(request, isIncludeStackTrace(request, MediaType.TEXT_HTML)));
  response.setStatus(status.value());
  ModelAndView modelAndView = resolveErrorView(request, response, status, model);
  return (modelAndView != null) ? modelAndView : new ModelAndView("error", model);
}

@RequestMapping
public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
  HttpStatus status = getStatus(request);
  if (status == HttpStatus.NO_CONTENT) {
    return new ResponseEntity<>(status);
  }
  Map<String, Object> body = getErrorAttributes(request, isIncludeStackTrace(request, MediaType.ALL));
  return new ResponseEntity<>(body, status);
}
```

text/html のような Content Type を示すクラスである MediaType は Spring Boot ではなく Spring Framework が提供している。

[spring\-framework/MediaType\.java at v5\.2\.1\.RELEASE · spring\-projects/spring\-framework · GitHub](https://github.com/spring-projects/spring-framework/blob/v5.2.1.RELEASE/spring-web/src/main/java/org/springframework/http/MediaType.java)

```java
/**
 * A String equivalent of {@link MediaType#TEXT_HTML}.
 */
public static final String TEXT_HTML_VALUE = "text/html";
```

参考:

- [Spring Boot エラーページの最低限のカスタマイズ \(ErrorController インターフェースの実装\) \- Qiita](https://qiita.com/niwasawa/items/52722528c9755f8159a1)
- [Spring Boot で 404 Not Found などのエラーが発生した際の表示をカスタマイズする \- Qiita](https://qiita.com/niwasawa/items/f3479ef16efa488039fb)