INT-4436: Register HTTP endpoints at runtime
JIRA: https://jira.spring.io/browse/INT-4436 * Fix `IntegrationRequestMappingHandlerMapping` and `IntegrationRequestMappingHandlerMapping` to implement a `DestructionAwareBeanPostProcessor` to allow to register and destroy HTTP endpoints at runtime, e.g. via `IntegrationFlowContext` with the dynamic `IntegrationFlow`s
This commit is contained in:
committed by
Gary Russell
parent
07c32ae93b
commit
ee501c801f
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013-2017 the original author or authors.
|
||||
* Copyright 2013-2018 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.
|
||||
@@ -24,6 +24,8 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.config.DestructionAwareBeanPostProcessor;
|
||||
import org.springframework.context.ApplicationListener;
|
||||
import org.springframework.context.event.ContextRefreshedEvent;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
@@ -60,13 +62,18 @@ import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandl
|
||||
* {@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
|
||||
* the basis of the same {@link BaseHttpInboundEndpoint} class and there is no
|
||||
* single {@link RequestMappingInfo} configuration without
|
||||
* {@link org.springframework.web.method.HandlerMethod} in Spring MVC.
|
||||
* Accordingly {@link IntegrationRequestMappingHandlerMapping} is a
|
||||
* {@link org.springframework.web.servlet.HandlerMapping}
|
||||
* compromise implementation between method-level annotations and component-level
|
||||
* (e.g. Spring Integration XML) configurations.
|
||||
* <p>
|
||||
* Starting with version 5.1, this class implements {@link DestructionAwareBeanPostProcessor} to
|
||||
* register HTTP endpoints at runtime for dynamically declared beans, e.g. via
|
||||
* {@link org.springframework.integration.dsl.context.IntegrationFlowContext}, and unregister
|
||||
* them during the {@link BaseHttpInboundEndpoint} destruction.
|
||||
*
|
||||
* @author Artem Bilan
|
||||
*
|
||||
@@ -76,13 +83,35 @@ import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandl
|
||||
* @see RequestMappingHandlerMapping
|
||||
*/
|
||||
public final class IntegrationRequestMappingHandlerMapping extends RequestMappingHandlerMapping
|
||||
implements ApplicationListener<ContextRefreshedEvent> {
|
||||
implements ApplicationListener<ContextRefreshedEvent>, DestructionAwareBeanPostProcessor {
|
||||
|
||||
private static final Method HANDLE_REQUEST_METHOD = ReflectionUtils.findMethod(HttpRequestHandler.class,
|
||||
"handleRequest", HttpServletRequest.class, HttpServletResponse.class);
|
||||
|
||||
private final AtomicBoolean initialized = new AtomicBoolean();
|
||||
|
||||
@Override
|
||||
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
|
||||
if (this.initialized.get() && isHandler(bean.getClass())) {
|
||||
detectHandlerMethods(bean);
|
||||
}
|
||||
|
||||
return bean;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public void postProcessBeforeDestruction(Object bean, String beanName) throws BeansException {
|
||||
if (isHandler(bean.getClass())) {
|
||||
unregisterMapping(getMappingForEndpoint((BaseHttpInboundEndpoint) bean));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean requiresDestruction(Object bean) {
|
||||
return isHandler(bean.getClass());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isHandler(Class<?> beanType) {
|
||||
return HttpRequestHandlingEndpointSupport.class.isAssignableFrom(beanType);
|
||||
@@ -97,6 +126,7 @@ public final class IntegrationRequestMappingHandlerMapping extends RequestMappin
|
||||
handler = bean;
|
||||
}
|
||||
}
|
||||
|
||||
return super.getHandlerExecutionChain(handler, request);
|
||||
}
|
||||
|
||||
|
||||
@@ -38,6 +38,7 @@ import org.springframework.integration.channel.DirectChannel;
|
||||
import org.springframework.integration.config.EnableIntegration;
|
||||
import org.springframework.integration.dsl.IntegrationFlow;
|
||||
import org.springframework.integration.dsl.IntegrationFlows;
|
||||
import org.springframework.integration.dsl.context.IntegrationFlowContext;
|
||||
import org.springframework.integration.http.outbound.HttpRequestExecutingMessageHandler;
|
||||
import org.springframework.integration.security.channel.ChannelSecurityInterceptor;
|
||||
import org.springframework.integration.security.channel.SecuredChannel;
|
||||
@@ -80,6 +81,9 @@ public class HttpDslTests {
|
||||
@Autowired
|
||||
private HttpRequestExecutingMessageHandler serviceInternalGatewayHandler;
|
||||
|
||||
@Autowired
|
||||
private IntegrationFlowContext integrationFlowContext;
|
||||
|
||||
private MockMvc mockMvc;
|
||||
|
||||
@Before
|
||||
@@ -114,6 +118,37 @@ public class HttpDslTests {
|
||||
.isForbidden());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDynamicHttpEndpoint() throws Exception {
|
||||
IntegrationFlow flow =
|
||||
IntegrationFlows.from(Http.inboundGateway("/dynamic")
|
||||
.requestMapping(r -> r.params("name"))
|
||||
.payloadExpression("#requestParams.name[0]"))
|
||||
.<String, String>transform(String::toLowerCase)
|
||||
.get();
|
||||
|
||||
IntegrationFlowContext.IntegrationFlowRegistration flowRegistration =
|
||||
this.integrationFlowContext.registration(flow).register();
|
||||
|
||||
this.mockMvc.perform(
|
||||
get("/dynamic")
|
||||
.with(httpBasic("admin", "admin"))
|
||||
.param("name", "BAR"))
|
||||
.andExpect(
|
||||
content()
|
||||
.string("bar"));
|
||||
|
||||
flowRegistration.destroy();
|
||||
|
||||
this.mockMvc.perform(
|
||||
get("/dynamic")
|
||||
.with(httpBasic("admin", "admin"))
|
||||
.param("name", "BAZ"))
|
||||
.andExpect(
|
||||
status()
|
||||
.isNotFound());
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
@EnableIntegration
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2017 the original author or authors.
|
||||
* Copyright 2017-2018 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.
|
||||
@@ -19,6 +19,8 @@ package org.springframework.integration.webflux.inbound;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.config.DestructionAwareBeanPostProcessor;
|
||||
import org.springframework.context.ApplicationListener;
|
||||
import org.springframework.context.event.ContextRefreshedEvent;
|
||||
import org.springframework.integration.http.config.HttpContextUtils;
|
||||
@@ -66,6 +68,11 @@ import org.springframework.web.server.WebHandler;
|
||||
* {@link org.springframework.web.reactive.HandlerMapping}
|
||||
* compromise implementation between method-level annotations and component-level
|
||||
* (e.g. Spring Integration XML) configurations.
|
||||
* <p>
|
||||
* Starting with version 5.1, this class implements {@link DestructionAwareBeanPostProcessor} to
|
||||
* register HTTP endpoints at runtime for dynamically declared beans, e.g. via
|
||||
* {@link org.springframework.integration.dsl.context.IntegrationFlowContext}, and unregister
|
||||
* them during the {@link WebFluxInboundEndpoint} destruction.
|
||||
*
|
||||
* @author Artem Bilan
|
||||
*
|
||||
@@ -75,13 +82,35 @@ import org.springframework.web.server.WebHandler;
|
||||
* @see RequestMappingHandlerMapping
|
||||
*/
|
||||
public class WebFluxIntegrationRequestMappingHandlerMapping extends RequestMappingHandlerMapping
|
||||
implements ApplicationListener<ContextRefreshedEvent> {
|
||||
implements ApplicationListener<ContextRefreshedEvent>, DestructionAwareBeanPostProcessor {
|
||||
|
||||
private static final Method HANDLER_METHOD = ReflectionUtils.findMethod(WebHandler.class,
|
||||
"handle", ServerWebExchange.class);
|
||||
|
||||
private final AtomicBoolean initialized = new AtomicBoolean();
|
||||
|
||||
@Override
|
||||
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
|
||||
if (this.initialized.get() && isHandler(bean.getClass())) {
|
||||
detectHandlerMethods(bean);
|
||||
}
|
||||
|
||||
return bean;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public void postProcessBeforeDestruction(Object bean, String beanName) throws BeansException {
|
||||
if (isHandler(bean.getClass())) {
|
||||
unregisterMapping(getMappingForEndpoint((WebFluxInboundEndpoint) bean));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean requiresDestruction(Object bean) {
|
||||
return isHandler(bean.getClass());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isHandler(Class<?> beanType) {
|
||||
return WebFluxInboundEndpoint.class.isAssignableFrom(beanType);
|
||||
|
||||
@@ -51,6 +51,7 @@ import org.springframework.integration.channel.QueueChannel;
|
||||
import org.springframework.integration.config.EnableIntegration;
|
||||
import org.springframework.integration.dsl.IntegrationFlow;
|
||||
import org.springframework.integration.dsl.IntegrationFlows;
|
||||
import org.springframework.integration.dsl.context.IntegrationFlowContext;
|
||||
import org.springframework.integration.http.HttpHeaders;
|
||||
import org.springframework.integration.http.dsl.Http;
|
||||
import org.springframework.integration.support.MessageBuilder;
|
||||
@@ -109,6 +110,9 @@ public class WebFluxDslTests {
|
||||
@Autowired
|
||||
private WebApplicationContext wac;
|
||||
|
||||
@Autowired
|
||||
private IntegrationFlowContext integrationFlowContext;
|
||||
|
||||
@Autowired
|
||||
@Qualifier("webFluxWithReplyPayloadToFlux.handler")
|
||||
private WebFluxRequestExecutingMessageHandler webFluxWithReplyPayloadToFlux;
|
||||
@@ -246,6 +250,33 @@ public class WebFluxDslTests {
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDynamicHttpEndpoint() throws Exception {
|
||||
IntegrationFlow flow =
|
||||
IntegrationFlows.from(WebFlux.inboundGateway("/dynamic")
|
||||
.requestMapping(r -> r.params("name"))
|
||||
.payloadExpression("#requestParams.name[0]"))
|
||||
.<String, String>transform(String::toLowerCase)
|
||||
.get();
|
||||
|
||||
IntegrationFlowContext.IntegrationFlowRegistration flowRegistration =
|
||||
this.integrationFlowContext.registration(flow).register();
|
||||
|
||||
this.webTestClient.get().uri("/dynamic?name=BAR")
|
||||
.attributes(basicAuthenticationCredentials("guest", "guest"))
|
||||
.exchange()
|
||||
.expectBody(String.class)
|
||||
.isEqualTo("bar");
|
||||
|
||||
flowRegistration.destroy();
|
||||
|
||||
this.webTestClient.get().uri("/dynamic?name=BAZ")
|
||||
.attributes(basicAuthenticationCredentials("guest", "guest"))
|
||||
.exchange()
|
||||
.expectStatus()
|
||||
.isNotFound();
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableWebFlux
|
||||
@EnableWebSecurity
|
||||
|
||||
Reference in New Issue
Block a user