用node探究http緩存

来源:https://www.cnblogs.com/floor/archive/2018/10/28/9867194.html
-Advertisement-
Play Games

用node搞web服務和直接用tomcat、Apache做伺服器不太一樣, 很多工作都需要自己做。緩存策略也要自己選擇,雖然有像koa-static,express.static這些東西可以用來管理靜態資源,但是為了開發或配置時更加得心應手,知其所以然,有瞭解http緩存的必要。另外,http緩存作... ...


用node搞web服務和直接用tomcat、Apache做伺服器不太一樣, 很多工作都需要自己做。緩存策略也要自己選擇,雖然有像koa-static,express.static這些東西可以用來管理靜態資源,但是為了開發或配置時更加得心應手,知其所以然,有瞭解http緩存的必要。另外,http緩存作為一個前端優化的一個要點,也應該有所瞭解。

什麼是http緩存

  • RFC 7234 (https://tools.ietf.org/pdf/rfc7234.pdf)指出HTTP緩存是響應消息的本地存儲,並且是控制其中消息的存儲、檢索和刪除的子系統。

  • 通俗講: http協議規定了一些指令, 實現http協議的伺服器和瀏覽器根據這些指令決定要不要以及如何把響應存儲起來以備後續使用.

http緩存的意義

  1. 提高響應速度
  2. 減少帶寬占用, 省流量
  3. 減小伺服器壓力

不指定任何與緩存有關的指令

這種情況下瀏覽器不做緩存, 每次都會想伺服器請求. 但是比較奇怪的是在nginx的實現中, 這種情況下還是被代理伺服器做了緩存.也就是說, 當多次請求同一個資源時, 代理伺服器只向源伺服器請求一次.

演示第1個例子nothing_1

強制緩存

  • 所謂強制緩存就是給出資源的到期時間expires或者有效時間max-age, 在這個時間之內該資源應該被緩存.

  • 如何讓一個資源被強緩存

1.expires

這個欄位定義了一個資源到期的時間. 看一個實際的例子:

可以看到這個expires是個GMT時間, 它的工作機制是, 首次請求時, 伺服器在響應中加上expires標識資源的到期時間, 瀏覽器緩存這個資源, 再次請求時, 瀏覽器將上一次請求到這個資源的過期時間與自己的系統時間對比, 若系統時間小於過期時間, 則證明資源沒有過期, 直接用上次緩存的資源, 不必請求; 否則重新請求, 伺服器在響應中給出新的過期時間.

演示第9個例子expires_9

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}

expires 存在的問題是他依賴於客戶端的系統時間, 客戶端系統時間錯誤可能會引起判斷錯誤. HTTP1.1增加了Cache-Control解決此問題, 這個指令值比較豐富, 常見的如下:

  • public/private: 標識資源能不能被代理伺服器緩存, public 標識資源既能被代理伺服器緩存也能被瀏覽器緩存, private標識資源只能被瀏覽器緩存, 不能被代理伺服器緩存.
  • max-age: 用於指定在客戶端緩存的有效時間, 單位s, 超過n秒需要重新請求, 不超過則可以使用緩存
  • s-maxage: 這個是針對代理伺服器的, 表示資源在代理伺服器緩存時間沒有超過這個時間不必向源伺服器請求, 否則需要.
  • no-cache: 有這個指令表示不走瀏覽器緩存了, 協商緩存還可以走
  • no-store: 強制無緩存, 協商緩存也不走了, 測試發下即使響應中有Last-Modified, 瀏覽器請求時頁不會帶If-Modified-Since

一個實例

演示第2,3,4,5,7

協商緩存

  • 所謂協商緩存就是客戶端想用緩存資源時先向伺服器詢問, 如果伺服器如果認為這個資源沒有過期, 可以繼續用則給出304響應, 客戶端繼續使用原來的資源; 否則給出200, 併在響應body加上資源, 客戶端使新的資源.

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

如何選擇緩存策略

https://developers.google.cn/web/fundamentals/performance/optimizing-content-efficiency/http-caching?hl=zh-cn

附錄

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

