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

如何使用 Node.js 和 Express 設置 Websocket 服務器

如何將 websocket 服務器附加到現有的 Express 服務器以向您的應用添加實時數據。

開始使用

對於本教程,我們將使用 CheatCode Node.js 樣板。這將使我們能夠訪問現有的 Express 服務器,我們可以將我們的 websocket 服務器附加到:

終端

git clone https://github.com/cheatcode/nodejs-server-boilerplate.git

克隆項目後,cd 進入其中並安裝其依賴項:

終端

cd nodejs-server-boilerplate && npm install

最後,對於本教程,我們需要安裝兩個額外的依賴項:ws 用於創建我們的 websocket 服務器和 query-string 從我們的 websocket 連接解析查詢參數:

終端

npm i ws query-string

之後,啟動開發服務器:

終端

npm run dev

創建 websocket 服務器

首先,我們需要設置一個新的 websocket 服務器來處理來自客戶端的入站 websocket 請求。一、在/index.js 我們剛剛克隆的項目的文件,讓我們添加對將設置我們的 websocket 服務器的函數的調用:

/index.js

import express from "express";
import startup from "./lib/startup";
import api from "./api/index";
import middleware from "./middleware/index";
import logger from "./lib/logger";
import websockets from './websockets';

startup()
  .then(() => {
    const app = express();
    const port = process.env.PORT || 5001;

    middleware(app);
    api(app);

    const server = app.listen(port, () => {
      if (process.send) {
        process.send(`Server running at http://localhost:${port}\n\n`);
      }
    });

    websockets(server);

    process.on("message", (message) => {
      console.log(message);
    });
  })
  .catch((error) => {
    logger.error(error);
  });

在這裡,我們導入了一個假設的 websockets ./websockets 中的函數 這是預期的 index.js 該路徑中的文件(Node.js 將其解釋為 ./websockets/index.js )。 .then() 內部 我們服務器的回調 startup() 函數,我們在對 app.listen() 的調用下方添加了對該函數的調用 .給它,我們傳遞 server 即在傳遞的port上打開HTTP服務器時Express返回的HTTP服務器 (在這種情況下 5001 )。

一次 server 可用,我們調用我們的 websockets() 函數,傳入 HTTP server (這就是我們將在下一節中創建的 websocket 服務器附加到的內容)。

將 websocket 服務器連接到 express 服務器

接下來,我們需要創建 /websockets/index.js 我們假設將在上面存在的文件。為了保持我們的代碼乾淨,我們將創建一個單獨的 websockets 在我們克隆的項目根目錄下創建一個index.js 裡面的文件:

/websockets/index.js

import WebSocket from "ws";

export default (expressServer) => {
  const websocketServer = new WebSocket.Server({
    noServer: true,
    path: "/websockets",
  });

  return websocketServer;
};

在這裡,我們導出一個函數,該函數接受 expressServer 的單個參數 其中包含 Express app 當我們從 /index.js 調用函數時我們打算傳入的實例 在項目的根目錄。

在該函數內部,我們使用 Websocket.Server 創建我們的 websocket 服務器 ws 的構造函數 我們在上面安裝的包。我們將 noServer 傳遞給該構造函數 選項為 true 說“不要在這個 websocket 服務器旁邊設置一個 HTTP 服務器。”這樣做的好處是我們可以跨多個 websocket 連接共享一個 HTTP 服務器(即我們的 Express 服務器)。我們還傳遞了一個 path 用於指定我們的 websocket 服務器可以訪問的 HTTP 服務器上的路徑的選項(最終,localhost:5001/websockets )。

/websockets/index.js

import WebSocket from "ws";

export default async (expressServer) => {
  const websocketServer = new WebSocket.Server({
    noServer: true,
    path: "/websockets",
  });

  expressServer.on("upgrade", (request, socket, head) => {
    websocketServer.handleUpgrade(request, socket, head, (websocket) => {
      websocketServer.emit("connection", websocket, request);
    });
  });

  return websocketServer;
};

擴展我們的代碼,接下來,我們需要處理websocket服務器對現有expressServer的附件 .要做到這一點,在 expressServer 我們監聽 upgrade 事件。每當我們的 Express 服務器(一個普通的 HTTP 服務器)接收到使用 websockets 協議的端點請求時,都會觸發此事件。這裡的“升級”是說,“我們需要升級這個請求來處理 websocket。”

傳遞給事件處理程序的回調 - .on('upgrade') 部分——我們有三個參數 request , socket , 和 head . request 表示從 websocket 客戶端發出的入站 HTTP 請求,socket 表示瀏覽器(客戶端)和服務器之間的網絡連接,head 表示入站請求的第一個數據包/數據塊。

接下來,在事件處理程序的回調中,我們調用 websocketServer.handleUpgrade() ,與 request 一起傳遞 , socket , 和 head .我們的意思是“我們被要求將此 HTTP 請求升級為 websocket 請求,因此請執行升級,然後將升級後的連接返回給我們。”

