前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >弱网客户端的福音:节省带宽的利器【ETag】

弱网客户端的福音:节省带宽的利器【ETag】

作者头像
烟雨平生
发布2023-03-07 16:30:34
1.8K0
发布2023-03-07 16:30:34
举报
文章被收录于专栏:数字化之路数字化之路
原文链接:https://www.baeldung.com/etags-for-rest-with-spring

作者:Eugen Paraschiv

译者:helloworldtang

目录

  • 1.概览
  • 2.REST和ETag
  • 3.使用 curl来验证ETag功能
  • 4.Spring对ETag的支持
  • 5.测试ETag
  • 6.ETag的其它用武之地
  • 7. 总结

1.概览

本文将重点介绍如何在Spring中添加ETag功能、如何使用 curl来验证添加了ETag功能的REST API以及对这些REST API进行集成测试。

2.REST和 ETag

来自Spring官方文档中对ETag特性的描述: ETag(实体标签)是由符合HTTP/1.1的Web服务器返回的HTTP响应头,用于检查给定URL的返回值是否发生变化。

ETag常用于这两个场景——缓存和条件请求。ETag的值可以是根据响应体计算出来的hash值。因为可能使用Hash函数,所以即使响应体出现很小的改动也会极大地改变输出,也就是ETag值会发生变化。这只适用于比较严格的ETag——协议也提供了一个简单的ETag。

使用If-*头将一个标准的GET请求转换为条件GET。与ETag一起使用的两个If-*头是 “If-None-Match”和“If-Match”——每一个HTTP头都有它自己的语义,正如本文后面所讨论的。

3.使用 curl来验证ETag功能

一个通过客户端和服务器通信来简单地测试ETag特性的操作可以分解为以下步骤:

首先,客户端发起一个对REST API的调用——响应包括了需要存储的ETag头,以便进一步使用:

代码语言:javascript
复制
curl -H "Accept: application/json" -i http://localhost:8080/rest-sec/api/resources/1
代码语言:javascript
复制
HTTP/1.1 200 OK
ETag: "f88dd058fe004909615a64f01be66a7"
Content-Type: application/json;charset=UTF-8
Content-Length: 52

– 客户端在下一步发起REST API请求时,会使用If-None-Match头携带上一步保存的ETag值;如果服务器上的资源没有发生变化,那么响应将不会包含任何响应体,并且返回的HTTP状态码将会是304——Not Modified

代码语言:javascript
复制
curl -H "Accept: application/json" -H 'If-None-Match: "f88dd058fe004909615a64f01be66a7"'
 -i http://localhost:8080/rest-sec/api/resources/1
代码语言:javascript
复制
HTTP/1.1 304 Not Modified
ETag: "f88dd058fe004909615a64f01be66a7"

现在,在检索资源之前,我们将通过执行更新操作来改变检索时返回的响应体:

代码语言:javascript
复制
curl --user admin@fake.com:adminpass -H "Content-Type: application/json" -i 
  -X PUT --data '{ "id":1, "name":"newRoleName2", "description":"theNewDescription" }'
    http://localhost:8080/rest-sec/api/resources/1
代码语言:javascript
复制
HTTP/1.1 200 OK
ETag: "d41d8cd98f00b204e9800998ecf8427e"
Content-Length: 0

– 我们发起最后一个请求来再次检索资源;请记住,自从上次检索以来,资源已经被更新了,所以前面存储的ETag值已经不能代表现在的资源了——响应将包含新的数据和一个新的ETag,这个新的ETag可以被存储起来以供后续使用:

代码语言:javascript
复制
curl -H "Accept: application/json" -H 'If-None-Match: "f88dd058fe004909615a64f01be66a7"' -i 
  http://localhost:8080/rest-sec/api/resources/1
代码语言:javascript
复制
HTTP/1.1 200 OK
ETag: "03cb37ca667706c68c0aad4cb04c3a211"
Content-Type: application/json;charset=UTF-8
Content-Length: 56

这就是ETag的作用了,你可以在更多场合使用,并且可以节省带宽。

4.Spring对ETag的支持

在Spring下启用ETag功能非常容易,并且对于应用程序来说也是完全透明的。通过在web.xml中简单地添加一个过滤器就可以启动这个功能:

代码语言:javascript
复制
<filter>
   <filter-name>etagFilter</filter-name>
   <filter-class>org.springframework.web.filter.ShallowEtagHeaderFilter</filter-class>
