在Spring Webflux功能应用程序中验证请求的最佳方法是什么

Han*_*tsy 5 spring functional-programming bean-validation project-reactor spring-webflux

在传统的Web应用程序中,很容易通过控制器方法来验证请求主体。

ResponseEntity create(@Valid @ResponseBody Post post) {
} 
Run Code Online (Sandbox Code Playgroud)

如果它是一个MVC应用程序,我们可以通过注入收集错误BindingResult,并决定是否有来自输入表单的一些验证错误。

在页面中,存在一些Freemarker和Thymeleaf的帮助程序来显示消息。

但是,当我来到Webflux并尝试使用它RouterFunction来定义应用程序中的路由时。例如,

Mono<ServerResponse> create(ServerRequest req) {
    return req.bodyToMono(Post.class)
    .flatMap { this.posts.save(it) }
    .flatMap { ServerResponse.created(URI.create("/posts/".concat(it.getId()))).build() }
}

@Bean
RouterFunction<ServerResponse> routes(PostHandler postController) {
    return route(GET("/posts"), postController.&all)
    .andRoute(POST("/posts"), postController.&create)
    .andRoute(GET("/posts/{id}"), postController.&get)
    .andRoute(PUT("/posts/{id}"), postController.&update)
    .andRoute(DELETE("/posts/{id}"), postController.&delete)
}
Run Code Online (Sandbox Code Playgroud)

一种可能的方法是将请求数据(MonoFlux)转换为阻止并注入Validator并手动验证它们。

但是我认为代码看起来有些丑陋。

如何优雅地处理请求正文或表单数据的验证?

是否有更好的方法来验证请求正文或表单数据,并且不会丢失WEB(呈现视图)和REST应用程序的功能和响应功能?

Tos*_*aki 7

我为这个 porpose 开发了“Yet Another Validator”。

https://github.com/making/yavi

如果 YAVI 能够满足您的期望,那就太好了。

验证代码如下所示:

static RouterFunction<ServerResponse> routes() {
    return route(POST("/"), req -> req.bodyToMono(User.class) //
            .flatMap(body -> validator.validateToEither(body) //
                    .leftMap(violations -> {
                        Map<String, Object> error = new LinkedHashMap<>();
                        error.put("message", "Invalid request body");
                        error.put("details", violations.details());
                        return error;
                    })
                    .fold(error -> badRequest().syncBody(error), //
                          user -> ok().syncBody(user))));
}
Run Code Online (Sandbox Code Playgroud)


Yur*_*kov 3

我在应用程序中设法做到这一点的方法之一如下(代码是用 Kotlin 编写的,但想法是相同的)。我已经声明了RequestHandler执行验证的类:

@Component
class RequestHandler(private val validator: Validator) {

    fun <BODY> withValidBody(
            block: (Mono<BODY>) -> Mono<ServerResponse>,
            request: ServerRequest, bodyClass: Class<BODY>): Mono<ServerResponse> {

        return request
                .bodyToMono(bodyClass)
                .flatMap { body ->
                    val violations = validator.validate(body)
                    if (violations.isEmpty())
                        block.invoke(Mono.just(body))
                    else
                        throw ConstraintViolationException(violations)
                }
    }
}
Run Code Online (Sandbox Code Playgroud)

请求对象可以通过以下方式包含 java 验证注释:

data class TokenRequest constructor(@get:NotBlank val accessToken: String) {
    constructor() : this("")
}
Run Code Online (Sandbox Code Playgroud)

处理程序类用于RequestHandler执行验证:

fun process(request: ServerRequest): Mono<ServerResponse> {
    return requestHandler.withValidBody({
        tokenRequest -> tokenRequest
                .flatMap { token -> tokenService.process(token.accessToken) }
                .map { result -> TokenResponse(result) }
                .flatMap { ServerResponse.ok()
                        .contentType(MediaType.APPLICATION_JSON_UTF8)
                        .body(Mono.just(it), TokenResponse::class.java)
                }
    }, request, TokenRequest::class.java)
}
Run Code Online (Sandbox Code Playgroud)

从这篇博文中得到了这个想法。