筆者所在項目使用的前端技術比較老舊,在開發的過程當中須要先啓動一個後端項目 (tomcat + mysql + redis) 來作爲靜態服務器javascript
而後使用的是一個公司內部的類AMD模塊加載工具,每次刷新頁面都要加載1000+ 的文件,頁面的響應時間接近20s, 致使開發的過程很是痛苦css
因此決定使用 HTTP/2 來開發一個開發服務器來加快頁面的加載速度. 目前來講效果不錯,相對於 HTTP1.1 來講加載速度提高了 50%。html
對於開發環境與咱們相似的項目,能夠嘗試一下。前端
雖然咱們開發的時候使用的是本地服務器,創建鏈接的速度和下載速度都很快,可是瀏覽器針對同一域名的併發請求是有上限的。java
當所須要的文件數量不少時,咱們每次只能請求必定數量的文件,當前面的文件的請求完成後才能去請求下一個文件,這就形成了堵塞。node
從圖中咱們能夠看到明顯的連接限制和堵塞mysql
而 HTTP/2 能夠在同一鏈接上進行多個併發交換,能夠避免出現由於瀏覽器的併發限制而形成的堵塞web
HTTP/2 經過支持標頭字段壓縮和在同一鏈接上進行多個併發交換,讓應用更有效地利用網絡資源,減小感知的延遲時間。具體來講,它能夠對同一鏈接上的請求和響應消息進行交錯發送併爲 HTTP 標頭字段使用有效編碼。 HTTP/2 還容許爲請求設置優先級,讓更重要的請求更快速地完成,從而進一步提高性能。出臺的協議對網絡更加友好,由於與 HTTP/1.x 相比,可使用更少的 TCP 鏈接。 這意味着與其餘流的競爭減少,而且鏈接的持續時間變長,這些特性反過來提升了可用網絡容量的利用率。 最後,HTTP/2 還能夠經過使用二進制消息分幀對消息進行更高效的處理。(超文本傳輸協議版本 2,草案 17) - https://developers.google.com...
HTTP/2 中最使人期待的特性就是 Sever Push (服務器推送)。redis
經過 Server Push,服務器能夠對瀏覽器的單個請求返回多個響應,而不須要等待瀏覽器發出請求再給去響應。sql
簡單舉個?
結合上面的鏈接複用,HTTP/2 能夠極大的加快資源文件的加載速度
能夠看到瀏覽器使用一個連接加載完了全部的資源文件
這裏先簡單介紹下 Node 中 HTTP/2 的使用,下篇文章將詳細闡述如何編寫一個能夠應用的 HTTP/2 開發服務器
因爲 HTTP/2 須要使用 HTTPS,這裏咱們須要先生成一個證書。
openssl req -x509 -newkey rsa:2048 -nodes -sha256 -subj '/CN=localhost' \ -keyout localhost-privkey.pem -out localhost-cert.pem
. ├── certificate │ ├── localhost-cert.pem │ └── localhost-privkey.pem ├── node_modules ├── package-lock.json ├── package.json ├── src │ └── app.js └── www ├── index.html ├── script.js └── styles.css
<!-- www/index.html --> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>HTTP2 demo</title> <link rel="stylesheet" href="styles.css"> </head> <body> <div id="content"></div> <script src="script.js"></script> </body> </html>
// www/script.js const content = document.querySelector("#content"); content.innerHTML = `<h1>Hello HTTP/2</h1>`;
/* www/styles.css */ h1 { color: cornflowerblue; }
建立如上的項目結構
const http2 = require("http2"); const fs = require("fs"); const path = require("path"); const server = http2.createSecureServer({ key: fs.readFileSync( path.resolve(__dirname, "../certificate/localhost-privkey.pem") ), cert: fs.readFileSync( path.resolve(__dirname, "../certificate/localhost-cert.pem") ) }); server.on("error", err => console.error(err)); server.on("stream", (stream, headers) => { // stream is a Duplex stream.respond({ "content-type": "text/html", ":status": 200 }); stream.end("<h1>Hello World</h1>"); }); server.listen(8443);
打開控制檯,進入 Network ,開啓 Protocol 顯示
訪問 https://localhost:8443/ ,便可看到協議變爲 h2
const http2 = require("http2"); const fs = require("fs"); const path = require("path"); const pemPath = path.resolve(__dirname, "../certificate/localhost-privkey.pem"); const certPaht = path.resolve(__dirname, "../certificate/localhost-cert.pem"); // 獲取 HTTP2 header 常量 const { HTTP2_HEADER_PATH, HTTP2_HEADER_STATUS } = http2.constants; // 獲取靜態目錄下的全部文件信息 function createFileInfoMap() { let fileInfoMap = new Map(); const fileList = fs.readdirSync(staticPath); const contentTypeMap = { js: "application/javascript", css: "text/css", html: "text/html" }; fileList.forEach(file => { const fd = fs.openSync(path.resolve(staticPath, file), "r"); const contentType = contentTypeMap[file.split(".")[1]]; const stat = fs.fstatSync(fd); const headers = { "content-length": stat.size, "last-modified": stat.mtime.toUTCString(), "content-type": contentType }; fileInfoMap.set(`/${file}`, { fd, headers }); }); return fileInfoMap; } // 定義靜態目錄 const staticPath = path.resolve(__dirname, "../www"); const fileInfoMap = createFileInfoMap(); // 將傳入的文件推送到瀏覽器 function push(stream, path) { const file = fileInfoMap.get(path); if (!file) { return; } stream.pushStream({ [HTTP2_HEADER_PATH]: path }, (err, pushStream) => { pushStream.respondWithFD(file.fd, file.headers); }); } const server = http2.createSecureServer({ key: fs.readFileSync(pemPath), cert: fs.readFileSync(certPaht) }); server.on("error", err => console.error(err)); server.on("stream", (stream, headers) => { // 獲取請求路徑 let requestPath = headers[HTTP2_HEADER_PATH]; // 請求到 '/' 的請求返回 index.html if (requestPath === "/") { requestPath = "/index.html"; } // 根據請求路徑獲取對應的文件信息 const fileInfo = fileInfoMap.get(requestPath); if (!fileInfo) { stream.respond({ [HTTP2_HEADER_STATUS]: 404 }); stream.end("Not found"); } // 訪問首頁時同時推送其餘文件資源 if (requestPath === "/index.html") { for (let key in fileInfoMap.keys()) { push(stream, key); } } // 推送首頁數據 stream.respondWithFD(fileInfo.fd, { ...fileInfo.headers }); }); server.listen(8443);
訪問 https://localhost:8443 就能夠看到 styles.css 和 script.js 是經過 HTTP/2 推送過來的