はじめに

今回から2回にかけて、Azure SignalR Serviceの紹介とリアルタイムアプリケーションの作成方法について説明していきます。

Azure SignalR Serviceの概要

まずはリアルタイムウェブを実現する機能としてのSignalRの説明およびPaaSとしてのAzure SignalR Serviceの説明をしていきます。

SignalRとは

SignalRとはリアルタイムウェブ機能をASP.NETに追加するためのASP.NETのライブラリのひとつで、正式には「ASP.NET SignalR」という名称でMicrosoftより提供されています。SignalRを使用することで、WebアプリケーションはWebブラウザやスマホアプリなどのクライアントに対してコンテンツをプッシュすることができるようになります。プッシュの仕組みを使用することでアプリケーションで発生したデータの更新をリアルタイムにクライアントに伝えることが可能となります。またSignalRでは、プッシュ機能に使う通信手段をクライアントのブラウザがサポートするものに自動的に選択してくれるため、開発者が通信手段を選ぶ必要がなく柔軟なアプリケーションを実装することができるようになっています。現在SignalRがサポートしている通信手段には以下のものが含まれます。

  • WebSocket
  • Server Sent Events(Internet Explorerを除くブラウザ)
  • Forever Frame(Internet Explorerのみ)
  • Ajaxロングポーリング

これらの利点から、ASP.NETでリアルタイムウェブアプリケーションを実装する際にはSignalRを採用するケースが多くありました。

Azure SignalR Serviceとは

Azure SignalR Serviceは前項で説明した「ASP.NET SignalR」の機能をASP.NETから切り離し、Azure上で稼働するPaaSとして独立させたサービスです。リアルタイムウェブの機能はそのままに、以下のようなメリットがさらに加わりました。

  • サーバ側の実装としてASP.NET以外の選択肢を持たせることができる
  • PaaSとして切り離されたことで、Webアプリケーションサーバが担っていたクライアントとSignalR間の永続的な接続の保持からサーバが開放され、サーバのリソース消費量を抑えることができる
  • クライアント接続の増減に対して、Azure SignalR Serviceがオートスケーリング機能で対応することができる

これまでASP.NETに限定されていたSignalRのユースケースが、Azure SignalR Serviceを使うことでその制限なく利用できるため、今後はAzure SignalR Serviceを使ったリアルタイムウェブアプリケーションの事例が増えるのではないかと思います。

Azure SignalR Serviceを使ったチャットアプリケーションの作成

それではここからは実際にアプリケーションを作成してAzure SignalR Serviceの利用方法について確認していきます。今回はサンプルアプリケーションとして、簡単なチャットアプリを作成していきます。チャットに参加しているユーザーがメッセージを送信すると、他のユーザーの画面にリアルタイムにメッセージが反映される仕組みを、Azure SignalR ServiceとAzure Functionsを使用して作成していきます。Azure SignalR ServerおよびAzure Functionsを組み合わせてアプリケーションを構築することで、サーバーレスな環境でリアルタイムアプリケーションを構築することができるようになります。

Azure SignalR Serviceのインスタンスを作成する

まずはAzure上にAzure SignalR Serviceのインスタンスを作成していきます。Azureポータルにログインし、画面上部の検索フィールドに「signalr」と入力し、SignalR Serviceの画面を表示します。 画面下部に表示される「SignalR」あるいは「追加」ボタンから新しいSignalR Serviceのインスタンス作成画面に遷移します。

  • Azure SignalR Serviceの一覧画面

    Azure SignalR Serviceの一覧画面

SignalR Serviceインスタンスの新規作成画面が表示されたら、必要な内容を入力していきます。「Subscription」と「Resource group」にはお手持ちのサブスクリプションとリソースグループを選択します。「Resource Name」にはこのSignalR Serviceインスタンス名となる任意の名称を入力します。「Region」はSignalR Serviceインスタンスが配置されるAzure上の地域です。今回は「東日本」を選択します。「Pricing tier」ではSignalR Serviceインスタンスが使用する価格を指定します。今回はサンプルですので無料枠である「Free」を選択します。「Unit Count」では稼働するSignalR Serviceインスタンスのインスタンス数を増やすことができ、これによりSignalR Serviceインスタンスの同時接続数をスケールすることができます。ただし、「Pricing tier」で「Free」を選択した場合には、「Unit count」の値は「1」に固定されます。最後に「ServiceMode」で「Serverless」選択します。「ServiceMode」ではSignalR Serviceインスタンスの起動方法を選択でき、今回のようなAzure Functionsとの連携時には「Serverless」を選択し、従来のASP.NETの一部としてSignalRを使用したい場合には「Default」を選択します。 内容を全て入力できたら、「Review + create」を選択します。

  • SignalR Serviceインスタンスの新規作成画面

    SignalR Serviceインスタンスの新規作成画面

