細說 ASP.NET控制HTTP緩衝

來源:互聯網
上載者:User

在上篇部落格【細說 ASP.NET Cache 及其進階用法】中,
我給大家介紹了ASP.NET Cache,這種服務端使用的緩衝API 。在我們開發一個ASP.NET網站的過程中,其實有很多地方都是可以使用緩衝的,
只是由於ASP.NET是一種基於服務端的開發平台,自然我們也經常在服務端的代碼中使用各類緩衝技術,
然而,由於WEB應用程式的服務物件是用戶端的瀏覽器,通常來說,我們並不能直接控制瀏覽器的行為,但是,
瀏覽器卻可以根據後台網站的指示,採取一些最佳化的方式來更快地呈現頁面。
用戶端瀏覽器也有自己的緩衝機制,通常瀏覽器也使用緩衝來最佳化一些頁面的顯示過程,
不過,我們並不能直接使用C#代碼控制瀏覽器的快取作業,但我們可以告訴瀏覽器如何使用緩衝,從而達到最佳化網站效能的目的。

這次部落格的主題是:用ASP.NET控制HTTP請求過程中瀏覽器緩衝的一些方法。

正常的HTTP請求過程

在開始介紹瀏覽器在HTTP請求過程前,我想有必要先來看一下瀏覽器請求一個普通ASPX頁面的過程。
說明:本文在介紹HTTP請求過程時,會大量使用Fiddler來分析具體的請求過程。

是一個普通的ASPX頁面的請求過程,說它普通是因為:我在建立這個頁面後,沒對它做任何緩衝方面的處理。
圖片中我們可以可以看到伺服器的響應狀態為:HTTP/1.1 200 OK,這是一個伺服器成功響應的標誌。
另外,要注意圖片中的Cache回應標頭部分,我之所以就紅線框出來,是想提醒您注意這塊的內容將在後面的小節中發生改變,
到時候請注意對比它們。而這裡所反映的情況其實也只是預設值而已,它並不表示此頁面需要緩衝。

快取頁面的請求過程

下面再來看一個快取頁面面的請求過程:

對比上一張圖片中可以看出,現在多了【max-age】,【Expires】以及【Last-Modified】這三個回應標頭。

這三個頭的含義如下:
1. max-age,Expires:要表達的意思基本差不多。max-age表示某次HTTP的響應結果應該緩衝多少秒。
   而Expires是說某次HTTP的響應結果應緩衝到什麼時候到期,此時間是一個UTC時間。
   另一個Date頭表示HTPP響應的發出時間,我們可以發現 Date + max-age = Expires
2. Last-Modified:服務端告訴用戶端本次響應返回的HTTP文檔的最後修改時間。這個頭與304的實現有關,後面再來解釋。

分析了HTTP請求過程後,我們再來看一下服務端的頁面是什麼樣子的:

<%@ Page Language="C#" %><%@ OutputCache Duration="10" VaryByParam="None" %><html xmlns="http://www.w3.org/1999/xhtml"><head>    <title>http://www.cnblogs.com/fish-li/</title></head><body>    <p>頁面產生時間:<%= DateTime.Now.ToString() %></p>    <p><a href="Default.aspx">回到首頁</a>        <a href="<%= Request.RawUrl %>">重新整理本頁</a>    </p></body></html>

注意:上面代碼中最關鍵的一行代碼為:

<%@ OutputCache Duration="10" VaryByParam="None" %>

正是由於使用了這個OutputCache指令,最後才會輸出上面那幾個回應標頭,用來告訴瀏覽器此頁面需要緩衝10秒鐘。

說到這裡,可能有些人想有疑惑了:快取頁面在什麼時候會起到什麼作用呢?
為了示範快取頁面所帶來的現實意義,我將點擊頁面的這些連結並以的形式來說明:在一系列請求過程中頁面的顯示情況,
並以頁面的顯示結果來分析緩衝所起的作用。

先來看看這個頁面的顯示:

頁面很簡單,主要是顯示了頁面的產生時間與一個重新整理連結。
從上面提供的頁面代碼,我們應該能知道這個頁面如果是由服務端產生的,則會顯示當前的時間。

