INT-3447: HTTP-inbound: status-code-expression

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

Polishing
This commit is contained in:
Artem Bilan
2014-07-04 13:22:16 +03:00
committed by Gary Russell
parent c309b031f8
commit 93be035cae
10 changed files with 139 additions and 16 deletions

View File

@@ -176,6 +176,12 @@ public class HttpInboundEndpointParser extends AbstractSingleBeanDefinitionParse
builder.addPropertyValue("requestMapping", requestMappingDef);
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "request-payload-type", "requestPayloadType");
BeanDefinition statusCodeExpressionDef =
IntegrationNamespaceUtils.createExpressionDefIfAttributeDefined("status-code-expression", element);
if (statusCodeExpressionDef != null) {
builder.addPropertyValue("statusCodeExpression", statusCodeExpressionDef);
}
}
private String getInputChannelAttributeName() {

View File

@@ -51,6 +51,7 @@ import org.springframework.web.servlet.mvc.Controller;
*
* @author Mark Fisher
* @author Gary Russell
* @author Artem Bilan
* @since 2.0
*/
public class HttpRequestHandlingController extends HttpRequestHandlingEndpointSupport implements Controller {
@@ -148,12 +149,16 @@ public class HttpRequestHandlingController extends HttpRequestHandlingEndpointSu
ModelAndView modelAndView = new ModelAndView();
try {
Message<?> replyMessage = super.doHandleRequest(servletRequest, servletResponse);
ServletServerHttpResponse response = new ServletServerHttpResponse(servletResponse);
if (replyMessage != null) {
ServletServerHttpResponse response = new ServletServerHttpResponse(servletResponse);
Object reply = setupResponseAndConvertReply(response, replyMessage);
response.close();
modelAndView.addObject(this.replyKey, reply);
}
else {
setStatusCodeIfNeeded(response);
}
if (this.viewExpression != null) {
Object view;
if (replyMessage != null) {
@@ -184,4 +189,5 @@ public class HttpRequestHandlingController extends HttpRequestHandlingEndpointSu
}
return modelAndView;
}
}

View File

@@ -31,6 +31,7 @@ import javax.xml.transform.Source;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.http.HttpEntity;
@@ -151,6 +152,10 @@ public abstract class HttpRequestHandlingEndpointSupport extends MessagingGatewa
private volatile boolean shuttingDown;
private volatile Expression statusCodeExpression;
private volatile EvaluationContext evaluationContext;
private final AtomicInteger activeCount = new AtomicInteger();
public HttpRequestHandlingEndpointSupport() {
@@ -315,6 +320,19 @@ public abstract class HttpRequestHandlingEndpointSupport extends MessagingGatewa
this.multipartResolver = multipartResolver;
}
/**
* 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/>}.
* 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.
* @since 4.1
*/
public void setStatusCodeExpression(Expression statusCodeExpression) {
this.statusCodeExpression = statusCodeExpression;
}
@Override
public String getComponentType() {
return (this.expectReply) ? "http:inbound-gateway" : "http:inbound-channel-adapter";
@@ -351,6 +369,15 @@ public abstract class HttpRequestHandlingEndpointSupport extends MessagingGatewa
this.messageConverters.addAll(this.defaultMessageConverters);
}
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();
}
}
/**
@@ -515,6 +542,19 @@ 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);
if (httpStatus != null) {
response.setStatusCode(httpStatus);
}
}
}
/**
* Prepares an instance of {@link ServletServerHttpRequest} from the raw
* {@link HttpServletRequest}. Also converts the request into a multipart request to
@@ -586,15 +626,19 @@ public abstract class HttpRequestHandlingEndpointSupport extends MessagingGatewa
private HttpStatus resolveHttpStatusFromHeaders(MessageHeaders headers) {
Object httpStatusFromHeader = headers.get(org.springframework.integration.http.HttpHeaders.STATUS_CODE);
return buildHttpStatus(httpStatusFromHeader);
}
private HttpStatus buildHttpStatus(Object httpStatusValue) {
HttpStatus httpStatus = null;
if (httpStatusFromHeader instanceof HttpStatus) {
httpStatus = (HttpStatus) httpStatusFromHeader;
if (httpStatusValue instanceof HttpStatus) {
httpStatus = (HttpStatus) httpStatusValue;
}
else if (httpStatusFromHeader instanceof Integer) {
httpStatus = HttpStatus.valueOf((Integer) httpStatusFromHeader);
else if (httpStatusValue instanceof Integer) {
httpStatus = HttpStatus.valueOf((Integer) httpStatusValue);
}
else if (httpStatusFromHeader instanceof String) {
httpStatus = HttpStatus.valueOf(Integer.parseInt((String) httpStatusFromHeader));
else if (httpStatusValue instanceof String) {
httpStatus = HttpStatus.valueOf(Integer.parseInt((String) httpStatusValue));
}
return httpStatus;
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2013 the original author or authors.
* Copyright 2002-2014 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.
@@ -57,9 +57,11 @@ import org.springframework.web.HttpRequestHandler;
*
* @author Mark Fisher
* @author Oleg Zhurakousky
* @author Artem Bilan
* @since 2.0
*/
public class HttpRequestHandlingMessagingGateway extends HttpRequestHandlingEndpointSupport implements HttpRequestHandler {
public class HttpRequestHandlingMessagingGateway extends HttpRequestHandlingEndpointSupport
implements HttpRequestHandler {
private volatile boolean convertExceptions;
@@ -115,6 +117,9 @@ public class HttpRequestHandlingMessagingGateway extends HttpRequestHandlingEndp
this.writeResponse(responseContent, response, request.getHeaders().getAccept());
}
}
else {
setStatusCodeIfNeeded(response);
}
}
private Object handleExceptionInternal(Exception e) throws IOException {
@@ -135,7 +140,8 @@ public class HttpRequestHandlingMessagingGateway extends HttpRequestHandlingEndp
}
@SuppressWarnings({"unchecked", "rawtypes"})
private void writeResponse(Object content, ServletServerHttpResponse response, List<MediaType> acceptTypes) throws IOException {
private void writeResponse(Object content, ServletServerHttpResponse response, List<MediaType> acceptTypes)
throws IOException {
if (CollectionUtils.isEmpty(acceptTypes)) {
acceptTypes = Collections.singletonList(MediaType.ALL);
}

View File

@@ -52,6 +52,23 @@
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="status-code-expression" type="xsd:string">
<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
'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.
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
Message.
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attributeGroup ref="inboundCommonAttributes" />
</xsd:complexType>
</xsd:element>
@@ -211,7 +228,7 @@
The expression can resolve to a view name or View object.
In the case of 'inbound-gateway' the root object of the evaluation context is the reply message.
In the case of 'inbound-channel-adapter' the 'evaluationContext' for this expression
is rather lightweight, because there is no reply message, ; it has a
is rather lightweight, because there is no reply message, it 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 a

View File

@@ -20,7 +20,8 @@
<si:queue capacity="1"/>
</si:channel>
<inbound-channel-adapter id="defaultAdapter" channel="requests" error-channel="errorChannel"/>
<inbound-channel-adapter id="defaultAdapter" channel="requests" error-channel="errorChannel"
status-code-expression="'101'"/>
<inbound-channel-adapter id="postOnlyAdapter" path="/postOnly" channel="requests" supported-methods="POST"/>
@@ -38,7 +39,8 @@
<inbound-channel-adapter id="putOrDeleteAdapter" channel="requests" supported-methods="PUT, delete"/>
<inbound-channel-adapter id="inboundController" channel="requests" view-name="foo" error-code="oops">
<inbound-channel-adapter id="inboundController" channel="requests" view-name="foo" error-code="oops"
status-code-expression="T(org.springframework.http.HttpStatus).ACCEPTED">
<request-mapping headers="BAR"/>
</inbound-channel-adapter>

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2013 the original author or authors.
* Copyright 2002-2014 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.
@@ -128,7 +128,7 @@ public class HttpInboundChannelAdapterParserTests extends AbstractHttpInboundTes
request.setParameter("foo", "bar");
MockHttpServletResponse response = new MockHttpServletResponse();
defaultAdapter.handleRequest(request, response);
assertEquals(HttpServletResponse.SC_OK, response.getStatus());
assertEquals(HttpServletResponse.SC_SWITCHING_PROTOCOLS, response.getStatus());
Message<?> message = requests.receive(0);
assertNotNull(message);
Object payload = message.getPayload();
@@ -256,6 +256,15 @@ public class HttpInboundChannelAdapterParserTests extends AbstractHttpInboundTes
assertEquals("oops", errorCode);
Expression viewExpression = TestUtils.getPropertyValue(inboundController, "viewExpression", Expression.class);
assertEquals("foo", viewExpression.getExpressionString());
MockHttpServletRequest request = new MockHttpServletRequest();
request.setMethod("GET");
request.setParameter("foo", "bar");
MockHttpServletResponse response = new MockHttpServletResponse();
inboundController.handleRequest(request, response);
assertEquals(HttpServletResponse.SC_ACCEPTED, response.getStatus());
Message<?> message = requests.receive(0);
assertNotNull(message);
}
@Test

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2013 the original author or authors.
* Copyright 2002-2014 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.
@@ -31,6 +31,7 @@ import java.util.List;
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.MediaType;
@@ -116,6 +117,7 @@ public class HttpRequestHandlingMessagingGatewayTests extends AbstractHttpInboun
}
});
HttpRequestHandlingMessagingGateway gateway = new HttpRequestHandlingMessagingGateway(true);
gateway.setStatusCodeExpression(new LiteralExpression("foo"));
gateway.setBeanFactory(mock(BeanFactory.class));
gateway.setRequestPayloadType(String.class);
gateway.setRequestChannel(requestChannel);