</filter>
<filter-mapping>
   <filter-name>etagFilter</filter-name>
   <url-pattern>/api/*</url-pattern>
</filter-mapping>

上面配置的过滤器与RESTful API映射在相同的URI规则。自Spring 3.0以来,这个 org.springframework.web.filter.ShallowEtagHeaderFilter过滤器本身就是ETag功能的标准实现了。

这是一个很浅的实现——ETag值是基于响应来计算的,这将节省带宽,而不是服务器性能。因此,一个从ETag中获益的请求仍然会被作为一个标准请求处理,消耗正常消耗的任何资源(数据库连接等),并且只有在将它的响应返回给客户端之前,ETag支持才会启动。

在这一点上,ETag值将根据响应体计算出来并和响应一起返回给客户端;另外,如果请求携带了If-None-Match头,那也将会被处理。

ETag机制的更深层实现可能提供更大的好处——比如服务缓存中的一些请求,完全不必执行计算——但是实现肯定不像浅层方法那么简单,也不像这里描述的浅层方法那样可插拔。

5.测试ETag

那就开始吧——在检索一个资源时,我们需要验证返回的响应体将包含一个“ETag”头。

代码语言:javascript
复制
@Test
public void givenResourceExists_whenRetrievingResource_thenEtagIsAlsoReturned() {
    // Given
    String uriOfResource = createAsUri();

    // When
    Response findOneResponse = RestAssured.given().header("Accept", "application/json").get(uriOfResource);

    // Then
    assertNotNull(findOneResponse.getHeader("ETag"));
}

接下来我们将验证正常使用ETag的效果——如果检索资源的请求使用了正确的ETag值,那么服务器将不再返回资源。

代码语言:javascript
复制
@Test
public void givenResourceWasRetrieved_whenRetrievingAgainWithEtag_thenNotModifiedReturned() {
    // Given
    String uriOfResource = createAsUri();
    Response findOneResponse = RestAssured.given().
      header("Accept", "application/json").get(uriOfResource);
    String etagValue = findOneResponse.getHeader(HttpHeaders.ETAG);

    // When
    Response secondFindOneResponse= RestAssured.given().
      header("Accept", "application/json").headers("If-None-Match", etagValue)
      .get(uriOfResource);

    // Then
    assertTrue(secondFindOneResponse.getStatusCode() == 304);
}

操作步骤

  • 首先创建并检索资源,然后存储ETag值以供进一步使用。
  • 发送一个新的检索请求,这次使用 “If-None-Match” 头携带上一次请求得到的ETag值。
  • 在第二个请求中,服务器仅仅返回一个304 Not Modified,这是因为资源本身在两次检索操作之间确实没有变化

最后,我们来验证在第一个和第二个检索请求之间更改资源的情况:

代码语言:javascript
复制
@Test
public void
  givenResourceWasRetrievedThenModified_whenRetrievingAgainWithEtag_thenResourceIsReturned() {
    // Given
    String uriOfResource = createAsUri();
    Response findOneResponse = RestAssured.given().
      header("Accept", "application/json").get(uriOfResource);
    String etagValue = findOneResponse.getHeader(HttpHeaders.ETAG);

    existingResource.setName(randomAlphabetic(6));
    update(existingResource);

    // When
    Response secondFindOneResponse= RestAssured.given().
      header("Accept", "application/json").headers("If-None-Match", etagValue)
      .get(uriOfResource);

    // Then
    assertTrue(secondFindOneResponse.getStatusCode() == 200);
}

操作步骤

  • 首先创建并检索资源,然后存储ETag值以供下一次请求使用。
  • 更新上一步返回的资源
  • 发送一个新的检索请求,这次使用 “If-None-Match”头携带上一步返回的ETag值
  • 在这第二次请求中,服务器将返回一个200 OK和完整的资源,这是因为此时资源已经更新但请求携带的ETag值却是旧的。

最后一个测试用例——因为Spring尚未支持If-Match HTTP头,所以这个测试用例在运行时会失败

代码语言:javascript
复制
@Test
public void givenResourceExists_whenRetrievedWithIfMatchIncorrectEtag_then412IsReceived() {
    // Given
    T existingResource = getApi().create(createNewEntity());

    // When
    String uriOfResource = baseUri + "/" + existingResource.getId();
    Response findOneResponse = RestAssured.given().header("Accept", "application/json").
      headers("If-Match", randomAlphabetic(8)).get(uriOfResource);

    // Then
    assertTrue(findOneResponse.getStatusCode() == 412);
}

操作步骤

  • 首先创建资源
  • 然后使用指定了错误ETag值的“If-Match”头检索资源——这是一个有条件的GET请求
  • 服务器将返回一个412 未满足先决条件

6.ETag的其它用武之地

我们只是使用ETag来进行读操作——这里有一个已经提交的RFC试图澄清应该如何处理写操作的ETag——这不是标准的,但是是一个有趣的思路。

当然,ETag机制还有其他用途,譬如基于Spring 3.1的乐观锁定机制 ,以及处理相关的“丢失更新问题”。

如果要使用ETag,需要了解下这些前人踩过的坑:潜在缺陷和注意事项。

7.总结

这篇文章只是简单地讲了使用Spring和ETag可以做些什么。 如果需要一个实现了ETag功能的RESTful服务以及配套的集成测试,请查看GitHub项目——这是一个基于maven的项目,因此应该很容易导入和运行。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2018-09-23,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 的数字化之路 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 目录
  • 1.概览
  • 2.REST和 ETag
  • 3.使用 curl来验证ETag功能
  • 4.Spring对ETag的支持
  • 5.测试ETag
  • 6.ETag的其它用武之地
  • 7.总结
相关产品与服务
对象存储
对象存储(Cloud Object Storage,COS)是由腾讯云推出的无目录层次结构、无数据格式限制,可容纳海量数据且支持 HTTP/HTTPS 协议访问的分布式存储服务。腾讯云 COS 的存储桶空间无容量上限,无需分区管理,适用于 CDN 数据分发、数据万象处理或大数据计算与分析的数据湖等多种场景。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档