ASP.NET Core MVC 的 Cache control 與 Response cache middleware - 上集

  • 1049
  • 0

在 web 環境下,可以發生 cache 的地方有三種類型,第一是用戶端,也就是發出 HTTP request 的地方,簡單的說就是你電腦上的 Chrome, Firefox 這類的瀏覽器.第二是 network proxy,它是網路上的一種服務,可將內容暫存下來,通常是由你的 ISP 公司所提供的服務.第三是伺服器端,也就是 cache content 的來源產生地,也就是 HTTP response 的起源,簡單的說就是你灠覽器所連接的目的地.依目前的 HTTP 規格而言,ASP.NET Core 所能夠控制 cache 的地方就是這三個.Response cache middleware 是用在伺服器端用的元件,用來定義伺服器端 cache 的規則和行為.這篇文章將從用戶端的 cache 先談起,然後下一篇文章再談到伺服器端的 Response cache middleware.
 

在 HTTP request / response 的過程中,cache 行為都定義在 HTTP 1.1 規格中的 Cache-Control.這是 HTTP Header 裡的一員.你可以在 HTTP request 中使用 Cache-Control,也可以在 HTTP response 中使用它.以一般的商業應用程式而言,用在 HTTP response 的情況相對較多.以 HTTP response 而言,當 HTTP Header 中有 Cache-Control 出現時,代表伺服器端能為該網頁 (HTTP 裡的內容) 來定義它在用戶端裡的 cache 方式.比較常見的 cache 指令就是 no-cache 和 no-store. No-cache 並非完全限制用戶端不 cache 資料,而是讓伺服器端有彈性來建議用戶端是否要用 cache 資料 (還需要其他 HTTP header 一起配合),例如伺服器端回應時給 HTTP status code = 304 並且 cache-ctronol 有 no-cache 時,則代表伺服器告訴用戶端用自己的 cache 資料顯示給用戶看即可.no-store 就是伺服器端告訴用戶端,發出 HTTP request 時,此 HTTP request 必須要送到伺服器端,不能使用用戶端裡的 cache 來回應這個 HTTP request.除以之外,其他的指令還有 public, private,only-if-cache 等,這些指令都是用來控制是否能有 cache 的行為.

Public 指令是指網頁內容可以放在用戶端的 cache 裡,如果某一個 HTTP request 所請求的內容在 cache 裡並且該內容是 public 時,則用戶端就可以直接從 cache 裡將資料讀出來顯示在畫面上,而不用將 HTTP request 送到伺服器端.

Private 指令是使用 cache 能發生,但只適用在某個使用者中,也就是說這不是一個共用的 cache,而是針對不同的使用者所建立獨立的 cache.

Only-if-cached 指令是讓舊有的資料產生 cache 行為,既便是伺服器端有了新版本的資料,用戶端也不會將 Http request 送到伺服器端.

這些是用來控制 Cache 行為是否能發生以及發生時該具有什麼樣的行為.除了這些指令以外,HTTP 1.1 Cache-Control 也定義 cache 具有有效期限的功能.例如 max-age 指令用來定義當資料放到 cache 時何時會過期.假設 max-age=10 代表這份資料能在 cache 中有十秒的合法期限,這代表當用戶端在十秒內想要對伺服器端發出 HTTP request 要求這份資料時,這 HTTP request 將不會到達伺服器端,因為用戶端將直接把 cache 裡的內容回傳.

有關 Cache-Control 所有的內容將記錄在 HTTP 1.1 規格裡,這篇文章不會把所有指令都說明一遍.如果你覺得 HTTP 1.1 規格較不好懂,建議你可以參考  Mozilla 的 Cache-Control 文件,較為精簡易懂.

在 ASP.NET Core MVC 裡提供了兩種簡易的方式來設定 cache,一個是 Cache tag helper,另一個是 ResponseCache attribute.

之前的文章中曾介紹過 tag helper 以及如何撰寫 tag helper.ASP.NET Core MVC 內建提供了 Cache tag helper,讓開發者可以很簡單的將網頁內容設定為 cache 內容.接著看一個簡單的例子,在 cshtml 中撰寫以下的內容
 

<cache>@DateTime.Now</cache>

