このチュートリアルでは、サーバーレス モードで Web PubSub for Socket.IO サービスを作成し、Azure Functions と統合するチャット アプリを構築する方法について説明します。
このチュートリアルで使用されている完全なコード サンプルについては、以下を参照してください。
重要
デフォルト モードには永続的なサーバーが必要です。デフォルト モードの Web PubSub for Socket.IO を Azure Functions と統合することはできません。
重要
この記事に示す生の接続文字列は、デモンストレーションのみを目的としています。
接続文字列には、アプリケーションが Azure Web PubSub サービスにアクセスするために必要な認可情報が含まれています。 接続文字列内のアクセス キーは、サービスのルート パスワードに似ています。 運用環境では、常にアクセス キーを保護してください。 Azure Key Vault を使ってキーの管理とローテーションを安全に行い、WebPubSubServiceClient を使って接続をセキュリティ保護します。
アクセス キーを他のユーザーに配布したり、ハードコーディングしたり、他のユーザーがアクセスできるプレーンテキストで保存したりしないでください。 キーが侵害された可能性があると思われる場合は、キーをローテーションしてください。
前提条件
- アクティブなサブスクリプションが含まれる Azure アカウント。 アカウントがない場合は、無料アカウントを作成することができます。
- Azure Functions Core Tools
- Socket.IO ライブラリに関するある程度の知識。
サーバーレス モードの Web PubSub for Socket.IO リソースを作成する
Web PubSub for Socket.IO を作成するには、次の Azure CLI コマンドを使用できます。
az webpubsub create -g <resource-group> -n <resource-name>--kind socketio --service-mode serverless --sku Premium_P1
Azure Functions プロジェクトをローカルに作成する
ローカルの Azure Functions プロジェクトを開始するには、この手順に従う必要があります。
最新の Azure Functions Core Tools をインストールする手順に従う
ターミナル ウィンドウまたはコマンド プロンプトで次のコマンドを実行して、
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 negotiatesrc/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 });これは、Socket.IO クライアント メッセージによってトリガーされるように
SocketIOTriggerを使用し、SocketIOバインドを使用して名前空間にメッセージをブロードキャストします。アクセス用のインデックス HTML を返す関数を作成します。
publicの下にフォルダーsrc/を作成します。次の内容を含む 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 Storage を設定する
Azure Functions が機能するには、ローカルで実行されている場合でも、ストレージ アカウントが必要になります。 次の 2 つのオプションのいずれかを選択します。
- 無料の Azurite エミュレーターを実行します。
- Azure Storage サービスを使用します。 使い続けるとコストが発生する可能性があります。
- Azurite をインストールします
npm install -g azurite
- Azurite ストレージ エミュレーターを起動します。
azurite -l azurite -d azurite\debug.log
-
AzureWebJobsStorageの がUseDevelopmentStorage=trueに設定されていることを確認します。
Web PubSub for Socket.IO の構成を設定する
- Function APP に接続文字列を追加します。
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 を使用して関数をトリガーします。 アプリをローカルで実行しようとするときの重要な問題は、サービスがローカルの関数エンドポイントにアクセスできるようにすることです。
それを達成する最も簡単な方法は、トンネル ツールを使用することです
トンネル ツールのインストール:
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 Functions で公開される URL です。 ポートが異なる場合があり、次の手順で関数を開始するときに出力を確認できます。
サンプル アプリを実行する
トンネル ツールが実行されたら、Function App をローカルで実行できます。
func start
次に http://localhost:7071/api/index の Web ページにアクセスします。
次の手順
次に、Bicep を使用して、ID ベースの認証でオンラインでのアプリのデプロイを試してみましょう。