INT-3933: HTTP & WS Inbound Lifecycle Handling

JIRA: https://jira.spring.io/browse/INT-3933

Previously the HTTP and WS Inbound Endpoint haven't handled the `stopped` state properly.

* Add `lifecycle` parsing to the `HttpInboundEndpointParser`
* Handle `!isRunning()` state and throw an appropriate `503 Service Unavailable` HTTP response
* Expose `lifecycle` options for the `<int-ws:inbound-gateway>`
* Introduce `ServiceUnavailableException` `WebServiceException` to indicate `stopped` state for the WS Inbound Endpoint
* Fix tests for the new logic

INT-3933: PR comments: `OrderlyShutdownCapable` for `AbstractWebServiceInboundGateway`
This commit is contained in:
Artem Bilan
2016-01-18 16:09:11 -05:00
committed by Gary Russell
parent c97afe92fe
commit c0784bf6ef
18 changed files with 216 additions and 72 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2015 the original author or authors.
* Copyright 2002-2016 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.
@@ -202,6 +202,9 @@ public class HttpInboundEndpointParser extends AbstractSingleBeanDefinitionParse
if (statusCodeExpressionDef != null) {
builder.addPropertyValue("statusCodeExpression", statusCodeExpressionDef);
}
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, IntegrationNamespaceUtils.AUTO_STARTUP);
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, IntegrationNamespaceUtils.PHASE);
}
private String getInputChannelAttributeName() {

View File

@@ -148,8 +148,6 @@ public abstract class HttpRequestHandlingEndpointSupport extends MessagingGatewa
private volatile Map<String, Expression> headerExpressions;
private volatile boolean shuttingDown;
private volatile Expression statusCodeExpression;
private volatile EvaluationContext evaluationContext;
@@ -397,11 +395,11 @@ public abstract class HttpRequestHandlingEndpointSupport extends MessagingGatewa
*/
protected final Message<?> doHandleRequest(HttpServletRequest servletRequest, HttpServletResponse servletResponse)
throws IOException {
if (this.isShuttingDown()) {
return createServiceUnavailableResponse();
if (isRunning()) {
return actualDoHandleRequest(servletRequest, servletResponse);
}
else {
return actualDoHandleRequest(servletRequest, servletResponse);
return createServiceUnavailableResponse();
}
}
@@ -536,9 +534,9 @@ public abstract class HttpRequestHandlingEndpointSupport extends MessagingGatewa
private Message<?> createServiceUnavailableResponse() {
if (logger.isDebugEnabled()) {
logger.debug("Endpoint is shutting down; returning status " + HttpStatus.SERVICE_UNAVAILABLE);
logger.debug("Endpoint is stopped; returning status " + HttpStatus.SERVICE_UNAVAILABLE);
}
return this.getMessageBuilderFactory().withPayload("Endpoint is shutting down")
return this.getMessageBuilderFactory().withPayload("Endpoint is stopped")
.setHeader(org.springframework.integration.http.HttpHeaders.STATUS_CODE, HttpStatus.SERVICE_UNAVAILABLE)
.build();
}
@@ -688,22 +686,10 @@ public abstract class HttpRequestHandlingEndpointSupport extends MessagingGatewa
}
}
/**
* Lifecycle
*/
@Override
protected void doStart() {
this.shuttingDown = false;
super.doStart();
}
protected boolean isShuttingDown() {
return this.shuttingDown;
}
@Override
public int beforeShutdown() {
this.shuttingDown = true;
stop();
return this.activeCount.get();
}

View File

@@ -188,7 +188,7 @@
<xsd:documentation><![CDATA[
Used to set the sendTimeout on the underlying MessagingTemplate instance
(org.springframework.integration.core.MessagingTemplate) for sending messages
to the request channel. If not specified this propery will default to "1000"
to the request channel. If not specified this property will default to "1000"
(1 second).
]]></xsd:documentation>
</xsd:annotation>
@@ -198,7 +198,7 @@
<xsd:documentation><![CDATA[
Used to set the receiveTimeout on the underlying MessagingTemplate instance
(org.springframework.integration.core.MessagingTemplate) for receiving messages
from the reply channel. If not specified this propery will default to "1000"
from the reply channel. If not specified this property will default to "1000"
(1 second).
]]></xsd:documentation>
</xsd:annotation>

