INT-3781: Configure HTTP GW Timeout Status Code

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

- Add mechanisms to set status code on an inbound gateway timeout.
- Send a message to the error channel if configured.

Polishing - Add failedMessage to MTE

Fix Schema Docs; Timeout Detection on Error Flow

Add Zookeeper Leadership Logs

Fix typo in the `SmartLifecycleRoleController`
This commit is contained in:
Gary Russell
2015-07-24 13:02:58 -04:00
committed by Artem Bilan
parent 178c86faa4
commit 9944e54ce5
9 changed files with 256 additions and 32 deletions

View File

@@ -195,6 +195,10 @@ public class HttpInboundEndpointParser extends AbstractSingleBeanDefinitionParse
BeanDefinition statusCodeExpressionDef =
IntegrationNamespaceUtils.createExpressionDefIfAttributeDefined("status-code-expression", element);
if (statusCodeExpressionDef == null) {
statusCodeExpressionDef = IntegrationNamespaceUtils
.createExpressionDefIfAttributeDefined("reply-timeout-status-code-expression", element);
}
if (statusCodeExpressionDef != null) {
builder.addPropertyValue("statusCodeExpression", statusCodeExpressionDef);
}

View File

@@ -50,6 +50,7 @@ import org.springframework.http.converter.xml.Jaxb2RootElementHttpMessageConvert
import org.springframework.http.converter.xml.SourceHttpMessageConverter;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.http.server.ServletServerHttpResponse;
import org.springframework.integration.MessageTimeoutException;
import org.springframework.integration.context.OrderlyShutdownCapable;
import org.springframework.integration.expression.ExpressionUtils;
import org.springframework.integration.gateway.MessagingGatewaySupport;
@@ -122,6 +123,8 @@ public abstract class HttpRequestHandlingEndpointSupport extends MessagingGatewa
private final List<HttpMessageConverter<?>> defaultMessageConverters = new ArrayList<HttpMessageConverter<?>>();
private final boolean expectReply;
private volatile List<HttpMessageConverter<?>> messageConverters = new ArrayList<HttpMessageConverter<?>>();
private volatile RequestMapping requestMapping = new RequestMapping();
@@ -136,8 +139,6 @@ public abstract class HttpRequestHandlingEndpointSupport extends MessagingGatewa
private volatile HeaderMapper<HttpHeaders> headerMapper = DefaultHttpHeaderMapper.inboundMapper();
private final boolean expectReply;
private volatile boolean extractReplyPayload = true;
private volatile MultipartResolver multipartResolver;
@@ -159,6 +160,7 @@ public abstract class HttpRequestHandlingEndpointSupport extends MessagingGatewa
}
public HttpRequestHandlingEndpointSupport(boolean expectReply) {
super(expectReply);
this.expectReply = expectReply;
this.defaultMessageConverters.add(new MultipartAwareFormHttpMessageConverter());
this.defaultMessageConverters.add(new ByteArrayHttpMessageConverter());
@@ -331,7 +333,8 @@ public abstract class HttpRequestHandlingEndpointSupport extends MessagingGatewa
/**
* Specify the {@link Expression} to resolve a status code for Response
* to override the default '200 OK'.
* <p> The {@link #statusCodeExpression} is applied only for the one-way {@code <http:inbound-channel-adapter/>}.
* <p> The {@link #statusCodeExpression} is applied only for the one-way {@code <http:inbound-channel-adapter/>}
* or when no reply (timeout) is received for a gateway.
* The {@code <http:inbound-gateway/>} resolves an {@link HttpStatus} from the
* {@link org.springframework.integration.http.HttpHeaders#STATUS_CODE} reply {@link Message} header.
* @param statusCodeExpression The status code Expression.
@@ -378,11 +381,6 @@ public abstract class HttpRequestHandlingEndpointSupport extends MessagingGatewa
}
this.validateSupportedMethods();
if (this.expectReply && this.statusCodeExpression != null) {
logger.warn("The 'statusCodeExpression' is ignored when " +
"this component is configured as request/reply gateway");
}
if (this.statusCodeExpression != null) {
this.evaluationContext = createEvaluationContext();
}
@@ -505,7 +503,23 @@ public abstract class HttpRequestHandlingEndpointSupport extends MessagingGatewa
Message<?> reply = null;
if (this.expectReply) {
reply = this.sendAndReceiveMessage(message);
try {
reply = this.sendAndReceiveMessage(message);
}
catch (MessageTimeoutException e) {
if (this.statusCodeExpression != null) {
reply = getMessageBuilderFactory().withPayload(e.getMessage())
.setHeader(org.springframework.integration.http.HttpHeaders.STATUS_CODE,
evaluateHttpStatus())
.build();
}
else {
reply = getMessageBuilderFactory().withPayload(e.getMessage())
.setHeader(org.springframework.integration.http.HttpHeaders.STATUS_CODE,
HttpStatus.INTERNAL_SERVER_ERROR)
.build();
}
}
}
else {
this.send(message);
@@ -552,17 +566,22 @@ public abstract class HttpRequestHandlingEndpointSupport extends MessagingGatewa
protected void setStatusCodeIfNeeded(ServletServerHttpResponse response) {
if (this.statusCodeExpression != null) {
if (this.evaluationContext == null) {
this.evaluationContext = createEvaluationContext();
}
Object value = this.statusCodeExpression.getValue(this.evaluationContext);
HttpStatus httpStatus = buildHttpStatus(value);
HttpStatus httpStatus = evaluateHttpStatus();
if (httpStatus != null) {
response.setStatusCode(httpStatus);
}
}
}
private HttpStatus evaluateHttpStatus() {
if (this.evaluationContext == null) {
this.evaluationContext = createEvaluationContext();
}
Object value = this.statusCodeExpression.getValue(this.evaluationContext);
HttpStatus httpStatus = buildHttpStatus(value);
return httpStatus;
}
/**
* Prepares an instance of {@link ServletServerHttpRequest} from the raw
* {@link HttpServletRequest}. Also converts the request into a multipart request to

View File

@@ -63,12 +63,12 @@
<xsd:annotation>
<xsd:documentation>
A SpEL expression that resolves to an 'HttpStatus' code when rendering a response.
The expression must return the object which can be converted to a
The expression must return an object which can be converted to a
'org.springframework.http.HttpStatus' enum value.
The 'evaluationContext' has a 'BeanResolver' but no variables, so the usage of this attribute
is somewhat limited.
An example might be to resolve, at runtime, some scoped Bean that returns an
'HttpStatus' value.
'HttpStatus' value, or use a literal expression e.g. "201".
By default 'status-code-expression' is null, meaning that the default '200 OK' response status
will be returned.
The 'http:inbound-gateway' resolves the 'status code' from the 'http_statusCode' header of the reply
@@ -203,6 +203,25 @@
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="reply-timeout-status-code-expression" type="xsd:string">
<xsd:annotation>
<xsd:documentation>
A SpEL expression that resolves to an 'HttpStatus' code when rendering a response after
a 'reply-timeout'.
The expression must return an object which can be converted to a
'org.springframework.http.HttpStatus' enum value.
The 'evaluationContext' has a 'BeanResolver' but no variables, so the usage of this attribute
is somewhat limited.
An example might be to resolve, at runtime, some scoped Bean that returns an
'HttpStatus' value, or use a literal expression e.g. "504".
By default 'status-code-expression' is null, meaning that the default
'500 Internal Server Error' response status will be returned after a timeout.
When a timeout is not encountered,
the 'http:inbound-gateway' resolves the 'status code' from the 'http_statusCode' header of the reply
Message.
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2014 the original author or authors.
* Copyright 2002-2015 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,7 @@ package org.springframework.integration.http.inbound;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThat;
import static org.mockito.Mockito.mock;
import java.io.IOException;
@@ -26,27 +27,32 @@ import java.io.PrintWriter;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import org.hamcrest.Matchers;
import org.junit.Test;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.expression.common.LiteralExpression;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.converter.AbstractHttpMessageConverter;
import org.springframework.http.converter.ByteArrayHttpMessageConverter;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;
import org.springframework.messaging.Message;
import org.springframework.integration.channel.DirectChannel;
import org.springframework.integration.channel.QueueChannel;
import org.springframework.integration.handler.AbstractReplyProducingMessageHandler;
import org.springframework.integration.http.AbstractHttpInboundTests;
import org.springframework.integration.http.HttpHeaders;
import org.springframework.integration.http.converter.SerializingHttpMessageConverter;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.GenericMessage;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.util.LinkedMultiValueMap;
@@ -296,6 +302,92 @@ public class HttpRequestHandlingMessagingGatewayTests extends AbstractHttpInboun
assertEquals("text/plain", contentTypes.get(0));
}
@Test
public void timeoutDefault() throws Exception {
QueueChannel requestChannel = new QueueChannel();
HttpRequestHandlingMessagingGateway gateway = new HttpRequestHandlingMessagingGateway(true);
gateway.setBeanFactory(mock(BeanFactory.class));
gateway.setRequestChannel(requestChannel);
gateway.setReplyTimeout(0);
gateway.afterPropertiesSet();
MockHttpServletRequest request = new MockHttpServletRequest();
request.setMethod("GET");
MockHttpServletResponse response = new MockHttpServletResponse();
gateway.handleRequest(request, response);
Message<?> message = requestChannel.receive(0);
assertNotNull(message);
assertEquals(500, response.getStatus());
}
@Test
public void timeoutStatusExpression() throws Exception {
QueueChannel requestChannel = new QueueChannel();
HttpRequestHandlingMessagingGateway gateway = new HttpRequestHandlingMessagingGateway(true);
gateway.setBeanFactory(mock(BeanFactory.class));
gateway.setRequestChannel(requestChannel);
gateway.setReplyTimeout(0);
gateway.setStatusCodeExpression(new LiteralExpression("501"));
gateway.afterPropertiesSet();
MockHttpServletRequest request = new MockHttpServletRequest();
request.setMethod("GET");
MockHttpServletResponse response = new MockHttpServletResponse();
gateway.handleRequest(request, response);
Message<?> message = requestChannel.receive(0);
assertNotNull(message);
assertEquals(501, response.getStatus());
}
@Test
public void timeoutErrorFlow() throws Exception {
QueueChannel requestChannel = new QueueChannel();
HttpRequestHandlingMessagingGateway gateway = new HttpRequestHandlingMessagingGateway(true);
gateway.setBeanFactory(mock(BeanFactory.class));
gateway.setRequestChannel(requestChannel);
gateway.setReplyTimeout(0);
DirectChannel errorChannel = new DirectChannel();
errorChannel.subscribe(new AbstractReplyProducingMessageHandler() {
@Override
protected Object handleRequestMessage(Message<?> requestMessage) {
return new GenericMessage<String>("foo",
Collections.<String, Object> singletonMap(HttpHeaders.STATUS_CODE, HttpStatus.GATEWAY_TIMEOUT));
}
});
gateway.setErrorChannel(errorChannel);
gateway.afterPropertiesSet();
MockHttpServletRequest request = new MockHttpServletRequest();
request.setMethod("GET");
MockHttpServletResponse response = new MockHttpServletResponse();
gateway.handleRequest(request, response);
Message<?> message = requestChannel.receive(0);
assertNotNull(message);
assertEquals(504, response.getStatus());
}
@Test
public void timeoutErrorFlowTimeout() throws Exception {
QueueChannel requestChannel = new QueueChannel();
HttpRequestHandlingMessagingGateway gateway = new HttpRequestHandlingMessagingGateway(true);
gateway.setBeanFactory(mock(BeanFactory.class));
gateway.setRequestChannel(requestChannel);
gateway.setReplyTimeout(0);
QueueChannel errorChannel = new QueueChannel();
gateway.setErrorChannel(errorChannel);
gateway.setStatusCodeExpression(new LiteralExpression("501"));
gateway.afterPropertiesSet();
MockHttpServletRequest request = new MockHttpServletRequest();
request.setMethod("GET");
MockHttpServletResponse response = new MockHttpServletResponse();
gateway.handleRequest(request, response);
Message<?> message = requestChannel.receive(0);
assertNotNull(message);
assertEquals(501, response.getStatus());
assertThat(response.getContentAsString(), Matchers.containsString("from error channel"));
}
private class ContentTypeCheckingMockHttpServletResponse extends MockHttpServletResponse {
private final List<String> contentTypeList = new ArrayList<String>();