日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 前端技术 > HTML >内容正文

HTML

浏览器静态资源的版本控制新思路.强制更新指定资源缓存.的探讨

發(fā)布時間:2023/12/20 HTML 35 豆豆
生活随笔 收集整理的這篇文章主要介紹了 浏览器静态资源的版本控制新思路.强制更新指定资源缓存.的探讨 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

 此篇,探討的是一種可以讓腳本自己更新自己緩存副本的能力. 因為上一版本,絕大多是朋友,給我的反饋是看不懂,所以我爭取在這個重寫的版本中.詳細(xì)把每個細(xì)節(jié)都介紹一二. ?如果大多數(shù)細(xì)節(jié),都是您了解的,則跳躍性閱讀即可. thx.

  另:本文討論的 方案,在國內(nèi)的網(wǎng)絡(luò)環(huán)境.很難實施. 僅僅是一種探討 .?此篇內(nèi)容非常多. 感謝您的寶貴時間,希望能耐心看完. ?

?

?

關(guān)于緩存

在開始之前,不得不提到 ?"web?緩存".如果您對它有充分理解,請直接跳過.

  我們可以簡單的理解下什么是資源文件的緩存, ?比如一個頁面中引入了一個腳本 a.js ,這個文件的內(nèi)容可能不會經(jīng)常變化. 所以每次打開這個頁面, 如果都去服務(wù)器端加載這個腳本,而它又沒有任何變化,就會顯得很多余.浪費帶寬和時間. 所以瀏覽器可以把一些不常改變的東西,做本地緩存,即,當(dāng)我再次打開這個頁面時,使用的a.js,不再通過網(wǎng)絡(luò),去服務(wù)器下載,而是直接使用瀏覽器緩存的副本. 一般我們稱這種資源為"靜態(tài)資源". 我們可以借助HTTP協(xié)議,通過一些緩存控制頭域和配套的緩存控制指令,來告知瀏覽器.某個文件,你可以緩存多久, 在這個允許的時間范圍內(nèi),你無須到服務(wù)器下載,而可以直接使用你的緩存副本. ?有趣的是, 類似的緩存機制,不僅僅瀏覽器具備,充斥在整個網(wǎng)絡(luò)中的一些代理服務(wù)器,也有相似的功能.所以我們稱這種代理服務(wù)器為 "緩存代理服務(wù)器" .

  你可以把瀏覽器,緩存代理,服務(wù)器原站,想象成這樣子:

    瀏覽器 : 你的學(xué)習(xí)筆記?

    緩存代理: 你的學(xué)習(xí)成績優(yōu)異的同桌同學(xué) A

    原始服務(wù)器: 你的老師.

  當(dāng)你遇到一個,老師曾經(jīng)教過,但是被你遺忘的問題,你就先去問了同學(xué)A ,他說:"這個問題我會,我告訴你". 然后你再把答案記在自己的學(xué)習(xí)筆記上. 下次再遇到這個問題,你就無需再問.你只要看看自己的學(xué)習(xí)筆記就行了. ?這個過程,就是緩存代理的作用,當(dāng)緩存代理有a.js的副本,并且確認(rèn)這個副本沒有過期時,就會把副本直接作為響應(yīng)返回給瀏覽器. 然后瀏覽器再把響應(yīng)作為副本,自己緩存起來.?

   假如同學(xué) A 也不知道這個問題的答案,我們看看會發(fā)生什么. ?他為了面子,就對你說:"稍等下,我要上個廁所,回來告訴你." . 然后這貨,跑到老師那邊,得到了答案.記在他自己的筆記上.然后又跑回來,把答案告訴了你. ?這就是緩存代理的回源過程.緩存代理,自己沒有副本,或者確認(rèn)副本過期了以后,就會這樣做.

   假如有一天,教育局更改了教材,發(fā)現(xiàn)那個問題的答案,其實一直有需要改進(jìn)的地方. 又怎么辦呢. 老實會緊急在課堂說對所有的同學(xué)們說:"對于問題A,老的答案可能有些問題,現(xiàn)在我們來重新學(xué)習(xí)一下這個問題....." . 然后同學(xué)們會如何呢? 會重新記一份筆記.把新的答案記下來.

  后來,老師發(fā)現(xiàn),來問問題的同學(xué)越來越多,嚴(yán)重耽誤了他干別的事情, 他決定請一個助教,專門來應(yīng)對來問問題的同學(xué)們.他把常見問題的答案都告訴給這個助教.讓他在教研室門口,攔住所有來問問題的學(xué)生,并把他們想要的答案,告知他們. 這位助教扮演的角色,就是我們耳熟能詳?shù)?#34;反向代理",又因為他也有一份答案保留.并不是每次都代替學(xué)生來問問題.所以,它也是緩存代理. 而反向代理和非反向代理的區(qū)別,就是, 同學(xué)A,是你主動去問的. 而助教是被動存在的.他是老師安排的. 所以從你的角度來說,助教就是反向代理.因為他是服務(wù)提供者安排并控制的.

  

  再后來,由于教委的人抽風(fēng),經(jīng)常要更新答案. 無論是同學(xué),還是老師,都有些崩潰. 同學(xué)也發(fā)現(xiàn),剛從其他同學(xué) 或助教那里得來的答案,往往和老師去教委開會后,獲得的最新答案有出入. 有些細(xì)心的學(xué)生,逐漸開始不信任別的同學(xué),甚至是助教的答案.他每次像問問題,被助教攔和其他同學(xué)攔下來時,都會大喊一句:"別想糊弄我,你給我去問老師". 迫于淫威,他們可能就真的不自作主張的把自己的答案告訴你.而去很抱歉的,打擾老師了. 這是一種傳遞性的情況. 也是我們后面要經(jīng)常提到的 端對端重載的概念.. ?但是現(xiàn)實和理想總是有些偏差, ?你對同學(xué)A發(fā)威, 同學(xué)A買賬了.結(jié)果他對攔在門口的助教說,這個問題,我們想知道老師怎么說.但是助教可能并不買賬.因為老師交代過他.任何問題,你都別來煩我.直接把你的答案給他們就是了... 所以.往往你還是拿不到最新的答案. 這就是協(xié)議的遵守和違約. 有時候違約并不是單向的. 比如同學(xué)A,他很有自信,他為了不沒事就往辦公室跑. 即使老師交代他, 某個問題的答案最近可能有改動.如果明天有同學(xué)還問你某個問題.你一定要來我這里確認(rèn)一下,拿到新的內(nèi)容在做答復(fù). 但是同學(xué)A,完全沒當(dāng)一回事,自作主張的把自己的答案直接交給了 來問的同學(xué).并且還騙他說,這就是最新的.相信我吧... 這種人,就是我們后面可能要提到的,ISP,或一些內(nèi)網(wǎng)的 流氓緩存代理所干的骯臟勾當(dāng).

 

