目的
Spring Boot で作成した REST API でエラーが発生したときに返る、デフォルトのエラーメッセージを制御したい。
背景
以下のような RestController を用意したとする。
package demo;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class MyRestController {
@RequestMapping(value="/test/{id}")
private String hello(@PathVariable String id){
if ("0000".equals(id)) {
throw new MyCustomException(); // 想定される Exception
}
return "Hello world!!";
}
@RequestMapping(value="/test-null")
private String hello(){
String s = null;
s.equals("hoge"); // うっかり NPE 発生
return "Hello world!!";
}
}
すると、Spring Boot はデフォルトで以下のようなエラーメッセージを返却してくれる。
$ curl http://localhost:8080/test/0000 # 想定される MyCustomError
{"timestamp":1526654632290,"status":500,"error":"Internal Server Error","exception":"my.spring.MySpringBootSample.MyCustomError","message":"No message available","path":"/test/0000"}%
$ curl http://localhost:8080/test-null # うっかり出してしまった NPE
{"timestamp":1526654636310,"status":500,"error":"Internal Server Error","exception":"java.lang.NullPointerException","message":"No message available","path":"/test-null"}%
$ curl http://localhost:8080/wrong-path # パス間違い
{"timestamp":1526654639289,"status":404,"error":"Not Found","message":"No message available","path":"/wrong-path"}%
しかし、うっかりで発生してしまった NullPointerException がユーザに見えてしまうのはあまりにもかっこ悪いし、セキュリティ的にもよろしくない。Spring Boot を使っているのもばれてしまう。
想定されるエラーの制御
@ControllerAdvice
アノテーションを付与したクラスを用意することで、RestController を横断してエラーメッセージを制御することが出来る。
package demo;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
@ControllerAdvice
public class MyControllerAdvice {
/**
* MyCustomException が発生した際のハンドラ
*
* @return レスポンス
*/
@ExceptionHandler(MyCustomException.class)
public ResponseEntity<String> handleControllerException() {
return new ResponseEntity<String>("customized message for MyCustomError.", HttpStatus.INTERNAL_SERVER_ERROR);
}
}
$ curl http://localhost:8080/test/0000
customized message for MyCustomError.%
想定外のエラーに対する制御
上記に加えて、Exception.class を受ける ExceptionHandler を作ってしまえば、むりやり、RestController で発生するあらゆる想定外のエラーに対して一律にカスタムエラーメッセージを返すことも出来なくはない。
package demo;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
@ControllerAdvice
public class MyControllerAdvice {
/**
* MyCustomException が発生した際のハンドラ
*
* @return レスポンス
*/
@ExceptionHandler(MyCustomException.class)
public ResponseEntity<String> handleControllerException() {
return new ResponseEntity<String>("customized message for MyCustomError.", HttpStatus.INTERNAL_SERVER_ERROR);
}
/**
* Exception が発生した際のハンドラ
*
* @return レスポンス
*/
@ExceptionHandler(Exception.class)
public ResponseEntity<String> handleControllerException2() {
return new ResponseEntity<String>("customized message for any unexpected exception.", HttpStatus.INTERNAL_SERVER_ERROR);
}
}
しかしこの方法では、パス間違いなど、RestController に入る以前のエラーは制御できない。
$ curl http://localhost:8080/test/0000
customized message for MyCustomError.%
$ curl http://localhost:8080/test-null
customized message for any unexpected exception.%
$ curl http://localhost:8080/wrong-path # パス間違いに対してはデフォルトエラーが返ってしまう
{"timestamp":1526655182855,"status":404,"error":"Not Found","message":"No message available","path":"/wrong-path"}%
調べてみると、公式に以下のような記述が。
Spring Boot provides an /error mapping by default that handles all errors in a sensible way, and it is registered as a ‘global’ error page in the servlet container.
要は、あらゆるエラーはデフォルトで /error
以下にマッピングされるとのこと。
好きに制御したい場合は、ErrorController を実現せよ、とのことなので、以下のようなクラスを用意する。
package demo;
import org.springframework.boot.autoconfigure.web.ErrorController;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class MyErrorController implements ErrorController {
@RequestMapping("/error")
public ResponseEntity<String> handleError() {
return new ResponseEntity<String>("customized message for any unhandled error.", HttpStatus.INTERNAL_SERVER_ERROR);
}
@Override
public String getErrorPath() {
return "/error";
}
}
すると無事、デフォルトのエラーメッセージが表示されなくなった。
$ curl http://localhost:8080/test/0000
customized message for MyCustomError.%
$ curl http://localhost:8080/test-null
customized message for any unexpected exception.%
$ curl http://localhost:8080/wrong-path
customized message for any unhandled error.%