INT-3714: Add HTTP CORS Support

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

Docs and change Reactor dependency to `RELEASE`

AMQP -> `1.5.0.M1` and rebase

Doc Polishing
This commit is contained in:
Artem Bilan
2015-05-06 00:38:16 +03:00
committed by Gary Russell
parent 5065a5a1d5
commit 580ecddcc0
10 changed files with 561 additions and 14 deletions

View File

@@ -113,8 +113,8 @@ subprojects { subproject ->
openJpaVersion = '2.3.0'
pahoMqttClientVersion = '1.0.2'
postgresVersion = '9.1-901-1.jdbc4'
reactorVersion = '2.0.1.BUILD-SNAPSHOT'
reactorSpringVersion = '2.0.1.BUILD-SNAPSHOT'
reactorVersion = '2.0.1.RELEASE'
reactorSpringVersion = '2.0.1.RELEASE'
romeToolsVersion = '1.5.0'
saajApiVersion = '1.3.5'
saajImplVersion = '1.3.23'
@@ -123,7 +123,7 @@ subprojects { subproject ->
tomcatVersion = "8.0.18"
smack3Version = '3.2.1'
smackVersion = '4.0.6'
springAmqpVersion = project.hasProperty('springAmqpVersion') ? project.springAmqpVersion : '1.5.0.BUILD-SNAPSHOT'
springAmqpVersion = project.hasProperty('springAmqpVersion') ? project.springAmqpVersion : '1.5.0.M1'
springDataMongoVersion = '1.7.0.RELEASE'
springDataRedisVersion = '1.5.0.RELEASE'
springGemfireVersion = '1.6.0.RELEASE'

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.
@@ -29,6 +29,7 @@ import org.springframework.beans.factory.support.ManagedMap;
import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.integration.config.xml.IntegrationNamespaceUtils;
import org.springframework.integration.http.inbound.CrossOrigin;
import org.springframework.integration.http.inbound.HttpRequestHandlingController;
import org.springframework.integration.http.inbound.HttpRequestHandlingMessagingGateway;
import org.springframework.integration.http.inbound.RequestMapping;
@@ -172,9 +173,24 @@ public class HttpInboundEndpointParser extends AbstractSingleBeanDefinitionParse
builder.addPropertyValue("headerMapper", headerMapperBuilder.getBeanDefinition());
}
BeanDefinition requestMappingDef = this.createRequestMapping(element);
BeanDefinition requestMappingDef = createRequestMapping(element);
builder.addPropertyValue("requestMapping", requestMappingDef);
Element crossOriginElement = DomUtils.getChildElementByTagName(element, "cross-origin");
if (crossOriginElement != null) {
BeanDefinitionBuilder crossOriginBuilder =
BeanDefinitionBuilder.genericBeanDefinition(CrossOrigin.class);
String[] attributes = {"origin", "allowed-headers", "exposed-headers", "max-age", "method"};
for (String crossOriginAttribute : attributes) {
IntegrationNamespaceUtils.setValueIfAttributeDefined(crossOriginBuilder, crossOriginElement,
crossOriginAttribute);
}
IntegrationNamespaceUtils.setValueIfAttributeDefined(crossOriginBuilder, crossOriginElement,
"allow-credentials", true);
builder.addPropertyValue("crossOrigin", crossOriginBuilder.getBeanDefinition());
}
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "request-payload-type", "requestPayloadType");
BeanDefinition statusCodeExpressionDef =

View File

