Java Spring MVC REST中的ETag处理

Java Spring MVC REST中的ETag处理,java,rest,spring-mvc,jax-rs,etag,Java,Rest,Spring Mvc,Jax Rs,Etag,我正在考虑从使用JAX-RS的apachecxf-RS切换到springmvc-REST,并看到springmvc-REST目前处理etag的方式存在一些问题。也许我理解得不对,或者有更好的方法来实现JAX-RS目前正在做的事情 使用ApacheCXFRS,上次修改的时间戳和ETag的条件在REST服务中进行评估(条件评估实际上相当复杂,请参见RFC2616第14.24和14.26节,因此我很高兴我完成了这项工作)。代码如下所示: @GET @Path("...") @Produces(Medi

我正在考虑从使用JAX-RS的apachecxf-RS切换到springmvc-REST,并看到springmvc-REST目前处理etag的方式存在一些问题。也许我理解得不对,或者有更好的方法来实现JAX-RS目前正在做的事情

使用ApacheCXFRS,上次修改的时间戳和ETag的条件在REST服务中进行评估(条件评估实际上相当复杂,请参见RFC2616第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();
}
@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!
}
@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 ...;
}
我对Spring MVC REST的相同尝试如下所示:

@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();
}
@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!
}
@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 ...;
}
@RequestMapping(value=“…”,products=MediaType.APPLICATION\u JSON\u value)
公共响应通过预激(…)查找{
…结果=…获取结果。。。;
//…结果=…获取结果或其部分…;-无法获取部分,必须获取完整结果,请参见下文
最后一个字符串eTag=“W/\”“+computeETagValue(result)+“\”“;//需要手动构造,而不是上面方便的JAX RS
返回响应性
.ok()//总是说“ok”(200)?
.cacheControl(
缓存控制
.cachePrivate()
.maxAge(15,时间单位秒)
.nottransform()
)
.eTag(eTag)
.body(result);//ETag比较在控制器(?)外部进行,但已生成完整响应的数据-这是浪费!
}
我反对必须事先构建所有数据以获得完整响应,即使不需要。这使得Spring MVC REST中使用的ETag远没有它应有的价值。根据我的理解,弱ETag的概念是,构建其价值并进行比较可能“便宜”。在令人高兴的情况下,这可以防止服务器上的负载。在这种情况下,资源被修改了,当然,必须构建完整的响应

在我看来,通过设计,Spring MVC REST目前需要构建完整的响应数据,无论最终是否需要响应体

总之,Spring MVC REST有两个问题:

  • 是否存在与
    evaluatePreconditions()等价的工具?
  • 有没有更好的方法来构造ETag字符串
  • 谢谢你的想法

  • ,有一种等效方法
  • 您可以这样使用它:

    @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();
    }
    
    @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!
    }
    
    @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 ...;
    }
    
    @RequestMapping
    公共响应FindByGPnPPrefixCache(WebRequest请求){
    //1.使用完整/部分数据的应用程序特定计算
    长lastModified=。。。;
    字符串etag=。。。;
    if(request.checkNotModified(lastModified,etag)){
    //2.快捷方式退出-无需进一步处理
    //它还将响应转换为未修改的响应
    //空空如也
    返回null;
    }
    //3.或其他进一步的请求处理,实际准备内容
    返回。。。;
    }
    
    请注意,
    checkNotModified
    方法有不同的版本,包括lastModified、ETag或两者

    您可以在此处找到文档:


  • 是的,但是你必须先改变一些事情
  • 您可以更改计算ETag的方式,这样就不需要获取完整的结果

    例如,如果此获取结果是一个数据库查询,则可以向实体中添加一个版本字段,并使用
    @version
    对其进行注释,以便每次修改时都递增,然后将该值用于ETag

    这能实现什么?由于可以将抓取配置为惰性,因此不需要检索实体的所有字段,也可以避免为了构建ETag而对其进行散列。只需将版本用作ETag字符串


    这里有一个关于的问题。

    好问题。我认为,如果Spring没有提供这种开箱即用的功能,您可以扩展
    ResponseEntity
    以执行回调来构建整个主体。在这两种情况下,您似乎都需要预加载
    result
    。。。但我明白了,在第一种情况下,你只需要一些便宜的东西(例如版本号)来计算ETag。我想最好的答案是做懒散的健身。最终,在某些应用程序中,为了获得最佳性能,应用程序必须自己测试条件。这并不难。这是我的逻辑实现,基于对最新HTTP规范的讨论。第一行可能是双重“切换自”。我们可以在REST调用中结合使用WebRequest和@RequestBody或HttpEntity吗?我有一个疑问,我们如何计算REST调用中的Etag,它有主体和头…我认为Etag只用于GET调用。WebRequest处理所有GET请求。Etag不适用于邮寄、邮寄等。。。我说得对吗?