html5 server-sent event 使用教程-翻译

来源:互联网 发布:mac版搜狗五笔造词 编辑:程序博客网 时间:2024/05/01 11:51
翻译自: http://www.html5rocks.com/en/tutorials/eventsource/basics/

HTML5 Features
Stream Updates with Server-Sent Events
By Eric Bidelman
Published Nov. 30, 2010 Updated June 16, 2011
 
Supported browsers:
Opera supported IE unsupported Safari supported Firefox supported Chrome supported
  
Table of Contents
=================

Introduction介绍
Server-Sent Events vs. WebSocketsSSE 和 WebSockets
JavaScript APIJS API
Event Stream FormatSSE流格式
Multiline Data多行数据
Send JSON Data发送JSON格式的数据
Associating an ID with an Event给一个SSE绑定一个ID
Controlling the Reconnection-timeout控制重连时间
Specifying an event name指定事件名称
Server Examples服务器端举例
Cancel an Event Stream取消一个事件流
A Word on Security关于安全的一点建议
Demo示例
References参考
============================

Introduction
------------

I wouldn't be surprised if you've stumbled on this article wondering, "What the heck are Server-Sent Events (SSEs)?" Many people have never heard of them, and rightfully so.
我一点也不会奇怪,如果你困在这篇文章上,疑惑“Server-Sent Events到底是什么鬼东西?”。很多人从来没有听说过SSEs,还觉得这很正常。
Over the years, the specification has seen significant changes, and the API has taken somewhat of a backseat to newer, ***ier communication protocols such as the WebSocket API.
这些年来,术语发生过很多显著的变化,相比于新的,性感的通讯协议比如WebSocket API,这个API的重要性有些淡化了。
The idea behind SSEs may be familiar: a web app "subscribes" to a stream of updates generated by a server and, whenever a new event occurs, a notification is sent to the client.
SSEs背后的思想也许是熟悉的:一个web应用程序“订阅”一个服务器端产生的更新,每当一个新的事件发生,一个通知就会发送到客户端。
But to really understand Server-Sent Events, we need to understand the limitations of its AJAX predecessors, which includes:
但是要真正理解SSEs,我们需要理解它的前辈AJAX的局限,包括如下:

Polling is a traditional technique used by the vast majority of AJAX applications. The basic idea is that the application repeatedly polls a server for data. If you're familiar with the HTTP protocol, you know that fetching data revolves around a request/response format. The client makes a request and waits for the server to respond with data. If none is available, an empty response is returned. So what's the big deal with polling? Extra polling creates greater HTTP overhead.
轮询是被大量的AJAX应用程序中广泛采用的一种传统技术。基本的思想时应用程序重复的向服务器询问数据。如果你熟悉HTTP协议,你应该知道获取数据需要的一个请求/响应的回合的格式。客户端发起一个请求然后等待服务器的响应数据。如果没有任何响应数据,将返回一个空的响应。那么,轮询的问题在于什么呢?额外的轮询产生了大量的HTTP开销。

Long polling (Hanging GET / COMET) is a slight variation on polling. In long polling, if the server does not have data available, the server holds the request open until new data is made available. Hence, this technique is often referred to as a "Hanging GET". When information becomes available, the server responds, closes the connection, and the process is repeated. The effect is that the server is constantly responding with new data as it becomes available. The shortcoming is that the implementation of such a procedure typically involves hacks such as appending script tags to an 'infinite' iframe. We can do better than hacks!
(unfinishword)长轮询(Hanging GET / COMET)是轮询的一个轻微变形。在长轮询中,如果服务器没有数据要传输,服务器将不回关掉这个请求,直到有需要传输的数据出现。这个技术因此被称为"Hanging GEt"。当有数据需要返回时,服务器才响应,然后关掉连接,双方重复这个过程。它的效果是服务器总是在新数据可用时将其返回。缺点是要实现这样的过程一般需要一些hacks如appending script tags to an 'infinite' iframe 来实现。我们可以不用hacks而做的更好。

Server-Sent Events on the other hand, have been designed from the ground up to be efficient. When communicating using SSEs, a server can push data to your app whenever it wants, without the need to make an initial request. In other words, updates can be streamed from server to client as they happen. SSEs open a single unidirectional channel between server and client.
而SSEs从一开始就被设计为高效的。当使用SSEs通讯时,服务器可以在任何需要的时候向客户端推送数据,而不用初始化一个请求。换言之,数据更新按照产生的方式以流的形式从服务器传送到客户端。SSEs在服务器和客户端之间打开一个单向的通道。