?

  前面的比喻,可能并不是特別恰當(dāng).但是基本上能說清楚這個過程了.?

  然后,我建議你可以詳細(xì)的看看這個鏈接里的內(nèi)容 :?http://www.cnblogs.com/_franky/archive/2011/11/23/2260109.html

  看完后,我們來提一下幾個Http1.1 的Cache-Control頭域的重要的偽指令, 你可以把這些指令看做是,老師的承諾,比如老師說,這個答案大家可以放心.在半年內(nèi),是不會有什么變化的. 指令也可以是學(xué)生發(fā)出的.比如學(xué)生對助教說, 你滾蛋,我要老師那里最新的答案..?

  如果,你看過我上面給出的鏈接的全部內(nèi)容,那么下面這幾個偽指令,你就不會陌生 :?

    1. max-age = x (單位秒) ?緩存新鮮期.對所有代理有效,包括用戶代理(也就是我們的瀏覽器)

    2. s-maxage = x (單位秒) 緩存的新鮮期,僅對緩存代理有效(不包括我們的瀏覽器)

    3. no-cache ?請求頭中出現(xiàn)這個是告知緩存代理不要拿你的緩存副本來騙我,請你回源. ?說白了就是,我的期望是,服務(wù)器端直接返回給我一個最新鮮的版本.(對應(yīng)名詞:端對端重載)

  

  請記住這幾個東西,我們會在稍后的地方詳細(xì)講解對他們的使用. ?現(xiàn)在,我們開始聊聊,我們到底要解決什么問題.

要解決的問題:

  我們在日常開發(fā)中,引入的腳本資源大概長下面這個摸樣:

<script?src=http://www.a.com/a.js?v=20121212></script> <script?src=http://www.a.com/20121212/a.js></script>

  

  其中 20121212 部分我們是作為版本號來存在的.?

當(dāng)然,也有一種做法,并不是把日期作為版本號,比如拿文件的內(nèi)容,用某種hash算法,獲得一個字串,作為版本號.這樣可以防止競爭對手枚舉版本號方式變相攻擊,導(dǎo)致緩存代理們,緩存錯誤的內(nèi)容.但是不管怎么樣.更新資源,我們總是需要改變資源的url. 這本身沒有什么問題. ?但假如我們沒有辦法修改url的資源就變的束手無策了. ?這樣的場景經(jīng)常發(fā)生在第三方腳本身上. ?比如我們的腳本作為第三方腳本,存在于某些渠道網(wǎng)站的頁面中. 當(dāng)文件更新時,我們可能沒辦法強迫哪些渠道修改腳本的url . 所以. 常見的做法有兩種:

  (1). 減少腳本的過期時間. 比如10分鐘的過期時間(baidu聯(lián)盟的腳本文件的過期時間是兩個小時)

  (2). 使用一個永不緩存的小加載器腳本.加載后面的主腳本. 通過修改加載器腳本中 主腳本的url來實現(xiàn)更新.

