JavaScript >> Javascript 文檔 >  >> Node.js

通過構建 Express 中間件學習 HTTP/2 服務器推送

在上一篇文章中,我們學習瞭如何在 Node 服務器中執行 HTTP/2 服務器推送。我們還介紹了服務器推送的好處,因此為避免重複,我們不會在此處列出它們。我們使用 spdy 用於服務器推送和 H2。但大多數時候 Node 開發人員不使用核心 HTTP 服務器,他們使用 Express 之類的框架。那麼讓我們看看如何在 Express 中實現服務器推送。

舉例說明 HTTP/2 服務器推送 使用 Express,我們將實現一個 Express 中間件,它將任何圖像或腳本推送到瀏覽器。可以說,中間件將使用依賴項的哈希映射。例如,index.html 將有 bundle.js , node-university-animation.gif 圖片和另外一個腳本 bundle2.js .

您甚至可以使用此中間件來提供圖像。正則表達式無需修改即可工作,因為 <script><img> 標籤使用 src 屬性。這就是推送(Node.University 動畫的)圖像的樣子:

如您所見,圖像中也沒有綠條(等待 TTFB)。

注意:此中間件不適用於生產用途。其目的是說明 HTTP/2 協議和 Node+Express 中的可能性。

項目結構

項目代碼在GitHub,項目結構是典型的Express服務器,有一個靜態文件夾:

/node_modules
/public
  - bundle.js
  - bundle2.js
  - index.html
  - node-university-animation.gif
- index-advanced.js
- package.json
- server.crt
- server.csr
- server.key

出於明顯的原因,我沒有提交 SSL 密鑰(你也不應該在你的項目中!),所以請生成你自己的。沒有 SSL/HTTPS,HTTP/2 將無法工作。您可以在 Optimize Your App with HTTP/2 Server Push Using Node and Express 中獲得說明帶有 Node.js 和 Express.js 的簡易 HTTP/2 服務器 .

安裝依賴項

首先,在 package.json 中聲明依賴項 使用這些 npm 部門:

{
  "name": "http2-node-server-push",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "./node_modules/.bin/node-dev .",
    "start-advanced": "./node_modules/.bin/node-dev index-advanced.js"
  },
  "keywords": [
    "node.js",
    "http2"
  ],
  "author": "Azat Mardan",
  "license": "MIT",
  "dependencies": {
    "express": "^4.14.0",
    "morgan": "^1.7.0",
    "spdy": "^3.4.0"
  },
  "devDependencies": {
    "node-dev": "^3.1.3"
  }
}

隨意複製 package.json 並運行 npm i .

HTML 文件

index.html 擁有三個資產:

<html>
<body>
  <script src="bundle.js"/></script>

  <h1>hello to http2 push server!</h1>
  <div></div>

  <img src="node-university-animation.gif"/>
  <div></div>
  <div></div>
  <div></div>
  <div></div>
  <div></div>
  <div></div>
  <div></div>
  <div></div>
  <div></div>
  <div></div>
  <div></div>
  <div></div>
</body>
  <script src="bundle2.js"/></script>
</html>

bundle.js 非常小:

console.log('bundle1')

另一方面,bundle2.js 相當大(它有 React 核心)。

定義 Express 服務器

讓我們看一下index-advanced.js中的實現 .一開始,我們定義了 Express 本身和其他一些模塊等依賴項。 pushOps 以後會用到這個對象

[旁注]

閱讀博客文章很好,但觀看視頻課程更好,因為它們更具吸引力。

許多開發人員抱怨 Node.js 上缺乏負擔得起的高質量視頻材料。觀看 YouTube 視頻會讓人分心,花 500 美元購買 Node 視頻課程很瘋狂!

去看看 Node University,它有關於 Node 的免費視頻課程:node.university。

[旁注結束]

var express = require('express')
var app = express()
const fs = require('fs')
const path = require('path')
const url = require('url')

現在,讓我們使用該算法讀取並映射所有文件中包含的所有腳本和圖像。它只會在你啟動服務器時運行一次,因此它不會佔用請求期間的時間。可以使用 readFileSync 因為我們還沒有運行服務器。

let files = {}
fs.readdir('public', (error, data)=>{
  data.forEach(name=>{
    files[`${name}`]=fs
      .readFileSync(path.join(__dirname, 'public', `${name}`), {encoding: 'utf8'})
      .split('\n')
      .filter(line=>line.match(/src *?= *?"(.*)"/)!=null)
      .map(line=>line.match(/src *?= *?"(.*)"/)[1])
  })
})

filter裡面的函數 和 map 將使用正則表達式來生成這個對象:

{ 'bundle.js': [],
  'bundle2.js': [],
  'index.html': [ 'bundle.js', 'node-university-animation.gif', 'bundle2.js' ],
  'node-university-animation.gif': [] }

通過使用 index.html 作為該對象的鍵,我們將能夠快速訪問其依賴項的數組。一個空數組意味著沒有我們可以服務器推送的依賴。

接下來,定義 logger 中間件來跟踪服務器端的請求:

const logger = require('morgan')
app.use(logger('dev'))

實現服務器推送中間件

