[譯] 經過 Node.js, Express.js 實現 HTTP/2 Server Push

原文:Optimize Your App with HTTP/2 Server Push Using Node and Express
做者:Azat Mardan
代碼:http2-node-server-pushjavascript


什麼是 HTTP/2 Server Push

HTTP/2 是 Web 開發的新標準,擁有不少不錯的優勢可以讓 Web 訪問更快且開發的工做更輕鬆簡單。好比,引入多路複用傳輸不用合併資源,服務器推送(Server Push)資源讓瀏覽器預加載。html

該文不會講述 HTTP/2 的全部優點。你能夠經過上篇文章瞭解更多{% post_link http2-node-express %}。該文主要關注於在 Node.js 環境使用 Express.jsHTTP/2spdyjava

服務器推送(Server Push)工做方式是經過在一個 HTTP/2 請求中捆綁多個資源。在底層,服務器會發送一個 PUSH_PROMISE,客戶端(包括瀏覽器)就能夠利用它且不基於 HTML 文件是否須要該資源。若是瀏覽器檢測到須要該資源,就會匹配到收到的服務器推送的 PROMISE 而後讓該資源表現的就像正常的瀏覽器 Get 請求資源。顯而易見,若是匹配到有推送,瀏覽器就不須要從新請求,而後直接使用客戶端緩存。這推薦幾篇文章關於服務器推送(Server Push)的好處:node

這是個關於在 Node.js 實現服務器推送(Server Push)實踐教程。爲了更清晰精簡,咱們只實現一個路由地址 /pushyNode.jsExpress.js 服務器,它會推送一個 JS 文件,正如以前所說,咱們會用到一個 HTTP/2spdyweb

HTTP/2 和 Node.js

先解釋一下,爲啥在 Node.js 環境選擇 HTTP/2spdy。當前來講,爲 Node.js 主要有兩個庫實現了 HTTP/2 :chrome

兩個庫都跟 Node.js 核心模塊的 httphttps 模塊 api 很類似。這就意味着若是你不使用 Express ,這兩個庫就沒什麼區別。然而, spdy 庫支持 HTTP/2Express,而 http2 庫當前不支持 Express。這就是爲何咱們選擇使用 spdyExpressNode.js 適合搭配的實踐標準的服務框架。之因此叫 spdy是來自於 Google 的 SPDY 協議後來升級成 HTTP/2

HTTPS密鑰和證書

要在瀏覽器(Firefox, Safari, Chrome, 或者 Edge)中訪問使用 HTTPS ,你須要生成密鑰和證書。去搜索 「ssl 密鑰生成」 或者按照如下步驟去生成密鑰、證書。在該文提供的源碼中沒有上傳生成的密鑰和證書

$ mkdir http2-node-server-push 
$ cd http2-node-server-push
$ openssl genrsa -des3 -passout pass:x -out server.pass.key 2048
...
$ openssl rsa -passin pass:x -in server.pass.key -out server.key
writing RSA key
$ rm server.pass.key
$ openssl req -new -key server.key -out server.csr
...
Country Name (2 letter code) [AU]:US
State or Province Name (full name) [Some-State]:California
...
A challenge password []:
...
$ openssl x509 -req -sha256 -days 365 -in server.csr -signkey server.key -out server.crt

按照以上步驟,你就會產生三個 SSL 文件:

  • server.crt

  • server.csr

  • server.key

你就能夠在 Node.js 的 server 腳本中讀取 server.key 和 server.crt。

搭建項目

首先,經過 package.json 初始化項目和下載項目依賴:

npm init -y
npm i express@4.14.0 morgan@1.7.0 spdy@3.4.0 --save
npm i node-dev@3.1.1 --save-dev

當前的目錄結構以下

/http2-node-server-push
/node_modules
index.js
package.json
server.crt
server.csr
server.key

而後,在 package.jsonscripts 中添加兩個腳本行,去簡化命令(node-dev、自動重載):

"start": "./node_modules/.bin/node-dev .",
"start-advanced": "./node_modules/.bin/node-dev index-advanced.js"

如今就能夠開始使用 Node.js 、 Express.js 、 spdy 編寫這個簡單實現的帶服務器推送 HTTP/2 服務器

編寫腳本

