Azure SignalR Service を使用した Azure Functions の開発と構成
Azure Functions アプリケーションは、Azure SignalR Service バインドを使って、リアルタイム機能を追加できます。 クライアント アプリケーションは、複数の言語で使用可能なクライアント SDK を使用して、Azure SignalR Service に接続し、リアルタイム メッセージを受信します。
この記事では、SignalR Service と統合されている Azure 関数アプリを開発および構成するための概念について説明します。
重要
この記事に示す生の接続文字列は、デモンストレーションのみを目的としています。
接続文字列には、アプリケーションが Azure Web PubSub サービスにアクセスするために必要な認可情報が含まれています。 接続文字列内のアクセス キーは、サービスのルート パスワードに似ています。 運用環境では、常にアクセス キーを保護してください。 Azure Key Vault を使用してキーの管理とローテーションを安全に行い、Microsoft Entra ID を使用して接続文字列をセキュリティで保護し、Microsoft Entra ID を使用してアクセスを認可します。
アクセス キーを他のユーザーに配布したり、ハードコーディングしたり、他のユーザーがアクセスできるプレーンテキストで保存したりしないでください。 キーが侵害された可能性があると思われる場合は、キーをローテーションしてください。
SignalR Service の構成
Azure SignalR Service はさまざまなモードで構成できます。 Azure Functions と使用する場合、サーバーレス モードでサービスを構成する必要があります。
Azure portal で、SignalR Service リソースの [設定] ページを見つけます。 [サービス モード] を [サーバーレス] に設定します。
Azure Functions の開発
Azure Functions と Azure SignalR Service で構築されるサーバーレスのリアルタイム アプリケーションには、少なくとも 2 つの Azure Functions が必要です。
- 有効な SignalR Service アクセス トークンとエンドポイント URL を取得するためにクライアントが呼び出す
negotiate
関数。 - SignalR Service からクライアントに送信されるメッセージを処理する 1 つ以上の関数。
ネゴシエーション関数
クライアント アプリケーションでは、Azure SignalR Service に接続するために、有効なアクセス トークンが必要になります。 アクセス トークンは、匿名でも、ユーザー ID に認証済みのものでもかまいません。 サーバーレスの SignalR Service アプリケーションでは、トークンと、SignalR Service エンドポイント URL などの他の接続情報を取得するために、negotiate
という名前の HTTP エンドポイントが必要です。
接続情報オブジェクトを生成するには、HTTP によってトリガーされる Azure 関数と SignalRConnectionInfo
入力バインドを使います。 関数には、/negotiate
で終わる HTTP ルートが必要です。
C# のクラス ベース モデルでは、SignalRConnectionInfo
入力バインドは必要なく、カスタム クレームをより簡単に追加できます。 詳しくは、「クラス ベース モデルでのネゴシエーション エクスペリエンス」を参照してください。
negotiate
関数について詳しくは、「Azure Functions の開発」をご覧ください。
認証済みトークンの作成方法については、「App Service 認証の使用」をご覧ください。
SignalR Service から送信されたメッセージを処理する
SignalR Service から送信されたメッセージを処理するには、SignalRTrigger
バインドを使用します。 クライアントがメッセージを送信したり、クライアントが接続または切断されたりすると、通知を受け取ることがあります。
詳しくは、SignalR Service トリガー バインド リファレンスに関する記事をご覧ください。
また、クライアントからのメッセージがあった場合にサービスが関数をトリガーできるように、関数エンドポイントをアップストリーム エンドポイントとして構成する必要もあります。 アップストリーム エンドポイントを構成する方法の詳細については、アップストリーム エンドポイントに関する記事を参照してください。
Note
SignalR Service では、サーバーレス モードでのクライアントからの StreamInvocation
メッセージはサポートされません。
メッセージの送信とグループ メンバーシップの管理
SignalR
出力バインドを使用して、Azure SignalR Service に接続したクライアントにメッセージを送信します。 すべてのクライアントにメッセージをブロードキャストすることも、クライアントのサブセットに送信することもできます。 たとえば、特定のユーザー ID で認証されたクライアントのみ、または特定のグループのみに、メッセージを送信します。
ユーザーは、1 つ以上のグループに追加できます。 SignalR
出力バインドを使用して、グループに対してユーザーを追加または削除することもできます。
詳細については、SignalR
出力バインド リファレンスに関するページを参照してください。
SignalR Hubs
SignalR には "ハブ" の概念があります。 各クライアント接続と Azure Functions から送信される各メッセージは、特定のハブに範囲指定されます。 接続およびメッセージを論理名前空間に分割する方法としてハブを使用できます。
クラス ベース モデル
クラス ベース モデルは C# 専用です。
クラスベースのモデルは、より優れたプログラミング エクスペリエンスを提供し、これにより SignalR の入力と出力のバインディングを次の機能に置き換えることができます。
- より柔軟なネゴシエーション、メッセージの送信、グループエクスペリエンスの管理。
- 接続の終了や、接続やユーザーまたはグループが存在することの確認など、さらに多くの管理機能もサポートされています。
- 厳密に型付けされた XML
- 統合ハブ名と接続文字列の設定は、1 つの場所で行ないます。
次のコードで、クラス ベースのモデルでの SignalR バインディングの記述方法を示します。
まず、クラス ServerlessHub
から派生したハブを定義します。
[SignalRConnection("AzureSignalRConnectionString")]
public class Functions : ServerlessHub
{
private const string HubName = nameof(Functions); // Used by SignalR trigger only
public Functions(IServiceProvider serviceProvider) : base(serviceProvider)
{
}
[Function("negotiate")]
public async Task<HttpResponseData> Negotiate([HttpTrigger(AuthorizationLevel.Anonymous, "post")] HttpRequestData req)
{
var negotiateResponse = await NegotiateAsync(new() { UserId = req.Headers.GetValues("userId").FirstOrDefault() });
var response = req.CreateResponse();
response.WriteBytes(negotiateResponse.ToArray());
return response;
}
[Function("Broadcast")]
public Task Broadcast(
[SignalRTrigger(HubName, "messages", "broadcast", "message")] SignalRInvocationContext invocationContext, string message)
{
return Clients.All.SendAsync("newMessage", new NewMessage(invocationContext, message));
}
[Function("JoinGroup")]
public Task JoinGroup([SignalRTrigger(HubName, "messages", "JoinGroup", "connectionId", "groupName")] SignalRInvocationContext invocationContext, string connectionId, string groupName)
{
return Groups.AddToGroupAsync(connectionId, groupName);
}
}
Program.cs ファイルの中で、サーバーレス ハブを登録します。
var host = new HostBuilder()
.ConfigureFunctionsWorkerDefaults(b => b.Services
.AddServerlessHub<Functions>())
.Build();
クラス ベース モデルでのネゴシエーション エクスペリエンス
SignalR の入力バインド [SignalRConnectionInfoInput]
を使う代わりに、クラス ベース モデルのネゴシエーションを使うといっそう柔軟になります。 基底クラス ServerlessHub
にはメソッド NegotiateAsync
があり、これにより userId
、claims
などのネゴシエーション オプションをカスタマイズすることができます。
Task<BinaryData> NegotiateAsync(NegotiationOptions? options = null)
クラス ベースのモデルにおけるメッセージの送信とエクスペリエンスの管理
基底クラス ServerlessHub
によって提供されるメンバーにアクセスして、メッセージの送信、グループの管理、クライアントの管理を行うことができます。
- クライアントへのメッセージ送信用の
ServerlessHub.Clients
。 - グループへの接続の追加、グループからの接続の削除など、グループとの接続を管理するための
ServerlessHub.Groups
。 - グループへのユーザーの追加、グループからのユーザーの削除など、グループとユーザーを管理するための
ServerlessHub.UserGroups
。 - 接続の存在の確認、接続の終了、その他を行なうための
ServerlessHub.ClientManager
。
厳密に型指定されたハブ
厳密に型指定されたハブにより、クライアントにメッセージを送信するときに、厳密に型指定されたメソッドを使用できるようになります。 クラス ベース モデルで厳密に型指定されたハブを使用するには、クライアント メソッドをインターフェイス T
に抽出し、ハブ クラスを ServerlessHub<T>
から派生させます。
次のコードは、クライアント メソッドでのインターフェイスのサンプルです。
public interface IChatClient
{
Task newMessage(NewMessage message);
}
その後、厳密に型指定されたメソッドを次のように使用できます。
この記事に示す生の接続文字列は、デモンストレーションのみを目的としています。 運用環境では、常にアクセス キーを保護してください。 Azure Key Vault を使用してキーの管理とローテーションを安全に行い、Microsoft Entra ID を使用して接続文字列をセキュリティで保護し、Microsoft Entra ID を使用してアクセスを認可します。
[SignalRConnection("AzureSignalRConnectionString")]
public class Functions : ServerlessHub<IChatClient>
{
private const string HubName = nameof(Functions); // Used by SignalR trigger only
public Functions(IServiceProvider serviceProvider) : base(serviceProvider)
{
}
[Function("Broadcast")]
public Task Broadcast(
[SignalRTrigger(HubName, "messages", "broadcast", "message")] SignalRInvocationContext invocationContext, string message)
{
return Clients.All.newMessage(new NewMessage(invocationContext, message));
}
}
Note
完全なプロジェクト サンプルは、GitHub から入手できます。
1 つの場所で統合ハブ名と接続文字列を設定
- サーバーレス ハブのクラス名は、自動的に
HubName
として使用されます。 - サーバーレス ハブ クラスで、
SignalRConnection
属性が次のように使用されていることも意識してください。
これにより、サーバーレス ハブの接続文字列を置く場所をカスタマイズできます。 これが見つからない場合は、既定値の[SignalRConnection("AzureSignalRConnectionString")] public class Functions : ServerlessHub<IChatClient>
AzureSignalRConnectionString
が使用されます。
重要
SignalR トリガーとサーバーレス ハブは、それぞれ独立しています。 つまり、サーバーレス ハブ内で SignalR トリガーを使用している場合でも、サーバーレス ハブと SignalRConnection
属性のクラス名により SignalR トリガーの設定が変更されることはありません。
クライアント開発
SignalR クライアント アプリケーションでは、複数の言語のいずれかで SignalR クライアント SDK を使って、簡単に Azure SignalR Service に接続してメッセージを受信できます。
クライアント接続の構成
SignalR Service に接続するには、クライアントで、以下の手順から構成される正常な接続ネゴシエーションを完了する必要があります。
- 有効な接続情報を取得するために上記で説明した
negotiate
HTTP エンドポイントに要求を行います negotiate
エンドポイントから取得したサービス エンドポイント URL およびアクセス トークンを使用して、SignalR Service に接続します
SignalR クライアント SDK には、ネゴシエーション ハンドシェイクを実行するために必要なロジックが既に含まれます。 negotiate
セグメントを除いた、ネゴシエーション エンドポイントの URL を SDK の HubConnectionBuilder
に渡します。 次に JavaScript の例を示します。
const connection = new signalR.HubConnectionBuilder()
.withUrl("https://my-signalr-function-app.azurewebsites.net/api")
.build();
慣例により、SDK が /negotiate
を URL に自動的に付加し、ネゴシエーションを開始するために使用します。
Note
ブラウザーで JavaScript/TypeScript SDK を使用している場合、Function App 上でクロス オリジン リソース共有 (CORS) を有効にする必要があります。
SignalR クライアント SDK の使い方について詳しくは、言語に応じたドキュメントをご覧ください。
クライアントからサービスへのメッセージの送信
SignalR リソース用に構成されたアップストリームがある場合は、任意の SignalR クライアントを使って、クライアントから Azure Functions にメッセージを送信できます。 次に JavaScript の例を示します。
connection.send("method1", "arg1", "arg2");
Azure Functions の構成
Azure SignalR Service と統合する Azure 関数アプリは、継続的なデプロイ、zip デプロイ、パッケージから実行などのテクニックを使用して、一般的な Azure 関数アプリのようにデプロイできます。
ただし、SignalR Service のバインドを使用するアプリに対する、いくつかの特別な考慮事項があります。 クライアントがブラウザーで実行する場合、CORS が有効になっている必要があります。 また、アプリが認証を必要とする場合は、ネゴシエーション エンドポイントを App Service の認証と統合できます。
CORS の有効化
JavaScript と TypeScript のクライアントは、ネゴシエーション関数に HTTP 要求を行って、接続のネゴシエーションを開始します。 クライアント アプリケーションが Azure 関数アプリと異なるドメイン上でホストされている場合、関数アプリではクロス オリジン リソース共有 (CORS) を有効にする必要があり、そうしない場合ブラウザーは要求をブロックします。
Localhost
関数アプリをローカル コンピューター上で実行する場合、Host
セクションを local.settings.json に追加して CORS を有効にできます。 Host
セクションで、次の 2 つのプロパティを追加します。
CORS
- 元のクライアントアプリケーションであるベース URL を入力しますCORSCredentials
-true
に設定して、「withCredentials」要求を許可します
例:
{
"IsEncrypted": false,
"Values": {
// values
},
"Host": {
"CORS": "http://localhost:8080",
"CORSCredentials": true
}
}
クラウド - Azure Functions CORS
Azure 関数アプリ上で CORS を有効にするには、Azure portal の関数アプリの [プラットフォーム機能] タブの下にある CORS 構成画面に移動します。
Note
CORS の構成は、Azure Functions Linux 従量課金プランではまだ使用できません。 Azure API Management を使用して、CORS を有効にします。
SignalR クライアントが ネゴシエーション関数を呼び出すためには、Access-Control-Allow-Credentials を持つ CORS を有効にする必要があります。 これを有効にするには、チェックボックスをオンにします。
[許可されたオリジン] セクションで、Web アプリケーションの元のベース URL を持つエントリを追加します。
クラウド - Azure API Management
Azure API Management は、既存のバックエンド サービスに機能を追加する API ゲートウェイを提供します。 これを使用して、CORS を関数アプリに追加することができます。 アクションごとの支払い価格と月あたりの無料提供を含む従量課金レベルを提供します。
Azure 関数アプリをインポートする方法については、API Management のドキュメントを参照してください。 インポートした後、インバウンド ポリシーを追加して、Access-Control-Allow-Credentials をサポートする CORS を有効にすることができます。
<cors allow-credentials="true">
<allowed-origins>
<origin>https://azure-samples.github.io</origin>
</allowed-origins>
<allowed-methods>
<method>GET</method>
<method>POST</method>
</allowed-methods>
<allowed-headers>
<header>*</header>
</allowed-headers>
<expose-headers>
<header>*</header>
</expose-headers>
</cors>
API Management URL を使用するように SignalR クライアントを構成します。
App Service 認証の使用
Azure Functions には組み込み認証が存在し、Facebook、X、Microsoft アカウント、Google、Microsoft Entra ID などの一般的なプロバイダーをサポートしています。 この機能を SignalRConnectionInfo
バインディングと統合すると、ユーザー ID に対し認証された Azure SignalR Service への接続を作成できます。 アプリケーションは、そのユーザー ID を対象とする SignalR
出力バインドを使用してメッセージを送信できます。
Azure portal の関数アプリの [プラットフォーム機能] タブで、[認証/承認] 設定ウィンドウを開きます。 [App Service 認証] のドキュメントに従って、選択した ID プロバイダーを使用して認証を構成します。
構成が完了すると、認証された HTTP 要求には、認証された ID のユーザー名とユーザー ID をそれぞれ含んだ x-ms-client-principal-name
および x-ms-client-principal-id
ヘッダーが収められています。
SignalRConnectionInfo
バインド構成でこれらのヘッダーを使用して、認証された接続を作成できます。 次に、x-ms-client-principal-id
ヘッダーを使用する、C# でのネゴシエーション関数の例を示します。
[FunctionName("negotiate")]
public static SignalRConnectionInfo Negotiate(
[HttpTrigger(AuthorizationLevel.Anonymous)]HttpRequest req,
[SignalRConnectionInfo
(HubName = "chat", UserId = "{headers.x-ms-client-principal-id}")]
SignalRConnectionInfo connectionInfo)
{
// connectionInfo contains an access key token with a name identifier claim set to the authenticated user
return connectionInfo;
}
この場合、SignalR メッセージの UserId
プロパティを設定することにより、そのユーザーにメッセージを送信できます。
[FunctionName("SendMessage")]
public static Task SendMessage(
[HttpTrigger(AuthorizationLevel.Anonymous, "post")]object message,
[SignalR(HubName = "chat")]IAsyncCollector<SignalRMessage> signalRMessages)
{
return signalRMessages.AddAsync(
new SignalRMessage
{
// the message will only be sent to these user IDs
UserId = "userId1",
Target = "newMessage",
Arguments = new [] { message }
});
}
その他の言語の詳細については、Azure Functions のリファレンスの Azure SignalR Service のバインドに関するページを参照してください。
次のステップ
この記事では、Azure Functions を使用して、サーバーレスの SignalR Service アプリケーションを開発および構成する方法について説明します。 SignalR Service 概要ページのクイック スタートまたはチュートリアルのいずれかを使用して自身でアプリケーションを作成してみてください。