不過呢,當我一直(頻繁)點擊【重新整理本頁】那個連結時,頁面的時間並沒有發生改變,當我發現時間改變時,頁面已顯示成這個樣子了:

由於測試過程中,我一直開啟了Fiddler,正好我也把Fiddler監視到的請求結果下來了:

從Fiddler中,我看到FireFox其實只發生了二次請求,但我點擊那個【重新整理本頁】起碼超過10次。

以上的這一切,只說明一個事實:如果頁面需要跳轉到某個快取頁面時,且那個快取頁面還沒到期,那麼瀏覽器並不會發起到伺服器的請求,而是使用快取頁面。

小結:頁面緩衝所帶來的好處是:快取頁面面在到期前,使用者通過點擊跳轉連結所引發的後續訪問,並不會再次請求伺服器。
這對伺服器來說可以減少許多訪問次數,因此使用這個特性可以很好地改善程式效能。

快取頁面的服務端編程

前面示範了使用OutputCache指令所產生的快取頁面的效果,由於那些指令需要在頁面的設計階段就寫到頁面上,因此顯得不夠靈活,
不能在運行時調整,雖然ASP.NET也允許我們使用CacheProfile來引入定義在Web.config中的配置,但配置還是沒有運行時的代碼靈活。
我們再來看看如何用代碼來實現上面的效果。

其實用代碼實現快取頁面也很簡單,只需要這樣就可以了:

protected void Page_Load(object sender, EventArgs e){    Response.Cache.SetCacheability(HttpCacheability.Public);    Response.Cache.SetExpires(DateTime.Now.AddSeconds(10.0));}

其實關鍵也就是對Response.Cache的調用。
注意:Response.Cache與我上篇
【細說 ASP.NET Cache 及其進階用法】部落格所講的Cache不是一回事,二者完全不相干。
Response.Cache提供:用於設定緩衝特定的 HTTP 標題的方法和用於控制 ASP.NET 頁輸出緩衝的方法。

我們還是來說前面的二段範例程式碼。可能有些人會想,它們最終的結果真的會是一致的嗎?

要想回答這個問題,我想有必要看一下前面用OutputCache指令的那個頁面最終啟動並執行代碼是個什麼樣子的。

在ASP.NET的臨時編譯目錄中,我找到了前面那個檔案的一個由ASP.NET處理後的版本:

