ASPNET Core 服务器发送事件/响应刷新
ASPNET Core Server Sent Events / Response flush
虽然没有官方文档,但有人知道如何使用 ASP.NET Core 实现 SSE 吗?
我怀疑一种实现可能会使用自定义中间件,但也许可以在控制器操作中做到这一点?
客户端 - wwwroot/index.html
在页面加载时,为 http://www.somehost.ca/sse
url 创建一个 EventSource
。然后将其事件写入控制台。
<body>
<script type="text/javascript">
var source = new EventSource('sse');
source.onmessage = function (event) {
console.log('onmessage: ' + event.data);
};
source.onopen = function(event) {
console.log('onopen');
};
source.onerror = function(event) {
console.log('onerror');
}
</script>
</body>
服务器端替代方案 #1 - 使用中间件
中间件处理 sse
路径。它将服务器套接字事件所需的 Content-Type
header 设置为 text/event-stream
。它写入响应流,而不关闭连接。它通过在写入之间延迟五秒钟来模仿工作。
app.Use(async (context, next) =>
{
if (context.Request.Path.ToString().Equals("/sse"))
{
var response = context.Response;
response.Headers.Add("Content-Type", "text/event-stream");
for(var i = 0; true; ++i)
{
// WriteAsync requires `using Microsoft.AspNetCore.Http`
await response
.WriteAsync($"data: Middleware {i} at {DateTime.Now}\r\r");
await response.Body.FlushAsync();
await Task.Delay(5 * 1000);
}
}
await next.Invoke();
});
服务器端备选方案 #2 - 使用控制器
控制器做的事情与中间件做的完全一样。
[Route("/api/sse")]
public class ServerSentEventController : Controller
{
[HttpGet]
public async Task Get()
{
var response = Response;
response.Headers.Add("Content-Type", "text/event-stream");
for(var i = 0; true; ++i)
{
await response
.WriteAsync($"data: Controller {i} at {DateTime.Now}\r\r");
response.Body.Flush();
await Task.Delay(5 * 1000);
}
}
}
Firefox 中的客户端控制台输出
这是 Firefox 控制台中的结果 window。每五秒钟就会收到一条新消息。
onopen
onmessage: Message 0 at 4/15/2016 3:39:04 PM
onmessage: Message 1 at 4/15/2016 3:39:09 PM
onmessage: Message 2 at 4/15/2016 3:39:14 PM
onmessage: Message 3 at 4/15/2016 3:39:19 PM
onmessage: Message 4 at 4/15/2016 3:39:24 PM
参考文献:
服务器发送的事件可以完全在控制器操作中实现。
这是基于 Shaun Luttin 的回答,但它更像是一个真实世界的示例,因为它将无限期地保持打开连接,并向 EventSource
发送消息以响应正在发送的消息已创建。
using Example.Models;
using Example.Repositories;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using System;
using System.Threading;
using System.Threading.Tasks;
namespace Example.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class SseMessagesController : ControllerBase
{
private readonly IMessageRepository messageRepository;
private readonly JsonSerializerSettings jsonSettings;
public SseMessagesController(IMessageRepository messageRepository)
{
this.messageRepository = messageRepository;
this.jsonSettings = new JsonSerializerSettings();
jsonSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
}
[HttpGet]
public async Task GetMessages(CancellationToken cancellationToken)
{
Response.StatusCode = 200;
Response.Headers.Add("Content-Type", "text/event-stream");
EventHandler<MessageCreatedArgs> onMessageCreated = async (sender, eventArgs) =>
{
try
{
var message = eventArgs.Message;
var messageJson = JsonConvert.SerializeObject(message, jsonSettings);
await Response.WriteAsync($"data:{messageJson}\n\n");
await Response.Body.FlushAsync();
}
catch (Exception)
{
// TODO: log error
}
};
messageRepository.MessageCreated += onMessageCreated;
while (!cancellationToken.IsCancellationRequested) {
await Task.Delay(1000);
}
messageRepository.MessageCreated -= onMessageCreated;
}
}
}
每当 EventSource
连接到 /api/ssemessages
时,我们都会向消息存储库中的 MessageCreated
事件添加一个事件委托。然后我们每隔1秒检查一次EventSource
是否已经关闭,这将导致请求被取消。取消请求后,我们将删除事件委托。
事件委托从事件参数中获取 Message
对象,将其序列化为 JSON (使用驼峰式大小写与 ASP.NET Core 在返回对象结果时的默认行为一致), 将 JSON 写入主体,并刷新主体的流以将数据推送到 EventSource
.
有关创建事件委托的更多信息,请参阅 this article and this update for .NET Core。
此外,如果您将其托管在 Nginx 后面,您将需要阅读 this SO answer and this ServerFault answer。
虽然没有官方文档,但有人知道如何使用 ASP.NET Core 实现 SSE 吗?
我怀疑一种实现可能会使用自定义中间件,但也许可以在控制器操作中做到这一点?
客户端 - wwwroot/index.html
在页面加载时,为 http://www.somehost.ca/sse
url 创建一个 EventSource
。然后将其事件写入控制台。
<body>
<script type="text/javascript">
var source = new EventSource('sse');
source.onmessage = function (event) {
console.log('onmessage: ' + event.data);
};
source.onopen = function(event) {
console.log('onopen');
};
source.onerror = function(event) {
console.log('onerror');
}
</script>
</body>
服务器端替代方案 #1 - 使用中间件
中间件处理 sse
路径。它将服务器套接字事件所需的 Content-Type
header 设置为 text/event-stream
。它写入响应流,而不关闭连接。它通过在写入之间延迟五秒钟来模仿工作。
app.Use(async (context, next) =>
{
if (context.Request.Path.ToString().Equals("/sse"))
{
var response = context.Response;
response.Headers.Add("Content-Type", "text/event-stream");
for(var i = 0; true; ++i)
{
// WriteAsync requires `using Microsoft.AspNetCore.Http`
await response
.WriteAsync($"data: Middleware {i} at {DateTime.Now}\r\r");
await response.Body.FlushAsync();
await Task.Delay(5 * 1000);
}
}
await next.Invoke();
});
服务器端备选方案 #2 - 使用控制器
控制器做的事情与中间件做的完全一样。
[Route("/api/sse")]
public class ServerSentEventController : Controller
{
[HttpGet]
public async Task Get()
{
var response = Response;
response.Headers.Add("Content-Type", "text/event-stream");
for(var i = 0; true; ++i)
{
await response
.WriteAsync($"data: Controller {i} at {DateTime.Now}\r\r");
response.Body.Flush();
await Task.Delay(5 * 1000);
}
}
}
Firefox 中的客户端控制台输出
这是 Firefox 控制台中的结果 window。每五秒钟就会收到一条新消息。
onopen
onmessage: Message 0 at 4/15/2016 3:39:04 PM
onmessage: Message 1 at 4/15/2016 3:39:09 PM
onmessage: Message 2 at 4/15/2016 3:39:14 PM
onmessage: Message 3 at 4/15/2016 3:39:19 PM
onmessage: Message 4 at 4/15/2016 3:39:24 PM
参考文献:
服务器发送的事件可以完全在控制器操作中实现。
这是基于 Shaun Luttin 的回答,但它更像是一个真实世界的示例,因为它将无限期地保持打开连接,并向 EventSource
发送消息以响应正在发送的消息已创建。
using Example.Models;
using Example.Repositories;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using System;
using System.Threading;
using System.Threading.Tasks;
namespace Example.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class SseMessagesController : ControllerBase
{
private readonly IMessageRepository messageRepository;
private readonly JsonSerializerSettings jsonSettings;
public SseMessagesController(IMessageRepository messageRepository)
{
this.messageRepository = messageRepository;
this.jsonSettings = new JsonSerializerSettings();
jsonSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
}
[HttpGet]
public async Task GetMessages(CancellationToken cancellationToken)
{
Response.StatusCode = 200;
Response.Headers.Add("Content-Type", "text/event-stream");
EventHandler<MessageCreatedArgs> onMessageCreated = async (sender, eventArgs) =>
{
try
{
var message = eventArgs.Message;
var messageJson = JsonConvert.SerializeObject(message, jsonSettings);
await Response.WriteAsync($"data:{messageJson}\n\n");
await Response.Body.FlushAsync();
}
catch (Exception)
{
// TODO: log error
}
};
messageRepository.MessageCreated += onMessageCreated;
while (!cancellationToken.IsCancellationRequested) {
await Task.Delay(1000);
}
messageRepository.MessageCreated -= onMessageCreated;
}
}
}
每当 EventSource
连接到 /api/ssemessages
时,我们都会向消息存储库中的 MessageCreated
事件添加一个事件委托。然后我们每隔1秒检查一次EventSource
是否已经关闭,这将导致请求被取消。取消请求后,我们将删除事件委托。
事件委托从事件参数中获取 Message
对象,将其序列化为 JSON (使用驼峰式大小写与 ASP.NET Core 在返回对象结果时的默认行为一致), 将 JSON 写入主体,并刷新主体的流以将数据推送到 EventSource
.
有关创建事件委托的更多信息,请参阅 this article and this update for .NET Core。
此外,如果您将其托管在 Nginx 后面,您将需要阅读 this SO answer and this ServerFault answer。