ASP.NET Core中的Http快取
阿新 • • 發佈:2020-03-08
# ASP.NET Core中的Http快取
`Http`響應快取可減少客戶端或代理對`web`伺服器發出的請求數。響應快取還減少了`web`伺服器生成響應所需的工作量。響應快取由`Http`請求中的`header`控制。
而`ASP.NET Core`對其都有相應的實現,並不需要了解裡面的工作細節,即可對其進行良好的控制。
# 瞭解`Http`快取
`Http`協議中定義了許多快取,但總體可以分為**強快取**和**協商快取**兩類。
![](https://img2020.cnblogs.com/blog/233608/202003/233608-20200307220320298-2001395125.png)
## 強快取
**強快取**是指快取命中時,客戶端不會向伺服器發請求,瀏覽器`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`有以下選項(可以多選):
1. `max-age`: 指定一個時間長度,在這個時間段內快取是有效的,單位是秒(`s`)。
例如設定`Cache-Control:max-age=31536000`,也就是說快取有效期為`31536000/24/60/60=365`天。
2. `s-maxage`: 同`max-age`,覆蓋`max-age`、`Expires`,但僅適用於共享快取,在私有快取中被忽略。
3. `public`: 表明響應可以被任何物件(傳送請求的客戶端、代理伺服器等等)快取。
4. `private`: 表明響應只能被單個使用者(可能是作業系統使用者、瀏覽器使用者)快取,是非共享的,不能被代理伺服器快取。
5. `no-cache`: 強制所有快取了該響應的使用者,在使用已快取的資料前,傳送帶驗證器的請求到伺服器。(**不是字面意思上的不快取**)
6. `no-store`: 禁止快取,每次請求都要向伺服器重新獲取資料。
7. `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`來實現快取,它的定義如下:
```csharp
[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`定義了快取的位置,是重點:
```csharp
// 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
這是一個很典型的使用示例:
```csharp
public class HomeController : Controller
{
[ResponseCache(Duration = 3600, Location = ResponseCacheLocation.Client)]
public IActionResult Data()
{
return Json(DateTime.Now);
}
}
```
我定義了`3600`秒的快取,並且`cache-control`應該為`private`,生成的`Http`快取頭可以通過如下`C#`程式碼來驗證:
```csharp
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`,將快取提前定義好,之後直接傳入這個定義名即可使用:
```csharp
.ConfigureServices(s =>
{
s.AddControllers(o =>
{
o.CacheProfiles.Add("3500", new CacheProfile
{
Duration = 3500,
Location = ResponseCacheLocation.Client,
});
});
});
```
這樣我就定義了一個名為`3500`的快取,稍後在`Controller`中我只需傳入`CacheProfileName = 3500`即可:
```csharp
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騷操作】
![DotNet騷操作](https://img2018.cnblogs.com/blog/233608/201908/233608-20190825165420518-990227