本教學課程將逐步引導您如何在無伺服器模式中建立適用於 Socket.IO 服務的Web PubSub,並建置與 Azure Function 整合的聊天應用程式。
尋找本教學課程中使用的完整程式碼範例:
重要
預設模式需要持續執行的伺服器,您無法在預設模式下將 Web PubSub for Socket.IO 與 Azure Functions 整合。
重要
原始 連接字串 只會出現在本文中,僅供示範之用。
連接字串 包含你的應用程式存取 Azure Web PubSub 服務所需的授權資訊。 連接字串 中的存取鍵就像是你服務的根密碼。 在生產環境中,請一律保護您的存取金鑰。 使用 Azure 金鑰保存庫,安全地管理和輪替密鑰,並使用保護連線WebPubSubServiceClient。
避免將存取金鑰散發給其他使用者、寫入程式碼,或將其以純文字儲存在他人可以存取的位置。 如果您認為金鑰可能已遭盜用,請輪替金鑰。
必要條件
- 具有有效訂用帳戶的 Azure 帳戶。 如果您沒有帳戶,您可以建立免費帳戶。
- Azure 函式核心工具
- 對 Socket.IO 程式庫有一定的了解。
在無伺服器模式中為 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 Functions 專案。
請遵循步驟來安裝最新的 Azure Function 核心工具
在終端機視窗中或從命令提示字元執行下列命令,以在
SocketIOProject資料夾中建立專案:func init SocketIOProject --worker-runtime javascript --model V4此命令會建立 JavaScript 專案。 然後輸入資料夾
SocketIOProject以執行下列命令。函式套件組合目前不包含 Socket.IO 函式系結,因此您必須手動新增套件。
若要排除函式套件組合參考,請編輯host.json檔案,並移除下列幾行。
"extensionBundle": { "id": "Microsoft.Azure.Functions.ExtensionBundle", "version": "[4.*, 5.0.0)" }執行命令:
func extensions install -p Microsoft.Azure.WebJobs.Extensions.WebPubSubForSocketIO -v 1.0.0-beta.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繫結產生的協商結果。建立函式來傳送訊息。
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 });這會使用
SocketIOTriggerSocket.IO 用戶端訊息觸發,並使用SocketIO系結將訊息廣播至命名空間。建立一個函式,用來回傳供瀏覽的 index.html。
在
src/下建立資料夾public。使用下列內容建立 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>若要傳回 HTML 頁面,請建立函式並複製程式代碼:
func new --template "Http Trigger" --name index開啟檔案
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 Functions 設定 Azure 儲存體
Azure Functions 即使在本機執行時,也需要儲存體帳戶才能運作。 選擇下列兩個選項之一:
- 執行免費的 Azurite 模擬器。
- 使用 Azure 儲存體服務。 如果您繼續使用它,可能會產生成本。
- 安裝 Azurite
npm install -g azurite
- 啟動 Azurite 儲存體模擬器:
azurite -l azurite -d azurite\debug.log
- 請確認 local.settings.json 中的
AzureWebJobsStorage已設為UseDevelopmentStorage=true。
設定適用於 Socket.IO 的 Web PubSub 組態
- 將 連接字串 新增至函式應用程式:
func settings add WebPubSubForSocketIOConnectionString "<connection string>"
- 為 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>
輸出包含 primaryConnectionString 和 secondaryConnectionString,而且兩者都可供使用。
設定通道
在無伺服器模式中,服務會使用 Webhook 來觸發函式。 當您嘗試在本機執行應用程式時,關鍵問題是讓服務能夠存取您的本機函式端點。
達成其最簡單的方式是使用 通道工具
安裝 Tunnel Tool:
npm install -g @azure/web-pubsub-tunnel-tool啟動通道
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,透過身分識別型驗證將應用程式部署到線上: