根据规范,在以毫秒为单位的日期上 evaluatePreconditions 的正确行为是什么?

What is the right behavior of evaluatePreconditions on a date with milliseconds according to the specification?

在将我的 JAX-RS 应用程序从 Jersey 迁移到 Quarkus/Resteasy 时,我遇到了方法 evaluatePreconditions(Date lastModified). Indeed, in my use case, the last modified date contains milliseconds and unfortunately the date format of the headers If-Modified-Since and Last-Modified doesn't support milliseconds as we can see in the RFC 2616.

的行为变化

Jersey 从提供的日期开始调整毫秒数(如我们所见here),而在 Resteasy 中,日期未被修改,因此它实际上比较日期(来自 header If-Modified-Since 和提供的日期)具有不同的精度(分别为秒与毫秒),最终不匹配,因此 HTTP 状态代码 200.

说明问题的代码:

@Path("/evaluatePreconditions")
public class EvaluatePreconditionsResource {

    @GET
    @Produces(MediaType.TEXT_PLAIN)
    public Response findData(@Context Request request) {
        final Data data = retrieveData();
        final Date lastModified = Timestamp.valueOf(data.getLastModified());
        final Response.ResponseBuilder responseBuilder = 
            request.evaluatePreconditions(lastModified);
        if (responseBuilder == null) {
            // Last modified date didn't match, send new content
            return Response.ok(data.toString())
                .lastModified(lastModified)
                .build();
        }
        // Sending 304 not modified
        return responseBuilder.build();
    }

    private Data retrieveData() {
        // Let's assume that we call a service here that provides this value
        // The date time is expressed in GMT+2, please adjust it according 
        // to your timezone
        return new Data(
            LocalDateTime.of(2020, 10, 2, 10, 23, 16, 1_000_000), 
            "This is my content"
        );
    }

    public static class Data {
        private final LocalDateTime lastModified;
        private final String content;

        public Data(LocalDateTime lastModified, String content) {
            this.lastModified = lastModified;
            this.content = content;
        }

        public LocalDateTime getLastModified() {
            return lastModified;
        }

        @Override
        public String toString() {
            return content;
        }
    }
}

与球衣对应的结果:

curl -H "If-Modified-Since: Fri, 02 Oct 2020 08:23:16 GMT" \
     -I localhost:8080/evaluatePreconditions
HTTP/1.1 304 Not Modified
...

与Quarkus/Resteasy对应的结果:

curl -H "If-Modified-Since: Fri, 02 Oct 2020 08:23:16 GMT" \
     -I localhost:8080/evaluatePreconditions
HTTP/1.1 200 OK
Last-Modified: Fri, 02 Oct 2020 08:23:16 GMT
...

此行为已被提出in the Resteasy project, but for the team, trimming the date would add a new bug because if the data/resource is modified several times within the same second, we would get a 304 if we trim the date and 200 if we don't, which is a fair point. However, I maybe wrong but according to what I understand from the RFC 7232, if several modifications can happen within the same second, we are supposed to rely on an ETag too which means that in the JAX-RS specification, we are supposed to use evaluatePreconditions(Date lastModified, EntityTag eTag)

那么根据 JAX-RS 规范关于这种特殊情况的正确行为是什么?

我认为没有具体说明,evaluatePreconditions 方法是否应该削减几分之一秒。但是:比较两个具有不同精度的时间戳是“不公平的”。您要么应该舍入更精确的精度,要么将精度截断为相同。特别是因为 RFC 7232 甚至指出了 HTTP 的“低”精度问题 header 并提出了解决方案 (ETag)。

我还发现了一个关于如何比较具有不同精度的时间戳的解决方案的问题:Compare Date objects with different levels of precision

Resteasy 4.5 中 Request.evaluatePreconditions(Date lastModified) 的实现是错误的。 class org.jboss.resteasy.specimpl.RequestImpl relies on a helper class DateUtil which expect the Last-Modified header to be in one of the formats: RFC 1123 "EEE, dd MMM yyyy HH:mm:ss zzz", RFC 1036 "EEEE, dd-MMM-yy HH:mm:ss zzz" or ANSI C "EEE MMM d HH:mm:ss yyyy". Of these three formats, only ANSI C is listed at RFC 7231 Section 7.1.1.1 and it is obsolete. The preferred format for an HTTP 1.1. header is as specified in RFC 5322 Section 3.3 and this format does not contain milliseconds. The format that Resteasy implementation refers as RFC 1123 actually comes from RFC 822 Section 5 but RFC 822 is for text messages (mail) not for HTTP headers. Java supports milliseconds at Date but HTTP headers do not. Therefore, comparing dates with different precisions is a bug. The correct implementation is the one at Jersey ContainerRequest 处的实施在比较之前将日期向下舍入到最接近的秒数。

JAX-RS spec 1.1在这方面没有具体说什么。或者,至少,我没能找到它。 JAX-RS 规范不需要解决这个问题。实现必须按照 HTTP 规范处理 HTTP headers,其中不包括 header 时间戳中的毫秒数。