问题描述
将我的JAX-RS应用程序从Jersey迁移到Quarkus / Resteasy时,我遇到了evaluatePreconditions(Date lastModified)
方法的行为更改。确实,在我的用例中,上次修改的日期包含毫秒,但不幸的是,标题If-Modified-Since
和Last-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标头,标头时间戳中不包含毫秒。