Add validation support into WebFlux Inbound (#2977)

* Add validation support into WebFlux Inbound

See https://stackoverflow.com/questions/56729517/spring-integration-webflux-validate-input-json-body

* Introduce a `WebFluxInboundEndpoint.validator` option to validate
a request payload after conversion HTTP request into a `Publisher`
* Expose a `WebFluxInboundEndpointSpec.validator()` option
* Introduce a `IntegrationWebExchangeBindException` to extend a
`WebExchangeBindException` for Spring Integration use-case.
such an exception is used in the `ResponseStatusExceptionHandler`
extensions, like one in Spring Boot for proper response rendering
* Test and document the solution

* * Remove unused imports from IntegrationWebExchangeBindException
This commit is contained in:
Artem Bilan
2019-06-26 11:11:33 -04:00
committed by Gary Russell
parent 97aaf95ccb
commit 59cdc4ddae
7 changed files with 231 additions and 46 deletions

View File

@@ -22,10 +22,12 @@ import static org.springframework.test.web.servlet.request.MockMvcRequestBuilder
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import java.security.Principal;
import java.time.Duration;
import java.util.Collections;
import javax.annotation.Resource;
import org.hamcrest.Matchers;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -81,8 +83,12 @@ import org.springframework.test.web.reactive.server.WebTestClient;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;
import org.springframework.validation.Errors;
import org.springframework.validation.Validator;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.reactive.config.EnableWebFlux;
import org.springframework.web.reactive.config.WebFluxConfigurer;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.util.UriComponentsBuilder;
@@ -135,7 +141,7 @@ public class WebFluxDslTests {
WebTestClient.bindToApplicationContext(this.wac)
.apply(SecurityMockServerConfigurers.springSecurity())
.configureClient()
// .responseTimeout(Duration.ofSeconds(600))
.responseTimeout(Duration.ofSeconds(600))
.build();
}
@@ -237,7 +243,9 @@ public class WebFluxDslTests {
.headers(headers -> headers.setBasicAuth("guest", "guest"))
.body(Mono.just("foo\nbar\nbaz"), String.class)
.exchange()
.expectStatus().isEqualTo(HttpStatus.BAD_GATEWAY);
.expectStatus().isEqualTo(HttpStatus.BAD_GATEWAY)
.expectBody(String.class)
.value(Matchers.containsString("errorTest"));
}
@Test
@@ -256,7 +264,7 @@ public class WebFluxDslTests {
}
@Test
public void testDynamicHttpEndpoint() throws Exception {
public void testDynamicHttpEndpoint() {
IntegrationFlow flow =
IntegrationFlows.from(WebFlux.inboundGateway("/dynamic")
.requestMapping(r -> r.params("name"))
@@ -282,12 +290,48 @@ public class WebFluxDslTests {
.isNotFound();
}
@Autowired
private Validator validator;
@Test
public void testValidation() {
IntegrationFlow flow =
IntegrationFlows.from(
WebFlux.inboundGateway("/validation")
.requestMapping((mapping) -> mapping
.methods(HttpMethod.POST)
.consumes(MediaType.APPLICATION_JSON_VALUE))
.requestPayloadType(
ResolvableType.forClassWithGenerics(Flux.class, TestModel.class))
.validator(this.validator))
.bridge()
.get();
IntegrationFlowContext.IntegrationFlowRegistration flowRegistration =
this.integrationFlowContext.registration(flow).register();
this.webTestClient.post().uri("/validation")
.headers(headers -> headers.setBasicAuth("guest", "guest"))
.contentType(MediaType.APPLICATION_JSON)
.syncBody("{\"name\": \"\"}")
.exchange()
.expectStatus().isBadRequest();
flowRegistration.destroy();
}
@Configuration
@EnableWebFlux
@EnableWebSecurity
@EnableWebFluxSecurity
@EnableIntegration
public static class ContextConfiguration extends WebSecurityConfigurerAdapter {
public static class ContextConfiguration extends WebSecurityConfigurerAdapter implements WebFluxConfigurer {
@Override
public Validator getValidator() {
return webFluxValidator();
}
@Bean
public UserDetails userDetails() {
@@ -411,6 +455,42 @@ public class WebFluxDslTests {
return new AffirmativeBased(Collections.singletonList(new RoleVoter()));
}
@Bean
public Validator webFluxValidator() {
return new TestModelValidator();
}
}
public static class TestModel {
private String name;
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
}
private static class TestModelValidator implements Validator {
@Override
public boolean supports(Class<?> clazz) {
return TestModel.class.isAssignableFrom(clazz);
}
@Override
public void validate(Object target, Errors errors) {
TestModel testModel = (TestModel) target;
if (!StringUtils.hasText(testModel.getName())) {
errors.rejectValue("name", "Must not be empty");
}
}
}
}

View File

@@ -69,8 +69,7 @@ public class WebFluxInboundEndpointTests {
this.webTestClient.get().uri("/test")
.exchange()
.expectStatus().isEqualTo(HttpStatus.SERVICE_UNAVAILABLE)
.expectBody(String.class).isEqualTo("Endpoint is stopped");
.expectStatus().isEqualTo(HttpStatus.SERVICE_UNAVAILABLE);
}
@Test