Servlet 3.0 异步传输的使用
AjaxCometServlet.java
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import javax.servlet.AsyncContext;
import javax.servlet.AsyncEvent;
import javax.servlet.AsyncListener;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet(urlPatterns = {"/chat"}, asyncSupported = true)
public class AjaxCometServlet extends HttpServlet {
private static final Queue<AsyncContext> queue = new ConcurrentLinkedQueue<AsyncContext>();
private static final BlockingQueue<String> messageQueue = new LinkedBlockingQueue<String>();
private static final String BEGIN_SCRIPT_TAG = "<script type='text/javascript'>\n";
private static final String END_SCRIPT_TAG = "</script>\n";
private static final long serialVersionUID = -2919167206889576860L;
private Thread notifierThread = null;
@Override
public void init(ServletConfig config) throws ServletException {
Runnable notifierRunnable = new Runnable() {
public void run() {
boolean done = false;
while (!done) {
String cMessage = null;
try {
cMessage = messageQueue.take();
for (AsyncContext ac : queue) {
try {
PrintWriter acWriter = ac.getResponse().getWriter();
acWriter.println(cMessage);
acWriter.flush();
} catch(IOException ex) {
System.out.println(ex);
queue.remove(ac);
}
}
} catch(InterruptedException iex) {
done = true;
System.out.println(iex);
}
}
}
};
notifierThread = new Thread(notifierRunnable);
notifierThread.start();
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
res.setContentType("text/html");
res.setHeader("Cache-Control", "private");
res.setHeader("Pragma", "no-cache");
PrintWriter writer = res.getWriter();
// for IE
writer.println("<!-- Comet is a programming technique that enables web servers to send data to the client without having any need for the client to request it. -->\n");
writer.flush();
final AsyncContext ac = req.startAsync();
ac.setTimeout(10 * 60 * 1000);
ac.addListener(new AsyncListener() {
public void onComplete(AsyncEvent event) throws IOException {
queue.remove(ac);
}
public void onTimeout(AsyncEvent event) throws IOException {
queue.remove(ac);
}
public void onError(AsyncEvent event) throws IOException {
queue.remove(ac);
}
public void onStartAsync(AsyncEvent event) throws IOException {
}
});
queue.add(ac);
}
@Override
@SuppressWarnings("unchecked")
protected void doPost(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
res.setContentType("text/plain");
res.setHeader("Cache-Control", "private");
res.setHeader("Pragma", "no-cache");
req.setCharacterEncoding("UTF-8");
String action = req.getParameter("action");
String name = req.getParameter("name");
if ("login".equals(action)) {
String cMessage = BEGIN_SCRIPT_TAG + toJsonp("System Message", name + " has joined.") + END_SCRIPT_TAG;
notify(cMessage);
res.getWriter().println("success");
} else if ("post".equals(action)) {
String message = req.getParameter("message");
String cMessage = BEGIN_SCRIPT_TAG + toJsonp(name, message) + END_SCRIPT_TAG;
notify(cMessage);
res.getWriter().println("success");
} else {
res.sendError(422, "Unprocessable Entity");
}
}
@Override
public void destroy() {
queue.clear();
notifierThread.interrupt();
}
private void notify(String cMessage) throws IOException {
try {
messageQueue.put(cMessage);
} catch(Exception ex) {
IOException t = new IOException();
t.initCause(ex);
throw t;
}
}
private String escape(String orig) {
StringBuffer buffer = new StringBuffer(orig.length());
for (int i = 0; i < orig.length(); i++) {
char c = orig.charAt(i);
switch (c) {
case '\b':
buffer.append("\\b");
break;
case '\f':
buffer.append("\\f");
break;
case '\n':
buffer.append("<br />");
break;
case '\r':
// ignore
break;
case '\t':
buffer.append("\\t");
break;
case '\'':
buffer.append("\\'");
break;
case '\"':
buffer.append("\\\"");
break;
case '\\':
buffer.append("\\\\");
break;
case '<':
buffer.append("<");
break;
case '>':
buffer.append(">");
break;
case '&':
buffer.append("&");
break;
default:
buffer.append(c);
}
}
return buffer.toString();
}
private String toJsonp(String name, String message) {
return "window.parent.update({ name: \"" + escape(name) + "\", message: \"" + escape(message) + "\" });\n";
}
}
ajax.js
var count=0;
$(document).ready(function () {
var url='/ajax/chat';
$('#login-name').focus();
$('#comet-frame')[0].src=url + '?' + count;
count ++;
$("#login-button").click(function () {
var name = $('#login-name').val();
if(! name.length > 0) {
$('#system-message').css("color","red");
$('#login-name').focus();
return;
}
$('#system-message').css("color","#2d2b3d") ;
$('#system-message').html(name + ':');
$('#login-button').disabled = true;
$('#login-form').css("display","none");
$('#message-form').css("display","");
var query =
'action=login' +
'&name=' + encodeURI($('#login-name').val());
$.ajax({
type:"post",
url:url,
data:query,
success:function (data,status) {
$('#message').focus();
},
error:function () {
alert("error occured!!!");
}
});
});
$("#post-button").click(function () {
var message = $('#message').val();
if(!message > 0) {
return;
}
$('#message').disabled = true;
$('#post-button').disabled = true;
var query =
'action=post' +
'&name=' + encodeURI($('#login-name').val()) +
'&message=' + encodeURI(message);
$.ajax({
type:"post",
url:url,
data:query,
success:function (data,status) {
$('#message').disabled = false;
$('#post-button').disabled = false;
$('#message').focus();
$('#message').val("");
},
error:function () {
alert("error occured!!!");
}
});
}
)
});
function update(data){
var p = document.createElement('p');
p.innerHTML = data.name + ':<br/>' + data.message;
$('#display')[0].appendChild(p);
}
index.html
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE html
PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>Glassfish Chat</title>
<link rel="stylesheet" href="css/default.css" type="text/css" />
<script type="text/javascript" src="js/jquery-1.4.min.js"></script>
<script type="text/javascript" src="js/ajax.js"></script>
</head>
<body>
<div id="container">
<div id="container-inner">
<div id="header">
<h1>Glassfish Chat</h1>
</div>
<div id="main">
<div id="display">
</div>
<div id="form">
<div id="system-message">Please input your name:</div>
<div id="login-form">
<input id="login-name" type="text" />
<br />
<input id="login-button" type="button" value="Login" />
</div>
<div id="message-form" style="display: none;">
<div>
<textarea id="message" name="message" rows="2" cols="40"></textarea>
<br />
<input id="post-button" type="button" value="Post Message" />
</div>
</div>
</div>
</div>
</div>
</div>
<iframe id="comet-frame" style="display: none;"></iframe>
</body>
</html>
servlet形成异步处理的过程
在init()方法里,开启一条线程从消息队列中读取消息,并且在AsyncContext(异步上下文)中处理这些消息
PrintWriter acWriter = ac.getResponse().getWriter();
acWriter.println(cMessage);
acWriter.flush();
前面的代码写入一个信息,每个异步请求对应AsyncContext。
在doGet方法里,设置超时时间为10分钟,开始异步并且增加AsyncContext到队列中
final AsyncContext ac = req.startAsync();
ac.setTimeout(10 * 60 * 1000);
queue.add(ac);
对AsyncContext注册AsyncListener 监听器,在这种情况下,只有从队列中清理AsyncContext
ac.addListener(new AsyncListener() {
public void onComplete(AsyncEvent event) throws IOException {
queue.remove(ac);
}
public void onTimeout(AsyncEvent event) throws IOException {
queue.remove(ac);
}
public void onError(AsyncEvent event) throws IOException {
queue.remove(ac);
}
public void onStartAsync(AsyncEvent event) throws IOException {
}
});
在doPost方法里,将消息加入到消息队列里,消息在init方法里开启线程执行
问题:不明白在ajax.js中为什么要加上$('#comet-frame')[0].src=url + '?' + count;这么一句,不加上的话,页面显示不了!