服务器发送的事件

  1. 介绍
  2. 订阅流: EventSource对象
  3. 从服务器发送事件
    1. 发送message事件
    2. 发送custom事件
    3. retry间隔管理重新连接
    4. 使用id字段设置唯一标识符
  4. 处理事件
  5. 处理错误
  6. 浏览器实现差异
  7. 浏览器支持和后备策略

想象一下,您所在国家的国家篮球队正在参加世界篮球锦标赛。 您想跟踪游戏,但是您不能观看它,因为它发生在您工作时。

对您来说幸运的是,您的国家新闻机构有一个crackjack网络开发团队。 他们建立了一个体育断续装置,可以随着每一次犯规或得分得分的发生而更新。 您访问一个URL,然后将更新直接推送到您的浏览器。 当然,您想知道他们是如何做到的。 答案? 服务器发送的事件

服务器发送的事件是使用流将数据和/或DOM事件从服务器推送到客户端的方式。 它适用于股票行情自动收录器,运动成绩,航班跟踪,电子邮件通知-任何需要定期更新数据的情况。

可是等等! 我听到你说, 我们是否已经可以使用XMLHttpRequestWeb Sockets这样的技术来做到这一点? 嗯,是。 但是,这样做需要扩展这些对象以执行EventSource本身的功能。

服务器端注意事项

由于服务器发送的事件是数据流,因此它们需要长期连接。 您将要使用可以处理大量同时连接的服务器。 当然,事件驱动的服务器特别适合流事件。 其中包括Node.jsJuggernautTwisted 。 对于Nginx ,有nginx-push-stream-module 。 服务器配置超出了本文的范围,但是,它随所使用的服务器而异。

让我们看一下使用EventSource对象订阅流。 然后,我们将研究发送和处理事件。

订阅事件流: EventSource对象

创建一个EventSource对象很简单。

var evtsrc = new EventSource('./url_of/event_stream/',{withCredentials:false});

EventSource构造函数最多接受两个参数:

  • URL字符串,这是必需的; 和
  • 可选的 字典参数,用于定义withCredentials属性的值。

字典在语法上类似于对象,但是它们实际上是具有定义的名称-值对的关联数据数组。 在这种情况下, withCredentials是唯一可能的字典成员。 其值可以为truefalse 。 (要全面了解有关字典的更多信息,请参阅Web IDL规范 。)

仅对于需要用户凭据(cookie)的跨域请求,才需要包含dictionary参数。 迄今为止,还没有浏览器支持跨域EventSource请求。 因此,我们不会在示例中包含第二个参数。

EventSource连接打开时,它将触发open事件 。 我们可以通过设置onopen属性来定义一个函数来处理该事件。

var evtsrc = new EventSource('./url_of/event_stream/');
evtsrc.onopen = function(openevent){
    // do something when the connection opens
}

如果我们的连接error将引发error 。 我们可以使用onerror属性为这些事件定义处理函数。 我们将在“ 处理错误”部分中讨论错误事件的一些原因。

evtsrc.onerror = function(openevent){
    // do something when there's an error
}

流事件默认是message事件。 为了处理消息事件,我们可以使用onmessage属性定义处理程序函数。

evtsrc.onmessage = function(openevent){
    // do something when we receive a message event.
}

我们还可以使用addEventListener()侦听事件。 这是处理自定义事件的唯一方法,我们将在“ 处理事件”部分中看到。

var onerrorhandler = function(openevent){
    // do something
}
evtsrc.addEventListener('error',onerrorhandler,false);

关闭连接,请使用close()方法。

evtsrc.close();

因此,我们创建了EventSource对象,并为openmessageerror事件定义了处理程序。 但是,为了使其正常工作,我们需要一个用于流式传输事件的URL。

从服务器发送事件

服务器发送的事件是作为URL流的一部分传递的文本片段。 为了使浏览器将我们的数据视为流,我们必须:

  • 使用Content-type标头提供内容,该标头的值为text/event-stream
  • 使用UTF-8字符编码。

服务器发送事件的语法很简单。 它由一个或多个以冒号分隔的字段名称/值对组成,后跟一个换行符。 字段名称可以包含四个可能值之一。

  • data :要发送的信息。
  • event :调度的事件类型。
  • id :客户端重新连接时要使用的事件的标识符。
  • retry :浏览器尝试重新连接到URL之前应经过的毫秒数。

