开发一个使用服务器发送的事件的Web
应用程序是很容易的。你需要在服务器上的一些代码将事件流传输到Web
应用程序,但Web
应用程序端的事情几乎完全相同,处理任何其他类型的事件。
在Web
应用程序中使用服务器发送事件很简单.在服务器端,只需要按照一定的格式返回事件流,在客户端中,只需要为一些事件类型绑定监听函数,和处理其他普通的事件没多大区别.
从服务器接受事件
服务器发送事件API
也就是EventSource
接口,在你创建一个新的EventSource
对象的同时,你可以指定一个接受事件的URI.例如:
var evtSource = new EventSource("ssedemo.php");
注:从
Firefox 11
开始,EventSource
开始支持CORS
.虽然该特性目前并不是标准,但很快会成为标准.
一旦你成功初始化了一个事件源,就可以开始监听它的消息了:
evtSource.onmessage = function(e) {
var newElement = document.createElement("li");
newElement.innerHTML = "message: " + e.data;
eventList.appendChild(newElement);
}
上面的代码监听了那些从服务器发送来的所有没有指定事件类型的消息(没有event
字段的消息),然后把消息内容显示在页面文档中.
你也可以使用**addEventListener()
方法来监听其他类型的事件**:
evtSource.addEventListener("ping", function(e) {
var newElement = document.createElement("li");
var obj = JSON.parse(e.data);
newElement.innerHTML = "ping at " + obj.time;
eventList.appendChild(newElement);
}, false);
这段代码也类似,只是只有在服务器发送的消息中包含一个值为"ping
"的event
字段的时候才会触发对应的处理函数,也就是将data
字段的字段值解析为JSON
数据,然后在页面上显示出所需要的内容.
服务器端如何发送事件流
服务器端发送的响应内容应该使用值为"text/event-stream
"的MIME
类型.这里有一个事件流文件的例子:
演示的PHP代码如下:
date_default_timezone_set("America/New_York");
header("Content-Type: text/event-stream\n\n");
$counter = rand(1, 10);
while (1) {
// Every second, sent a "ping" event.
echo "event: ping\n";
$curDate = date(DATE_ISO8601);
echo 'data: {"time": "' . $curDate . '"}';
echo "\n\n";
// Send a simple message at random intervals.
$counter--;
if (!$counter) {
echo 'data: This is a message at time ' . $curDate . "\n\n";
$counter = rand(1, 10);
}
ob_flush();
flush();
sleep(1);
}
上面的代码会让服务器每隔一秒生成一个事件流并返回,其中每条消息的事件类型为"ping
",数据字段都使用了JSON
格式,数组字段中包含了每个事件流生成时的时间字符串.而且会随机返回一些无事件类型的消息.
事件流格式
事件流仅仅是一个简单的文本数据流,文本应该使用UTF- 8
格式的编码.每条消息后面都由一个空行作为分隔符.以冒号开头的行为注释行,会被忽略.
注:注释行可以用来防止连接超时,服务器可以定期发送一条消息注释行,以保持连接不断.
每条消息是由多个字段组成的,每个字段由字段名,一个冒号,以及字段值组成.
字段
规范中规定了下面这些字段:
event
事件类型.如果指定了该字段,则在客户端接收到该条消息时,会在当前的EventSource
对象上触发一个事件,事件类型就是该字段的字段值,你可以使用addEventListener()
方法在当前EventSource
对象上监听任意类型的命名事件,如果该条消息没有event
字段,则会触发onmessage
属性上的事件处理函数.data
消息的数据字段.如果该条消息包含多个data字段,则客户端会用换行符把它们连接成一个字符串来作为字段值.id
事件ID,会成为当前EventSource对象的内部属性"最后一个事件ID"的属性值.retry
一个整数值,指定了重新连接的时间(单位为毫秒),如果该字段值不是整数,则会被忽略.
除了上面规定的字段名,其他所有的字段名都会被忽略.
注: 如果一行文本中不包含冒号,则整行文本会被解析成为字段名,其字段值为空.
例子
未命名事件
下面的例子中发送了三条消息,第一条仅仅是个注释,因为它以冒号开头.第二条消息只包含了一个data
字段,值为"some text
".第三条消息包含的两个data
字段会被解析成为一个字段,值为"another message\nwith two lines
".其中每两条消息之间是以一个空行为分割符的.
: this is a test stream
data: some text
data: another message
data: with two lines
命名事件
下面的事件流中包含了一些命名事件.每个事件的类型都是由event
字段指定的,另外每个data
字段的值可以使用JSON
格式,当然也可以不是.
event: userconnect
data: {"username": "bobby", "time": "02:33:48"}
event: usermessage
data: {"username": "bobby", "time": "02:34:11", "text": "Hi everyone."}
event: userdisconnect
data: {"username": "bobby", "time": "02:34:23"}
event: usermessage
data: {"username": "sean", "time": "02:34:36", "text": "Bye, bobby."}
混合两种事件
你可以在一个事件流中同时使用命名事件和未命名事件.
event: userconnect
data: {"username": "bobby", "time": "02:33:48"}
data: Here's a system message of some kind that will get used
data: to accomplish some task.
event: usermessage
data: {"username": "bobby", "time": "02:34:11", "text": "Hi everyone."}