INT-4315: Add WebFlux module
JIRA: https://jira.spring.io/browse/INT-4315 Move Reactive components outside of HTTP module to the new WebFlux one, including XSD, tests and documentation Make an appropriate polishing for the `http.adoc` with cross-link to the `webflux.adoc` Exclude transitive `spring-webmvc` for the `spring-integration-webflux`
This commit is contained in:
committed by
Gary Russell
parent
4fd32d2bd4
commit
84d60f4ab4
@@ -23,14 +23,10 @@ import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionReaderUtils;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
|
||||
import org.springframework.beans.factory.support.RootBeanDefinition;
|
||||
import org.springframework.integration.config.IntegrationConfigurationInitializer;
|
||||
import org.springframework.integration.config.xml.IntegrationNamespaceUtils;
|
||||
import org.springframework.integration.http.inbound.IntegrationHandlerResultHandler;
|
||||
import org.springframework.integration.http.inbound.IntegrationRequestMappingHandlerMapping;
|
||||
import org.springframework.integration.http.inbound.ReactiveIntegrationRequestMappingHandlerMapping;
|
||||
import org.springframework.integration.http.support.HttpContextUtils;
|
||||
|
||||
/**
|
||||
@@ -48,7 +44,6 @@ public class HttpIntegrationConfigurationInitializer implements IntegrationConfi
|
||||
public void initialize(ConfigurableListableBeanFactory beanFactory) throws BeansException {
|
||||
if (beanFactory instanceof BeanDefinitionRegistry) {
|
||||
registerRequestMappingHandlerMappingIfNecessary((BeanDefinitionRegistry) beanFactory);
|
||||
registerReactiveRequestMappingHandlerMappingIfNecessary((BeanDefinitionRegistry) beanFactory);
|
||||
}
|
||||
else {
|
||||
logger.warn("'IntegrationRequestMappingHandlerMapping' isn't registered because 'beanFactory'" +
|
||||
@@ -78,30 +73,4 @@ public class HttpIntegrationConfigurationInitializer implements IntegrationConfi
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a {@link ReactiveIntegrationRequestMappingHandlerMapping}
|
||||
* which could also be overridden by the user by simply registering
|
||||
* a {@link ReactiveIntegrationRequestMappingHandlerMapping} {@code <bean>} with 'id'
|
||||
* {@link HttpContextUtils#REACTIVE_HANDLER_MAPPING_BEAN_NAME}.
|
||||
* <p>
|
||||
* In addition, checks if the {@code org.springframework.web.reactive.result.method.RequestMappingInfo}
|
||||
* class is present on the classpath.
|
||||
* When Spring Integration HTTP is used only as an HTTP client, there is no reason to use and register
|
||||
* the HTTP server components.
|
||||
*/
|
||||
private void registerReactiveRequestMappingHandlerMappingIfNecessary(BeanDefinitionRegistry registry) {
|
||||
if (HttpContextUtils.WEB_FLUX_PRESENT &&
|
||||
!registry.containsBeanDefinition(HttpContextUtils.REACTIVE_HANDLER_MAPPING_BEAN_NAME)) {
|
||||
BeanDefinitionBuilder requestMappingBuilder =
|
||||
BeanDefinitionBuilder.genericBeanDefinition(ReactiveIntegrationRequestMappingHandlerMapping.class);
|
||||
requestMappingBuilder.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
|
||||
requestMappingBuilder.addPropertyValue(IntegrationNamespaceUtils.ORDER, 0);
|
||||
registry.registerBeanDefinition(HttpContextUtils.REACTIVE_HANDLER_MAPPING_BEAN_NAME,
|
||||
requestMappingBuilder.getBeanDefinition());
|
||||
|
||||
BeanDefinitionReaderUtils.registerWithGeneratedName(
|
||||
new RootBeanDefinition(IntegrationHandlerResultHandler.class), registry);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -34,8 +34,6 @@ public class HttpNamespaceHandler extends AbstractIntegrationNamespaceHandler {
|
||||
registerBeanDefinitionParser("inbound-gateway", new HttpInboundEndpointParser(true));
|
||||
registerBeanDefinitionParser("outbound-channel-adapter", new HttpOutboundChannelAdapterParser());
|
||||
registerBeanDefinitionParser("outbound-gateway", new HttpOutboundGatewayParser());
|
||||
registerBeanDefinitionParser("outbound-reactive-channel-adapter", new HttpOutboundChannelAdapterParser());
|
||||
registerBeanDefinitionParser("outbound-reactive-gateway", new HttpOutboundGatewayParser());
|
||||
registerBeanDefinitionParser("graph-controller", new IntegrationGraphControllerParser());
|
||||
}
|
||||
|
||||
|
||||
@@ -18,13 +18,13 @@ package org.springframework.integration.http.config;
|
||||
|
||||
import org.w3c.dom.Element;
|
||||
|
||||
import org.springframework.beans.factory.config.RuntimeBeanReference;
|
||||
import org.springframework.beans.factory.support.AbstractBeanDefinition;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
|
||||
import org.springframework.beans.factory.xml.ParserContext;
|
||||
import org.springframework.integration.config.xml.AbstractOutboundChannelAdapterParser;
|
||||
import org.springframework.integration.config.xml.IntegrationNamespaceUtils;
|
||||
import org.springframework.integration.http.outbound.HttpRequestExecutingMessageHandler;
|
||||
import org.springframework.integration.http.outbound.ReactiveHttpRequestExecutingMessageHandler;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
@@ -42,41 +42,13 @@ public class HttpOutboundChannelAdapterParser extends AbstractOutboundChannelAda
|
||||
|
||||
@Override
|
||||
protected AbstractBeanDefinition parseConsumer(Element element, ParserContext parserContext) {
|
||||
BeanDefinitionBuilder builder;
|
||||
boolean reactive = element.getLocalName().contains("reactive");
|
||||
if (reactive) {
|
||||
builder = BeanDefinitionBuilder.genericBeanDefinition(ReactiveHttpRequestExecutingMessageHandler.class);
|
||||
}
|
||||
else {
|
||||
builder = BeanDefinitionBuilder.genericBeanDefinition(HttpRequestExecutingMessageHandler.class);
|
||||
}
|
||||
BeanDefinitionBuilder builder = getBuilder(element, parserContext);
|
||||
|
||||
builder.addPropertyValue("expectReply", false);
|
||||
HttpAdapterParsingUtils.configureUrlConstructorArg(element, parserContext, builder);
|
||||
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "encode-uri");
|
||||
HttpAdapterParsingUtils.setHttpMethodOrExpression(element, parserContext, builder);
|
||||
|
||||
if (reactive) {
|
||||
String webClientRef = element.getAttribute("web-client");
|
||||
|
||||
if (StringUtils.hasText(webClientRef)) {
|
||||
builder.addConstructorArgReference(webClientRef);
|
||||
}
|
||||
}
|
||||
else {
|
||||
String restTemplateRef = element.getAttribute("rest-template");
|
||||
|
||||
if (StringUtils.hasText(restTemplateRef)) {
|
||||
HttpAdapterParsingUtils.verifyNoRestTemplateAttributes(element, parserContext);
|
||||
builder.addConstructorArgReference(restTemplateRef);
|
||||
}
|
||||
else {
|
||||
for (String referenceAttributeName : HttpAdapterParsingUtils.SYNC_REST_TEMPLATE_REFERENCE_ATTRIBUTES) {
|
||||
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, referenceAttributeName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String headerMapper = element.getAttribute("header-mapper");
|
||||
String mappedRequestHeaders = element.getAttribute("mapped-request-headers");
|
||||
if (StringUtils.hasText(headerMapper)) {
|
||||
@@ -101,4 +73,24 @@ public class HttpOutboundChannelAdapterParser extends AbstractOutboundChannelAda
|
||||
return builder.getBeanDefinition();
|
||||
}
|
||||
|
||||
protected BeanDefinitionBuilder getBuilder(Element element, ParserContext parserContext) {
|
||||
BeanDefinitionBuilder builder =
|
||||
BeanDefinitionBuilder.genericBeanDefinition(HttpRequestExecutingMessageHandler.class);
|
||||
|
||||
String restTemplateRef = element.getAttribute("rest-template");
|
||||
|
||||
if (StringUtils.hasText(restTemplateRef)) {
|
||||
HttpAdapterParsingUtils.verifyNoRestTemplateAttributes(element, parserContext);
|
||||
builder.getBeanDefinition()
|
||||
.getConstructorArgumentValues()
|
||||
.addIndexedArgumentValue(1, new RuntimeBeanReference(restTemplateRef));
|
||||
}
|
||||
else {
|
||||
for (String referenceAttributeName : HttpAdapterParsingUtils.SYNC_REST_TEMPLATE_REFERENCE_ATTRIBUTES) {
|
||||
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, referenceAttributeName);
|
||||
}
|
||||
}
|
||||
return builder;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -18,12 +18,12 @@ package org.springframework.integration.http.config;
|
||||
|
||||
import org.w3c.dom.Element;
|
||||
|
||||
import org.springframework.beans.factory.config.RuntimeBeanReference;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
|
||||
import org.springframework.beans.factory.xml.ParserContext;
|
||||
import org.springframework.integration.config.xml.AbstractConsumerEndpointParser;
|
||||
import org.springframework.integration.config.xml.IntegrationNamespaceUtils;
|
||||
import org.springframework.integration.http.outbound.HttpRequestExecutingMessageHandler;
|
||||
import org.springframework.integration.http.outbound.ReactiveHttpRequestExecutingMessageHandler;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
@@ -44,39 +44,12 @@ public class HttpOutboundGatewayParser extends AbstractConsumerEndpointParser {
|
||||
|
||||
@Override
|
||||
protected BeanDefinitionBuilder parseHandler(Element element, ParserContext parserContext) {
|
||||
BeanDefinitionBuilder builder;
|
||||
boolean reactive = element.getLocalName().contains("reactive");
|
||||
if (reactive) {
|
||||
builder = BeanDefinitionBuilder.genericBeanDefinition(ReactiveHttpRequestExecutingMessageHandler.class);
|
||||
}
|
||||
else {
|
||||
builder = BeanDefinitionBuilder.genericBeanDefinition(HttpRequestExecutingMessageHandler.class);
|
||||
}
|
||||
BeanDefinitionBuilder builder = getBuilder(element, parserContext);
|
||||
|
||||
HttpAdapterParsingUtils.configureUrlConstructorArg(element, parserContext, builder);
|
||||
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "encode-uri");
|
||||
HttpAdapterParsingUtils.setHttpMethodOrExpression(element, parserContext, builder);
|
||||
|
||||
if (reactive) {
|
||||
String webClientRef = element.getAttribute("web-client");
|
||||
if (StringUtils.hasText(webClientRef)) {
|
||||
builder.addConstructorArgReference(webClientRef);
|
||||
}
|
||||
}
|
||||
else {
|
||||
String restTemplateRef = element.getAttribute("rest-template");
|
||||
|
||||
if (StringUtils.hasText(restTemplateRef)) {
|
||||
HttpAdapterParsingUtils.verifyNoRestTemplateAttributes(element, parserContext);
|
||||
builder.addConstructorArgReference(restTemplateRef);
|
||||
}
|
||||
else {
|
||||
for (String referenceAttributeName : HttpAdapterParsingUtils.SYNC_REST_TEMPLATE_REFERENCE_ATTRIBUTES) {
|
||||
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, referenceAttributeName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String headerMapper = element.getAttribute("header-mapper");
|
||||
String mappedRequestHeaders = element.getAttribute("mapped-request-headers");
|
||||
String mappedResponseHeaders = element.getAttribute("mapped-response-headers");
|
||||
@@ -113,4 +86,24 @@ public class HttpOutboundGatewayParser extends AbstractConsumerEndpointParser {
|
||||
return builder;
|
||||
}
|
||||
|
||||
protected BeanDefinitionBuilder getBuilder(Element element, ParserContext parserContext) {
|
||||
BeanDefinitionBuilder builder =
|
||||
BeanDefinitionBuilder.genericBeanDefinition(HttpRequestExecutingMessageHandler.class);
|
||||
|
||||
String restTemplateRef = element.getAttribute("rest-template");
|
||||
|
||||
if (StringUtils.hasText(restTemplateRef)) {
|
||||
HttpAdapterParsingUtils.verifyNoRestTemplateAttributes(element, parserContext);
|
||||
builder.getBeanDefinition()
|
||||
.getConstructorArgumentValues()
|
||||
.addIndexedArgumentValue(1, new RuntimeBeanReference(restTemplateRef));
|
||||
}
|
||||
else {
|
||||
for (String referenceAttributeName : HttpAdapterParsingUtils.SYNC_REST_TEMPLATE_REFERENCE_ATTRIBUTES) {
|
||||
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, referenceAttributeName);
|
||||
}
|
||||
}
|
||||
return builder;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -61,7 +61,7 @@ public abstract class BaseHttpMessageHandlerSpec<S extends BaseHttpMessageHandle
|
||||
this.target.setHeaderMapper(this.headerMapper);
|
||||
}
|
||||
|
||||
S expectReply(boolean expectReply) {
|
||||
protected S expectReply(boolean expectReply) {
|
||||
this.target.setExpectReply(expectReply);
|
||||
return _this();
|
||||
}
|
||||
|
||||
@@ -24,12 +24,10 @@ import org.springframework.expression.common.LiteralExpression;
|
||||
import org.springframework.integration.expression.FunctionExpression;
|
||||
import org.springframework.integration.http.inbound.HttpRequestHandlingController;
|
||||
import org.springframework.integration.http.inbound.HttpRequestHandlingMessagingGateway;
|
||||
import org.springframework.integration.http.inbound.ReactiveHttpInboundEndpoint;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
|
||||
/**
|
||||
* The HTTP components Factory.
|
||||
@@ -128,98 +126,6 @@ public final class Http {
|
||||
return new HttpMessageHandlerSpec(uriExpression, restTemplate).expectReply(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an {@link ReactiveHttpMessageHandlerSpec} builder for one-way adapter based on provided {@link URI}.
|
||||
* @param uri the {@link URI} to send requests.
|
||||
* @return the ReactiveHttpMessageHandlerSpec instance
|
||||
*/
|
||||
public static ReactiveHttpMessageHandlerSpec outboundReactiveChannelAdapter(URI uri) {
|
||||
return outboundReactiveChannelAdapter(uri, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an {@link ReactiveHttpMessageHandlerSpec} builder for one-way adapter based on provided {@code uri}.
|
||||
* @param uri the {@code uri} to send requests.
|
||||
* @return the ReactiveHttpMessageHandlerSpec instance
|
||||
*/
|
||||
public static ReactiveHttpMessageHandlerSpec outboundReactiveChannelAdapter(String uri) {
|
||||
return outboundReactiveChannelAdapter(uri, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an {@link ReactiveHttpMessageHandlerSpec} builder for one-way adapter based on provided {@code Function}
|
||||
* to evaluate target {@code uri} against request message.
|
||||
* @param uriFunction the {@code Function} to evaluate {@code uri} at runtime.
|
||||
* @param <P> the expected payload type.
|
||||
* @return the ReactiveHttpMessageHandlerSpec instance
|
||||
*/
|
||||
public static <P> ReactiveHttpMessageHandlerSpec outboundReactiveChannelAdapter(Function<Message<P>, ?> uriFunction) {
|
||||
return outboundReactiveChannelAdapter(new FunctionExpression<>(uriFunction));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an {@link ReactiveHttpMessageHandlerSpec} builder for one-way adapter
|
||||
* based on provided SpEL {@link Expression} to evaluate target {@code uri}
|
||||
* against request message.
|
||||
* @param uriExpression the SpEL {@link Expression} to evaluate {@code uri} at runtime.
|
||||
* @return the ReactiveHttpMessageHandlerSpec instance
|
||||
*/
|
||||
public static ReactiveHttpMessageHandlerSpec outboundReactiveChannelAdapter(Expression uriExpression) {
|
||||
return outboundReactiveChannelAdapter(uriExpression, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an {@link ReactiveHttpMessageHandlerSpec} builder for one-way adapter
|
||||
* based on provided {@link URI} and {@link WebClient}.
|
||||
* @param uri the {@link URI} to send requests.
|
||||
* @param webClient {@link WebClient} to use.
|
||||
* @return the ReactiveHttpMessageHandlerSpec instance
|
||||
*/
|
||||
public static ReactiveHttpMessageHandlerSpec outboundReactiveChannelAdapter(URI uri, WebClient webClient) {
|
||||
return new ReactiveHttpMessageHandlerSpec(uri, webClient)
|
||||
.expectReply(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an {@link ReactiveHttpMessageHandlerSpec} builder for one-way adapter
|
||||
* based on provided {@code uri} and {@link WebClient}.
|
||||
* @param uri the {@code uri} to send requests.
|
||||
* @param webClient {@link WebClient} to use.
|
||||
* @return the ReactiveHttpMessageHandlerSpec instance
|
||||
*/
|
||||
public static ReactiveHttpMessageHandlerSpec outboundReactiveChannelAdapter(String uri, WebClient webClient) {
|
||||
return new ReactiveHttpMessageHandlerSpec(uri, webClient)
|
||||
.expectReply(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an {@link ReactiveHttpMessageHandlerSpec} builder for one-way adapter
|
||||
* based on provided {@code Function} to evaluate target {@code uri} against request message
|
||||
* and {@link WebClient} for HTTP exchanges.
|
||||
* @param uriFunction the {@code Function} to evaluate {@code uri} at runtime.
|
||||
* @param webClient {@link WebClient} to use.
|
||||
* @param <P> the expected payload type.
|
||||
* @return the ReactiveHttpMessageHandlerSpec instance
|
||||
*/
|
||||
public static <P> ReactiveHttpMessageHandlerSpec outboundReactiveChannelAdapter(Function<Message<P>, ?> uriFunction,
|
||||
WebClient webClient) {
|
||||
return outboundReactiveChannelAdapter(new FunctionExpression<>(uriFunction), webClient);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an {@link ReactiveHttpMessageHandlerSpec} builder for one-way adapter
|
||||
* based on provided SpEL {@link Expression} to evaluate target {@code uri}
|
||||
* against request message and {@link WebClient} for HTTP exchanges.
|
||||
* @param uriExpression the SpEL {@link Expression} to evaluate {@code uri} at runtime.
|
||||
* @param webClient {@link WebClient} to use.
|
||||
* @return the ReactiveHttpMessageHandlerSpec instance
|
||||
*/
|
||||
public static ReactiveHttpMessageHandlerSpec outboundReactiveChannelAdapter(Expression uriExpression,
|
||||
WebClient webClient) {
|
||||
return new ReactiveHttpMessageHandlerSpec(uriExpression, webClient)
|
||||
.expectReply(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an {@link HttpMessageHandlerSpec} builder for request-reply gateway based on provided {@link URI}.
|
||||
* @param uri the {@link URI} to send requests.
|
||||
@@ -307,96 +213,6 @@ public final class Http {
|
||||
return new HttpMessageHandlerSpec(uriExpression, restTemplate);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an {@link ReactiveHttpMessageHandlerSpec} builder for request-reply gateway
|
||||
* based on provided {@link URI}.
|
||||
* @param uri the {@link URI} to send requests.
|
||||
* @return the ReactiveHttpMessageHandlerSpec instance
|
||||
*/
|
||||
public static ReactiveHttpMessageHandlerSpec outboundReactiveGateway(URI uri) {
|
||||
return outboundReactiveGateway(uri, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an {@link ReactiveHttpMessageHandlerSpec} builder for request-reply gateway
|
||||
* based on provided {@code uri}.
|
||||
* @param uri the {@code uri} to send requests.
|
||||
* @return the ReactiveHttpMessageHandlerSpec instance
|
||||
*/
|
||||
public static ReactiveHttpMessageHandlerSpec outboundReactiveGateway(String uri) {
|
||||
return outboundReactiveGateway(uri, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an {@link ReactiveHttpMessageHandlerSpec} builder for request-reply gateway
|
||||
* based on provided {@code Function} to evaluate target {@code uri} against request message.
|
||||
* @param uriFunction the {@code Function} to evaluate {@code uri} at runtime.
|
||||
* @param <P> the expected payload type.
|
||||
* @return the ReactiveHttpMessageHandlerSpec instance
|
||||
*/
|
||||
public static <P> ReactiveHttpMessageHandlerSpec outboundReactiveGateway(Function<Message<P>, ?> uriFunction) {
|
||||
return outboundReactiveGateway(new FunctionExpression<>(uriFunction));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an {@link ReactiveHttpMessageHandlerSpec} builder for request-reply gateway
|
||||
* based on provided SpEL {@link Expression} to evaluate target {@code uri} against request message.
|
||||
* @param uriExpression the SpEL {@link Expression} to evaluate {@code uri} at runtime.
|
||||
* @return the ReactiveHttpMessageHandlerSpec instance
|
||||
*/
|
||||
public static ReactiveHttpMessageHandlerSpec outboundReactiveGateway(Expression uriExpression) {
|
||||
return outboundReactiveGateway(uriExpression, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an {@link ReactiveHttpMessageHandlerSpec} builder for request-reply gateway
|
||||
* based on provided {@link URI} and {@link WebClient}.
|
||||
* @param uri the {@link URI} to send requests.
|
||||
* @param webClient {@link WebClient} to use.
|
||||
* @return the ReactiveHttpMessageHandlerSpec instance
|
||||
*/
|
||||
public static ReactiveHttpMessageHandlerSpec outboundReactiveGateway(URI uri, WebClient webClient) {
|
||||
return new ReactiveHttpMessageHandlerSpec(uri, webClient);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an {@link ReactiveHttpMessageHandlerSpec} builder for request-reply gateway
|
||||
* based on provided {@code uri} and {@link WebClient}.
|
||||
* @param uri the {@code uri} to send requests.
|
||||
* @param webClient {@link WebClient} to use.
|
||||
* @return the ReactiveHttpMessageHandlerSpec instance
|
||||
*/
|
||||
public static ReactiveHttpMessageHandlerSpec outboundReactiveGateway(String uri, WebClient webClient) {
|
||||
return new ReactiveHttpMessageHandlerSpec(uri, webClient);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an {@link ReactiveHttpMessageHandlerSpec} builder for request-reply gateway
|
||||
* based on provided {@code Function} to evaluate target {@code uri} against request message
|
||||
* and {@link WebClient} for HTTP exchanges.
|
||||
* @param uriFunction the {@code Function} to evaluate {@code uri} at runtime.
|
||||
* @param webClient {@link WebClient} to use.
|
||||
* @param <P> the expected payload type.
|
||||
* @return the ReactiveHttpMessageHandlerSpec instance
|
||||
*/
|
||||
public static <P> ReactiveHttpMessageHandlerSpec outboundReactiveGateway(Function<Message<P>, ?> uriFunction,
|
||||
WebClient webClient) {
|
||||
return outboundReactiveGateway(new FunctionExpression<>(uriFunction), webClient);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an {@link ReactiveHttpMessageHandlerSpec} builder for request-reply gateway
|
||||
* based on provided SpEL {@link Expression} to evaluate target {@code uri}
|
||||
* against request message and {@link WebClient} for HTTP exchanges.
|
||||
* @param uriExpression the SpEL {@link Expression} to evaluate {@code uri} at runtime.
|
||||
* @param webClient {@link WebClient} to use.
|
||||
* @return the ReactiveHttpMessageHandlerSpec instance
|
||||
*/
|
||||
public static ReactiveHttpMessageHandlerSpec outboundReactiveGateway(Expression uriExpression,
|
||||
WebClient webClient) {
|
||||
return new ReactiveHttpMessageHandlerSpec(uriExpression, webClient);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an {@link HttpControllerEndpointSpec} builder for one-way adapter
|
||||
* based on the provided MVC {@code viewName} and {@code path} array for mapping.
|
||||
@@ -468,28 +284,6 @@ public final class Http {
|
||||
return new HttpRequestHandlerEndpointSpec(new HttpRequestHandlingMessagingGateway(), path);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create an {@link ReactiveHttpInboundEndpointSpec} builder for one-way reactive adapter
|
||||
* based on the provided {@code path} array for mapping.
|
||||
* @param path the path mapping URIs (e.g. "/myPath.do").
|
||||
* @return the ReactiveHttpInboundEndpointSpec instance
|
||||
*/
|
||||
public static ReactiveHttpInboundEndpointSpec inboundReactiveChannelAdapter(String... path) {
|
||||
ReactiveHttpInboundEndpoint httpInboundChannelAdapter = new ReactiveHttpInboundEndpoint(false);
|
||||
return new ReactiveHttpInboundEndpointSpec(httpInboundChannelAdapter, path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an {@link ReactiveHttpInboundEndpointSpec} builder for request-reply reactive gateway
|
||||
* based on the provided {@code path} array for mapping.
|
||||
* @param path the path mapping URIs (e.g. "/myPath.do").
|
||||
* @return the ReactiveHttpInboundEndpointSpec instance
|
||||
*/
|
||||
public static ReactiveHttpInboundEndpointSpec inboundReactiveGateway(String... path) {
|
||||
return new ReactiveHttpInboundEndpointSpec(new ReactiveHttpInboundEndpoint(), path);
|
||||
}
|
||||
|
||||
private Http() {
|
||||
super();
|
||||
}
|
||||
|
||||
@@ -57,7 +57,7 @@ public abstract class HttpInboundEndpointSupportSpec<S extends HttpInboundEndpoi
|
||||
|
||||
private HeaderMapper<HttpHeaders> explicitHeaderMapper;
|
||||
|
||||
HttpInboundEndpointSupportSpec(E gateway, String... path) {
|
||||
protected HttpInboundEndpointSupportSpec(E gateway, String... path) {
|
||||
super(gateway);
|
||||
this.requestMapping.setPathPatterns(path);
|
||||
this.target.setRequestMapping(this.requestMapping);
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.integration.http.dsl;
|
||||
|
||||
import org.springframework.core.ReactiveAdapterRegistry;
|
||||
import org.springframework.http.codec.ServerCodecConfigurer;
|
||||
import org.springframework.integration.http.inbound.ReactiveHttpInboundEndpoint;
|
||||
import org.springframework.web.reactive.accept.RequestedContentTypeResolver;
|
||||
|
||||
/**
|
||||
* The {@link HttpInboundEndpointSupportSpec} implementation for the {@link ReactiveHttpInboundEndpoint}.
|
||||
*
|
||||
* @author Artem Bilan
|
||||
*
|
||||
* @since 5.0
|
||||
*/
|
||||
public class ReactiveHttpInboundEndpointSpec
|
||||
extends HttpInboundEndpointSupportSpec<ReactiveHttpInboundEndpointSpec, ReactiveHttpInboundEndpoint> {
|
||||
|
||||
ReactiveHttpInboundEndpointSpec(ReactiveHttpInboundEndpoint gateway, String... path) {
|
||||
super(gateway, path);
|
||||
}
|
||||
|
||||
public ReactiveHttpInboundEndpointSpec codecConfigurer(ServerCodecConfigurer codecConfigurer) {
|
||||
this.target.setCodecConfigurer(codecConfigurer);
|
||||
return this;
|
||||
}
|
||||
|
||||
public ReactiveHttpInboundEndpointSpec requestedContentTypeResolver(
|
||||
RequestedContentTypeResolver requestedContentTypeResolver) {
|
||||
|
||||
this.target.setRequestedContentTypeResolver(requestedContentTypeResolver);
|
||||
return this;
|
||||
}
|
||||
|
||||
public ReactiveHttpInboundEndpointSpec reactiveAdapterRegistry(ReactiveAdapterRegistry adapterRegistry) {
|
||||
this.target.setReactiveAdapterRegistry(adapterRegistry);
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.integration.http.dsl;
|
||||
|
||||
import java.net.URI;
|
||||
|
||||
import org.springframework.expression.Expression;
|
||||
import org.springframework.expression.common.LiteralExpression;
|
||||
import org.springframework.integration.expression.ValueExpression;
|
||||
import org.springframework.integration.http.outbound.ReactiveHttpRequestExecutingMessageHandler;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
|
||||
/**
|
||||
* The {@link BaseHttpMessageHandlerSpec} implementation for the {@link ReactiveHttpRequestExecutingMessageHandler}.
|
||||
*
|
||||
* @author Shiliang Li
|
||||
* @author Artem Bilan
|
||||
*
|
||||
* @since 5.0
|
||||
*
|
||||
* @see ReactiveHttpRequestExecutingMessageHandler
|
||||
*/
|
||||
public class ReactiveHttpMessageHandlerSpec
|
||||
extends BaseHttpMessageHandlerSpec<ReactiveHttpMessageHandlerSpec, ReactiveHttpRequestExecutingMessageHandler> {
|
||||
|
||||
private final WebClient webClient;
|
||||
|
||||
ReactiveHttpMessageHandlerSpec(URI uri, WebClient webClient) {
|
||||
this(new ValueExpression<>(uri), webClient);
|
||||
}
|
||||
|
||||
ReactiveHttpMessageHandlerSpec(String uri, WebClient webClient) {
|
||||
this(new LiteralExpression(uri), webClient);
|
||||
}
|
||||
|
||||
ReactiveHttpMessageHandlerSpec(Expression uriExpression, WebClient webClient) {
|
||||
super(new ReactiveHttpRequestExecutingMessageHandler(uriExpression, webClient));
|
||||
this.webClient = webClient;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isClientSet() {
|
||||
return this.webClient != null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.integration.http.inbound;
|
||||
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.web.method.HandlerMethod;
|
||||
import org.springframework.web.reactive.HandlerResult;
|
||||
import org.springframework.web.reactive.HandlerResultHandler;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
/**
|
||||
* A {@link HandlerResultHandler} implementation to handle the result of the
|
||||
* {@link ReactiveHttpInboundEndpoint} execution. Actually just return the
|
||||
* {@code result.getReturnValue()} which essentially is expected {@code Mono<Void>}.
|
||||
*
|
||||
* @author Artem Bilan
|
||||
*
|
||||
* @since 5.0
|
||||
*
|
||||
* @see ReactiveHttpInboundEndpoint
|
||||
*/
|
||||
public class IntegrationHandlerResultHandler implements HandlerResultHandler, Ordered {
|
||||
|
||||
@Override
|
||||
public boolean supports(HandlerResult result) {
|
||||
Object handler = result.getHandler();
|
||||
return handler instanceof HandlerMethod
|
||||
&& ReactiveHttpInboundEndpoint.class.isAssignableFrom(((HandlerMethod) handler).getBeanType());
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public Mono<Void> handleResult(ServerWebExchange exchange, HandlerResult result) {
|
||||
return (Mono<Void>) result.getReturnValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return HIGHEST_PRECEDENCE;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -50,7 +50,7 @@ import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandl
|
||||
* elements, if there is none registered, yet. However it can be configured as a regular
|
||||
* bean with appropriate configuration for {@link RequestMappingHandlerMapping}.
|
||||
* It is recommended to have only one similar bean in the application context using the 'id'
|
||||
* {@link org.springframework.integration.http.support.HttpContextUtils#REACTIVE_HANDLER_MAPPING_BEAN_NAME}.
|
||||
* {@link org.springframework.integration.http.support.HttpContextUtils#HANDLER_MAPPING_BEAN_NAME}.
|
||||
* <p>
|
||||
* In most cases, Spring MVC offers to configure Request Mapping via
|
||||
* {@code org.springframework.stereotype.Controller} and
|
||||
|
||||
@@ -1,461 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.integration.http.inbound;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.reactivestreams.Publisher;
|
||||
|
||||
import org.springframework.core.ReactiveAdapter;
|
||||
import org.springframework.core.ReactiveAdapterRegistry;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.expression.Expression;
|
||||
import org.springframework.expression.spel.support.StandardEvaluationContext;
|
||||
import org.springframework.http.HttpEntity;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.codec.HttpMessageReader;
|
||||
import org.springframework.http.codec.HttpMessageWriter;
|
||||
import org.springframework.http.codec.ServerCodecConfigurer;
|
||||
import org.springframework.http.server.reactive.ServerHttpRequest;
|
||||
import org.springframework.http.server.reactive.ServerHttpResponse;
|
||||
import org.springframework.integration.gateway.MessagingGatewaySupport;
|
||||
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.web.reactive.HandlerMapping;
|
||||
import org.springframework.web.reactive.accept.HeaderContentTypeResolver;
|
||||
import org.springframework.web.reactive.accept.RequestedContentTypeResolver;
|
||||
import org.springframework.web.server.NotAcceptableStatusException;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import org.springframework.web.server.UnsupportedMediaTypeStatusException;
|
||||
import org.springframework.web.server.WebHandler;
|
||||
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
/**
|
||||
* A {@link MessagingGatewaySupport} implementation for Spring WebFlux
|
||||
* HTTP requests execution.
|
||||
*
|
||||
* @author Artem Bilan
|
||||
*
|
||||
* @since 5.0
|
||||
*
|
||||
* @see org.springframework.web.reactive.result.HandlerResultHandlerSupport
|
||||
* @see org.springframework.web.reactive.config.EnableWebFlux
|
||||
*/
|
||||
public class ReactiveHttpInboundEndpoint extends BaseHttpInboundEndpoint implements WebHandler {
|
||||
|
||||
private static final MediaType MEDIA_TYPE_APPLICATION_ALL = new MediaType("application");
|
||||
|
||||
private ServerCodecConfigurer codecConfigurer = ServerCodecConfigurer.create();
|
||||
|
||||
private RequestedContentTypeResolver requestedContentTypeResolver = new HeaderContentTypeResolver();
|
||||
|
||||
private ReactiveAdapterRegistry adapterRegistry = new ReactiveAdapterRegistry();
|
||||
|
||||
public ReactiveHttpInboundEndpoint() {
|
||||
this(true);
|
||||
}
|
||||
|
||||
public ReactiveHttpInboundEndpoint(boolean expectReply) {
|
||||
super(expectReply);
|
||||
}
|
||||
|
||||
/**
|
||||
* A {@link ServerCodecConfigurer} for the request readers and response writers.
|
||||
* By default the {@link ServerCodecConfigurer#create()} factory is used.
|
||||
* @param codecConfigurer the {@link ServerCodecConfigurer} to use.
|
||||
*/
|
||||
public void setCodecConfigurer(ServerCodecConfigurer codecConfigurer) {
|
||||
Assert.notNull(codecConfigurer, "'codecConfigurer' must not be null");
|
||||
this.codecConfigurer = codecConfigurer;
|
||||
}
|
||||
|
||||
/**
|
||||
* A strategy to resolve the requested media types for a {@code ServerWebExchange}.
|
||||
* A {@link HeaderContentTypeResolver} is used by default.
|
||||
* @param requestedContentTypeResolver the {@link RequestedContentTypeResolver} to use.
|
||||
*/
|
||||
public void setRequestedContentTypeResolver(RequestedContentTypeResolver requestedContentTypeResolver) {
|
||||
Assert.notNull(requestedContentTypeResolver, "'requestedContentTypeResolver' must not be null");
|
||||
this.requestedContentTypeResolver = requestedContentTypeResolver;
|
||||
}
|
||||
|
||||
/**
|
||||
* A registry of adapters to adapt a Reactive Streams {@link Publisher} to/from.
|
||||
* @param adapterRegistry the {@link ReactiveAdapterRegistry} to use.
|
||||
*/
|
||||
public void setReactiveAdapterRegistry(ReactiveAdapterRegistry adapterRegistry) {
|
||||
Assert.notNull(adapterRegistry, "'adapterRegistry' must not be null");
|
||||
this.adapterRegistry = adapterRegistry;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getComponentType() {
|
||||
return super.getComponentType().replaceFirst("(http:)", "$1reactive-");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onInit() throws Exception {
|
||||
super.onInit();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> handle(ServerWebExchange exchange) {
|
||||
return Mono.defer(() -> {
|
||||
if (isRunning()) {
|
||||
return doHandle(exchange);
|
||||
}
|
||||
else {
|
||||
return serviceUnavailableResponse(exchange);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private Mono<Void> doHandle(ServerWebExchange exchange) {
|
||||
return extractRequestBody(exchange)
|
||||
.doOnSubscribe(s -> this.activeCount.incrementAndGet())
|
||||
.map(body -> new HttpEntity<>(body, exchange.getRequest().getHeaders()))
|
||||
.map(entity -> buildMessage(entity, exchange))
|
||||
.flatMap(requestMessage -> {
|
||||
if (this.expectReply) {
|
||||
return sendAndReceiveMessageReactive(requestMessage)
|
||||
.flatMap(replyMessage -> populateResponse(exchange, replyMessage));
|
||||
}
|
||||
else {
|
||||
send(requestMessage);
|
||||
return setStatusCode(exchange);
|
||||
}
|
||||
})
|
||||
.doOnTerminate((e, t) -> this.activeCount.decrementAndGet());
|
||||
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T> Mono<T> extractRequestBody(ServerWebExchange exchange) {
|
||||
ServerHttpRequest request = exchange.getRequest();
|
||||
ServerHttpResponse response = exchange.getResponse();
|
||||
|
||||
if (isReadable(request)) {
|
||||
MediaType contentType;
|
||||
if (request.getHeaders().getContentType() == null) {
|
||||
contentType = MediaType.APPLICATION_OCTET_STREAM;
|
||||
}
|
||||
else {
|
||||
contentType = request.getHeaders().getContentType();
|
||||
}
|
||||
|
||||
if (MediaType.APPLICATION_FORM_URLENCODED.isCompatibleWith(contentType)) {
|
||||
return (Mono<T>) exchange.getFormData();
|
||||
}
|
||||
else if (MediaType.MULTIPART_FORM_DATA.isCompatibleWith(contentType)) {
|
||||
return (Mono<T>) exchange.getMultipartData();
|
||||
}
|
||||
else {
|
||||
ResolvableType bodyType = getRequestPayloadType();
|
||||
if (bodyType == null) {
|
||||
bodyType =
|
||||
"text".equals(contentType.getType())
|
||||
? ResolvableType.forClass(String.class)
|
||||
: ResolvableType.forClass(byte[].class);
|
||||
}
|
||||
|
||||
Class<?> resolvedType = bodyType.resolve();
|
||||
|
||||
ReactiveAdapter adapter = (resolvedType != null ? this.adapterRegistry.getAdapter(resolvedType) : null);
|
||||
ResolvableType elementType = (adapter != null ? bodyType.getGeneric() : bodyType);
|
||||
|
||||
HttpMessageReader<?> httpMessageReader = this.codecConfigurer
|
||||
.getReaders()
|
||||
.stream()
|
||||
.filter(reader -> reader.canRead(elementType, contentType))
|
||||
.findFirst()
|
||||
.orElseThrow(() -> new UnsupportedMediaTypeStatusException(
|
||||
"Could not convert request: no suitable HttpMessageReader found for expected type ["
|
||||
+ elementType + "] and content type [" + contentType + "]"));
|
||||
|
||||
|
||||
Map<String, Object> readHints = Collections.emptyMap();
|
||||
if (adapter != null && adapter.isMultiValue()) {
|
||||
Flux<?> flux = httpMessageReader.read(bodyType, elementType, request, response, readHints);
|
||||
|
||||
return (Mono<T>) Mono.just(adapter.fromPublisher(flux));
|
||||
}
|
||||
else {
|
||||
Mono<?> mono = httpMessageReader.readMono(bodyType, elementType, request, response, readHints);
|
||||
|
||||
if (adapter != null) {
|
||||
return (Mono<T>) Mono.just(adapter.fromPublisher(mono));
|
||||
}
|
||||
else {
|
||||
return (Mono<T>) mono;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
return (Mono<T>) Mono.just(exchange.getRequest().getQueryParams());
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private Message<?> buildMessage(HttpEntity<?> httpEntity, ServerWebExchange exchange) {
|
||||
ServerHttpRequest request = exchange.getRequest();
|
||||
HttpHeaders requestHeaders = request.getHeaders();
|
||||
Map<String, Object> exchangeAttributes = exchange.getAttributes();
|
||||
|
||||
StandardEvaluationContext evaluationContext = createEvaluationContext();
|
||||
|
||||
evaluationContext.setVariable("requestAttributes", exchangeAttributes);
|
||||
MultiValueMap<String, String> requestParams = request.getQueryParams();
|
||||
evaluationContext.setVariable("requestParams", requestParams);
|
||||
evaluationContext.setVariable("requestHeaders", requestHeaders);
|
||||
if (!CollectionUtils.isEmpty(request.getCookies())) {
|
||||
evaluationContext.setVariable("cookies", request.getCookies());
|
||||
}
|
||||
|
||||
Map<String, String> pathVariables =
|
||||
(Map<String, String>) exchangeAttributes.get(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);
|
||||
|
||||
if (!CollectionUtils.isEmpty(pathVariables)) {
|
||||
evaluationContext.setVariable("pathVariables", pathVariables);
|
||||
}
|
||||
|
||||
Map<String, MultiValueMap<String, String>> matrixVariables =
|
||||
(Map<String, MultiValueMap<String, String>>) exchangeAttributes.get(HandlerMapping.MATRIX_VARIABLES_ATTRIBUTE);
|
||||
|
||||
if (!CollectionUtils.isEmpty(matrixVariables)) {
|
||||
evaluationContext.setVariable("matrixVariables", matrixVariables);
|
||||
}
|
||||
|
||||
evaluationContext.setRootObject(httpEntity);
|
||||
Object payload;
|
||||
if (getPayloadExpression() != null) {
|
||||
payload = getPayloadExpression().getValue(evaluationContext);
|
||||
if (payload == null) {
|
||||
throw new IllegalStateException("The payload expression '" + getPayloadExpression().getExpressionString()
|
||||
+ "' returned null.");
|
||||
}
|
||||
}
|
||||
else {
|
||||
payload = httpEntity.getBody();
|
||||
}
|
||||
|
||||
Map<String, Object> headers = getHeaderMapper().toHeaders(request.getHeaders());
|
||||
if (!CollectionUtils.isEmpty(getHeaderExpressions())) {
|
||||
for (Map.Entry<String, Expression> entry : getHeaderExpressions().entrySet()) {
|
||||
String headerName = entry.getKey();
|
||||
Expression headerExpression = entry.getValue();
|
||||
Object headerValue = headerExpression.getValue(evaluationContext);
|
||||
if (headerValue != null) {
|
||||
headers.put(headerName, headerValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AbstractIntegrationMessageBuilder<?> messageBuilder;
|
||||
|
||||
if (payload instanceof Message<?>) {
|
||||
messageBuilder =
|
||||
getMessageBuilderFactory()
|
||||
.fromMessage((Message<?>) payload)
|
||||
.copyHeadersIfAbsent(headers);
|
||||
}
|
||||
else {
|
||||
messageBuilder =
|
||||
getMessageBuilderFactory()
|
||||
.withPayload(payload)
|
||||
.copyHeaders(headers);
|
||||
}
|
||||
|
||||
return messageBuilder
|
||||
.setHeader(org.springframework.integration.http.HttpHeaders.REQUEST_URL, request.getURI().toString())
|
||||
.setHeader(org.springframework.integration.http.HttpHeaders.REQUEST_METHOD, request.getMethod().toString())
|
||||
.setHeader(org.springframework.integration.http.HttpHeaders.USER_PRINCIPAL, exchange.getPrincipal().block())
|
||||
.build();
|
||||
}
|
||||
|
||||
private Mono<Void> populateResponse(ServerWebExchange exchange, Message<?> replyMessage) {
|
||||
ServerHttpResponse response = exchange.getResponse();
|
||||
getHeaderMapper().fromHeaders(replyMessage.getHeaders(), response.getHeaders());
|
||||
|
||||
Object responseContent = replyMessage;
|
||||
if (getExtractReplyPayload()) {
|
||||
responseContent = replyMessage.getPayload();
|
||||
}
|
||||
|
||||
if (responseContent instanceof HttpStatus) {
|
||||
response.setStatusCode((HttpStatus) responseContent);
|
||||
return response.setComplete();
|
||||
}
|
||||
else {
|
||||
HttpStatus httpStatus = resolveHttpStatusFromHeaders(replyMessage.getHeaders());
|
||||
if (httpStatus != null) {
|
||||
response.setStatusCode(httpStatus);
|
||||
}
|
||||
|
||||
return writeResponseBody(exchange, responseContent);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private Mono<Void> writeResponseBody(ServerWebExchange exchange, Object body) {
|
||||
ResolvableType bodyType = ResolvableType.forInstance(body);
|
||||
ReactiveAdapter adapter = this.adapterRegistry.getAdapter(bodyType.resolve(), body);
|
||||
|
||||
Publisher<?> publisher;
|
||||
ResolvableType elementType;
|
||||
if (adapter != null) {
|
||||
publisher = adapter.toPublisher(body);
|
||||
ResolvableType genericType = bodyType.getGeneric(0);
|
||||
elementType = getElementType(adapter, genericType);
|
||||
}
|
||||
else {
|
||||
publisher = Mono.justOrEmpty(body);
|
||||
elementType = bodyType;
|
||||
}
|
||||
|
||||
if (void.class == elementType.getRawClass() || Void.class == elementType.getRawClass()) {
|
||||
return Mono.from((Publisher<Void>) publisher);
|
||||
}
|
||||
|
||||
List<MediaType> producibleMediaTypes = getProducibleMediaTypes(bodyType);
|
||||
MediaType bestMediaType = selectMediaType(exchange, () -> producibleMediaTypes);
|
||||
|
||||
if (bestMediaType != null) {
|
||||
for (HttpMessageWriter<?> writer : this.codecConfigurer.getWriters()) {
|
||||
if (writer.canWrite(bodyType, bestMediaType)) {
|
||||
return ((HttpMessageWriter<Object>) writer).write(publisher, elementType,
|
||||
bestMediaType, exchange.getResponse(), Collections.emptyMap());
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (producibleMediaTypes.isEmpty()) {
|
||||
return Mono.error(new IllegalStateException("No HttpMessageWriters for response type: " + bodyType));
|
||||
}
|
||||
}
|
||||
|
||||
return Mono.error(new NotAcceptableStatusException(producibleMediaTypes));
|
||||
}
|
||||
|
||||
private ResolvableType getElementType(ReactiveAdapter adapter, ResolvableType genericType) {
|
||||
if (adapter.isNoValue()) {
|
||||
return ResolvableType.forClass(Void.class);
|
||||
}
|
||||
else if (genericType != ResolvableType.NONE) {
|
||||
return genericType;
|
||||
}
|
||||
else {
|
||||
return ResolvableType.forClass(Object.class);
|
||||
}
|
||||
}
|
||||
|
||||
private List<MediaType> getProducibleMediaTypes(ResolvableType elementType) {
|
||||
return this.codecConfigurer.getWriters()
|
||||
.stream()
|
||||
.filter(converter -> converter.canWrite(elementType, null))
|
||||
.flatMap(converter -> converter.getWritableMediaTypes().stream())
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private MediaType selectMediaType(ServerWebExchange exchange, Supplier<List<MediaType>> producibleTypesSupplier) {
|
||||
List<MediaType> acceptableTypes = getAcceptableTypes(exchange);
|
||||
List<MediaType> producibleTypes = getProducibleTypes(exchange, producibleTypesSupplier);
|
||||
|
||||
Set<MediaType> compatibleMediaTypes = new LinkedHashSet<>();
|
||||
for (MediaType acceptable : acceptableTypes) {
|
||||
for (MediaType producible : producibleTypes) {
|
||||
if (acceptable.isCompatibleWith(producible)) {
|
||||
compatibleMediaTypes.add(selectMoreSpecificMediaType(acceptable, producible));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
List<MediaType> result = new ArrayList<>(compatibleMediaTypes);
|
||||
MediaType.sortBySpecificityAndQuality(result);
|
||||
|
||||
for (MediaType mediaType : result) {
|
||||
if (mediaType.isConcrete()) {
|
||||
return mediaType;
|
||||
}
|
||||
else if (mediaType.equals(MediaType.ALL) || mediaType.equals(MEDIA_TYPE_APPLICATION_ALL)) {
|
||||
return MediaType.APPLICATION_OCTET_STREAM;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private List<MediaType> getAcceptableTypes(ServerWebExchange exchange) {
|
||||
List<MediaType> mediaTypes = this.requestedContentTypeResolver.resolveMediaTypes(exchange);
|
||||
return (mediaTypes.isEmpty() ? Collections.singletonList(MediaType.ALL) : mediaTypes);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private List<MediaType> getProducibleTypes(ServerWebExchange exchange,
|
||||
Supplier<List<MediaType>> producibleTypesSupplier) {
|
||||
|
||||
Set<MediaType> mediaTypes = exchange.getAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
|
||||
return (mediaTypes != null ? new ArrayList<>(mediaTypes) : producibleTypesSupplier.get());
|
||||
}
|
||||
|
||||
private MediaType selectMoreSpecificMediaType(MediaType acceptable, MediaType producible) {
|
||||
producible = producible.copyQualityValue(acceptable);
|
||||
Comparator<MediaType> comparator = MediaType.SPECIFICITY_COMPARATOR;
|
||||
return (comparator.compare(acceptable, producible) <= 0 ? acceptable : producible);
|
||||
}
|
||||
|
||||
|
||||
private Mono<Void> setStatusCode(ServerWebExchange exchange) {
|
||||
ServerHttpResponse response = exchange.getResponse();
|
||||
if (getStatusCodeExpression() != null) {
|
||||
HttpStatus httpStatus = evaluateHttpStatus();
|
||||
if (httpStatus != null) {
|
||||
response.setStatusCode(httpStatus);
|
||||
}
|
||||
}
|
||||
|
||||
return response.setComplete();
|
||||
}
|
||||
|
||||
private Mono<Void> serviceUnavailableResponse(ServerWebExchange exchange) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Endpoint is stopped; returning status " + HttpStatus.SERVICE_UNAVAILABLE);
|
||||
}
|
||||
ServerHttpResponse response = exchange.getResponse();
|
||||
response.setStatusCode(HttpStatus.SERVICE_UNAVAILABLE);
|
||||
return response.writeWith(
|
||||
Mono.just(response.bufferFactory()
|
||||
.wrap("Endpoint is stopped".getBytes())));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,170 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.integration.http.inbound;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import org.springframework.context.ApplicationListener;
|
||||
import org.springframework.context.event.ContextRefreshedEvent;
|
||||
import org.springframework.integration.http.support.HttpContextUtils;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.cors.CorsConfiguration;
|
||||
import org.springframework.web.reactive.result.condition.NameValueExpression;
|
||||
import org.springframework.web.reactive.result.method.RequestMappingInfo;
|
||||
import org.springframework.web.reactive.result.method.annotation.RequestMappingHandlerMapping;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import org.springframework.web.server.WebHandler;
|
||||
|
||||
/**
|
||||
* The {@link org.springframework.web.servlet.HandlerMapping} implementation that
|
||||
* detects and registers {@link org.springframework.web.servlet.mvc.method.RequestMappingInfo}s for
|
||||
* {@link HttpRequestHandlingEndpointSupport} from a Spring Integration HTTP configuration
|
||||
* of {@code <inbound-channel-adapter/>} and {@code <inbound-gateway/>} elements.
|
||||
* <p>
|
||||
* This class is automatically configured as a bean in the application context during the
|
||||
* parsing phase of the {@code <reactive-inbound-gateway/>}
|
||||
* elements, if there is none registered, yet. However it can be configured as a regular
|
||||
* bean with appropriate configuration for
|
||||
* {@link org.springframework.web.reactive.result.method.annotation.RequestMappingHandlerMapping}.
|
||||
* It is recommended to have only one similar bean in the application context using the 'id'
|
||||
* {@link org.springframework.integration.http.support.HttpContextUtils#REACTIVE_HANDLER_MAPPING_BEAN_NAME}.
|
||||
* <p>
|
||||
* In most cases, Spring MVC offers to configure Request Mapping via
|
||||
* {@code org.springframework.stereotype.Controller} and
|
||||
* {@link org.springframework.web.bind.annotation.RequestMapping}.
|
||||
* That's why Spring MVC's Handler Mapping infrastructure relies on
|
||||
* {@link org.springframework.web.method.HandlerMethod}, as different methods at the same
|
||||
* {@code org.springframework.stereotype.Controller} user-class may have their own
|
||||
* {@link org.springframework.web.bind.annotation.RequestMapping}.
|
||||
* On the other side, all Spring Integration HTTP Inbound Endpoints are configured on
|
||||
* the basis of the same {@link HttpRequestHandlingEndpointSupport} class and there is no
|
||||
* single {@link org.springframework.web.reactive.result.method.RequestMappingInfo} configuration without
|
||||
* {@link org.springframework.web.method.HandlerMethod} in Spring MVC.
|
||||
* Accordingly {@link ReactiveIntegrationRequestMappingHandlerMapping} is a
|
||||
* {@link org.springframework.web.reactive.HandlerMapping}
|
||||
* compromise implementation between method-level annotations and component-level
|
||||
* (e.g. Spring Integration XML) configurations.
|
||||
*
|
||||
* @author Artem Bilan
|
||||
*
|
||||
* @since 5.0
|
||||
*
|
||||
* @see RequestMapping
|
||||
* @see RequestMappingHandlerMapping
|
||||
*/
|
||||
public class ReactiveIntegrationRequestMappingHandlerMapping extends RequestMappingHandlerMapping
|
||||
implements ApplicationListener<ContextRefreshedEvent> {
|
||||
|
||||
private static final Method HANDLER_METHOD = ReflectionUtils.findMethod(WebHandler.class,
|
||||
"handle", ServerWebExchange.class);
|
||||
|
||||
private final AtomicBoolean initialized = new AtomicBoolean();
|
||||
|
||||
@Override
|
||||
protected boolean isHandler(Class<?> beanType) {
|
||||
return ReactiveHttpInboundEndpoint.class.isAssignableFrom(beanType);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void detectHandlerMethods(Object handler) {
|
||||
if (handler instanceof String) {
|
||||
handler = getApplicationContext().getBean((String) handler);
|
||||
}
|
||||
RequestMappingInfo mapping = getMappingForEndpoint((ReactiveHttpInboundEndpoint) handler);
|
||||
if (mapping != null) {
|
||||
registerMapping(mapping, handler, HANDLER_METHOD);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a {@link RequestMappingInfo} from
|
||||
* a Spring Integration Reactive HTTP Inbound Endpoint {@link RequestMapping}.
|
||||
* @see RequestMappingHandlerMapping#getMappingForMethod
|
||||
*/
|
||||
private RequestMappingInfo getMappingForEndpoint(ReactiveHttpInboundEndpoint endpoint) {
|
||||
org.springframework.web.bind.annotation.RequestMapping requestMappingAnnotation =
|
||||
HttpContextUtils.convertRequestMappingToAnnotation(endpoint.getRequestMapping());
|
||||
if (requestMappingAnnotation != null) {
|
||||
return createRequestMappingInfo(requestMappingAnnotation, getCustomTypeCondition(endpoint.getClass()));
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected CorsConfiguration initCorsConfiguration(Object handler, Method method, RequestMappingInfo mappingInfo) {
|
||||
CrossOrigin crossOrigin = ((BaseHttpInboundEndpoint) handler).getCrossOrigin();
|
||||
if (crossOrigin != null) {
|
||||
CorsConfiguration config = new CorsConfiguration();
|
||||
for (String origin : crossOrigin.getOrigin()) {
|
||||
config.addAllowedOrigin(origin);
|
||||
}
|
||||
for (RequestMethod requestMethod : crossOrigin.getMethod()) {
|
||||
config.addAllowedMethod(requestMethod.name());
|
||||
}
|
||||
for (String header : crossOrigin.getAllowedHeaders()) {
|
||||
config.addAllowedHeader(header);
|
||||
}
|
||||
for (String header : crossOrigin.getExposedHeaders()) {
|
||||
config.addExposedHeader(header);
|
||||
}
|
||||
if (crossOrigin.getAllowCredentials() != null) {
|
||||
config.setAllowCredentials(crossOrigin.getAllowCredentials());
|
||||
}
|
||||
if (crossOrigin.getMaxAge() != -1) {
|
||||
config.setMaxAge(crossOrigin.getMaxAge());
|
||||
}
|
||||
if (CollectionUtils.isEmpty(config.getAllowedMethods())) {
|
||||
for (RequestMethod allowedMethod : mappingInfo.getMethodsCondition().getMethods()) {
|
||||
config.addAllowedMethod(allowedMethod.name());
|
||||
}
|
||||
}
|
||||
if (CollectionUtils.isEmpty(config.getAllowedHeaders())) {
|
||||
for (NameValueExpression<String> headerExpression : mappingInfo.getHeadersCondition().getExpressions()) {
|
||||
if (!headerExpression.isNegated()) {
|
||||
config.addAllowedHeader(headerExpression.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
return config.applyPermitDefaultValues();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() {
|
||||
// No-op in favor of onApplicationEvent
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link HttpRequestHandlingEndpointSupport}s may depend on auto-created
|
||||
* {@code requestChannel}s, so MVC Handlers detection should be postponed
|
||||
* as late as possible.
|
||||
* @see org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping#afterPropertiesSet()
|
||||
*/
|
||||
@Override
|
||||
public void onApplicationEvent(ContextRefreshedEvent event) {
|
||||
if (!this.initialized.getAndSet(true)) {
|
||||
super.afterPropertiesSet();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,189 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.integration.http.outbound;
|
||||
|
||||
import java.net.URI;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.core.ParameterizedTypeReference;
|
||||
import org.springframework.core.io.buffer.DataBuffer;
|
||||
import org.springframework.core.io.buffer.DataBufferUtils;
|
||||
import org.springframework.expression.Expression;
|
||||
import org.springframework.expression.common.LiteralExpression;
|
||||
import org.springframework.http.HttpEntity;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ReactiveHttpInputMessage;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.integration.expression.ValueExpression;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.MessageHandler;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.MimeType;
|
||||
import org.springframework.web.reactive.function.BodyExtractor;
|
||||
import org.springframework.web.reactive.function.BodyExtractors;
|
||||
import org.springframework.web.reactive.function.BodyInserters;
|
||||
import org.springframework.web.reactive.function.client.ClientResponse;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
import org.springframework.web.reactive.function.client.WebClientResponseException;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
/**
|
||||
* A {@link MessageHandler} implementation that executes HTTP requests by delegating
|
||||
* to a Reactive {@link WebClient} instance.
|
||||
*
|
||||
* @author Shiliang Li
|
||||
* @author Artem Bilan
|
||||
*
|
||||
* @since 5.0
|
||||
*
|
||||
* @see HttpRequestExecutingMessageHandler
|
||||
*/
|
||||
public class ReactiveHttpRequestExecutingMessageHandler extends AbstractHttpRequestExecutingMessageHandler {
|
||||
|
||||
private final WebClient webClient;
|
||||
|
||||
/**
|
||||
* Create a handler that will send requests to the provided URI.
|
||||
* @param uri The URI.
|
||||
*/
|
||||
public ReactiveHttpRequestExecutingMessageHandler(URI uri) {
|
||||
this(new ValueExpression<>(uri));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a handler that will send requests to the provided URI.
|
||||
* @param uri The URI.
|
||||
*/
|
||||
public ReactiveHttpRequestExecutingMessageHandler(String uri) {
|
||||
this(uri, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a handler that will send requests to the provided URI Expression.
|
||||
* @param uriExpression The URI expression.
|
||||
*/
|
||||
public ReactiveHttpRequestExecutingMessageHandler(Expression uriExpression) {
|
||||
this(uriExpression, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a handler that will send requests to the provided URI using a provided WebClient.
|
||||
* @param uri The URI.
|
||||
* @param webClient The WebClient to use.
|
||||
*/
|
||||
public ReactiveHttpRequestExecutingMessageHandler(String uri, WebClient webClient) {
|
||||
this(new LiteralExpression(uri), webClient);
|
||||
/*
|
||||
* We'd prefer to do this assertion first, but the compiler doesn't allow it. However,
|
||||
* it's safe because the literal expression simply wraps the String variable, even
|
||||
* when null.
|
||||
*/
|
||||
Assert.hasText(uri, "URI is required");
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a handler that will send requests to the provided URI using a provided WebClient.
|
||||
* @param uriExpression A SpEL Expression that can be resolved against the message object and
|
||||
* {@link BeanFactory}.
|
||||
* @param webClient The WebClient to use.
|
||||
*/
|
||||
public ReactiveHttpRequestExecutingMessageHandler(Expression uriExpression, WebClient webClient) {
|
||||
super(uriExpression);
|
||||
this.webClient = (webClient == null ? WebClient.create() : webClient);
|
||||
this.setAsync(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getComponentType() {
|
||||
return (isExpectReply() ? "http:outbound-reactive-gateway" : "http:outbound-reactive-channel-adapter");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object exchange(Supplier<URI> uriSupplier, HttpMethod httpMethod, HttpEntity<?> httpRequest,
|
||||
Object expectedResponseType, Message<?> requestMessage) {
|
||||
|
||||
WebClient.RequestBodySpec requestSpec =
|
||||
this.webClient.method(httpMethod)
|
||||
.uri(b -> uriSupplier.get())
|
||||
.headers(headers -> headers.putAll(httpRequest.getHeaders()));
|
||||
|
||||
if (httpRequest.hasBody()) {
|
||||
requestSpec.body(BodyInserters.fromObject(httpRequest.getBody()));
|
||||
}
|
||||
|
||||
Mono<ClientResponse> responseMono = requestSpec.exchange()
|
||||
.doOnNext(response -> {
|
||||
HttpStatus httpStatus = response.statusCode();
|
||||
if (httpStatus.is4xxClientError() || httpStatus.is5xxServerError()) {
|
||||
throw new WebClientResponseException(
|
||||
String.format("ClientResponse has erroneous status code: %d %s",
|
||||
response.statusCode().value(),
|
||||
response.statusCode().getReasonPhrase()),
|
||||
httpStatus.value(),
|
||||
httpStatus.getReasonPhrase(),
|
||||
response.headers()
|
||||
.asHttpHeaders(),
|
||||
response.body(BodyExtractors.toDataBuffers())
|
||||
.reduce(DataBuffer::write)
|
||||
.map(dataBuffer -> {
|
||||
byte[] bytes = new byte[dataBuffer.readableByteCount()];
|
||||
dataBuffer.read(bytes);
|
||||
DataBufferUtils.release(dataBuffer);
|
||||
return bytes;
|
||||
})
|
||||
.block(),
|
||||
response.headers()
|
||||
.contentType()
|
||||
.map(MimeType::getCharset)
|
||||
.orElse(StandardCharsets.ISO_8859_1));
|
||||
}
|
||||
});
|
||||
|
||||
if (isExpectReply()) {
|
||||
BodyExtractor<? extends Mono<?>, ReactiveHttpInputMessage> bodyExtractor;
|
||||
|
||||
if (expectedResponseType instanceof ParameterizedTypeReference<?>) {
|
||||
bodyExtractor = BodyExtractors.toMono((ParameterizedTypeReference<?>) expectedResponseType);
|
||||
}
|
||||
else if (expectedResponseType != null) {
|
||||
bodyExtractor = BodyExtractors.toMono((Class<?>) expectedResponseType);
|
||||
}
|
||||
else {
|
||||
bodyExtractor = null;
|
||||
}
|
||||
|
||||
return responseMono
|
||||
.map(response ->
|
||||
new ResponseEntity<>(bodyExtractor != null
|
||||
? response.body(bodyExtractor).block()
|
||||
: null,
|
||||
response.headers().asHttpHeaders(),
|
||||
response.statusCode()))
|
||||
.map(this::getReply);
|
||||
}
|
||||
else {
|
||||
responseMono.subscribe(v -> { }, ex -> sendErrorMessage(requestMessage, ex));
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -48,27 +48,12 @@ public final class HttpContextUtils {
|
||||
ClassUtils.isPresent("org.springframework.web.servlet.DispatcherServlet",
|
||||
HttpContextUtils.class.getClassLoader());
|
||||
|
||||
/**
|
||||
* The {@code boolean} flag to indicate if the
|
||||
* {@code org.springframework.web.reactive.result.method.RequestMappingInfo}
|
||||
* is present in the CLASSPATH to allow to register the Integration server reactive components.
|
||||
*/
|
||||
public static final boolean WEB_FLUX_PRESENT =
|
||||
ClassUtils.isPresent("org.springframework.web.reactive.result.method.RequestMappingInfo",
|
||||
HttpContextUtils.class.getClassLoader());
|
||||
|
||||
/**
|
||||
* The name for the infrastructure
|
||||
* {@link org.springframework.integration.http.inbound.IntegrationRequestMappingHandlerMapping} bean.
|
||||
*/
|
||||
public static final String HANDLER_MAPPING_BEAN_NAME = "integrationRequestMappingHandlerMapping";
|
||||
|
||||
/**
|
||||
* The name for the infrastructure
|
||||
* {@link org.springframework.integration.http.inbound.ReactiveIntegrationRequestMappingHandlerMapping} bean.
|
||||
*/
|
||||
public static final String REACTIVE_HANDLER_MAPPING_BEAN_NAME = "reactiveIntegrationRequestMappingHandlerMapping";
|
||||
|
||||
/**
|
||||
* Represents the environment property for the {@code IntegrationGraphController} request mapping path.
|
||||
*/
|
||||
|
||||
@@ -401,55 +401,6 @@
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
|
||||
<xsd:element name="outbound-reactive-channel-adapter">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
Configures a Consumer Endpoint for the
|
||||
'org.springframework.integration.http.outbound.ReactiveHttpRequestExecutingMessageHandler'
|
||||
with 'expectReply = false' that sends HTTP requests based on incoming messages.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
<xsd:complexType>
|
||||
<xsd:choice minOccurs="0" maxOccurs="3">
|
||||
<xsd:element name="uri-variable" type="uriVariableType" minOccurs="0" maxOccurs="unbounded">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
Specify an expression for URI variable placeholder within 'url'.
|
||||
This element is mutually exclusive with 'uri-variables-expression' attribute.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:element>
|
||||
<xsd:element name="request-handler-advice-chain" type="integration:handlerAdviceChainType" minOccurs="0" maxOccurs="1" />
|
||||
<xsd:element ref="integration:poller" minOccurs="0" maxOccurs="1"/>
|
||||
</xsd:choice>
|
||||
<xsd:attributeGroup ref="integration:channelAdapterAttributes"/>
|
||||
<xsd:attributeGroup ref="httpOutboundCommonAttributes"/>
|
||||
<xsd:attribute name="web-client" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:appinfo>
|
||||
<tool:annotation kind="ref">
|
||||
<tool:expected-type type="org.springframework.web.reactive.function.client.WebClient" />
|
||||
</tool:annotation>
|
||||
</xsd:appinfo>
|
||||
<xsd:documentation>
|
||||
A reference to an org.springframework.web.reactive.function.client.WebClient bean
|
||||
which is used to send to send the HTTP Requests reactive manner.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="extract-payload" type="xsd:string" default="true">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
Specify whether the outbound message's payload should be extracted
|
||||
when preparing the request body. Otherwise the Message instance itself
|
||||
will be serialized.
|
||||
The default value is 'true'.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
|
||||
<xsd:element name="outbound-gateway">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
@@ -544,114 +495,6 @@
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
|
||||
<xsd:element name="outbound-reactive-gateway">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
Configures a Consumer Endpoint for the
|
||||
'org.springframework.integration.http.outbound.ReactiveHttpRequestExecutingMessageHandler'
|
||||
that sends HTTP requests based on incoming messages and expects HTTP responses.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
<xsd:complexType>
|
||||
<xsd:complexContent>
|
||||
<xsd:extension base="gatewayType">
|
||||
<xsd:choice minOccurs="0" maxOccurs="3">
|
||||
<xsd:element name="uri-variable" type="uriVariableType" minOccurs="0" maxOccurs="unbounded">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
Specify an expression for URI variable placeholder within 'url'.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:element>
|
||||
<xsd:element name="transactional" type="integration:transactionalType" minOccurs="0"
|
||||
maxOccurs="1" />
|
||||
<xsd:element name="request-handler-advice-chain" type="integration:handlerAdviceChainType"
|
||||
minOccurs="0" maxOccurs="1" />
|
||||
<xsd:element ref="integration:poller" minOccurs="0" maxOccurs="1"/>
|
||||
</xsd:choice>
|
||||
<xsd:attribute name="request-channel" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:appinfo>
|
||||
<tool:annotation kind="ref">
|
||||
<tool:expected-type type="org.springframework.messaging.MessageChannel" />
|
||||
</tool:annotation>
|
||||
</xsd:appinfo>
|
||||
<xsd:documentation>
|
||||
The receiving Message Channel of this endpoint.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="mapped-response-headers" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation><![CDATA[
|
||||
Comma-separated list of names of HttpHeaders to be mapped from the HTTP response into the MessageHeaders.
|
||||
This can only be provided if the 'header-mapper' reference is not being set directly. The values in
|
||||
this list can also be simple patterns to be matched against the header names (e.g. "foo*" or "*foo").
|
||||
The String "HTTP_RESPONSE_HEADERS" will match against any of the standard HTTP Response headers.
|
||||
]]></xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="extract-request-payload" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
Specifies whether the outbound message's payload should be extracted
|
||||
when preparing the request body. Otherwise the Message instance itself
|
||||
will be serialized.
|
||||
The default value is 'true'.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="transfer-cookies" type="xsd:string" default="false">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation><![CDATA[
|
||||
When set to "true", if a response contains a 'Set-Cookie' header, it will be mapped to a 'Cookie' header. This enables simple
|
||||
cookie handling where subsequent HTTP interactions in the same message flow can use a cookie
|
||||
supplied by the server. Default is "false".
|
||||
]]></xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="reply-timeout" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation><![CDATA[
|
||||
Allows you to specify how long this gateway will wait for
|
||||
the reply message to be sent successfully to the reply channel
|
||||
before throwing an exception. This attribute only applies when the
|
||||
channel might block, for example when using a bounded queue channel that
|
||||
is currently full.
|
||||
|
||||
Also, keep in mind that when sending to a DirectChannel, the
|
||||
invocation will occur in the sender's thread. Therefore,
|
||||
the failing of the send operation may be caused by other
|
||||
components further downstream.
|
||||
|
||||
The "reply-timeout" attribute maps to the "sendTimeout" property of the
|
||||
underlying 'MessagingTemplate' instance (org.springframework.integration.core.MessagingTemplate).
|
||||
|
||||
The attribute will default, if not specified, to '-1', meaning that
|
||||
by default, the Gateway will wait indefinitely. The value is
|
||||
specified in milliseconds.
|
||||
]]></xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attributeGroup ref="httpOutboundCommonAttributes"/>
|
||||
<xsd:attribute name="web-client" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:appinfo>
|
||||
<tool:annotation kind="ref">
|
||||
<tool:expected-type type="org.springframework.web.reactive.function.client.WebClient" />
|
||||
</tool:annotation>
|
||||
</xsd:appinfo>
|
||||
<xsd:documentation>
|
||||
A reference to an org.springframework.web.reactive.function.client.WebClient bean
|
||||
which is used to send to send the HTTP Requests reactive manner.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
</xsd:extension>
|
||||
</xsd:complexContent>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
|
||||
<xsd:element name="graph-controller">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
|
||||
Reference in New Issue
Block a user