HTTP中的ETag在移動客戶端的應用

絕大多數移動客戶端在設計網絡模塊時,都會選用HTTP做爲客戶端和服務端通訊的網絡協議。隨着業務的不斷髮展以及用戶量的持續增加,整個客戶端的穩定性和性能會逐漸成爲關注的焦點,其中網絡的性能優化更是重中之重,本文介紹的 ETag 緩存技術,能夠在緩存數據的同時作到數據的實時更新,適用於對數據實效性要求較高的業務。html

基本原理和概念

相同的兩次請求返回的結果相同時,第一次返回的結果緩存在客戶端,第二次服務端再也不返回結果,僅返回一個特殊的狀態碼,告訴客戶端第二次請求的結果與上次相同,能夠直接使用上次返回的數據。swift

實現中,會用到HTTP頭中的兩個字段:緩存

  • ETag 返回應答數據的標記,服務端生成發送給客戶端性能優化

  • If-None-Match 一樣的請求,上一次返回的 ETag 值服務器

交互過程

  • 服務端在將數據發送給客戶端以前,首先計算應答數據的摘要(一般是MD5),把計算結果做爲 ETag 的值,和數據一同發送給客戶端。網絡

  • 客戶端收到應答數據後,檢測 HTTP Header 中是否有 ETag 字段,若有則緩存應答數據和 ETag 的值。session

  • 客戶端再次發起同一請求時,讀取上次緩存的 ETag 值,將其做爲 If-None-Match 的值,並與請求數據一同發送給服務端。性能

  • 服務端收到請求,執行請求,在把應答數據返回給客戶端以前計算摘要,並與客戶端上報的摘要比較,若是兩次摘要相同,說明本次的應答數據與上一次請求的應答數據相同,且客戶端已緩存該數據,則簡單返回304。優化

  • 客戶端收到304,直接讀取本地緩存的數據返回給調用網絡模塊的業務方。url

交互過程總結以下圖:

ETag

代碼實現

本文的示例代碼使用 NSURLSession 實現,因爲 NSURLSession 完善的緩存策略,爲了演示 ETag 的用法,須要先關閉緩存。

let config = NSURLSessionConfiguration.defaultSessionConfiguration()
config.requestCachePolicy = NSURLRequestCachePolicy.ReloadIgnoringCacheData
let session = NSURLSession(configuration: config)

NSURLSessionConfiguration 定義了 NSURLSession 在上傳和下載時的行爲及策略,咱們指定了請求的緩存策略爲 ReloadIgnoringCacheData, 意思就是在請求時不使用本地緩存。

建立請求的 NSURLRequest 對象:

let url = NSURL(string: "http://www.joywek.com/50x.html")!
let request = NSMutableURLRequest(URL: url)

上面會下載指定靜態頁面的內容,這個頁面是放在 Nginx 的服務器上,Nginx 默認會對應答數據計算 ETag。固然,在實際的應用中請求的都是動態數據,服務器要動態計算 ETag 的值。

設置 HTTP Header 中的 If-None-Match 字段:

if let tag = self.findTagByURL(url) {
    request.addValue(tag, forHTTPHeaderField: "If-None-Match")
}

在發起請求時,先檢查相同的請求是否存在 ETag,若是存在就,就意味着上次請求的應答數據已緩存。

發起請求並處理應答數據:

self.dataTask = session.dataTaskWithRequest(request,
    completionHandler: { (var data, response, error) -> Void in
        data = self.handleResponse(response!, data!, request)
})
self.dataTask?.resume()

若是 HTTP 返回的狀態碼是 200,說明是服務器正常返回數據,此時記錄 ETag 的值並緩存應答數據:

if (resp.statusCode == 200) {
    self.etags[response.URL!] = resp.allHeaderFields["ETag"] as? String
    let cachedResponse = NSCachedURLResponse(response: resp, data: data)
    NSURLCache.sharedURLCache().storeCachedResponse(cachedResponse, forRequest: request)
    return data
}

若是返回 304,說明應答數據沒有變化,與上次請求的同樣,則直接返回緩存中的數據:

else if (resp.statusCode == 304) {
    let cachedResponse = NSURLCache.sharedURLCache().cachedResponseForRequest(request)
    return cachedResponse?.data
}

關於演示 Demo

下載地址:http://www.joywek.com/res/ETagExample.zip

相關文章
相關標籤/搜索