顯然.兩種方式,都是一些權(quán)衡的產(chǎn)物. 很難說哪個更好.也許只有更合適自身情況的方案.才是相對好的那一個. 而本文.主要想探討的是第三種方案. 即腳本自更新技術(shù).他是一種無版本號,且強制更新自身的方式. 但這個方案不是無懈可擊的.至少通過我一個月的測試.發(fā)現(xiàn)他在中國的網(wǎng)絡(luò)環(huán)境下.很難推廣使用. 具體細(xì)節(jié)會在后面詳細(xì)討論. ??

?


?

  讓我們開始今天的正題:??假設(shè)我們有一種方法.可以在得知 xxx.js 已經(jīng)有了新版本.需要客戶端重新去服務(wù)器獲取新版本時.立刻實現(xiàn)更新. 這個xxx.js.而且url并不發(fā)生變化. 那么是一件多么快意的事情啊. ?我們來描述一下大概的場景.

  A頁面部署了?http://www.a.com/a.js?. 這個腳本每次會動態(tài)的去服務(wù)器獲取一些數(shù)據(jù).填充到A頁面. 這些數(shù)據(jù)是動態(tài)的.所以不涉及到緩存. ?突然動態(tài)數(shù)據(jù)中有一個段數(shù)據(jù)是通知 a.js有了新版本要更新了. ?a.js控制瀏覽器,立刻向服務(wù)器重新發(fā)起一個到http://www.a.com/a.js 自身的請求.獲取到新版本的內(nèi)容.同時,瀏覽器更新本地緩存.并且.這次更新動作.不會再次解釋執(zhí)行a.js的內(nèi)容. ?這就是我們理想的更新模式. 有了這個模式. 我們可以把a.js的緩存時間設(shè)置為10年. 一但a.js很長時間都沒內(nèi)容更新.那么就不會有任何浪費性的請求到達(dá)服務(wù)器. ? ?這時候,你可能會問. 用戶刷新瀏覽器.是不會直接拿本地緩存的.而是會發(fā)起http請求. 至少也會有一個304的響應(yīng)才對. 事實卻是如此. 但是請記得. 還是有相當(dāng)多的情況.用戶只是不斷的點鏈接跳來跳去.又或者在地址欄中直接輸入網(wǎng)址,又或者從收藏夾中訪問某個頁面. ?當(dāng)這種情況下.如果a.js的緩存時間很短. 就會經(jīng)常因a.js緩存過期,而導(dǎo)致瀏覽器發(fā)起http請求. 我們這個方案的最終目的之一.就是要消滅這部分額外的請求. 從而節(jié)省寶貴的流量. ?流量的節(jié)省不僅僅體現(xiàn)在客戶端, 還體現(xiàn)在 反向代理的回源花費的流量. ?如果a.js的緩存時間很短. 非特殊設(shè)置下. 反向代理也同樣會頻繁的回源. ?大多數(shù)CDN.可都是雙向收費的. 減少回源.也是我們的目標(biāo)之一. ?現(xiàn)在我們簡單總結(jié)下.這個方案的好處:

  (1). 可以讓靜態(tài)資源擁有非常長的過期時間. 從而帶來網(wǎng)絡(luò)流量上的成本節(jié)省.

  (2). 這個方案,可以做到需要更新靜態(tài)資源時.立刻更新.并在用戶下次訪問時.使用最新的版本.

?

現(xiàn)在.我們就來討論下這個方案的細(xì)節(jié). 我們可能存在以下幾個疑惑:

  (1)如何保證瀏覽器發(fā)起一個對a.js的http請求.

  (2)如何保證這次請求只更新緩存.而不解釋執(zhí)行a.js?

?

對于問題(1) . 我們的答案是 . location.reload 這個BOM 方法 + ajax代理(少數(shù)瀏覽器需要). ?我們需要借助一個iframe. 把要更新的資源放在iframe中. 然后調(diào)用iframe的 location.reload方法來做. ?遺憾的是. 無論是location.reload 還是ajax 代理方式.各個瀏覽器的行為都不太一樣. 這些行為差異.主要體現(xiàn)在是否會真的發(fā)起對a.js的請求. 以及發(fā)出的http請求頭域的差異.

好在經(jīng)過我不懈的測試.最終還是覆蓋了所有主流瀏覽器,以及主流版本.

?

?

