Add Validation to HTTP Inbound (#2978)
* Add Validation to HTTP Inbound * Pull a validation functionality from `WebFluxInboundEndpoint` to its super class `BaseHttpInboundEndpoint` making a validation available for the `HttpRequestHandlingEndpointSupport` as well * Do the same for the `validator()` option in DSL for the `HttpInboundEndpointSupportSpec` * Add `validator` XML attribute for both HTTP and WebFlux inbound endpoint XSDs * Test parsers for a new `validator` option * Document validation in the `http.adoc` * Apply some polishing in the `http.adoc`, as well as in the `HttpRequestHandlingEndpointSupport` JavaDocs * Clarify in `webflux.adoc` that validation is applied for the `Publisher` items before the payload is finally built for the message to send. * Add WebFlux into the table of endpoints in the `endpoint-summary.adoc` * * Remove unused imports * Fix `SimpleMessageListenerContainerSpec` for deprecated `txSize` option
This commit is contained in:
committed by
Gary Russell
parent
c5ec2d94c6
commit
c18c2e2141
@@ -107,12 +107,13 @@ public class HttpInboundEndpointParser extends AbstractSingleBeanDefinitionParse
|
||||
List<Element> headerElements = DomUtils.getChildElementsByTagName(element, "header");
|
||||
|
||||
if (!CollectionUtils.isEmpty(headerElements)) {
|
||||
ManagedMap<String, Object> headerElementsMap = new ManagedMap<String, Object>();
|
||||
ManagedMap<String, Object> headerElementsMap = new ManagedMap<>();
|
||||
for (Element headerElement : headerElements) {
|
||||
String name = headerElement.getAttribute(NAME_ATTRIBUTE);
|
||||
BeanDefinition headerExpressionDef =
|
||||
IntegrationNamespaceUtils.createExpressionDefIfAttributeDefined(IntegrationNamespaceUtils.EXPRESSION_ATTRIBUTE,
|
||||
headerElement);
|
||||
IntegrationNamespaceUtils
|
||||
.createExpressionDefIfAttributeDefined(IntegrationNamespaceUtils.EXPRESSION_ATTRIBUTE,
|
||||
headerElement);
|
||||
if (headerExpressionDef != null) {
|
||||
headerElementsMap.put(name, headerExpressionDef);
|
||||
}
|
||||
@@ -133,8 +134,8 @@ public class HttpInboundEndpointParser extends AbstractSingleBeanDefinitionParse
|
||||
}
|
||||
|
||||
BeanDefinition expressionDef =
|
||||
IntegrationNamespaceUtils.createExpressionDefinitionFromValueOrExpression("view-name", "view-expression",
|
||||
parserContext, element, false);
|
||||
IntegrationNamespaceUtils.createExpressionDefinitionFromValueOrExpression("view-name",
|
||||
"view-expression", parserContext, element, false);
|
||||
if (expressionDef != null) {
|
||||
builder.addPropertyValue("viewExpression", expressionDef);
|
||||
}
|
||||
@@ -154,13 +155,16 @@ public class HttpInboundEndpointParser extends AbstractSingleBeanDefinitionParse
|
||||
|
||||
if (StringUtils.hasText(headerMapper)) {
|
||||
if (hasMappedRequestHeaders || hasMappedResponseHeaders) {
|
||||
parserContext.getReaderContext().error("Neither 'mapped-request-headers' or 'mapped-response-headers' " +
|
||||
"attributes are allowed when a 'header-mapper' has been specified.", parserContext.extractSource(element));
|
||||
parserContext.getReaderContext()
|
||||
.error("Neither 'mapped-request-headers' or 'mapped-response-headers' " +
|
||||
"attributes are allowed when a 'header-mapper' has been specified.",
|
||||
parserContext.extractSource(element));
|
||||
}
|
||||
builder.addPropertyReference("headerMapper", headerMapper);
|
||||
}
|
||||
else {
|
||||
BeanDefinitionBuilder headerMapperBuilder = BeanDefinitionBuilder.genericBeanDefinition(DefaultHttpHeaderMapper.class);
|
||||
BeanDefinitionBuilder headerMapperBuilder =
|
||||
BeanDefinitionBuilder.genericBeanDefinition(DefaultHttpHeaderMapper.class);
|
||||
headerMapperBuilder.setFactoryMethod("inboundMapper");
|
||||
|
||||
if (hasMappedRequestHeaders) {
|
||||
@@ -181,7 +185,7 @@ public class HttpInboundEndpointParser extends AbstractSingleBeanDefinitionParse
|
||||
if (crossOriginElement != null) {
|
||||
BeanDefinitionBuilder crossOriginBuilder =
|
||||
BeanDefinitionBuilder.genericBeanDefinition(CrossOrigin.class);
|
||||
String[] attributes = {"origin", "allowed-headers", "exposed-headers", "max-age", "method"};
|
||||
String[] attributes = { "origin", "allowed-headers", "exposed-headers", "max-age", "method" };
|
||||
for (String crossOriginAttribute : attributes) {
|
||||
IntegrationNamespaceUtils.setValueIfAttributeDefined(crossOriginBuilder, crossOriginElement,
|
||||
crossOriginAttribute);
|
||||
@@ -191,7 +195,8 @@ public class HttpInboundEndpointParser extends AbstractSingleBeanDefinitionParse
|
||||
builder.addPropertyValue("crossOrigin", crossOriginBuilder.getBeanDefinition());
|
||||
}
|
||||
|
||||
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "request-payload-type", "requestPayloadTypeClass");
|
||||
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element,
|
||||
"request-payload-type", "requestPayloadTypeClass");
|
||||
|
||||
BeanDefinition statusCodeExpressionDef =
|
||||
IntegrationNamespaceUtils.createExpressionDefIfAttributeDefined("status-code-expression", element);
|
||||
@@ -205,6 +210,7 @@ public class HttpInboundEndpointParser extends AbstractSingleBeanDefinitionParse
|
||||
|
||||
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, IntegrationNamespaceUtils.AUTO_STARTUP);
|
||||
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, IntegrationNamespaceUtils.PHASE);
|
||||
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "validator");
|
||||
}
|
||||
|
||||
private String getInputChannelAttributeName() {
|
||||
@@ -212,7 +218,8 @@ public class HttpInboundEndpointParser extends AbstractSingleBeanDefinitionParse
|
||||
}
|
||||
|
||||
private BeanDefinition createRequestMapping(Element element) {
|
||||
BeanDefinitionBuilder requestMappingDefBuilder = BeanDefinitionBuilder.genericBeanDefinition(RequestMapping.class);
|
||||
BeanDefinitionBuilder requestMappingDefBuilder =
|
||||
BeanDefinitionBuilder.genericBeanDefinition(RequestMapping.class);
|
||||
|
||||
String methods = element.getAttribute("supported-methods");
|
||||
if (StringUtils.hasText(methods)) {
|
||||
@@ -224,7 +231,7 @@ public class HttpInboundEndpointParser extends AbstractSingleBeanDefinitionParse
|
||||
Element requestMappingElement = DomUtils.getChildElementByTagName(element, "request-mapping");
|
||||
|
||||
if (requestMappingElement != null) {
|
||||
for (String requestMappingAttribute : new String[]{"params", "headers", "consumes", "produces"}) {
|
||||
for (String requestMappingAttribute : new String[] { "params", "headers", "consumes", "produces" }) {
|
||||
IntegrationNamespaceUtils.setValueIfAttributeDefined(requestMappingDefBuilder, requestMappingElement,
|
||||
requestMappingAttribute);
|
||||
}
|
||||
|
||||
@@ -37,6 +37,7 @@ import org.springframework.integration.http.inbound.RequestMapping;
|
||||
import org.springframework.integration.http.support.DefaultHttpHeaderMapper;
|
||||
import org.springframework.integration.mapping.HeaderMapper;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.validation.Validator;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
|
||||
/**
|
||||
@@ -45,7 +46,8 @@ import org.springframework.web.bind.annotation.RequestMethod;
|
||||
*
|
||||
* @since 5.0
|
||||
*/
|
||||
public abstract class HttpInboundEndpointSupportSpec<S extends HttpInboundEndpointSupportSpec<S, E>, E extends BaseHttpInboundEndpoint>
|
||||
public abstract class HttpInboundEndpointSupportSpec<S extends HttpInboundEndpointSupportSpec<S, E>,
|
||||
E extends BaseHttpInboundEndpoint>
|
||||
extends MessagingGatewaySpec<S, E>
|
||||
implements ComponentsRegistration {
|
||||
|
||||
@@ -93,7 +95,7 @@ public abstract class HttpInboundEndpointSupportSpec<S extends HttpInboundEndpoi
|
||||
* Specify a SpEL expression to evaluate in order to generate the Message payload.
|
||||
* @param payloadExpression The payload expression.
|
||||
* @return the spec
|
||||
* @see org.springframework.integration.http.inbound.HttpRequestHandlingEndpointSupport#setPayloadExpression(Expression)
|
||||
* @see BaseHttpInboundEndpoint#setPayloadExpression(Expression)
|
||||
*/
|
||||
public S payloadExpression(String payloadExpression) {
|
||||
return payloadExpression(PARSER.parseExpression(payloadExpression));
|
||||
@@ -103,7 +105,7 @@ public abstract class HttpInboundEndpointSupportSpec<S extends HttpInboundEndpoi
|
||||
* Specify a SpEL expression to evaluate in order to generate the Message payload.
|
||||
* @param payloadExpression The payload expression.
|
||||
* @return the spec
|
||||
* @see org.springframework.integration.http.inbound.HttpRequestHandlingEndpointSupport#setPayloadExpression(Expression)
|
||||
* @see BaseHttpInboundEndpoint#setPayloadExpression(Expression)
|
||||
*/
|
||||
public S payloadExpression(Expression payloadExpression) {
|
||||
this.target.setPayloadExpression(payloadExpression);
|
||||
@@ -115,7 +117,7 @@ public abstract class HttpInboundEndpointSupportSpec<S extends HttpInboundEndpoi
|
||||
* @param payloadFunction The payload {@link Function}.
|
||||
* @param <P> the expected HTTP request body type.
|
||||
* @return the spec
|
||||
* @see org.springframework.integration.http.inbound.HttpRequestHandlingEndpointSupport#setPayloadExpression(Expression)
|
||||
* @see BaseHttpInboundEndpoint#setPayloadExpression(Expression)
|
||||
*/
|
||||
public <P> S payloadFunction(Function<HttpEntity<P>, ?> payloadFunction) {
|
||||
return payloadExpression(new FunctionExpression<>(payloadFunction));
|
||||
@@ -125,7 +127,7 @@ public abstract class HttpInboundEndpointSupportSpec<S extends HttpInboundEndpoi
|
||||
* Specify a Map of SpEL expressions to evaluate in order to generate the Message headers.
|
||||
* @param expressions The {@link Map} of SpEL expressions for headers.
|
||||
* @return the spec
|
||||
* @see org.springframework.integration.http.inbound.HttpRequestHandlingEndpointSupport#setHeaderExpressions(Map)
|
||||
* @see BaseHttpInboundEndpoint#setHeaderExpressions(Map)
|
||||
*/
|
||||
public S headerExpressions(Map<String, Expression> expressions) {
|
||||
Assert.notNull(expressions, "'headerExpressions' must not be null");
|
||||
@@ -139,7 +141,7 @@ public abstract class HttpInboundEndpointSupportSpec<S extends HttpInboundEndpoi
|
||||
* @param header the header name to populate.
|
||||
* @param expression the SpEL expression for the header.
|
||||
* @return the spec
|
||||
* @see org.springframework.integration.http.inbound.HttpRequestHandlingEndpointSupport#setHeaderExpressions(Map)
|
||||
* @see BaseHttpInboundEndpoint#setHeaderExpressions(Map)
|
||||
*/
|
||||
public S headerExpression(String header, String expression) {
|
||||
return headerExpression(header, PARSER.parseExpression(expression));
|
||||
@@ -150,7 +152,7 @@ public abstract class HttpInboundEndpointSupportSpec<S extends HttpInboundEndpoi
|
||||
* @param header the header name to populate.
|
||||
* @param expression the SpEL expression for the header.
|
||||
* @return the spec
|
||||
* @see org.springframework.integration.http.inbound.HttpRequestHandlingEndpointSupport#setHeaderExpressions(Map)
|
||||
* @see BaseHttpInboundEndpoint#setHeaderExpressions(Map)
|
||||
*/
|
||||
public S headerExpression(String header, Expression expression) {
|
||||
this.headerExpressions.put(header, expression);
|
||||
@@ -163,7 +165,7 @@ public abstract class HttpInboundEndpointSupportSpec<S extends HttpInboundEndpoi
|
||||
* @param headerFunction the function to evaluate the header value against {@link HttpEntity}.
|
||||
* @param <P> the expected HTTP body type.
|
||||
* @return the current Spec.
|
||||
* @see org.springframework.integration.http.inbound.HttpRequestHandlingEndpointSupport#setHeaderExpressions(Map)
|
||||
* @see BaseHttpInboundEndpoint#setHeaderExpressions(Map)
|
||||
*/
|
||||
public <P> S headerFunction(String header, Function<HttpEntity<P>, ?> headerFunction) {
|
||||
return headerExpression(header, new FunctionExpression<>(headerFunction));
|
||||
@@ -251,7 +253,7 @@ public abstract class HttpInboundEndpointSupportSpec<S extends HttpInboundEndpoi
|
||||
* the default '200 OK' or '500 Internal Server Error' for a timeout.
|
||||
* @param statusCodeExpression The status code Expression.
|
||||
* @return the current Spec.
|
||||
* @see org.springframework.integration.http.inbound.HttpRequestHandlingEndpointSupport#setStatusCodeExpression(Expression)
|
||||
* @see BaseHttpInboundEndpoint#setStatusCodeExpression(Expression)
|
||||
*/
|
||||
public S statusCodeExpression(String statusCodeExpression) {
|
||||
this.target.setStatusCodeExpressionString(statusCodeExpression);
|
||||
@@ -263,7 +265,7 @@ public abstract class HttpInboundEndpointSupportSpec<S extends HttpInboundEndpoi
|
||||
* the default '200 OK' or '500 Internal Server Error' for a timeout.
|
||||
* @param statusCodeExpression The status code Expression.
|
||||
* @return the current Spec.
|
||||
* @see org.springframework.integration.http.inbound.HttpRequestHandlingEndpointSupport#setStatusCodeExpression(Expression)
|
||||
* @see BaseHttpInboundEndpoint#setStatusCodeExpression(Expression)
|
||||
*/
|
||||
public S statusCodeExpression(Expression statusCodeExpression) {
|
||||
this.target.setStatusCodeExpression(statusCodeExpression);
|
||||
@@ -275,12 +277,23 @@ public abstract class HttpInboundEndpointSupportSpec<S extends HttpInboundEndpoi
|
||||
* the default '200 OK' or '500 Internal Server Error' for a timeout.
|
||||
* @param statusCodeFunction The status code {@link Function}.
|
||||
* @return the current Spec.
|
||||
* @see org.springframework.integration.http.inbound.HttpRequestHandlingEndpointSupport#setStatusCodeExpression(Expression)
|
||||
* @see BaseHttpInboundEndpoint#setStatusCodeExpression(Expression)
|
||||
*/
|
||||
public S statusCodeFunction(Function<RequestEntity<?>, ?> statusCodeFunction) {
|
||||
return statusCodeExpression(new FunctionExpression<>(statusCodeFunction));
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify a {@link Validator} to validate a converted payload from request.
|
||||
* @param validator the {@link Validator} to use.
|
||||
* @return the spec
|
||||
* @since 5.2
|
||||
*/
|
||||
public S validator(Validator validator) {
|
||||
this.target.setValidator(validator);
|
||||
return _this();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<Object, String> getComponentsToRegister() {
|
||||
HeaderMapper<HttpHeaders> headerMapperToRegister =
|
||||
|
||||
@@ -33,11 +33,15 @@ import org.springframework.integration.context.OrderlyShutdownCapable;
|
||||
import org.springframework.integration.expression.ExpressionUtils;
|
||||
import org.springframework.integration.gateway.MessagingGatewaySupport;
|
||||
import org.springframework.integration.http.support.DefaultHttpHeaderMapper;
|
||||
import org.springframework.integration.http.support.IntegrationWebExchangeBindException;
|
||||
import org.springframework.integration.mapping.HeaderMapper;
|
||||
import org.springframework.messaging.MessageHeaders;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.validation.BeanPropertyBindingResult;
|
||||
import org.springframework.validation.ValidationUtils;
|
||||
import org.springframework.validation.Validator;
|
||||
|
||||
/**
|
||||
* The {@link MessagingGatewaySupport} extension for HTTP Inbound endpoints
|
||||
@@ -65,6 +69,8 @@ public class BaseHttpInboundEndpoint extends MessagingGatewaySupport implements
|
||||
|
||||
private final boolean expectReply;
|
||||
|
||||
private Validator validator;
|
||||
|
||||
private ResolvableType requestPayloadType = null;
|
||||
|
||||
private HeaderMapper<HttpHeaders> headerMapper = DefaultHttpHeaderMapper.inboundMapper();
|
||||
@@ -253,6 +259,19 @@ public class BaseHttpInboundEndpoint extends MessagingGatewaySupport implements
|
||||
return this.statusCodeExpression;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify a {@link Validator} to validate a converted payload from request.
|
||||
* @param validator the {@link Validator} to use.
|
||||
* @since 5.2
|
||||
*/
|
||||
public void setValidator(Validator validator) {
|
||||
this.validator = validator;
|
||||
}
|
||||
|
||||
protected Validator getValidator() {
|
||||
return this.validator;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onInit() {
|
||||
super.onInit();
|
||||
@@ -334,4 +353,12 @@ public class BaseHttpInboundEndpoint extends MessagingGatewaySupport implements
|
||||
return !(CollectionUtils.containsInstance(NON_READABLE_BODY_HTTP_METHODS, httpMethod));
|
||||
}
|
||||
|
||||
protected void validate(Object value) {
|
||||
BeanPropertyBindingResult errors = new BeanPropertyBindingResult(value, "requestPayload");
|
||||
ValidationUtils.invokeValidator(this.validator, value, errors);
|
||||
if (errors.hasErrors()) {
|
||||
throw new IntegrationWebExchangeBindException(getComponentName(), value, errors);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -319,6 +319,10 @@ public abstract class HttpRequestHandlingEndpointSupport extends BaseHttpInbound
|
||||
|
||||
AbstractIntegrationMessageBuilder<?> messageBuilder;
|
||||
|
||||
if (getValidator() != null) {
|
||||
validate(payload);
|
||||
}
|
||||
|
||||
if (payload instanceof Message<?>) {
|
||||
messageBuilder =
|
||||
getMessageBuilderFactory()
|
||||
|
||||
@@ -362,6 +362,18 @@
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="validator" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:appinfo>
|
||||
<tool:annotation kind="ref">
|
||||
<tool:expected-type type="org.springframework.validation.Validator" />
|
||||
</tool:annotation>
|
||||
</xsd:appinfo>
|
||||
<xsd:documentation>
|
||||
A 'Validator' bean reference to validate a payload converted from the HTTP request.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
</xsd:attributeGroup>
|
||||
|
||||
<xsd:element name="outbound-channel-adapter">
|
||||
|
||||
Reference in New Issue
Block a user