用node搞web服務和直接用tomcat、Apache作服務器不太同樣, 不少工做都須要本身作。緩存策略也要本身選擇,雖然有像koa-static,express.static這些東西能夠用來管理靜態資源,可是爲了開發或配置時更加駕輕就熟,知其因此然,有了解http緩存的必要。另外,http緩存做爲一個前端優化的一個要點,也應該有所瞭解。
這種狀況下瀏覽器不作緩存, 每次都會想服務器請求. 可是比較奇怪的是在nginx的實現中, 這種狀況下仍是被代理服務器作了緩存.也就是說, 當屢次請求同一個資源時, 代理服務器只向源服務器請求一次.javascript
演示第1個例子nothing_1css
expires
或者有效時間max-age
, 在這個時間以內該資源應該被緩存.1.expireshtml
這個字段定義了一個資源到期的時間. 看一個實際的例子:前端
能夠看到這個expires
是個GMT
時間, 它的工做機制是, 首次請求時, 服務器在響應中加上expires
標識資源的到期時間, 瀏覽器緩存這個資源, 再次請求時, 瀏覽器將上一次請求到這個資源的過時時間與本身的系統時間對比, 若系統時間小於過時時間, 則證實資源沒有過時, 直接用上次緩存的資源, 沒必要請求; 不然從新請求, 服務器在響應中給出新的過時時間.java
演示第9個例子expires_9node
const d = new Date(Date.now() + 5000); res.writeHead(200, { 'Content-Type': 'image/png', 'expires': d.toGMTString() }); res.end(img);
2.Cache-Control:[public | private,] max-age=${n}, s-maxage=${m}nginx
expires
存在的問題是他依賴於客戶端的系統時間, 客戶端系統時間錯誤可能會引發判斷錯誤. HTTP1.1增長了Cache-Control
解決此問題, 這個指令值比較豐富, 常見的以下:web
public
標識資源既能被代理服務器緩存也能被瀏覽器緩存, private
標識資源只能被瀏覽器緩存, 不能被代理服務器緩存.Last-Modified
, 瀏覽器請求時頁不會帶If-Modified-Since
一個實例express
演示第2,3,4,5,7後端
1.Last-Modified與If-Modified-Since
這個機制是, 服務器在響應頭中加上Last-Modified
, 通常是一個資源的最後修改時間, 瀏覽器首次請求時得到這個時間, 下一次請求時將這個時間放在請求頭的If-Modified-Since
, 服務器收到這個If-Modified-Since
時間n
後查詢資源的最後修改時間m
與之對比, 若m>n
, 給出200響應, 更新Last-Modified
爲新的值, body中爲這個資源, 瀏覽器收到後使用新的資源; 不然給出304響應, body無數據, 瀏覽器使用上一次緩存的資源.
2.Etag與If-None-Match
Last-Modified
模式存兩個問題, 一是它是秒級別的比對, 因此當資源的變化小於一秒時瀏覽器可能使用錯誤的資源; 二是資源的最新修改時間變了可能內容並無變, 可是仍是會給出完整響應, 形成浪費. 基於此在HTTP1.1引入了Etag模式.
這個與上面的Last-Modified
機制基本相同, 不過再也不是比對最後修改時間而是比對資源的標識, 這個Etag通常是基於資源內容生成的標識. 因爲Etag是基於內容生成的, 因此當且僅當內容變化纔會給出完整響應, 無浪費和錯誤的問題.
演示第8, 10
1.演示代碼
const http = require('http'); const fs = require('fs'); let etag = 0; let tpl = fs.readFileSync('./index.html'); let img = fs.readFileSync('./test.png'); http.createServer((req, res) => { etag++; // 我是個假的eTag console.log('--->', req.url); switch (req.url) { // 模板 case '/index': res.writeHead(200, { 'Content-Type': 'text/html', 'Cache-Control': 'no-store' }); res.end(tpl); break; // 1. 不給任何與緩存相關的頭, 任何狀況下, 既不會被瀏覽器緩存, 也不會被代理服務緩存 case '/img/nothing_1': res.writeHead(200, { 'Content-Type': 'image/png' }); res.end(img); break; // 2. 設置了no-cache代表每次要使用緩存資源前須要向服務器確認 case '/img/cache-control=no-cache_2': res.writeHead(200, { 'Content-Type': 'image/png', 'cache-control': 'no-cache' }); res.end(img); break; // 3. 設置max-age表示在瀏覽器最多緩存的時間 case '/img/cache-control=max-age_3': res.writeHead(200, { 'Content-Type': 'image/png', 'cache-control': 'max-age=10' }); res.end(img); break; // 4. 設置了max-age s-maxage public: public 是說這個資源能夠被服務器緩存, 也能夠被瀏覽器緩存, // max-age意思是瀏覽器的最長緩存時間爲n秒, s-maxage代表代理服務器的最長緩存時間爲那麼多秒 case '/img/cache-control=max-age_s-maxage_public_4': res.writeHead(200, { 'Content-Type': 'image/png', 'cache-control': 'public, max-age=10, s-maxage=40' }); res.end(img); break; // 設置了max-age s-maxage private: private 是說這個資源只能被瀏覽器緩存, 不能被代理服務器緩存 // max-age說明了在瀏覽器最長緩存時間, 這裏的s-maxage實際是無效的, 由於不能被代理服務緩存 case '/img/cache-control=max-age_s-maxage_private_5': res.writeHead(200, { 'Content-Type': 'image/png', 'cache-control': 'private, max-age=10, s-maxage=40' }); res.end(img); break; // 7. 能夠被代理服務器緩存, 確不能被瀏覽器緩存 case '/img/cache-control=private_max-age_7': res.writeHead(200, { 'Content-Type': 'image/png', 'cache-control': 'public, s-maxage=40' }); res.end(img); break; // 8. 協商緩存 case '/img/talk_8': let stats = fs.statSync('./test.png'); let mtimeMs = stats.mtimeMs; let If_Modified_Since = req.headers['if-modified-since']; let oldTime = 0; if(If_Modified_Since) { const If_Modified_Since_Date = new Date(If_Modified_Since); oldTime = If_Modified_Since_Date.getTime(); } mtimeMs = Math.floor(mtimeMs / 1000) * 1000; // 這種方式的精度是秒, 因此毫秒的部分忽略掉 console.log('mtimeMs', mtimeMs); console.log('oldTime', oldTime); if(oldTime < mtimeMs) { res.writeHead(200, { 'Cache-Control': 'no-cache', // 測試發現, 必需要有max-age=0 或者no-cache,或者expires爲當前, 纔會協商, 不然沒有協商的過程 'Last-Modified': new Date(mtimeMs).toGMTString() }); res.end(fs.readFileSync('./test.png')); }else { res.writeHead(304); res.end(); } // 9. 設置了expires, 表示資源到期時間 case '/img/expires_9': const d = new Date(Date.now() + 5000); res.writeHead(200, { 'Content-Type': 'image/png', 'expires': d.toGMTString() }); res.end(img); break; // 10. 設置了expires, 表示資源到期時間 case '/img/etag_10': const If_None_Match = req.headers['if-none-match']; console.log('If_None_Match,',If_None_Match); if(If_None_Match != etag) { res.writeHead(200, { 'Content-Type': 'image/png', 'Etag': String(etag) }); res.end(img); }else { res.statusCode = 304; res.end(); } break; // 11. no-store 能協商緩存嗎? 不能, 請求不會帶if-modified-since case '/img/no-store_11': const stats2 = fs.statSync('./test.png'); let mtimeMs2 = stats2.mtimeMs; let If_Modified_Since2 = req.headers['if-modified-since']; let oldTime2 = 0; if(If_Modified_Since2) { const If_Modified_Since_Date = new Date(If_Modified_Since2); oldTime2 = If_Modified_Since_Date.getTime(); } mtimeMs2 = Math.floor(mtimeMs2 / 1000) * 1000; // 這種方式的精度是秒, 因此毫秒的部分忽略掉 console.log('mtimeMs', mtimeMs2); console.log('oldTime', oldTime2); if(oldTime2 < mtimeMs2) { res.writeHead(200, { 'Cache-Control': 'no-store', // 測試發現, 必需要有max-age=0 或者no-cache,或者expires爲當前, 纔會協商, 不然沒有協商的過程 'Last-Modified': new Date(mtimeMs2).toGMTString() }); res.end(fs.readFileSync('./test.png')); }else { res.writeHead(304); res.end(); } default: res.statusCode = 404; res.statusMessage = 'Not found', res.end(); } }).listen(1234);
2.測試用代理服務器nginx配置
不要問我這是個啥, 我是copy的
worker_processes 8; events { worker_connections 65535; } http { include mime.types; default_type application/octet-stream; charset utf-8; log_format main '$http_x_forwarded_for $remote_addr $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent" "$http_cookie" $host $request_time'; sendfile on; tcp_nopush on; tcp_nodelay on; keepalive_timeout 65; proxy_connect_timeout 500; #跟後端服務器鏈接的超時時間_發起握手等候響應超時時間 proxy_read_timeout 600; #鏈接成功後_等候後端服務器響應的時間_其實已經進入後端的排隊之中等候處理 proxy_send_timeout 500; #後端服務器數據回傳時間_就是在規定時間內後端服務器必須傳完全部數據 proxy_buffer_size 128k; #代理請求緩存區_這個緩存區間會保存用戶的頭信息以供Nginx進行規則處理_通常只要能保存下頭信息便可 proxy_buffers 4 128k; #同上 告訴Nginx保存單個用的幾個Buffer最大用多大空間 proxy_busy_buffers_size 256k; #若是系統很忙的時候能夠申請更大的proxy_buffers 官方推薦*2 proxy_temp_file_write_size 128k; #設置web緩存區名爲cache_one,內存緩存空間大小爲12000M,自動清除超過15天沒有被訪問過的緩存數據,硬盤緩存空間大小200g #要想開啓nginx的緩存功能,須要添加此處的兩行內容! #設置Web緩存區名稱爲cache_one,內存緩存空間大小爲500M,緩存的數據超過1天沒有被訪問就自動清除;訪問的緩存數據,硬盤緩存空間大小爲30G proxy_cache_path /usr/local/nginx/proxy_cache_path levels=1:2 keys_zone=cache_one:500m inactive=1d max_size=30g; #建立緩存的時候可能生成一些臨時文件存放的位置 proxy_temp_path /usr/local/nginx/proxy_temp_path; fastcgi_connect_timeout 3000; fastcgi_send_timeout 3000; fastcgi_read_timeout 3000; fastcgi_buffer_size 256k; fastcgi_buffers 8 256k; fastcgi_busy_buffers_size 256k; fastcgi_temp_file_write_size 256k; fastcgi_intercept_errors on; client_header_timeout 600s; client_body_timeout 600s; client_max_body_size 100m; client_body_buffer_size 256k; gzip off; gzip_min_length 1k; gzip_buffers 4 16k; gzip_http_version 1.1; gzip_comp_level 9; gzip_types text/plain application/x-javascript text/css application/xml text/javascript; gzip_vary on; include vhosts/*.conf; server { listen 80; server_name localhost; location / { proxy_pass http://127.0.0.1:1234; proxy_set_header Host $http_host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_redirect off; proxy_cache cache_one; #此處的cache_one必須於上一步配置的緩存區域名稱相同 proxy_cache_valid 200 304 12h; proxy_cache_valid 301 302 1d; proxy_cache_valid any 1h; #不一樣的請求設置不一樣的緩存時效 proxy_cache_key $uri$is_args$args; #生產緩存文件的key,經過4個string變量結合生成 expires off; #加了這個的話會本身修改cache-control, 寫成off則不會 proxy_set_header X-Forwarded-Proto $scheme; } } }
https://juejin.im/book/5b936540f265da0a9624b04b/section/5b9ba651f265da0ac726e5de
這是一個付費的冊子,可能無法訪問