笔者所在项目使用的前端技术比较老旧,在开发的过程当中须要先启动一个后端项目 (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 推送过来的