我們先來看看reload API 的一些情況.?

下如果你不明白 Cache-Control, Pragma, if-None-Match, If-Modified-Since .以及直接走cache的含義.

我建議你先讀一下:??http://www.cnblogs.com/_franky/archive/2011/11/23/2260109.html

?

reload(true) html頁面本身:
Browser Cache-Control Pragma If-None-Match If-Modified-Since 是否仍然直接走cache
IE6,IE7,IE8 no-cache
IE9,IE10 no-cache
Firefox no-cache no-cache
Chrome max-age=0
Safari max-age=0 no-cache
Opera no-cache no-cache
reload(true) html內(nèi),硬編碼外鏈腳本資源:
Browser Cache-Control Pragma If-None-Match If-Modified-Since 是否仍然直接走cache
IE6,IE7,IE8 no-cache
IE9,IE10 no-cache
Firefox no-cache no-cache
Chrome max-age=0
Safari3 max-age=0 no-cache
Safari4+ max-age=0 no-cache
Opera11.0- - - - -
Opera11.64+ no-cache no-cache
reload(false) html頁面本身:
Browser Cache-Control Pragma If-None-Match If-Modified-Since 是否仍然直接走cache
IE6,IE7,IE8 no-cache
IE9,IE10
Firefox max-age=0
Chrome max-age=0
Safari max-age=0 no-cache
Opera no-cache no-cache
reload(false) html內(nèi),硬編碼外鏈腳本資源:
Browser Cache-Control Pragma If-None-Match If-Modified-Since 是否仍然直接走cache
IE6,IE7,IE8 no-cache
IE9,IE10
Firefox max-age=0
Chrome max-age=0
Safari3 max-age=0 no-cache
Safari4+ max-age=0 no-cache
Opera - - - -

?

以上,四個表.其實我們要關(guān)心的是 硬編碼外鏈腳本資源的情況.因為他們正是我們要更新的靜態(tài)資源.

補充: 所謂硬編碼即 直接寫在html頁面中的腳本.而不是借助dom api動態(tài)插入而加載的腳本.

  在結(jié)果中.我們尤其要關(guān)注的是那些請求中帶有 Cache-Control : no-cache 瀏覽器. ?還記得前面我一再提到的端對端重載的概念么? 根據(jù)http1.1協(xié)議.這樣做.會導(dǎo)致所有遵守http1.1協(xié)議的緩存代理服務(wù)器回源. 假如.所有的緩存代理都遵守http1.1的端對端重載. 那么這將給原站帶來災(zāi)難. 瞬間帶來海量的請求.如果我們當(dāng)前的瞬時流量很大.壓垮原站.那就是分分鐘的事情.所以為了防止這種情況出現(xiàn).我們必須強迫我們的反向代理(比如我們的CDN).不支持端對端重載. 然后在a.js有更新時,清除CDN對a.js的緩存(幾乎所有的CDN,都會開發(fā)這種接口的.大公司自搭的更是如此). 這樣做的結(jié)果.就是保證每個節(jié)點都只回源一次.壓力可以忽略不計. ?你一定會問.那就用reload(false) 唄. 這樣就沒有端對端重載了. ?但是我們不要忘記 其他緩存代理的影響. 我們?nèi)匀贿€是希望用戶和 反向代理之間的那些 緩存代理可以遵守端對端重載,從而正確的回源. (但實際情況,我自己測試下來,發(fā)現(xiàn)大部分緩存代理都不支持.端對端重載... 所以這里true,也好,false也罷.真的無所謂了. 只要你發(fā)起請求.我們就謝天謝地了.)

  細(xì)心的你,一定發(fā)現(xiàn)Opera11.0-?似乎不能用reload . 沒關(guān)系.我們可以用ajax 代理方式來做. 我們可以創(chuàng)建一個永久緩存的 html頁面.根據(jù)url中的hash 部分.來傳入要更新資源. 該html頁面所在的域,與要更新的資源同域.即可有效的更新了. 你也許問.為啥其他瀏覽器不能這樣做. 下面這個表,會給我們答案:

  

AJAX ? 設(shè)置xhr.setRequestHeader('Cache-Control', 'no-cache');.發(fā)起請求的情況:
Browser Cache-Control 是否自動加入Pragma : no-cache If-None-Match If-Modified-Since 是否仍然直接走cache
IE - - -
Firefox no-cache
Chrome6- max-age=0
Chrome7+ no-cache
Safari5.0- max-age=0
Safari5.1+ no-cache
Opera10.7-
Opera11+ no-cache

  我們看到.IE.是不發(fā)起http請求的. 而其他瀏覽器雖然可以.但是我們?yōu)榱斯?jié)省對那個代理html的請求. 還是使用reload好了. ?這時候你也許會說. reload ? ireload難道不也需要一個iframe 頁面作為容器么? ?答案是 no . 不需要. ?我們只需要 iframe.src = about:blank ?或 配合 DataURI 來做就好了. 這樣就只有在 opera瀏覽器下.我們需要使用 ajax代理 訪問額外的html頁面了.

   

  然后,我們來看看如何讓資源加載,而不執(zhí)行.或不渲染(對于.css來說. 是的.這個方案可不僅僅只對.js)

