在我們開發(fā)一個(gè)ASP.NET網(wǎng)站的過(guò)程中,其實(shí)有很多地方都是可以使用緩存的, 只是由于ASP.NET是一種基于服務(wù)端的開發(fā)平臺(tái),自然我們也經(jīng)常在服務(wù)端的代碼中使用各類緩存技術(shù), 然而,由于WEB應(yīng)用程序的服務(wù)對(duì)象是客戶端的瀏覽器,通常來(lái)說(shuō),我們并不能直接控制瀏覽器的行為,但是, 瀏覽器卻可以根據(jù)后臺(tái)網(wǎng)站的指示,采取一些優(yōu)化的方式來(lái)更快地呈現(xiàn)頁(yè)面。 客戶端瀏覽器也有自己的緩存機(jī)制,通常瀏覽器也使用緩存來(lái)優(yōu)化一些頁(yè)面的顯示過(guò)程, 不過(guò),我們并不能直接使用C#代碼控制瀏覽器的緩存操作,但我們可以告訴瀏覽器如何使用緩存,從而達(dá)到優(yōu)化網(wǎng)站性能的目的。
這次博客的主題是:用ASP.NET控制HTTP請(qǐng)求過(guò)程中瀏覽器緩存的一些方法。
正常的HTTP請(qǐng)求過(guò)程
在開始介紹瀏覽器在HTTP請(qǐng)求過(guò)程前,我想有必要先來(lái)看一下瀏覽器請(qǐng)求一個(gè)普通ASPX頁(yè)面的過(guò)程。
說(shuō)明:本文在介紹HTTP請(qǐng)求過(guò)程時(shí),會(huì)大量使用Fiddler來(lái)分析具體的請(qǐng)求過(guò)程。

上圖是一個(gè)普通的ASPX頁(yè)面的請(qǐng)求過(guò)程,說(shuō)它普通是因?yàn)椋何以趧?chuàng)建這個(gè)頁(yè)面后,沒(méi)對(duì)它做任何緩存方面的處理。
圖片中我們可以可以看到服務(wù)器的響應(yīng)狀態(tài)為:HTTP/1.1 200 OK,這是一個(gè)服務(wù)器成功響應(yīng)的標(biāo)志。
另外,要注意圖片中的Cache響應(yīng)頭部分,我之所以就紅線框出來(lái),是想提醒您注意這塊的內(nèi)容將在后面的小節(jié)中發(fā)生改變, 到時(shí)候請(qǐng)注意對(duì)比它們。而這里所反映的情況其實(shí)也只是默認(rèn)值而已,它并不表示此頁(yè)面需要緩存。
緩存頁(yè)的請(qǐng)求過(guò)程
下面再來(lái)看一個(gè)緩存頁(yè)面的請(qǐng)求過(guò)程:

對(duì)比上一張圖片中可以看出,現(xiàn)在多了【max-age】,【Expires】以及【Last-Modified】這三個(gè)響應(yīng)頭。
這三個(gè)頭的含義如下:
1. max-age,Expires:要表達(dá)的意思基本差不多。max-age表示某次HTTP的響應(yīng)結(jié)果應(yīng)該緩存多少秒。
而Expires是說(shuō)某次HTTP的響應(yīng)結(jié)果應(yīng)緩存到什么時(shí)候過(guò)期,此時(shí)間是一個(gè)UTC時(shí)間。
另一個(gè)Date頭表示HTPP響應(yīng)的發(fā)出時(shí)間,我們可以發(fā)現(xiàn) Date + max-age = Expires
2. Last-Modified:服務(wù)端告訴客戶端本次響應(yīng)返回的HTTP文檔的最后修改時(shí)間。這個(gè)頭與304的實(shí)現(xiàn)有關(guān),后面再來(lái)解釋。
分析了HTTP請(qǐng)求過(guò)程后,我們?cè)賮?lái)看一下服務(wù)端的頁(yè)面是什么樣子的:
注意:上面代碼中最關(guān)鍵的一行代碼為:
《%@ OutputCache Duration=“10” VaryByParam=“None” %》
正是由于使用了這個(gè)OutputCache指令,最后才會(huì)輸出上面那幾個(gè)響應(yīng)頭,用來(lái)告訴瀏覽器此頁(yè)面需要緩存10秒鐘。
說(shuō)到這里,可能有些人想有疑惑了:緩存頁(yè)在什么時(shí)候會(huì)起到什么作用呢?
為了演示緩存頁(yè)所帶來(lái)的現(xiàn)實(shí)意義,我將點(diǎn)擊頁(yè)面的這些鏈接并以截圖的形式來(lái)說(shuō)明:在一系列請(qǐng)求過(guò)程中頁(yè)面的顯示情況, 并以頁(yè)面的顯示結(jié)果來(lái)分析緩存所起的作用。
先來(lái)看看這個(gè)頁(yè)面的顯示截圖:

