Skip to content

Commit 7635eef

Browse files
artembilangaryrussell
authored andcommitted
Fix If-Unmodified-Since header mapping
https://build.spring.io/browse/INT-MASTERSPRING40-664 https://build.spring.io/browse/INT-FATS5IC-833 Fix `DefaultHttpHeaderMapper` to populate an `If-Unmodified-Since` request header with the same formatter as it is in the `HttpHeaders` in Spring Web **Cherry-pick to 5.1.x & 5.0.x**
1 parent 4a55fed commit 7635eef

File tree

3 files changed

+61
-51
lines changed

3 files changed

+61
-51
lines changed

spring-integration-http/src/main/java/org/springframework/integration/http/outbound/AbstractHttpRequestExecutingMessageHandler.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -371,7 +371,7 @@ private HttpEntity<?> createHttpEntityFromPayload(Message<?> message, HttpMethod
371371
// payload is already an HttpEntity, just return it as-is
372372
return (HttpEntity<?>) payload;
373373
}
374-
HttpHeaders httpHeaders = this.mapHeaders(message);
374+
HttpHeaders httpHeaders = mapHeaders(message);
375375
if (!shouldIncludeRequestBody(httpMethod)) {
376376
return new HttpEntity<>(httpHeaders);
377377
}

spring-integration-http/src/main/java/org/springframework/integration/http/support/DefaultHttpHeaderMapper.java

