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
@@ -22,6 +22,8 @@ import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer;
|
||||
* Spec for a {@link SimpleMessageListenerContainer}.
|
||||
*
|
||||
* @author Gary Russell
|
||||
* @author Artem Bilan
|
||||
*
|
||||
* @since 5.0
|
||||
*
|
||||
*/
|
||||
@@ -108,10 +110,22 @@ public class SimpleMessageListenerContainerSpec extends
|
||||
/**
|
||||
* @param txSize the txSize.
|
||||
* @return the spec.
|
||||
* @see SimpleMessageListenerContainer#setTxSize(int)
|
||||
* @see SimpleMessageListenerContainer#setBatchSize(int)
|
||||
* @deprecated since 5.2 in favor of {@link #batchSize(int)}
|
||||
*/
|
||||
public SimpleMessageListenerContainerSpec txSize(int txSize) {
|
||||
this.listenerContainer.setTxSize(txSize);
|
||||
return batchSize(txSize);
|
||||
}
|
||||
|
||||
/**
|
||||
* The batch size to use.
|
||||
* @param batchSize the batchSize.
|
||||
* @return the spec.
|
||||
* @see SimpleMessageListenerContainer#setBatchSize(int)
|
||||
* @since 5.2
|
||||
*/
|
||||
public SimpleMessageListenerContainerSpec batchSize(int batchSize) {
|
||||
this.listenerContainer.setBatchSize(batchSize);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans:beans
|
||||
xmlns="http://www.springframework.org/schema/integration/http"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:beans="http://www.springframework.org/schema/beans"
|
||||
xmlns:si="http://www.springframework.org/schema/integration"
|
||||
xmlns:util="http://www.springframework.org/schema/util"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans
|
||||
xmlns="http://www.springframework.org/schema/integration/http"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:beans="http://www.springframework.org/schema/beans"
|
||||
xmlns:si="http://www.springframework.org/schema/integration"
|
||||
xmlns:util="http://www.springframework.org/schema/util"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans
|
||||
https://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/integration
|
||||
https://www.springframework.org/schema/integration/spring-integration.xsd
|
||||
@@ -20,20 +20,25 @@
|
||||
<si:queue capacity="1"/>
|
||||
</si:channel>
|
||||
|
||||
<beans:bean id="validator" class="org.mockito.Mockito" factory-method="mock">
|
||||
<beans:constructor-arg value="org.springframework.validation.Validator"/>
|
||||
</beans:bean>
|
||||
|
||||
<inbound-channel-adapter id="defaultAdapter" channel="requests" error-channel="errorChannel"
|
||||
auto-startup="false"
|
||||
phase="1001"
|
||||
status-code-expression="'101'"/>
|
||||
auto-startup="false"
|
||||
phase="1001"
|
||||
status-code-expression="'101'"
|
||||
validator="validator"/>
|
||||
|
||||
<inbound-channel-adapter id="postOnlyAdapter" path="/postOnly" channel="requests" supported-methods="POST"/>
|
||||
|
||||
<inbound-channel-adapter id="adapterWithCustomConverterWithDefaults" message-converters="customConverters"
|
||||
channel="requests" supported-methods="DELETE" merge-with-default-converters="true"/>
|
||||
channel="requests" supported-methods="DELETE" merge-with-default-converters="true"/>
|
||||
|
||||
<inbound-channel-adapter id="adapterWithCustomConverterNoDefaults" message-converters="customConverters"
|
||||
channel="requests" supported-methods="HEAD" />
|
||||
channel="requests" supported-methods="HEAD"/>
|
||||
|
||||
<inbound-channel-adapter id="adapterNoCustomConverterNoDefaults" channel="requests" supported-methods="POST" />
|
||||
<inbound-channel-adapter id="adapterNoCustomConverterNoDefaults" channel="requests" supported-methods="POST"/>
|
||||
|
||||
<util:list id="customConverters">
|
||||
<beans:bean class="org.springframework.integration.http.converter.SerializingHttpMessageConverter"/>
|
||||
@@ -42,7 +47,7 @@
|
||||
<inbound-channel-adapter id="putOrDeleteAdapter" channel="requests" supported-methods="PUT, delete"/>
|
||||
|
||||
<inbound-channel-adapter id="inboundController" channel="requests" view-name="foo" error-code="oops"
|
||||
status-code-expression="T(org.springframework.http.HttpStatus).ACCEPTED">
|
||||
status-code-expression="T(org.springframework.http.HttpStatus).ACCEPTED">
|
||||
<request-mapping headers="BAR"/>
|
||||
</inbound-channel-adapter>
|
||||
|
||||
|
||||
@@ -17,6 +17,9 @@
|
||||
package org.springframework.integration.http.config;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.BDDMockito.willReturn;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.ObjectOutputStream;
|
||||
@@ -49,10 +52,10 @@ import org.springframework.messaging.PollableChannel;
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
import org.springframework.mock.web.MockHttpServletResponse;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
import org.springframework.util.AntPathMatcher;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.validation.Validator;
|
||||
import org.springframework.web.HttpRequestMethodNotSupportedException;
|
||||
import org.springframework.web.servlet.HandlerMapping;
|
||||
|
||||
@@ -65,8 +68,7 @@ import org.springframework.web.servlet.HandlerMapping;
|
||||
* @author Artem Bilan
|
||||
* @author Biju Kunjummen
|
||||
*/
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@ContextConfiguration
|
||||
@RunWith(SpringRunner.class)
|
||||
@DirtiesContext
|
||||
public class HttpInboundChannelAdapterParserTests extends AbstractHttpInboundTests {
|
||||
|
||||
@@ -112,9 +114,13 @@ public class HttpInboundChannelAdapterParserTests extends AbstractHttpInboundTes
|
||||
@Autowired
|
||||
private MessageChannel autoChannel;
|
||||
|
||||
@Autowired @Qualifier("autoChannel.adapter")
|
||||
@Autowired
|
||||
@Qualifier("autoChannel.adapter")
|
||||
private HttpRequestHandlingMessagingGateway autoChannelAdapter;
|
||||
|
||||
@Autowired
|
||||
private Validator validator;
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void getRequestOk() throws Exception {
|
||||
@@ -128,6 +134,7 @@ public class HttpInboundChannelAdapterParserTests extends AbstractHttpInboundTes
|
||||
assertThat(response.getStatus()).isEqualTo(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
|
||||
this.defaultAdapter.start();
|
||||
response = new MockHttpServletResponse();
|
||||
willReturn(true).given(this.validator).supports(any());
|
||||
this.defaultAdapter.handleRequest(request, response);
|
||||
assertThat(response.getStatus()).isEqualTo(HttpServletResponse.SC_SWITCHING_PROTOCOLS);
|
||||
Message<?> message = requests.receive(0);
|
||||
@@ -140,12 +147,13 @@ public class HttpInboundChannelAdapterParserTests extends AbstractHttpInboundTes
|
||||
assertThat(map.get("foo").size()).isEqualTo(1);
|
||||
assertThat(map.getFirst("foo")).isEqualTo("bar");
|
||||
assertThat(TestUtils.getPropertyValue(this.defaultAdapter, "errorChannel")).isNotNull();
|
||||
assertThat(TestUtils.getPropertyValue(this.defaultAdapter, "validator")).isSameAs(this.validator);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getRequestWithHeaders() throws Exception {
|
||||
public void getRequestWithHeaders() {
|
||||
DefaultHttpHeaderMapper headerMapper =
|
||||
(DefaultHttpHeaderMapper) TestUtils.getPropertyValue(withMappedHeaders, "headerMapper");
|
||||
TestUtils.getPropertyValue(withMappedHeaders, "headerMapper", DefaultHttpHeaderMapper.class);
|
||||
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.set("foo", "foo");
|
||||
@@ -187,19 +195,18 @@ public class HttpInboundChannelAdapterParserTests extends AbstractHttpInboundTes
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getRequestNotAllowed() throws Exception {
|
||||
public void getRequestNotAllowed() {
|
||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||
request.setMethod("GET");
|
||||
request.setParameter("foo", "bar");
|
||||
request.setRequestURI("/postOnly");
|
||||
try {
|
||||
this.integrationRequestMappingHandlerMapping.getHandler(request);
|
||||
}
|
||||
catch (HttpRequestMethodNotSupportedException e) {
|
||||
assertThat(e.getMethod()).isEqualTo("GET");
|
||||
assertThat(e.getSupportedMethods()).isEqualTo(new String[] { "POST" });
|
||||
}
|
||||
|
||||
assertThatExceptionOfType(HttpRequestMethodNotSupportedException.class)
|
||||
.isThrownBy(() -> this.integrationRequestMappingHandlerMapping.getHandler(request))
|
||||
.satisfies((ex) -> {
|
||||
assertThat(ex.getMethod()).isEqualTo("GET");
|
||||
assertThat(ex.getSupportedMethods()).containsExactly("POST");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -222,7 +229,8 @@ public class HttpInboundChannelAdapterParserTests extends AbstractHttpInboundTes
|
||||
assertThat(message.getPayload()).isEqualTo("test");
|
||||
}
|
||||
|
||||
@Test @DirtiesContext
|
||||
@Test
|
||||
@DirtiesContext
|
||||
public void postRequestWithSerializedObjectContentOk() throws Exception {
|
||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||
request.setMethod("POST");
|
||||
@@ -244,15 +252,15 @@ public class HttpInboundChannelAdapterParserTests extends AbstractHttpInboundTes
|
||||
|
||||
|
||||
@Test
|
||||
public void putOrDeleteMethodsSupported() throws Exception {
|
||||
public void putOrDeleteMethodsSupported() {
|
||||
HttpMethod[] supportedMethods =
|
||||
TestUtils.getPropertyValue(putOrDeleteAdapter, "requestMapping.methods", HttpMethod[].class);
|
||||
assertThat(supportedMethods.length).isEqualTo(2);
|
||||
assertThat(supportedMethods).isEqualTo(new HttpMethod[] { HttpMethod.PUT, HttpMethod.DELETE });
|
||||
assertThat(supportedMethods).containsExactly(HttpMethod.PUT, HttpMethod.DELETE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testController() throws Exception {
|
||||
public void testController() {
|
||||
String errorCode = TestUtils.getPropertyValue(inboundController, "errorCode", String.class);
|
||||
assertThat(errorCode).isEqualTo("oops");
|
||||
Expression viewExpression = TestUtils.getPropertyValue(inboundController, "viewExpression", Expression.class);
|
||||
@@ -269,8 +277,9 @@ public class HttpInboundChannelAdapterParserTests extends AbstractHttpInboundTes
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInt2717ControllerWithViewExpression() throws Exception {
|
||||
Expression viewExpression = TestUtils.getPropertyValue(inboundControllerViewExp, "viewExpression", Expression.class);
|
||||
public void testInt2717ControllerWithViewExpression() {
|
||||
Expression viewExpression = TestUtils
|
||||
.getPropertyValue(inboundControllerViewExp, "viewExpression", Expression.class);
|
||||
assertThat(viewExpression.getExpressionString()).isEqualTo("'foo'");
|
||||
}
|
||||
|
||||
@@ -282,7 +291,8 @@ public class HttpInboundChannelAdapterParserTests extends AbstractHttpInboundTes
|
||||
@Test
|
||||
public void testInboundAdapterWithMessageConverterDefaults() {
|
||||
@SuppressWarnings("unchecked")
|
||||
List<HttpMessageConverter<?>> messageConverters = TestUtils.getPropertyValue(adapterWithCustomConverterWithDefaults, "messageConverters", List.class);
|
||||
List<HttpMessageConverter<?>> messageConverters = TestUtils
|
||||
.getPropertyValue(adapterWithCustomConverterWithDefaults, "messageConverters", List.class);
|
||||
assertThat(messageConverters.size() > 1)
|
||||
.as("There should be more than 1 message converter. The customized one and the defaults.").isTrue();
|
||||
|
||||
@@ -293,17 +303,19 @@ public class HttpInboundChannelAdapterParserTests extends AbstractHttpInboundTes
|
||||
@Test
|
||||
public void testInboundAdapterWithNoMessageConverterDefaults() {
|
||||
@SuppressWarnings("unchecked")
|
||||
List<HttpMessageConverter<?>> messageConverters = TestUtils.getPropertyValue(adapterWithCustomConverterNoDefaults, "messageConverters", List.class);
|
||||
List<HttpMessageConverter<?>> messageConverters = TestUtils
|
||||
.getPropertyValue(adapterWithCustomConverterNoDefaults, "messageConverters", List.class);
|
||||
//First converter should be the customized one
|
||||
assertThat(messageConverters.get(0)).isInstanceOf(SerializingHttpMessageConverter.class);
|
||||
assertThat(messageConverters.size() == 1).as("There should be only the customized messageconverter registered.")
|
||||
.isTrue();
|
||||
assertThat(messageConverters).as("There should be only the customized MessageConverter registered.")
|
||||
.hasSize(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInboundAdapterWithNoMessageConverterNoDefaults() {
|
||||
@SuppressWarnings("unchecked")
|
||||
List<HttpMessageConverter<?>> messageConverters = TestUtils.getPropertyValue(adapterNoCustomConverterNoDefaults, "messageConverters", List.class);
|
||||
List<HttpMessageConverter<?>> messageConverters = TestUtils
|
||||
.getPropertyValue(adapterNoCustomConverterNoDefaults, "messageConverters", List.class);
|
||||
assertThat(messageConverters.size() > 1).as("There should be more than 1 message converter. The defaults.")
|
||||
.isTrue();
|
||||
}
|
||||
@@ -316,6 +328,7 @@ public class HttpInboundChannelAdapterParserTests extends AbstractHttpInboundTes
|
||||
TestObject(String text) {
|
||||
this.text = text;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ import static org.springframework.security.test.web.servlet.request.SecurityMock
|
||||
import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.multipart;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
@@ -38,6 +39,7 @@ import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.http.client.ClientHttpRequestFactory;
|
||||
@@ -71,6 +73,9 @@ import org.springframework.test.context.web.WebAppConfiguration;
|
||||
import org.springframework.test.web.client.MockMvcClientHttpRequestFactory;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.validation.Errors;
|
||||
import org.springframework.validation.Validator;
|
||||
import org.springframework.web.client.DefaultResponseErrorHandler;
|
||||
import org.springframework.web.client.HttpClientErrorException;
|
||||
import org.springframework.web.context.WebApplicationContext;
|
||||
@@ -199,6 +204,37 @@ public class HttpDslTests {
|
||||
}));
|
||||
}
|
||||
|
||||
@Autowired
|
||||
private Validator validator;
|
||||
|
||||
@Test
|
||||
public void testValidation() throws Exception {
|
||||
IntegrationFlow flow =
|
||||
IntegrationFlows.from(
|
||||
Http.inboundChannelAdapter("/validation")
|
||||
.requestMapping((mapping) -> mapping
|
||||
.methods(HttpMethod.POST)
|
||||
.consumes(MediaType.APPLICATION_JSON_VALUE))
|
||||
.requestPayloadType(TestModel.class)
|
||||
.validator(this.validator))
|
||||
.bridge()
|
||||
.get();
|
||||
|
||||
IntegrationFlowContext.IntegrationFlowRegistration flowRegistration =
|
||||
this.integrationFlowContext.registration(flow).register();
|
||||
|
||||
this.mockMvc.perform(
|
||||
post("/validation")
|
||||
.with(httpBasic("user", "user"))
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content("{\"name\": \"\"}"))
|
||||
.andExpect(status().isBadRequest())
|
||||
.andExpect(status().reason("Validation failure"));
|
||||
|
||||
flowRegistration.destroy();
|
||||
}
|
||||
|
||||
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
@EnableIntegration
|
||||
@@ -309,6 +345,11 @@ public class HttpDslTests {
|
||||
return channelSecurityInterceptor;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Validator customValidator() {
|
||||
return new TestModelValidator();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class HttpProxyResponseErrorHandler extends DefaultResponseErrorHandler {
|
||||
@@ -322,4 +363,35 @@ public class HttpDslTests {
|
||||
|
||||
}
|
||||
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -20,7 +20,6 @@ import org.springframework.core.ReactiveAdapterRegistry;
|
||||
import org.springframework.http.codec.ServerCodecConfigurer;
|
||||
import org.springframework.integration.http.dsl.HttpInboundEndpointSupportSpec;
|
||||
import org.springframework.integration.webflux.inbound.WebFluxInboundEndpoint;
|
||||
import org.springframework.validation.Validator;
|
||||
import org.springframework.web.reactive.accept.RequestedContentTypeResolver;
|
||||
|
||||
/**
|
||||
@@ -54,15 +53,4 @@ public class WebFluxInboundEndpointSpec
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 WebFluxInboundEndpointSpec validator(Validator validator) {
|
||||
this.target.setValidator(validator);
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -48,15 +48,11 @@ import org.springframework.http.server.reactive.ServerHttpRequest;
|
||||
import org.springframework.http.server.reactive.ServerHttpResponse;
|
||||
import org.springframework.integration.expression.ExpressionEvalMap;
|
||||
import org.springframework.integration.http.inbound.BaseHttpInboundEndpoint;
|
||||
import org.springframework.integration.http.support.IntegrationWebExchangeBindException;
|
||||
import org.springframework.integration.support.AbstractIntegrationMessageBuilder;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.validation.BeanPropertyBindingResult;
|
||||
import org.springframework.validation.ValidationUtils;
|
||||
import org.springframework.validation.Validator;
|
||||
import org.springframework.web.reactive.HandlerMapping;
|
||||
import org.springframework.web.reactive.accept.HeaderContentTypeResolver;
|
||||
import org.springframework.web.reactive.accept.RequestedContentTypeResolver;
|
||||
@@ -94,8 +90,6 @@ public class WebFluxInboundEndpoint extends BaseHttpInboundEndpoint implements W
|
||||
|
||||
private ReactiveAdapterRegistry adapterRegistry = new ReactiveAdapterRegistry();
|
||||
|
||||
private Validator validator;
|
||||
|
||||
public WebFluxInboundEndpoint() {
|
||||
this(true);
|
||||
}
|
||||
@@ -133,15 +127,6 @@ public class WebFluxInboundEndpoint extends BaseHttpInboundEndpoint implements W
|
||||
this.adapterRegistry = adapterRegistry;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getComponentType() {
|
||||
return super.getComponentType().replaceFirst("http", "webflux");
|
||||
@@ -244,14 +229,14 @@ public class WebFluxInboundEndpoint extends BaseHttpInboundEndpoint implements W
|
||||
Map<String, Object> readHints = Collections.emptyMap();
|
||||
if (adapter != null && adapter.isMultiValue()) {
|
||||
Flux<?> flux = httpMessageReader.read(bodyType, elementType, request, response, readHints);
|
||||
if (this.validator != null) {
|
||||
if (getValidator() != null) {
|
||||
flux = flux.doOnNext(this::validate);
|
||||
}
|
||||
return Mono.just(adapter.fromPublisher(flux));
|
||||
}
|
||||
else {
|
||||
Mono<?> mono = httpMessageReader.readMono(bodyType, elementType, request, response, readHints);
|
||||
if (this.validator != null) {
|
||||
if (getValidator() != null) {
|
||||
mono = mono.doOnNext(this::validate);
|
||||
}
|
||||
if (adapter != null) {
|
||||
@@ -263,14 +248,6 @@ public class WebFluxInboundEndpoint extends BaseHttpInboundEndpoint implements W
|
||||
}
|
||||
}
|
||||
|
||||
private 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);
|
||||
}
|
||||
}
|
||||
|
||||
private Mono<Tuple2<Message<Object>, RequestEntity<?>>> buildMessage(RequestEntity<?> httpEntity,
|
||||
ServerWebExchange exchange) {
|
||||
|
||||
|
||||
@@ -327,6 +327,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">
|
||||
|
||||
@@ -10,7 +10,11 @@
|
||||
|
||||
<si:channel id="requests"/>
|
||||
|
||||
<inbound-channel-adapter id="reactiveMinimalConfig" channel="requests"/>
|
||||
<beans:bean id="validator" class="org.mockito.Mockito" factory-method="mock">
|
||||
<beans:constructor-arg value="org.springframework.validation.Validator"/>
|
||||
</beans:bean>
|
||||
|
||||
<inbound-channel-adapter id="reactiveMinimalConfig" channel="requests" validator="validator"/>
|
||||
|
||||
<inbound-channel-adapter id="reactiveFullConfig" channel="requests"
|
||||
path="test1"
|
||||
|
||||
@@ -37,6 +37,7 @@ import org.springframework.integration.webflux.inbound.WebFluxInboundEndpoint;
|
||||
import org.springframework.messaging.MessageChannel;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
import org.springframework.validation.Validator;
|
||||
import org.springframework.web.reactive.accept.RequestedContentTypeResolver;
|
||||
|
||||
/**
|
||||
@@ -71,6 +72,9 @@ public class WebFluxInboundChannelAdapterParserTests {
|
||||
@Autowired
|
||||
private ReactiveAdapterRegistry reactiveAdapterRegistry;
|
||||
|
||||
@Autowired
|
||||
private Validator validator;
|
||||
|
||||
@Test
|
||||
public void reactiveMinimalConfig() {
|
||||
DirectFieldAccessor endpointAccessor = new DirectFieldAccessor(this.reactiveMinimalConfig);
|
||||
@@ -88,6 +92,7 @@ public class WebFluxInboundChannelAdapterParserTests {
|
||||
assertThat(endpointAccessor.getPropertyValue("requestedContentTypeResolver"))
|
||||
.isNotSameAs(this.requestedContentTypeResolver);
|
||||
assertThat(endpointAccessor.getPropertyValue("adapterRegistry")).isNotSameAs(this.reactiveAdapterRegistry);
|
||||
assertThat(endpointAccessor.getPropertyValue("validator")).isSameAs(this.validator);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -330,7 +330,7 @@ public class WebFluxDslTests {
|
||||
|
||||
@Override
|
||||
public Validator getValidator() {
|
||||
return webFluxValidator();
|
||||
return new TestModelValidator();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@@ -455,11 +455,6 @@ public class WebFluxDslTests {
|
||||
return new AffirmativeBased(Collections.singletonList(new RoleVoter()));
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Validator webFluxValidator() {
|
||||
return new TestModelValidator();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class TestModel {
|
||||
|
||||
@@ -90,6 +90,12 @@ The following table summarizes the various endpoints with quick links to the app
|
||||
| <<./http.adoc#http-inbound,Http Inbound Components>>
|
||||
| <<./http.adoc#http-outbound,HTTP Outbound Components>>
|
||||
|
||||
| *WebFlux*
|
||||
| <<./webflux.adoc#webflux-namespace,WebFlux Namespace Support>>
|
||||
| <<./webflux.adoc#webflux-namespace,WebFlux Namespace Support>>
|
||||
| <<./webflux.adoc#webflux-inbound,WebFlux Inbound Components>>
|
||||
| <<./webflux.adoc#webflux-outbound,WebFlux Outbound Components>>
|
||||
|
||||
| *JDBC*
|
||||
| <<./jdbc.adoc#jdbc-inbound-channel-adapter,Inbound Channel Adapter>> and <<./jdbc.adoc#stored-procedure-inbound-channel-adapter,Stored Procedure Inbound Channel Adapter>>
|
||||
| <<./jdbc.adoc#jdbc-outbound-channel-adapter,Outbound Channel Adapter>> and <<./jdbc.adoc#stored-procedure-outbound-channel-adapter,Stored Procedure Outbound Channel Adapter>>
|
||||
|
||||
@@ -32,8 +32,7 @@ The `javax.servlet:javax.servlet-api` dependency must be provided on the target
|
||||
|
||||
To receive messages over HTTP, you need to use an HTTP inbound channel adapter or an HTTP inbound gateway.
|
||||
To support the HTTP inbound adapters, they need to be deployed within a servlet container such as https://tomcat.apache.org/[Apache Tomcat] or https://www.eclipse.org/jetty/[Jetty].
|
||||
The easiest way to do this is to use Spring's
|
||||
https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/web/context/support/HttpRequestHandlerServlet.html[`HttpRequestHandlerServlet`], by providing the following servlet definition in the `web.xml` file:
|
||||
The easiest way to do this is to use Spring's https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/web/context/support/HttpRequestHandlerServlet.html[`HttpRequestHandlerServlet`], by providing the following servlet definition in the `web.xml` file:
|
||||
|
||||
====
|
||||
[source,xml]
|
||||
@@ -46,9 +45,7 @@ https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/web/c
|
||||
====
|
||||
|
||||
Notice that the servlet name matches the bean name.
|
||||
For more information on using the `HttpRequestHandlerServlet`, see
|
||||
https://docs.spring.io/spring/docs/current/spring-framework-reference/html/remoting.html[Remoting and web services using Spring],
|
||||
which is part of the Spring Framework Reference documentation.
|
||||
For more information on using the `HttpRequestHandlerServlet`, see https://docs.spring.io/spring/docs/current/spring-framework-reference/html/remoting.html[Remoting and web services using Spring], which is part of the Spring Framework Reference documentation.
|
||||
|
||||
If you are running within a Spring MVC application, then the aforementioned explicit servlet definition is not necessary.
|
||||
In that case, the bean name for your gateway can be matched against the URL path as you would for a Spring MVC Controller bean.
|
||||
@@ -79,8 +76,7 @@ An additional flag (`mergeWithDefaultConverters`) can be set along with the list
|
||||
By default, this flag is set to `false`, meaning that the custom converters replace the default list.
|
||||
|
||||
The message conversion process uses the (optional) `requestPayloadType` property and the incoming `Content-Type` header.
|
||||
Starting with version 4.3, if a request has no content type header, `application/octet-stream` is assumed, as
|
||||
recommended by `RFC 2616`.
|
||||
Starting with version 4.3, if a request has no content type header, `application/octet-stream` is assumed, as recommended by `RFC 2616`.
|
||||
Previously, the body of such messages was ignored.
|
||||
|
||||
Spring Integration 2.0 implemented multipart file support.
|
||||
@@ -146,10 +142,17 @@ The preceding example also shows how to customize the HTTP methods accepted by t
|
||||
The reply message is available in the model map.
|
||||
By default, the key for that map entry is 'reply', but you can override this default by setting the 'replyKey' property on the endpoint's configuration.
|
||||
|
||||
[[http-validation]]
|
||||
==== Payload Validation
|
||||
|
||||
Starting with version 5.2, the HTTP inbound endpoints can be supplied with a `Validator` to check a payload before sending into the channel.
|
||||
This payload is already a result of conversion and extraction after `payloadExpression` to narrow a validation scope in regards to the valuable data.
|
||||
The validation failure handling is fully the same what we have in Spring MVC https://docs.spring.io/spring/docs/current/spring-framework-reference/web.html#mvc-exceptionhandlers[Error Handling].
|
||||
|
||||
[[http-outbound]]
|
||||
=== HTTP Outbound Components
|
||||
|
||||
This section describes Spring Integration's HTTP outbound components
|
||||
This section describes Spring Integration's HTTP outbound components.
|
||||
|
||||
==== Using `HttpRequestExecutingMessageHandler`
|
||||
|
||||
@@ -205,8 +208,7 @@ If `transfer-cookies` is `false`, any `Set-Cookie` header received remains as `S
|
||||
HTTP is a request-response protocol.
|
||||
However, the response may not have a body, only headers.
|
||||
In this case, the `HttpRequestExecutingMessageHandler` produces a reply `Message` with the payload being an `org.springframework.http.ResponseEntity`, regardless of any provided `expected-response-type`.
|
||||
According to the https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html[HTTP RFC Status Code Definitions], there are many statuses that mandate that a response must not contain a message-body (for example,
|
||||
`204 No Content`).
|
||||
According to the https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html[HTTP RFC Status Code Definitions], there are many statuses that mandate that a response must not contain a message-body (for example, `204 No Content`).
|
||||
There are also cases where calls to the same URL might or might not return a response body.
|
||||
For example, the first request to an HTTP resource returns content, but the second does not (returning a `304 Not Modified`).
|
||||
In all cases, however, the `http_statusCode` message header is populated.
|
||||
@@ -350,8 +352,7 @@ For more information regarding handler mappings, see https://docs.spring.io/spri
|
||||
[[http-cors]]
|
||||
==== Cross-origin Resource Sharing (CORS) Support
|
||||
|
||||
Starting with version 4.2, you can configure the `<http:inbound-channel-adapter>` and `<http:inbound-gateway>` with
|
||||
a `<cross-origin>` element.
|
||||
Starting with version 4.2, you can configure the `<http:inbound-channel-adapter>` and `<http:inbound-gateway>` with a `<cross-origin>` element.
|
||||
It represents the same options as Spring MVC's `@CrossOrigin` for `@Controller` annotations and allows the configuration of cross-origin resource sharing (CORS) for Spring Integration HTTP endpoints:
|
||||
|
||||
* `origin`: List of allowed origins.
|
||||
@@ -376,8 +377,7 @@ This property controls the value of the `Access-Control-Max-Age` header in the p
|
||||
A value of `-1` means undefined.
|
||||
The default value is 1800 seconds (30 minutes).
|
||||
|
||||
The CORS Java Configuration is represented by the `org.springframework.integration.http.inbound.CrossOrigin` class,
|
||||
instances of which can be injected into the `HttpRequestHandlingEndpointSupport` beans.
|
||||
The CORS Java Configuration is represented by the `org.springframework.integration.http.inbound.CrossOrigin` class, instances of which can be injected into the `HttpRequestHandlingEndpointSupport` beans.
|
||||
|
||||
[[http-response-statuscode]]
|
||||
==== Response Status Code
|
||||
@@ -403,8 +403,7 @@ The following example shows how to set the status code to `ACCEPTED`:
|
||||
====
|
||||
|
||||
The `<http:inbound-gateway>` resolves the 'status code' from the `http_statusCode` header of the reply `Message`.
|
||||
Starting with version 4.2, the default response status code when no reply is received within the `reply-timeout`
|
||||
is `500 Internal Server Error`.
|
||||
Starting with version 4.2, the default response status code when no reply is received within the `reply-timeout` is `500 Internal Server Error`.
|
||||
There are two ways to modify this behavior:
|
||||
|
||||
* Add a `reply-timeout-status-code-expression`.
|
||||
@@ -427,8 +426,7 @@ The payload of the `ErrorMessage` is a `MessageTimeoutException`.
|
||||
It must be transformed to something that can be converted by the gateway, such as a `String`.
|
||||
A good candidate is the exception's message property, which is the value used when you use the `expression` technique.
|
||||
|
||||
If the error flow times out after a main flow timeout, `500 Internal Server Error` is returned, or, if the
|
||||
`reply-timeout-status-code-expression` is present, it is evaluated.
|
||||
If the error flow times out after a main flow timeout, `500 Internal Server Error` is returned, or, if the `reply-timeout-status-code-expression` is present, it is evaluated.
|
||||
|
||||
NOTE: Previously, the default status code for a timeout was `200 OK`.
|
||||
To restore that behavior, set `reply-timeout-status-code-expression="200"`.
|
||||
@@ -655,7 +653,7 @@ The `uriVariablesExpression` property provides a very powerful mechanism for eva
|
||||
We anticipate that people mostly use simple expressions, such as the preceding example.
|
||||
However, you can also configure something such as `"@uriVariablesBean.populate(#root)"` with an expression in the returned map being `variables.put("thing1", EXPRESSION_PARSER.parseExpression(message.getHeaders().get("thing2", String.class)));`, where the expression is dynamically provided in the message header named `thing2`.
|
||||
Since the header may come from an untrusted source, the HTTP outbound endpoints use `SimpleEvaluationContext` when evaluating these expressions.
|
||||
`SimpleEvaluationContext` uses only a subset of SpEL features.
|
||||
The `SimpleEvaluationContext` uses only a subset of SpEL features.
|
||||
If you trust your message sources and wish to use the restricted SpEL constructs, set the `trustedSpel` property of the outbound endpoint to `true`.
|
||||
====
|
||||
|
||||
@@ -830,8 +828,7 @@ image::images/http-outbound-gateway.png[align="center"]
|
||||
//TODO These images are too small, and the text within them is much too small.
|
||||
|
||||
You may want to configure the HTTP related timeout behavior, when making active HTTP requests by using the HTTP outbound gateway or the HTTP outbound channel adapter.
|
||||
In those instances, these two components use Spring's
|
||||
https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/web/client/RestTemplate.html[`RestTemplate`] support to execute HTTP requests.
|
||||
In those instances, these two components use Spring's https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/web/client/RestTemplate.html[`RestTemplate`] support to execute HTTP requests.
|
||||
|
||||
To configure timeouts for the HTTP outbound gateway and the HTTP outbound channel adapter, you can either reference a `RestTemplate` bean directly (by using the `rest-template` attribute) or you can provide a reference to a https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/http/client/ClientHttpRequestFactory.html[`ClientHttpRequestFactory`] bean (by using the `request-factory` attribute).
|
||||
Spring provides the following implementations of the `ClientHttpRequestFactory` interface:
|
||||
|
||||
@@ -91,8 +91,14 @@ See <<./http.adoc#http-request-mapping,Request Mapping Support>> and <<./http.ad
|
||||
|
||||
When the request body is empty or `payloadExpression` returns `null`, the request params (`MultiValueMap<String, String>`) is used for a `payload` of the target message to process.
|
||||
|
||||
[[webflux-validation]]
|
||||
==== Payload Validation
|
||||
|
||||
Starting with version 5.2, the `WebFluxInboundEndpoint` can be configured with a `Validator`.
|
||||
It is used to validate elements in the `Publisher` to which a request has been converted by the `HttpMessageReader`.
|
||||
Unlike the MVC validation in the <<./http.adoc#http-validation,HTTP Support>>, it is used to validate elements in the `Publisher` to which a request has been converted by the `HttpMessageReader`, before performing a fallback and `payloadExpression` functions.
|
||||
The Framework can't assume how complex the `Publisher` object can be after building the final payload.
|
||||
If there is a requirements to restrict validation visibility for exactly final payload (or its `Publisher` elements), the validation should go downstream instead of WebFlux endpoint.
|
||||
See more information in the Spring WebFlux https://docs.spring.io/spring/docs/5.1.8.RELEASE/spring-framework-reference/web-reactive.html#webflux-fn-handler-validation[documentation].
|
||||
An invalid payload is rejected with an `IntegrationWebExchangeBindException` (a `WebExchangeBindException` extension), containing all the validation `Errors`.
|
||||
See more in Spring Framework https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#validation[Reference Manual] about validation.
|
||||
|
||||
|
||||
@@ -95,6 +95,12 @@ See <<./ip.adoc#tcp-connection-factory, TCP Connection Factories>> for more info
|
||||
The `AbstractMailReceiver` has now an `autoCloseFolder` option (`true` by default), to disable an automatic folder close after a fetch, but populate `IntegrationMessageHeaderAccessor.CLOSEABLE_RESOURCE` header instead for downstream interaction.
|
||||
See <<./mail.adoc#mail-inbound,Mail-receiving Channel Adapter>> for more information.
|
||||
|
||||
[[x5.2-http]]
|
||||
==== HTTP Changes
|
||||
|
||||
The HTTP inbound endpoint now support a request payload validation.
|
||||
See <<./http.adoc#http,HTTP Support>> for more information.
|
||||
|
||||
[[x5.2-webflux]]
|
||||
==== WebFlux Changes
|
||||
|
||||
|
||||
Reference in New Issue
Block a user