问题
In a traditional web application it is easy to validate the request body in the controller method, eg.
ResponseEntity create(@Valid @ResponseBody Post post) {
}
If it is a MVC application, we can gather the errors by injecting a BindingResult
, and decide if there is some validation errors from the input form.
In the pages, there are some helpers existed for Freemarker and Thymeleaf to display the messages.
But when I come to Webflux and try to use RouterFunction
to define the routing in the applications. For example,
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)
}
A possible approach is converting the request data(Mono
or Flux
) to blocking and injecting a Validator
and validate them manually.
But I think the codes will look a little ugly.
How to process the validation of request body or form data gracefully?
Is there a better to validate the request body or form data and do not lost the functional and reactive features for both WEB(rendering a view) and REST applications?
回答1:
One of the ways I've managed to do it in my application is the following (code is in Kotlin but the idea is the same). I've declared RequestHandler
class which performs validation:
@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)
}
}
}
Request objects can contain java validation annotations in this way:
data class TokenRequest constructor(@get:NotBlank val accessToken: String) {
constructor() : this("")
}
And handler classes use RequestHandler
to perform validation:
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)
}
Got the idea from this blog post.
回答2:
I've developed "Yet Another Validator" for this porpose.
https://github.com/making/yavi
It would be great if YAVI could meet your expectation.
Validation code will look like following:
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))));
}
来源:https://stackoverflow.com/questions/48031647/what-is-the-best-way-to-validate-request-in-a-spring-webflux-functional-applicat