チュートリアル: Azure Functions と Azure Web PubSub サービスを使用してサーバーレスのリアルタイム チャット アプリを作成する
Azure Web PubSub サービスは、WebSocket とパブリッシュ-サブスクライブ パターンを使用して、リアルタイム メッセージング Web アプリケーションを作成するのに役立ちます。 Azure Functions は、インフラストラクチャを管理することなくコードを実行できるサーバーレス プラットフォームです。 このチュートリアルでは、Azure Web PubSub サービスと Azure Functions を使用して、リアルタイム メッセージングとパブリッシュ-サブスクライブ パターンによるサーバーレス アプリケーションを作成する方法について説明します。
このチュートリアルでは、以下の内容を学習します。
- サーバーレスのリアルタイム チャット アプリを構築する
- Web PubSub 関数トリガーのバインドと出力バインドを使用する
- 関数を Azure Function App にデプロイする
- Azure 認証を構成する
- イベントとメッセージをアプリケーションにルーティングするように Web PubSub イベント ハンドラーを構成する
前提条件
コード エディター (Visual Studio Code など)。
Node.js、バージョン 18.x 以上。
Note
サポートされているバージョンの Node.js の詳細については、Azure Functions ランタイム バージョンのドキュメントを参照してください。
Azure Function アプリをローカルで実行して Azure にデプロイするための Azure Functions Core Tools (v4 以上を推奨)。
Azure リソースを管理するための Azure CLI。
Azure サブスクリプションをお持ちでない場合は、開始する前に Azure 無料アカウントを作成してください。
Azure へのサインイン
Azure アカウントで Azure Portal (https://portal.azure.com/) にサインインします。
Azure Web PubSub サービス インスタンスを作成する
アプリケーションは Azure 内の Web PubSub サービス インスタンスに接続します。
Azure portal の左上にある [新規] ボタンを選択します。 [新規] 画面で、検索ボックスに「Web PubSub」と入力して Enter キーを押します。 (Azure Web PubSub を
Web
カテゴリから検索することもできます)。検索結果の [Web PubSub] を選択し、 [作成] を選択します。
次の設定を入力します。
設定 提案された値 説明 リソース名 グローバルに一意の名前 新しい Web PubSub サービス インスタンスを識別するグローバルで一意の名前。 有効な文字は、 a-z
、A-Z
、0-9
、-
です。サブスクリプション 該当するサブスクリプション この新しい Web PubSub インスタンスが作成される Azure サブスクリプション。 リソース グループ myResourceGroup Web PubSub サービス インスタンスの作成先となる新しいリソース グループの名前。 場所 米国西部 近くのリージョンを選択します。 [価格レベル] 無料 まず Azure Web PubSub サービスを無料でお試しいただけます。 Azure Web PubSub サービスの価格レベルの詳細をご覧ください。 [ユニット数] - ユニット数は、Web PubSub サービス インスタンスで受け入れることができる接続の数を指定します。 各ユニットで最大 1,000 のコンカレント接続がサポートされます。 Standard レベルでのみ構成可能です。 [作成] を選択して Web PubSub サービス インスタンスのデプロイを開始します。
関数を作成する
Azure Functions Core Tools がインストールされていることを確認します。 次に、プロジェクトの空のディレクトリを作成します。 この作業ディレクトリの下でコマンドを実行します。
func init --worker-runtime javascript --model V4
Microsoft.Azure.WebJobs.Extensions.WebPubSub
をインストールする。host.json
の extensionBundle を確認し、バージョン 4.* 以降に更新して、Web PubSub サポートを取得します。{ "extensionBundle": { "id": "Microsoft.Azure.Functions.ExtensionBundle", "version": "[4.*, 5.0.0)" } }
クライアントの静的 Web ページを読み取ってホストする
index
関数を作成します。func new -n index -t HttpTrigger
src/functions/index.js
を更新して次のコードをコピーします。const { app } = require('@azure/functions'); const { readFile } = require('fs/promises'); app.http('index', { methods: ['GET', 'POST'], authLevel: 'anonymous', handler: async (context) => { const content = await readFile('index.html', 'utf8', (err, data) => { if (err) { context.err(err) return } }); return { status: 200, headers: { 'Content-Type': 'text/html' }, body: content, }; } });
クライアントがアクセス トークンを含むサービス接続 URL を取得するのに役立つ
negotiate
関数を作成します。func new -n negotiate -t HttpTrigger
Note
このサンプルでは、Microsoft Entra ID ユーザー ID ヘッダー
x-ms-client-principal-name
を使用して、userId
を取得します。 また、これはローカル関数では機能しません。 ローカルで実行する際には、これを空にするか他の方法に変えることでuserId
を取得または生成できます。 たとえば、negotiate
関数を呼び出してサービスの接続 URL を取得する際に、クライアントがユーザー名を入力し、それを?user={$username}
のようなクエリで渡すようにします。 また、negotiate
関数でuserId
を値{query.user}
に設定します。src/functions/negotiate
を更新して次のコードをコピーします。const { app, input } = require('@azure/functions'); const connection = input.generic({ type: 'webPubSubConnection', name: 'connection', userId: '{headers.x-ms-client-principal-name}', hub: 'simplechat' }); app.http('negotiate', { methods: ['GET', 'POST'], authLevel: 'anonymous', extraInputs: [connection], handler: async (request, context) => { return { body: JSON.stringify(context.extraInputs.get('connection')) }; }, });
サービスを使用してクライアント メッセージをブロードキャストするための
message
関数を作成します。func new -n message -t HttpTrigger
src/functions/message.js
を更新して次のコードをコピーします。const { app, output, trigger } = require('@azure/functions'); const wpsMsg = output.generic({ type: 'webPubSub', name: 'actions', hub: 'simplechat', }); const wpsTrigger = trigger.generic({ type: 'webPubSubTrigger', name: 'request', hub: 'simplechat', eventName: 'message', eventType: 'user' }); app.generic('message', { trigger: wpsTrigger, extraOutputs: [wpsMsg], handler: async (request, context) => { context.extraOutputs.set(wpsMsg, [{ "actionName": "sendToAll", "data": `[${context.triggerMetadata.connectionContext.userId}] ${request.data}`, "dataType": request.dataType }]); return { data: "[SYSTEM] ack.", dataType: "text", }; } });
プロジェクトのルート フォルダーにクライアントのシングル ページ
index.html
を追加し、コンテンツをコピーします。<html> <body> <h1>Azure Web PubSub Serverless Chat App</h1> <div id="login"></div> <p></p> <input id="message" placeholder="Type to chat..." /> <div id="messages"></div> <script> (async function () { let authenticated = window.location.href.includes( "?authenticated=true" ); if (!authenticated) { // auth let login = document.querySelector("#login"); let link = document.createElement("a"); link.href = `${window.location.origin}/.auth/login/aad?post_login_redirect_url=/api/index?authenticated=true`; link.text = "login"; login.appendChild(link); } else { // negotiate let messages = document.querySelector("#messages"); let res = await fetch(`${window.location.origin}/api/negotiate`, { credentials: "include", }); let url = await res.json(); // connect let ws = new WebSocket(url.url); ws.onopen = () => console.log("connected"); ws.onmessage = (event) => { let m = document.createElement("p"); m.innerText = event.data; messages.appendChild(m); }; let message = document.querySelector("#message"); message.addEventListener("keypress", (e) => { if (e.charCode !== 13) return; ws.send(message.value); message.value = ""; }); } })(); </script> </body> </html>
Azure 関数アプリを作成してデプロイする
関数コードを Azure にデプロイする前に、3 つのリソースを作成する必要があります。
- リソース グループ。関連リソースの論理コンテナーです。
- ストレージ アカウント。関数についての情報 (状態など) を維持する目的で使用されます。
- 関数アプリ。関数コードを実行するための環境となります。 関数アプリは、ローカルの関数プロジェクトと対応関係にあります。これを使用すると、リソースの管理、デプロイ、共有を容易にするための論理ユニットとして関数をグループ化できます。
以下のコマンドを使用してこれらの項目を作成します。
まだ Azure にサインインしていない場合は、Azure にサインインします。
az login
リソース グループを作成します。または、Azure Web PubSub サービスのいずれかを再利用してスキップできます。
az group create -n WebPubSubFunction -l <REGION>
リソース グループとリージョン内に汎用ストレージ アカウントを作成します。
az storage account create -n <STORAGE_NAME> -l <REGION> -g WebPubSubFunction
Azure に関数アプリを作成します。
az functionapp create --resource-group WebPubSubFunction --consumption-plan-location <REGION> --runtime node --runtime-version 18 --functions-version 4 --name <FUNCIONAPP_NAME> --storage-account <STORAGE_NAME>
Note
Azure Functions ランタイム バージョンに関するドキュメントを確認して、
--runtime-version
パラメーターをサポートされている値に設置します。Azure に関数プロジェクトをデプロイする:
Azure への関数アプリの作成に成功したら、func azure functionapp publish コマンドを使用して、ローカル関数プロジェクトをデプロイすることができます。
func azure functionapp publish <FUNCIONAPP_NAME>
関数アプリ用に
WebPubSubConnectionString
を構成します。最初に、Azure portal から Web PubSub リソースを見つけ、 [キー] から接続文字列をコピーします。 次に、Azure portal で [設定]、[構成]、[関数アプリの設定] の順に移動します。 [アプリケーション設定] の下に新しい項目を追加します。名前は
WebPubSubConnectionString
にし、値は Web PubSub リソースの接続文字列とします。
Web PubSub サービス Event Handler
を構成する
このサンプルでは、WebPubSubTrigger
を使用して、サービスのアップストリーム要求をリッスンしています。 そのため、Web PubSub では、ターゲット クライアント要求を送信するために、関数のエンドポイント情報を知る必要があります。 また、Azure Function App には、拡張機能固有の Webhook メソッドに関するセキュリティのためのシステム キーが必要です。 前の手順で message
関数を使用して Function App をデプロイした後、システム キーを取得できます。
Azure portal に移動し、自分の Function App リソースを見つけ、[アプリ キー]、[システム キー]、webpubsub_extension
の順に移動します。 <APP_KEY>
として、値をコピーします。
Azure Web PubSub サービスに Event Handler
を設定します。 Azure portal に移動して、自分の Web PubSub リソースを見つけ、[設定] に移動します。 新しいハブ設定と使用中の 1 つの関数とのマッピングを追加します。 <FUNCTIONAPP_NAME>
と <APP_KEY>
を自分のものに置き換えます。
- ハブ名:
simplechat
- URL テンプレート: https://<FUNCTIONAPP_NAME>.azurewebsites.net/runtime/webhooks/webpubsub?code=<APP_KEY>
- ユーザー イベント パターン: *
- システム イベント: (このサンプルでは構成する必要はありません)
クライアント認証を有効に構成する
Azure portal に移動して、自分の Function App リソースを見つけ、[認証] に移動します。 [Add identity provider
] をクリックします。 App Service 認証の設定を [認証されていないアクセスを許可する] に設定すると、認証にリダイレクトされる前に、匿名のユーザーがクライアントのインデックス ページにアクセスできます。 その後、 [保存] を選択します。
ここでは、ID プロバイダーとして Microsoft
を選択します。これにより、negotiate
関数で userId
として x-ms-client-principal-name
が使用されます。 なお、リンクから他の ID プロバイダーを構成することもできます。それに応じて negotiate
関数の userId
値を忘れずに更新してください。
アプリケーションを試す
これで、関数アプリからページをテストできるようになりました (https://<FUNCTIONAPP_NAME>.azurewebsites.net/api/index
)。 スナップショットを参照してください。
login
をクリックして自分を認証します。- 入力ボックスにメッセージを入力してチャットします。
メッセージ関数では、呼び出し元のメッセージをすべてのクライアントにブロードキャストし、呼び出し元にメッセージ [SYSTEM] ack
を返します。 そのため、サンプルのチャット スナップショットでは、最初の 4 つのメッセージは現在のクライアントからのものであり、最後の 2 つのメッセージは別のクライアントからのものであることがわかります。
リソースをクリーンアップする
このアプリの使用を続けない場合は、次の手順に従って、このドキュメントで作成したすべてのリソースを削除して、課金が発生しないようにします。
Azure Portal の左端で [リソース グループ] を選択し、作成したリソース グループを選択します。 検索ボックスを使用して名前でリソース グループを検索することもできます。
表示されたウィンドウでリソース グループを選択し、 [リソース グループの削除] を選択します。
新しいウィンドウで、削除するリソース グループの名前を入力し、 [削除] を選択します。
次のステップ
このクイックスタートでは、サーバーレス チャット アプリケーションを実行する方法について説明しました。 これで、独自のアプリケーションの作成を始められます。