SignalR Serviceインスタンスの作成前にAzure側で入力内容のチェックが行われ、問題がなければインスタンスの作成ができるようになります。「Create」ボタンが有効になったら選択して、インスタンスの作成を開始します。

  • SignalR Serviceインスタンスの新規作成確認画面

    SignalR Serviceインスタンスの新規作成確認画面

Azureポータル上に、作成が完了した旨のメッセージが表示されればSignalR Serviceインスタンスの作成は完了です。

チャットアプリケーションの実装

続いてアプリケーションの実装を行っていきます。まずはアプリケーションをAzure Functionsの関数として動作させるために必要な設定ファイルを作成していきます。host.jsonは、すべての関数に関係するグローバルな設定を記述するファイルです。

Azure Functionsの設定ファイル(host.json)

{
  "version": "2.0", ・・・①
  "extensionBundle": { ・・・②
    "id": "Microsoft.Azure.Functions.ExtensionBundle",
    "version": "[1.*, 2.0.0)"
  }
}

「version」プロパティには、host.jsonのスキーマバージョンを指定します(①)。「extensionBundle」は拡張機能バンドルとよばれるもので、Azure Functionsをローカル環境で実行する際にこのプロパティの記述が必要となります(②)。

次にローカル環境で実行する際に使用する設定ファイル(local.settings.json)を作成します。

ローカル環境での実行用設定ファイル(local.settings.json)

{
  "IsEncrypted": false,
  "Values": {
    "AzureSignalRConnectionString": "<Azure SignalR Serviceインスタンスの接続文字列>", ・・・①
    "FUNCTIONS_WORKER_RUNTIME": "node"
  },
  "Host": {
    "LocalHttpPort": 7071,
    "CORS": "http://localhost:8080,https://azure-samples.github.io",
    "CORSCredentials": true
  }
}

「Values」オブジェクト内では、アプリが参照できる環境変数を定義することができます。「AzureSignalRConnectionString」プロパティには、SignalR Serviceインスタンスの接続文字列を設定する必要があります(①)。接続文字列はAzureポータルから確認することができます。Azureポータルにて前項で作成したSignalR Serviceインスタンスを表示し、「Keys」というメニューを選択します。この画面の「Primary」の部分にある「CONNECTION STRING」という項目の内容をコピーして「AzureSignalRConnectionString」の値として貼り付けます。

  • SignalR Serviceインスタンスの接続文字列をコピーする

    SignalR Serviceインスタンスの接続文字列をコピーする

また「Host」オブジェクトは、Azure Functionsをローカル環境で実行する際のポート番号の指定や、CORSに関する設定を記述することができます。

negotiate関数の実装

続いてAzure Functionsの各関数を定義・実装していきます。まずはnegotiate関数と呼ばれる関数を作成していきます。negotiate関数は、アプリケーションのクライアントがSignalRサービスと直接接続をする際に必要となる接続情報を提供するAPIとして機能します。この関数はSignalRのクライアントSDKから呼び出されるため、関数は決められたフォーマットで定義・実装する必要があります。

negotiate関数のバインディング定義は以下のように記述します。アプリケーションのルートディレクトリに「negotiate」というディレクトリを作成し、その中に「function.json」という名前のファイルを作成します。

negotiate関数のバインディング定義(negotiate/function.json)

{
  "disabled": false,
  "bindings": [
    {
      "authLevel": "anonymous",
      "type": "httpTrigger",
      "direction": "in",
      "name": "req"
    },
    {
      "type": "http",
      "direction": "out",
      "name": "res"
    },
    {
      "type": "signalRConnectionInfo",
      "name": "connectionInfo",
      "hubName": "chat",
      "direction": "in"
    }
  ]
}

Azure Functionsでは、上記のようにバインディング定義のJSONの中で関数のインタフェースを定義します。「bindings」リスト内の各オブジェクトが関数のインプットやアウトプットの詳細な定義情報になります。このうち、typeが「signalRConnectionInfo」となっているバインディングにより、SignalR Serviceインスタンスの接続情報を取得します。

続いて、negotiate関数の実装を行います。

negotiate関数の実装(negotiate/index.js)

module.exports = async function (context, req, connectionInfo) {
  context.res.json(connectionInfo); ・・・①
};

関数の引数にある「req」と「connectionInfo」は、バインディング定義ファイル(function.json)したものと同一の名称で記述します。この関数ではJSON形式でレスポンスを返しており、その内容として「connectionInfo」つまりSignalR Serviceインスタンスの接続情報を設定しています(①)。

messages関数の実装

最後にmessages関数の定義と実装を行っていきます。この関数ではクライアントからチャットメッセージを受け取り、SignalR Serviceインスタンスを経由して別のクライアントにメッセージを伝播する機能を実装します。 negotiate関数と同様に、アプリケーションのルートディレクトリに「messages」というディレクトリを作成してバインディング定義ファイルから作成していきます。

messages関数のバインディング定義(messages/function.json)