這是一個付費的冊子,可能沒法訪問


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • Android預設的Toast太醜了,我們來封裝一個花里胡哨的Toast吧,就叫ColoredToast。 Github:https://github.com/imcloudfloating/DesignApp 效果: Toast有一個setView方法,通過它我們可以設置自定義的佈局,這裡我只是加 ...
  • C/S架構(Client/Server,即客戶機/伺服器模式)分為客戶機和伺服器兩層:第一層是在客戶機系統上結合了表示與業務邏輯,第二層是通過網路結合了資料庫伺服器。簡單的說就是第一層是用戶表示層,第二層是資料庫層。客戶端和伺服器直接相連,這兩個組成部分都承擔著重要的角色。 Android內核是基於 ...
  • 前面已經封裝了很多常用、基礎的組件了: "base module" , 包括了: crash 處理 常用工具類 apk 升級處理 log 組件 logcat 採集 ftp 文件上傳 blur 高斯模糊 fresco 圖片處理 等等 那麼,今天繼續再來封裝一個網路組件,基於 "volley" 的二次封 ...
  • Application是Android的又一大組件,在App運行過程中,有且僅有一個Application對象貫穿應用的整個生命周期,所以適合在Application中保存應用運行時的全局變數。而開展該工作的基礎,是必須獲得Application對象的唯一實例,也就是將Application單例化。 ...
  • 元素分類: 1.行級元素:內聯元素 inline 特征:內容決定元素所占位置,不可以通過CSS改變寬高 span strong em a del 2.塊級元素:block特征:獨占一行,可以通過CSS改變寬高 div p ul li ol form address 3.行級塊元素:inline-bl ...
  • 看完一整本書,結果寫不出什麼東西,按書上教程來,基本能把例子完成個七七八八,可是用padding還是margin完全整不清……,而且只要結構一複雜,元素就各種不受控制。 沒辦法 找來韓順平老師的視頻(沒錯,就是在網上不知道怎麼搞來的) 看完老師講完整個html&CSS部分2,終於把這個內容吃透了。 ...
  • Vue.js 的源碼都是在src 目錄下,其目錄結構如下。 1.compiler 目錄包含Vue.js 所有編譯相關的代碼。它包括把所有模板解析成ast 語法樹, ast 語法樹優化等功能。 2.core 目錄 包含了Vue.js 的核心代碼,包括內置組件,全局API封裝,Vue 實例化,觀察者,虛 ...
  • 上一篇博客我向大家介紹了基於ko-easyui實現的開發模板,博客地址:https://www.cnblogs.com/cqhaibin/p/9825465.html#4095185。但在還遺留三個問題。本篇幅文章就以解決這三問題展開。 一、代理 前後端分離的開發模式,一定會存在前端開發工程,與後端 ...
一周排行
    -Advertisement-
    Play Games
  • 前言 本文介紹一款使用 C# 與 WPF 開發的音頻播放器,其界面簡潔大方,操作體驗流暢。該播放器支持多種音頻格式(如 MP4、WMA、OGG、FLAC 等),並具備標記、實時歌詞顯示等功能。 另外,還支持換膚及多語言(中英文)切換。核心音頻處理採用 FFmpeg 組件,獲得了廣泛認可,目前 Git ...
  • OAuth2.0授權驗證-gitee授權碼模式 本文主要介紹如何筆者自己是如何使用gitee提供的OAuth2.0協議完成授權驗證並登錄到自己的系統,完整模式如圖 1、創建應用 打開gitee個人中心->第三方應用->創建應用 創建應用後在我的應用界面,查看已創建應用的Client ID和Clien ...
  • 解決了這個問題:《winForm下,fastReport.net 從.net framework 升級到.net5遇到的錯誤“Operation is not supported on this platform.”》 本文內容轉載自:https://www.fcnsoft.com/Home/Sho ...
  • 國內文章 WPF 從裸 Win 32 的 WM_Pointer 消息獲取觸摸點繪製筆跡 https://www.cnblogs.com/lindexi/p/18390983 本文將告訴大家如何在 WPF 裡面,接收裸 Win 32 的 WM_Pointer 消息,從消息裡面獲取觸摸點信息,使用觸摸點 ...
  • 前言 給大家推薦一個專為新零售快消行業打造了一套高效的進銷存管理系統。 系統不僅具備強大的庫存管理功能,還集成了高性能的輕量級 POS 解決方案,確保頁面載入速度極快,提供良好的用戶體驗。 項目介紹 Dorisoy.POS 是一款基於 .NET 7 和 Angular 4 開發的新零售快消進銷存管理 ...
  • ABP CLI常用的代碼分享 一、確保環境配置正確 安裝.NET CLI: ABP CLI是基於.NET Core或.NET 5/6/7等更高版本構建的,因此首先需要在你的開發環境中安裝.NET CLI。這可以通過訪問Microsoft官網下載並安裝相應版本的.NET SDK來實現。 安裝ABP ...
  • 問題 問題是這樣的:第三方的webapi,需要先調用登陸介面獲取Cookie,訪問其它介面時攜帶Cookie信息。 但使用HttpClient類調用登陸介面,返回的Headers中沒有找到Cookie信息。 分析 首先,使用Postman測試該登陸介面,正常返回Cookie信息,說明是HttpCli ...
  • 國內文章 關於.NET在中國為什麼工資低的分析 https://www.cnblogs.com/thinkingmore/p/18406244 .NET在中國開發者的薪資偏低,主要因市場需求、技術棧選擇和企業文化等因素所致。歷史上,.NET曾因微軟的閉源策略發展受限,儘管後來推出了跨平臺的.NET ...
  • 在WPF開發應用中,動畫不僅可以引起用戶的註意與興趣,而且還使軟體更加便於使用。前面幾篇文章講解了畫筆(Brush),形狀(Shape),幾何圖形(Geometry),變換(Transform)等相關內容,今天繼續講解動畫相關內容和知識點,僅供學習分享使用,如有不足之處,還請指正。 ...
  • 什麼是委托? 委托可以說是把一個方法代入另一個方法執行,相當於指向函數的指針;事件就相當於保存委托的數組; 1.實例化委托的方式: 方式1:通過new創建實例: public delegate void ShowDelegate(); 或者 public delegate string ShowDe ...