- 原文地址:Node.js can HTTP/2 push!
- 原文做者:Node.js Foundation
- 譯文出自:掘金翻譯計劃
- 本文永久連接:github.com/xitu/gold-m…
- 譯者:Raoul1996
- 校對者:Starriers、FateZeros、LeviDing
本文由來自 @nearForm 的首席架構師、Node.js 技術指導委員會成員 Matteo Collina 以及谷歌軟件工程師 Jinwoo Lee 共同撰寫。css
自從 2017 年 7 月 Node.js 中引入 HTTP/2 以來,該實踐經歷了好幾輪的改進。如今咱們基本已經準備好去掉「實驗性」標誌。固然最好使用 Node.js 版本 9 來嘗試 HTTP/2 支持,由於這個版本有着最新的修復和改進的內容。html
最簡單的入門方法是使用新版 http2 核心模塊部分提供的的兼容層:前端
const http2 = require('http2');
const options = {
key: getKeySomehow(),
cert: getCertSomehow()
};
// 必須使用 https
// 否則瀏覽器沒法鏈接
const server = http2.createSecureServer(options, (req, res) => {
res.end('Hello World!');
});
server.listen(3000);
複製代碼
兼容層提供了和 require('http')
相同的高級 API(具備請求和響應對象相同的請求偵聽器),這樣就能夠平滑的遷移到 HTTP/2。node
兼容層的也爲 web 框架做者提供了一個簡單的升級途徑,到目前爲止,Restify 和Fastify 都基於 Node.js HTTP/2 兼容層實現了對 HTTP/2 的支持。android
Fastify 是一個新的 web 框架,它專一於性能而不犧牲開發者的生產力,也不拋棄最近升級到 1.0.0 版本的豐富的插件生態系統。ios
在 fastify 中使用 HTTP/2 很是簡單:git
const Fastify = require('fastify');
// 必須使用 https
// 否則瀏覽器沒法鏈接
const fastify = Fastify({
http2: true, // 譯者注:原文做者這裏少了逗號
https: {
key: getKeySomehow(),
cert: getCertSomehow()
}
});
fastify.get('/fastify', async (request, reply) => {
return 'Hello World!';
});
server.listen(3000);
複製代碼
儘管能在 HTTP/1.1 和 HTTP/2 上運行相同的應用代碼對於協議的選擇很是重要,但單獨的兼容層並無提供 HTTP/2 支持的一些更強大的功能。http2 核心模塊能夠經過」流「偵聽器來實現對新的核心 API(Http2Stream)來使用這些額外的功能:github
const http2 = require('http2');
const options = {
key: getKeySomehow(),
cert: getCertSomehow()
};
// 必須使用 https
// 否則瀏覽器沒法鏈接
const server = http2.createSecureServer(options);
server.on('stream', (stream, headers) => {
// 流是雙工的
// headers 是一個包含請求頭的對象
// 響應將把 headers 發到客戶端
// meta headers 用冒號(:)開頭
stream.respond({ ':status': 200 });
// 這是 stream.respondWithFile()
// 和 stream.pushStream()
stream.end('Hello World!');
});
server.listen(3000);
複製代碼
在 Fastify 中, 能夠經過 request.raw.stream API 訪問 Http2Stream 以下所示:web
fastify.get('/fastify', async (request, reply) => {
request.raw.stream.pushStream({
':path': '/a/resource'
}, function (err, stream) {
if (err) {
request.log.warn(err);
return
}
stream.respond({ ':status': 200 });
stream.end('content');
});
return 'Hello World!';
});
複製代碼
HTTP/2 在 HTTP/1 的基礎上對性能進行了至關大的提高,服務端推送是其一大成果。npm
典型的(或者說是簡化的)HTTP 請求和響應的流程應該像是這樣(下面屏幕截圖是和 Hack News 的鏈接):
這意味着渲染一個 HTML 文檔一般會須要屢次請求和響應,由於瀏覽器須要額外與其關聯的資源來完成對文檔的正確渲染。若是這些相關的資源能在不須要瀏覽器請求的狀況下隨原始 HTML 文檔一塊兒發送給瀏覽器,那就太棒了。這也正是 HTTP/2 服務端推送的目的。
在 HTTP/2 中,服務器能夠主動將它認爲瀏覽器稍候會請求的額外資源和原來的請求響應一塊兒推送。若是稍後瀏覽器真的須要這些額外資源,它只是會使用已經推送的資源,而不去發送額外的請求。 例如,假設服務器正在發送這個 /index.html 文件
<!DOCTYPE html>
<html>
<head>
<title>Awesome Unicorn!</title>
<link rel="stylesheet" type="text/css" href="/static/awesome.css">
</head>
<body>
This is an awesome Unicorn! <img src="/static/unicorn.png">
</body>
</html>
複製代碼
服務器將經過發回這個文件來響應請求。但它知道 /index.html 須要 /static/awesome.css 和 /static/unicorn.png 才能正確渲染。所以,服務器將這些文件和 /index.html 一塊兒推送
for (const asset of ['/static/awesome.css', '/static/unicorn.png']) {
// stream 是 ServerHttp2Stream。
stream.pushStream({':path': asset}, (err, pushStream) => {
if (err) throw err;
pushStream.respondWithFile(asset);
});
}
複製代碼
在客戶端,一但瀏覽器解析 /index.html,它會指出須要 /static/awesome.css 和 /static/unicorn.png,可是瀏覽器得知他們已經被推進並存儲到了緩存中!全部他並不須要發送兩個額外的請求,而是使用已經推送的資源。
這聽起來蠻不錯。可是有一些挑戰(難點)。首先,服務器要想知道爲原始請求推送哪些附加資源並非那麼容易。雖然咱們能夠把這個決定權放到應用程序層,可是讓開發人員作出決定也一樣不簡單。一種方法是手動解析 HTML,找出其所須要的資源列表。可是隨着應用程序的迭代和 HTML 文件的更新,維護該列表的工做將很是繁瑣並且容易出錯。
另外一個挑戰來自瀏覽器內部緩存先前檢索到的資源。使用上面的例子,若是瀏覽器昨天加載了 /index.html,它也會加載 /static/unicorn.png,而且該文件一般會緩存在瀏覽器中。當瀏覽器加載 /index.html,而後嘗試加載 /static/unicorn.png 時,它知道後者已經被緩存,而且只會使用它而不是去再次請求。這種狀況下,若是服務器推送 /static/unicorn.png 就會浪費帶寬。因此服務器應該有一些方法來判斷資源是否已經緩存到了瀏覽器中。
還會有其餘類型的挑戰,以及針對 HTTP/2 推送文檔的經驗法則等這些。
爲了方便 Node.js 開發者支持服務端推送功能,Google 發佈了一個 npm 包來實現自動化:h2-auto-push。其設計目的是處理上面和 針對 HTTP/2 推送文檔的經驗法則 中提到的諸多挑戰。
它會監視來自瀏覽器的請求的模式,而且肯定與最初請求資源相關聯的附加資源。以後若是請求原始資源,相關的資源會自動推送到瀏覽器。它還將估計瀏覽器是否可能已經緩存了某個資源,若是肯定了就會跳過推送。
h2-auto-push 被設計爲供各類 web 框架使用的中間件。做爲一個靜態文件服務中間件,使用這個 npm 包開發一個自動推送中間件很是容易。好比說請參閱 fastify-auto-push。這是一個支持 HTTP/2 自動推送並使用 h2-auto-push 包的 fastify 插件。
在應用程序中使用這個中間件也很是容易
const fastify = require('fastify');
const fastifyAutoPush = require('fastify-auto-push');
const fs = require('fs');
const path = require('path');
const {promisify} = require('util');
const fsReadFile = promisify(fs.readFile);
const STATIC_DIR = path.join(__dirname, 'static');
const CERTS_DIR = path.join(__dirname, 'certs');
const PORT = 8080;
async function createServerOptions() {
const readCertFile = (filename) => {
return fsReadFile(path.join(CERTS_DIR, filename));
};
const [key, cert] = await Promise.all(
[readCertFile('server.key'), readCertFile('server.crt')]);
return {key, cert};
}
async function main() {
const {key, cert} = await createServerOptions();
// 瀏覽器只支持 https 使用 HTTP/2。
const app = fastify({https: {key, cert}, http2: true});
// 新建並註冊自動推送插件
// 它應該註冊在中間件鏈的一開始。
app.register(fastifyAutoPush.staticServe, {root: STATIC_DIR});
await app.listen(PORT);
console.log(`Listening on port ${PORT}`);
}
main().catch((err) => {
console.error(err);
});
複製代碼
很簡單,是吧?
咱們的測試代表,h2-auto-push 比 HTTP/2 的性能提升了 12%,比 HTTP/1 提升了大概 135%。咱們但願本文能讓您更好地理解 HTTP2 以及其能夠爲您應用帶來的好處,包括 HTTP2 推送。
特別感謝 nearForm 的 James Snell 和 David Mark Clements 以及 Google 的 Ali Sheikh 和 Kelvin Jin 能幫忙編輯這篇博文。很是感謝 Google 的 Matt Loring 在自動推送方面的最初的努力。
掘金翻譯計劃 是一個翻譯優質互聯網技術文章的社區,文章來源爲 掘金 上的英文分享文章。內容覆蓋 Android、iOS、前端、後端、區塊鏈、產品、設計、人工智能等領域,想要查看更多優質譯文請持續關注 掘金翻譯計劃、官方微博、知乎專欄。