筆者所在項目使用的前端技術比較老舊,在開發的過程當中須要先啓動一個後端項目 (tomcat + mysql + redis) 來作爲靜態服務器javascript
而後使用的是一個公司內部的類AMD模塊加載工具,每次刷新頁面都要加載1000+ 的文件,頁面的響應時間接近20s, 致使開發的過程很是痛苦css
因此決定使用 HTTP/2 來開發一個開發服務器來加快頁面的加載速度. 目前來講效果不錯,相對於 HTTP1.1 來講加載速度提高了 50%。html
對於開發環境與咱們相似的項目,能夠嘗試一下。前端
雖然咱們開發的時候使用的是本地服務器,創建鏈接的速度和下載速度都很快,可是瀏覽器針對同一域名的併發請求是有上限的。java
當所須要的文件數量不少時,咱們每次只能請求必定數量的文件,當前面的文件的請求完成後才能去請求下一個文件,這就形成了堵塞。node
從圖中咱們能夠看到明顯的連接限制和堵塞mysql
而 HTTP/2 能夠在同一鏈接上進行多個併發交換,能夠避免出現由於瀏覽器的併發限制而形成的堵塞git
HTTP/2 經過支持標頭字段壓縮和在同一鏈接上進行多個併發交換,讓應用更有效地利用網絡資源,減小感知的延遲時間。具體來講,它能夠對同一鏈接上的請求和響應消息進行交錯發送併爲 HTTP 標頭字段使用有效編碼。 HTTP/2 還容許爲請求設置優先級,讓更重要的請求更快速地完成,從而進一步提高性能。出臺的協議對網絡更加友好,由於與 HTTP/1.x 相比,可使用更少的 TCP 鏈接。 這意味着與其餘流的競爭減少,而且鏈接的持續時間變長,這些特性反過來提升了可用網絡容量的利用率。 最後,HTTP/2 還能夠經過使用二進制消息分幀對消息進行更高效的處理。(超文本傳輸協議版本 2,草案 17) - https://developers.google.com/web/fundamentals/performance/http2/?hl=zh-cngithub
HTTP/2 中最使人期待的特性就是 Sever Push (服務器推送)。web
經過 Server Push,服務器能夠對瀏覽器的單個請求返回多個響應,而不須要等待瀏覽器發出請求再給去響應。
簡單舉個🌰
結合上面的鏈接複用,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 推送過來的