1
2
3
4
5
6
7
8
9
10
11
12
13 package com.acme;
14
15 import java.io.IOException;
16 import java.io.PrintWriter;
17 import java.util.HashMap;
18 import java.util.LinkedList;
19 import java.util.Map;
20 import java.util.Queue;
21
22 import javax.servlet.ServletException;
23 import javax.servlet.http.HttpServlet;
24 import javax.servlet.http.HttpServletRequest;
25 import javax.servlet.http.HttpServletResponse;
26
27 import org.eclipse.jetty.continuation.Continuation;
28 import org.eclipse.jetty.continuation.ContinuationSupport;
29
30
31
32
33
34
35 public class ChatServlet extends HttpServlet
36 {
37
38
39 class Member
40 {
41 String _name;
42 Continuation _continuation;
43 Queue<String> _queue = new LinkedList<String>();
44 }
45
46 Map<String,Map<String,Member>> _rooms = new HashMap<String,Map<String, Member>>();
47
48
49
50 protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
51 {
52
53 String action = request.getParameter("action");
54 String message = request.getParameter("message");
55 String username = request.getParameter("user");
56
57 if (action.equals("join"))
58 join(request,response,username);
59 else if (action.equals("poll"))
60 poll(request,response,username);
61 else if (action.equals("chat"))
62 chat(request,response,username,message);
63 }
64
65 private synchronized void join(HttpServletRequest request,HttpServletResponse response,String username)
66 throws IOException
67 {
68 Member member = new Member();
69 member._name=username;
70 Map<String,Member> room=_rooms.get(request.getPathInfo());
71 if (room==null)
72 {
73 room=new HashMap<String,Member>();
74 _rooms.put(request.getPathInfo(),room);
75 }
76 room.put(username,member);
77 response.setContentType("text/json;charset=utf-8");
78 PrintWriter out=response.getWriter();
79 out.print("{action:\"join\"}");
80 }
81
82 private synchronized void poll(HttpServletRequest request,HttpServletResponse response,String username)
83 throws IOException
84 {
85 Map<String,Member> room=_rooms.get(request.getPathInfo());
86 if (room==null)
87 {
88 response.sendError(503);
89 return;
90 }
91 Member member = room.get(username);
92 if (room==null)
93 {
94 response.sendError(503);
95 return;
96 }
97
98 synchronized(member)
99 {
100 if (member._queue.size()>0)
101 {
102
103 response.setContentType("text/json;charset=utf-8");
104 StringBuilder buf=new StringBuilder();
105
106 buf.append("{\"action\":\"poll\",");
107 buf.append("\"from\":\"");
108 buf.append(member._queue.poll());
109 buf.append("\",");
110
111 String message = member._queue.poll();
112 int quote=message.indexOf('"');
113 while (quote>=0)
114 {
115 message=message.substring(0,quote)+'\\'+message.substring(quote);
116 quote=message.indexOf('"',quote+2);
117 }
118 buf.append("\"chat\":\"");
119 buf.append(message);
120 buf.append("\"}");
121 byte[] bytes = buf.toString().getBytes("utf-8");
122 response.setContentLength(bytes.length);
123 response.getOutputStream().write(bytes);
124 }
125 else
126 {
127 Continuation continuation = ContinuationSupport.getContinuation(request,response);
128 if (continuation.isInitial())
129 {
130
131 continuation.suspend();
132 member._continuation=continuation;
133 }
134 else
135 {
136
137 response.setContentType("text/json;charset=utf-8");
138 PrintWriter out=response.getWriter();
139 out.print("{action:\"poll\"}");
140 }
141 }
142 }
143 }
144
145 private synchronized void chat(HttpServletRequest request,HttpServletResponse response,String username,String message)
146 throws IOException
147 {
148 Map<String,Member> room=_rooms.get(request.getPathInfo());
149
150 for (Member m:room.values())
151 {
152 synchronized (m)
153 {
154 m._queue.add(username);
155 m._queue.add(message);
156
157
158 if (m._continuation!=null)
159 {
160 m._continuation.resume();
161 m._continuation=null;
162 }
163 }
164 }
165
166 response.setContentType("text/json;charset=utf-8");
167 PrintWriter out=response.getWriter();
168 out.print("{action:\"chat\"}");
169 }
170
171
172
173
174 protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
175 {
176 if (!request.getRequestURI().endsWith("/"))
177 {
178 response.sendRedirect(request.getRequestURI()+"/");
179 return;
180 }
181 if (request.getParameter("action")!=null)
182 {
183 doPost(request,response);
184 return;
185 }
186
187 response.setContentType("text/html");
188 PrintWriter out=response.getWriter();
189 out.println("<html><head>");
190 out.println(" <title>async chat</title>");
191 out.println(" <script type='text/javascript'>");
192 out.println(" function $() { return document.getElementById(arguments[0]); }");
193 out.println(" function $F() { return document.getElementById(arguments[0]).value; }");
194 out.println(" function getKeyCode(ev) { if (window.event) return window.event.keyCode; return ev.keyCode; } ");
195 out.println(" function xhr(method,uri,body,handler) {");
196 out.println(" var req=(window.XMLHttpRequest)?new XMLHttpRequest():new ActiveXObject('Microsoft.XMLHTTP');");
197 out.println(" req.onreadystatechange=function() { if (req.readyState==4 && handler) { eval('var o='+req.responseText);handler(o);} }");
198 out.println(" req.open(method,uri,true);");
199 out.println(" req.setRequestHeader('Content-Type','application/x-www-form-urlencoded');");
200 out.println(" req.send(body);");
201 out.println(" };");
202 out.println(" function send(action,user,message,handler){");
203 out.println(" if (message) message=message.replace('%','%25').replace('&','%26').replace('=','%3D');");
204 out.println(" if (user) user=user.replace('%','%25').replace('&','%26').replace('=','%3D');");
205 out.println(" xhr('POST','chat','action='+action+'&user='+user+'&message='+message,handler);");
206 out.println(" };");
207 out.println(" ");
208 out.println(" var room = {");
209 out.println(" join: function(name) {");
210 out.println(" this._username=name;");
211 out.println(" $('join').className='hidden';");
212 out.println(" $('joined').className='';");
213 out.println(" $('phrase').focus();");
214 out.println(" send('join', room._username,null);");
215 out.println(" send('chat', room._username,'has joined!');");
216 out.println(" send('poll', room._username,null, room._poll);");
217 out.println(" },");
218 out.println(" chat: function(text) {");
219 out.println(" if (text != null && text.length>0 )");
220 out.println(" send('chat',room._username,text);");
221 out.println(" },");
222 out.println(" _poll: function(m) {");
223 out.println(" //console.debug(m);");
224 out.println(" if (m.chat){");
225 out.println(" var chat=document.getElementById('chat');");
226 out.println(" var spanFrom = document.createElement('span');");
227 out.println(" spanFrom.className='from';");
228 out.println(" spanFrom.innerHTML=m.from+': ';");
229 out.println(" var spanText = document.createElement('span');");
230 out.println(" spanText.className='text';");
231 out.println(" spanText.innerHTML=m.chat;");
232 out.println(" var lineBreak = document.createElement('br');");
233 out.println(" chat.appendChild(spanFrom);");
234 out.println(" chat.appendChild(spanText);");
235 out.println(" chat.appendChild(lineBreak);");
236 out.println(" chat.scrollTop = chat.scrollHeight - chat.clientHeight; ");
237 out.println(" }");
238 out.println(" if (m.action=='poll')");
239 out.println(" send('poll', room._username,null, room._poll);");
240 out.println(" },");
241 out.println(" _end:''");
242 out.println(" };");
243 out.println(" </script>");
244 out.println(" <style type='text/css'>");
245 out.println(" div { border: 0px solid black; }");
246 out.println(" div#chat { clear: both; width: 40em; height: 20ex; overflow: auto; background-color: #f0f0f0; padding: 4px; border: 1px solid black; }");
247 out.println(" div#input { clear: both; width: 40em; padding: 4px; background-color: #e0e0e0; border: 1px solid black; border-top: 0px }");
248 out.println(" input#phrase { width:30em; background-color: #e0f0f0; }");
249 out.println(" input#username { width:14em; background-color: #e0f0f0; }");
250 out.println(" div.hidden { display: none; }");
251 out.println(" span.from { font-weight: bold; }");
252 out.println(" span.alert { font-style: italic; }");
253 out.println(" </style>");
254 out.println("</head><body>");
255 out.println("<div id='chat'></div>");
256 out.println("<div id='input'>");
257 out.println(" <div id='join' >");
258 out.println(" Username: <input id='username' type='text'/><input id='joinB' class='button' type='submit' name='join' value='Join'/>");
259 out.println(" </div>");
260 out.println(" <div id='joined' class='hidden'>");
261 out.println(" Chat: <input id='phrase' type='text'></input>");
262 out.println(" <input id='sendB' class='button' type='submit' name='join' value='Send'/>");
263 out.println(" </div>");
264 out.println("</div>");
265 out.println("<script type='text/javascript'>");
266 out.println("$('username').setAttribute('autocomplete','OFF');");
267 out.println("$('username').onkeyup = function(ev) { var keyc=getKeyCode(ev); if (keyc==13 || keyc==10) { room.join($F('username')); return false; } return true; } ; ");
268 out.println("$('joinB').onclick = function(event) { room.join($F('username')); return false; };");
269 out.println("$('phrase').setAttribute('autocomplete','OFF');");
270 out.println("$('phrase').onkeyup = function(ev) { var keyc=getKeyCode(ev); if (keyc==13 || keyc==10) { room.chat($F('phrase')); $('phrase').value=''; return false; } return true; };");
271 out.println("$('sendB').onclick = function(event) { room.chat($F('phrase')); $('phrase').value=''; return false; };");
272 out.println("</script>");
273 out.println("</body></html>");
274 }
275
276 }