這個時間值將會被暫存二十分鐘,也就是說當你發出第一個 Http request 到這網頁時,你會看到當時的時間值,而當你再重新發出一個 HTTP request 到相同網頁時,你會發現你看到的時間值跟前面是一樣的值.預設上 Razor View engine 設定的過期時間為二十分鐘後,所以該時間值將被保留二十分鐘.

Cache tag helper 還提供其他的屬性可以設定,這篇文章不再對其他屬性多做介紹.其他的屬性資料可以在 Cache tag helper 官方文件中找到.

除了 Cache tag helper 之外,ASP.NET Core MVC 提供了 ResponseCache 屬性可以讓開發者以 Controller 或是 Controller 的 Action 產生的內容為單位來設定 cache.底下是一個簡單的例子,
 

[ResponseCache(Duration = 60)]
public IActionResult About()
{
	ViewData["Time"] = DateTime.Now.ToString();
}

以上的例子將會把 Time 的過期時間設定在六十秒後,也就是說在六十秒內,你對 About() action 發出 HTTP request 時,你得到的 Time 會是同一個值.當你用瀏覽器的 F12 工具或是像 POSTMAN 之類的工具來觀察 HTTP 連線內容時,你將會看到 HTTP Response header 裡的 cache-control 的內容是 public, max-age=60.此時應該能讓你了解到 ResponseCache attribute 就是方便讓你設定 cache-control 之處了.再來看另外一個例子,

[ResponseCache(Location = ResponseCacheLocation.Client)]
public IActionResult About()
{
	ViewData["Time"] = DateTime.Now.ToString();
}

ResponseCacheLocation 是一個 enum,用來定義 cache 所用的空間所在地.當設定為 Client 時,相當於將 cache-control 設定為 private,等於 cache 的內容不會在瀏覽器之間共用,自己只能用自己的 cache.有關這屬性的細節,你可以參考 ASP.NET Core 的 api 文件

再來看一個不同的例子,
 

[ResponseCache(Location = ResponseCacheLocation.Any, NoStore =true )]
public IActionResult About()
{
	ViewData["Time"] = DateTime.Now.ToString();

	return View();
}

你為 cache-control 指定了 no-store,同時也指定了 public (Location = Any),這讓 Location 看起來是多餘的.

Cache-control 跟 Vary header 也可以一起合作.Vary header 基本上就是用來對內容做 “差異化” 的辦別.伺服器回傳資料給用戶端時,可以先回傳多個內容選擇給用戶端,讓用戶端決定用什麼樣的內容顯示給用戶,例如不同語言,不同文件類型,有沒有壓縮等.不同的用戶端不見得能用同一份資料,例如手機用戶端和一般桌機用戶端所接收到的資料理論上應該有所不同.Vary header 便用來定義這些文件還要依什麼條件而來將內容視為不同.很常見的就是 User-Agent.因為手機用戶端和桌機用戶端使用的瀏覽器應該要傳出不同的 User-Agent 內容.因此,透過 Vary 的內容也能告訴 cache-control 該如何回應 cache 資料,以免 cache-control 回傳不適合的 cache 資料.以下是一個使用 Vary = User-Agent 的例子,
 

[ResponseCache(VaryByHeader = "User-Agent", Duration = 30)]
public IActionResult About()
{
}

VaryByHeader 便是用來設定 Vary header 的內容.除此之外,ResponseCache 屬性還能針對 URL 裡不同的 query string 來執行 cache 功能,這是透過 VaryByQueryKeys 來執行.如下面的例子,
 

[ResponseCache(VaryByQueryKeys = new string[]{"key1"}, Duration =60 )]
public IActionResult About()
{
}

當前一個 Http request url 是 http://{server_address}/{controller}/{action}?key1=value1 而且後一個 Http request 也是用相同的 query string (key1=valuye1),則 cache-control 將會有作用.Http request 被伺服器收到進入到 middleware 層層處理時,資料將會直接從 Response cache middleware 回傳,而不需要再進入到 MVC middleware 來處理.不過這項功能必須搭配 Response cache middleware 才能達成.

下一篇的文章將介紹 Response cache middleware.

Hope it helps,