1

看来,自 Spring 5.0 以来,DateTime 格式:RFC2616中指定的 ANSI C 的 asctime() 在提供单个数字(即 9 而不是 09)时不再被正确解析。

查看测试方法时:Spring提供的 HttpHeadersTest.firstZonedDateTime() ;我们可以看到,对于“ANSI C 的 asctime() 格式”,提供了两位数(即 02)作为测试输入,而不是 RFC2616(3.3.1)中指定的一位数(即 2) )。

我编写了一个测试方法来展示潜在的错误。

/**
 * Assumption: ANSI C's single-digit date (i.e; 0-9) should be viable syntax as specified in the RFC2616: https://www.rfc-editor.org/rfc/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 开始,似乎正在应用一种解析 Header 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://www.rfc-editor.org/rfc/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 开始引入了一个错误,其中在解析单个数字时不再正确解析 RFC2616 中定义的 ANSI C 的 asctime() 格式;

我相信错误的原因是从 simpleDateFormat 更改为 ZonedDateTime 进行解析。

我希望任何人在通过 Github 将其提交给 Spring 之前重现此错误;以确保我在测试用例或假设中没有犯任何错误。

这是我的第一篇文章; 原谅任何错误;欢迎(结构化)反馈。

4

0 回答 0