The main difference between Server-Sent Events and long-polling is that SSEs are handled directly by the browser and the user simply has to listen for messages.
SSEs和long-polling之间的主要区别在于,SSEs直接被浏览器控制,用户只需要简单的监听信息即可。

Server-Sent Events vs. WebSockets
-------------------------------

Why would you choose Server-Sent Events over WebSockets? Good question.
为什么不选用WebSockets而要用Server-Sent Event呢?好问题。

One reason SSEs have been kept in the shadow is because later APIs like WebSockets provide a richer protocol to perform bi-directional, full-duplex communication.
SSEs不那么有名的原因之一是后来的API,如WebSockets,提供了一个更丰富的协议,双向的,全双工的通讯。
Having a two-way channel is more attractive for things like games, messaging apps, and for cases where you need near real-time updates in both directions.
对于游戏,消息应用程序和需要实时双向更新的情况下,有一个双向的通道确实是非常有吸引力的。
However, in some scenarios data doesn't need to be sent from the client. You simply need updates from some server action. A few examples would be friends' status updates, stock tickers, news feeds, or other automated data push mechanisms (e.g. updating a client-side Web SQL Database or IndexedDB object store).
然而,在一些场景中,客户端不需要发送数据。你仅仅需要用服务器端的某些动作来产生更新。一小部分例子有朋友状态的更新,股票,新闻速递或者其他一些数据自动推送机制(如,更新一个客户端的Web SQL 数据库或者 IndexedDB对象存储)。
If you'll need to send data to a server, XMLHttpRequest is always a friend.
如果你需要向服务器发送数据,XMLHttpRequest仍然是个好的选择。

SSEs are sent over traditional HTTP. That means they do not require a special protocol or server implementation to get working. WebSockets on the other hand, require full-duplex connections and new Web Socket servers to handle the protocol. In addition, Server-Sent Events have a variety of features that WebSockets lack by design such as automatic reconnection, event IDs, and the ability to send arbitrary events.
SSEs依然是通过传统的HTTP传送的。这意味着他们不需要一个特定的协议或者服务器端的实现来工作。而WebSockets却需要全双工的连接和新的Web Socket服务器来使用这个协议。另外,SSEs有很多特性是WebSockets在设计上所不具有的,如自动重连,事件ID和发送任意事件的能力。

JavaScript API
-------------------------------

To subscribe to an event stream, create an EventSource object and pass it the URL of your stream:
要订阅一个事件流,创建一个事件源对象然后将流的URL传递给它:

if (!!window.EventSource) {// 如果浏览器支持EventSource对象
  var source = new EventSource('stream.php');
} else {
  // Result to xhr polling :(// 使用XMLHttpRequest轮询
}
Note: If the URL passed to the EventSource constructor is an absolute URL, its origin (scheme, domain, port) must match that of the calling page.
注意:如果传递给EventSource构造器的是一个绝对URL,它的内容(协议,域,端口)必需匹配所要调用的页面。

Next, set up a handler for the message event. You can optionally listen for open and error:
然后,为消息对象设定一个控制器。可选的,你可以监听对象的打开和出错:

source.addEventListener('message', function(e) {
  console.log(e.data);
}, false);

source.addEventListener('open', function(e) {
  // Connection was opened.
}, false);

source.addEventListener('error', function(e) {
  if (e.readyState == EventSource.CLOSED) {
    // Connection was closed.
  }
}, false);
When updates are pushed from the server, the onmessage handler fires and new data is be available in its e.data property. The magical part is that whenever the connection is closed, the browser will automatically reconnect to the source after ~3 seconds. Your server implementation can even have control over this reconnection timeout. See Controlling the reconnection-timeout in the next section.
当更新被从服务器端推送过来时,onmessage控制器将启动,新的数据将可以通过它的e.data属性访问。神奇的是每当连接被关闭后,浏览器会在3秒后自动的重新连接。你服务器端的实现甚至可以控制这个断线重连的时间间隔。参见下一节中的控制断线重连时间。

That's it. Your client is now ready to process events from stream.php.
就这样。你的客户端现在已经准备好处理从stream.php来的事件(流)了。

Event Stream Format
事件流格式

