ソケットや類似のものを実装するために利用可能なライブラリのいずれかを使用せずに、MVC 4 Web で軽量の HTML5 Server-Sent Event 実装をセットアップしようとしています。私が試している軽量のアプローチは次のとおりです。
クライアント側:
EventSource
(またはjquery.eventsource
IE の場合)
サーバー側:長いポーリングAsynchController
(ここに生のテストコードを落として申し訳ありませんが、アイデアを与えるだけです)
public class HTML5testAsyncController : AsyncController
{
private static int curIdx = 0;
private static BlockingCollection<string> _data = new BlockingCollection<string>();
static HTML5testAsyncController()
{
addItems(10);
}
//adds some test messages
static void addItems(int howMany)
{
_data.Add("started");
for (int i = 0; i < howMany; i++)
{
_data.Add("HTML5 item" + (curIdx++).ToString());
} _data.Add("ended");
}
// here comes the async action, 'Simple'
public void SimpleAsync()
{
AsyncManager.OutstandingOperations.Increment();
Task.Factory.StartNew(() =>
{
var result = string.Empty; var sb = new StringBuilder();
string serializedObject = null;
//wait up to 40 secs that a message arrives
if (_data.TryTake(out result, TimeSpan.FromMilliseconds(40000)))
{
JavaScriptSerializer ser = new JavaScriptSerializer();
serializedObject = ser.Serialize(new { item = result, message = "MSG content" });
sb.AppendFormat("data: {0}\n\n", serializedObject);
}
AsyncManager.Parameters["serializedObject"] = serializedObject;
AsyncManager.OutstandingOperations.Decrement();
});
}
// callback which returns the results on the stream
public ActionResult SimpleCompleted(string serializedObject)
{ ServerSentEventResult sar = new ServerSentEventResult();
sar.Content = () => { return serializedObject; };
return sar;
}
//pushes the data on the stream in a format conforming HTML5 SSE
public class ServerSentEventResult : ActionResult
{
public ServerSentEventResult() { }
public delegate string GetContent();
public GetContent Content { get; set; }
public int Version { get; set; }
public override void ExecuteResult(ControllerContext context)
{
if (context == null)
{
throw new ArgumentNullException("context");
} if (this.Content != null)
{
HttpResponseBase response = context.HttpContext.Response;
// this is the content type required by chrome 6 for server sent events
response.ContentType = "text/event-stream";
response.BufferOutput = false; // this is important because chrome fails with a "failed to load resource" error if the server attempts to put the char set after the content type
response.Charset = null;
string[] newStrings = context.HttpContext.Request.Headers.GetValues("Last-Event-ID");
if (newStrings == null || newStrings[0] != this.Version.ToString())
{
string value = this.Content();
response.Write(string.Format("data:{0}\n\n", value));
//response.Write(string.Format("id:{0}\n", this.Version));
}
else
{
response.Write("");
}
}
}
}
}
期待される結果と実際に起こっていることの間にはまだ大きなギャップがあるため、問題はサーバー側にあります。
期待される結果:
EventSource
サーバーへのストリーム接続を開き、- サーバーは安全な時間 (たとえば 2 分間) 開いたままにして、デッド クライアントからのスレッド リークから保護します。
新しいメッセージ イベントがサーバーによって受信されると (そして などのスレッド セーフなコレクションにエンキューされると
BlockingCollection
)、それらはオープン ストリームでクライアントにプッシュされます。- メッセージ 1 は T+0ms で受信され、T+x でクライアントにプッシュされます
- メッセージ 2 はT+200msで受信され、 T+x+200msでクライアントにプッシュされます
実際の動作:
EventSource
サーバーへのストリーム接続を開き、- サーバーはメッセージイベントが到着するまでそれを開いたままにします (長いポーリングのおかげで)
- メッセージが受信されると、MVC はメッセージをプッシュして接続を閉じます。
EventSource
接続を再開する必要があり、これは数秒後に発生します。- メッセージ 1 は T+0ms で受信され、T+x でクライアントにプッシュされます
- メッセージ 2 はT+200msで受信され、 T+x+3200msでクライアントにプッシュされます
クライアントが通常のポーリングと同様に再接続を開始し、メッセージ配信が遅延するため、SSE を使用する目的が無効になるため、これは問題です。
さて、質問: 最初のメッセージを送信し、同じ接続でさらにメッセージを送信した後、接続を開いたままにするネイティブな方法はありますか?