基礎知識
1) 什麼是”Last-Modified”?
在瀏覽器第一次請求某一個URL時,伺服器端的返回狀態會是200,內容是你請求的資源,同時有一個Last-Modified的屬性標記此檔案在服務期端最後被修改的時間,格式類似這樣:
Tue, 24 Apr 2012 13:53:56 GMT
用戶端第二次請求此URL時,根據 HTTP 協議的規定,瀏覽器會向伺服器傳送 If-Modified-Since 前序,詢問該時間之後檔案是否有被修改過:
If-Modified-Since: Tue, 24 Apr 2012 13:53:56 GMT
如果伺服器端的資源沒有變化,則自動返回 HTTP 304 (Not Changed.)狀態代碼,內容為空白,這樣就節省了傳輸資料量。當伺服器端代碼發生改變或者重啟伺服器時,則重新發出資源,返回和第一次請求時類似。從而保證不向用戶端重複發出資源,也保證當伺服器有變化時,用戶端能夠得到最新的資源。
2) 什麼是”Etag”?
HTTP 協議規格說明定義ETag為“被請求變數的實體值” 。 另一種說法是,ETag是一個可以與Web資源關聯的記號(token)。典型的Web資源可以一個Web頁,但也可能是JSON或XML文檔。伺服器單獨負責判斷記號是什麼及其含義,並在HTTP回應標頭中將其傳送到用戶端,以下是伺服器端返回的格式:
"9077da2dec72bbb7151a6579fa214de0"
用戶端的查詢更新格式是這樣的:
"9077da2dec72bbb7151a6579fa214de0"
如果ETag沒改變,則返回狀態304然後不返回,這也和Last-Modified一樣。
Last-Modified和Etags如何協助提高效能?
聰明的開發人員會把Last-Modified 和ETags請求的http前序一起使用,這樣可利用用戶端(例如瀏覽器)的緩衝。因為伺服器首先產生 Last-Modified/Etag標記,伺服器可在稍後使用它來判斷頁面是否已經被修改。本質上,用戶端通過將該記號傳回伺服器要求伺服器驗證其(用戶端)緩衝。
過程如下:
1,用戶端請求一個頁面(A)。
2,伺服器返回頁面A,並在給A加上一個Last-Modified/ETag。
3,用戶端展現該頁面,並將頁面連同Last-Modified/ETag一起緩衝。
4,客戶再次請求頁面A,並將上次請求時伺服器返回的Last-Modified/ETag一起傳遞給伺服器。
5,伺服器檢查該Last-Modified或ETag,並判斷出該頁面自上次用戶端請求之後還未被修改,直接返迴響應304和一個空的響應體。
正確使用Etag和Expires標識處理,可以使得頁面更加有效被Cache。
在用戶端通過瀏覽器發出第一次請求某一個URL時,根據 HTTP 協議的規定,瀏覽器會向伺服器傳送前序(Http Request Header),伺服器端響應同時記錄相關屬性標記(Http Reponse Header),伺服器端的返回狀態會是200,格式類似如下:
HTTP/1.1 200 OK
Cache-Control: public, max-age=1728000
Transfer-Encoding: chunked
Content-Type: image/jpeg
Expires: Sun, 20 May 2012 16:26:22 GMT
Last-Modified: Tue, 24 Apr 2012 13:53:56 GMT
ETag: "9077da2dec72bbb7151a6579fa214de0"
Server: Microsoft-IIS/7.5
X-AspNet-Version: 4.0.30319
X-Powered-By: ASP.NET
Date: Mon, 30 Apr 2012 16:26:22 GMT
用戶端第二次請求此URL時,根據 HTTP 協議的規定,瀏覽器會向伺服器傳送前序(Http Request Header),伺服器端響應並記錄相關記錄屬性標記檔案沒有發生改動,伺服器端返回304,直接從緩衝中讀取:
HTTP/1.1 304 Not Modified
Cache-Control: no-cache
Pragma: no-cache
Expires: -1
Server: Microsoft-IIS/7.5
X-AspNet-Version: 4.0.30319
X-Powered-By: ASP.NET
Date: Mon, 30 Apr 2012 16:30:06 GMT
asp.net web api的實現代碼如下:
| 代碼如下 |
複製代碼 |
// GET /img/2012031023134652.png [HttpGet] public HttpResponseMessage Get([FromUri]string filename) { HttpResponseMessage response = new HttpResponseMessage(); MongoFSDirectory fs = new MongoFSDirectory(new MongoFSDirectoryParameters() { ConnectionString = this.ConnectionString }); MongoGridFSFileInfo fileInfo = fs.GetFileInfo(filename); if (fileInfo == null) { throw new HttpResponseException("The file does not exist.", HttpStatusCode.NotFound); } string etag = string.Format(""{0}"", fileInfo.MD5); var tag = Request.Headers.IfNoneMatch.FirstOrDefault(); if (Request.Headers.IfModifiedSince.HasValue && tag != null && tag.Tag == etag) { response.StatusCode = HttpStatusCode.NotModified; } else { MemoryStream responseStream = new MemoryStream(); MongoGridFSStream gfs = fileInfo.OpenRead(); bool fullContent = true; if (this.Request.Headers.Range != null) { fullContent = false; // Currently we only support a single range. RangeItemHeaderValue range = this.Request.Headers.Range.Ranges.First(); // From specified, so seek to the requested position. if (range.From != null) { gfs.Seek(range.From.Value, SeekOrigin.Begin); // In this case, actually the complete file will be returned. if (range.From == 0 && (range.To == null || range.To >= gfs.Length)) { gfs.CopyTo(responseStream); fullContent = true; } } if (range.To != null) { // 10-20, return the range. if (range.From != null) { long? rangeLength = range.To - range.From; int length = (int)Math.Min(rangeLength.Value, gfs.Length - range.From.Value); byte[] buffer = new byte[length]; gfs.Read(buffer, 0, length); responseStream.Write(buffer, 0, length); } // -20, return the bytes from beginning to the specified value. else { int length = (int)Math.Min(range.To.Value, gfs.Length); byte[] buffer = new byte[length]; gfs.Read(buffer, 0, length); responseStream.Write(buffer, 0, length); } } // No Range.To else { // 10-, return from the specified value to the end of file. if (range.From != null) { if (range.From < gfs.Length) { int length = (int)(gfs.Length - range.From.Value); byte[] buffer = new byte[length]; gfs.Read(buffer, 0, length); responseStream.Write(buffer, 0, length); } } } } // No Range header. Return the complete file. else { gfs.CopyTo(responseStream); } gfs.Close(); responseStream.Position = 0; response.StatusCode = fullContent ? HttpStatusCode.OK : HttpStatusCode.PartialContent; response.Content = new StreamContent(responseStream); response.Content.Headers.ContentType = new MediaTypeHeaderValue(fileInfo.ContentType); response.Headers.ETag = new EntityTagHeaderValue(etag); response.Headers.CacheControl = new CacheControlHeaderValue(); response.Headers.CacheControl.Public = true; response.Headers.CacheControl.MaxAge = TimeSpan.FromHours(480); response.Content.Headers.Expires = DateTimeOffset.Now.AddDays(20); response.Content.Headers.LastModified = fileInfo.UploadDate; } return response; } |
參考:
sample message handler for managing etags in web api
http://codepaste.net/4w6c6i