Servlet 3.0筆記之異步請求Comet推送iFrame示范
- 在注解處標記上 asyncSupported = true;
- final AsyncContext ac = request.startAsync();

protected void doPost(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
MicBlog blog = new MicBlog();
blog.setAuthor("發布者");
blog.setId(System.currentTimeMillis());
blog.setContent(iso2UTF8(request.getParameter("content")));
blog.setPubDate(new Date());
// 放到博文隊列里面去
NewBlogListener.BLOG_QUEUE.add(blog);
request.setAttribute("message", "博文發布成功!");
request.getRequestDispatcher("/WEB-INF/pages/write.jsp").forward(
request, response);
}
private static String iso2UTF8(String str){
try {
return new String(str.getBytes("ISO-8859-1"), "UTF-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return null;
}

<html>
<head>
<title>comet推送測試</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"/>
<script type="text/javascript" src="js/jquery-1.4.min.js"></script>
<script type="text/javascript" src="js/comet.js"></script>
</head>
<body style="margin: 0; overflow: hidden">
<div id="showDiv" class="inputStyle"></div>
</body>
</html>
/**
* 客戶端Comet JS 渲染部分
* @author yongboy@gmail.com
* @date 2010-10-18
* @version 1.0
*/
String.prototype.template=function(){
var args=arguments;
return this.replace(/\{(\d+)\}/g, function(m, i){
return args[i];
});
}
var html = '<div class="logDiv">'
+ '<div class="contentDiv">{0}</div>'
+ '<div class="tipDiv">last date : {1}</div>'
+ '<div class="clear"> </div>'
+ '</div>';
function showContent(json) {
$("#showDiv").prepend(html.template(json.content, json.date));
}
var server = 'blogpush';
var comet = {
connection : false,
iframediv : false,
initialize: function() {
if (navigator.appVersion.indexOf("MSIE") != -1) {
comet.connection = new ActiveXObject("htmlfile");
comet.connection.open();
comet.connection.write("<html>");
comet.connection.write("<script>document.domain = '"+document.domain+"'");
comet.connection.write("</html>");
comet.connection.close();
comet.iframediv = comet.connection.createElement("div");
comet.connection.appendChild(comet.iframediv);
comet.connection.parentWindow.comet = comet;
comet.iframediv.innerHTML = "<iframe id='comet_iframe' src='"+server+"'></iframe>";
}else if (navigator.appVersion.indexOf("KHTML") != -1 || navigator.userAgent.indexOf('Opera') >= 0) {
comet.connection = document.createElement('iframe');
comet.connection.setAttribute('id', 'comet_iframe');
comet.connection.setAttribute('src', server);
with (comet.connection.style) {
position = "absolute";
left = top = "-100px";
height = width = "1px";
visibility = "hidden";
}
document.body.appendChild(comet.connection);
}else {
comet.connection = document.createElement('iframe');
comet.connection.setAttribute('id', 'comet_iframe');
with (comet.connection.style) {
left = top = "-100px";
height = width = "1px";
visibility = "hidden";
display = 'none';
}
comet.iframediv = document.createElement('iframe');
comet.iframediv.setAttribute('onLoad', 'comet.frameDisconnected()');
comet.iframediv.setAttribute('src', server);
comet.connection.appendChild(comet.iframediv);
document.body.appendChild(comet.connection);
}
},
frameDisconnected: function() {
comet.connection = false;
$('#comet_iframe').remove();
//setTimeout("chat.showConnect();",100);
},
showMsg:function(data){
showContent(data);
},
timeout:function(){
var url = server + "?time=" + new Date().getTime();
if (navigator.appVersion.indexOf("MSIE") != -1) {
comet.iframediv.childNodes[0].src = url;
} else if (navigator.appVersion.indexOf("KHTML") != -1 || navigator.userAgent.indexOf('Opera') >= 0) {
document.getElementById("comet_iframe").src = url;
} else {
comet.connection.removeChild(comet.iframediv);
document.body.removeChild(comet.connection);
comet.iframediv.setAttribute('src', url);
comet.connection.appendChild(comet.iframediv);
document.body.appendChild(comet.connection);
}
},
onUnload: function() {
if (comet.connection) {
comet.connection = false;
}
}
}
if (window.addEventListener) {
window.addEventListener("load", comet.initialize, false);
window.addEventListener("unload", comet.onUnload, false);
} else if (window.attachEvent) {
window.attachEvent("onload", comet.initialize);
window.attachEvent("onunload", comet.onUnload);
}
/**
* 負責客戶端的推送
* @author yongboy
* @date 2011-1-13
* @version 1.0
*/
@WebServlet(urlPatterns = { "/blogpush" }, asyncSupported = true)
public class BlogPushAction extends HttpServlet {
private static final long serialVersionUID = 8546832356595L;
private static final Log log = LogFactory.getLog(BlogPushAction.class);
protected void doGet(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
response.setHeader("Cache-Control", "private");
response.setHeader("Pragma", "no-cache");
response.setContentType("text/html;charset=UTF-8");
response.setCharacterEncoding("UTF-8");
final PrintWriter writer = response.getWriter();
// 創建Comet Iframe
writer.println("<!doctype html public \"-//w3c//dtd html 4.0 transitional//en\">");
writer.println("<script type=\"text/javascript\">var comet = window.parent.comet;</script>");
writer.flush();
final AsyncContext ac = request.startAsync();
ac.setTimeout(10 * 60 * 1000);// 10分鐘時間;tomcat7下默認為10000毫秒
ac.addListener(new AsyncListener() {
public void onComplete(AsyncEvent event) throws IOException {
log.info("the event : " + event.toString()
+ " is complete now !");
NewBlogListener.ASYNC_AJAX_QUEUE.remove(ac);
}
public void onTimeout(AsyncEvent event) throws IOException {
log.info("the event : " + event.toString()
+ " is timeout now !");
// 嘗試向客戶端發送超時方法調用,客戶端會再次請求/blogpush,周而復始
log.info("try to notify the client the connection is timeout now ...");
String alertStr = "<script type=\"text/javascript\">comet.timeout();</script>";
writer.println(alertStr);
writer.flush();
writer.close();
NewBlogListener.ASYNC_AJAX_QUEUE.remove(ac);
}
public void onError(AsyncEvent event) throws IOException {
log.info("the event : " + event.toString() + " is error now !");
NewBlogListener.ASYNC_AJAX_QUEUE.remove(ac);
}
public void onStartAsync(AsyncEvent event) throws IOException {
log.info("the event : " + event.toString()
+ " is Start Async now !");
}
});
NewBlogListener.ASYNC_AJAX_QUEUE.add(ac);
}
}
/**
* 監聽器單獨線程推送到客戶端
* @author yongboy
* @date 2011-1-13
* @version 1.0
*/
@WebListener
public class NewBlogListener implements ServletContextListener {
private static final Log log = LogFactory.getLog(NewBlogListener.class);
public static final BlockingQueue<MicBlog> BLOG_QUEUE = new LinkedBlockingDeque<MicBlog>();
public static final Queue<AsyncContext> ASYNC_AJAX_QUEUE = new ConcurrentLinkedQueue<AsyncContext>();
private static final String TARGET_STRING = "<script type=\"text/javascript\">comet.showMsg(%s);</script>";
private String getFormatContent(MicBlog blog) {
return String.format(TARGET_STRING, buildJsonString(blog));
}
public void contextDestroyed(ServletContextEvent arg0) {
log.info("context is destroyed!");
}
public void contextInitialized(ServletContextEvent servletContextEvent) {
log.info("context is initialized!");
// 啟動一個線程處理線程隊列
new Thread(runnable).start();
}
private Runnable runnable = new Runnable() {
public void run() {
boolean isDone = true;
while (isDone) {
if (!BLOG_QUEUE.isEmpty()) {
try {
log.info("ASYNC_AJAX_QUEUE size : "
+ ASYNC_AJAX_QUEUE.size());
MicBlog blog = BLOG_QUEUE.take();
if (ASYNC_AJAX_QUEUE.isEmpty()) {
continue;
}
String targetJSON = getFormatContent(blog);
for (AsyncContext context : ASYNC_AJAX_QUEUE) {
if (context == null) {
log.info("the current ASYNC_AJAX_QUEUE is null now !");
continue;
}
log.info(context.toString());
PrintWriter out = context.getResponse().getWriter();
if (out == null) {
log.info("the current ASYNC_AJAX_QUEUE's PrintWriter is null !");
continue;
}
out.println(targetJSON);
out.flush();
}
} catch (Exception e) {
e.printStackTrace();
isDone = false;
}
}
}
}
};
private static String buildJsonString(MicBlog blog) {
Map<String, Object> info = new HashMap<String, Object>();
info.put("content", blog.getContent());
info.put("date",
DateFormatUtils.format(blog.getPubDate(), "HH:mm:ss SSS"));
JSONObject jsonObject = JSONObject.fromObject(info);
return jsonObject.toString();
}
}
- 客戶端請求 blog.html
- blog.html的comet.js開始注冊啟動事件
- JS產生一個iframe,在iframe中請求/blogpush,注冊異步連接,設定超時為10分鐘,注冊異步監聽器
- 服務器接收到請求,添加異步連接到隊列中
- 客戶端處于等待狀態(長連接一直建立),等待被調用
- 后臺發布新的博客文章
- 博客文章被放入到隊列中
- 一直在守候的獨立線程處理博客文章隊列;把博客文章格式化成JSON對象,一一輪詢推送到客戶端
- 客戶端JS方法被調用,進行JSON對象解析,組裝HTML代碼,顯示在當前頁面上
- 超時發生時,/blogpush通知客戶端已經超時,調用超時(timeout)方法;同時從異步連接隊列中刪除
- 客戶端接到通知,對iframe進行操作,再次進行連接請求,重復步驟2
posted on 2011-01-10 10:57 nieyong 閱讀(7586) 評論(5) 編輯 收藏 所屬分類: Servlet3