Sending an event stream from the source is a matter of constructing a plaintext response, served with a text/event-stream Content-Type, that follows the SSE format. In its basic form, the response should contain a "data:" line, followed by your message, followed by two "\n" characters to end the stream:
从源头发送一个事件流其实就是构建一些纯文本的响应,用text/event-stream作为Content-Type,然后遵循SSE格式。
最基本的形式中,响应应该包含一个“data:”行,后面跟着要发送的消息,然后跟着两个'\n'字符表示流的结束。

data: My message\n\n
Multiline Data

If your message is longer, you can break it up by using multiple "data:" lines. Two or more consecutive lines beginning with "data:" will be treated as a single piece of data, meaning only one message event will be fired. Each line should end in a single "\n" (except for the last, which should end with two). The result passed to your message handler is a single string concatenated by newline characters. For example:
如果你要发送的消息比较长,你可以使用多个“data:”行来将消息断开。两行或多行连续以“data:”开头的行将被认为是一条消息,也就是说,只触发一次message事件。每一行应该以'\n'结尾(除了最后一行,它要用两个来结尾)。最终被传递给你的消息控制器的是被换行符连接的单个字符串。例如:

data: first line\n
data: second line\n\n
will produce "first line\nsecond line" in e.data. One could then use e.data.split('\n').join('') to reconstruct the message sans "\n" characters.
将在e.data中产生“first line\nsecond line”。此时你可以使用e.data.split('\n').join('')来重构一条没有换行符的消息。

Send JSON Data
--------------------------------

Using multiple lines makes it easy to send JSON without breaking syntax:
使用多行数据可以轻松的发送JSON格式的数据:

data: {\n
data: "msg": "hello world",\n
data: "id": 12345\n
data: }\n\n
and possible client-side code to handle that stream:
客户端处理流的代码可能是这样的:

source.addEventListener('message', function(e) {
  var data = JSON.parse(e.data);
  console.log(data.id, data.msg);
}, false);

Associating an ID with an Event
--------------------------------
You can send a unique id with an stream event by including a line starting with "id:":
你可以通过包含一个以"id:"开头的行来为流指定一个唯一的id:

id: 12345\n
data: GOOG\n
data: 556\n\n
Setting an ID lets the browser keep track of the last event fired so that if, the connection to the server is dropped, a special HTTP header (Last-Event-ID) is set with the new request. This lets the browser determine which event is appropriate to fire. The message event contains a e.lastEventId property.
设定ID可以让浏览器跟踪上次的事件ID,如果和服务器的连接已经断开,一个特定的HTTP header(Last-Event-ID)将和新的请求一起发出。这让浏览器可以决定处理哪个事件。消息事件有e.lastEvent-ID属性。

Controlling the Reconnection-timeout
-------------------------------------

The browser attempts to reconnect to the source roughly 3 seconds after each connection is closed. You can change that timeout by including a line beginning with "retry:", followed by the number of milliseconds to wait before trying to reconnect.
浏览器在连接关闭之后大概3秒时会尝试重新连接事件源。你可以通过包含一个以"retry:"开头的行,后面跟断线重连要等待的时间,以毫秒计。

The following example attempts a reconnect after 10 seconds:
下面这个例子中,10秒后尝试重连:

retry: 10000\n
data: hello world\n\n
Specifying an event name

A single event source can generate different types events by including an event name. If a line beginning with "event:" is present, followed by a unique name for the event, the event is associated with that name. On the client, an event listener can be setup to listen to that particular event.
单个的事件源可以通过包含事件名来产生不同类型的事件。如果给出一个以"event:"开头的行,后面跟一个唯一的事件名,这个事件就和这个事件名向关联了。在客户端,一个事件监听器可以被设定为只监听某个特定的事件。

For example, the following server output sends three types of events, a generic 'message' event, 'userlogon', and 'update' event:
例如,如下的服务器输出发送了三种类型的事件,一个普通的'message'事件,'userlogon'事件和'update'事件:

data: {"msg": "First message"}\n\n
event: userlogon\n
data: {"username": "John123"}\n\n
event: update\n
data: {"username": "John123", "emotion": "happy"}\n\n
With event listeners setup on the client:
客户端建立如下的事件监听器:

source.addEventListener('message', function(e) {
  var data = JSON.parse(e.data);
  console.log(data.msg);
}, false);