加載資源不執(zhí)行不渲染的方式:

其實,針對不同瀏覽器,方法還是很多的.?參考地址:http://www.cnblogs.com/_franky/archive/2010/12/09/1901720.html?。有興趣可以仔細(xì)看下.

我所推薦的方式是:

IE 全系, Opera全系, Safari5-, Chrome7-: 使用 <script type="text/c" src="xxx"></script> 方式加載資源

ps: 對于Safari5.0-,和Chrome6-,應(yīng)注意加載資源的方式,.js采用 script,.css采用下面的link 方式.原因會在后面 非IE的 iframe,處理方案中詳細(xì)解析.

Chrome8+, Safari5.1+, Firefox全系建議使用Link標(biāo)簽來做:??<link rel="stylesheet" media="c" href="xxx" />

?

我那份關(guān)于資源預(yù)加載的帖子里描述的其他方法,或多或少都有其他一些缺陷,所以請記得,上面兩種方式,就是你需要的.加載資源,不執(zhí)行,不渲染的方式了.

原理就是 script的 type給個不認(rèn)識的值, ?以及 ?link stylesheet 的media 給個不認(rèn)識的值. 就讓所有主流的A級瀏覽器,達(dá)到了只加載資源的目的.

?

?

  一切看起來還不錯. 但是這引出了后面我們要說的一些惡心問題. 其中任何一問題,都一度讓我想要放棄. 但最終我堅持了下載.并一一解決了. 僅作為一個 偏執(zhí)狂的心酸歷程.記錄下來好了:

?


?

到此,原理方面,討論結(jié)束. 我們來點實施上的經(jīng)驗:

顯然,我們在更新一些資源的時候,他們要有iframe作為容器, 但是我們顯然不希望這個iframe,部署一個真正的實體頁面來做. 否則reload這個額外的iframe,也很苦惱.如何解決?

?

1.??IE下 用 一個 src 為?about:blank的 iframe,通過動態(tài)寫入文檔流的方式,組織其html.來加載這些資源.

爽的地方就在這里, IE下,reload 一個動態(tài)寫入的文檔流.刷新后還是記得這些文檔流內(nèi)的內(nèi)容. 就搞定問題了. 又因為 about:blank 被視為同parent頁面同域. 所以似乎 iframe.contentWindow.document.write 就滿足一切需求了.?

 但是很不幸, IE全系,至今都有一個同源策略的bug. 即 在我們創(chuàng)建一個 src 為about:blank的iframe前,parent?頁面修改過document.domain. 則該iframe就被視為同parent頁,跨域.也就是說 我們的iframe.contentWindow.document 是沒權(quán)限的.

  解決辦法:

  使用javascript: 偽協(xié)議. 即 設(shè)置iframe的url 為 javascript:document.write(xxx)的方式來寫入文檔流.實現(xiàn)同樣的功能. 記住,write的機會只有一次.所以我們務(wù)必要一次性把內(nèi)容寫入. 但是當(dāng)我們決定這樣做的那一刻起,注定我們要面對IE系對我們設(shè)置的重重考驗.因為這樣做可能帶來的問題如下:

  (1).?當(dāng)parent頁存在 base標(biāo)簽,且target 為_blank時, 使用偽協(xié)議方式,會導(dǎo)致IE系彈窗(并可能會被瀏覽器攔截).?

    解決辦法: 掃描parent頁面的所有base標(biāo)簽,看是不是target為_blank.如果有,就暫時把它的target改為 _self. ?后面對iframe寫入偽協(xié)議結(jié)束后.再改回去.

    ps: 務(wù)必迭代所有base標(biāo)簽,網(wǎng)上有資料說, 多個base標(biāo)簽 只有第一個生效是錯誤的. 實際上只要有target=_blank的base,無論他在哪,優(yōu)先級都高于其他.

      且,務(wù)必不要移除base標(biāo)簽,因為你可能遇到 IE6 base單閉合引發(fā)的bug.導(dǎo)致base后面的節(jié)點都被IE6解析成base的子節(jié)點.移除它,再恢復(fù)是很可怕的.

  

  (2).?避免過早的獲取document.contentWindow.document,導(dǎo)致拿不到iframe的document.

?

    解決辦法: iframe的url設(shè)置為about:blank后在iframe.onload中獲取其document對象.

