Spring MVC REST 中的 ETag 处理
ETag handling in Spring MVC REST
我正在考虑从使用 JAX RS 的 Apache CXF RS 切换到 Spring MVC REST,发现 Spring MVC REST 当前处理 ETag 的方式存在一些问题。也许我理解不正确,或者有更好的方法来实现目前使用 JAX RS 所做的事情?
使用 Apache CXF RS,在 REST 服务内部评估最后修改时间戳和 ETag 的条件(条件评估实际上非常复杂,请参阅 RFC 2616 第 14.24 和 14.26 节,所以我很高兴这样做是为了我)。代码看起来像这样:
@GET
@Path("...")
@Produces(MediaType.APPLICATION_JSON)
public Response findBy...(..., @Context Request request) {
... result = ...fetch-result-or-parts-of-it...;
final EntityTag eTag = new EntityTag(computeETagValue(result), true);
ResponseBuilder builder = request.evaluatePreconditions(lastModified, eTag);
if (builder == null) {
// a new response is required, because the ETag or time stamp do not match
// ...potentially fetch full result object now, then:
builder = Response.ok(result);
} else {
// a new response is not needed, send "not modified" status without a body
}
final CacheControl cacheControl = new CacheControl();
cacheControl.setPrivate(true); // store in private browser cache of user only
cacheControl.setMaxAge(15); // may stay unchecked in private browser cache for 15s, afterwards revalidation is required
cacheControl.setNoTransform(true); // proxies must not transform the response
return builder
.cacheControl(cacheControl)
.lastModified(lastModified)
.tag(eTag)
.build();
}
我对 Spring MVC REST 的相同尝试看起来像这样:
@RequestMapping(value="...", produces=MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<...> findByGpnPrefixCacheable(...) {
... result = ...fetch-result...;
// ... result = ...fetch-result-or-parts-of-it...; - can't fetch parts, must obtain full result, see below
final String eTag = "W/\""+computeETagValue(result)+"\""; // need to manually construct, as opposed to convenient JAX RS above
return ResponseEntity
.ok() // always say 'ok' (200)?
.cacheControl(
CacheControl
.cachePrivate()
.maxAge(15, TimeUnit.SECONDS)
.noTransform()
)
.eTag(eTag)
.body(result); // ETag comparison takes place outside controller(?), but data for a full response has already been built - that is wasteful!
}
我对必须事先为完整响应构建所有数据有异议,即使这不是必需的。这使得 Spring MVC REST 中使用的 ETag 的价值远不如它应有的价值。根据我的理解,弱 ETag 的想法是可能 "cheap" 建立其价值并进行比较。在令人满意的情况下,这可以防止服务器上的负载。在这种情况下,资源被修改了,当然必须构建完整的响应。
在我看来,根据设计 Spring MVC REST 目前需要构建完整的响应数据,无论最终是否需要响应主体。
所以,总而言之,Spring MVC REST 有两个问题:
- 是否有等价于
evaluatePreconditions()?
- 是否有更好的 ETag 字符串构造方法?
感谢您的想法!
- 是的,还有一个等价的方法
你可以这样使用它:
@RequestMapping
public ResponseEntity<...> findByGpnPrefixCacheable(WebRequest request) {
// 1. application-specific calculations with full/partial data
long lastModified = ...;
String etag = ...;
if (request.checkNotModified(lastModified, etag)) {
// 2. shortcut exit - no further processing necessary
// it will also convert the response to an 304 Not Modified
// with an empty body
return null;
}
// 3. or otherwise further request processing, actually preparing content
return ...;
}
请注意,checkNotModified
方法有不同的版本,有 lastModified、ETag 或两者都有。
您可以在此处找到文档:Support for ETag and Last-Modified response headers。
- 是,但你必须先改变一些东西。
您可以更改计算 ETag 的方式,这样您就不需要获取完整的结果。
例如,如果这个fetch-result是一个数据库查询,你可以在你的entity中添加一个version字段,并用@Version
注解它,这样每次修改它都会递增,然后使用ETag 的那个值。
这有什么用?由于您可以将获取配置为惰性的,因此您不需要检索实体的所有字段,并且还避免了必须对其进行散列以构建 ETag。只需使用版本作为 ETag 字符串。
这是关于 Using @Version in Spring Data project 的问题。
我正在考虑从使用 JAX RS 的 Apache CXF RS 切换到 Spring MVC REST,发现 Spring MVC REST 当前处理 ETag 的方式存在一些问题。也许我理解不正确,或者有更好的方法来实现目前使用 JAX RS 所做的事情?
使用 Apache CXF RS,在 REST 服务内部评估最后修改时间戳和 ETag 的条件(条件评估实际上非常复杂,请参阅 RFC 2616 第 14.24 和 14.26 节,所以我很高兴这样做是为了我)。代码看起来像这样:
@GET
@Path("...")
@Produces(MediaType.APPLICATION_JSON)
public Response findBy...(..., @Context Request request) {
... result = ...fetch-result-or-parts-of-it...;
final EntityTag eTag = new EntityTag(computeETagValue(result), true);
ResponseBuilder builder = request.evaluatePreconditions(lastModified, eTag);
if (builder == null) {
// a new response is required, because the ETag or time stamp do not match
// ...potentially fetch full result object now, then:
builder = Response.ok(result);
} else {
// a new response is not needed, send "not modified" status without a body
}
final CacheControl cacheControl = new CacheControl();
cacheControl.setPrivate(true); // store in private browser cache of user only
cacheControl.setMaxAge(15); // may stay unchecked in private browser cache for 15s, afterwards revalidation is required
cacheControl.setNoTransform(true); // proxies must not transform the response
return builder
.cacheControl(cacheControl)
.lastModified(lastModified)
.tag(eTag)
.build();
}
我对 Spring MVC REST 的相同尝试看起来像这样:
@RequestMapping(value="...", produces=MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<...> findByGpnPrefixCacheable(...) {
... result = ...fetch-result...;
// ... result = ...fetch-result-or-parts-of-it...; - can't fetch parts, must obtain full result, see below
final String eTag = "W/\""+computeETagValue(result)+"\""; // need to manually construct, as opposed to convenient JAX RS above
return ResponseEntity
.ok() // always say 'ok' (200)?
.cacheControl(
CacheControl
.cachePrivate()
.maxAge(15, TimeUnit.SECONDS)
.noTransform()
)
.eTag(eTag)
.body(result); // ETag comparison takes place outside controller(?), but data for a full response has already been built - that is wasteful!
}
我对必须事先为完整响应构建所有数据有异议,即使这不是必需的。这使得 Spring MVC REST 中使用的 ETag 的价值远不如它应有的价值。根据我的理解,弱 ETag 的想法是可能 "cheap" 建立其价值并进行比较。在令人满意的情况下,这可以防止服务器上的负载。在这种情况下,资源被修改了,当然必须构建完整的响应。
在我看来,根据设计 Spring MVC REST 目前需要构建完整的响应数据,无论最终是否需要响应主体。
所以,总而言之,Spring MVC REST 有两个问题:
- 是否有等价于
evaluatePreconditions()?
- 是否有更好的 ETag 字符串构造方法?
感谢您的想法!
- 是的,还有一个等价的方法
你可以这样使用它:
@RequestMapping
public ResponseEntity<...> findByGpnPrefixCacheable(WebRequest request) {
// 1. application-specific calculations with full/partial data
long lastModified = ...;
String etag = ...;
if (request.checkNotModified(lastModified, etag)) {
// 2. shortcut exit - no further processing necessary
// it will also convert the response to an 304 Not Modified
// with an empty body
return null;
}
// 3. or otherwise further request processing, actually preparing content
return ...;
}
请注意,checkNotModified
方法有不同的版本,有 lastModified、ETag 或两者都有。
您可以在此处找到文档:Support for ETag and Last-Modified response headers。
- 是,但你必须先改变一些东西。
您可以更改计算 ETag 的方式,这样您就不需要获取完整的结果。
例如,如果这个fetch-result是一个数据库查询,你可以在你的entity中添加一个version字段,并用@Version
注解它,这样每次修改它都会递增,然后使用ETag 的那个值。
这有什么用?由于您可以将获取配置为惰性的,因此您不需要检索实体的所有字段,并且还避免了必须对其进行散列以构建 ETag。只需使用版本作为 ETag 字符串。
这是关于 Using @Version in Spring Data project 的问题。