javascript
带有Spring的REST的ETag
1.概述
本文將重點(diǎn)介紹ETags-Spring支持,RESTful API的集成測(cè)試以及帶有curl的使用場(chǎng)景。 這是關(guān)于使用Spring 3.1和Spring Security 3.1和基于Java的配置來(lái)建立安全的RESTful Web服務(wù)的系列文章的第9篇。
REST with Spring系列:
- 第1部分 – 使用Spring 3.1和基于Java的配置引導(dǎo)Web應(yīng)用程序
- P藝術(shù)2 - 構(gòu)建RESTful Web服務(wù)使用Spring 3.1和Java配置
- P藝術(shù)3 - 保護(hù)RESTful Web服務(wù)使用Spring Security 3.1
- 第4部分 – RESTful Web服務(wù)可發(fā)現(xiàn)性
- 第5部分 – 使用Spring進(jìn)行REST服務(wù)發(fā)現(xiàn)
- 第6部分 – 使用Spring Security 3.1的RESTful服務(wù)的基本身份驗(yàn)證和摘要身份驗(yàn)證
- 第7部分 – Spring的REST分頁(yè)
- 第8部分 – 使用Spring Security對(duì)RESTful服務(wù)進(jìn)行身份驗(yàn)證
2. REST和ETag
從有關(guān)ETag支持的Spring官方文檔中:
ETag (實(shí)體標(biāo)簽)是由HTTP / 1.1兼容的Web服務(wù)器返回的HTTP響應(yīng)標(biāo)頭,用于確定給定URL的內(nèi)容更改。ETag用于兩件事–緩存和條件請(qǐng)求。 ETag值可以是從Response主體的字節(jié)中計(jì)算得出的哈希值 。 因?yàn)楹芸赡苁褂昧思用芄:瘮?shù),所以即使是主體的最小修改也將極大地改變輸出,從而改變ETag的值。 這僅適用于強(qiáng)大的ETag-該協(xié)議的確也提供了較弱的Etag 。
使用If- *標(biāo)頭會(huì)將標(biāo)準(zhǔn)GET請(qǐng)求轉(zhuǎn)換為條件GET 。 與ETag一起使用的兩個(gè)If- *標(biāo)頭是“ If-None-Match ”和“ If-Match ” –各自具有自己的語(yǔ)義,如本文稍后所述。
3.使用
涉及ETag的簡(jiǎn)單的Client-Server通信可以分為以下步驟:
– 首先 ,客戶端進(jìn)行REST API調(diào)用–響應(yīng)包括要存儲(chǔ)以供進(jìn)一步使用的ETag標(biāo)頭:
curl -H 'Accept: application/json' -i http://localhost:8080/rest-sec/api/resources/1HTTP/1.1 200 OK ETag: 'f88dd058fe004909615a64f01be66a7' Content-Type: application/json;charset=UTF-8 Content-Length: 52–客戶端對(duì)RESTful API發(fā)出的下一個(gè)請(qǐng)求包括帶有上一步中的ETag值的If-None-Match請(qǐng)求標(biāo)頭; 如果服務(wù)器上的資源未更改,則響應(yīng)將不包含任何正文,并且狀態(tài)代碼為304 –未修改 :
curl -H 'Accept: application/json' -H 'If-None-Match: 'f88dd058fe004909615a64f01be66a7''-i http://localhost:8080/rest-sec/api/resources/1HTTP/1.1 304 Not Modified ETag: 'f88dd058fe004909615a64f01be66a7'– 現(xiàn)在 ,在再次檢索資源之前,我們將通過(guò)執(zhí)行更新來(lái)對(duì)其進(jìn)行更改:
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/1HTTP/1.1 200 OK ETag: 'd41d8cd98f00b204e9800998ecf8427e' <strong>Content-Length: 0</strong>– 最后 ,我們發(fā)出了最后一個(gè)請(qǐng)求以再次獲取特權(quán); 請(qǐng)記住,自上次檢索以來(lái)已對(duì)其進(jìn)行了更新,因此先前的ETag值將不再起作用-響應(yīng)將包含新數(shù)據(jù)和新ETag,這些ETag可以再次存儲(chǔ)以備后用:
curl -H 'Accept: application/json' -H 'If-None-Match: 'f88dd058fe004909615a64f01be66a7'' -i http://localhost:8080/rest-sec/api/resources/1HTTP/1.1 200 OK ETag: '03cb37ca667706c68c0aad4cb04c3a211' Content-Type: application/json;charset=UTF-8 Content-Length: 56一切就在這里– ETags狂野地節(jié)省了帶寬。
4. Spring對(duì)ETag的支持
對(duì)Spring的支持–在Spring中使用ETag非常容易設(shè)置,并且對(duì)于應(yīng)用程序是完全透明的。 通過(guò)在web.xml中添加一個(gè)簡(jiǎn)單的Filter來(lái)啟用該支持:
<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開(kāi)始,過(guò)濾器本身就是ETag功能的標(biāo)準(zhǔn)實(shí)現(xiàn)。
該實(shí)現(xiàn)是一個(gè)淺層實(shí)現(xiàn)-根據(jù)響應(yīng)計(jì)算ETag,這將節(jié)省帶寬,但不能 節(jié)省 服務(wù)器性能 。 因此,將從ETag支持中受益的請(qǐng)求仍將作為標(biāo)準(zhǔn)請(qǐng)求進(jìn)行處理,消耗其通常會(huì)消耗的任何資源(數(shù)據(jù)庫(kù)連接等),并且只有在將其響應(yīng)返回給客戶端之前,ETag支持才會(huì)啟動(dòng)在。
屆時(shí),ETag將從響應(yīng)主體中計(jì)算出來(lái),并在資源本身上設(shè)置; 同樣,如果在請(qǐng)求中設(shè)置了If-None-Match標(biāo)頭,則也會(huì)對(duì)其進(jìn)行處理。
ETag機(jī)制的更深層實(shí)現(xiàn)可能會(huì)帶來(lái)更大的好處-例如為緩存中的某些請(qǐng)求提供服務(wù),而根本不必執(zhí)行計(jì)算-但這種實(shí)現(xiàn)絕非像淺層方法那樣簡(jiǎn)單,也不可插入在這里描述。
5.測(cè)試ETag
讓我們開(kāi)始簡(jiǎn)單–我們需要驗(yàn)證檢索單個(gè)Resource的簡(jiǎn)單請(qǐng)求的響應(yīng)是否實(shí)際上將返回“ ETag”標(biāo)頭:
@Test public void givenResourceExists_whenRetrievingResource_thenEtagIsAlsoReturned() {// GivenResource existingResource = getApi().create(new Resource());String uriOfResource = baseUri + '/' + existingResource.getId();// WhenResponse findOneResponse = RestAssured.given().header('Accept', 'application/json').get(uriOfResource);// ThenassertNotNull(findOneResponse.getHeader(HttpHeaders.ETAG)); }接下來(lái) , 我們驗(yàn)證ETag行為的幸福路徑 –如果從服務(wù)器檢索資源的請(qǐng)求使用正確的ETag值,則不再返回資源。
@Test public void givenResourceWasRetrieved_whenRetrievingAgainWithEtag_thenNotModifiedReturned() {// GivenT existingResource = getApi().create(createNewEntity());String uriOfResource = baseUri + '/' + existingResource.getId();Response findOneResponse = RestAssured.given().header('Accept', 'application/json').get(uriOfResource);String etagValue = findOneResponse.getHeader(HttpHeaders.ETAG);// WhenResponse secondFindOneResponse= RestAssured.given().header('Accept', 'application/json').headers('If-None-Match', etagValue).get(uriOfResource);// ThenassertTrue(secondFindOneResponse.getStatusCode() == 304); }一步步:
- 首先創(chuàng)建資源 ,然后再檢索–保存ETag值以備將來(lái)使用
- 發(fā)送新的檢索請(qǐng)求,這次使用“ If-None-Match ”標(biāo)題指定先前存儲(chǔ)的ETag值
- 在第二個(gè)請(qǐng)求上,服務(wù)器僅返回304 Not Modified ,因?yàn)橘Y源本身在兩次檢索操作之間確實(shí)沒(méi)有被修改。
最后 ,我們驗(yàn)證在第一個(gè)和第二個(gè)檢索請(qǐng)求之間更改資源的情況:
@Test public void givenResourceWasRetrieved_whenRetrievingAgainWithEtag_thenNotModifiedReturned() {// GivenT existingResource = getApi().create(createNewEntity());String uriOfResource = baseUri + '/' + existingResource.getId();Response findOneResponse = RestAssured.given().header('Accept', 'application/json').get(uriOfResource);String etagValue = findOneResponse.getHeader(HttpHeaders.ETAG);existingResource.setName(randomAlphabetic(6))getApi().update(existingResource.setName(randomString));// WhenResponse secondFindOneResponse= RestAssured.given().header('Accept', 'application/json').headers('If-None-Match', etagValue).get(uriOfResource);// ThenassertTrue(secondFindOneResponse.getStatusCode() == 200); }一步步:
- 首先創(chuàng)建資源 ,然后再檢索–保存ETag值以備將來(lái)使用
- 然后更新相同的資源
- 發(fā)送新的檢索請(qǐng)求,這次使用“ If-None-Match ”標(biāo)題指定先前存儲(chǔ)的ETag值
- 在第二個(gè)請(qǐng)求上,服務(wù)器將返回200 OK以及完整的Resource,因?yàn)镋Tag值不再正確,因?yàn)榕c此同時(shí)資源已更新
接下來(lái) ,我們測(cè)試“ If-Match ”的行為– ShallowEtagHeaderFilter沒(méi)有為If-Match HTTP標(biāo)頭提供開(kāi)箱即用的支持(在此JIRA問(wèn)題上進(jìn)行了跟蹤),因此以下測(cè)試應(yīng)失敗:
@Test public void givenResourceExists_whenRetrievedWithIfMatchIncorrectEtag_then412IsReceived() {// GivenT existingResource = getApi().create(createNewEntity());// WhenString uriOfResource = baseUri + '/' + existingResource.getId();Response findOneResponse = RestAssured.given().header('Accept', 'application/json').headers('If-Match', randomAlphabetic(8)).get(uriOfResource);// ThenassertTrue(findOneResponse.getStatusCode() == 412); }一步步:
- 首先創(chuàng)建資源
- 然后使用“ If-Match ”標(biāo)題檢索的資源指定了錯(cuò)誤的ETag值-這是一個(gè)有條件的GET請(qǐng)求
- 服務(wù)器應(yīng)返回412前提條件失敗
6. ETag很大
我們僅將ETag用于讀取操作 – 存在RFC,試圖闡明實(shí)現(xiàn)方式應(yīng)如何在寫入操作中處理ETag –這不是標(biāo)準(zhǔn)的,但很有趣。
當(dāng)然,ETag機(jī)制還有其他可能的用途,例如用于使用Spring 3.1的樂(lè)觀鎖定機(jī)制以及處理相關(guān)的“丟失更新問(wèn)題” 。
使用ETag時(shí),還需要注意一些已知的潛在陷阱和警告 。
7.結(jié)論
本文僅介紹了Spring和ETags所能提供的功能。 要全面實(shí)現(xiàn)啟用了ETag的RESTful服務(wù),以及用于驗(yàn)證ETag行為的集成測(cè)試,請(qǐng)查看github項(xiàng)目 。
參考:來(lái)自badung博客的JCG合作伙伴 Eugen Paraschiv 提供的Spring的ETags 。
翻譯自: https://www.javacodegeeks.com/2013/01/etags-for-rest-with-spring.html
總結(jié)
以上是生活随笔為你收集整理的带有Spring的REST的ETag的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 智驾开城:一场360度无死角的必经之战
- 下一篇: Spring属性占位符配置器–一些不太明