source.addEventListener('userlogon', function(e) {
  var data = JSON.parse(e.data);
  console.log('User login:' + data.username);
}, false);

source.addEventListener('update', function(e) {
  var data = JSON.parse(e.data);
  console.log(data.username + ' is now ' + data.emotion);
}, false);
Server Examples
服务器端例子

A simple server implementation in PHP:
服务器端的一个简单实现,以PHP为例:

<?php
header('Content-Type: text/event-stream');
header('Cache-Control: no-cache'); // recommended to prevent caching of event data.

/**
 * Constructs the SSE data format and flushes that data to the client.
 *
 * @param string $id Timestamp/id of this connection.
 * @param string $msg Line of text that should be transmitted.
 */
function sendMsg($id, $msg) {
  echo "id: $id" . PHP_EOL;
  echo "data: $msg" . PHP_EOL;
  echo PHP_EOL;
  ob_flush();
  flush();
}

$serverTime = time();

sendMsg($serverTime, 'server time: ' . date("h:i:s", time()));
?>

Here's a similiar implementation using Node JS:
还有一个相似的实现,用的是Node JS:

var http = require('http');
var sys = require('sys');
var fs = require('fs');

http.createServer(function(req, res) {
  //debugHeaders(req);

  if (req.headers.accept && req.headers.accept == 'text/event-stream') {
    if (req.url == '/events') {
      sendSSE(req, res);
    } else {
      res.writeHead(404);
      res.end();
    }
  } else {
    res.writeHead(200, {'Content-Type': 'text/html'});
    res.write(fs.readFileSync(__dirname + '/sse-node.html'));
    res.end();
  }
}).listen(8000);

function sendSSE(req, res) {
  res.writeHead(200, {
    'Content-Type': 'text/event-stream',
    'Cache-Control': 'no-cache',
    'Connection': 'keep-alive'
  });

  var id = (new Date()).toLocaleTimeString();

  // Sends a SSE every 5 seconds on a single connection.
  setInterval(function() {
    constructSSE(res, id, (new Date()).toLocaleTimeString());
  }, 5000);

  constructSSE(res, id, (new Date()).toLocaleTimeString());
}

function constructSSE(res, id, data) {
  res.write('id: ' + id + '\n');
  res.write("data: " + data + '\n\n');
}

function debugHeaders(req) {
  sys.puts('URL: ' + req.url);
  for (var key in req.headers) {
    sys.puts(key + ': ' + req.headers[key]);
  }
  sys.puts('\n\n');
}

sse-node.html:
文件sse-node.html:

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8" />
</head>
<body>
  <script>
    var source = new EventSource('/events');
    source.onmessage = function(e) {
      document.body.innerHTML += e.data + '
';
    };
  </script>
</body>
</html>

Cancel an Event Stream
-------------------------------

Normally, the browser auto-reconnects to the event source when the connection is closed, but that behavior can be canceled from either the client or server.
通常情况下,当连接被关闭后浏览器会重新连接事件源,但是这个行为可以客户端或者服务器端取消。

To cancel a stream from the client, simply call:
要从客户端取消一个流,简单的调用:
source.close();
To cancel a stream from the server, respond with a non "text/event-stream" Content-Type or return an HTTP status other than 200 OK (e.g. 404 Not Found).
要从服务器端取消一个流,用一个非"text/event-stream"的Content-Type响应客户端或者返回一个非200OK的状态码(如404 Not Found)给客户端。

Both methods will prevent the browser from re-establishing the connection.
两种方法都能组织浏览器再度建立连接。

A Word on Security
-------------------------------

From the WHATWG's section on Cross-document messaging security:

Authors should check the origin attribute to ensure that messages are only accepted from domains that they expect to receive messages from. Otherwise, bugs in the author's message handling code could be exploited by hostile sites.
作者们应该检查origin属性以确保仅仅接收来自期望中的域的消息。否则的话,……
So, as an extra level of precaution, be sure to verify e.origin in your message handler matches your app's origin:
所以,作为一种额外的预防,一定要确认一下消息控制器中的e.origin和你的应用程序的origin是相符的:

source.addEventListener('message', function(e) {
  if (e.origin != 'http://example.com') {
    alert('Origin was not http://example.com');
    return;
  }
  ...
}, false);

References
参考

Server-Sent Events specification
Cross-document messaging security
原创粉丝点击