View File

@@ -21,6 +21,8 @@
</si:channel>
<inbound-channel-adapter id="defaultAdapter" channel="requests" error-channel="errorChannel"
auto-startup="false"
phase="1001"
status-code-expression="'101'"/>
<inbound-channel-adapter id="postOnlyAdapter" path="/postOnly" channel="requests" supported-methods="POST"/>

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2015 the original author or authors.
* Copyright 2002-2016 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.
@@ -20,6 +20,7 @@ import static org.hamcrest.CoreMatchers.instanceOf;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
@@ -123,11 +124,17 @@ public class HttpInboundChannelAdapterParserTests extends AbstractHttpInboundTes
@Test
@SuppressWarnings("unchecked")
public void getRequestOk() throws Exception {
assertFalse(TestUtils.getPropertyValue(this.defaultAdapter, "autoStartup", Boolean.class));
assertEquals(1001, TestUtils.getPropertyValue(this.defaultAdapter, "phase"));
MockHttpServletRequest request = new MockHttpServletRequest();
request.setMethod("GET");
request.setParameter("foo", "bar");
MockHttpServletResponse response = new MockHttpServletResponse();
defaultAdapter.handleRequest(request, response);
this.defaultAdapter.handleRequest(request, response);
assertEquals(HttpServletResponse.SC_SERVICE_UNAVAILABLE, response.getStatus());
this.defaultAdapter.start();
response = new MockHttpServletResponse();
this.defaultAdapter.handleRequest(request, response);
assertEquals(HttpServletResponse.SC_SWITCHING_PROTOCOLS, response.getStatus());
Message<?> message = requests.receive(0);
assertNotNull(message);
@@ -138,7 +145,7 @@ public class HttpInboundChannelAdapterParserTests extends AbstractHttpInboundTes
assertEquals("foo", map.keySet().iterator().next());
assertEquals(1, map.get("foo").size());
assertEquals("bar", map.getFirst("foo"));
assertNotNull(TestUtils.getPropertyValue(defaultAdapter, "errorChannel"));
assertNotNull(TestUtils.getPropertyValue(this.defaultAdapter, "errorChannel"));
}
@Test

View File