頁(yè)面很簡(jiǎn)單,主要是顯示了頁(yè)面的生成時(shí)間與一個(gè)刷新鏈接。 從上面提供的頁(yè)面代碼,我們應(yīng)該能知道這個(gè)頁(yè)面如果是由服務(wù)端生成的,則會(huì)顯示當(dāng)前的時(shí)間。
不過(guò)呢,當(dāng)我一直(頻繁)點(diǎn)擊【刷新本頁(yè)】那個(gè)鏈接時(shí),頁(yè)面的時(shí)間并沒(méi)有發(fā)生改變,當(dāng)我發(fā)現(xiàn)時(shí)間改變時(shí),頁(yè)面已顯示成這個(gè)樣子了:

由于測(cè)試過(guò)程中,我一直打開了Fiddler,正好我也把Fiddler監(jiān)視到的請(qǐng)求結(jié)果截圖下來(lái)了:

從Fiddler中,我看到FireFox其實(shí)只發(fā)生了二次請(qǐng)求,但我點(diǎn)擊那個(gè)【刷新本頁(yè)】起碼超過(guò)10次。
以上的這一切,只說(shuō)明一個(gè)事實(shí):如果頁(yè)面需要跳轉(zhuǎn)到某個(gè)緩存頁(yè)時(shí),且那個(gè)緩存頁(yè)還沒(méi)過(guò)期,那么瀏覽器并不會(huì)發(fā)起到服務(wù)器的請(qǐng)求,而是使用緩存頁(yè)。
小結(jié):頁(yè)面緩存所帶來(lái)的好處是:緩存頁(yè)面在過(guò)期前,用戶通過(guò)點(diǎn)擊跳轉(zhuǎn)鏈接所引發(fā)的后續(xù)訪問(wèn),并不會(huì)再次請(qǐng)求服務(wù)器。 這對(duì)服務(wù)器來(lái)說(shuō)可以減少許多訪問(wèn)次數(shù),因此使用這個(gè)特性可以很好地改善程序性能。
緩存頁(yè)的服務(wù)端編程
前面演示了使用OutputCache指令所產(chǎn)生的緩存頁(yè)的效果,由于那些指令需要在頁(yè)面的設(shè)計(jì)階段就寫到頁(yè)面上,因此顯得不夠靈活, 不能在運(yùn)行時(shí)調(diào)整,雖然ASP.NET也允許我們使用CacheProfile來(lái)引入定義在Web.config中的配置,但配置還是沒(méi)有運(yùn)行時(shí)的代碼靈活。 我們?cè)賮?lái)看看如何用代碼來(lái)實(shí)現(xiàn)上面的效果。
其實(shí)用代碼實(shí)現(xiàn)緩存頁(yè)也很簡(jiǎn)單,只需要這樣就可以了:
protected void Page_Load(object sender, EventArgs e)
{
Response.Cache.SetCacheability(HttpCacheability.Public);
Response.Cache.SetExpires(DateTime.Now.AddSeconds(10.0));
}
其實(shí)關(guān)鍵也就是對(duì)Response.Cache的調(diào)用。
注意:Response.Cache與我上篇 【細(xì)說(shuō) ASP.NET Cache 及其高級(jí)用法】博客所講的Cache不是一回事,二者完全不相干。
Response.Cache提供:用于設(shè)置緩存特定的 HTTP 標(biāo)頭的方法和用于控制 ASP.NET 頁(yè)輸出緩存的方法。
我們還是來(lái)說(shuō)前面的二段示例代碼??赡苡行┤藭?huì)想,它們最終的結(jié)果真的會(huì)是一致的嗎?
要想回答這個(gè)問(wèn)題,我想有必要看一下前面用OutputCache指令的那個(gè)頁(yè)面最終運(yùn)行的代碼是個(gè)什么樣子的。
在ASP.NET的臨時(shí)編譯目錄中,我找到了前面那個(gè)文件的一個(gè)由ASP.NET處理后的版本:
我們可以看到頁(yè)面針對(duì)OutputCache指令的設(shè)置,最終會(huì)調(diào)用Page類定義一個(gè)方法中:
protected internal virtual void InitOutputCache(OutputCacheParameters cacheSettings)
那個(gè)方法實(shí)在太長(zhǎng),最終的處理方式也還是在調(diào)用this.Response.Cache,有興趣的可以自己去看看那個(gè)方法。 至于這個(gè)方法的參數(shù)為什么是OutputCacheParameters,我想這個(gè)容易理解:方便將OutputCache指令的參數(shù)全部一起傳入嘛。
所以,也正因?yàn)檫@個(gè)緣故,我們也可以直接在代碼中調(diào)用Response.Cache的一些方法來(lái)實(shí)現(xiàn)相同的效果, 由于代碼可以在運(yùn)行時(shí)根據(jù)各種參數(shù)調(diào)整緩存策略,因此會(huì)更加靈活,而且可以采用基類的繼承方式來(lái)簡(jiǎn)化實(shí)現(xiàn)。
注意:如果使用OutputCache指令再配合OutputCache Module的使用,可以實(shí)現(xiàn)304的效果。
什么是304應(yīng)答?
通過(guò)前面的示例,我們已經(jīng)看到緩存帶來(lái)的好處:那就是可以減少到服務(wù)器的訪問(wèn),由于不訪問(wèn)服務(wù)器就能顯示頁(yè)面,這對(duì)于服務(wù)器來(lái)說(shuō), 能減輕一定的訪問(wèn)壓力。但是,如果用戶強(qiáng)制刷新瀏覽器,那么瀏覽器將會(huì)忽略緩存頁(yè),直接向服務(wù)器重新發(fā)起請(qǐng)求。
也就是說(shuō):緩存頁(yè)在用戶強(qiáng)制刷新瀏覽器時(shí)會(huì)無(wú)效。
但是,我們之所以使用緩存頁(yè),是因?yàn)槲覀兿M嬖V瀏覽器:這些數(shù)據(jù)在一定時(shí)間內(nèi),并不會(huì)發(fā)生變化,因此根本不需要再次請(qǐng)求服務(wù)器了。 然而,我們不能阻止用戶的行為。由于瀏覽器的重新訪問(wèn),我們?cè)瓉?lái)設(shè)想的緩存想法將會(huì)落空,最后的結(jié)果是: 頁(yè)面在服務(wù)器中重新執(zhí)行,產(chǎn)生的HTML代碼將重新發(fā)送到客戶端。而這一重新刷新的結(jié)果可能也是無(wú)意義的,因?yàn)閿?shù)據(jù)可能根本沒(méi)有發(fā)生變化, 因此得到的頁(yè)面也是不可能有變化的。
再來(lái)舉個(gè)簡(jiǎn)單的例子來(lái)說(shuō)吧:客戶端要瀏覽一張圖片。 當(dāng)瀏覽器第一次要訪問(wèn)圖片時(shí),瀏覽器肯定是沒(méi)有它的任何緩存記錄的,此時(shí)它去訪問(wèn)服務(wù)器,服務(wù)器也返回圖片的內(nèi)容了。 但由于圖片可能會(huì)被多個(gè)頁(yè)面所引用,而它被修改的可能性是很小的。 因此沒(méi)有必要為同一瀏覽器的多次請(qǐng)求都去讀取圖片并返回圖片的內(nèi)容,這樣做既影響性能也學(xué)浪費(fèi)帶寬。 于是,像IIS這樣服務(wù)器軟件針對(duì)這類靜態(tài)文件的訪問(wèn)時(shí),都會(huì)在響應(yīng)頭上輸出一些標(biāo)記,用來(lái)告之瀏覽器這個(gè)文件你可以緩存起來(lái)了。
還是回到前面所說(shuō)的【用戶強(qiáng)制刷新】問(wèn)題,此時(shí)的IIS又會(huì)如何處理呢?請(qǐng)看下圖:

注意哦,此時(shí)除了HTTP狀態(tài)碼變成304之外,沒(méi)有任何數(shù)據(jù)返回哦。
為了讓您對(duì)304應(yīng)答有個(gè)深刻的印象,我截了一張狀態(tài)碼為200的圖片響應(yīng)結(jié)果:

通過(guò)這二張圖片的對(duì)比,現(xiàn)在看清楚了吧:304和200并不只是數(shù)字上的差別,最重要的差別在于有沒(méi)有返回結(jié)果。
沒(méi)有返回結(jié)果,瀏覽器該如何顯示?
您會(huì)有這樣的疑慮嗎?
其實(shí)不用擔(dān)心,此時(shí)瀏覽器會(huì)使用它緩存版本來(lái)顯示。也就是說(shuō):不管用戶如何強(qiáng)制刷,服務(wù)器就是不返回結(jié)果,但仍然可以正常顯示。
顯然,這個(gè)效果就是我們想要的。
前面所說(shuō)的緩存頁(yè)遭用戶強(qiáng)刷的問(wèn)題,如果采用這種方法,就比較完美了。
不過(guò),有一點(diǎn)我要提醒您:Visual Studio自帶的那個(gè)WebDev.WebServer.exe不支持304應(yīng)答,所以您就不要拿它試驗(yàn)了,不會(huì)有結(jié)果的。
如何編程實(shí)現(xiàn)304應(yīng)答
前面我們看到了304應(yīng)答的效果。不過(guò),在ASP.NET中,我們開發(fā)的程序,是動(dòng)態(tài)頁(yè)面,而不是圖片, 我們更希望某個(gè)頁(yè)面能以這種方式緩存一段時(shí)間,我想這個(gè)需求或許會(huì)更有意義。
下面,我就來(lái)演示如何通過(guò)編程的方式實(shí)現(xiàn)它。
接下來(lái)的示例中,頁(yè)面的顯示還是那個(gè)樣,顯示頁(yè)面在服務(wù)器上產(chǎn)生的時(shí)間,時(shí)間變化了,說(shuō)明頁(yè)面被重新執(zhí)行了。
重新截一系列的圖片,我認(rèn)為意義也不大,我就截一張圖片展現(xiàn)多次強(qiáng)刷而產(chǎn)生的過(guò)程