?

?

? ? ??(3). ?不恰當(dāng)?shù)膶懭敕绞?造成parent頁的document.readyState永遠(yuǎn)卡在inneractive狀態(tài).進(jìn)一步導(dǎo)致parent頁面的onload事件,永遠(yuǎn)不會被觸發(fā).

    解決辦法: 不要對iframe.立刻寫偽協(xié)議,而是在iframe.onload后,再次setTimeout 去寫入偽協(xié)議. 注意這兩個條件都是必要的.

    ps: 記得onload注冊要在 iframe的url 設(shè)置為'about:blank' 之前.

?

  ??(4). IE8- iframe.onload,被認(rèn)定跨域,注冊無效問題.

    解決辦法:?請使用iframe.attachEvent('onload', callback)的方式.

?

  (5). iframe.src = "about:blank" ,導(dǎo)致IE全系,后退按鈕bug.(體現(xiàn)為,parent頁后退按鈕失效.可以無限按后退,并導(dǎo)致iframe內(nèi)部的資源無限重載)

    解決辦法: ?用iframe.contentWindow.location.replace('about:blank') 代替 iframe.src = "about:blank"方式. 前者,跳轉(zhuǎn)的好處是,不會使iframe,產(chǎn)生記錄.

    ps: 此bug的本質(zhì)原因就是iframe.src= xxx 產(chǎn)生歷史記錄.再寫入偽協(xié)議.造成了這個惡心的問題.

      另外, 請別擔(dān)心location.replace會受同源策略限制. 如果你寫過反iframe嵌套的腳本,就知道,這個api,完全不受限制.

? ? ? ? ? ? ? ? ? 需要說明的是,新創(chuàng)建的iframe,是沒這個問題的.只有你打算復(fù)用某個已經(jīng)有src屬性的iframe時才要注意這個問題.

?

  (6). 不恰當(dāng)?shù)氖褂脗螀f(xié)議,寫入文檔流,IE,出現(xiàn),并永遠(yuǎn)卡在loading狀態(tài).(不停的轉(zhuǎn)啊轉(zhuǎn)...)

    解決辦法: 無需理會,因為我們的需求,先天上,幫我們修正了這個bug.即iframe內(nèi)部產(chǎn)生一個網(wǎng)絡(luò)請求(cache的不算).就會修正這個bug.

?

    小結(jié): 其實IE,或非IE,在使用偽協(xié)議的時候,bug遠(yuǎn)不止這么多,只不過我們的需求,先天上,繞過了他們而已.所以我就不再一一列出了.

?  

  (7). 寫入文檔流,接口不一致的隱患問題

    本質(zhì)上來說,這并不是一個bug. 而是一個隱患. 因為我們直接使用doc.write 和使用偽協(xié)議寫入doc.write,有一個區(qū)別:

      偽協(xié)議 : str = 'javascript:document.write("' + xxx + ?'")'; iframe.src = str;

      直接寫文檔流: 則是iframe.contentWindow.document.write(xxx);

    這就產(chǎn)生了一個接口的不一致性, 那么我們的腳本在大多數(shù)沒有修改document.domain的頁面,因為直接寫文檔流,沒有權(quán)限限制,而被通過.則規(guī)避了xxx 內(nèi)容中單引號或雙引號的轉(zhuǎn)意問題. 而用偽協(xié)議方式,因為我們要拼接一個外部包裹的字符串,就必須考慮內(nèi)部的單雙引號的轉(zhuǎn)意.雖然技術(shù)上,我們可以借助單雙引號的交錯嵌套,避免轉(zhuǎn)意序列\(zhòng)' 或\"的使用. 但是,我們也應(yīng)該人為的保持接口的一致性.因為此部分功能,可能被你用在別的地方,而被寫入文檔流的數(shù)據(jù),可能是不確定的. 那么保持一致性,可以做到要出錯,兩個分支都出錯,要么就都通過. 避免了測試用例不全.帶來的隱患.導(dǎo)致線上事故的發(fā)生. ?這是我們一個血的教訓(xùn).所以覺得,十分有必要補充進(jìn)來的.

      解決辦法: iframe.contentWindow.document.write(eval('"' + str +'"'));

?      例子:?

        var str = '<script>alert(\\"\'franky\'\\")<\/script>';
        document.write(eval('"' + str +'"'));//打印 'franky'

