使用标准ASP.NET MVC发送服务器事件(EventSource)导致错误。

7
我正在尝试使用服务器推送事件(Server-Sent Events),但是我无法在一个MVC项目(非WebAPI)中使其正常工作。我在网上没有找到任何好的示例。
以下是我尝试的服务器端代码(包括从各种帖子中失败的尝试):
Function GetRows() as ActionResult
    Dim ret = New HttpResponseMessage
    ' ret.Content.w
    ' Return ret

    Response.ContentType = "text/event-stream"
    Response.Write("data: " & "aaaa")
    Dim flag = False
    If flag Then
        For i = 0 To 100
            Response.Write("data: " & i)
        Next
    End If
    Response.Flush()
    'Return Content("Abv")
    'Return Content(("data: {0}\n\n" & DateTime.Now.ToString(), "text/event-stream")
End Function

以下是Javascript代码

var source = new EventSource("/Tool/GetRows");
source.onmessage = function (event) {
    document.getElementById("messages").innerHTML += event.data + "<br>";
};
source.onerror = function (e) {
    console.log(e);
};

由于某种原因,它总是进入onerror中,但没有提供任何可能的错误类型信息。

我做错了什么?

顺便说一下,我认为这个操作实际上不应该返回任何东西,因为我的理解是它只应该逐个字符串地写入流字符串。

1个回答

17

EventSource 期望特定的格式,如果流不符合该格式,将会引发onerror -- 一组由字段/值对组成的行,用:分隔,每行以换行符结束:

field: value
field: value

"field" 可以是 "data"、"event"、"id"、"retry",或为空表示评论(将被 EventSource 忽略)。
"data" 可以跨越多行,但每行必须以 "data:" 开头。
每个事件触发器必须以双换行符结束。
data: 1
data: second line of message

data: 2
data: second line of second message

注意:如果您在使用VB.NET编写代码,您不能使用\n转义序列来换行;您必须使用vbLfChr(10)

顺带一提,EventSource 应该保持与服务器的开放连接。根据 MDN(我强调):

EventSource 接口用于接收服务器发送的事件。它通过 HTTP 连接到服务器并以文本/事件流格式接收事件,而无需关闭连接

一旦控制权从 MVC 控制器方法退出,结果将被打包并发送到客户端,并关闭连接。 EventSource 的一部分是客户端将尝试重新打开连接,然后服务器会立即关闭这个连接; resulting close -> reopen 周期也可以在 here 看到。

方法不应该退出,而是应该具有某种循环,将连续写入 Response 流。


VB.NET示例

Imports System.Threading
Public Class HomeController
    Inherits Controller

    Sub Message()
        Response.ContentType= "text/event-stream"
        Dim i As Integer
        Do
            i += 1
            Response.Write("data: DateTime = " & Now & vbLf)
            Response.Write("data: Iteration = " & i & vbLf)
            Response.Write(vbLf)
            Response.Flush

            'The timing of data sent to the client is determined by the Sleep interval (and latency)
            Thread.Sleep(1000) 
        Loop
    End Sub
End Class

C#示例

客户端:

<input type="text" id="userid" placeholder="UserID" /><br />
<input type="button" id="ping" value="Ping" />

<script>
    var es = new EventSource('/home/message');
    es.onmessage = function (e) {
        console.log(e.data);
    };
    es.onerror = function () {
        console.log(arguments);
    };

    $(function () {
        $('#ping').on('click', function () {
            $.post('/home/ping', {
                UserID: $('#userid').val() || 0
            });
        });
    });
</script>

服务器端:

using System;
using System.Collections.Concurrent;
using System.Threading;
using System.Web.Mvc;
using Newtonsoft.Json;

namespace EventSourceTest2.Controllers {
    public class PingData {
        public int UserID { get; set; }
        public DateTime Date { get; set; } = DateTime.Now;
    }

    public class HomeController : Controller {
        public ActionResult Index() {
            return View();
        }

        static ConcurrentQueue<PingData> pings = new ConcurrentQueue<PingData>();

        public void Ping(int userID) {
            pings.Enqueue(new PingData { UserID = userID });
        }

        public void Message() {
            Response.ContentType = "text/event-stream";
            do {
                PingData nextPing;
                if (pings.TryDequeue(out nextPing)) {
                    Response.Write("data:" + JsonConvert.SerializeObject(nextPing, Formatting.None) + "\n\n");
                }
                Response.Flush();
                Thread.Sleep(1000);
            } while (true);
        }
    }
}

1
感谢 Zev 提供清晰详细的指导,现在它可以正常工作了! - Yisroel M. Olewski

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接