Expires headers 是什麼?
Expires headers:直接翻譯是過時頭。Expires headers 告訴瀏覽器是否應該從服務器請求一個特定的文件或者是否應該從瀏覽器的緩存抓住它。Expires headers 的設計目的是但願使用緩存來減小HTTP requests的數量,從而減小HTTP相應的大小。javascript
Expires headers 中的 Expires 說明了 Expires headers 是有時間限制的,只有在這個指定的時間期限內,瀏覽器纔會從緩存讀取數據,而超過這個時間期限,再次訪問同一個頁面時瀏覽器仍是會向服務器發起 HTTP requests,從服務器端下載頁面所需的文件。css
Max-age 和 mod_expires
(註釋:本小節內容節選至Steve Souders的《High Performance Web Sites》)前端
在介紹如何很好地改善傳輸性能以前,須要說起除了 Expires 頭以外另外一種選擇。 HTTP 1.1 引入了 Cache-Control頭來客服Expires頭的限制。由於 Expires 頭使用一個特定的時間,它要求服務器和客戶端的時鐘嚴格同步。另外,過時日期須要常常檢查,而且一旦將來這一天到來了,還要在服務器中提供一個新的日期。java
換一種方式,Cache-Control 使用 max-age 指令制定組件被緩存多久。它以秒爲單位定義了一個更新窗。若是組件被請求開始過去的時間少於max-age,瀏覽器就使用緩存版本,這就避免了額外的HTTP請求。一個長久的max-age頭能夠刷新窗爲將來10年。web
Cache-Control: max-age=315360000
使用帶有max-age的Cache-Control能夠消除Expires頭的限制,但對於不支持HTTP 1.1的瀏覽器(儘管這隻佔你的訪問量的1%之內),你可能仍然但願體統Expires頭,你能夠同時指定兩個響應頭——Expires和Cache-Control max-age。若是二者同時出現,HTTP 規範規定max-age指令將重寫Expires頭。因此,你會看到在 PageSpeed 的前端性能優化規則 Leverage browser caching 看到這樣的說明:瀏覽器
It is important to specify one of Expires or Cache-Control max-age, and one of Last-Modified or ETag, for all cacheable resources. It is redundant to specify both Expires and Cache-Control: max-age, or to specify both Last-Modified and ETag.緩存
Google 的工程師認爲同時設置 Expires 和 Cache-Control: max-age 是一種冗餘,可是實際的開發中,爲了前面提到的不支持HTTP 1.1的不到1%的用戶,咱們一般仍是同時設置了這個兩個頭信息。性能優化
另外, PageSpeed 文檔對於HTTP 1.1 提供的相應的緩存頭信息作了更具體的介紹:服務器
- Expires and Cache-Control: max-age. These specify the 「freshness lifetime」 of a resource, that is, the time period during which the browser can use the cached resource without checking to see if a new version is available from the web server. They are 「strong caching headers」 that apply unconditionally; that is, once they’re set and the resource is downloaded, the browser will not issue any GET requests for the resource until the expiry date or maximum age is reached.
- Last-Modified and ETag. These specify some characteristic about the resource that the browser checks to determine if the files are the same. In the Last-Modified header, this is always a date. In the ETag header, this can be any value that uniquely identifies a resource (file versions or content hashes are typical). Last-Modified is a 「weak」 caching header in that the browser applies a heuristic to determine whether to fetch the item from cache or not. (The heuristics are different among different browsers.) However, these headers allow the browser to efficiently update its cached resources by issuing conditional GET requests when the user explicitly reloads the page. Conditional GETs don’t return the full response unless the resource has changed at the server, and thus have lower latency than full GETs.
Expires 和 Cache-Control: max-age. 制定緩存文件的 「freshness lifetime」(這裏我就翻譯爲過時時間)。這個前面我已經介紹過了,只是 「the browser will not issue any GET requests for the resource until the expiry date or maximum age is reached.」,更準確的說應該是說瀏覽器仍是會向服務器發送一個GET請求,只是服務器發現請求的文件是一個設置了 Expires header 的文件,就只返回一個304的狀態信息,而不是發送一個完整的文件到瀏覽器,接受到304狀態碼後,瀏覽器就會請求本地已經緩存的靜態文件了。網絡
Last-Modified and ETag的說明在介紹另外的 YSlow 的規則 Configure entity tags (ETags) 會給你們作詳細的說明。這裏簡單的說明一下,這兩個是頭服務器在檢測緩存的組件是否和原始服務器上的組件匹配時有兩種方式。
服務器會經過 Last-Modified 相應頭來返回組件的最新修改日期,而瀏覽器會使用 If-Modified-Since 頭將最新修改日期傳回到服務器經行比較。若是比配,就會返回 304狀態碼,而不是從新下載組件。ETag 提供了另一種方式,瀏覽器經過 If-None-Match 頭髮送本地資源的 ETag值,跟服務器端返回的ETag值對比,一致則返回304狀態,瀏覽器讀取本地資源。ETag 值的優先級比 Last-Modified 高,若是服務器端的 Last-Modified 和本地的 If-Modified-Since 值相同,可是服務器和本地的 ETag 值和 If-None-Match 不一樣,瀏覽器而就會從新下載請求的資源。
爲何要 Add Expires headers?
咱們知道,當你訪問一個網站,你的瀏覽器負責從服務器下載所需的全部文件。這裏的下載就是咱們前面介紹過的HTTP requests。因爲HTTP協議是無狀態協議,因此若是不加任何處理的話,瀏覽器在訪問同一個頁面時是會反覆向服務器請求相同文件的,這樣會給服務器帶來沒必要要的下載負擔。而隨着網頁內容變得愈來愈豐富,因此頁面的 HTTP requests 也愈來愈多。設置 Expires headers,讓瀏覽器從本地讀取已經下載過的緩存文件就會減小不少 HTTP requests 了。固然,頁面的加載速度就會快不少。
這裏有兩點須要明確:
- Add Expires headers 可以減小 HTTP requests,是指的在瀏覽器再次訪問同一個頁面(或者再次請求同一個文件)時,瀏覽器纔會從本地讀取緩存文件。而用戶第一次訪問頁面時,Expires headers是不起做用的。因此纔有《前端性能優化:Make fewer HTTP requests》中介紹的,在首次加載Web頁面時的 HTTP requests 一般會比再次訪問時的 HTTP requests 多。
- Expires headers 是有時間限制的,超過了指定的過時時間,瀏覽器會再次想服務器發出 HTTP requests,而不是讀取本地的緩存文件。
Add Expires headers 的規則
在瞭解了 Expires headers 相關的基礎知識後,咱們如今要介紹的是 Add Expires headers 的規則,仍是看看YSlow的文檔吧:
There are two aspects to this rule:
- For static components: implement 「Never expire」 policy by setting far future
Expires
header- For dynamic components: use an appropriate
Cache-Control
header to help the browser with conditional requests
咱們看到,有兩點規則:
- 靜態文件:落實「永不過時」的原則,經過設置一個足夠長的過時時間來實現。
- 動態文件:設置一個適當的
Cache-Control
頭。怎麼纔算適當?原則很簡單,根據這個動態內容的變動頻率作出設置,常常修改的,就設置較短的過時時間,不怎麼變更的就設置長一些的過時時間。
另外要說明一點,YSlow 建議靜態文件最短的過時時間爲6天。 而 PageSpeed 則認爲要設置得更就一些,至少30天。
Set Expires to a minimum of one month, and preferably up to one year.
最後要注意的就是添加了(長時間的) Expires headers 的文件,若是在設置的過時時間內須要修改時,你必須修改文件名或者添加修改文件的時間戳。這樣用戶再訪問頁面時,纔會向服務器請求新文件。例如你須要修改首頁的layout.css文件,你能夠這麼處理:
// 原來的調用處理 <link href="/css/layout.css" rel="stylesheet" /> // 調用新文件的處理 <link href="/css/layout-v20140913.css" rel="stylesheet" /> // 或者這麼處理 <link href="/css/layout.css?version=v20140913" rel="stylesheet" />
如何 Add Expires headers?
在介紹具體的服務器端的設置時,其實你須要先肯定你要給哪些(這裏主要是針對靜態文件)文件設置 Expires headers。一般須要緩存的文件有這些:
- Images: jpg, gif, png
- favicon/ico
- JavaScript: js
- CSS: css
- Flash: swf
- PDF: pdf
- media files:視頻,音頻文件
HTML 文件不要設置 Expires headers。實際的開發經驗告訴我,給HTML文件添加 Expires headers 會帶來不少的麻煩。即使你要添加 Expires headers,也儘可能設置較短的過時時間。這一點在 PageSpeed 的 Leverage browser caching 規則中也明確提到了:
In general, HTML is not static, and shouldn’t be considered cacheable.
知道要緩存哪些資源文件後,接着就是預判這些文件的變動頻率,設置合適的過時時間。仍是前面的原則,變動頻繁的 Expire 時間就越短,不怎麼變更的就能夠設置長的過時時間,也就是落實「永不過時」的原則。
Apache 服務器配置 Expires headers
接下來就是在服務器端設置 Expires headers 了,這裏以Apache服務器爲例,咱們在 .htaccess 文件中配置(.htaccess是跟目錄下的一個隱藏文件)添加以下代碼:
<IfModule mod_expires.c>
# Enable expirations
# 開啓 Expires headers
ExpiresActive On
# Default directive
# 默認的過時時間
ExpiresDefault "access plus 1 month"
Cache-Control max-age=2592000
# My favicon
# 針對 ICON 文件的配置
ExpiresByType image/x-icon "access plus 1 year"
# Images
# 針對圖片的配置
ExpiresByType image/gif "access plus 1 month"
ExpiresByType image/png "access plus 1 month"
ExpiresByType image/jpg "access plus 1 month"
ExpiresByType image/jpeg "access plus 1 month"
# CSS # 針對 CSS 文件的配置 ExpiresByType text/css "access 1 month" # Javascript # 針對 JavaScript 文件的配置 ExpiresByType application/javascript "access plus 1 year" </IfModule>
Ngnix 服務器配置 Expires headers
現實的狀況是如今的在服務器端網站的靜態文件一般都是經過 Ngnix 服務器處理的。而後經過Ngnix配置反向帶代理指向Apache服務器處理動態內容。在Ngnix 服務器的配置代碼以下:
# 如下代碼加入到ngnix服務器的server區塊中 server { # cache static files location ~* \.(gif|jpe?g|png|ico|swf)$ { # d - 天 # h - 小時 # m - 分鐘 expires 168h; add_header Pragma public; add_header Cache-Control "public, must-revalidate, proxy-revalidate"; } # 因爲js和css文件須要改動,設置的時間爲5分鐘 location ~* \.(css|js)$ { expires 5m; add_header Pragma public; add_header Cache-Control "public, must-revalidate, proxy-revalidate"; } }
這裏還要另外補充一下 PageSpeed 文檔中說起的設置緩存的一些技巧:
- Use fingerprinting to dynamically enable caching.
- For resources that change occasionally, you can have the browser cache the resource until it changes on the server, at which point the server tells the browser that a new version is available. You accomplish this by embedding a fingerprint of the resource in its URL (i.e. the file path). When the resource changes, so does its fingerprint, and in turn, so does its URL. As soon as the URL changes, the browser is forced to re-fetch the resource. Fingerprinting allows you to set expiry dates long into the future even for resources that change more frequently than that. Of course, this technique requires that all of the pages that reference the resource know about the fingerprinted URL, which may or may not be feasible, depending on how your pages are coded.
- Set the Vary header correctly for Internet Explorer.
- Internet Explorer does not cache any resources that are served with the Vary header and any fields but Accept-Encoding and User-Agent. To ensure these resources are cached by IE, make sure to strip out any other fields from the Vary header, or remove the Vary header altogether if possible.
- Avoid URLs that cause cache collisions in Firefox.
- The Firefox disk cache hash functions can generate collisions for URLs that differ only slightly, namely only on 8-character boundaries. When resources hash to the same key, only one of the resources is persisted to disk cache; the remaining resources with the same key have to be re-fetched across browser restarts. Thus, if you are using fingerprinting or are otherwise programmatically generating file URLs, to maximize cache hit rate, avoid the Firefox hash collision issue by ensuring that your application generates URLs that differ on more than 8-character boundaries.
- Use the Cache control: public directive to enable HTTPS caching for Firefox.
- Some versions of Firefox require that the Cache control: public header to be set in order for resources sent over SSL to be cached on disk, even if the other caching headers are explicitly set. Although this header is normally used to enable caching by proxy servers (as described below), proxies cannot cache any content sent over HTTPS, so it is always safe to set this header for HTTPS resources.
- Use fingerprinting to dynamically enable caching:fingerprinting 處理是針對偶爾會修改的文件,但不肯定何時修改的時候採起的處理措施。其實這個處理技巧我以爲最簡單的應用就是我前面提到的時間戳的處理技巧。你能夠給這個文件設計較長的過時時間,可是你卻能夠比較頻繁的修改,一旦版本肯定後,長的過時時間仍是會發揮做用。
- Set the Vary header correctly for Internet Explorer:針對IE瀏覽器設置 Vary 頭。IE 瀏覽器不會緩存被送達 Vary 頭和任何領域的任何資源,但接受Accept-Encoding 和 User-Agent。因此爲了確保IE瀏覽可以正確緩存資源,應該去掉 Vary 頭信息中的其餘信息,若是能夠乾脆就清空Vary 頭信息。不過服務器端咱們一般設置 Vary:Accept-Encoding,而且是針對文本類型的資源文件。
- Avoid URLs that cause cache collisions in Firefox:簡單的講,這是針對Firefox對相同URL地址文件只緩存其中一個要注意的問題。在使用fingerprint相似技巧自動命名文件名的時候,生成的文件名必定要超過8個字符長度,避免Firefox重名文件產生相同的hash key。(不過這種狀況,我實際開發式尚未遇到過,不過你們能夠參考一下 Remove duplicate JavaScript and CSS 規則中介紹的關於重複調用資源的問題。)
- Use the Cache control: public directive to enable HTTPS caching for Firefox:也是針對Firefox的設置。在 Cache-Control頭中添加 public值能夠確保Firefox緩存HTTPS協議中請求的資源。實際上在我上面的 Ngnix 服務器中配置時就設置了public值:
add_header Cache-Control "public, must-revalidate, proxy-revalidate";
空緩存VS完整緩存
湖北省政府門戶網站首頁的空緩存網絡請求視圖
湖北省政府門戶網站首頁的完整緩存網絡請求視圖
看看我如今工做的湖北省政府門戶網站首頁的空緩存和完整緩存的網絡請求視圖視圖你們應該是一目瞭然了。空緩存時頁面請求的大小是1.9M,46個請求,完成頁面加載須要43.73秒,而完整緩存請求的大小是214.2K,只有4個新請求,頁面加載時間才526毫秒。不用過多解釋了,設置 Expires headers 帶來的性能優化的效果