首先,建立 index.js 腳本,並引入以及實例化依賴,看看查看上面的項目目錄結構。其中,我使用了 ES6/ES2015 的語法 const 來聲明依賴,若是你不熟悉該聲明語法,你能夠進一步閱讀Top 10 ES6 Features Every Busy JavaScript Developer Must Know

const http2 = require('spdy')
const logger = require('morgan')
const express = require('express')
const app = express()
const fs = require('fs')

而後,設置 morgan logger 來監聽服務器服務了哪些請求。

app.use(logger('dev'))

設置主頁,該頁面顯示了 /pushy 是咱們服務器推送的頁面。

app.get('/', function (req, res) {
  res.send(`hello, http2! go to /pushy`)
})

服務器推送只需簡單的調用 spdy 實現的 res.push ,咱們將文件路徑名傳輸進去做爲第一個參數,瀏覽器會使用這個路徑名來匹配 push promise 資源。res.push() 的第一個參數 /main.js 必定得跟 HTML 文件中須要的文件名相匹配。

而第二個參數是一個可選的對象,設置了該資源的一些資源信息描述。

app.get('/pushy', (req, res) => {
  var stream = res.push('/main.js', {
    status: 200, // optional
    method: 'GET', // optional
    request: {
      accept: '*/*'
    },
    response: {
      'content-type': 'application/javascript'
    }
  })
  stream.on('error', function() {
  })
  stream.end('alert("hello from push stream!");')
  res.end('<script src="/main.js"></script>')
})

你能夠看到,stream 對象有兩個方法 onend。前者監聽了 errorfinish 事件,然後者則監聽完成傳輸 end,而後就會 main.js 就會觸發彈窗。

或者,若是你擁有多個數據塊,你能夠選擇使用 res.write() 而後最後使用 res.end(),其中 res.end() 會自動關閉結束 responseres.write() 則讓它保持開啓。(該文的源碼中未實現這種狀況)

最後,讀取 HTTPS 密鑰和證書並使用 spdy 啓動運轉服務器。

var options = {
  key: fs.readFileSync('./server.key'),
  cert: fs.readFileSync('./server.crt')
}

http2
  .createServer(options, app)
  .listen(8080, ()=>{
    console.log(`Server is listening on https://localhost:8080.
    You can open the URL in the browser.`)
  }
)

該實現的關鍵就在於,圍繞着 streams(流)。不是樹林中的河流,而是指開發者使用的從源頭到客戶端的創建起的數據通道流。若是你幾乎不懂流以及 Node.jsExpress.js 的 HTTP 的請求和返回信息,你能夠看看該文章 You Don’t Know Node

啓動和對比 HTTP/2 Server Push

使用命令 node index.js 或者 npm stat 運行服務端腳本,而後訪問 https://localhost:3000/pushy,就能夠看到彈窗!並且咱們在該路由不存在文件,你能夠查看服務器終端的 logs ,只會有一個請求,而不是沒使用服務器推送的時候的兩個請求(一個 HTML、一個 JS)。

能夠在瀏覽器中檢測收到服務器端推送的行爲。Chrome 啓動開發者工具,打開 Network 標籤,你能夠看到 main.js 不存在綠色時間條,就是說明沒有等待時間 TTFB (Time to First Byte)詳細

服務器推送的效果

再仔細看,能夠看到請求是由 Push 開始發起的(Initiator列查看),沒有使用服務器推送的 HTTP/2 服務器或者 HTTP/1,這一列就會顯示文件名稱,如 index.html 發起的顯示就是 index.html

實踐就結束了,使用了 Express 和 Spdy 簡單就實現了推送 JS 資源,而該資源能夠用於後面 HTML 中 <script> 標籤引入的。固然你也能夠在腳本中使用 fs 來讀取文件資源。事實上,這就是做者實現的 Express HTTP/2 靜態資源中間件 設計原理,能夠看看這篇文章

總結

HTTP/2 擁有不少很好的特性,服務器推送是最被看好的特性之一。它的好處就在於當瀏覽器請求頁面的時候,同時發送必需的資源文件(圖片,CSS 樣式,JS 文件),而不須要等待客戶端瀏覽器請求這些資源,從而作到更快的第一次渲染時間

HTTP/2spdy 讓開發者在基於 Express 的應用能更容易的實現服務器推送特性。

能夠下載參考本文的源碼,而後爲你本身的服務器編寫服務器推送你的資源。

PS:
個人博客

相關文章
相關標籤/搜索