所以我們得到了包含推送什麼信息的對象。要實際推送資產,請創建一個像這樣的中間件,我們在其中剝離 / 並默認為 index.html 當 URL 中沒有路徑時(例如 https://localhost:8080/ urlName 將變為 index.html ):

app.use((request, response, next)=>{
  let urlName = url.parse(request.url).pathname.substr(1)
  if (urlName === '' || urlName === '/') urlName = 'index.html'
  console.log('Request for: ', urlName)

當然,讓我們檢查一下我們的 public 中是否有這個文件 通過將名稱匹配為 files 的鍵的文件夾 目的。如果為真,則繼續創建 assets 存儲服務器推送的代碼。每個 assets 數組項將是腳本或圖像之類的資產。

  if (files[urlName]) {
    let assets = files[urlName]
      .filter(name=>(name.substr(0,4)!='http'))
      .map((fileToPush)=>{
        let fileToPushPath = path.join(__dirname, 'public', fileToPush)
        return (cb)=>{
          fs.readFile(fileToPushPath, (error, data)=>{
            if (error) return cb(error)
            console.log('Will push: ', fileToPush, fileToPushPath)
            try {
              response.push(`/${fileToPush}`, {}).end(data)
              cb()
            } catch(e) {
              cb(e)
            }
          })
        }
      })

實際推送發生在 response.push( /${fileToPush}, {}).end(data) .您可以通過傳遞內容類型而不是空對​​象 {} 來改進此調用 .此外,可以使用流而不是緩衝區 data readFile .

接下來,讓我們添加 index.html 本身(或任何文件名):

    // Uncomment to disable server push
    // assets = []
    console.log('Total number of assets to push: ', assets.length)
    assets.unshift((cb)=>{
      fs.readFile(path.join(__dirname, 'public', urlName), (error, data)=>{
        if (error) return cb(error)
        response.write(data)
        cb()
      })
    })

現在,我們可以一次性發送所有資產和 HMTL:

    require('neo-async').parallel(assets, (results)=>{
      response.end()
    })
  } else {
    return next()
  }
})

啟動 HTTP/2 服務器

最後,使用密鑰、證書和 spdy 啟動 H2 服務器 :

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

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

當您使用 npm run start-advanced 啟動服務器時 ,然後你會看到這個提示:

Server is listening on https://localhost:8080.
You can open the URL in the browser.

請記住使用 https 而不是 http。雖然按照 HTTP/2 標準,可以使用未加密的 http 協議,但出於明顯的安全原因,大多數瀏覽器決定只支持 https。

向主頁發出請求時,服務器會發送index.html .從日誌中可以看出,使用服務器推送時只有一個請求。

Request for:  index.html
Total number of assets to push:  13
Will push:  bundle.js /Users/azat/Documents/Code/http2-node-server-push/public/bundle.js
Will push:  node-university-animation.gif /Users/azat/Documents/Code/http2-node-server-push/public/node-university-animation.gif
Will push:  bundle2.js /Users/azat/Documents/Code/http2-node-server-push/public/bundle2.js

我們完成了我們的服務器和中間件。啟動服務器並在 https://localhost:8080/ 查看結果。它們可能會有所不同……

總結

服務器推送的實際好處取決於許多因素,例如緩存、資產順序、大小和呈現 HTML 的複雜性。我的 index.html 並沒有太大的提升 ,但“等待 TTFB”在 H2 推送中消失了。

您可以取消註釋 assets = [] 這基本上是刪除資產推送代碼。有趣的是,我比使用 HTTP/2 服務器推送更快地獲得了資產的啟動時間(DevTools 中的網絡選項卡):

在沒有推送的情況下,開始順序將始終與 HTML 中的相同 ,即 bundle.js , node-university-animation.gifbundle2.js .

服務器推送非常強大,但應該有意識地使用它來避免與緩存發生任何衝突,例如發送已經在緩存中的資產。服務器推送結果取決於許多因素。您可以將此中間件用於教育目的。如果您喜歡這篇文章,請考慮查看 Node.University。


Tutorial JavaScript 教程
  1. 有人可以解釋一下 jjencode 是如何工作的,以及是否可以在我的代碼中使用它

  2. API 架構

  3. 你最喜歡的 Vim 配色方案是什麼?

  4. 在線舉辦 Ionic 聚會

  5. 從全棧開發人員轉變為 Web3 先鋒

  6. 使用 Faast.js 分析無服務器函數的成本

  7. 空間導航

  1. 如何通過單擊其 div 來選擇單選按鈕?

  2. 我如何承諾原生 XHR?

  3. 🪄 更新 React Native 版本的經驗教訓

  4. 總是使總數為 100 的隨機數進入數組

  5. 發送表單時更改按鈕/圖標

  6. 我轟炸了我的第一次技術面試,感覺很自由。

  7. 如何使用 jQuery 或僅使用 Javascript 將按鈕重定向到另一個頁面

  1. 使用 Node.js 在 Amazon S3 存儲桶中上傳和刪除圖像的自定義服務

  2. 如何在 React 和 Tailwind 中構建多圖像輪播

  3. 使用 Webhook 在 20 分鐘內集成自動部署。

  4. 使用 Vue.js 讀取客戶端文件以進行驗證