{
  "disabled": false,
  "bindings": [
    {
      "authLevel": "anonymous",
      "type": "httpTrigger",
      "direction": "in",
      "name": "req",
      "methods": [
        "post"
      ]
    },
    {
      "type": "http",
      "direction": "out",
      "name": "res"
    },
    {
      "type": "signalR",
      "name": "$return",
      "hubName": "chat",
      "direction": "out"
     }
   ]
 }

typeが「signalR」となっているバインディング定義を加えることで、この関数の戻り値をSignalR Serviceインスタンスに転送することができるようになります。

messages関数の実装(messages/index.js)

module.exports = async function (context, req) {
  return {
    "target": "newMessage", ・・・①
    "arguments": [ req.body ] ・・・②
  };
};

この関数ではオブジェクトを返却しています。「target」プロパティに指定している「newMessage」という値は、SignalR Serviceから送信されたメッセージをクライアントがコールバックする際の名前として使用されます(①)。「arguments」プロパティにはSignalR Serviceインスタンスへ転送する内容をリスト形式で設定します。今回はクライアントから送信された内容をそのまま転送するため、リクエストボディを指定しています(②)。なおリクエストボディにはチャットのユーザー名とメッセージ本文が含まれます。

以上でアプリケーションの実装は完了です。ここまで実装したコード類が以下のようなディレクトリ構成で作成されていることを確認します。なお、プロジェクトのルートディレクトリ名(図では「signalr-serverless」となっている箇所)は、任意のもので構いません。

サンプルプロジェクトのディレクトリ構成

signalr-serverless/
├── host.json
├── local.settings.json
├── messages
│   ├── function.json
│   └── index.js
└── negotiate
    ├── function.json
    └── index.js

ここまで実装が完了したら、ローカルマシン上でアプリケーションの動作確認を行っていきます。

ローカルマシンでのAzure Functionsの起動

negotiate関数とmessages関数の実装まで完了したら、アプリケーションを実行して動作確認を行ってみましょう。Azure Functionsをローカル環境で実行するためには、コマンドラインツールのインストールが必要となります。ツールのインストールページから、お使いのOSのインストール手順に従ってインストールを行って下さい。 ツールのインストール後、コマンドラインから以下のコマンドを実行してAzure Functionsを起動します。

Azure Functionsをローカルマシンで実行する

cd <アプリケーションのルートディレクトリ>
func start

以下のようなメッセージが出力されればAzure Functionsの起動は成功です。

Azure Functionsをローカルマシンで実行する

・・・中略

Now listening on: http://0.0.0.0:7071
Application started. Press Ctrl+C to shut down.

Http Functions:

    messages: [POST] http://localhost:7071/api/messages

    negotiate:  http://localhost:7071/api/negotiate

アプリケーションが起動した旨のメッセージに続き、公開している関数の一覧が表示されます。今回実装したnegotiate関数とmessages関数はともにHTTPトリガーで起動する関数として定義しているため、関数にアクセスするためのURLが表示されています。

アプリケーションの動作確認

Azure Functionsをローカル環境で起動することができたら、最後にアプリケーションを動作確認を行います。今回作成したチャットアプリケーションのインターフェースに対応したクライアントページがAzureから提供されているため、そちらを使用して動作確認をしていきます。 まずはWebブラウザでサンプルクライアントのページを表示します。ページを表示すると、まずはじめに接続先となるAzure FunctionsのURLを指定するように求められるので、すでに入力されている「http://localhost:7071」のまま「OK」を選択します。

  • チャットクライアントの起動

    チャットクライアントの起動

次にユーザー名を入力するとチャットを開始することができます。メッセージを入力してEnterキーでメッセージを投稿することができます。別ブラウザでもう一つチャットクライアントを別名で起動すると、擬似的にチャットが行えることを確認することができます。

  • チャットクライアントでのチャットの様子

    チャットクライアントでのチャットの様子

チャットクライアントページの実装では、まずチャットへの入室時にAzure Functionsのnegotiate関数が呼び出されてSignalR Serviceインスタンスの接続情報をもとにクライアントとSignalR Serviceインスタンス間での接続を確立します。この動作はクライアントページでロードしたSignalRのクライアントSDKの内部で実行されます。次にチャットにメッセージを送信すると、messages関数が呼び出されてユーザー名とメッセージがSignalR Serviceインスタンスへ転送されます。転送されたデータはSignalR Serviceインスタンスからチャットに入室している全てのユーザーに対してブロードキャストされ、画面へ内容が反映される仕組みとなっています。

まとめ

今回は、Azure SignalR Serviceの概要とチャットアプリケーションの構築を通じてサービスの使用方法について説明しました。 次回も引き続きAzure SignalR Serviceについて、より実践的なアプリケーションの例を交えて説明する予定です。

WINGSプロジェクト 秋葉龍一著/山田祥寛監修
<WINGSプロジェクトについて>テクニカル執筆プロジェクト(代表山田祥寛)。海外記事の翻訳から、主にWeb開発分野の書籍・雑誌/Web記事の執筆、講演等を幅広く手がける。一緒に執筆をできる有志を募集中