上圖反映了我多次請(qǐng)求某個(gè)ASPX頁(yè)面的過(guò)程,從圖片中可以看出,只有第一次是200的響應(yīng),后面全是304,是您所期待的結(jié)果吧。
再來(lái)看看它的實(shí)現(xiàn)代碼吧:
雖然代碼并不復(fù)雜,但我還是打算來(lái)解釋一下:
在瀏覽器第一次請(qǐng)求頁(yè)面時(shí),會(huì)執(zhí)行SetLastModified的調(diào)用,它會(huì)在響應(yīng)時(shí)輸出一個(gè)“Last-Modified”這個(gè)響應(yīng)頭, 然后,當(dāng)瀏覽器再次訪問(wèn)這個(gè)頁(yè)面時(shí),會(huì)將上次請(qǐng)求所獲取的“Last-Modified”頭的內(nèi)容 , 以“If-Modified-Since”這個(gè)請(qǐng)求頭的形式發(fā)給服務(wù)端,此時(shí)服務(wù)器就可以根據(jù)具體邏輯來(lái)判斷要不要使用304應(yīng)答了。
在前面的請(qǐng)求圖片的示例中,服務(wù)器以圖片文件的最后修改時(shí)間做為“Last-Modified”發(fā)給瀏覽器, 瀏覽器在后續(xù)請(qǐng)求那張圖片時(shí),又以“If-Modified-Since”的形式告之服務(wù)端,此時(shí)服務(wù)端只要再次檢查一下這張圖片就知道圖片在上次訪問(wèn)后有沒(méi)有發(fā)生修改, 如果沒(méi)有修改,當(dāng)然就以304的形式告之瀏覽器:繼續(xù)使用緩存版本。
還是前面的請(qǐng)求圖片的示例,其實(shí)服務(wù)端還使用了另一對(duì)【請(qǐng)求/響應(yīng)】頭:

這二個(gè)頭的使用方式是:服務(wù)端輸出一個(gè)ETag頭,瀏覽器在接收后,以If-None-Match的形式在后續(xù)請(qǐng)求中發(fā)送到服務(wù)端, 供服務(wù)端判斷是否使用304應(yīng)答。
“Last-Modified”與“ETag”這二者,事實(shí)上只需要使用一個(gè)就夠了,關(guān)鍵還是看服務(wù)端如何處理它們,瀏覽器只是在接收后,下次再發(fā)出去而已。
不過(guò),前面的示例代碼并沒(méi)有使用緩存頭,事實(shí)上,也可以帶上它,這樣可以盡量減少對(duì)服務(wù)器的訪問(wèn),畢竟用戶不會(huì)一直強(qiáng)刷瀏覽器。 這二種方式雖然有較大差別,但它們絕對(duì)是可以互補(bǔ)的。
為了能形象的描繪緩存頁(yè)(或者其它文檔)的請(qǐng)求過(guò)程,我畫了張示意圖供大家參考:

