ASP.NET SignalR を使ったリアルタイム Web サイト/アプリを開発中の話。
※ちなみに「SignalR」とは、「Node.js でいうところの Socket.io」に相当する、WebSocket などを活用した双方向リアルタイム通信を実現するための ASP.NET 用のライブラリ。
ngrok + SignalR が不調...前回の投稿で取り上げた、ローカル開発 Web サーバーを "中継" して、インターネット上からアクセス可能にするサービス「ngrok」。
この ngrok、公式サイトの説明によれば「Websocket にも対応してるはずだよ」とのこと。
なので、SignalR を使った Web アプリ開発においても、その動作確認などの目的で ngrok を経由してインターネット上からアクセスするようにしても動作するだろうと予想された。
しかし残念ながらそうはいかず、期待どおりに動作しないケースがあった。
ブラウザ側の SiganlR クライアントコードから、サーバー側の Hub メソッドの呼び出しが、なぜかサーバー側に到達しないのだ。
サーバー側 C# コードが以下のように、常に文字列 "Boo" を返す Foo メソッドを持つ SignalR Hub 実装だったとして、using Microsoft.AspNet.SignalR;
...
public class FooBarHub : Hub {
public string Foo() {
return "Bar";
}
}ブラウザ側 JavaScript コードから以下のように上記 Hub の Foo メソッドを呼び出し、その戻り値を表示するとする。let conn = $.hubConnection();
let hub = conn.createHubProxy("FooBarHub");
conn.start();
...
// 何かの DOM イベントのハンドラにて、下記を実行
hub.invoke('Foo')
.then(res => console.log(res);これらコードを、ローカル開発環境で実行したり、Azure Web Apps に配置してインターネット上からアクセスするぶんには、当たり前ではあるが、ちゃんとブラウザの開発コンソールに (Foo メソッドの戻り値である)「Bar」と表示される。
ところが、である。
このローカル開発環境では普通に動作するこのコードが、ngrok コマンドを起動してのインターネット上からのアクセスで試すと、どういうわけか Foo メソッド呼び出し結果のコールバックが呼び出されないのだ。
Visual Studio にてサーバー側の Foo メソッドにブレークポイントを設定して待ち構えても、もちろんローカル開発環境ではちゃんとブレークするが、ngrok 経由ではまったくもってブレークしない。
サーバー側からの push 通知だけは動作ちなみに、「conn.start()」メソッドの戻り値である jQuery の Promise オブジェクトについてコールバック関数を実装して様子を見てみると、ngrok 経由でもちゃんと接続成功のコールバックが発生している模様。
しかも、別の要因で発火させたサーバー側からの push 送信を購読していた場合、その push 送信ハンドラは、ngrok 経由の場合でも、正しく呼び出されるのだ。
ちょっと不思議な挙動である。
回避策それはさておき、予想としては、ngrok の WebSocket 対応と、IIS Express や ASP.NET SignalR における実装との関連で、なにか相性問題のようなことが起きているのかもしれない。
※ちなみに、ローカル開発環境および ngrok 経由の各々で、トランスポート層は WebSocket が使用されていることを確認済み。
そこで回避策として、ブラウザ側にて SignalR のサーバー側への接続を開くにあたり、使用するプロトコルを自動で決めさせずに、もっとも古典的であろうロングポーリングで接続するよう明示的に指定するようにしてみた。
コードとしては以下のとおり。
SignalR 接続オブジェクトの start メソッドを呼び出す際に、トランスポート層をロングポーリングに限定指定した設定を渡すようにする。let conn = $.hubConnection();
let hub = conn.createHubProxy("FooBarHub");
conn.start({
transport: 'longPolling'
});こうすると、ngrok 経由でのインターネット上からのアクセスでも正常に動作するようになった。
ちなみに SignalR 接続オブジェクトの start メソッドにて、トランスポート層指定にどんな選択肢があるかについては以下が参考になる。
http://www.asp.net/signalr/overview/guide-to-the-api/hubs-api-guide-javascript-client#transport
補足なお、この回避策は、あくまでも ngrok を利用してローカル開発中の Web アプリの動作確認が目的である。
よって上記のようにロングポーリングが指定されたコードがそのままリリースされてしまうのは "事故" であり、そのような事態は避けておきたい。
そこで自分のコードでは、現在のページの URL を見て、そのホスト名が ".ngrok.io" で終わっているかどうかを正規表現で判定して、もしそうである場合に限り、トランスポート層のロングポーリング限定指定を行う実装とした。
※ちなみに「SignalR」とは、「Node.js でいうところの Socket.io」に相当する、WebSocket などを活用した双方向リアルタイム通信を実現するための ASP.NET 用のライブラリ。
ngrok + SignalR が不調...前回の投稿で取り上げた、ローカル開発 Web サーバーを "中継" して、インターネット上からアクセス可能にするサービス「ngrok」。
この ngrok、公式サイトの説明によれば「Websocket にも対応してるはずだよ」とのこと。
なので、SignalR を使った Web アプリ開発においても、その動作確認などの目的で ngrok を経由してインターネット上からアクセスするようにしても動作するだろうと予想された。
しかし残念ながらそうはいかず、期待どおりに動作しないケースがあった。
ブラウザ側の SiganlR クライアントコードから、サーバー側の Hub メソッドの呼び出しが、なぜかサーバー側に到達しないのだ。
サーバー側 C# コードが以下のように、常に文字列 "Boo" を返す Foo メソッドを持つ SignalR Hub 実装だったとして、using Microsoft.AspNet.SignalR;
...
public class FooBarHub : Hub {
public string Foo() {
return "Bar";
}
}ブラウザ側 JavaScript コードから以下のように上記 Hub の Foo メソッドを呼び出し、その戻り値を表示するとする。let conn = $.hubConnection();
let hub = conn.createHubProxy("FooBarHub");
conn.start();
...
// 何かの DOM イベントのハンドラにて、下記を実行
hub.invoke('Foo')
.then(res => console.log(res);これらコードを、ローカル開発環境で実行したり、Azure Web Apps に配置してインターネット上からアクセスするぶんには、当たり前ではあるが、ちゃんとブラウザの開発コンソールに (Foo メソッドの戻り値である)「Bar」と表示される。
ところが、である。
このローカル開発環境では普通に動作するこのコードが、ngrok コマンドを起動してのインターネット上からのアクセスで試すと、どういうわけか Foo メソッド呼び出し結果のコールバックが呼び出されないのだ。
Visual Studio にてサーバー側の Foo メソッドにブレークポイントを設定して待ち構えても、もちろんローカル開発環境ではちゃんとブレークするが、ngrok 経由ではまったくもってブレークしない。
サーバー側からの push 通知だけは動作ちなみに、「conn.start()」メソッドの戻り値である jQuery の Promise オブジェクトについてコールバック関数を実装して様子を見てみると、ngrok 経由でもちゃんと接続成功のコールバックが発生している模様。
しかも、別の要因で発火させたサーバー側からの push 送信を購読していた場合、その push 送信ハンドラは、ngrok 経由の場合でも、正しく呼び出されるのだ。
ちょっと不思議な挙動である。
回避策それはさておき、予想としては、ngrok の WebSocket 対応と、IIS Express や ASP.NET SignalR における実装との関連で、なにか相性問題のようなことが起きているのかもしれない。
※ちなみに、ローカル開発環境および ngrok 経由の各々で、トランスポート層は WebSocket が使用されていることを確認済み。
そこで回避策として、ブラウザ側にて SignalR のサーバー側への接続を開くにあたり、使用するプロトコルを自動で決めさせずに、もっとも古典的であろうロングポーリングで接続するよう明示的に指定するようにしてみた。
コードとしては以下のとおり。
SignalR 接続オブジェクトの start メソッドを呼び出す際に、トランスポート層をロングポーリングに限定指定した設定を渡すようにする。let conn = $.hubConnection();
let hub = conn.createHubProxy("FooBarHub");
conn.start({
transport: 'longPolling'
});こうすると、ngrok 経由でのインターネット上からのアクセスでも正常に動作するようになった。
ちなみに SignalR 接続オブジェクトの start メソッドにて、トランスポート層指定にどんな選択肢があるかについては以下が参考になる。
http://www.asp.net/signalr/overview/guide-to-the-api/hubs-api-guide-javascript-client#transport
補足なお、この回避策は、あくまでも ngrok を利用してローカル開発中の Web アプリの動作確認が目的である。
よって上記のようにロングポーリングが指定されたコードがそのままリリースされてしまうのは "事故" であり、そのような事態は避けておきたい。
そこで自分のコードでは、現在のページの URL を見て、そのホスト名が ".ngrok.io" で終わっているかどうかを正規表現で判定して、もしそうである場合に限り、トランスポート層のロングポーリング限定指定を行う実装とした。