Похоже, что начиная с Spring 5.0 формат DateTime: asctime() ANSI C, как указано в RFC2616 больше не анализируется правильно, если указана одна цифра (т. е. 9 вместо 09).
При просмотре метода тестирования: HttpHeadersTest.firstZonedDateTime(), предоставленный Spring; мы можем видеть, что для «формата asctime() ANSI C» в качестве тестового ввода предоставляется двузначное число (т. е.; 02), а не однозначное (т. е. 2), как указано в RFC2616 (3.3.1). ).
Я написал тестовый метод, чтобы продемонстрировать потенциальную ошибку.
/**
* Assumption: ANSI C's single-digit date (i.e; 0-9) should be viable syntax as specified in the RFC2616: https://tools.ietf.org/html/rfc2616#section-3.3.1
* Expected output: assertThat(true) & assertThat(true)
* Actual output: assertThat(true) & assertThat(false)
*
* throws: java.lang.IllegalArgumentException: Cannot parse date value "Fri Jun 2 02:22:00 2017" for "Date" header
*/
@Test
public void firstZonedDateTimeANSI(){
ZonedDateTime date = ZonedDateTime.of(2017, 6, 2, 2, 22, 0, 0, ZoneId.of("GMT"));
// ANSI C's asctime() format where single digit dates are represented as double digits (i.e; 2 as 02)
headers.set(HttpHeaders.DATE, "Fri Jun 02 02:22:00 2017");
assertThat(headers.getFirstZonedDateTime(HttpHeaders.DATE) // getFirstZonedDateTime parses the Date Syntax as ANSI (HttpHeaders.DATE_PARSERS[2])
.isEqual(date))
.isTrue(); // expected assertThat(true) vs actual assertThat(true)
headers.clear();
// ANSI C's asctime() format where single digit dates are viable (i.e; 2 as 2 not 02); as
headers.set(HttpHeaders.DATE, "Fri Jun 2 02:22:00 2017");
assertThat(headers.getFirstZonedDateTime(HttpHeaders.DATE) // getFirstZonedDateTime throws java.time.format.DateTimeParseException: Text 'Fri Jun 2 02:22:00 2017' could not be parsed at index 8
.isEqual(date))
.isTrue(); // expected assertThat(true) vs actual assertThat(false)
}
Я ожидаю, что приведенный выше тест подтвердит истинность даже для однозначного ввода. Но как видите, при запуске тест-метода вылетает ошибка:
throws: java.lang.IllegalArgumentException: Cannot parse date value "Fri Jun 2 02:22:00 2017" for "Date" header.
При ближайшем рассмотрении с помощью отладчика; ошибка может быть отслежена до:
java.time.format.DateTimeParseException: Text 'Fri Jun 2 02:22:00 2017' could not be parsed at index 8
Похоже, что с Spring 5.0 применяется новый способ разбора заголовка DateTime. См. HttpHeaders.getFirstZonedDataTime(String headerName):
/**
* Parse the first header value for the given header name as a date,
* return {@code null} if there is no value, or raise {@link IllegalArgumentException}
* if the value cannot be parsed as a date.
* @param headerName the header name
* @return the parsed date header, or {@code null} if none
* @since 5.0
*/
@Nullable
public ZonedDateTime getFirstZonedDateTime(String headerName) {
return getFirstZonedDateTime(headerName, true);
}
/**
* Parse the first header value for the given header name as a date,
* return {@code null} if there is no value or also in case of an invalid value
* (if {@code rejectInvalid=false}), or raise {@link IllegalArgumentException}
* if the value cannot be parsed as a date.
* @param headerName the header name
* @param rejectInvalid whether to reject invalid values with an
* {@link IllegalArgumentException} ({@code true}) or rather return {@code null}
* in that case ({@code false})
* @return the parsed date header, or {@code null} if none (or invalid)
*/
@Nullable
private ZonedDateTime getFirstZonedDateTime(String headerName, boolean rejectInvalid) {
String headerValue = getFirst(headerName);
if (headerValue == null) {
// No header value sent at all
return null;
}
if (headerValue.length() >= 3) {
// Short "0" or "-1" like values are never valid HTTP date headers...
// Let's only bother with DateTimeFormatter parsing for long enough values.
// See https://stackoverflow.com/questions/12626699/if-modified-since-http-header-passed-by-ie9-includes-length
int parametersIndex = headerValue.indexOf(';');
if (parametersIndex != -1) {
headerValue = headerValue.substring(0, parametersIndex);
}
for (DateTimeFormatter dateFormatter : DATE_PARSERS) {
try {
return ZonedDateTime.parse(headerValue, dateFormatter);
}
catch (DateTimeParseException ex) {
// ignore
}
}
}
if (rejectInvalid) {
throw new IllegalArgumentException("Cannot parse date value \"" + headerValue +
"\" for \"" + headerName + "\" header");
}
return null;
}
Я считаю, что ошибка была введена в Spring 5.0, а точнее в этом цикле по адресу private ZonedDateTime getFirstZonedDateTime(String headerName, boolean rejectInvalid):
for (DateTimeFormatter dateFormatter : DATE_PARSERS) {
try {
return ZonedDateTime.parse(headerValue, dateFormatter);
}
catch (DateTimeParseException ex) {
// ignore
}
}
При просмотре последней функциональной сборки: Spring 4.3 использовался аналогичный цикл: private long getFirstDate(String headerName, boolean rejectInvalid)
for (String dateFormat : DATE_FORMATS) {
SimpleDateFormat simpleDateFormat = new SimpleDateFormat(dateFormat, Locale.US);
simpleDateFormat.setTimeZone(GMT);
try {
return simpleDateFormat.parse(headerValue).getTime();
}
catch (ParseException ex) {
// ignore
}
}
Но в то время как Spring 4.3 по-прежнему использовал java.text.SimpleDateFormat для синтаксического анализа, начиная с Spring 5.0 для синтаксического анализа используется Java.time.format.ZonedDateTime.
И Spring 4.3, и Spring 5.0 обращаются к одному и тому же частному статическому массиву для итерации:
/**
* Date formats with time zone as specified in the HTTP RFC to use for parsing.
* @see <a href="https://tools.ietf.org/html/rfc7231#section-7.1.1.1">Section 7.1.1.1 of RFC 7231</a>
*/
private static final DateTimeFormatter[] DATE_PARSERS = new DateTimeFormatter[] {
DateTimeFormatter.RFC_1123_DATE_TIME,
DateTimeFormatter.ofPattern("EEEE, dd-MMM-yy HH:mm:ss zzz", Locale.US),
DateTimeFormatter.ofPattern("EEE MMM dd HH:mm:ss yyyy", Locale.US).withZone(GMT)
};
Заключить:
Я полагаю, что в Spring 5.0 была введена ошибка, из-за которой формат asctime() ANSI C, определенный в RFC2616, больше не анализируется правильно при анализе одной цифры;
Я считаю, что причиной ошибки является изменение с simpleDateFormat на ZonedDateTime для синтаксического анализа.
Я хотел бы, чтобы кто-нибудь воспроизвел эту ошибку, прежде чем я отправлю ее в Spring через Github; чтобы убедиться, что я не сделал ошибок в тестовом примере или предположениях.
Это мой первый пост; прощать любые ошибки; (структурированные) отзывы приветствуются.