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?
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.
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: Compare Date objects with different levels of precision
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With