EventSource

EventSource 是浏览器端配合服务端事件(Server-Sent Events)使用的技术。和 WebSocket 相比,服务端事件是单向推送,而 WebSocket 是双向通信。

浏览器端

// 第二个参数是可选的,withCredentials 目前用于跨域 cookie,默认 false
const es = new EventSource(url, { withCredentials: true });
es.addEventListener('error', err => console.error(err));
// 监听 message 事件
es.addEventListener('message', evt => console.log(evt.data));
// 监听 type/custom 事件
es.addEventListener('type/custom', evt => console.log(evt.data));

EventSource 要求的是持续不断的输出,如果请求正常地结束终止了,那么客户端会再次自动发起连接,除非显式地调用 close() 方法来结束连接。

服务端

服务端的实现是返回标记为 Content-Type: text/event-stream 的持续内容,其内容的基本格式是:

event: evt-type
data: any message

其中,event 对应 EventSource 监听的事件类型,但该内容是可选的,如果没指定 event,那么则被 message 事件监听(可简化为 es.onmessage)。

紧邻的 data 字段会把内容合并起来,连接内容是一个空行。

直接以 : 开头的内容则是注释,不被解析使用。

示例

// EventSource

document.cookie = 'PHPSESSID="";max-age=-1'

const $list = document.getElementById('J-list');

function addMsg(type, data) {
  data = data || '';

  data = data.replace(/\n/g, '<br />');
  const $ele = document.createElement('li');
  $ele.innerHTML = type ? `<span class="type">${type}</span>${data}` : data;
  $ele.classList.add(`type-${type}`);
  $list.appendChild($ele);
  $ele.scrollIntoView();
}

const es = new EventSource('msg.php');
es.onerror = err => console.error(err);
es.onmessage = e => {
  addMsg(e.type, e.data);
};

es.addEventListener('ping', e => {
  addMsg('ping', e.data);
});

es.addEventListener('start', e => {
  addMsg('start', e.data);
});

es.addEventListener('times', e => {
  addMsg('times', e.data);
  if (e.data >=5 ) {
    es.close();
    addMsg('close');
  }
});

window.addEventListener('unload', () => {
  es.close();
});
<!-- Server-send Event -->

<?php
if (!session_id()) session_start();
if (!isset($_SESSION['count'])) {
  $_SESSION['count'] = 1;
} else if($_SESSION['count'] > 5) {
  session_unset();
  $_SESSION['count'] = 1;
} else {
  $_SESSION['count'] += 1;
}

// Send appropriate mime type
header("Content-Type: text/event-stream\n\n");

echo "event: times\n";
echo "data: " . $_SESSION['count'];
echo "\n\n";

// Read sample messages
$filename = "data.txt";
$handle = fopen($filename, "r");
$contents = fread($handle, filesize($filename));
fclose($handle);
// Split the messages into an array
$messages = preg_split( "/\n\n/", $contents );
// Send one message every 2 to 7 seconds
foreach ( $messages as $message ) {
    echo $message;
    echo "\n\n";
    ob_flush();
    flush();
    sleep( rand(0, 2) );
}
?>

参考