@@ -0,0 +1,93 @@
/*
* Copyright 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.
* 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.web.bind.annotation.RequestMethod;
/**
* The mapping to permit cross origin requests (CORS) for {@link HttpRequestHandlingEndpointSupport}.
* Provides direct mapping in terms of functionality compared to
* {@link org.springframework.web.bind.annotation.CrossOrigin}.
*
* @author Artem Bilan
* @since 4.2
* @see org.springframework.web.bind.annotation.CrossOrigin
* @see IntegrationRequestMappingHandlerMapping
*/
public class CrossOrigin {
private String[] origin = {"*"};
private String[] allowedHeaders = {"*"};
private String[] exposedHeaders = {};
private RequestMethod[] method = {};
private Boolean allowCredentials = true;
private long maxAge = 1800;
public void setOrigin(String... origin) {
this.origin = origin;
}
public String[] getOrigin() {
return origin;
}
public void setAllowedHeaders(String... allowedHeaders) {
this.allowedHeaders = allowedHeaders;
}
public String[] getAllowedHeaders() {
return allowedHeaders;
}
public void setExposedHeaders(String... exposedHeaders) {
this.exposedHeaders = exposedHeaders;
}
public String[] getExposedHeaders() {
return exposedHeaders;
}
public void setMethod(RequestMethod... method) {
this.method = method;
}
public RequestMethod[] getMethod() {
return method;
}
public void setAllowCredentials(Boolean allowCredentials) {
this.allowCredentials = allowCredentials;
}
public Boolean getAllowCredentials() {
return allowCredentials;
}
public void setMaxAge(long maxAge) {
this.maxAge = maxAge;
}
public long getMaxAge() {
return maxAge;
}
}

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.
@@ -126,6 +126,8 @@ public abstract class HttpRequestHandlingEndpointSupport extends MessagingGatewa
private volatile RequestMapping requestMapping = new RequestMapping();
private volatile CrossOrigin crossOrigin;
private volatile Class<?> requestPayloadType = null;
private volatile boolean convertersMerged;
@@ -283,6 +285,20 @@ public abstract class HttpRequestHandlingEndpointSupport extends MessagingGatewa
return requestMapping;
}
/**
* Set the {@link CrossOrigin} to permit cross origin requests for this endpoint.
* @param crossOrigin the CrossOrigin config.
* @since 4.2
*/
public void setCrossOrigin(CrossOrigin crossOrigin) {
this.crossOrigin = crossOrigin;
}
public CrossOrigin getCrossOrigin() {
return crossOrigin;
}
/**
* Specify the type of payload to be generated when the inbound HTTP request content is read by the
* {@link HttpMessageConverter}s. By default this value is null which means at runtime any "text" Content-Type will

View File

@@ -27,11 +27,15 @@ import javax.servlet.http.HttpServletResponse;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.ReflectionUtils;
import org.springframework.web.HttpRequestHandler;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerExecutionChain;
import org.springframework.web.servlet.mvc.condition.NameValueExpression;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
@@ -98,10 +102,50 @@ public final class IntegrationRequestMappingHandlerMapping extends RequestMappin
}
RequestMappingInfo mapping = this.getMappingForEndpoint((HttpRequestHandlingEndpointSupport) handler);
if (mapping != null) {
this.registerMapping(mapping, handler, HANDLE_REQUEST_METHOD);
registerMapping(mapping, handler, HANDLE_REQUEST_METHOD);
}
}
@Override
protected CorsConfiguration initCorsConfiguration(Object handler, Method method, RequestMappingInfo mappingInfo) {
CrossOrigin crossOrigin = ((HttpRequestHandlingEndpointSupport) 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;
}
return null;
}
/**
* Created a {@link RequestMappingInfo} from a
* 'Spring Integration HTTP Inbound Endpoint' {@link RequestMapping}.

View File

@@ -32,6 +32,13 @@
</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="cross-origin" type="crossOriginType" minOccurs="0">
<xsd:annotation>
<xsd:documentation>
Marks this endpoint as permitting cross origin requests (CORS).
</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="header" type="headerType" minOccurs="0" maxOccurs="unbounded">
<xsd:annotation>
<xsd:documentation>
@@ -108,6 +115,13 @@
</xsd:complexContent>
</xsd:complexType>
</xsd:element>
<xsd:element name="cross-origin" type="crossOriginType" minOccurs="0">
<xsd:annotation>
<xsd:documentation>
Marks this endpoint as permitting cross origin requests (CORS).
</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="header" type="headerType" minOccurs="0" maxOccurs="unbounded">
<xsd:annotation>
<xsd:documentation>
@@ -589,6 +603,79 @@
</xsd:attribute>
</xsd:complexType>
<xsd:complexType name="crossOriginType">
<xsd:annotation>
<xsd:documentation>
Defines configuration for org.springframework.web.cors.CorsConfiguration.
</xsd:documentation>
</xsd:annotation>
<xsd:attribute name="origin" type="xsd:string" default="*">
<xsd:annotation>
<xsd:documentation>
List of allowed origins. "*" means that all origins are allowed. These values
are placed in the 'Access-Control-Allow-Origin' header of both the pre-flight
and actual responses. Default value is "*".
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="allowed-headers" type="xsd:string" default="*">
<xsd:annotation>
<xsd:documentation>
Indicates which request headers can be used during the actual request. "*" means
that all headers asked by the client are allowed. This property controls the value of
pre-flight response's 'Access-Control-Allow-Headers' header.
Default value is "*".
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="exposed-headers" type="xsd:string">
<xsd:annotation>
<xsd:documentation>
List of response headers that the user-agent will allow the client to access. This property
controls the value of actual response's 'Access-Control-Expose-Headers' header.
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="method">
<xsd:annotation>
<xsd:documentation>
The HTTP request methods to allow: GET, POST, HEAD, OPTIONS, PUT, PATCH, DELETE, TRACE.
Methods specified here overrides 'supported-methods' ones.
</xsd:documentation>
</xsd:annotation>
<xsd:simpleType>
<xsd:union memberTypes="httpMethodEnumeration xsd:string" />
</xsd:simpleType>
</xsd:attribute>
<xsd:attribute name="allow-credentials" default="true">
<xsd:annotation>
<xsd:documentation>
Set to "true" if the the browser should include any cookies associated to the domain
of the request being annotated, or "false" if it should not. Empty string "" means undefined.
If true, the pre-flight response will include the header
'Access-Control-Allow-Credentials=true'. Default value is "true".
</xsd:documentation>
</xsd:annotation>
<xsd:simpleType>
<xsd:union memberTypes="xsd:boolean xsd:string" />
</xsd:simpleType>
</xsd:attribute>
<xsd:attribute name="max-age" default="1800">
<xsd:annotation>
<xsd:documentation>
Controls the cache duration for pre-flight responses. Setting this to a reasonable
value can reduce the number of pre-flight request/response interaction required by
the browser. This property controls the value of the 'Access-Control-Max-Age' header
in the pre-flight response. Value set to '-1' means undefined.
Default value is 1800 seconds, or 30 minutes.
</xsd:documentation>
</xsd:annotation>
<xsd:simpleType>
<xsd:union memberTypes="xsd:long xsd:string" />
</xsd:simpleType>
</xsd:attribute>
</xsd:complexType>
<xsd:attributeGroup name="httpOutboundCommonAttributes">
<xsd:attribute name="url" type="xsd:string" use="optional">
<xsd:annotation>

View File

@@ -0,0 +1,52 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns:int-http="http://www.springframework.org/schema/integration/http"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:beans="http://www.springframework.org/schema/beans"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/integration/http
http://www.springframework.org/schema/integration/http/spring-integration-http.xsd">
<int-http:inbound-channel-adapter path="/no" supported-methods="GET" channel="nullChannel"/>
<int-http:inbound-channel-adapter path="/no" supported-methods="POST" channel="nullChannel"/>
<int-http:inbound-channel-adapter path="/default" supported-methods="GET" channel="nullChannel">
<int-http:cross-origin/>
</int-http:inbound-channel-adapter>
<int-http:inbound-channel-adapter path="/default" supported-methods="GET" channel="nullChannel">
<int-http:request-mapping params="q"/>
<int-http:cross-origin/>
</int-http:inbound-channel-adapter>
<int-http:inbound-channel-adapter path="/ambiguous-header" supported-methods="GET" channel="nullChannel">
<int-http:request-mapping headers="header1=a, header2=foo"/>
<int-http:cross-origin/>
</int-http:inbound-channel-adapter>
<int-http:inbound-channel-adapter path="/ambiguous-header" supported-methods="GET" channel="nullChannel">
<int-http:request-mapping headers="header1=b"/>
<int-http:cross-origin/>
</int-http:inbound-channel-adapter>
<int-http:inbound-channel-adapter path="/customized" supported-methods="GET,POST" channel="nullChannel">
<int-http:cross-origin origin="http://site1.com,http://site2.com"
allowed-headers="header1, header2"
exposed-headers="header3, header4"
method="DELETE"
max-age="123"
allow-credentials="false"/>
</int-http:inbound-channel-adapter>
<int-http:inbound-gateway path="/ambiguous-produces" supported-methods="GET" request-channel="nullChannel">
<int-http:request-mapping produces="application/xml"/>
<int-http:cross-origin/>
</int-http:inbound-gateway>
<int-http:inbound-gateway path="/ambiguous-produces" supported-methods="GET" request-channel="nullChannel">
<int-http:request-mapping produces="application/json"/>
<int-http:cross-origin/>
</int-http:inbound-gateway>
</beans:beans>

View File

@@ -0,0 +1,190 @@
/*
* Copyright 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.
* 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 static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.integration.test.util.TestUtils;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.servlet.HandlerExecutionChain;
import org.springframework.web.servlet.HandlerInterceptor;
/**
* @author Artem Bilan
* @since 4.2
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
@DirtiesContext
public class CrossOriginTests {
@Autowired
private IntegrationRequestMappingHandlerMapping handlerMapping;
private MockHttpServletRequest request;
@Before
public void setUp() {
this.request = new MockHttpServletRequest();
this.request.setMethod("GET");
this.request.addHeader(HttpHeaders.ORIGIN, "http://domain.com/");
}
@Test
public void noEndpointWithoutOriginHeader() throws Exception {
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/no");
HandlerExecutionChain chain = this.handlerMapping.getHandler(request);
CorsConfiguration config = getCorsConfiguration(chain, false);
assertNull(config);
}
@Test
public void noEndpointWithOriginHeader() throws Exception {
this.request.setRequestURI("/no");
HandlerExecutionChain chain = this.handlerMapping.getHandler(request);
CorsConfiguration config = getCorsConfiguration(chain, false);
assertNull(config);
}
@Test
public void noEndpointPostWithOriginHeader() throws Exception {
this.request.setMethod("POST");
this.request.setRequestURI("/no");
HandlerExecutionChain chain = this.handlerMapping.getHandler(this.request);
CorsConfiguration config = getCorsConfiguration(chain, false);
assertNull(config);
}
@Test
public void defaultEndpointWithCrossOrigin() throws Exception {
this.request.setRequestURI("/default");
HandlerExecutionChain chain = this.handlerMapping.getHandler(this.request);
CorsConfiguration config = getCorsConfiguration(chain, false);
assertNotNull(config);
assertArrayEquals(new String[]{"GET"}, config.getAllowedMethods().toArray());
assertArrayEquals(new String[]{"*"}, config.getAllowedOrigins().toArray());
assertTrue(config.getAllowCredentials());
assertArrayEquals(new String[]{"*"}, config.getAllowedHeaders().toArray());
assertNull(config.getExposedHeaders());
assertEquals(new Long(1800), config.getMaxAge());
}
@Test
public void customized() throws Exception {
this.request.setRequestURI("/customized");
HandlerExecutionChain chain = this.handlerMapping.getHandler(this.request);
CorsConfiguration config = getCorsConfiguration(chain, false);
assertNotNull(config);
assertArrayEquals(new String[]{"DELETE"}, config.getAllowedMethods().toArray());
assertArrayEquals(new String[]{"http://site1.com", "http://site2.com"}, config.getAllowedOrigins().toArray());
assertArrayEquals(new String[]{"header1", "header2"}, config.getAllowedHeaders().toArray());
assertArrayEquals(new String[]{"header3", "header4"}, config.getExposedHeaders().toArray());
assertEquals(new Long(123), config.getMaxAge());
assertEquals(false, config.getAllowCredentials());
}
@Test
public void preFlightRequest() throws Exception {
this.request.setMethod("OPTIONS");
this.request.addHeader(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "GET");
this.request.setRequestURI("/default");
HandlerExecutionChain chain = this.handlerMapping.getHandler(this.request);
CorsConfiguration config = getCorsConfiguration(chain, true);
assertNotNull(config);
assertArrayEquals(new String[]{"GET"}, config.getAllowedMethods().toArray());
assertArrayEquals(new String[]{"*"}, config.getAllowedOrigins().toArray());
assertTrue(config.getAllowCredentials());
assertArrayEquals(new String[]{"*"}, config.getAllowedHeaders().toArray());
assertNull(config.getExposedHeaders());
assertEquals(new Long(1800), config.getMaxAge());
}
@Test
public void ambiguousHeaderPreFlightRequest() throws Exception {
this.request.setMethod("OPTIONS");
this.request.addHeader(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "GET");
this.request.addHeader(HttpHeaders.ACCESS_CONTROL_REQUEST_HEADERS, "header1");
this.request.setRequestURI("/ambiguous-header");
HandlerExecutionChain chain = this.handlerMapping.getHandler(this.request);
CorsConfiguration config = getCorsConfiguration(chain, true);
assertNotNull(config);
assertArrayEquals(new String[]{"*"}, config.getAllowedMethods().toArray());
assertArrayEquals(new String[]{"*"}, config.getAllowedOrigins().toArray());
assertArrayEquals(new String[]{"*"}, config.getAllowedHeaders().toArray());
assertTrue(config.getAllowCredentials());
assertNull(config.getExposedHeaders());
assertNull(config.getMaxAge());
}
@Test
public void ambiguousProducesPreFlightRequest() throws Exception {
this.request.setMethod("OPTIONS");
this.request.addHeader(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "GET");
this.request.setRequestURI("/ambiguous-produces");
HandlerExecutionChain chain = this.handlerMapping.getHandler(this.request);
CorsConfiguration config = getCorsConfiguration(chain, true);
assertNotNull(config);
assertArrayEquals(new String[]{"*"}, config.getAllowedMethods().toArray());
assertArrayEquals(new String[]{"*"}, config.getAllowedOrigins().toArray());
assertArrayEquals(new String[]{"*"}, config.getAllowedHeaders().toArray());
assertTrue(config.getAllowCredentials());
assertNull(config.getExposedHeaders());
assertNull(config.getMaxAge());
}
@Test(expected = HttpRequestMethodNotSupportedException.class)
public void preFlightRequestWithoutRequestMethodHeader() throws Exception {
MockHttpServletRequest request = new MockHttpServletRequest("OPTIONS", "/default");
request.addHeader(HttpHeaders.ORIGIN, "http://domain2.com");
this.handlerMapping.getHandler(request);
}
private CorsConfiguration getCorsConfiguration(HandlerExecutionChain chain, boolean isPreFlightRequest) {
if (isPreFlightRequest) {
Object handler = chain.getHandler();
assertTrue(handler.getClass().getSimpleName().equals("PreFlightHandler"));
return TestUtils.getPropertyValue(handler, "config", CorsConfiguration.class);
}
else {
HandlerInterceptor[] interceptors = chain.getInterceptors();
if (interceptors != null) {
for (HandlerInterceptor interceptor : interceptors) {
if (interceptor.getClass().getSimpleName().equals("CorsInterceptor")) {
return TestUtils.getPropertyValue(interceptor, "config", CorsConfiguration.class);
}
}
}
}
return null;
}
}

View File

@@ -156,6 +156,8 @@ Of course, this can be an abstract class, or even an interface (such as `java.io
[[http-namespace]]
=== HTTP Namespace Support
==== Introduction
Spring Integration provides an _http_ namespace and the corresponding schema definition.
To include it in your configuration, simply provide the following namespace declaration in your application context configuration file:
@@ -177,7 +179,7 @@ To include it in your configuration, simply provide the following namespace decl
</beans>
----
_Inbound_
==== Inbound
The XML Namespace provides two components for handling HTTP Inbound requests.
In order to process requests without returning a dedicated response, use the _inbound-channel-adapter_:
@@ -197,7 +199,7 @@ To process requests that do expect a response, use an _inbound-gateway_:
reply-channel="responses"/>
----
_Request Mapping support_
==== Request Mapping Support
NOTE: _Spring Integration 3.0_ is improving the REST support by introducing the http://static.springsource.org/spring-integration/api/org/springframework/integration/http/inbound/IntegrationRequestMappingHandlerMapping.html[IntegrationRequestMappingHandlerMapping].
The implementation relies on the enhanced REST support provided by Spring Framework 3.1 or higher.
@@ -265,9 +267,48 @@ This allows you to get the `Message` into the flow as early as possibly, e.g.:
For more information regarding _Handler Mappings_, please see: http://static.springsource.org/spring/docs/current/spring-framework-reference/html/mvc.html#mvc-handlermapping[Handler Mappings].
[[cors]]
==== Cross-Origin Resource Sharing (CORS) Support
Starting with _version 4.2_ the `<http:inbound-channel-adapter>` and `<http:inbound-gateway>` can be configured with
a `<cross-origin>` sub-element.
It represents the same options as Spring MVC's `@CrossOrigin` for `@Controller` methods
and allows the configuration of Cross-origin resource sharing (CORS) for Spring Integration HTTP endpoints:
_Response StatusCode_
* `origin` - List of allowed origins.
`*` means that all origins are allowed.
These values are placed in the `Access-Control-Allow-Origin` header of both the pre-flight
and actual responses.
Default value is `*`.
* `allowed-headers` - Indicates which request headers can be used during the actual request.
`*` means that all headers asked by the client are allowed.
This property controls the value of the pre-flight response's `Access-Control-Allow-Headers` header.
Default value is `*`.
* `exposed-headers` - List of response headers that the user-agent will allow the client to access.
This property controls the value of the actual response's `Access-Control-Expose-Headers` header.
* `method` - The HTTP request methods to allow: GET, POST, HEAD, OPTIONS, PUT, PATCH, DELETE, TRACE.
Methods specified here overrides those in `supported-methods`.
* `allow-credentials` - Set to `true` if the the browser should include any cookies associated to the domain
of the request, or `false` if it should not.
Empty string "" means undefined.
If `true`, the pre-flight response will include the header `Access-Control-Allow-Credentials=true`.
Default value is `true`.
* `max-age` - Controls the cache duration for pre-flight responses.
Setting this to a reasonable value can reduce the number of pre-flight request/response interactions required by
the browser.
This property controls the value of the `Access-Control-Max-Age` header in the pre-flight response.
A value of `-1` means undefined.
Default value is 1800 seconds, or 30 minutes.
The CORS Java Configuration is represented by the `org.springframework.integration.http.inbound.CrossOrigin` class,
instances of which can be injected to the `HttpRequestHandlingEndpointSupport` beans.
==== Response StatusCode
Starting with _version 4.1_ the `<http:inbound-channel-adapter>` can be configured with a `status-code-expression` to override the default `200 OK` status.
The expression must return an object which can be converted to an `org.springframework.http.HttpStatus` enum value.
@@ -285,7 +326,7 @@ By default, `status-code-expression` is null meaning that the normal '200 OK' re
The `<http:inbound-gateway>` resolves the 'status code' from the `http_statusCode` header of the reply Message.
_URI Template Variables and Expressions_
==== URI Template Variables and Expressions
By Using the _path_ attribute in conjunction with the _payload-expression_ attribute as well as the _header_ sub-element, you have a high degree of flexibility for mapping inbound request data.
@@ -334,7 +375,7 @@ Note, all these values (and others) can be accessed within expressions in the do
----
_Outbound_
==== Outbound
To configure the outbound gateway you can use the namespace support as well.
The following code snippet shows the different configuration options for an outbound Http gateway.
@@ -417,7 +458,7 @@ Changes in Spring 3.1 can cause some issues with escaped characters, such as '?'
For this reason, it is recommended that if you wish to generate the URL entirely at runtime, you use the 'url-expression' attribute.
=====
_Mapping URI Variables_
==== Mapping URI Variables
If your URL contains URI variables, you can map them using the `uri-variable` sub-element.
This sub-element is available for the _Http Outbound Gateway_ and the _Http Outbound Channel Adapter_.
@@ -475,7 +516,7 @@ NOTE: The `uri-variables-expression` must evaluate to a `Map`.
The values of the Map must be instances of `String` or `Expression`.
This Map is provided to an `ExpressionEvalMap` for further resolution of URI variable placeholders using those expressions in the context of the outbound `Message`.
_Controlling URI Encoding_
==== Controlling URI Encoding
By default, the URL string is encoded (see http://static.springsource.org/spring/docs/current/javadoc-api/org/springframework/web/util/UriComponentsBuilder.html[UriComponentsBuilder]) to the URI object before sending the request.
In some scenarios with a non-standard URI (e.g.

View File

@@ -116,3 +116,11 @@ for the internal `javax.xml.transform.Transformer` and supports an `Iterator` mo
evaluation `org.w3c.dom.NodeList` result.
See <<xml-xpath-splitting>> for more information.
[[x4.2-http-changes]]
==== HTTP changes
The HTTP Inbound Endpoints (`<int-http:inbound-channel-adapter>` and `<int-http:inbound-gateway>`) now allow the
configuration of _Cross-Origin Resource Sharing (CORS)_.
See <<cors>> for more information.