The Will Will Web

記載著 Will 在網路世界的學習心得與技術分享

解決 OutputCacheLocation 指定為 ServerAndClient 的問題

Web 效能調校博大精深,眉眉角角的細節之多實在難以整理,我想大部分人也都跟我一樣,也是從一次又一次與茶包戰鬥中累積經驗,這次又遇到了一個有趣的問題。當我想將頁面設定輸出快取時,可能會希望同時快取在伺服器端(Server)與用戶端(Client),因此在設定 OutputCache 時有個 Location 屬性,我們會將其設定為 ServerAndClient 或 Any,但是你可否知道即便你這樣設定,一樣無法快取網頁內容於用戶端(Client),所以單單這樣設定並沒有達到我們真正想達到的目的 ( 同時在 Server 與 Client 快取網頁 )。

我簡單寫了一個測試輸出快取的網頁如下:

ASP.NET Web Form 範例

<%@ Page Language="C#" AutoEventWireup="true" 
         CodeBehind="OutputCacheTest.aspx.cs" 
         Inherits="MvcApplication5.OutputCacheTest" %>

<%@ OutputCache Duration="300" 
                VaryByParam="*" 
                Location="ServerAndClient" %>

<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
    <title></title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <%=DateTime.Now %>
    </div>
    </form>
</body>
</html>

ASP.NET MVC 範例

OutputCacheLocation 設定成 ServerAndClient 與 Any 的差別

ServerAndClient 只會將網頁快取在 伺服器用戶端

Any 除了快取在 伺服器用戶端 之外還會快取網頁在 代理伺服器端(Proxy)

我用 HttpWatch 軟體查看我網頁的 HTTP 通訊過程,發現了以下狀況 (點圖可放大):

同樣一頁雖然已經設定了輸出快取,而且將 Location 屬性設定為 ServerAndClient,但是連續讀取兩次同樣的網頁卻完全沒有快取的樣子,每次都還是向伺服器取得網頁內容,你從 Result 為 200 或 Received 的位元數不為 0 即可判斷出來。

發生問題的主因就出在 ASP.NET 多送出了一個 Vary 標頭,導致用戶端不會快取這一頁,不過這一頁確實已經在伺服器端快取了,所以網頁瀏覽速度的確會變快,不知道這樣檢視的人可能就會不知不覺的用下去,以為網頁已經達到 Web 效能調校的極限。

我看到這個問題時覺得非常詭異,這非常明顯是個 Bug,但為何到了 ASP.NET 3.5 都還沒有修正呢?不過這問題已經確定在 ASP.NET 4 修正,從 ASP.NET 4 開始不再會送出 Vary 標頭,所以用戶端快取終於可以順利的正常運作了。

看了 ASP.NET 4 Release Candidate (RC) Breaking Changes 才發現這是有歷史包袱的,原來此問題從 ASP.NET 1.0 開始就被發現,到了 ASP.NET 1.1 的時候新增了一個 HttpCachePolicy.SetOmitVaryStar 方法 試圖解決這個問題,但你必須在 Code Behind 或 Controller 裡執行一次這個方法才能有效去除 Vary 標頭,不過我想能看完 MSDN 文件的人應該不多吧!

到了 ASP.NET 2.0 開發團隊可能認為他們已經有 HttpCachePolicy.SetOmitVaryStar 方法可用,所以也不處理這個 Bug,到了 ASP.NET 3.5 因為直接繼承 ASP.NET 2.0 的關係所以也沒有進一步處理,直到 ASP.NET 4 這次大幅更新的時刻才決定將此問題徹底解決!

所以如果你還沒辦法升級到 ASP.NET 4 請參考以下方法:

ASP.NET Web Form 的解決之道

public partial class OutputCacheTest : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        Response.Cache.SetOmitVaryStar(true);
    }
}

ASP.NET MVC 的解決之道

[OutputCache(Duration = 300, VaryByParam = "*",
             Location = OutputCacheLocation.Any)]
public ActionResult About()
{
    Response.Cache.SetOmitVaryStar(true);

    return View();
}

上述改善後的程式我們再用 HttpWatch 測試一次該網頁在用戶端快取的情況,如下圖示 (點圖可放大):

透過上圖可知,我的網頁在第一次下載的時候花了 2.222 秒,但在第二次下載時已經完全被用戶端快取,所以瀏覽器完全沒有發出任何 HTTP Request 到伺服器端,不用從遠端下載網頁所以網頁瀏覽速度當然快,兩者速度差了近 1111 倍之多,而這也是 Web 效能調校迷人的地方!

相關連結