? ? ? ? ? ??

      當(dāng)然,就這一點上,可能我們還有另外一個選擇,就是先用偽協(xié)議,寫一段'<script>document.domain = 父頁面.document.domain</script>進(jìn)iframe的文檔流. 然后,iframe 和parent就同域了.按道理就可以繼續(xù)轉(zhuǎn)回使用iframe.contentWindow.document.write了.(但我實測,還是會無法寫入.所以我還是選擇了偽協(xié)議方式) 不過,請務(wù)必要記得.不要這樣:iframe.src = 'javacript:document.domain = xxx';?.這樣做是會被ie拋出異常的.所以你要:

        iframe.src = 'javascript:document.open();document.write("<script>document.domain = \'xxx\'<\/script>");undefined';

        

? ? ? ? ? ? ? ? ? ? 另外值得一提的就是, 后面將要介紹的dataURI方式.以及普通document.write處理<\/script> 時,不能要寫成<\\\/script>(早期webkit 版本存在 bug. 新的則會自動修正html錯誤) .雖然你那樣寫,IE和其他非IE較新版本的瀏覽器會自動修正.但是在運行時,會有一些微妙差異.尤其是在你的更新腳本.可以執(zhí)行的時候.此處不是重點.就不詳細(xì)解釋了. 保持接口一致性,需要費一番心思.(尤其是,復(fù)用iframe,以及這個接口,不僅僅用于資源更新,還有其他用途的時候.).我是自己寫了些中間函數(shù)來處理這一系列惱人的問題...

? ? ? ? ? ? ? ? ? ? 還有一點就是webkit下 dataURI的iframe,會被視為跨域,而Firefox,Opera,則沒問題.所以reload可以在外面做.而不必需要在iframe里面做.

??

?

2.關(guān)于非IE的方案:

  我只能很遺憾的說, 方案1僅對IE有效,其他瀏覽器不可行. ?FF表現(xiàn)為,資源不被加載, Chrome,Safari,Opera,表現(xiàn)為,iframe,嵌套性的reload parent頁面.導(dǎo)致無限嵌套的iframe出現(xiàn).并不停的reload. 原因也無需解釋.

  ?但其實, DataURI 的方式可以解決這個問題,,比如 src="data:text/html;charset=utf-8," + encodeURIComponent的html內(nèi)容.

? ? ?最初,我在測試這個方式的時候,是失敗的. 后來我發(fā)現(xiàn)aoao的blog,記錄非IE使用DataURI方式. 在與aoao請教后,發(fā)現(xiàn)了我的悲劇. 我使用dataURI時, 內(nèi)部加載資源,用了自適應(yīng)協(xié)議方式,即. //www.a.com/a.js 的方式. 導(dǎo)致其被自適應(yīng)為 data://www.a.com/a.js 而不是我們期望的http://www.a.com/a.js . 所以失敗了... ?所以在使用dataURI時,切忌寫全協(xié)議頭.

?

那么我們看看,哪些瀏覽器可以毫無顧忌的使用DataURI方式.

Firefox1.0+?(FF0.8版本,我沒測.無視它吧)

Chrome10+

Safari5.1+

Opera11.6+

?

而其他低版本的非IE,存在的問題,并不直接和DataURI有關(guān).

大多數(shù)情況下,他們和加載資源的方式有關(guān)系, 比如.js 使用link href的方式加載. .css使用 script方式加載等. 這樣的本質(zhì)問題是,瀏覽器會有一個會話級的緩存.它會記住第一次加載某資源的類型,如果它記住某個本來是.js的資源卻使用link href方式加載,則再次加載該資源時,即使你使用script src 方式常規(guī)加載. 也會導(dǎo)致這個腳本不被執(zhí)行. .只有重啟瀏覽器,才能修正這個問題.?

Chrome3-Chrome7?(Chrome1-2不存在這個類型差異的bug. )注意加載資源方式最好互相對應(yīng). (會話級緩存,該資源的類型,導(dǎo)致不渲染或不執(zhí)行.)
Safari5.0-?注意加載資源方式,最好相互對應(yīng). (會話級緩存,該資源類型,導(dǎo)致reload,更新指定資源,不會為不同類型的資源提供緩存.即link方式加載.js. .js再加載仍然是老的)

后面是比較悲劇的情況了:

Chrome8 和Chrome9?,我們無法使用script 方式加載而保證不執(zhí)行. 只能使用link 或Object 或其他方式. 但是他們?nèi)匀淮嬖诓煌愋?不執(zhí)行或不渲染的問題.

我在純技巧的角度上,始終沒有想出解決辦法, 那么我們唯一能做的是,假如 我要更新的資源,我確保它在我試圖更新它之前,它已經(jīng)被正確的方式加載過. 那么我就可以隨便使用script 或link方式,去強制更新他們了. 因為瀏覽器已經(jīng)正確的記住了他們的類型. ?所以,我們需要評估使用環(huán)境,是否可以用. 又或者我們不考慮 比較低版本的問題瀏覽器,那么一切都不是問題了.

?

我們來總結(jié)一下:

?

我們可以在

