From a37256146d47af79cda384cfe848fa9e05254004 Mon Sep 17 00:00:00 2001 From: Gary Russell Date: Tue, 11 Sep 2012 16:20:09 -0400 Subject: [PATCH] INT-2744 Mechanism to Suppress Standard Headers Currently, HTTP mappers don't know if they are inbound or outbound. This causes problems with mapping certain headers. For example, on an inbound adapter, a mapped Content-Length header should not be re-mapped to an HTTP Header during response header mapping. However, the Content-Length does need to be mapped on an outbound adapter response. Introduce the notion of an exclusion list for both request and response standard headers. If a mapper has been configured to use standard headers, we can detect whether the mapper is being used for inbound or outbound mapping. We can then apply an 'exclusion' for certain headers. Currently, no request headers are suppressed on outbound mappers and only the 'Content-Length' response header is suppressed on inbound mappers. The default mappers created via the static factory methods use these defaults. Exposing the exclusion setters will allow users to suppress additional headers, if deemed necessary. --- .../http/support/DefaultHttpHeaderMapper.java | 59 ++++++++++++++++++- ...tpHeaderMapperFromMessageInboundTests.java | 13 ++++ ...pHeaderMapperFromMessageOutboundTests.java | 13 +++- 3 files changed, 83 insertions(+), 2 deletions(-) diff --git a/spring-integration-http/src/main/java/org/springframework/integration/http/support/DefaultHttpHeaderMapper.java b/spring-integration-http/src/main/java/org/springframework/integration/http/support/DefaultHttpHeaderMapper.java index c703635e76..c926a689d0 100644 --- a/spring-integration-http/src/main/java/org/springframework/integration/http/support/DefaultHttpHeaderMapper.java +++ b/spring-integration-http/src/main/java/org/springframework/integration/http/support/DefaultHttpHeaderMapper.java @@ -32,7 +32,6 @@ import java.util.Set; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; - import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactoryAware; @@ -45,6 +44,7 @@ import org.springframework.http.MediaType; import org.springframework.integration.MessageHeaders; import org.springframework.integration.context.IntegrationContextUtils; import org.springframework.integration.mapping.HeaderMapper; +import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; import org.springframework.util.ObjectUtils; import org.springframework.util.PatternMatchUtils; @@ -57,6 +57,7 @@ import org.springframework.util.StringUtils; * @author Jeremy Grelle * @author Oleg Zhurakousky * @author Gunnar Hillert + * @author Gary Russell * @since 2.0 */ public class DefaultHttpHeaderMapper implements HeaderMapper, BeanFactoryAware, InitializingBean{ @@ -228,6 +229,12 @@ public class DefaultHttpHeaderMapper implements HeaderMapper, BeanF WWW_AUTHENTICATE }; + private static String[] HTTP_REQUEST_HEADER_NAMES_OUTBOUND_EXCLUSIONS = new String[0]; + + private static String[] HTTP_RESPONSE_HEADER_NAMES_INBOUND_EXCLUSIONS = new String[] { + CONTENT_LENGTH + }; + public static final String HTTP_REQUEST_HEADER_NAME_PATTERN = "HTTP_REQUEST_HEADERS"; public static final String HTTP_RESPONSE_HEADER_NAME_PATTERN = "HTTP_RESPONSE_HEADERS"; @@ -237,6 +244,10 @@ public class DefaultHttpHeaderMapper implements HeaderMapper, BeanF private volatile String[] inboundHeaderNames = new String[0]; + private volatile String[] excludedOutboundStandardRequestHeaderNames = new String[0]; + + private volatile String[] excludedInboundStandardResponseHeaderNames = new String[0]; + private volatile String userDefinedHeaderPrefix = "X-"; public void setBeanFactory(BeanFactory beanFactory) throws BeansException { @@ -268,6 +279,26 @@ public class DefaultHttpHeaderMapper implements HeaderMapper, BeanF this.inboundHeaderNames = (inboundHeaderNames != null) ? inboundHeaderNames : new String[0]; } + /** + * Provide header names from the list of standard headers that should be suppressed when + * mapping outbound endpoint request headers. + * @param excludedOutboundStandardRequestHeaderNames the excludedStandardRequestHeaderNames to set + */ + public void setExcludedOutboundStandardRequestHeaderNames(String[] excludedOutboundStandardRequestHeaderNames) { + Assert.notNull(excludedOutboundStandardRequestHeaderNames, "'excludedOutboundStandardRequestHeaderNames' must not be null"); + this.excludedOutboundStandardRequestHeaderNames = excludedOutboundStandardRequestHeaderNames; + } + + /** + * Provide header names from the list of standard headers that should be suppressed when + * mapping inbound endopoint response headers. + * @param excludedInboundStandardResponseHeaderNames the excludedStandardResponseHeaderNames to set + */ + public void setExcludedInboundStandardResponseHeaderNames(String[] excludedInboundStandardResponseHeaderNames) { + Assert.notNull(excludedInboundStandardResponseHeaderNames, "'excludedInboundStandardResponseHeaderNames' must not be null"); + this.excludedInboundStandardResponseHeaderNames = excludedInboundStandardResponseHeaderNames; + } + /** * Sets the prefix to use with user-defined (non-standard) headers. Default is 'X-'. */ @@ -359,6 +390,30 @@ public class DefaultHttpHeaderMapper implements HeaderMapper, BeanF } private boolean shouldMapOutboundHeader(String headerName) { + if (this.outboundHeaderNames == HTTP_RESPONSE_HEADER_NAMES) { // a default inbound mapper + /* + * When using the default response header name list, suppress the + * mapping of exclusions for specific headers. + */ + if (this.containsElementIgnoreCase(this.excludedInboundStandardResponseHeaderNames, headerName)) { + if (logger.isDebugEnabled()) { + logger.debug(MessageFormat.format("headerName=[{0}] WILL NOT be mapped", headerName)); + } + return false; + } + } + else if (this.outboundHeaderNames == HTTP_REQUEST_HEADER_NAMES) { // a default outbound mapper + /* + * When using the default request header name list, suppress the + * mapping of exclusions for specific headers. + */ + if (this.containsElementIgnoreCase(this.excludedOutboundStandardRequestHeaderNames, headerName)) { + if (logger.isDebugEnabled()) { + logger.debug(MessageFormat.format("headerName=[{0}] WILL NOT be mapped", headerName)); + } + return false; + } + } return this.shouldMapHeader(headerName, this.outboundHeaderNames); } @@ -841,6 +896,7 @@ public class DefaultHttpHeaderMapper implements HeaderMapper, BeanF DefaultHttpHeaderMapper mapper = new DefaultHttpHeaderMapper(); mapper.setOutboundHeaderNames(HTTP_REQUEST_HEADER_NAMES); mapper.setInboundHeaderNames(HTTP_RESPONSE_HEADER_NAMES); + mapper.setExcludedOutboundStandardRequestHeaderNames(HTTP_REQUEST_HEADER_NAMES_OUTBOUND_EXCLUSIONS); return mapper; } @@ -853,6 +909,7 @@ public class DefaultHttpHeaderMapper implements HeaderMapper, BeanF DefaultHttpHeaderMapper mapper = new DefaultHttpHeaderMapper(); mapper.setInboundHeaderNames(HTTP_REQUEST_HEADER_NAMES); mapper.setOutboundHeaderNames(HTTP_RESPONSE_HEADER_NAMES); + mapper.setExcludedInboundStandardResponseHeaderNames(HTTP_RESPONSE_HEADER_NAMES_INBOUND_EXCLUSIONS); return mapper; } } diff --git a/spring-integration-http/src/test/java/org/springframework/integration/http/support/DefaultHttpHeaderMapperFromMessageInboundTests.java b/spring-integration-http/src/test/java/org/springframework/integration/http/support/DefaultHttpHeaderMapperFromMessageInboundTests.java index 11b8ae08c2..7543afe5d7 100644 --- a/spring-integration-http/src/test/java/org/springframework/integration/http/support/DefaultHttpHeaderMapperFromMessageInboundTests.java +++ b/spring-integration-http/src/test/java/org/springframework/integration/http/support/DefaultHttpHeaderMapperFromMessageInboundTests.java @@ -24,6 +24,7 @@ import static org.junit.Assert.assertNull; import java.net.URI; import java.text.ParseException; import java.text.SimpleDateFormat; +import java.util.Arrays; import java.util.Collections; import java.util.Date; import java.util.HashMap; @@ -543,6 +544,18 @@ public class DefaultHttpHeaderMapperFromMessageInboundTests { assertEquals("TestClass.class", headers.get("X-customHeaderB").get(0)); } + @Test + public void dontPropagateContentLength() { + HeaderMapper mapper = DefaultHttpHeaderMapper.inboundMapper(); + HttpHeaders headers = new HttpHeaders(); + // suppressed in response on inbound, by default + headers.put("Content-Length", Arrays.asList(new String[] {"3"})); + Map messageHeaders = mapper.toHeaders(headers); + headers = new HttpHeaders(); + mapper.fromHeaders(new MessageHeaders(messageHeaders), headers); + assertNull(headers.get("Content-Length")); + } + public static class TestClass { } diff --git a/spring-integration-http/src/test/java/org/springframework/integration/http/support/DefaultHttpHeaderMapperFromMessageOutboundTests.java b/spring-integration-http/src/test/java/org/springframework/integration/http/support/DefaultHttpHeaderMapperFromMessageOutboundTests.java index 2283866211..68194f8b78 100644 --- a/spring-integration-http/src/test/java/org/springframework/integration/http/support/DefaultHttpHeaderMapperFromMessageOutboundTests.java +++ b/spring-integration-http/src/test/java/org/springframework/integration/http/support/DefaultHttpHeaderMapperFromMessageOutboundTests.java @@ -32,7 +32,6 @@ import java.util.Locale; import java.util.Map; import org.junit.Test; - import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.integration.MessageHeaders; @@ -646,4 +645,16 @@ public class DefaultHttpHeaderMapperFromMessageOutboundTests { assertEquals(1, headers.get("X-baz").size()); assertEquals(1, headers.get("x-baz").size()); } + @Test + public void dontPropagateContentLength() { + DefaultHttpHeaderMapper mapper = DefaultHttpHeaderMapper.outboundMapper(); + // not suppressed on outbound request, by default + mapper.setExcludedOutboundStandardRequestHeaderNames(new String[] {"Content-Length"}); + Map messageHeaders = new HashMap(); + messageHeaders.put("Content-Length", 4); + + HttpHeaders headers = new HttpHeaders(); + mapper.fromHeaders(new MessageHeaders(messageHeaders), headers); + assertNull(headers.get("Content-Length")); + } }