共用方式為


教學課程:在無伺服器模式中使用 Azure 函式建置聊天應用程式 (預覽)

本教學課程將逐步引導您如何在無伺服器模式中建立適用於 Socket.IO 服務的Web PubSub,並建置與 Azure Function 整合的聊天應用程式。

尋找本教學課程中使用的完整程式碼範例:

重要

默認模式需要持續性伺服器,您無法將 Web PubSub 與 Azure Function 整合為預設模式中的 Socket.IO。

重要

原始 連接字串 只會出現在本文中,僅供示範之用。

連接字串包含應用程式存取 Azure Web PubSub 服務所需的授權資訊。 連接字串內的存取金鑰類似於服務的根密碼。 在生產環境中,請一律保護您的存取金鑰。 使用 Azure 金鑰保存庫,安全地管理和輪替密鑰,並使用保護連線WebPubSubServiceClient

避免將存取金鑰散發給其他使用者、寫入程式碼,或將其以純文字儲存在他人可以存取的位置。 如果您認為金鑰可能已遭盜用,請輪替金鑰。

必要條件

在無伺服器模式中為 Socket.IO 資源建立 Web PubSub

若要建立 Socket.IO 的 Web PubSub,您可以使用下列 Azure CLI 命令:

az webpubsub create -g <resource-group> -n <resource-name>--kind socketio --service-mode serverless --sku Premium_P1

在本機建立 Azure 函式專案

您應該遵循步驟來起始本機 Azure 函式專案。

  1. 請遵循步驟來安裝最新的 Azure Function 核心工具

  2. 在終端機視窗中或從命令提示字元執行下列命令,以在 SocketIOProject 資料夾中建立專案:

    func init SocketIOProject --worker-runtime javascript --model V4
    

    此命令會建立 JavaScript 專案。 然後輸入資料夾 SocketIOProject 以執行下列命令。

  3. 函式套件組合目前不包含 Socket.IO 函式系結,因此您必須手動新增套件。

    1. 若要排除函式套件組合參考,請編輯host.json檔案,並移除下列幾行。

      "extensionBundle": {
          "id": "Microsoft.Azure.Functions.ExtensionBundle",
          "version": "[4.*, 5.0.0)"
      }
      
    2. 執行命令:

      func extensions install -p Microsoft.Azure.WebJobs.Extensions.WebPubSubForSocketIO -v 1.0.0-beta.4
      
  4. 建立交涉的函式。 交涉函式,用於產生端點和令牌,供用戶端存取服務。

    func new --template "Http Trigger" --name negotiate
    

    在 中 src/functions/negotiate.js 開啟 檔案,並以下列程式代碼取代 :

    const { app, input } = require('@azure/functions');
    
    const socketIONegotiate = input.generic({
        type: 'socketionegotiation',
        direction: 'in',
        name: 'result',
        hub: 'hub'
    });
    
    async function negotiate(request, context) {
        let result = context.extraInputs.get(socketIONegotiate);
        return { jsonBody: result };
    };
    
    // Negotiation
    app.http('negotiate', {
        methods: ['GET', 'POST'],
        authLevel: 'anonymous',
        extraInputs: [socketIONegotiate],
        handler: negotiate
    });
    

    此步驟會建立一個具有 HTTP 觸發器和negotiate輸出繫結的函式SocketIONegotiation,這表示您可以使用 HTTP 呼叫來觸發該函式,並傳回由SocketIONegotiation繫結產生的協商結果。

  5. 建立函式來傳送訊息。

    func new --template "Http Trigger" --name message
    

    開啟檔案 src/functions/message.js ,並以下列程式代碼取代 :

    const { app, output, trigger } = require('@azure/functions');
    
    const socketio = output.generic({
    type: 'socketio',
    hub: 'hub',
    })
    
    async function chat(request, context) {
        context.extraOutputs.set(socketio, {
        actionName: 'sendToNamespace',
        namespace: '/',
        eventName: 'new message',
        parameters: [
            context.triggerMetadata.socketId,
            context.triggerMetadata.message
        ],
        });
    }
    
    // Trigger for new message
    app.generic('chat', {
        trigger: trigger.generic({
            type: 'socketiotrigger',
            hub: 'hub',
            eventName: 'chat',
            parameterNames: ['message'],
        }),
        extraOutputs: [socketio],
        handler: chat
    });
    

    這會使用 SocketIOTrigger Socket.IO 用戶端訊息觸發,並使用 SocketIO 系結將訊息廣播至命名空間。

  6. 建立函式以傳回要流覽的索引 HTML。

    1. 在下public建立資料夾src/

    2. 使用下列內容建立 HTML 檔案 index.html

      <html>
      
      <body>
      <h1>Socket.IO Serverless Sample</h1>
      <div id="chatPage" class="chat-container">
          <div class="chat-input">
              <input type="text" id="chatInput" placeholder="Type your message here...">
              <button onclick="sendMessage()">Send</button>
          </div>
          <div id="chatMessages" class="chat-messages"></div>
      </div>
      <script src="https://cdn.socket.io/4.7.5/socket.io.min.js"></script>
      <script>
          function appendMessage(message) {
          const chatMessages = document.getElementById('chatMessages');
          const messageElement = document.createElement('div');
          messageElement.innerText = message;
          chatMessages.appendChild(messageElement);
          hatMessages.scrollTop = chatMessages.scrollHeight;
          }
      
          function sendMessage() {
          const message = document.getElementById('chatInput').value;
          if (message) {
              document.getElementById('chatInput').value = '';
              socket.emit('chat', message);
          }
          }
      
          async function initializeSocket() {
          const negotiateResponse = await fetch(`/api/negotiate`);
          if (!negotiateResponse.ok) {
              console.log("Failed to negotiate, status code =", negotiateResponse.status);
              return;
          }
          const negotiateJson = await negotiateResponse.json();
          socket = io(negotiateJson.endpoint, {
              path: negotiateJson.path,
              query: { access_token: negotiateJson.token }
          });
      
          socket.on('new message', (socketId, message) => {
              appendMessage(`${socketId.substring(0,5)}: ${message}`);
          })
          }
      
          initializeSocket();
      </script>
      </body>
      
      </html>
      
    3. 若要傳回 HTML 頁面,請建立函式並複製程式代碼:

      func new --template "Http Trigger" --name index
      
    4. 開啟檔案 src/functions/index.js ,並以下列程式代碼取代 :

      const { app } = require('@azure/functions');
      
      const fs = require('fs').promises;
      const path = require('path')
      
      async function index(request, context) {
          try {
              context.log(`HTTP function processed request for url "${request.url}"`);
      
              const filePath = path.join(__dirname,'../public/index.html');
              const html = await fs.readFile(filePath);
              return {
                  body: html,
                  headers: {
                      'Content-Type': 'text/html'
                  }
              };
          } catch (error) {
              context.log(error);
              return {
                  status: 500,
                  jsonBody: error
              }
          }
      };
      
      app.http('index', {
          methods: ['GET', 'POST'],
          authLevel: 'anonymous',
          handler: index
      });
      
      