然後,升級後的連接被傳遞給我們作為 websocketServer.handleUpgrade() 的第四個參數添加的回調 .使用升級後的連接,我們需要處理連接——明確地說,這是現在連接的 websocket 客戶端連接。為此,我們“移交”升級後的連接 websocket 和原來的 request 通過在 websocketServer 上發出事件 名稱為 connection .

處理入站 websocket 連接

此時,我們已經升級了現有的 Express HTTP 服務器,但是,我們還沒有完全處理入站請求。在上一節中,我們已經能夠將來自 websocket 客戶端的入站 HTTP 請求升級為真正的 websocket 連接,但是,我們還沒有處理 這種聯繫。

/websockets/index.js

import WebSocket from "ws";
import queryString from "query-string";

export default async (expressServer) => {
  const websocketServer = new WebSocket.Server({[...]});

  expressServer.on("upgrade", (request, socket, head) => {[...]});

  websocketServer.on(
    "connection",
    function connection(websocketConnection, connectionRequest) {
      const [_path, params] = connectionRequest?.url?.split("?");
      const connectionParams = queryString.parse(params);

      // NOTE: connectParams are not used here but good to understand how to get
      // to them if you need to pass data with the connection to identify it (e.g., a userId).
      console.log(connectionParams);

      websocketConnection.on("message", (message) => {
        const parsedMessage = JSON.parse(message);
        console.log(parsedMessage);
      });
    }
  );

  return websocketServer;
};

為了處理這個連接,我們需要監聽 connection 我們在上一節中發出的事件。為此,我們調用 websocketServer.on('connection') 向它傳遞一個回調函數,該函數將處理入站 websocket 連接和伴隨的請求。

為了澄清,websocketConnection 之間的區別 和 connectionRequest 是前者代表瀏覽器和服務器之間開放的、長時間運行的網絡連接,而 connectionRequest 表示對打開的原始請求 這種聯繫。

關注我們傳遞給 .on('connection') 的回調 處理程序,我們做一些特別的事情。根據 websocket 的實現,無法在 websocket 請求的主體中傳遞數據(例如,用戶 ID 或其他一些識別信息)(類似於通過 HTTP POST 請求傳遞主體的方式)。

相反,當通過 websocket 客戶端連接到服務器時,我們需要在 websocket 服務器的 URL 的查詢參數中包含任何識別信息(下一節將詳細介紹)。不幸的是,這些查詢參數不是 由我們的 websocket 服務器解析,因此我們需要手動執行此操作。

要將查詢參數提取到 JavaScript 對像中,請從 connectionRequest ,我們獲取請求的 URL(這是 websocket 客戶端向其發出連接請求的 URL)並將其拆分為 ? .我們這樣做是因為我們不關心 URL 在 ? 之前和之前的任何部分 ,或者,我們的 URL 形式的查詢參數。

使用 JavaScript 數組解構,我們獲取 .split('?') 的結果 並假設它返回一個包含兩個值的數組:URL 的路徑部分和 URL 形式的查詢參數。在這裡,我們將路徑標記為 _path 建議我們不使用該值(前綴 _ 變量名的下劃線是跨編程語言表示這一點的常用方法)。然後,我們“拔掉”params 從 URL 中分離出來的值。為了清楚起見,假設請求中的 URL 看起來像 ws://localhost:5001/websockets?test=123&test2=456 我們希望數組中有這樣的東西:

['ws://localhost:5001/websockets', 'test=123&test2=456']

由於它們存在,params (在上面的例子中 test=123&test2=456 ) 在我們的代碼中不可用。為了使它們可用,我們引入 queryString.parse() query-string 中的方法 我們之前安裝的軟件包。此方法採用 URL 格式的查詢字符串並將其轉換為 JavaScript 對象。考慮到上面的示例 URL 的最終結果是:

{ test: '123', test2: '456' }

有了這個,現在我們可以通過 connectionParams 在我們的代碼中引用我們的查詢參數 多變的。我們對這裡的內容不做任何事情,但包含此信息是因為坦率地說,弄清楚這部分內容令人沮喪。

/websockets/index.js

import WebSocket from "ws";
import queryString from "query-string";

export default async (expressServer) => {
  const websocketServer = new WebSocket.Server({
    noServer: true,
    path: "/websockets",
  });

  expressServer.on("upgrade", (request, socket, head) => {
    websocketServer.handleUpgrade(request, socket, head, (websocket) => {
      websocketServer.emit("connection", websocket, request);
    });
  });

  websocketServer.on(
    "connection",
    function connection(websocketConnection, connectionRequest) {
      const [_path, params] = connectionRequest?.url?.split("?");
      const connectionParams = queryString.parse(params);

      // NOTE: connectParams are not used here but good to understand how to get
      // to them if you need to pass data with the connection to identify it (e.g., a userId).
      console.log(connectionParams);

      websocketConnection.on("message", (message) => {
        const parsedMessage = JSON.parse(message);
        console.log(parsedMessage);
        websocketConnection.send(JSON.stringify({ message: 'There be gold in them thar hills.' }));
      });
    }
  );

  return websocketServer;
};

上面,我們已經完成了 websocket 服務器的實現。我們添加的是 websocketConnection 時的事件處理程序 接收入站消息(websockets 的想法是在瀏覽器和服務器之間保持一個長時間運行的連接打開,通過該連接可以來回發送消息)。

在這裡,當消息事件進來時,在傳遞給事件處理程序的回調中,我們接受一個 message 屬性作為字符串。在這裡,我們假設我們的 message 是一個字符串化的 JavaScript 對象,所以我們使用 JSON.parse() 將該字符串轉換為我們可以在代碼中與之交互的 JavaScript 對象。

最後,為了展示對來自服務器的消息的響應,我們調用 websocketConnection.send() ,傳回一個字符串化的對象(我們假設客戶端也期待一個字符串化的 JavaScript 對像在其入站消息中傳遞)。

測試 websocket 服務器

因為我們沒有在本教程中展示如何在前端設置 websocket 客戶端,所以我們將使用一個名為 Smart Websocket Client 的 Chrome/Brave 瀏覽器擴展,它為我們提供了一個可以使用的偽前端測試一下。

在頂部,我們在終端中運行了正在運行的 HTTP/websocket 服務器(這是我們在項目開始時克隆的項目的開發服務器),在底部,我們在瀏覽器中打開了 Smart Websocket Client 擴展(勇敢)。

首先,我們輸入我們希望我們的 websocket 服務器存在的 URL。請注意,不是通常的 http:// 我們在連接到服務器時給 URL 加上前綴,因為我們想打開一個 websocket 連接,我們在 URL 前面加上 ws:// (類似地,在生產環境中,如果我們啟用了 SSL,我們希望使用 wss:// 對於“websockets 安全”)。

因為我們希望我們的服務器在端口 5001 上運行 (我們正在構建的項目的默認端口以及我們的 HTTP 服務器接受請求的位置),我們使用 localhost:5001 ,後跟 /websockets?userId=123 說“在此服務器上,導航到 /websockets 我們的 websocket 服務器連接的路徑並包含查詢參數 userId 設置為值 123 。”

當我們單擊擴展程序中的“連接”按鈕時,我們會獲得到我們的 websocket 服務器的開放連接。接下來,為了測試它,在“發送”按鈕下方的文本區域中,我們輸入一個預先編寫的字符串化對象(通過運行 JSON.stringify({ howdy: "tester" }) 創建 在瀏覽器控制台中),然後單擊“發送”按鈕將該字符串化對象發送到服務器。

如果我們觀察頂部的服務器終端,我們可以看到 userId 當我們連接和發送消息時,從 URL 解析查詢參數,我們看到該消息在服務器上註銷並獲得預期的 { message: "There be gold in them thar hills." } 在客戶端返回消息。

總結

在本教程中,我們學習瞭如何設置 websocket 服務器並將其附加到現有的 Express HTTP 服務器。我們學習瞭如何初始化 websocket 服務器,然後使用 upgrade 支持 websockets 協議的入站連接請求事件。

最後,我們研究瞭如何向連接的客戶端發送和接收消息以及如何使用 JSON.stringify()JSON.parse() 通過 websockets 發送對象。


Tutorial JavaScript 教程
  1. Svelte 中驚人的 macOS Dock 動畫

  2. 使用 React Leaflet 構建地圖 - Egghead.io 上的課程

  3. 如何在不改變原始數組的情況下對數組進行排序?

  4. 帶有 Framer Motion 的動畫模態

  5. 我可以將變量設置為未定義或將未定義作為參數傳遞嗎?

  6. 如何在 Laravel 中使用 Tailwind CSS

  7. Project 88 of 100 - Lorem Ipsum 生成器

  1. Package.json 文件解釋!!!

  2. 如何更新 dynamoDB 表中的項目

  3. 在javascript中獲取鼠標光標的大小

  4. 計劃開發一個App來計劃開發👨‍💻

  5. 使用 Cocycles 按功能查找 JavaScript 代碼片段

  6. 轉譯器與編譯器⚙

  7. 在 React 中使用 textarea 提交表單

  1. 如何使用 Vue 動態更改背景顏色?

  2. 為什麼我無法使用 Jquery 插件中的 FormData 從 tinymce 編輯器中獲取 textarea 的值?

  3. currencylayer:簡單、有效的貨幣轉換

  4. 如何使用 Vanilla HTML、CSS 和 JS 構建和驗證漂亮的表單