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.
This commit is contained in:
Gary Russell
2012-09-11 16:20:09 -04:00
committed by Gunnar Hillert
parent 091e3e3456
commit a37256146d
3 changed files with 83 additions and 2 deletions

View File

@@ -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<HttpHeaders>, BeanFactoryAware, InitializingBean{
@@ -228,6 +229,12 @@ public class DefaultHttpHeaderMapper implements HeaderMapper<HttpHeaders>, 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<HttpHeaders>, 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<HttpHeaders>, 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<HttpHeaders>, 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<HttpHeaders>, 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<HttpHeaders>, 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;
}
}

View File

@@ -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<HttpHeaders> mapper = DefaultHttpHeaderMapper.inboundMapper();
HttpHeaders headers = new HttpHeaders();
// suppressed in response on inbound, by default
headers.put("Content-Length", Arrays.asList(new String[] {"3"}));
Map<String, Object> messageHeaders = mapper.toHeaders(headers);
headers = new HttpHeaders();
mapper.fromHeaders(new MessageHeaders(messageHeaders), headers);
assertNull(headers.get("Content-Length"));
}
public static class TestClass {
}

View File

@@ -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<String, Object> messageHeaders = new HashMap<String, Object>();
messageHeaders.put("Content-Length", 4);
HttpHeaders headers = new HttpHeaders();
mapper.fromHeaders(new MessageHeaders(messageHeaders), headers);
assertNull(headers.get("Content-Length"));
}
}