如何在本機執行應用程式

備妥程式代碼之後,請遵循指示來執行範例。

設定 Azure 函式的 Azure 儲存體

Azure Functions 需要記憶體帳戶,即使在本機執行時也能運作。 選擇下列兩個選項之一:

  • 執行免費的 Azurite 模擬器
  • 使用 Azure 儲存體服務。 如果您繼續使用它,可能會產生成本。
  1. 安裝 Azurite
npm install -g azurite
  1. 啟動 Azurite 儲存體模擬器:
azurite -l azurite -d azurite\debug.log
  1. 確定 AzureWebJobsStorage 中的 local.settings.json 設定為 UseDevelopmentStorage=true

設定適用於 Socket.IO 的 Web PubSub 組態

  1. 將 連接字串 新增至函式應用程式:
func settings add WebPubSubForSocketIOConnectionString "<connection string>"
  1. 將中樞設定新增至 web PubSub for Socket.IO
az webpubsub hub create -n <resource name> -g <resource group> --hub-name hub --event-handler url-template="tunnel:///runtime/webhooks/socketio" user-event-pattern="*"

Azure CLI 命令可以取得 連接字串

az webpubsub key show -g <resource group> -n <resource name>

輸出包含 primaryConnectionStringsecondaryConnectionString,而且兩者都可供使用。

設定通道

在無伺服器模式中,服務會使用 Webhook 來觸發函式。 當您嘗試在本機執行應用程式時,關鍵問題是讓服務能夠存取您的本機函式端點。

達成其最簡單的方式是使用 通道工具

  1. 安裝通道工具:

    npm install -g @azure/web-pubsub-tunnel-tool
    
  2. 執行通道

    awps-tunnel run --hub hub --connection "<connection string>" --upstream http://127.0.0.1:7071
    

    --upstream是本機 Azure 函式公開的 URL。 埠可能不同,您可以在下一個步驟中啟動函式時檢查輸出。

執行範例應用程式

通道工具執行之後,您可以在本機執行函式應用程式:

func start

並在瀏覽網頁 http://localhost:7071/api/index

無伺服器聊天應用程式的螢幕快照。

下一步

接下來,您可以嘗試使用 Bicep,透過身分識別型驗證在在線部署應用程式: