Как вернуть 404 с помощью Spring WebFlux

У меня такой контроллер (в Котлине):

@RestController
@RequestMapping("/")
class CustomerController (private val service: CustomerService) {
    @GetMapping("/{id}")
    fun findById(@PathVariable id: String,
                 @RequestHeader(value = IF_NONE_MATCH) versionHeader: String?): Mono<HttpEntity<KundeResource>> =
        return service.findById(id)
            .switchIfEmpty(Mono.error(NotFoundException()))
            .map {
                // ETag stuff ...
                ok().eTag("...").body(...)
            }
}

Мне интересно, есть ли лучший подход, чем выброс исключения, помеченного @ResponseStatus(code = NOT_FOUND)


person Juergen Zimmermann    schedule 18.08.2017    source источник


Ответы (3)


Я хотел бы использовать RouteFunction вместо @RestController, когда Spring 5 стабилен. Определите HandlerFunction для обработки запроса, а затем объявите RouteFunction для сопоставления запроса с HandlerFunction:

public Mono<ServerResponse> get(ServerRequest req) {
    return this.posts
        .findById(req.pathVariable("id"))
        .flatMap((post) -> ServerResponse.ok().body(Mono.just(post), Post.class))
        .switchIfEmpty(ServerResponse.notFound().build());
}

См. Полные примеры кодов здесь.

Версия Kotlin, определите функцию для обработки запроса, используйте RouteFunctionDSL для сопоставления входящего запроса с HandlerFuncation:

fun get(req: ServerRequest): Mono<ServerResponse> {
    return this.posts.findById(req.pathVariable("id"))
            .flatMap { post -> ok().body(Mono.just(post), Post::class.java) }
            .switchIfEmpty(notFound().build())
}

Это может быть выражение, например:

fun get(req: ServerRequest): Mono<ServerResponse> = this.posts.findById(req.pathVariable("id"))
            .flatMap { post -> ok().body(Mono.just(post), Post::class.java) }
            .switchIfEmpty(notFound().build())

Полные примеры кодов Kotlin DSL можно найти здесь.

Если вы предпочитаете традиционные контроллеры для предоставления REST API, попробуйте этот подход.

Сначала определите исключение, например. PostNotFoundException. Потом закидывай в контроллер.

 @GetMapping(value = "/{id}")
    public Mono<Post> get(@PathVariable(value = "id") Long id) {
        return this.posts.findById(id).switchIfEmpty(Mono.error(new PostNotFoundException(id)));
    }

Определите ExceptionHandler для обработки исключения и зарегистрируйте его в HttpHandler.

@Profile("default")
@Bean
public NettyContext nettyContext(ApplicationContext context) {
    HttpHandler handler = WebHttpHandlerBuilder.applicationContext(context)
        .exceptionHandler(exceptionHandler())
        .build();
    ReactorHttpHandlerAdapter adapter = new ReactorHttpHandlerAdapter(handler);
    HttpServer httpServer = HttpServer.create("localhost", this.port);
    return httpServer.newHandler(adapter).block();
}

@Bean
public WebExceptionHandler exceptionHandler() {
    return (ServerWebExchange exchange, Throwable ex) -> {
        if (ex instanceof PostNotFoundException) {
            exchange.getResponse().setStatusCode(HttpStatus.NOT_FOUND);
            return exchange.getResponse().setComplete();
        }
        return Mono.error(ex);
    };
}

Ознакомьтесь с полными кодами здесь. Пользователи Spring Boot могут ознакомиться с этим примером .

Обновление : В последней весне 5.2 я обнаружил, что в целом @RestControllerAdvice работает с контроллерами в приложениях webflux.

person Hantsy    schedule 19.08.2017
comment
В вашем примере кода GitHub здесь: github.com/hantsy/spring-reactive-sample/blob/master/routes/src/ для метода create все работает нормально, но когда тело запроса пусто, он возвращает HTTP 200 ? !!! Какие-либо предложения - person Mr.Q; 26.09.2019
comment
Добавьте проверку для тела запроса, см. Этот вопрос: stackoverflow.com/questions/48031647/, я надеюсь, что для этого есть официальные решения. - person Hantsy; 30.09.2019

Вы можете использовать ResponseStatusException, просто расширите свое исключение:

public class YourLogicException extends ResponseStatusException {

public YourLogicException(String message) {
    super(HttpStatus.NOT_FOUND, message);
}

public YourLogicException(String message, Throwable cause) {
    super(HttpStatus.NOT_FOUND, message, cause);
}

А в сервисе:

public Mono<String> doLogic(Mono<YourContext> ctx) {
    return ctx.map(ctx -> doSomething(ctx));
}

private String doSomething(YourContext ctx) {
    try {
        // some logic
    } catch (Exception e) {
        throw new YourLogicException("Exception message", e);
    }
}

И после этого у вас может быть красивое сообщение:

 { "timestamp": 00000000, "path": "/endpoint", "status": 404, "error": "Not found", "message": "Exception message" }
person mchernyakov    schedule 19.11.2018

Вместо того, чтобы генерировать исключение, реализация метода может быть изменена на

fun findById(@PathVariable id: String,
             @RequestHeader(value = IF_NONE_MATCH) versionHeader: String?): Mono<ResponseEntity<KundeResource>> =
    return service.findById(id)
        .map {
            // ETag stuff ...
            ok().eTag("...").body(...)
        }
        .defaultIfEmpty(notFound().build())
person Juergen Zimmermann    schedule 18.08.2017