http響應Last-Modified和ETag以及asp.net web api實現
基礎知識
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;
}
參考:
歡迎大家掃描下面二維碼成為我的客戶,扶你上云

浙公網安備 33010602011771號