基于服务器端推送事件的Comet技术
在服务器端推送事件的标准草案中定义一个EventSource对象,简化Comet应用程序的编写可以传递一个URL给EventSource()构造函数,然后在返回实例上监听消息事件。
var ticker = new EventSource(“stockprices.php”);
ticker.onmessage = function(e){
var type = e.type;//默认是message,事件源可以修改这个值
var data = e.data; //这属性保存服务器作为该事件的负载发送的任何字符串。
}
例:一个使用EventSource的简易聊天客户端
<script>
window.onload = function(){
var nick = puompt(“Enter yournickname”);
var input =document.getElementById(“input”);
input.foucs();
//通过EventSource注册新消息的通知
var chat = new EventSource(“/chat”);
chat.onmessage = function(event){
var msg = event.data;
var node = document.createTextNode(msg);
var div = document.createEelement(“div”);
div.appendChild(node);
document.body.insertBefore(div,input);
input.scrollIntoView();
}
input.onchange = function(){
var msg = nick+”: “+input.value
var xhr = newXMLHttpReqeust();
xhr.open(“POST”,”/chat”);
xhr.setRequestHeader(“Content-Type”,”text/plain;charset=UTF-8”);
xhr.send(msg);
input.value = “”;
}
}
</script>
Chrome和Safari已经开始支持EventSource,Mozilla也准备在Firefox4.0之后的第一个版本中实现它。其XMLHttpRequest实现在下载中触发readystatechange事件的浏览器,也可以很容易地使用XMLHttpRequest模拟EventSource。
例:用XMLHttpRequest模拟EventSource
//在不支持EventSource API的浏览器里进行模拟
if(window.EventSource===undefined){
window.EventSource = function(url){
var xhr;
var evtsrc=this;
var charsReceived = 0;
var type=null;
var data=””;
var eventName = “message”;
var lastEventId=””;
var retrydelay = 1000;
var aborted = false;
//创建一个XHR对象
xhr = new XMLHttpRequest();
xhr.onreadystatechange = function(){
switch(xhr.readyState){
case 3: processData(); break;
case 4: reconnect();break;
}
};
//通过connect()创建一个长期存在的连接
connect();
//如果连接正常关闭,等待1秒钟再尝试连接
function reconnect(){
if(aborted) return; //在终止之后不再重连接
if(xhr.status>=300) return;//报错之后不再重连接
setTimeout(connect,retrydelay);//1秒后
}
//这里的代码展示了如何建立一个连接
function connect(){
charsReceived = 0;
type = null;
xhr.open(“GET”,url);
xhr.setRequestHeader(“Cache-Control”,”on-cache”);
if(lastEventId)xhr.setRequestHeader(“Last-Event-ID”,lastEventId);
xhr.send();
}
//每当数据到达的时候,会处理并触发onmessage处理程序
function processData(){
if(!type){ //如果没有准备好,先检查响应类型
type =xhr.getResponseHeader(‘Content-Type’);
if(type!=”text/event-stream”){
aborted = true;
xhr.abort();
return;
}
}
}
//记录接收的数据
//获取响应中未处理的数据
var chunk =xhr.responseText.substring(charsReceived);
charsReceived =xhr.responseText.length;
//将大块的文本数据分成多行遍历它们
var lines =chunk.replace(/(\r\n|\r|\n)$/,””).split(/\r\n|\r|\n/);
for(var i=0;i<lines.length;i++){
var line = lines[i],pos =line.indexOf(“:”),name,value=””;
if(pos==0)continue;
if(pos>0){
name =line.substring(0,pos);
value = line.substring(pos+1);
if(value.chartAt==” ”)value= value.substring(1);
}else name = line;
switch(name){
case “event”:eventName =value;break;
case “data”:data+=value+”\n”;break;
case “id”:lastEventId =value;break;
case “retry”:retrydelay =parseInt(value) || 1000;break;
default:break; //忽略其他行
}
if(line==””){
if(evtsrc.onmessage&& data !=””){
//如果末尾有新行,就裁剪新行
if(data.charAt(data.length-1)==”\n”)
data = data.substring(0,data.length-1);
evtsrc.onmessage({
type:eventName,
data:data,
origin:url
});
}
data =””;
continue;
}
}
}
}
我们通过一个服务器示例来结束comet的探讨。下例是一个用服务器端js为Node编写的定制http服务器。当一个客户端请求根URL"/"时,它会把前面介绍的聊天客户端和模拟代码发送到客户端。当客户端创建了一个指向"/chat"POST请求时,它会将响应的主体部分作为一条聊天消息使用并写入数据,以“data:”作为Server-Sent Events的前缀,添加到每个已打开的响应数据流上。如果安装了Node,那就可以在本地运行这个服务例子。它监听8000端口,因此在启动服务器之后,就可以用浏览器访问http://localhost:8000进行聊天。
例:定制的Server-Sent Events聊天服务器
//这个例子用的是服务器的javascript,运行在NodeJS平台上
//这聊天室的实现比较简单,而且是完全匿名的
//将新的消息以POST发送到/chat地址,或者以GET形式从同一个URL获取消息的文本/事件流
//创建一个GET请求“/”来返回一个简单的html文件这个文件包括客户端聊天UI
var http =require('http');
//聊天客户端使用的html文件,在下面用到
var clientui =require('fs').readFileSync('chatclient.html');
var emulation =require('fs').readFileSync('EventSourceEmulation.js');
var clients = [];
//每隔20s发送一条注释到客户端这样它们就不会关闭连接再重连
setInterval(function(){
clients.forEach(function(client){
client.write(":ping?n");
});
},2000);
//创建一个新的服务器
var server = new http.Server();
//当服务器获取到一个新的请求,运行回调函数
server.on("request",function(){
var url = require('url').parse(request.url);
if(url.pathname==="/"){
response.writeHead(200,{Content-Type:"text/html"});
response.write("<script>"+emulation+"</script>");
response.write(clientui);
response.end();
return ;
}else if(url.pathname!="/chat"){
response.writeHead(404);
response.end();
return;
}
//如果请求类型是POST,那么就有一个客户端发送了一条新的消息
if(request.method ==="POST"){
request.setEncoding("utf8");
var body = "";
request.on("data",function(chunk){body+=chunk;});//在获取到数据之后,将其加到请求主体中
//当请求完成时,发送一个空响应
//并将消息传播到所有处于监听状态的客户端中
request.on("end",function(){
response.writeHead(200); //响应请求
//将消息转换成文本/事件流格式
//并以两个换行结束
message = "data:"+body.replace("\n",'\ndata: ')+"\r\n\r\n";
//发送消息给所有监听的客户端
clients.forEach(function(client){client.write(message);});
});
}else{
//Otherwise,a client is requesting astream of messages
response.writeHead(200,{'Content-Type':"text/event-stream"});
response.write("data:Connected\n\n");
request.connection.on("end",function(){
//如果客户端关闭连接
//从活动客户端数组中删除对应的响应对象
clients.splice(clients.indexOf(response),1);
response.end();
});
//记下响应对象,这样就可以向它改善未来的消息
clients.push(response);
}
});
//启动服务器,监听8000端口,访问http://localhost:8000来进行使用它
server.listen(8000);