private static System.Web.UI.OutputCacheParameters @__outputCacheSettings = null;public demo1_aspx() {    // ........ 刪除掉一些與Cache無關的代碼    if ((global::ASP.demo1_aspx.@__outputCacheSettings == null)) {        System.Web.UI.OutputCacheParameters outputCacheSettings;        outputCacheSettings = new System.Web.UI.OutputCacheParameters();        outputCacheSettings.Duration = 20;        outputCacheSettings.VaryByParam = null;        global::ASP.demo1_aspx.@__outputCacheSettings = outputCacheSettings;    }}protected override void FrameworkInitialize() {    base.FrameworkInitialize();    // ........     this.InitOutputCache(global::ASP.demo1_aspx.@__outputCacheSettings);    this.Request.ValidateInput();}

我們可以看到頁面針對OutputCache指令的設定,最終會調用Page類定義一個方法中:

protected internal virtual void InitOutputCache(OutputCacheParameters cacheSettings)

那個方法實在太長,最終的處理方式也還是在調用this.Response.Cache,有興趣的可以自己去看看那個方法。
至於這個方法的參數為什麼是OutputCacheParameters,我想這個容易理解:方便將OutputCache指令的參數全部一起傳入嘛。

所以,也正因為這個緣故,我們也可以直接在代碼中調用Response.Cache的一些方法來實現相同的效果,
由於代碼可以在運行時根據各種參數調整緩衝策略,因此會更加靈活,而且可以採用基類的繼承方式來簡化實現。

注意:如果使用OutputCache指令再配合OutputCache Module的使用,可以實現304的效果。

什麼是304應答?

通過前面的樣本,我們已經看到緩衝帶來的好處:那就是可以減少到伺服器的訪問,由於不訪問伺服器就能顯示頁面,這對於伺服器來說,
能減輕一定的訪問壓力。但是,如果使用者強制重新整理瀏覽器,那麼瀏覽器將會忽略快取頁面,直接向伺服器重新發起請求。

也就是說:快取頁面在使用者強制重新整理瀏覽器時會無效。
但是,我們之所以使用快取頁面,是因為我們希望告訴瀏覽器:這些資料在一定時間內,並不會發生變化,因此根本不需要再次請求伺服器了。
然而,我們不能阻止使用者的行為。由於瀏覽器的重新訪問,我們原來設想的緩衝想法將會落空,最後的結果是:
頁面在伺服器中重新執行,產生的HTML代碼將重新發送到用戶端。而這一重新重新整理的結果可能也是無意義的,因為資料可能根本沒有發生變化,
因此得到的頁面也是不可能有變化的。

再來舉個簡單的例子來說吧:用戶端要瀏覽一張圖片。
當瀏覽器第一次要訪問圖片時,瀏覽器肯定是沒有它的任何緩衝記錄的,此時它去訪問伺服器,伺服器也返回圖片的內容了。
但由於圖片可能會被多個頁面所引用,而它被修改的可能性是很小的。
因此沒有必要為同一瀏覽器的多次請求都去讀取圖片並返回圖片的內容,這樣做既影響效能也學浪費頻寬。
於是,像IIS這樣伺服器軟體針對這類靜態檔案的訪問時,都會在回應標頭上輸出一些標記,用來告之瀏覽器這個檔案你可以緩衝起來了。

還是回到前面所說的【使用者強制重新整理】問題,此時的IIS又會如何處理呢?請看:

注意哦,此時除了HTTP狀態代碼變成304之外,沒有任何資料返回哦。

為了讓您對304應答有個深刻的印象,我截了一張狀態代碼為200的圖片響應結果:

通過這二張圖片的對比,現在看清楚了吧:304和200並不只是數字上的差別,最重要的差別在於有沒有返回結果。

沒有返回結果,瀏覽器該如何顯示?
您會有這樣的疑慮嗎?
其實不用擔心,此時瀏覽器會使用它緩衝版本來顯示。也就是說:不管使用者如何強制刷,伺服器就是不返回結果,但仍然可以正常顯示。

顯然,這個效果就是我們想要的。
前面所說的快取頁面遭使用者強刷的問題,如果採用這種方法,就比較完美了。
不過,有一點我要提醒您:Visual Studio內建的那個WebDev.WebServer.exe不支援304應答,所以您就不要拿它實驗了,不會有結果的。

如何編程實現304應答

前面我們看到了304應答的效果。不過,在ASP.NET中,我們開發的程式,是動態網頁面,而不是圖片,
我們更希望某個頁面能以這種方式緩衝一段時間,我想這個需求或許會更有意義。

下面,我就來示範如何通過編程的方式實現它。
接下來的樣本中,頁面的顯示還是那個樣,顯示頁面在伺服器上產生的時間,時間變化了,說明頁面被重新執行了。

重新截一系列的圖片,我認為意義也不大,我就截一張圖片展現多次強刷而產生的過程

反映了我多次請求某個ASPX頁面的過程,從圖片中可以看出,只有第一次是200的響應,後面全是304,是您所期待的結果吧。

再來看看它的實現代碼吧:

public partial class Demo304 : System.Web.UI.Page{    protected void Page_Load(object sender, EventArgs e)    {        DateTime dt;        if( DateTime.TryParse(Request.Headers["If-Modified-Since"], out dt) ) {            // 注意:如果是20秒內,我就以304的方式響應。            if( (DateTime.Now - dt).TotalSeconds < 20.0 ) {                Response.StatusCode = 304;                Response.End();                return;            }        }        // 注意這個調用,它可以產生"Last-Modified"這個回應標頭,瀏覽器在收到這個頭以後,        // 在後續對這個頁面訪問時,就會將時間以"If-Modified-Since"的形式發到伺服器        // 這樣,上面代碼的判斷就能生效。        Response.Cache.SetLastModified(DateTime.Now);    }}

雖然代碼並不複雜,但我還是打算來解釋一下:
在瀏覽器第一次請求頁面時,會執行SetLastModified的調用,它會在響應時輸出一個"Last-Modified"這個回應標頭,
然後,當瀏覽器再次訪問這個頁面時,會將上次請求所擷取的"Last-Modified"頭的內容
以"If-Modified-Since"這個要求標頭的形式發給服務端,此時伺服器就可以根據具體邏輯來判斷要不要使用304應答了。

在前面的請求圖片的樣本中,伺服器以圖片檔案的最後修改時間做為"Last-Modified"發給瀏覽器,
瀏覽器在後續請求那張圖片時,又以"If-Modified-Since"的形式告之服務端,此時服務端只要再次檢查一下這張圖片就知道圖片在上次訪問後有沒有發生修改,
如果沒有修改,當然就以304的形式告之瀏覽器:繼續使用緩衝版本。

還是前面的請求圖片的樣本,其實服務端還使用了另一對【請求/響應】頭:

這二個頭的使用方式是:服務端輸出一個ETag頭,瀏覽器在接收後,以If-None-Match的形式在後續請求中發送到服務端,
供服務端判斷是否使用304應答。

"Last-Modified"與"ETag"這二者,事實上只需要使用一個就夠了,關鍵還是看服務端如何處理它們,瀏覽器只是在接收後,下次再發出去而已。

不過,前面的範例程式碼並沒有使用緩衝頭,事實上,也可以帶上它,這樣可以盡量減少對伺服器的訪問,畢竟使用者不會一直強刷瀏覽器。
這二種方式雖然有較大差別,但它們絕對是可以互補的。

為了能形象的描繪快取頁面(或者其它文檔)的請求過程,我畫了張供大家參考:

如何避開HTTP緩衝

前面小節中,介紹了二種方法使用瀏覽器的緩衝。但有些時候可能反而希望瀏覽器能放棄它緩衝的結果。
現在的瀏覽器都有緩衝功能,尤其是對一些靜態檔案,比如:圖片,JS,CSS, HTML檔案,都能緩衝。
但有時候我們需要更新CSS, JS檔案呢,瀏覽器如果還使用它的緩衝版本,顯然就有問題了。
而且有些網站使用了URL重寫,使原來的動態網頁面副檔名也變成靜態HTML檔案了,
因此,仍然希望瀏覽器在某些時候能夠不要緩衝這些偽靜態頁面。

此時,我們就希望瀏覽器放棄從HTTP請求所獲得的結果了。
一般說來,瀏覽器在處理(它認為的)靜態檔案時,會按照URL為kEY來儲存那些緩衝結果,
因此,通常的解決辦法也就是修改URL,比如:原來是請求abc.js的,要改成abc.js?t=23434,後面要跟上一個參數,
讓以前的緩衝不起作用。至於參數t的取值可以根據檔案的最後修改時間,也可以手工指定,總之只要改變它就可以了。

但是,對於偽靜態頁面,我們不能再使用這種方法了,原因就不用解釋了吧。
那麼,可以採用在服務端輸出一個回應標頭,通過回應標頭的方式告之瀏覽器,不要緩衝此檔案。
比如,可以調用這個方法:

Response.Cache.SetNoStore();

它會產生這樣的回應標頭內容:

Cache-Control: private, no-store

許多瀏覽器都能識別它。還有另一種方法是設定一個已到期的到期時間。

前面所說的在URL中加額外參數的做法,在JS中也比較常用,比如 JQuery就支援讓某個Ajax請求不緩衝,
它的方式就是設定{cache: false},最終它便會在產生的URL中加上一個臨時參數,以保證後面的請求的地址是不重複的,
最終達到避開緩衝的目的。JQuery的使用太簡單,我就不再給出範例程式碼了。

點擊此處下載範例程式碼

如果,您認為閱讀這篇部落格讓您有些收穫,不妨點擊一下右下角的【推薦】按鈕。
如果,您希望更容易地發現我的新部落格,不妨點擊一下右下角的【關注 Fish Li】。
因為,我的寫作熱情也離不開您的肯定支援。

感謝您的閱讀,如果您對我的部落格所講述的內容有興趣,請繼續關注我的後續部落格,我是Fish Li 。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.