View File

@@ -317,6 +317,29 @@ By default the HTTP request will be generated using an instance of <classname>Si
</listitem>
</itemizedlist>
<para><emphasis>Response StatusCode</emphasis></para>
<para>
Starting with <emphasis>version 4.1</emphasis> the <code>&lt;http:inbound-channel-adapter&gt;</code>
can be configured with a <code>status-code-expression</code> to override the default <code>200 OK</code> status.
The expression must return an object which can be converted to a
<classname>org.springframework.http.HttpStatus</classname> enum value.
The <code>evaluationContext</code> has a <classname>BeanResolver</classname> 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 a status code value but,
most likely, it will be set to a fixed value
such as <code>status-code=expression="'204'"</code> (No Content), or
<code>status-code-expression="T(org.springframework.http.HttpStatus).NO_CONTENT"</code>.
By default, <code>status-code-expression</code> is null meaning that the normal '200 OK' response status
will be returned.
<programlisting language="xml"><![CDATA[<http:inbound-channel-adapter id="inboundController"
channel="requests" view-name="foo" error-code="oops"
status-code-expression="T(org.springframework.http.HttpStatus).ACCEPTED">
<request-mapping headers="BAR"/>
</http:inbound-channel-adapter>]]></programlisting>
The <code>&lt;http:inbound-gateway&gt;</code> resolves the 'status code' from the <code>http_statusCode</code>
header of the reply Message.
</para>
<para><emphasis>URI Template Variables and Expressions</emphasis></para>
<para>
By Using the <emphasis>path</emphasis> attribute in conjunction with the

View File

@@ -46,5 +46,13 @@
before sending the request.
</para>
</section>
<section id="4.1-http-status-code">
<title>Http Inbound Channel Adapter and StatusCode</title>
<para>
The <code>&lt;http:inbound-channel-adapter&gt;</code> can now be configured with a
<code>status-code-expression</code> to override the default <code>200 OK</code> status.
See <xref linkend="http-namespace"/> for more information.
</para>
</section>
</section>
</chapter>