Java tutorial
/* * Copyright 2002-2019 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 * * https://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.support; import java.net.URI; import java.net.URISyntaxException; import java.nio.charset.Charset; import java.text.MessageFormat; import java.time.ZoneId; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; import java.time.format.DateTimeParseException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Map.Entry; 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; import org.springframework.beans.factory.InitializingBean; import org.springframework.core.convert.ConversionService; import org.springframework.core.convert.TypeDescriptor; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.MediaType; import org.springframework.integration.mapping.HeaderMapper; import org.springframework.integration.support.utils.IntegrationUtils; import org.springframework.lang.Nullable; import org.springframework.messaging.MessageHeaders; import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; import org.springframework.util.MimeType; import org.springframework.util.ObjectUtils; import org.springframework.util.PatternMatchUtils; import org.springframework.util.StringUtils; /** * Default {@link HeaderMapper} implementation for HTTP. * * @author Mark Fisher * @author Jeremy Grelle * @author Oleg Zhurakousky * @author Gunnar Hillert * @author Gary Russell * @author Artem Bilan * * @since 2.0 */ public class DefaultHttpHeaderMapper implements HeaderMapper<HttpHeaders>, BeanFactoryAware, InitializingBean { private static final String UNUSED = "unused"; protected final Log logger = LogFactory.getLog(getClass()); // NOSONAR - final /** * @deprecated since 5.2 in favor of {@link HttpHeaders#ACCEPT} */ @Deprecated public static final String ACCEPT = HttpHeaders.ACCEPT; /** * @deprecated since 5.2 in favor of {@link HttpHeaders#ACCEPT_CHARSET} */ @Deprecated public static final String ACCEPT_CHARSET = HttpHeaders.ACCEPT_CHARSET; /** * @deprecated since 5.2 in favor of {@link HttpHeaders#ACCEPT_ENCODING} */ @Deprecated public static final String ACCEPT_ENCODING = HttpHeaders.ACCEPT_ENCODING; /** * @deprecated since 5.2 in favor of {@link HttpHeaders#ACCEPT_LANGUAGE} */ @Deprecated public static final String ACCEPT_LANGUAGE = HttpHeaders.ACCEPT_LANGUAGE; /** * @deprecated since 5.2 in favor of {@link HttpHeaders#ACCEPT_RANGES} */ @Deprecated public static final String ACCEPT_RANGES = HttpHeaders.ACCEPT_RANGES; /** * @deprecated since 5.2 in favor of {@link HttpHeaders#AGE} */ @Deprecated public static final String AGE = HttpHeaders.AGE; /** * @deprecated since 5.2 in favor of {@link HttpHeaders#ALLOW} */ @Deprecated public static final String ALLOW = HttpHeaders.ALLOW; /** * @deprecated since 5.2 in favor of {@link HttpHeaders#AUTHORIZATION} */ @Deprecated public static final String AUTHORIZATION = HttpHeaders.AUTHORIZATION; /** * @deprecated since 5.2 in favor of {@link HttpHeaders#CACHE_CONTROL} */ @Deprecated public static final String CACHE_CONTROL = HttpHeaders.CACHE_CONTROL; /** * @deprecated since 5.2 in favor of {@link HttpHeaders#CONNECTION} */ @Deprecated public static final String CONNECTION = HttpHeaders.CONNECTION; /** * @deprecated since 5.2 in favor of {@link HttpHeaders#CONTENT_ENCODING} */ @Deprecated public static final String CONTENT_ENCODING = HttpHeaders.CONTENT_ENCODING; /** * @deprecated since 5.2 in favor of {@link HttpHeaders#CONTENT_LANGUAGE} */ @Deprecated public static final String CONTENT_LANGUAGE = HttpHeaders.CONTENT_LANGUAGE; /** * @deprecated since 5.2 in favor of {@link HttpHeaders#CONTENT_LENGTH} */ @Deprecated public static final String CONTENT_LENGTH = HttpHeaders.CONTENT_LENGTH; /** * @deprecated since 5.2 in favor of {@link HttpHeaders#CONTENT_LOCATION} */ @Deprecated public static final String CONTENT_LOCATION = HttpHeaders.CONTENT_LOCATION; /** * @deprecated since 5.2 in favor of {@link HttpHeaders#CONTENT_RANGE} */ @Deprecated public static final String CONTENT_RANGE = HttpHeaders.CONTENT_RANGE; /** * @deprecated since 5.2 in favor of {@link HttpHeaders#CONTENT_TYPE} */ @Deprecated public static final String CONTENT_TYPE = HttpHeaders.CONTENT_TYPE; /** * @deprecated since 5.2 in favor of {@link HttpHeaders#CONTENT_DISPOSITION} */ @Deprecated public static final String CONTENT_DISPOSITION = HttpHeaders.CONTENT_DISPOSITION; /** * @deprecated since 5.2 in favor of {@link HttpHeaders#COOKIE} */ @Deprecated public static final String COOKIE = HttpHeaders.COOKIE; /** * @deprecated since 5.2 in favor of {@link HttpHeaders#DATE} */ @Deprecated public static final String DATE = HttpHeaders.DATE; /** * @deprecated since 5.2 in favor of {@link HttpHeaders#ETAG} */ @Deprecated public static final String ETAG = HttpHeaders.ETAG; /** * @deprecated since 5.2 in favor of {@link HttpHeaders#EXPECT} */ @Deprecated public static final String EXPECT = HttpHeaders.EXPECT; /** * @deprecated since 5.2 in favor of {@link HttpHeaders#EXPIRES} */ @Deprecated public static final String EXPIRES = HttpHeaders.EXPIRES; /** * @deprecated since 5.2 in favor of {@link HttpHeaders#FROM} */ @Deprecated public static final String FROM = HttpHeaders.FROM; /** * @deprecated since 5.2 in favor of {@link HttpHeaders#HOST} */ @Deprecated public static final String HOST = HttpHeaders.HOST; /** * @deprecated since 5.2 in favor of {@link HttpHeaders#IF_MATCH} */ @Deprecated public static final String IF_MATCH = HttpHeaders.IF_MATCH; /** * @deprecated since 5.2 in favor of {@link HttpHeaders#IF_MODIFIED_SINCE} */ @Deprecated public static final String IF_MODIFIED_SINCE = HttpHeaders.IF_MODIFIED_SINCE; /** * @deprecated since 5.2 in favor of {@link HttpHeaders#IF_NONE_MATCH} */ @Deprecated public static final String IF_NONE_MATCH = HttpHeaders.IF_NONE_MATCH; /** * @deprecated since 5.2 in favor of {@link HttpHeaders#IF_RANGE} */ @Deprecated public static final String IF_RANGE = HttpHeaders.IF_RANGE; /** * @deprecated since 5.2 in favor of {@link HttpHeaders#IF_UNMODIFIED_SINCE} */ @Deprecated public static final String IF_UNMODIFIED_SINCE = HttpHeaders.IF_UNMODIFIED_SINCE; /** * @deprecated since 5.2 in favor of {@link HttpHeaders#LAST_MODIFIED} */ @Deprecated public static final String LAST_MODIFIED = HttpHeaders.LAST_MODIFIED; /** * @deprecated since 5.2 in favor of {@link HttpHeaders#LOCATION} */ @Deprecated public static final String LOCATION = HttpHeaders.LOCATION; /** * @deprecated since 5.2 in favor of {@link HttpHeaders#MAX_FORWARDS} */ @Deprecated public static final String MAX_FORWARDS = HttpHeaders.MAX_FORWARDS; /** * @deprecated since 5.2 in favor of {@link HttpHeaders#PRAGMA} */ @Deprecated public static final String PRAGMA = HttpHeaders.PRAGMA; /** * @deprecated since 5.2 in favor of {@link HttpHeaders#PROXY_AUTHENTICATE} */ @Deprecated public static final String PROXY_AUTHENTICATE = HttpHeaders.PROXY_AUTHENTICATE; /** * @deprecated since 5.2 in favor of {@link HttpHeaders#PROXY_AUTHORIZATION} */ @Deprecated public static final String PROXY_AUTHORIZATION = HttpHeaders.PROXY_AUTHORIZATION; /** * @deprecated since 5.2 in favor of {@link HttpHeaders#RANGE} */ @Deprecated public static final String RANGE = HttpHeaders.RANGE; /** * @deprecated since 5.2 in favor of {@link HttpHeaders#REFERER} */ @Deprecated public static final String REFERER = HttpHeaders.REFERER; /** * @deprecated since 5.2 in favor of {@link HttpHeaders#RETRY_AFTER} */ @Deprecated public static final String RETRY_AFTER = HttpHeaders.RETRY_AFTER; /** * @deprecated since 5.2 in favor of {@link HttpHeaders#SERVER} */ @Deprecated public static final String SERVER = HttpHeaders.SERVER; /** * @deprecated since 5.2 in favor of {@link HttpHeaders#SET_COOKIE} */ @Deprecated public static final String SET_COOKIE = HttpHeaders.SET_COOKIE; /** * @deprecated since 5.2 in favor of {@link HttpHeaders#TE} */ @Deprecated public static final String TE = HttpHeaders.TE; /** * @deprecated since 5.2 in favor of {@link HttpHeaders#TRAILER} */ @Deprecated public static final String TRAILER = HttpHeaders.TRAILER; /** * @deprecated since 5.2 in favor of {@link HttpHeaders#UPGRADE} */ @Deprecated public static final String UPGRADE = HttpHeaders.UPGRADE; /** * @deprecated since 5.2 in favor of {@link HttpHeaders#USER_AGENT} */ @Deprecated public static final String USER_AGENT = HttpHeaders.USER_AGENT; /** * @deprecated since 5.2 in favor of {@link HttpHeaders#VARY} */ @Deprecated public static final String VARY = HttpHeaders.VARY; /** * @deprecated since 5.2 in favor of {@link HttpHeaders#VIA} */ @Deprecated public static final String VIA = HttpHeaders.VIA; /** * @deprecated since 5.2 in favor of {@link HttpHeaders#WARNING} */ @Deprecated public static final String WARNING = HttpHeaders.WARNING; /** * @deprecated since 5.2 in favor of {@link HttpHeaders#WWW_AUTHENTICATE} */ @Deprecated public static final String WWW_AUTHENTICATE = HttpHeaders.WWW_AUTHENTICATE; /** * @deprecated since 5.2 in favor of {@link HttpHeaders#TRANSFER_ENCODING} */ @Deprecated public static final String TRANSFER_ENCODING = HttpHeaders.TRANSFER_ENCODING; public static final String CONTENT_MD5 = "Content-MD5"; public static final String REFRESH = "Refresh"; private static final String ACCEPT_LOWER = "accept"; private static final String ACCEPT_CHARSET_LOWER = "accept-charset"; private static final String ALLOW_LOWER = "allow"; private static final String CACHE_CONTROL_LOWER = "cache-control"; private static final String CONTENT_LENGTH_LOWER = "content-length"; private static final String CONTENT_TYPE_LOWER = "content-type"; private static final String DATE_LOWER = "date"; private static final String ETAG_LOWER = "etag"; private static final String EXPIRES_LOWER = "expires"; private static final String IF_MODIFIED_SINCE_LOWER = "if-modified-since"; private static final String IF_NONE_MATCH_LOWER = "if-none-match"; private static final String IF_UNMODIFIED_SINCE_LOWER = "if-unmodified-since"; private static final String LAST_MODIFIED_LOWER = "last-modified"; private static final String LOCATION_LOWER = "location"; private static final String PRAGMA_LOWER = "pragma"; private static final String[] HTTP_REQUEST_HEADER_NAMES = { HttpHeaders.ACCEPT, HttpHeaders.ACCEPT_CHARSET, HttpHeaders.ACCEPT_ENCODING, HttpHeaders.ACCEPT_LANGUAGE, HttpHeaders.ACCEPT_RANGES, HttpHeaders.AUTHORIZATION, HttpHeaders.CACHE_CONTROL, HttpHeaders.CONNECTION, HttpHeaders.CONTENT_LENGTH, HttpHeaders.CONTENT_TYPE, HttpHeaders.COOKIE, HttpHeaders.DATE, HttpHeaders.EXPECT, HttpHeaders.FROM, HttpHeaders.HOST, HttpHeaders.IF_MATCH, HttpHeaders.IF_MODIFIED_SINCE, HttpHeaders.IF_NONE_MATCH, HttpHeaders.IF_RANGE, HttpHeaders.IF_UNMODIFIED_SINCE, HttpHeaders.MAX_FORWARDS, HttpHeaders.PRAGMA, HttpHeaders.PROXY_AUTHORIZATION, HttpHeaders.RANGE, HttpHeaders.REFERER, HttpHeaders.TE, HttpHeaders.UPGRADE, HttpHeaders.USER_AGENT, HttpHeaders.VIA, HttpHeaders.WARNING }; private static final Set<String> HTTP_REQUEST_HEADER_NAMES_LOWER = new HashSet<>(); private static final String[] HTTP_RESPONSE_HEADER_NAMES = { HttpHeaders.ACCEPT_RANGES, HttpHeaders.AGE, HttpHeaders.ALLOW, HttpHeaders.CACHE_CONTROL, HttpHeaders.CONNECTION, HttpHeaders.CONTENT_ENCODING, HttpHeaders.CONTENT_LANGUAGE, HttpHeaders.CONTENT_LENGTH, HttpHeaders.CONTENT_LOCATION, CONTENT_MD5, HttpHeaders.CONTENT_RANGE, HttpHeaders.CONTENT_TYPE, HttpHeaders.CONTENT_DISPOSITION, HttpHeaders.TRANSFER_ENCODING, HttpHeaders.DATE, HttpHeaders.ETAG, HttpHeaders.EXPIRES, HttpHeaders.LAST_MODIFIED, HttpHeaders.LOCATION, HttpHeaders.PRAGMA, HttpHeaders.PROXY_AUTHENTICATE, REFRESH, HttpHeaders.RETRY_AFTER, HttpHeaders.SERVER, HttpHeaders.SET_COOKIE, HttpHeaders.TRAILER, HttpHeaders.VARY, HttpHeaders.VIA, HttpHeaders.WARNING, HttpHeaders.WWW_AUTHENTICATE }; private static final Set<String> HTTP_RESPONSE_HEADER_NAMES_LOWER = new HashSet<>(); private static final String[] HTTP_REQUEST_HEADER_NAMES_OUTBOUND_EXCLUSIONS = {}; private static final String[] HTTP_RESPONSE_HEADER_NAMES_INBOUND_EXCLUSIONS = { HttpHeaders.CONTENT_LENGTH, HttpHeaders.TRANSFER_ENCODING }; public static final String HTTP_REQUEST_HEADER_NAME_PATTERN = "HTTP_REQUEST_HEADERS"; public static final String HTTP_RESPONSE_HEADER_NAME_PATTERN = "HTTP_RESPONSE_HEADERS"; // Copy of 'org.springframework.http.HttpHeaders#DATE_FORMATS' protected static final DateTimeFormatter[] DATE_FORMATS = { DateTimeFormatter.RFC_1123_DATE_TIME, DateTimeFormatter.ofPattern("EEEE, dd-MMM-yy HH:mm:ss zz", Locale.US), DateTimeFormatter.ofPattern("EEE MMM dd HH:mm:ss yyyy", Locale.US).withZone(ZoneId.of("GMT")) // NOSONAR }; static { for (String header : HTTP_REQUEST_HEADER_NAMES) { HTTP_REQUEST_HEADER_NAMES_LOWER.add(header.toLowerCase()); } for (String header : HTTP_RESPONSE_HEADER_NAMES) { HTTP_RESPONSE_HEADER_NAMES_LOWER.add(header.toLowerCase()); } } private volatile String[] outboundHeaderNames = {}; private volatile String[] outboundHeaderNamesLowerWithContentType = {}; private volatile String[] inboundHeaderNames = {}; private volatile String[] inboundHeaderNamesLower = {}; private volatile String[] excludedOutboundStandardRequestHeaderNames = {}; private volatile String[] excludedInboundStandardResponseHeaderNames = {}; private volatile String userDefinedHeaderPrefix = ""; private volatile boolean isDefaultOutboundMapper; private volatile boolean isDefaultInboundMapper; private volatile ConversionService conversionService; private volatile BeanFactory beanFactory; @Override public void setBeanFactory(BeanFactory beanFactory) throws BeansException { this.beanFactory = beanFactory; } protected BeanFactory getBeanFactory() { return this.beanFactory; } /** * Provide the header names that should be mapped to an HTTP request (for outbound adapters) * or HTTP response (for inbound adapters) from a Spring Integration Message's headers. * The values can also contain simple wildcard patterns (e.g. "foo*" or "*foo") to be matched. * <p> Any non-standard headers will be prefixed with the value specified by * {@link DefaultHttpHeaderMapper#setUserDefinedHeaderPrefix(String)}. The default is 'X-'. * @param outboundHeaderNames The outbound header names. */ public void setOutboundHeaderNames(String... outboundHeaderNames) { if (HTTP_REQUEST_HEADER_NAMES == outboundHeaderNames) { this.isDefaultOutboundMapper = true; } else if (HTTP_RESPONSE_HEADER_NAMES == outboundHeaderNames) { this.isDefaultInboundMapper = true; } this.outboundHeaderNames = outboundHeaderNames != null ? Arrays.copyOf(outboundHeaderNames, outboundHeaderNames.length) : new String[0]; String[] outboundHeaderNamesLower = new String[this.outboundHeaderNames.length]; for (int i = 0; i < this.outboundHeaderNames.length; i++) { if (HTTP_REQUEST_HEADER_NAME_PATTERN.equals(this.outboundHeaderNames[i]) || HTTP_RESPONSE_HEADER_NAME_PATTERN.equals(this.outboundHeaderNames[i])) { outboundHeaderNamesLower[i] = this.outboundHeaderNames[i]; } else { outboundHeaderNamesLower[i] = this.outboundHeaderNames[i].toLowerCase(); } } this.outboundHeaderNamesLowerWithContentType = Arrays.copyOf(outboundHeaderNamesLower, this.outboundHeaderNames.length + 1); this.outboundHeaderNamesLowerWithContentType[this.outboundHeaderNamesLowerWithContentType.length - 1] = MessageHeaders.CONTENT_TYPE.toLowerCase(); } /** * Provide the header names that should be mapped from an HTTP request (for inbound * adapters) or HTTP response (for outbound adapters) to a Spring Integration * Message's headers. The values can also contain simple wildcard patterns (e.g. * "foo*" or "*foo") to be matched. * <p>This will match the header name directly or, for non-standard HTTP headers, it * will match the header name prefixed with the value specified by * {@link DefaultHttpHeaderMapper#setUserDefinedHeaderPrefix(String)}. The default for * that is an empty String. * @param inboundHeaderNamesArg The inbound header names. */ public void setInboundHeaderNames(String... inboundHeaderNamesArg) { this.inboundHeaderNames = inboundHeaderNamesArg != null ? Arrays.copyOf(inboundHeaderNamesArg, inboundHeaderNamesArg.length) : new String[0]; this.inboundHeaderNamesLower = new String[this.inboundHeaderNames.length]; for (int i = 0; i < this.inboundHeaderNames.length; i++) { if (HTTP_REQUEST_HEADER_NAME_PATTERN.equals(this.inboundHeaderNames[i]) || HTTP_RESPONSE_HEADER_NAME_PATTERN.equals(this.inboundHeaderNames[i])) { this.inboundHeaderNamesLower[i] = this.inboundHeaderNames[i]; } else { this.inboundHeaderNamesLower[i] = this.inboundHeaderNames[i].toLowerCase(); } } } /** * 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"); Assert.noNullElements(excludedOutboundStandardRequestHeaderNames, "'excludedOutboundStandardRequestHeaderNames' must not have null elements"); this.excludedOutboundStandardRequestHeaderNames = Arrays.copyOf(excludedOutboundStandardRequestHeaderNames, excludedOutboundStandardRequestHeaderNames.length); } /** * Provide header names from the list of standard headers that should be suppressed when * mapping inbound endpoint response headers. * @param excludedInboundStandardResponseHeaderNames the excludedStandardResponseHeaderNames to set */ public void setExcludedInboundStandardResponseHeaderNames( String... excludedInboundStandardResponseHeaderNames) { Assert.notNull(excludedInboundStandardResponseHeaderNames, "'excludedInboundStandardResponseHeaderNames' must not be null"); Assert.noNullElements(excludedInboundStandardResponseHeaderNames, "'excludedInboundStandardResponseHeaderNames' must not have null elements"); this.excludedInboundStandardResponseHeaderNames = Arrays.copyOf(excludedInboundStandardResponseHeaderNames, excludedInboundStandardResponseHeaderNames.length); } /** * Sets the prefix to use with user-defined (non-standard) headers. The default is an * empty string. * @param userDefinedHeaderPrefix The user defined header prefix. */ public void setUserDefinedHeaderPrefix(String userDefinedHeaderPrefix) { this.userDefinedHeaderPrefix = (userDefinedHeaderPrefix != null) ? userDefinedHeaderPrefix : ""; } @Override public void afterPropertiesSet() { if (this.beanFactory != null) { this.conversionService = IntegrationUtils.getConversionService(this.beanFactory); } } /** * Map from the integration MessageHeaders to an HttpHeaders instance. * Depending on which type of adapter is using this mapper, the HttpHeaders might be * for an HTTP request (outbound adapter) or for an HTTP response (inbound adapter). */ @Override public void fromHeaders(MessageHeaders headers, HttpHeaders target) { if (this.logger.isDebugEnabled()) { this.logger.debug("outboundHeaderNames=" + Arrays.toString(this.outboundHeaderNames)); } for (Entry<String, Object> entry : headers.entrySet()) { String name = entry.getKey(); Object value = entry.getValue(); String lowerName = name.toLowerCase(); if (value != null && shouldMapOutboundHeader(lowerName)) { if (!HTTP_REQUEST_HEADER_NAMES_LOWER.contains(lowerName) && !HTTP_RESPONSE_HEADER_NAMES_LOWER.contains(lowerName) && !MessageHeaders.CONTENT_TYPE.equalsIgnoreCase(name)) { // prefix the user-defined header names if not already prefixed name = StringUtils.startsWithIgnoreCase(name, this.userDefinedHeaderPrefix) ? name : this.userDefinedHeaderPrefix + name; } if (this.logger.isDebugEnabled()) { this.logger.debug(MessageFormat.format("setting headerName=[{0}], value={1}", name, value)); } setHttpHeader(target, name, value); } } } private void setHttpHeader(HttpHeaders target, String name, Object value) { // NOSONAR switch (name.toLowerCase()) { case ACCEPT_LOWER: setAccept(target, value); break; case ACCEPT_CHARSET_LOWER: setAcceptCharset(target, value); break; case ALLOW_LOWER: setAllow(target, value); break; case CACHE_CONTROL_LOWER: setCacheControl(target, value); break; case CONTENT_LENGTH_LOWER: setContentLength(target, value); break; case "contenttype": // Lower case for MessageHeaders.CONTENT_TYPE setContentType(target, value); break; case DATE_LOWER: setDate(target, value); break; case ETAG_LOWER: setETag(target, value); break; case EXPIRES_LOWER: setExpires(target, value); break; case IF_MODIFIED_SINCE_LOWER: setIfModifiedSince(target, value); break; case IF_UNMODIFIED_SINCE_LOWER: setIfUnmodifiedSince(target, value); break; case IF_NONE_MATCH_LOWER: setIfNoneMatch(target, value); break; case LAST_MODIFIED_LOWER: setLastModified(target, value); break; case LOCATION_LOWER: setLocation(target, value); break; case PRAGMA_LOWER: setPragma(target, value); break; default: if (value instanceof String) { target.set(name, (String) value); } else if (value instanceof String[]) { target.addAll(name, Arrays.asList((String[]) value)); } else if (value instanceof Iterable<?>) { setIterableHeader(target, name, value); } else { setPlainHeader(target, name, value); } } } private void setAccept(HttpHeaders target, Object value) { Collection<?> valuesToAccept = valueToCollection(value); if (!CollectionUtils.isEmpty(valuesToAccept)) { List<MediaType> acceptableMediaTypes = new ArrayList<>(); for (Object type : valuesToAccept) { if (type instanceof MimeType) { acceptableMediaTypes.add(MediaType.asMediaType((MimeType) type)); } else if (type instanceof String) { acceptableMediaTypes.addAll(MediaType.parseMediaTypes((String) type)); } else { throwIllegalArgumentForUnexpectedValue("Expected org.springframework.util.MimeType " + "or String value for 'Accept' header value, but received: ", type); } } target.setAccept(acceptableMediaTypes); } } private void setAcceptCharset(HttpHeaders target, Object value) { Collection<?> valuesToConvert = valueToCollection(value); if (!CollectionUtils.isEmpty(valuesToConvert)) { List<Charset> acceptableCharsets = new ArrayList<>(); for (Object charset : valuesToConvert) { if (charset instanceof Charset) { acceptableCharsets.add((Charset) charset); } else if (charset instanceof String) { String[] charsets = StringUtils.delimitedListToStringArray((String) charset, ",", " "); for (String charset2 : charsets) { acceptableCharsets.add(Charset.forName(charset2)); } } else { throwIllegalArgumentForUnexpectedValue( "Expected Charset or String value for 'Accept-Charset' header value, but received: ", charset); } } target.setAcceptCharset(acceptableCharsets); } } private void setAllow(HttpHeaders target, Object value) { Collection<?> valuesToConvert = valueToCollection(value); if (!CollectionUtils.isEmpty(valuesToConvert)) { Set<HttpMethod> allowedMethods = new HashSet<>(); for (Object method : valuesToConvert) { if (method instanceof HttpMethod) { allowedMethods.add((HttpMethod) method); } else if (method instanceof String) { String[] methods = StringUtils.delimitedListToStringArray((String) method, ",", " "); for (String method2 : methods) { allowedMethods.add(HttpMethod.valueOf(method2)); } } else { throwIllegalArgumentForUnexpectedValue( "Expected HttpMethod or String value for 'Allow' header value, but received: ", method); } } target.setAllow(allowedMethods); } } private Collection<?> valueToCollection(Object value) { Collection<?> valuesToConvert; if (value instanceof Collection<?>) { valuesToConvert = (Collection<?>) value; } else if (value.getClass().isArray()) { valuesToConvert = Arrays.asList(ObjectUtils.toObjectArray(value)); } else { valuesToConvert = Collections.singleton(value); } return valuesToConvert; } private void setCacheControl(HttpHeaders target, Object value) { if (value instanceof String) { target.setCacheControl((String) value); } else { throwIllegalArgumentForUnexpectedValue( "Expected String value for 'Cache-Control' header value, but received: ", value); } } private void setContentLength(HttpHeaders target, Object value) { if (value instanceof Number) { target.setContentLength(((Number) value).longValue()); } else if (value instanceof String) { target.setContentLength(Long.parseLong((String) value)); } else { throwIllegalArgumentForUnexpectedValue( "Expected Number or String value for 'Content-Length' header value, but received: ", value); } } private void setContentType(HttpHeaders target, Object value) { if (value instanceof MimeType) { target.setContentType(MediaType.asMediaType((MimeType) value)); } else if (value instanceof String) { target.setContentType(MediaType.parseMediaType((String) value)); } else { throwIllegalArgumentForUnexpectedValue("Expected org.springframework.util.MimeType " + "or String value for 'Content-Type' header value, but received: ", value); } } private void setDate(HttpHeaders target, Object value) { if (value instanceof Date) { target.setDate(((Date) value).getTime()); } else if (value instanceof Number) { target.setDate(((Number) value).longValue()); } else if (value instanceof String) { try { target.setDate(Long.parseLong((String) value)); } catch (@SuppressWarnings(UNUSED) NumberFormatException e) { target.setDate(getFirstDate((String) value, HttpHeaders.DATE)); } } else { throwIllegalArgumentForUnexpectedValue( "Expected Date, Number, or String value for 'Date' header value, but received: ", value); } } private void setETag(HttpHeaders target, Object value) { if (value instanceof String) { target.setETag((String) value); } else { throwIllegalArgumentForUnexpectedValue("Expected String value for 'ETag' header value, but received: ", value); } } private void setExpires(HttpHeaders target, Object value) { if (value instanceof Date) { target.setExpires(((Date) value).getTime()); } else if (value instanceof Number) { target.setExpires(((Number) value).longValue()); } else if (value instanceof String) { try { target.setExpires(Long.parseLong((String) value)); } catch (@SuppressWarnings(UNUSED) NumberFormatException e) { target.setExpires(getFirstDate((String) value, HttpHeaders.EXPIRES)); } } else { throwIllegalArgumentForUnexpectedValue( "Expected Date, Number, or String value for 'Expires' header value, but received: ", value); } } private void setIfModifiedSince(HttpHeaders target, Object value) { if (value instanceof Date) { target.setIfModifiedSince(((Date) value).getTime()); } else if (value instanceof Number) { target.setIfModifiedSince(((Number) value).longValue()); } else if (value instanceof String) { try { target.setIfModifiedSince(Long.parseLong((String) value)); } catch (@SuppressWarnings(UNUSED) NumberFormatException e) { target.setIfModifiedSince(getFirstDate((String) value, HttpHeaders.IF_MODIFIED_SINCE)); } } else { throwIllegalArgumentForUnexpectedValue( "Expected Date, Number, or String value for 'If-Modified-Since' header value, but received: ", value); } } private void setIfUnmodifiedSince(HttpHeaders target, Object value) { if (value instanceof Date) { target.setIfUnmodifiedSince(((Date) value).getTime()); } else if (value instanceof Number) { target.setIfUnmodifiedSince(((Number) value).longValue()); } else if (value instanceof String) { try { target.setIfUnmodifiedSince(Long.parseLong((String) value)); } catch (@SuppressWarnings(UNUSED) NumberFormatException e) { target.setIfUnmodifiedSince(getFirstDate((String) value, HttpHeaders.IF_UNMODIFIED_SINCE)); } } else { throwIllegalArgumentForUnexpectedValue( "Expected Date, Number, or String value for 'If-Unmodified-Since' header value, but received: ", value); } } private void setIfNoneMatch(HttpHeaders target, Object value) { Collection<?> valuesToAccept = valueToCollection(value); List<String> ifNoneMatchList = new ArrayList<>(); for (Object match : valuesToAccept) { if (match instanceof String) { ifNoneMatchList.add((String) match); } else { throwIllegalArgumentForUnexpectedValue( "Expected String value for 'If-None-Match' header value, but received: ", value); } } if (!ifNoneMatchList.isEmpty()) { target.setIfNoneMatch(ifNoneMatchList); } } private void setLastModified(HttpHeaders target, Object value) { if (value instanceof Date) { target.setLastModified(((Date) value).getTime()); } else if (value instanceof Number) { target.setLastModified(((Number) value).longValue()); } else if (value instanceof String) { try { target.setLastModified(Long.parseLong((String) value)); } catch (@SuppressWarnings(UNUSED) NumberFormatException e) { target.setLastModified(getFirstDate((String) value, HttpHeaders.LAST_MODIFIED)); } } else { throwIllegalArgumentForUnexpectedValue( "Expected Date, Number, or String value for 'Last-Modified' header value, but received: ", value); } } private void setLocation(HttpHeaders target, Object value) { if (value instanceof URI) { target.setLocation((URI) value); } else if (value instanceof String) { try { target.setLocation(new URI((String) value)); } catch (URISyntaxException e) { throw new IllegalArgumentException(e); } } else { throwIllegalArgumentForUnexpectedValue( "Expected URI or String value for 'Location' header value, but received: ", value); } } private void setPragma(HttpHeaders target, Object value) { if (value instanceof String) { target.setPragma((String) value); } else { throwIllegalArgumentForUnexpectedValue( "Expected String value for 'Pragma' header value, but received: ", value); } } private void throwIllegalArgumentForUnexpectedValue(String message, @Nullable Object value) { throw new IllegalArgumentException(message + (value != null ? value.getClass() : null)); } private void setIterableHeader(HttpHeaders target, String name, Object value) { for (Object next : (Iterable<?>) value) { String convertedValue; if (next instanceof String) { convertedValue = (String) next; } else { convertedValue = convertToString(value); } if (StringUtils.hasText(convertedValue)) { target.add(name, convertedValue); } else { this.logger.warn("Element of the header '" + name + "' with value '" + value + "' will not be set since it is not a String and no Converter is available. " + "Consider registering a Converter with ConversionService (e.g., <int:converter>)"); } } } private void setPlainHeader(HttpHeaders target, String name, Object value) { String convertedValue = convertToString(value); if (StringUtils.hasText(convertedValue)) { target.set(name, convertedValue); } else { this.logger.warn("Header '" + name + "' with value '" + value + "' will not be set since it is not a String and no Converter is available. " + "Consider registering a Converter with ConversionService (e.g., <int:converter>)"); } } /** * Map from an HttpHeaders instance to integration MessageHeaders. * Depending on which type of adapter is using this mapper, the HttpHeaders might be * from an HTTP request (inbound adapter) or from an HTTP response (outbound adapter). */ @Override public Map<String, Object> toHeaders(HttpHeaders source) { if (this.logger.isDebugEnabled()) { this.logger.debug("inboundHeaderNames=" + Arrays.toString(this.inboundHeaderNames)); } Map<String, Object> target = new HashMap<>(); Set<String> headerNames = source.keySet(); for (String name : headerNames) { String lowerName = name.toLowerCase(); if (shouldMapInboundHeader(lowerName)) { if (!HTTP_REQUEST_HEADER_NAMES_LOWER.contains(lowerName) && !HTTP_RESPONSE_HEADER_NAMES_LOWER.contains(lowerName)) { populateUserDefinedHeader(source, target, name); } else { populateStandardHeader(source, target, name); } } } return target; } private void populateUserDefinedHeader(HttpHeaders source, Map<String, Object> target, String name) { String prefixedName = StringUtils.startsWithIgnoreCase(name, this.userDefinedHeaderPrefix) ? name : this.userDefinedHeaderPrefix + name; Object value = source.containsKey(prefixedName) ? getHttpHeader(source, prefixedName) : getHttpHeader(source, name); if (value != null) { setMessageHeader(target, name, value); } } private void populateStandardHeader(HttpHeaders source, Map<String, Object> target, String name) { Object value = getHttpHeader(source, name); if (value != null) { setMessageHeader(target, HttpHeaders.CONTENT_TYPE.equalsIgnoreCase(name) ? MessageHeaders.CONTENT_TYPE : name, value); } } protected Object getHttpHeader(HttpHeaders source, String name) { // NOSONAR switch (name.toLowerCase()) { case ACCEPT_LOWER: return source.getAccept(); case ACCEPT_CHARSET_LOWER: return source.getAcceptCharset(); case ALLOW_LOWER: return source.getAllow(); case CACHE_CONTROL_LOWER: String cacheControl = source.getCacheControl(); return (StringUtils.hasText(cacheControl)) ? cacheControl : null; case CONTENT_LENGTH_LOWER: long contentLength = source.getContentLength(); return (contentLength > -1) ? contentLength : null; case CONTENT_TYPE_LOWER: return source.getContentType(); case DATE_LOWER: long date = source.getDate(); return (date > -1) ? date : null; case ETAG_LOWER: String eTag = source.getETag(); return (StringUtils.hasText(eTag)) ? eTag : null; case EXPIRES_LOWER: long expires = source.getExpires(); return (expires > -1) ? expires : null; case IF_NONE_MATCH_LOWER: return source.getIfNoneMatch(); case IF_MODIFIED_SINCE_LOWER: long modifiedSince = source.getIfModifiedSince(); return (modifiedSince > -1) ? modifiedSince : null; case IF_UNMODIFIED_SINCE_LOWER: long unmodifiedSince = source.getIfUnmodifiedSince(); return (unmodifiedSince > -1) ? unmodifiedSince : null; case LAST_MODIFIED_LOWER: long lastModified = source.getLastModified(); return (lastModified > -1) ? lastModified : null; case LOCATION_LOWER: return source.getLocation(); case PRAGMA_LOWER: String pragma = source.getPragma(); return (StringUtils.hasText(pragma)) ? pragma : null; default: return source.get(name); } } private void setMessageHeader(Map<String, Object> target, String name, Object value) { if (this.logger.isDebugEnabled()) { this.logger.debug(MessageFormat.format("setting headerName=[{0}], value={1}", name, value)); } if (ObjectUtils.isArray(value)) { Object[] values = ObjectUtils.toObjectArray(value); if (!ObjectUtils.isEmpty(values)) { if (values.length == 1) { target.put(name, values); } else { target.put(name, values[0]); } } } else if (value instanceof Collection<?>) { Collection<?> values = (Collection<?>) value; if (!CollectionUtils.isEmpty(values)) { if (values.size() == 1) { target.put(name, values.iterator().next()); } else { target.put(name, values); } } } else if (value != null) { target.put(name, value); } } private boolean shouldMapOutboundHeader(String headerName) { String[] outboundHeaderNamesLower = this.outboundHeaderNamesLowerWithContentType; if (this.isDefaultInboundMapper) { /* * When using the default response header name list, suppress the * mapping of exclusions for specific headers. */ if (containsElementIgnoreCase(this.excludedInboundStandardResponseHeaderNames, headerName)) { if (this.logger.isDebugEnabled()) { this.logger.debug( MessageFormat.format("headerName=[{0}] WILL NOT be mapped (excluded)", headerName)); } return false; } } else if (this.isDefaultOutboundMapper) { outboundHeaderNamesLower = this.outboundHeaderNamesLowerWithContentType; /* * When using the default request header name list, suppress the * mapping of exclusions for specific headers. */ if (containsElementIgnoreCase(this.excludedOutboundStandardRequestHeaderNames, headerName)) { if (this.logger.isDebugEnabled()) { this.logger.debug( MessageFormat.format("headerName=[{0}] WILL NOT be mapped (excluded)", headerName)); } return false; } } return shouldMapHeader(headerName, outboundHeaderNamesLower); } protected final boolean shouldMapInboundHeader(String headerName) { return shouldMapHeader(headerName, this.inboundHeaderNamesLower); } /** * @param headerName the header name (lower cased). * @param patterns the patterns (lower cased). * @return true if should be mapped. */ private boolean shouldMapHeader(String headerName, String[] patterns) { if (patterns != null && patterns.length > 0) { for (String pattern : patterns) { if (matchHeaderForPattern(headerName, pattern)) { return true; } } } if (this.logger.isDebugEnabled()) { this.logger.debug(MessageFormat.format("headerName=[{0}] WILL NOT be mapped", headerName)); } return false; } private boolean matchHeaderForPattern(String headerName, String pattern) { if (PatternMatchUtils.simpleMatch(pattern, headerName)) { if (this.logger.isDebugEnabled()) { this.logger.debug(MessageFormat.format("headerName=[{0}] WILL be mapped, matched pattern={1}", headerName, pattern)); } return true; } else if (HTTP_REQUEST_HEADER_NAME_PATTERN.equals(pattern) && HTTP_REQUEST_HEADER_NAMES_LOWER.contains(headerName)) { if (this.logger.isDebugEnabled()) { this.logger.debug(MessageFormat.format("headerName=[{0}] WILL be mapped, matched pattern={1}", headerName, pattern)); } return true; } else if (HTTP_RESPONSE_HEADER_NAME_PATTERN.equals(pattern) && HTTP_RESPONSE_HEADER_NAMES_LOWER.contains(headerName)) { if (this.logger.isDebugEnabled()) { this.logger.debug(MessageFormat.format("headerName=[{0}] WILL be mapped, matched pattern={1}", headerName, pattern)); } return true; } return false; } @Nullable protected String convertToString(Object value) { if (this.conversionService != null && this.conversionService.canConvert(TypeDescriptor.forObject(value), TypeDescriptor.valueOf(String.class))) { return this.conversionService.convert(value, String.class); } return null; } // Utility methods protected static boolean containsElementIgnoreCase(String[] headerNames, String name) { for (String headerName : headerNames) { if (headerName.equalsIgnoreCase(name)) { return true; } } return false; } protected static long getFirstDate(String headerValue, String headerName) { for (DateTimeFormatter dateFormatter : DATE_FORMATS) { try { return ZonedDateTime.parse(headerValue, dateFormatter).toInstant().toEpochMilli(); } catch (@SuppressWarnings(UNUSED) DateTimeParseException ex) { // ignore } } throw new IllegalArgumentException( "Cannot parse date value '" + headerValue + "' for '" + headerName + "' header"); } /** * Factory method for creating a basic outbound mapper instance. * This will map all standard HTTP request headers when sending an HTTP request, * and it will map all standard HTTP response headers when receiving an HTTP response. * @return The default outbound mapper. */ public static DefaultHttpHeaderMapper outboundMapper() { DefaultHttpHeaderMapper mapper = new DefaultHttpHeaderMapper(); setupDefaultOutboundMapper(mapper); return mapper; } /** * Subclasses can call this from a static outboundMapper() method to set up * standard header mappings for an outbound mapper. * @param mapper the mapper. */ protected static void setupDefaultOutboundMapper(DefaultHttpHeaderMapper mapper) { mapper.setOutboundHeaderNames(HTTP_REQUEST_HEADER_NAMES); mapper.setInboundHeaderNames(HTTP_RESPONSE_HEADER_NAMES); mapper.setExcludedOutboundStandardRequestHeaderNames(HTTP_REQUEST_HEADER_NAMES_OUTBOUND_EXCLUSIONS); } /** * Factory method for creating a basic inbound mapper instance. * This will map all standard HTTP request headers when receiving an HTTP request, * and it will map all standard HTTP response headers when sending an HTTP response. * @return The default inbound mapper. */ public static DefaultHttpHeaderMapper inboundMapper() { DefaultHttpHeaderMapper mapper = new DefaultHttpHeaderMapper(); setupDefaultInboundMapper(mapper); return mapper; } /** * Subclasses can call this from a static inboundMapper() method to set up * standard header mappings for an inbound mapper. * @param mapper the mapper. */ protected static void setupDefaultInboundMapper(DefaultHttpHeaderMapper mapper) { mapper.setInboundHeaderNames(HTTP_REQUEST_HEADER_NAMES); mapper.setOutboundHeaderNames(HTTP_RESPONSE_HEADER_NAMES); mapper.setExcludedInboundStandardResponseHeaderNames(HTTP_RESPONSE_HEADER_NAMES_INBOUND_EXCLUSIONS); } }