Azure SignalR Service を使用した Azure Functions の開発と構成

Azure Functions アプリケーションは、Azure SignalR Service バインドを使って、リアルタイム機能を追加できます。 クライアント アプリケーションは、複数の言語で使用可能なクライアント SDK を使用して、Azure SignalR Service に接続し、リアルタイム メッセージを受信します。

この記事では、SignalR Service と統合されている Azure 関数アプリを開発および構成するための概念について説明します。

SignalR Service の構成

Azure SignalR Service はさまざまなモードで構成できます。 Azure Functions と使用する場合、サーバーレス モードでサービスを構成する必要があります。

Azure portal で、SignalR Service リソースの [設定] ページを見つけます。 [サービス モード][サーバーレス] に設定します。

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 があり、これにより userIdclaims などのネゴシエーション オプションをカスタマイズすることができます。

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);
}

その後、厳密に型指定されたメソッドを次のように使用できます。

[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 に接続するには、クライアントで、以下の手順から構成される正常な接続ネゴシエーションを完了する必要があります。

  1. 有効な接続情報を取得するために上記で説明した negotiate HTTP エンドポイントに要求を行います
  2. 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 を持つエントリを追加します。

CORS の構成

クラウド - 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、Twitter、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 概要ページのクイック スタートまたはチュートリアルのいずれかを使用して自身でアプリケーションを作成してみてください。