IE全系?

使用about:blank方式創(chuàng)建iframe,并使用恰當(dāng)?shù)姆绞?或者直接寫文檔流,或借助javascript偽協(xié)議)寫入文檔流,并配合<script type="text/c" src=xxx>來實現(xiàn)更新指定資源.

?

FF全系,Chrome10 +, Opera11.6+,Safari5.1+?

使用dataURI方式創(chuàng)建iframe.并使用<link rel="stylesheet" media="c" href="xxx" />方式,更新指定資源.

?

Chrome1-2

使用dataURI方式創(chuàng)建iframe,并使用<script type="text/c" src=xxx>來實現(xiàn)更新指定資源

?

Chrome3-7, Safari5.0-

使用dataURI方式創(chuàng)建iframe,并使用根據(jù)資源類型,切換使用對應(yīng)的link 或script方式來實現(xiàn)更新指定資源

?

?

?Chrome8-9

使用dataURI方式創(chuàng)建iframe,并在確保資源在一次會話中,已經(jīng)被正確方式加載過. 再使用<link rel="stylesheet" media="c" href="xxx" />方式,更新指定資源.

?

?Opera11-

目前,reload方式無解. 我仍然沒有想出應(yīng)對的辦法. 但是考慮到Opera11-,千分之一都不到的占有率.也許我們會欣慰很多. 更何況,我們可以使用ajax來搞.

?


?

最后的話

感謝你,花了這么長時間看到這里. 但是我仍然要說一下這個方案最終沒有被采納的原因. 還記得我一再說的 流氓緩存代理么. 是的.我們的很多ISP,都有流氓緩存代理存在.

他們會無視 Cache-Control : no-cache請求頭域, 無視 Cache-Control: s-maxage=0 響應(yīng)頭域. (僅對緩存代理有效的一個響應(yīng)頭.),甚至無視Expires 和 max-age= xxx.自顧自的去緩存任何資源. 在我的測試中.部分ISP的緩存代理.甚至還會緩存 .php 的 動態(tài)資源(即no-cache的資源). 而有的緩存代理則有擴展名策略. 即,假設(shè)某資源是一個常見的 動態(tài)資源文件的擴展名.比如 .php .aspx ?之類的東西.他們會和 .js .css等常見靜態(tài)資源的緩存策略存在差異(即使,他們的響應(yīng)頭域完全一致). 遺憾的是.這樣稍微有點良心的流氓ISP.最多只占一半..

我在實際測試中.遇到的最多的 無法更新的用戶.大多數(shù)來自教育網(wǎng). ?不死心的我. 最終出了必殺技. ?我們啟用了在教育網(wǎng)內(nèi)部的布置了雙線接入?CDN節(jié)點. . 試圖攔截部分來自教育網(wǎng)的請求到該節(jié)點上. 然后當(dāng)該節(jié)點回源時.走另外的出口.而不走教育網(wǎng)統(tǒng)一的出口.以繞過教育網(wǎng)公共出口的流氓緩存代理. 但是效果也十分不理想. ?一部分可能是因為 相當(dāng)多的用戶的local dns 解析到了錯誤的節(jié)點上. 一部分則可能是各個院校自己也存在類似的緩存代理.用于節(jié)省其流量成本. ??

?

  還記得前陣子,方舟子 污蔑360瀏覽器竄號的問題么? 是的.就是污蔑. 因為竄號就是類似的流氓ISP的流氓緩存代理導(dǎo)致的. 導(dǎo)致其緩存了 setCookie: sessionID的響應(yīng).. ? 這種問題誣陷給瀏覽器.不得不說 Mr Know all 方舟子先生.實在是太能胡扯了.

?

  好吧.似乎跑題了. 我要說的就是這些了. ?我在這個方案上.大概花了兩個月的時間. 一個月.寫代碼.并不斷完善. ?另一個月做實際測試. 最終得到的結(jié)果很讓人傷心.. 特更新此文記錄之.

?

?  最后補充一點數(shù)據(jù):?

    類似流氓緩存代理.即更新失敗的情況大約占8%左右.

    Cache-Control: s-maxage=0響應(yīng)頭域的使用.是有一定效果的. 包括教育網(wǎng)內(nèi)的CDN節(jié)點.也能起到一定作用. 我最高的時候.看到的某一天的數(shù)據(jù).達(dá)到96%的更新成功率. 說以說.最理想的狀態(tài)下.也有4%的用戶會更新失敗. 這是難以接受的情況.?

?轉(zhuǎn)載于:http://www.cnblogs.com/_franky/archive/2012/07/05/2577141.html

總結(jié)

以上是生活随笔為你收集整理的浏览器静态资源的版本控制新思路.强制更新指定资源缓存.的探讨的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。