其中,仅data字段是必需的。

发送message事件

在此示例中,我们将发送一个事件,宣布哪些球队正在参加我们的冠军赛。 当浏览器收到此文本时,它将调度一个message事件。

data: Brazil v. United States

data字段的值成为消息事件的data属性的值。 如上所述,默认情况下,服务器发送的事件是message事件。 但是,正如我们稍后讨论的那样,我们还可以通过包含event字段来调度自定义事件

我们还可以将多个数据作为单个事件发送。 每个数据块后均应跟随一个行尾字符(换行符,回车符或两者)。 在这里,我们将添加一个包含该游戏的位置和出席人数的事件。

data: Brazil v. United States

:Comments begin with a colon. Events must be followed a blank line.
data: Air Canada Centre
data: Toronto, Ontario, Canada
data: Attendance: 19,800

对于此事件, data属性的值将是: Air Canada CentrenToronto, Ontario, CanadanAttendance: 19,800

注意事件之间的空白行。 为了使客户端能够接收事件,必须在其后跟随一个空白行。 注释以冒号开头。

发送自定义事件

除非我们另外指定,否则事件属于message类型。 为此,我们需要包含一个event字段。 在下面的示例中,我们将向流中添加两个startingfive事件,并将数据作为JSON格式的字符串发送。

event: startingfive
data: {"team":{"country":"Brazil","players":[{"id":15,"name":"de Sousa","position":"C"},{"id":12,"name":"Dantas","position":"F"},
{"id":7,"name":"Jacintho","position":"F"},{"id":6,"name":"de Oliveira Ferreira","position":"G"},{"id":4,"name":"Moisés Pinto","position":"G"}]}}

event: startingfive
data: {"team":{"country":"USA","players":[{"id":15,"name":"Charles","position":"C"},{"id":11,"name":"Cash","position":"F"},
{"id":5,"name":"Jones","position":"F"},{"id":7,"name":"Montgomery","position":"G"},{"id":4,"name":"Pondexter","position":"G"}]}}

在这里,我们需要侦听startingfive事件而不是message事件。 但是,我们的data字段仍将成为事件的data属性的值。

我们将在“ 处理事件”部分中讨论data属性和MessageEvent接口。

管理连接和重新连接

现在,虽然服务器确实将事件推送到浏览器,但实际情况却有些微妙。 如果服务器保持连接打开,则EventSource请求将是一个扩展请求。 如果关闭,浏览器将等待几秒钟,然后重新连接。 例如,如果URL发送文件结束令牌,则连接可能会关闭。

每个浏览器都设置自己的默认重新连接间隔。 大多数在3到6秒后重新连接。 但是,您可以通过包含retry字段来控制此时间间隔。 retry字段指示客户端在重新连接到URL之前应等待的毫秒数。 让我们从上面的示例开始,将事件更改为包括5秒(5000毫秒)的重试间隔。

event: startingfive
data: {"team":{"country":"USA","players":[{"id":15,"name":"Charles","position":"C"},{"id":11,"name":"Cash","position":"F"},
{"id":5,"name":"Jones","position":"F"},{"id":7,"name":"Montgomery","position":"G"},{"id":4,"name":"Pondexter","position":"G"}]}}

只要连接了客户端,事件流就可以保持活动状态。 根据您的体系结构和应用程序,您可能希望服务器定期关闭连接。

使用id字段设置唯一标识符

当浏览器重新连接到URL时,它将收到重新连接时可用的所有数据。 但是如果是游戏代言人,我们可能希望让访客了解他或她错过的事情。 这就是为什么最佳做法是为每个事件设置一个id 。 在下面的示例中,我们将发送id作为score事件的一部分。

event: score
retry: 3000
data: Brazil 14
data: USA 13
data: 2pt, de Sousa
id: 09:42

它的值对于流应该是唯一的。 在这种情况下,我们使用的是篮得分的时间。

id字段成为此事件对象的lastEventId属性。 但这有另一个目的。 如果连接关闭,浏览器将在其下一个请求中包含一个Last-Event-ID标头。 将其视为流的书签。 如果存在Last-Event-ID标头,则可以调整应用程序的响应以仅发送后继事件。

处理事件

如上所述,默认情况下,所有事件都是message事件。 每个message事件都有三个属性,由MessageEvent接口定义。

event.data
返回作为消息事件的一部分发送的数据或消息。
event.origin
返回消息的来源,通常是一个字符串,其中包含发送消息的方案(例如:http,https),主机名和端口。
event.lastEventId
返回接收到的最后一个事件的唯一标识符。

每当触发message事件时,我们的onmessage函数都会被调用。 这对于发送消息事件的应用程序来说效果很好。 但是,如果您希望像我们的示例一样发送score或前startingfive事件,则它的局限性显而易见。 使用addEventListener更灵活。 在下面的代码中,我们正在使用addEventListener处理startingfive事件。

var evtsrc = new EventSource('./url_of/event_stream/');

var startingFiveHandler = function(event){
    var data = JSON.parse(event.data), numplayers, pl;

    console.log( data.team.country );

    numplayers = data.team.players.length;

    for(var i=0; i 

处理错误

智能错误处理比仅设置onerror属性需要更多的工作。 我们还需要知道错误是导致连接失败还是暂时中断。 连接失败后,浏览器将不会尝试重新连接。 如果是暂时的中断(如计算机处于睡眠状态或服务器关闭了连接,则可能发生),浏览器将重试。 浏览器将出于以下任何原因调度error事件。

  • URL发送具有错误值的Content-type响应标头。
  • URL返回了HTTP错误标头,例如404 File Not Found500 Internal Server Error。
  • 网络或DNS问题阻止了连接。
  • 服务器关闭了连接。
  • URL不允许请求源。

最后一点值得澄清。 迄今为止,还没有浏览器支持跨源的服务器发送的事件请求。 在Firefox和Opera中,尝试跨域请求将触发EventSource对象上的error事件,并且连接将失败。 在Chrome和Safari中,它将触发DOM安全异常。

然后,在处理错误时,检查readyState属性很重要。 让我们来看一个例子。

var onerror = function(event){
    var txt;
    switch( event.target.readyState ){
        // if reconnecting
        case EventSource.CONNECTING:
            txt = 'Reconnecting...';
            break;
        // if error was fatal
        case EventSource.CLOSED:
            txt = 'Connection failed. Will not retry.';
            break;
    }
    alert(txt);
}

在上面的代码中,如果e.target.readyState值为EventSource.CONNECTING (规范定义的常数;其值为0),我们将提醒用户我们正在重新连接。 如果其值等于EventSource.CLOSED (另一个值为2的常数),我们将警告用户浏览器将不会重新连接。

浏览器实现差异

当计算机从睡眠模式唤醒时,Firefox和Opera都不会更改EventSource对象的readyState属性。 即使暂时断开连接, EventSource.readyState的值仍为1。相比之下,Chrome和Safari将readyState值更改为0,表明浏览器正在重新建立连接。 但是,在测试中,所有浏览器似乎在唤醒后几秒钟会自动重新连接到URL。

浏览器支持和后备策略

发行时,Opera 11.60 +,Firefox 6.0 +,Safari 5.0 +,iOS Safari 4.0+和Chrome 6.0+均支持服务器发送的事件。 Android的WebKit和Opera Mini没有。 由于EventSource是全局对象的属性(在浏览器中,通常是window对象),因此我们可以使用以下代码确定是否支持。

if(window.EventSource !== undefined){
    // create an event source object.
} else {
    // Use a fallback or throw an error.
}

XMLHttpRequest可以用作不支持EventSource浏览器的后备。 使用一个XHR Polyfills后备包括EventSource的由Yaffle和EventSource.js通过雷米夏普。

请记住,使用XHR时,理想情况下,您的URL应在每次请求后关闭连接。 这样做可以确保最大程度的浏览器兼容性。

当然,您的应用程序并不完全知道EventSource请求的对象是EventSource还是XMLHttpRequest ,因此不知道它是否应该关闭连接。 要解决此问题,请在使用XMLHttpRequest时包括一个自定义请求标头,如下所示。

var xhr = new XMLHttpRequest();
xhr.open('GET','./url_of/event_stream/');
xhr.setRequestHeader('X-Requestor','XHR');
xhr.send(null);

然后,当此自定义标头存在时,确保您的应用程序关闭连接。 通过将Content-type:标头的值设置为text/plain ,并(可选)在URL的响应中包括Connection: close标头来实现此目的。

通过Shutterstock 互连的节点图像

From: https://www.sitepoint.com/server-sent-events/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值