Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to return 404 with Spring WebFlux

I'm having a controller like this one (in Kotlin):

@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(...)
            }
}

Im wondering whether there is a better approach than throwing an exception which is annotated with @ResponseStatus(code = NOT_FOUND)

like image 595
Juergen Zimmermann Avatar asked Aug 18 '17 08:08

Juergen Zimmermann


1 Answers

I would like use RouteFunction instead of @RestController when Spring 5 is stable. Define a HandlerFunction to handle request, and then declare a RouteFunction to map request to the 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());
}

Check the complete example codes here.

Kotlin version, define a function to handle request, the use RouteFunctionDSL to map incoming request to 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())
}

It is can be an expression, like:

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())

Check the complete example codes of Kotlin DSL here.

If you prefer traditional controllers to expose REST APIs, try this approach.

Firstly define an exception, eg. PostNotFoundException. Then throw it in controller.

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

Define an ExceptionHandler to handle the exception, and register it in 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);
    };
}

Check the complete codes here. For Spring Boot users, check this sample.

Update:In the latest spring 5.2, I found the general @RestControllerAdvice works for controllers in webflux applications.

like image 96
Hantsy Avatar answered Sep 23 '22 06:09

Hantsy