如何避開HTTP緩存
前面小節(jié)中,介紹了二種方法使用瀏覽器的緩存。但有些時(shí)候可能反而希望瀏覽器能放棄它緩存的結(jié)果。 現(xiàn)在的瀏覽器都有緩存功能,尤其是對(duì)一些靜態(tài)文件,比如:圖片,JS,CSS, HTML文件,都能緩存。 但有時(shí)候我們需要更新CSS, JS文件呢,瀏覽器如果還使用它的緩存版本,顯然就有問(wèn)題了。 而且有些網(wǎng)站使用了URL重寫,使原來(lái)的動(dòng)態(tài)頁(yè)面擴(kuò)展名也變成靜態(tài)的HTML文件了, 因此,仍然希望瀏覽器在某些時(shí)候能夠不要緩存這些偽靜態(tài)頁(yè)面。
此時(shí),我們就希望瀏覽器放棄從HTTP請(qǐng)求所獲得的結(jié)果了。 一般說(shuō)來(lái),瀏覽器在處理(它認(rèn)為的)靜態(tài)文件時(shí),會(huì)按照URL為kEY來(lái)保存那些緩存結(jié)果, 因此,通常的解決辦法也就是修改URL,比如:原來(lái)是請(qǐng)求abc.js的,要改成abc.js?t=23434,后面要跟上一個(gè)參數(shù), 讓以前的緩存不起作用。至于參數(shù)t的取值可以根據(jù)文件的最后修改時(shí)間,也可以手工指定,總之只要改變它就可以了。
但是,對(duì)于偽靜態(tài)的頁(yè)面,我們不能再使用這種方法了,原因就不用解釋了吧。
那么,可以采用在服務(wù)端輸出一個(gè)響應(yīng)頭,通過(guò)響應(yīng)頭的方式告之瀏覽器,不要緩存此文件。 比如,可以調(diào)用這個(gè)方法:
Response.Cache.SetNoStore();
它會(huì)生成這樣的響應(yīng)頭內(nèi)容:
Cache-Control: private, no-store
許多瀏覽器都能識(shí)別它。還有另一種方法是設(shè)置一個(gè)已過(guò)期的過(guò)期時(shí)間。
前面所說(shuō)的在URL中加額外參數(shù)的做法,在JS中也比較常用,比如 JQuery就支持讓某個(gè)Ajax請(qǐng)求不緩存, 它的方式就是設(shè)置{cache: false},最終它便會(huì)在生成的URL中加上一個(gè)臨時(shí)參數(shù),以保證后面的請(qǐng)求的地址是不重復(fù)的, 最終達(dá)到避開緩存的目的。JQuery的使用太簡(jiǎn)單,我就不再給出示例代碼了。
-
HTTP
+關(guān)注
關(guān)注
0文章
530瀏覽量
34630 -
ASP
+關(guān)注
關(guān)注
0文章
98瀏覽量
34780 -
瀏覽器
+關(guān)注
關(guān)注
1文章
1042瀏覽量
36851
發(fā)布評(píng)論請(qǐng)先 登錄
老電視如何安裝瀏覽器?
在KaihongOS應(yīng)用開發(fā)中,如何通過(guò)HTTP發(fā)起一個(gè)數(shù)據(jù)請(qǐng)求
nginx中強(qiáng)緩存和協(xié)商緩存介紹
HTTP和HTTPS的關(guān)鍵區(qū)別
Spire.PDFViewer for ASP.NET強(qiáng)大的PDF查看組件
E2000 Speedometer測(cè)試瀏覽器性能
服務(wù)器如何處理 HTTP 請(qǐng)求
如何調(diào)試 HTTP 請(qǐng)求和響應(yīng)
HTTP 協(xié)議的工作原理
HTTP緩存頭的使用 本地緩存與遠(yuǎn)程緩存的區(qū)別
Web緩存的類型及功能分析
AWTK 最新動(dòng)態(tài):支持瀏覽器控件
寫一個(gè)Chrome瀏覽器插件
通過(guò)瀏覽器訪問(wèn)文件——P2Link內(nèi)置HTTP服務(wù)

用ASP.NET控制HTTP請(qǐng)求過(guò)程中瀏覽器緩存問(wèn)題解析
評(píng)論