ASP.NET Core中的Http快取
- 2020 年 3 月 7 日
- 筆記
ASP.NET Core中的Http快取
Http
響應快取可減少客戶端或代理對web
伺服器發出的請求數。響應快取還減少了web
伺服器生成響應所需的工作量。響應快取由Http
請求中的header
控制。
而ASP.NET Core
對其都有相應的實現,並不需要了解裡面的工作細節,即可對其進行良好的控制。
了解Http
快取
Http
協議中定義了許多快取,但總體可以分為強快取和協商快取兩類。
強快取
強快取是指快取命中時,客戶端不會向伺服器發請求,瀏覽器F12
能看到響應狀態碼為200
,size
為from cache
,它的實現有以下幾種方式:
Expires – 絕對時間
示例:Expires:Thu,31 Dec 2037 23:59:59 GMT
,就表示快取有效期至2037年12月31日,在這之前瀏覽器都不會向伺服器發請求了(除非按F5
/Ctrl+F5
刷新)。
Cache-Control – 相對時間/更多控制
絕對時間是一個絕對時間,因為計算時不方便;而且服務端是依據伺服器的時間來返回,但客戶端卻需要依據客戶的時間來判斷,因此也容易失去控制。
Cache-Control
有以下選項(可以多選):
-
max-age
: 指定一個時間長度,在這個時間段內快取是有效的,單位是秒(s
)。例如設置
Cache-Control:max-age=31536000
,也就是說快取有效期為31536000/24/60/60=365
天。 s-maxage
: 同max-age
,覆蓋max-age
、Expires
,但僅適用於共享快取,在私有快取中被忽略。public
: 表明響應可以被任何對象(發送請求的客戶端、代理伺服器等等)快取。private
: 表明響應只能被單個用戶(可能是作業系統用戶、瀏覽器用戶)快取,是非共享的,不能被代理伺服器快取。no-cache
: 強制所有快取了該響應的用戶,在使用已快取的數據前,發送帶驗證器的請求到伺服器。(不是字面意思上的不快取)no-store
: 禁止快取,每次請求都要向伺服器重新獲取數據。-
must-revalidate
: 指定如果頁面是過期的,則去伺服器進行獲取。(意思是瀏覽器在某些情況下,快取失效後仍可使用老快取,加了這個頭,失效後就必須驗證,並不是字面上有沒有過期都驗證)
其中最有意思的要數no-cache
和must-revalidate
了,因為它們的表現都不是字面意義。
no-cache
並不是字面上的不快取,而是會一直服務端驗證(真實意義很像字面上的must-revalidate
)。
而must-revalidate
是只是為了給瀏覽器強調,快取過期後,千萬要遵守約定重新驗證。
協商快取
協商快取是指快取命中時,伺服器返回Http
狀態碼為304
但無內容(Body
),沒命中時返回200
有內容。
在要精細控制時,協商快取比強快取更有用,它有Last-Modified
和ETag
兩種。
Last-Modified/If-Modify-Since(對比修改時間)
示例:
伺服器:Last-Modified: Sat, 27 Jun 2015 16:48:38 GMT 客戶端:If-Modified-Since: Sat, 27 Jun 2015 16:48:38 GMT
ETag/If-None-Match(對比校驗碼)
伺服器:ETag: W/"0a0b8e05663d11:0" 客戶端:If-None-Match: W/"0a0b8e05663d11:0"
清快取要點
- 按
F5
刷新時,強快取失效 - 按
Ctrl+F5
刷新時 強快取和協商快取都失效
ASP.NET Core的Http快取
ASP.NET Core
中提供了ResponseCacheAttribute
來實現快取,它的定義如下:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)] public class ResponseCacheAttribute : Attribute, IFilterFactory, IFilterMetadata, IOrderedFilter { public ResponseCacheAttribute(); public string CacheProfileName { get; set; } public int Duration { get; set; } public bool IsReusable { get; } public ResponseCacheLocation Location { get; set; } public bool NoStore { get; set; } public int Order { get; set; } public string VaryByHeader { get; set; } public string[] VaryByQueryKeys { get; set; } }
其中,ResponseCacheLocation
定義了快取的位置,是重點:
// Determines the value for the "Cache-control" header in the response. public enum ResponseCacheLocation { // Cached in both proxies and client. Sets "Cache-control" header to "public". Any = 0, // Cached only in the client. Sets "Cache-control" header to "private". Client = 1, // "Cache-control" and "Pragma" headers are set to "no-cache". None = 2 }
注意看源文件中的注釋,Any
表示Cache-Control: public
,Client
表示Cache-Control: private
,None
表示Cache-Control: no-cache
。
注意
ResponseCacheLocation
並沒有定義將快取放到伺服器的選項。
其中Duration
表示快取時間,單位為秒,它將翻譯為max-age
。
另外可以通過VaryByHeader
和VaryByQueryKeys
來配置快取要不要通過header
和query string
來變化,其中VaryByHeader
是通過Http
協議中的Vary
頭來實現的,VaryByQueryKeys
必須通過Middleware
來實現。
不要誤會,所有
ResponseCacheAttribute
的屬性配置都不會在服務端快取你的響應數據(雖然你可能有這種錯覺),它和輸出快取不同,它沒有狀態,只用來做客戶端強快取。
如果不想快取,則設置NoStore = true
,它會設置cache-control: no-store
,我們知道no-store
的真實意思是不快取。一般還會同時設置Location = ResponseCacheLocation.None
,它會設置cache-control: no-cache
(真實意思是表示一定會驗證)。
注意單獨設置Location = ResponseCacheLocation.None
而不設置NoStore
並不會有任何效果。
示例1
這是一個很典型的使用示例:
public class HomeController : Controller { [ResponseCache(Duration = 3600, Location = ResponseCacheLocation.Client)] public IActionResult Data() { return Json(DateTime.Now); } }
我定義了3600
秒的快取,並且cache-control
應該為private
,生成的Http
快取頭可以通過如下C#
程式碼來驗證:
using var http = new HttpClient(); var resp1 = await http.GetAsync("https://localhost:55555/home/data"); Console.WriteLine(resp1.Headers.CacheControl.ToString()); Console.WriteLine(await resp1.Content.ReadAsStringAsync());
輸入結果如下:
max-age=3600, private "2020-03-07T21:35:01.5843686+08:00"
另外,ResponseCacheAttribute
也可以定義在Controller
級別上,表示整個Controller
都受到快取的影響。
CacheProfileName示例
另外,如果需要共用快取配置,可以使用CacheProfileName
,將快取提前定義好,之後直接傳入這個定義名即可使用:
.ConfigureServices(s => { s.AddControllers(o => { o.CacheProfiles.Add("3500", new CacheProfile { Duration = 3500, Location = ResponseCacheLocation.Client, }); }); });
這樣我就定義了一個名為3500
的快取,稍後在Controller
中我只需傳入CacheProfileName = 3500
即可:
public class HomeController : Controller { [ResponseCache(CacheProfileName = "3500")] public IActionResult Data() { return Json(DateTime.Now); } }
總結
Http
快取分為強快取和協商快取,ASP.NET Core
提供了便利的ResponseCacheAttribute
實現了強快取,還能通過Profile
來批量配置多個快取點。
但ASP.NET MVC
並沒有提供協商快取實現,因為這些多半和業務邏輯相關,需要自己寫程式碼。靜態文件是特例,Microsoft.AspNetCore.StaticFiles
中提供有,因為靜態文件的邏輯很清晰。
ASP.NET
中的OutputCacheAttribute
在ASP.NET Core
中不復存在,取而代之的是app/services.AddResponseCaching()
,這些和Http
協議不相關。
有機會我會具體聊聊這些快取。
喜歡的朋友請關注我的微信公眾號:【DotNet騷操作】