@@ -23,6 +23,8 @@
convert-exceptions="true"
request-timeout="1234"
reply-timeout="4567"
auto-startup="false"
phase="1001"
error-channel="errorChannel"/>
<inbound-gateway id="inboundController" request-channel="requests" reply-channel="responses" view-name="foo"

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2015 the original author or authors.
* Copyright 2002-2016 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.
@@ -118,28 +118,33 @@ public class HttpInboundGatewayParserTests {
@Test
public void checkConfig() {
assertNotNull(gateway);
assertTrue(getPropertyValue(gateway, "expectReply", Boolean.class));
assertTrue(getPropertyValue(gateway, "convertExceptions", Boolean.class));
assertSame(this.responses, getPropertyValue(gateway, "replyChannel"));
assertNotNull(TestUtils.getPropertyValue(gateway, "errorChannel"));
MessagingTemplate messagingTemplate = TestUtils.getPropertyValue(
gateway, "messagingTemplate", MessagingTemplate.class);
assertNotNull(this.gateway);
assertTrue(getPropertyValue(this.gateway, "expectReply", Boolean.class));
assertTrue(getPropertyValue(this.gateway, "convertExceptions", Boolean.class));
assertSame(this.responses, getPropertyValue(this.gateway, "replyChannel"));
assertNotNull(TestUtils.getPropertyValue(this.gateway, "errorChannel"));
MessagingTemplate messagingTemplate =
TestUtils.getPropertyValue(this.gateway, "messagingTemplate", MessagingTemplate.class);
assertEquals(1234L, TestUtils.getPropertyValue(messagingTemplate, "sendTimeout"));
assertEquals(4567L, TestUtils.getPropertyValue(messagingTemplate, "receiveTimeout"));
boolean registerDefaultConverters = TestUtils.getPropertyValue(gateway,"mergeWithDefaultConverters", Boolean.class);
boolean registerDefaultConverters =
TestUtils.getPropertyValue(this.gateway,"mergeWithDefaultConverters", Boolean.class);
assertFalse("By default the register-default-converters flag should be false", registerDefaultConverters);
@SuppressWarnings("unchecked")
List<HttpMessageConverter<?>> messageConverters = TestUtils.getPropertyValue(gateway,"messageConverters", List.class);
List<HttpMessageConverter<?>> messageConverters =
TestUtils.getPropertyValue(this.gateway,"messageConverters", List.class);
assertTrue("The default converters should have been registered, given there are no custom converters",
messageConverters.size() > 0);
assertFalse(TestUtils.getPropertyValue(this.gateway, "autoStartup", Boolean.class));
assertEquals(1001, TestUtils.getPropertyValue(this.gateway, "phase"));
}
@Test @DirtiesContext
public void checkFlow() throws Exception {
requests.subscribe(handlerExpecting(any(Message.class)));
this.requests.subscribe(handlerExpecting(any(Message.class)));
MockHttpServletRequest request = new MockHttpServletRequest();
request.setMethod("GET");
request.addHeader("Accept", "application/x-java-serialized-object");
@@ -148,10 +153,11 @@ public class HttpInboundGatewayParserTests {
MockHttpServletResponse response = new MockHttpServletResponse();
List<HttpMessageConverter<?>> converters = new ArrayList<HttpMessageConverter<?>>();
converters.add(new SerializingHttpMessageConverter());
gateway.setMessageConverters(converters);
gateway.afterPropertiesSet();
this.gateway.setMessageConverters(converters);
this.gateway.afterPropertiesSet();
this.gateway.start();
gateway.handleRequest(request, response);
this.gateway.handleRequest(request, response);
assertThat(response.getStatus(), is(HttpServletResponse.SC_OK));
assertEquals(response.getContentType(), "application/x-java-serialized-object");

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2013 the original author or authors.
* Copyright 2002-2016 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.
@@ -53,6 +53,7 @@ import org.springframework.web.servlet.View;
* @author Gary Russell
* @author Gunnar Hillert
* @author Biju Kunjummen
* @author Artem Bilan
* @since 2.0
*/
public class HttpRequestHandlingControllerTests extends AbstractHttpInboundTests {
@@ -65,6 +66,8 @@ public class HttpRequestHandlingControllerTests extends AbstractHttpInboundTests
controller.setRequestChannel(requestChannel);
controller.setViewName("foo");
controller.afterPropertiesSet();
controller.start();
MockHttpServletRequest request = new MockHttpServletRequest();
request.setMethod("POST");
request.setContent("hello".getBytes());
@@ -91,6 +94,8 @@ public class HttpRequestHandlingControllerTests extends AbstractHttpInboundTests
Expression viewExpression = new SpelExpressionParser().parseExpression("'baz'");
controller.setViewExpression(viewExpression);
controller.afterPropertiesSet();
controller.start();
MockHttpServletRequest request = new MockHttpServletRequest();
request.setMethod("POST");
request.setContent("hello".getBytes());
@@ -123,6 +128,8 @@ public class HttpRequestHandlingControllerTests extends AbstractHttpInboundTests
controller.setRequestChannel(requestChannel);
controller.setViewName("foo");
controller.afterPropertiesSet();
controller.start();
MockHttpServletRequest request = new MockHttpServletRequest();
request.setMethod("POST");
@@ -157,6 +164,8 @@ public class HttpRequestHandlingControllerTests extends AbstractHttpInboundTests
Expression viewExpression = new SpelExpressionParser().parseExpression("headers['bar']");
controller.setViewExpression(viewExpression);
controller.afterPropertiesSet();
controller.start();
MockHttpServletRequest request = new MockHttpServletRequest();
request.setMethod("POST");
request.setContent("hello".getBytes());
@@ -188,6 +197,8 @@ public class HttpRequestHandlingControllerTests extends AbstractHttpInboundTests
Expression viewExpression = new SpelExpressionParser().parseExpression("headers['bar']");
controller.setViewExpression(viewExpression);
controller.afterPropertiesSet();
controller.start();
MockHttpServletRequest request = new MockHttpServletRequest();
request.setMethod("POST");
request.setContent("hello".getBytes());
@@ -217,6 +228,8 @@ public class HttpRequestHandlingControllerTests extends AbstractHttpInboundTests
controller.setViewName("foo");
controller.setReplyKey("myReply");
controller.afterPropertiesSet();
controller.start();
MockHttpServletRequest request = new MockHttpServletRequest();
request.setMethod("POST");
request.setContent("howdy".getBytes());
@@ -250,6 +263,8 @@ public class HttpRequestHandlingControllerTests extends AbstractHttpInboundTests
controller.setViewName("foo");
controller.setExtractReplyPayload(false);
controller.afterPropertiesSet();
controller.start();
MockHttpServletRequest request = new MockHttpServletRequest();
request.setMethod("POST");
request.setContent("abc".getBytes());
@@ -280,6 +295,8 @@ public class HttpRequestHandlingControllerTests extends AbstractHttpInboundTests
controller.setBeanFactory(mock(BeanFactory.class));
controller.setRequestChannel(requestChannel);
controller.afterPropertiesSet();
controller.start();
MockHttpServletRequest request = new MockHttpServletRequest();
request.setMethod("POST");
request.setContent("hello".getBytes());
@@ -319,6 +336,8 @@ public class HttpRequestHandlingControllerTests extends AbstractHttpInboundTests
controller.setRequestChannel(requestChannel);
controller.setViewName("foo");
controller.afterPropertiesSet();
controller.start();
final MockHttpServletRequest request = new MockHttpServletRequest();
request.setMethod("POST");
request.setContent("hello".getBytes());

View File

@@ -76,6 +76,8 @@ public class HttpRequestHandlingMessagingGatewayTests extends AbstractHttpInboun
gateway.setBeanFactory(mock(BeanFactory.class));
gateway.setRequestChannel(requestChannel);
gateway.afterPropertiesSet();
gateway.start();
MockHttpServletRequest request = new MockHttpServletRequest();
request.setMethod("GET");
request.setParameter("foo", "bar");
@@ -97,6 +99,8 @@ public class HttpRequestHandlingMessagingGatewayTests extends AbstractHttpInboun
gateway.setRequestPayloadType(String.class);
gateway.setRequestChannel(requestChannel);
gateway.afterPropertiesSet();
gateway.start();
MockHttpServletRequest request = new MockHttpServletRequest();
request.setMethod("POST");
request.setContentType("text/plain");
@@ -133,6 +137,8 @@ public class HttpRequestHandlingMessagingGatewayTests extends AbstractHttpInboun
gateway.setRequestPayloadType(String.class);
gateway.setRequestChannel(requestChannel);
gateway.afterPropertiesSet();
gateway.start();
MockHttpServletRequest request = new MockHttpServletRequest();
request.setMethod("POST");
request.addHeader("Accept", "x-application/octet-stream");
@@ -159,6 +165,8 @@ public class HttpRequestHandlingMessagingGatewayTests extends AbstractHttpInboun
gateway.setRequestPayloadType(String.class);
gateway.setRequestChannel(requestChannel);
gateway.afterPropertiesSet();
gateway.start();
MockHttpServletRequest request = new MockHttpServletRequest();
request.setMethod("POST");
request.setContentType("text/plain");
@@ -184,6 +192,8 @@ public class HttpRequestHandlingMessagingGatewayTests extends AbstractHttpInboun
gateway.setConvertExceptions(true);
gateway.setMessageConverters(Arrays.<HttpMessageConverter<?>>asList(new TestHttpMessageConverter()));
gateway.afterPropertiesSet();
gateway.start();
MockHttpServletRequest request = new MockHttpServletRequest();
request.addHeader("Accept", "application/x-java-serialized-object");
request.setMethod("GET");
@@ -200,6 +210,8 @@ public class HttpRequestHandlingMessagingGatewayTests extends AbstractHttpInboun
gateway.setBeanFactory(mock(BeanFactory.class));
gateway.setRequestChannel(channel);
gateway.afterPropertiesSet();
gateway.start();
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/test");
request.setParameter("foo", "123");
request.addParameter("bar", "456");
@@ -233,6 +245,7 @@ public class HttpRequestHandlingMessagingGatewayTests extends AbstractHttpInboun
converters.add(new SerializingHttpMessageConverter());
gateway.setMessageConverters(converters);
gateway.afterPropertiesSet();
gateway.start();
MockHttpServletRequest request = new MockHttpServletRequest("POST", "/test");
@@ -281,6 +294,7 @@ public class HttpRequestHandlingMessagingGatewayTests extends AbstractHttpInboun
gateway.setBeanFactory(mock(BeanFactory.class));
gateway.setMessageConverters(messageConverters);
gateway.setRequestChannel(requestChannel);
gateway.start();
final MockHttpServletRequest request = new MockHttpServletRequest();
request.setMethod("GET");
@@ -306,6 +320,7 @@ public class HttpRequestHandlingMessagingGatewayTests extends AbstractHttpInboun
gateway.setRequestChannel(requestChannel);
gateway.setReplyTimeout(0);
gateway.afterPropertiesSet();
gateway.start();
MockHttpServletRequest request = new MockHttpServletRequest();
request.setMethod("GET");
MockHttpServletResponse response = new MockHttpServletResponse();
@@ -324,6 +339,8 @@ public class HttpRequestHandlingMessagingGatewayTests extends AbstractHttpInboun
gateway.setReplyTimeout(0);
gateway.setStatusCodeExpression(new LiteralExpression("501"));
gateway.afterPropertiesSet();
gateway.start();
MockHttpServletRequest request = new MockHttpServletRequest();
request.setMethod("GET");
MockHttpServletResponse response = new MockHttpServletResponse();
@@ -352,6 +369,8 @@ public class HttpRequestHandlingMessagingGatewayTests extends AbstractHttpInboun
});
gateway.setErrorChannel(errorChannel);
gateway.afterPropertiesSet();
gateway.start();
MockHttpServletRequest request = new MockHttpServletRequest();
request.setMethod("GET");
MockHttpServletResponse response = new MockHttpServletResponse();
@@ -372,6 +391,8 @@ public class HttpRequestHandlingMessagingGatewayTests extends AbstractHttpInboun
gateway.setErrorChannel(errorChannel);
gateway.setStatusCodeExpression(new LiteralExpression("501"));
gateway.afterPropertiesSet();
gateway.start();
MockHttpServletRequest request = new MockHttpServletRequest();
request.setMethod("GET");
MockHttpServletResponse response = new MockHttpServletResponse();

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2013 the original author or authors.
* Copyright 2002-2016 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.
@@ -78,6 +78,7 @@ public class HttpRequestHandlingMessagingGatewayWithPathMappingTests extends Abs
requestMapping.setPathPatterns("/fname/{f}/lname/{l}");
gateway.setRequestMapping(requestMapping);
gateway.afterPropertiesSet();
gateway.start();
gateway.setRequestChannel(echoChannel);
@@ -125,6 +126,7 @@ public class HttpRequestHandlingMessagingGatewayWithPathMappingTests extends Abs
gateway.setRequestChannel(echoChannel);
gateway.setPayloadExpression(PARSER.parseExpression("#pathVariables.f"));
gateway.afterPropertiesSet();
gateway.start();
Object result = gateway.doHandleRequest(request, response);
assertTrue(result instanceof Message);
@@ -170,6 +172,7 @@ public class HttpRequestHandlingMessagingGatewayWithPathMappingTests extends Abs
gateway.setRequestChannel(echoChannel);
gateway.setPayloadExpression(PARSER.parseExpression("#pathVariables"));
gateway.afterPropertiesSet();
gateway.start();
Object result = gateway.doHandleRequest(request, response);
assertTrue(result instanceof Message);

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2015 the original author or authors.
* Copyright 2015-2016 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.
@@ -63,6 +63,7 @@ public class MultipartAsRawByteArrayTests {
gw.setBeanFactory(mock(BeanFactory.class));
gw.setRequestPayloadType(byte[].class);
gw.afterPropertiesSet();
gw.start();
HttpServletRequest request = mock(HttpServletRequest.class);
ServletInputStream sis = mock(ServletInputStream.class);