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

问题描述

将我的JAX-RS应用程序从Jersey迁移到Quarkus / Resteasy时,我遇到了evaluatePreconditions(Date lastModified)方法的行为更改。确实,在我的用例中,上次修改的日期包含毫秒,但不幸的是,标题If-Modified-SinceLast-Modified的日期格式不支持毫秒,如我们在RFC 2616中所见。 / p>

Jersey从提供的日期开始剪裁毫秒数(如我们所见,here),而在Resteasy中,日期没有被修改,因此它实际上是比较日期(标头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,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;
        }
    }
}

与Jersey对应的结果:

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,但是对于团队而言,缩短日期会增加一个新的错误,因为如果在同一秒钟内多次修改数据/资源,我们将得到一个{{1 }}(如果我们修剪日期)和304(如果不修剪),这是一个公平的观点。但是,根据我对RFC 7232的理解,我可能错了,如果同一秒钟内可以进行多次修改,我们也应该依赖 ETag ,这意味着JAX-RS规范,我们应该改evaluatePreconditions(Date lastModified,EntityTag eTag)

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

解决方法

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

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

,

在Resteasy 4.5上实施Request.evaluatePreconditions(Date lastModified)是错误的。类org.jboss.resteasy.specimpl.RequestImpl上的实现依赖于辅助类DateUtil,该类希望Last-Modified头采用以下格式之一:RFC 1123 "EEE,dd MMM yyyy HH:mm:ss zzz",RFC 1036 {{1} }或ANSI C "EEEE,dd-MMM-yy HH:mm:ss zzz"。在这三种格式中,RFC 7231 Section 7.1.1.1中仅列出了ANSI C,并且已过时。 HTTP 1.1的首选格式。标头是在RFC 5322 Section 3.3中指定的,并且此格式不包含毫秒。 Resteasy实现称为RFC 1123的格式实际上来自RFC 822 Section 5,但是RFC 822用于文本消息(邮件),而不用于HTTP标头。 Java在"EEE MMM d HH:mm:ss yyyy"处支持毫秒,但HTTP标头不支持。因此,比较具有不同精度的日期是一个错误。正确的实现方法是在泽西岛ContainerRequest实施,在比较之前将日期四舍五入到最接近的秒数。

JAX-RS spec 1.1在这方面没有特别说明。或者,至少,我找不到它。 JAX-RS规范不需要解决此问题。该实现必须按照HTTP规范处理HTTP标头,标头时间戳中不包含毫秒。