英文:
What is the right behavior of evaluatePreconditions on a date with milliseconds according to the specification?
问题
迁移我的 JAX-RS 应用程序从 Jersey 到 Quarkus/Resteasy 时,我遇到了方法 evaluatePreconditions(Date lastModified)
的行为变更。事实上,在我的用例中,最后修改日期包含毫秒,不幸的是,标头 If-Modified-Since
和 Last-Modified
的日期格式不支持毫秒,正如我们在 RFC 2616 中所见。
Jersey 会从提供的日期中删除毫秒(正如我们在 这里 中所见),而在 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) {
// 最后修改日期不匹配,发送新内容
return Response.ok(data.toString())
.lastModified(lastModified)
.build();
}
// 发送 304 未修改
return responseBuilder.build();
}
// ... 其他方法和类 ...
public static class Data {
// ... 类的定义 ...
}
}
使用 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
...
这个行为已经在 Resteasy 项目中提出过,但是对于团队来说,修剪日期会引入一个新的 bug,因为如果在同一秒内多次修改数据/资源,如果修剪日期,我们会得到一个 304
,如果不修剪,会得到 200
,这是一个合理的观点。然而,我可能是错误的,但根据我从 RFC 7232 中理解的内容,如果在同一秒内可能会发生多次修改,我们也应该依赖于 ETag,这意味着在 JAX-RS 规范中,我们应该使用 evaluatePreconditions(Date lastModified, EntityTag eTag)
。
那么,根据 JAX-RS 规范,对于这种特殊情况,什么是正确的行为?
英文:
While migrating my JAX-RS application from Jersey to Quarkus/Resteasy, I came across a behavior change with the method 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 trims the milliseconds from the provided date (as we can see here) while in Resteasy, the date is not modified so it actually compares dates (the date from the header If-Modified-Since
and the provided date) with different precisions (respectively seconds versus milliseconds) which ends up with a mismatch so an HTTP status code 200
.
The code that illustrates the issue:
@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;
}
}
}
The corresponding result with Jersey:
curl -H "If-Modified-Since: Fri, 02 Oct 2020 08:23:16 GMT" \
-I localhost:8080/evaluatePreconditions
HTTP/1.1 304 Not Modified
...
The corresponding result with 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
...
This behavior has already been raised 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)
instead.
So what is the correct behavior according to the JAX-RS specification regarding this particular case?
答案1
得分: 1
我认为没有明确指定 evaluatePreconditions
方法是否应该保留秒的小数部分。但是:用不同精度来比较两个时间戳是“不公平”的。您可以将精度更高的时间戳四舍五入,或者截断精度使其相同。特别是因为RFC 7232甚至提到了HTTP标头的“低”精度问题,并提出了解决方案(ETag)。
我还找到了一个关于如何比较具有不同精度的时间戳的Stack Overflow问题,其中包含了解决方案:https://stackoverflow.com/q/1671001
英文:
I think it is not specified, if the evaluatePreconditions
methods should cut the fractions of a second or not. But: it is just "not fair" to compare two timestamps with different precision. You either should round the more precise one or truncate the precision to be the same. Especially since RFC 7232 even names the problem of the "low" precision of the HTTP header and suggests a solution (ETag).
I also found a SO question with solutions how to compare timestamps with different precisions: https://stackoverflow.com/q/1671001
答案2
得分: 1
Request.evaluatePreconditions(Date lastModified)
在 Resteasy 4.5 中的实现是错误的。在类 org.jboss.resteasy.specimpl.RequestImpl 中的实现依赖于一个辅助类 DateUtil,该类期望 Last-Modified
头部使用以下格式之一:RFC 1123 格式 "EEE, dd MMM yyyy HH:mm:ss zzz"
、RFC 1036 格式 "EEEE, dd-MMM-yy HH:mm:ss zzz"
或 ANSI C 格式 "EEE MMM d HH:mm:ss yyyy"
。在这三种格式中,只有 ANSI C 格式在 RFC 7231 第7.1.1.1节 中列出,并且已经过时。HTTP 1.1 头部的首选格式如 RFC 5322 第3.3节 中所述,此格式不包含毫秒。Resteasy 实现所称的 RFC 1123 格式实际上来自于 RFC 822 第5节,但 RFC 822 适用于文本消息(邮件),而不适用于HTTP头部。Java 支持 Date
中的毫秒,但HTTP头部不支持。因此,比较具有不同精度的日期是一个错误。正确的实现是 Jersey ContainerRequest
中的实现,在比较之前将日期舍入到最近的秒数。
JAX-RS 规范 1.1 在这方面并未具体说明任何内容。或者,至少我没有找到。JAX-RS 规范不需要解决此问题。实现必须根据 HTTP 规范处理HTTP头部,其中头部时间戳不包含毫秒。
英文:
The implementation of Request.evaluatePreconditions(Date lastModified)
at Resteasy 4.5 is wrong. The implementation at 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
which before comparing rounds down the date to the nearest second.
JAX-RS spec 1.1 does not say anything specifically at this regard. Or, at least, I've not been able to find it. JAX-RS spec does not need to address this issue. The implementation must handle HTTP headers as per HTTP specs, which do not include milliseconds in header timestamps.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论