Spring WebFlux × Kotlinで NoHandlerFoundException 対策をする
背景
Spring BootでRest APIを作る際、Spring MVCならNoHandlerFoundExceptionがあるため、設定していないURLにRequestが来た場合のハンドリングは ExceptionHandlerで簡単に実装できる。
@RestControllerAdvice class ErrorAdvice { @ExceptionHandler(value = [NoHandlerFoundException::class]) @ResponseStatus(HttpStatus.BAD_REQUEST) fun noHandleFound(ex: NoHandlerFoundException, req: WebRequest): ErrorResponse { return ErrorResponse("no handler found") } }
しかし、WebFluxでは前述のNoHandlerFoundExceptionが存在しないので、ExceptionHandlerでは実装できない。なので、自分で実装するしかない、多分 (自信なし)。
実装
AbstractErrorWebExceptionHandler を継承したClassを実装する。
ライブラリ
Library | Version |
---|---|
Kotlin | 1.6.0 |
Spring Boot | 2.6.0 |
@Component @Order(-2) class MyExceptionHandler( errorAttributes: ErrorAttributes, applicationContext: ApplicationContext, serverCodecConfigurer: ServerCodecConfigurer, ) : AbstractErrorWebExceptionHandler( errorAttributes, WebProperties.Resources(), applicationContext ) { init { // https://spring.pleiades.io/spring-framework/docs/current/reference/html/web-reactive.html#webflux-config-message-codecs // defaultを設定 this.setMessageWriters(serverCodecConfigurer.writers) this.setMessageReaders(serverCodecConfigurer.readers) } override fun getRoutingFunction(errorAttributes: ErrorAttributes?): RouterFunction<ServerResponse> { return route(all(), this::renderErrorResponse) } private fun renderErrorResponse(serverRequest: ServerRequest): Mono<ServerResponse> { val errorProperties = getErrorAttributes( serverRequest, ErrorAttributeOptions.defaults() ) logger.debug { errorProperties } val errorAttribute = serverRequest .attribute( "org.springframework.boot.web.reactive.error.DefaultErrorAttributes.ERROR" ) if (errorAttribute.isPresent) { val th = errorAttribute.get() if (th is Throwable && th.message == NO_HANDLER_MESSAGE) { return ServerResponse.status(HttpStatus.BAD_REQUEST) .contentType(MediaType.APPLICATION_JSON) .body( BodyInserters.fromValue(ErrorResponse(NO_HANDLER_MESSAGE)) ) } } return ServerResponse.status(HttpStatus.BAD_REQUEST) .contentType(MediaType.APPLICATION_JSON) .body(BodyInserters.fromValue(ErrorResponse("something wrong ;-("))) } companion object { private const val NO_HANDLER_MESSAGE = "404 NOT_FOUND \"No matching handler\"" private val logger = KotlinLogging.logger { } } } data class ErrorResponse( val reason: String )
あとはapplication.ymlでwhitelabelを無効にしておく。
spring: mvc: throw-exception-if-no-handler-found: true web: resources: add-mappings: false server: error: whitelabel: enabled: false
一応動いているから、多分大丈夫、大丈夫だよな。。KotlinでのAbstractErrorWebExceptionHandlerがいくらググっても全然出てこないから若干ゃ不安
参考
Spring Boot Reference Documentation
spring - String insted of whitelabel error page in webflux? - Stack Overflow