Lines changed: 59 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,8 @@
7373
*/
7474
public class DefaultHttpHeaderMapper implements HeaderMapper<HttpHeaders>, BeanFactoryAware, InitializingBean {
7575

76+
private static final String UNUSED = "unused";
77+
7678
protected final Log logger = LogFactory.getLog(getClass());
7779

7880
public static final String ACCEPT = "Accept";
@@ -260,6 +262,10 @@ public class DefaultHttpHeaderMapper implements HeaderMapper<HttpHeaders>, BeanF
260262
// Copy of 'org.springframework.http.HttpHeaders#GMT'
261263
private static final ZoneId GMT = ZoneId.of("GMT");
262264

265+
// Copy of 'org.springframework.http.HttpHeaders#DATE_FORMATTER'
266+
protected static final DateTimeFormatter DATE_FORMATTER =
267+
DateTimeFormatter.ofPattern("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US).withZone(GMT);
268+
263269
// Copy of 'org.springframework.http.HttpHeaders#DATE_FORMATS'
264270
protected static final DateTimeFormatter[] DATE_FORMATS = new DateTimeFormatter[] {
265271
DateTimeFormatter.RFC_1123_DATE_TIME,
@@ -411,27 +417,28 @@ public void setUserDefinedHeaderPrefix(String userDefinedHeaderPrefix) {
411417
@Override
412418
public void fromHeaders(MessageHeaders headers, HttpHeaders target) {
413419
if (this.logger.isDebugEnabled()) {
414-
this.logger.debug(MessageFormat.format("outboundHeaderNames={0}",
415-
CollectionUtils.arrayToList(this.outboundHeaderNames)));
420+
this.logger.debug("outboundHeaderNames=" + Arrays.toString(this.outboundHeaderNames));
416421
}
417422
for (Entry<String, Object> entry : headers.entrySet()) {
418423
String name = entry.getKey();
419424
String lowerName = name.toLowerCase();
420-
if (this.shouldMapOutboundHeader(lowerName)) {
425+
if (shouldMapOutboundHeader(lowerName)) {
421426
Object value = entry.getValue();
422427
if (value != null) {
423428
if (!HTTP_REQUEST_HEADER_NAMES_LOWER.contains(lowerName) &&
424429
!HTTP_RESPONSE_HEADER_NAMES_LOWER.contains(lowerName) &&
425430
!MessageHeaders.CONTENT_TYPE.equalsIgnoreCase(name)) {
426431
// prefix the user-defined header names if not already prefixed
427432

428-
name = StringUtils.startsWithIgnoreCase(name, this.userDefinedHeaderPrefix) ? name :
429-
this.userDefinedHeaderPrefix + name;
433+
name =
434+
StringUtils.startsWithIgnoreCase(name, this.userDefinedHeaderPrefix)
435+
? name
436+
: this.userDefinedHeaderPrefix + name;
430437
}
431438
if (this.logger.isDebugEnabled()) {
432439
this.logger.debug(MessageFormat.format("setting headerName=[{0}], value={1}", name, value));
433440
}
434-
this.setHttpHeader(target, name, value);
441+
setHttpHeader(target, name, value);
435442
}
436443
}
437444
}
@@ -448,36 +455,36 @@ public Map<String, Object> toHeaders(HttpHeaders source) {
448455
this.logger.debug(MessageFormat.format("inboundHeaderNames={0}",
449456
CollectionUtils.arrayToList(this.inboundHeaderNames)));
450457
}
451-
Map<String, Object> target = new HashMap<String, Object>();
458+
Map<String, Object> target = new HashMap<>();
452459
Set<String> headerNames = source.keySet();
453460
for (String name : headerNames) {
454461
String lowerName = name.toLowerCase();
455-
if (this.shouldMapInboundHeader(lowerName)) {
462+
if (shouldMapInboundHeader(lowerName)) {
456463
if (!HTTP_REQUEST_HEADER_NAMES_LOWER.contains(lowerName)
457464
&& !HTTP_RESPONSE_HEADER_NAMES_LOWER.contains(lowerName)) {
458465
String prefixedName = StringUtils.startsWithIgnoreCase(name, this.userDefinedHeaderPrefix)
459466
? name
460467
: this.userDefinedHeaderPrefix + name;
461468
Object value = source.containsKey(prefixedName)
462-
? this.getHttpHeader(source, prefixedName)
463-
: this.getHttpHeader(source, name);
469+
? getHttpHeader(source, prefixedName)
470+
: getHttpHeader(source, name);
464471
if (value != null) {
465472
if (this.logger.isDebugEnabled()) {
466473
this.logger.debug(MessageFormat.format("setting headerName=[{0}], value={1}", name, value));
467474
}
468-
this.setMessageHeader(target, name, value);
475+
setMessageHeader(target, name, value);
469476
}
470477
}
471478
else {
472-
Object value = this.getHttpHeader(source, name);
479+
Object value = getHttpHeader(source, name);
473480
if (value != null) {
474481
if (this.logger.isDebugEnabled()) {
475482
this.logger.debug(MessageFormat.format("setting headerName=[{0}], value={1}", name, value));
476483
}
477484
if (CONTENT_TYPE.equalsIgnoreCase(name)) {
478485
name = MessageHeaders.CONTENT_TYPE;
479486
}
480-
this.setMessageHeader(target, name, value);
487+
setMessageHeader(target, name, value);
481488
}
482489
}
483490
}
@@ -509,9 +516,10 @@ private boolean shouldMapOutboundHeader(String headerName) {
509516
* When using the default response header name list, suppress the
510517
* mapping of exclusions for specific headers.
511518
*/
512-
if (this.containsElementIgnoreCase(this.excludedInboundStandardResponseHeaderNames, headerName)) {
519+
if (containsElementIgnoreCase(this.excludedInboundStandardResponseHeaderNames, headerName)) {
513520
if (this.logger.isDebugEnabled()) {
514-
this.logger.debug(MessageFormat.format("headerName=[{0}] WILL NOT be mapped (excluded)", headerName));
521+
this.logger
522+
.debug(MessageFormat.format("headerName=[{0}] WILL NOT be mapped (excluded)", headerName));
515523
}
516524
return false;
517525
}
@@ -522,18 +530,19 @@ else if (this.isDefaultOutboundMapper) {
522530
* When using the default request header name list, suppress the
523531
* mapping of exclusions for specific headers.
524532
*/
525-
if (this.containsElementIgnoreCase(this.excludedOutboundStandardRequestHeaderNames, headerName)) {
533+
if (containsElementIgnoreCase(this.excludedOutboundStandardRequestHeaderNames, headerName)) {
526534
if (this.logger.isDebugEnabled()) {
527-
this.logger.debug(MessageFormat.format("headerName=[{0}] WILL NOT be mapped (excluded)", headerName));
535+
this.logger
536+
.debug(MessageFormat.format("headerName=[{0}] WILL NOT be mapped (excluded)", headerName));
528537
}
529538
return false;
530539
}
531540
}
532-
return this.shouldMapHeader(headerName, outboundHeaderNamesLower);
541+
return shouldMapHeader(headerName, outboundHeaderNamesLower);
533542
}
534543

535544
protected final boolean shouldMapInboundHeader(String headerName) {
536-
return this.shouldMapHeader(headerName, this.inboundHeaderNamesLower);
545+
return shouldMapHeader(headerName, this.inboundHeaderNamesLower);
537546
}
538547

539548
/**
@@ -580,7 +589,7 @@ private void setHttpHeader(HttpHeaders target, String name, Object value) {
580589
if (value instanceof Collection<?>) {
581590
Collection<?> values = (Collection<?>) value;
582591
if (!CollectionUtils.isEmpty(values)) {
583-
List<MediaType> acceptableMediaTypes = new ArrayList<MediaType>();
592+
List<MediaType> acceptableMediaTypes = new ArrayList<>();
584593
for (Object type : values) {
585594
if (type instanceof MediaType) {
586595
acceptableMediaTypes.add((MediaType) type);
@@ -602,7 +611,7 @@ else if (value instanceof MediaType) {
602611
target.setAccept(Collections.singletonList((MediaType) value));
603612
}
604613
else if (value instanceof String[]) {
605-
List<MediaType> acceptableMediaTypes = new ArrayList<MediaType>();
614+
List<MediaType> acceptableMediaTypes = new ArrayList<>();
606615
for (String next : (String[]) value) {
607616
acceptableMediaTypes.add(MediaType.parseMediaType(next));
608617
}
@@ -621,7 +630,7 @@ else if (ACCEPT_CHARSET.equalsIgnoreCase(name)) {
621630
if (value instanceof Collection<?>) {
622631
Collection<?> values = (Collection<?>) value;
623632
if (!CollectionUtils.isEmpty(values)) {
624-
List<Charset> acceptableCharsets = new ArrayList<Charset>();
633+
List<Charset> acceptableCharsets = new ArrayList<>();
625634
for (Object charset : values) {
626635
if (charset instanceof Charset) {
627636
acceptableCharsets.add((Charset) charset);
@@ -640,7 +649,7 @@ else if (charset instanceof String) {
640649
}
641650
}
642651
else if (value instanceof Charset[] || value instanceof String[]) {
643-
List<Charset> acceptableCharsets = new ArrayList<Charset>();
652+
List<Charset> acceptableCharsets = new ArrayList<>();
644653
Object[] values = ObjectUtils.toObjectArray(value);
645654
for (Object charset : values) {
646655
if (charset instanceof Charset) {
@@ -657,7 +666,7 @@ else if (value instanceof Charset) {
657666
}
658667
else if (value instanceof String) {
659668
String[] charsets = StringUtils.commaDelimitedListToStringArray((String) value);
660-
List<Charset> acceptableCharsets = new ArrayList<Charset>();
669+
List<Charset> acceptableCharsets = new ArrayList<>();
661670
for (String charset : charsets) {
662671
acceptableCharsets.add(Charset.forName(charset.trim()));
663672
}
@@ -673,7 +682,7 @@ else if (ALLOW.equalsIgnoreCase(name)) {
673682
if (value instanceof Collection<?>) {
674683
Collection<?> values = (Collection<?>) value;
675684
if (!CollectionUtils.isEmpty(values)) {
676-
Set<HttpMethod> allowedMethods = new HashSet<HttpMethod>();
685+
Set<HttpMethod> allowedMethods = new HashSet<>();
677686
for (Object method : values) {
678687
if (method instanceof HttpMethod) {
679688
allowedMethods.add((HttpMethod) method);
@@ -696,14 +705,14 @@ else if (method instanceof String) {
696705
target.setAllow(Collections.singleton((HttpMethod) value));
697706
}
698707
else if (value instanceof HttpMethod[]) {
699-
Set<HttpMethod> allowedMethods = new HashSet<HttpMethod>();
708+
Set<HttpMethod> allowedMethods = new HashSet<>();
700709
Collections.addAll(allowedMethods, (HttpMethod[]) value);
701710
target.setAllow(allowedMethods);
702711
}
703712
else if (value instanceof String || value instanceof String[]) {
704713
String[] values = (value instanceof String[]) ? (String[]) value
705714
: StringUtils.commaDelimitedListToStringArray((String) value);
706-
Set<HttpMethod> allowedMethods = new HashSet<HttpMethod>();
715+
Set<HttpMethod> allowedMethods = new HashSet<>();
707716
for (String next : values) {
708717
allowedMethods.add(HttpMethod.valueOf(next.trim()));
709718
}
@@ -763,8 +772,8 @@ else if (value instanceof String) {
763772
try {
764773
target.setDate(Long.parseLong((String) value));
765774
}
766-
catch (NumberFormatException e) {
767-
target.setDate(this.getFirstDate((String) value, DATE));
775+
catch (@SuppressWarnings(UNUSED) NumberFormatException e) {
776+
target.setDate(getFirstDate((String) value, DATE));
768777
}
769778
}
770779
else {
@@ -794,8 +803,8 @@ else if (value instanceof String) {
794803
try {
795804
target.setExpires(Long.parseLong((String) value));
796805
}
797-
catch (NumberFormatException e) {
798-
target.setExpires(this.getFirstDate((String) value, EXPIRES));
806+
catch (@SuppressWarnings(UNUSED) NumberFormatException e) {
807+
target.setExpires(getFirstDate((String) value, EXPIRES));
799808
}
800809
}
801810
else {
@@ -815,8 +824,8 @@ else if (value instanceof String) {
815824
try {
816825
target.setIfModifiedSince(Long.parseLong((String) value));
817826
}
818-
catch (NumberFormatException e) {
819-
target.setIfModifiedSince(this.getFirstDate((String) value, IF_MODIFIED_SINCE));
827+
catch (@SuppressWarnings(UNUSED) NumberFormatException e) {
828+
target.setIfModifiedSince(getFirstDate((String) value, IF_MODIFIED_SINCE));
820829
}
821830
}
822831
else {
@@ -827,20 +836,20 @@ else if (value instanceof String) {
827836
}
828837
}
829838
else if (IF_UNMODIFIED_SINCE.equalsIgnoreCase(name)) {
830-
String ifUnmodifiedSinceValue = null;
839+
String ifUnmodifiedSinceValue;
831840
if (value instanceof Date) {
832-
ifUnmodifiedSinceValue = this.formatDate(((Date) value).getTime());
841+
ifUnmodifiedSinceValue = formatDate(((Date) value).getTime());
833842
}
834843
else if (value instanceof Number) {
835-
ifUnmodifiedSinceValue = this.formatDate(((Number) value).longValue());
844+
ifUnmodifiedSinceValue = formatDate(((Number) value).longValue());
836845
}
837846
else if (value instanceof String) {
838847
try {
839-
ifUnmodifiedSinceValue = this.formatDate(Long.parseLong((String) value));
848+
ifUnmodifiedSinceValue = formatDate(Long.parseLong((String) value));
840849
}
841-
catch (NumberFormatException e) {
842-
long longValue = this.getFirstDate((String) value, IF_UNMODIFIED_SINCE);
843-
ifUnmodifiedSinceValue = this.formatDate(longValue);
850+
catch (@SuppressWarnings(UNUSED) NumberFormatException e) {
851+
long longValue = getFirstDate((String) value, IF_UNMODIFIED_SINCE);
852+
ifUnmodifiedSinceValue = formatDate(longValue);
844853
}
845854
}
846855
else {
@@ -862,7 +871,7 @@ else if (value instanceof String[]) {
862871
else if (value instanceof Collection) {
863872
Collection<?> values = (Collection<?>) value;
864873
if (!CollectionUtils.isEmpty(values)) {
865-
List<String> ifNoneMatchList = new ArrayList<String>();
874+
List<String> ifNoneMatchList = new ArrayList<>();
866875
for (Object next : values) {
867876
if (next instanceof String) {
868877
ifNoneMatchList.add((String) next);
@@ -888,8 +897,8 @@ else if (value instanceof String) {
888897
try {
889898
target.setLastModified(Long.parseLong((String) value));
890899
}
891-
catch (NumberFormatException e) {
892-
target.setLastModified(this.getFirstDate((String) value, LAST_MODIFIED));
900+
catch (@SuppressWarnings(UNUSED) NumberFormatException e) {
901+
target.setLastModified(getFirstDate((String) value, LAST_MODIFIED));
893902
}
894903
}
895904
else {
@@ -937,12 +946,12 @@ else if (value instanceof String[]) {
937946
}
938947
else if (value instanceof Iterable<?>) {
939948
for (Object next : (Iterable<?>) value) {
940-
String convertedValue = null;
949+
String convertedValue;
941950
if (next instanceof String) {
942951
convertedValue = (String) next;
943952
}
944953
else {
945-
convertedValue = this.convertToString(value);
954+
convertedValue = convertToString(value);
946955
}
947956
if (StringUtils.hasText(convertedValue)) {
948957
target.add(name, convertedValue);
@@ -955,7 +964,7 @@ else if (value instanceof Iterable<?>) {
955964
}
956965
}
957966
else {
958-
String convertedValue = this.convertToString(value);
967+
String convertedValue = convertToString(value);
959968
if (StringUtils.hasText(convertedValue)) {
960969
target.set(name, convertedValue);
961970
}
@@ -1016,7 +1025,7 @@ else if (IF_MODIFIED_SINCE.equalsIgnoreCase(name)) {
10161025
}
10171026
else if (IF_UNMODIFIED_SINCE.equalsIgnoreCase(name)) {
10181027
String unmodifiedSince = source.getFirst(IF_UNMODIFIED_SINCE);
1019-
return unmodifiedSince != null ? this.getFirstDate(unmodifiedSince, IF_UNMODIFIED_SINCE) : null;
1028+
return unmodifiedSince != null ? getFirstDate(unmodifiedSince, IF_UNMODIFIED_SINCE) : null;
10201029
}
10211030
else if (LAST_MODIFIED.equalsIgnoreCase(name)) {
10221031
long lastModified = source.getLastModified();
@@ -1064,6 +1073,7 @@ protected String convertToString(Object value) {
10641073
if (this.conversionService != null &&
10651074
this.conversionService.canConvert(TypeDescriptor.forObject(value),
10661075
TypeDescriptor.valueOf(String.class))) {
1076+
10671077
return this.conversionService.convert(value, String.class);
10681078
}
10691079
return null;
@@ -1087,10 +1097,10 @@ protected long getFirstDate(String headerValue, String headerName) {
10871097
+ "' header");
10881098
}
10891099

1090-
protected String formatDate(long date) {
1100+
protected static String formatDate(long date) {
10911101
Instant instant = Instant.ofEpochMilli(date);
10921102
ZonedDateTime zonedDateTime = ZonedDateTime.ofInstant(instant, GMT);
1093-
return DATE_FORMATS[0].format(zonedDateTime);
1103+
return DATE_FORMATTER.format(zonedDateTime);
10941104
}
10951105

10961106
/**

spring-integration-http/src/test/java/org/springframework/integration/http/HttpProxyScenarioTests.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,7 @@ public void testHttpMultipartProxyScenario() throws Exception {
192192
MultiValueMap<String, String> responseHeaders = new LinkedMultiValueMap<String, String>(httpHeaders);
193193
responseHeaders.set("Connection", "close");
194194
responseHeaders.set("Content-Type", "text/plain");
195-
return new ResponseEntity<Object>(responseHeaders, HttpStatus.OK);
195+
return new ResponseEntity<>(responseHeaders, HttpStatus.OK);
196196
}).when(template).exchange(Mockito.any(URI.class), Mockito.any(HttpMethod.class),
197197
Mockito.any(HttpEntity.class), (Class<?>) isNull());
198198

0 commit comments

Comments
 (0)