说实话,各个浏览器对XMLHttpRequest内置对象请求异步连接(或者称呼为长连接)的支持不一而足,除了Firefox,Safari,IE8等,其它的要么不支持,要么有缺陷。总之,单纯依靠XHR(XMLHttpRequest的简称)来调用长连接,不靠谱。
XMLHttpRequest对象在标准情况下,在请求长连接情况下,readyState = 3时处于一直监听情况,但各大主流浏览器内的支持相关情况不尽相同:
- IE(IE6-IE7),只有在请求的内容不再发生变化时以及 readyState = 4 的时候才会获取到返回的responseText内容,因此不支持长连接。
- IE8:以XDomainRequest取代ActiveXObject对象,也算是一个进步,但在服务器端有邀请,必须在头部设置 Access-Control-Allow-Origin值,若不知道客户端调用JS所处的域名,设置成星号,通用即可。
- Opera:貌似只有在readyState=3时才会触发一次onreadystatechange函数,不支持长连接。
- Firefox 3.6 和Safari 4:默认支持XMLHttpRequest Streaming。
- Chrome 4.1版本:只有在请求内容不再发生变化才会达到readyState=2状态,所以支持情况不太好。
一个客户端订阅页面:
页面代码:
<html>
<head>
<title>comet XHR测试</title>
<meta http-equiv="X-UA-Compatible" content="IE=8" />
<meta http-equiv="content-type" content="text/html;charset=UTF-8" />
<meta name="author" content="yongboy@gmail.com" />
<meta name="keywords" content="servlet3, comet, ajax" />
<meta name="description" content="" />
<link type="text/css" rel="stylesheet" href="css/main.css" />
</head>
<body style="margin: 0; overflow: hidden" onload="">
<div id="showDiv" class="inputStyle"></div>
<script>
function showMsg(msg) {
document.getElementById("showDiv").innerHTML = msg;
}
function doXHR() {
var xhr = null;
// 在IE8下面,window.XMLHttpRequest = true,因此需要window.XDomainRequest放在第一位置
if (window.XDomainRequest) {
xhr = new XDomainRequest();
} else if (window.XMLHttpRequest) {
xhr = new XMLHttpRequest();
} else if (window.ActiveXObject) {
var aVersions = [ "Msxml2.XMLHttp.5.0", "Msxml2.XMLHttp.4.0",
"Msxml2.XMLHttp.3.0", "Msxml2.XMLHttp", "Microsoft.XMLHttp" ];
for ( var i = 0; i < aVersions.length; i++) {
try {
xhr = new ActiveXObject(aVersions[i]);
break;
} catch (e) {
}
}
}
if (xhr == null) {
showMsg("当前浏览器不支持创建XMLHttpRequest !");
return;
}
try {
xhr.onload = function() {
showMsg(xhr.responseText);
};
xhr.onerror = function() {
showMsg("XMLHttpRequest Fatal Error.");
};
xhr.onreadystatechange = function() {
try {
if (xhr.readyState > 2) {
showMsg(xhr.responseText);
}
} catch (e) {
showMsg("XMLHttpRequest Exception: " + e);
}
};
// 经测试:
// IE8,Safayi完美支持onprogress事件(可以不需要onreadystatechange事件);
// Chrome也支持,在后台数据推送到时,会调用其方法,但无法得到responseText值;除非(长连接关闭)
// Firefox 3.6 也支持,但得到返回值有些BUG
xhr.onprogress = function() {
showMsg(xhr.responseText);
};
xhr.open("GET", "getnew?" + Math.random(), true);
xhr.send(null);
} catch (e) {
showMsg("XMLHttpRequest Exception: " + e);
}
}
if (window.addEventListener) {
window.addEventListener("load", doXHR, false);
} else if (window.attachEvent) {
window.attachEvent("onload", doXHR);
}
</script>
</body>
</html>
当然后台需要一个内容发布的页面:
后台处理的代码和上篇
隐藏IFrame服务器推送部分较为类似相似:
/**
* XHR获取最新信息
*
* @author yongboy
* @date 2011-1-10
* @version 1.0
*/
@WebServlet(urlPatterns = "/getnew", asyncSupported = true)
public class GetNewBlogPosts extends HttpServlet {
private static final long serialVersionUID = 5656988888865656L;
private static final Log log = LogFactory.getLog(GetNewBlogPosts.class);
protected void doGet(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
response.setHeader("Cache-Control", "private");
response.setHeader("Pragma", "no-cache");
response.setHeader("Connection", "Keep-Alive");
response.setHeader("Proxy-Connection", "Keep-Alive");
response.setHeader("Access-Control-Allow-Origin", "*");
response.setContentType("text/html;charset=UTF-8");
response.setCharacterEncoding("UTF-8");
PrintWriter writer = response.getWriter();
// 若当前输出内容较少情况下,需要产生大约2KB的垃圾数据,诸如下面产生一些空格
for (int i = 0; i < 10; i++) {
writer.print(" ");
}
writer.println("<div class='logDiv,clear'>waiting for ......</div>");
writer.flush();
final AsyncContext ac = request.startAsync();
// 设置成长久链接
ac.setTimeout(10 * 60 * 1000);
ac.addListener(new AsyncListener() {
public void onComplete(AsyncEvent event) throws IOException {
NewBlogXHRListener.ASYNC_XHR_QUEUE.remove(ac);
}
public void onTimeout(AsyncEvent event) throws IOException {
NewBlogXHRListener.ASYNC_XHR_QUEUE.remove(ac);
}
public void onError(AsyncEvent event) throws IOException {
NewBlogXHRListener.ASYNC_XHR_QUEUE.remove(ac);
}
public void onStartAsync(AsyncEvent event) throws IOException {
log.info("now add the AsyncContext");
}
});
NewBlogXHRListener.ASYNC_XHR_QUEUE.add(ac);
}
}
不过多了兼容IE8的代码部分:
response.setHeader("Access-Control-Allow-Origin", "*");
在IE8以及Chrome平台下,需要预先生成一些大小为2K的垃圾数据,诸如一些空格。
单独线程代码和上篇隐藏IFrame服务器推送部分相似,这里不再贴出。
经测试:
- 在ubuntu 10.10系统下Chrome(版本号8.0.552.224),支持XHR Streaming,测试通过。
- 2.Firefox 3.6 下测试通过。
- Safari 5.0.3 测试通过。
- IE8测试通过。