/sub-projects/jquery-stream-jetty/trunk/src/main/java/flowersinthesand/example/ChatServlet.java
Java | 205 lines | 154 code | 38 blank | 13 comment | 7 complexity | 3e75b58a8f78f725dc166d19e5d5ab13 MD5 | raw file
1package flowersinthesand.example; 2 3import java.io.IOException; 4import java.io.PrintWriter; 5import java.util.LinkedHashMap; 6import java.util.Map; 7import java.util.Queue; 8import java.util.UUID; 9import java.util.concurrent.BlockingQueue; 10import java.util.concurrent.ConcurrentHashMap; 11import java.util.concurrent.ConcurrentLinkedQueue; 12import java.util.concurrent.LinkedBlockingQueue; 13 14import javax.servlet.AsyncContext; 15import javax.servlet.AsyncEvent; 16import javax.servlet.AsyncListener; 17import javax.servlet.ServletConfig; 18import javax.servlet.ServletException; 19import javax.servlet.annotation.WebServlet; 20import javax.servlet.http.HttpServletRequest; 21import javax.servlet.http.HttpServletResponse; 22 23import org.eclipse.jetty.util.UrlEncoded; 24import org.eclipse.jetty.websocket.WebSocket; 25import org.eclipse.jetty.websocket.WebSocketServlet; 26 27import com.google.gson.Gson; 28 29@WebServlet(urlPatterns = "/chat", asyncSupported = true) 30public class ChatServlet extends WebSocketServlet { 31 32 private static final long serialVersionUID = 4805728426990609124L; 33 34 private Map<String, AsyncContext> asyncContexts = new ConcurrentHashMap<String, AsyncContext>(); 35 private Queue<ChatWebSocket> webSockets = new ConcurrentLinkedQueue<ChatWebSocket>(); 36 private BlockingQueue<String> messages = new LinkedBlockingQueue<String>(); 37 private Thread notifier = new Thread(new Runnable() { 38 public void run() { 39 while (true) { 40 try { 41 // Waits until a message arrives 42 String message = messages.take(); 43 44 // Sends the message to all the AsyncContext's response 45 for (AsyncContext asyncContext : asyncContexts.values()) { 46 try { 47 sendMessage(asyncContext.getResponse().getWriter(), message); 48 } catch (Exception e) { 49 asyncContexts.values().remove(asyncContext); 50 } 51 } 52 53 // Sends the message to all the WebSocket's connection 54 for (ChatWebSocket webSocket : webSockets) { 55 try { 56 webSocket.connection.sendMessage(message); 57 } catch (Exception e) { 58 webSockets.remove(webSocket); 59 } 60 } 61 } catch (InterruptedException e) { 62 break; 63 } 64 } 65 } 66 }); 67 68 private void sendMessage(PrintWriter writer, String message) throws IOException { 69 // default message format is message-size ; message-data ; 70 writer.print(message.length()); 71 writer.print(";"); 72 writer.print(message); 73 writer.print(";"); 74 writer.flush(); 75 } 76 77 @Override 78 public void init(ServletConfig config) throws ServletException { 79 super.init(config); 80 notifier.start(); 81 } 82 83 // GET method is used to establish a stream connection 84 @Override 85 protected void doGet(HttpServletRequest request, HttpServletResponse response) 86 throws ServletException, IOException { 87 88 // Content-Type header 89 response.setContentType("text/plain"); 90 response.setCharacterEncoding("utf-8"); 91 92 // Access-Control-Allow-Origin header 93 response.setHeader("Access-Control-Allow-Origin", "*"); 94 95 PrintWriter writer = response.getWriter(); 96 97 // Id 98 final String id = UUID.randomUUID().toString(); 99 writer.print(id); 100 writer.print(';'); 101 102 // Padding 103 for (int i = 0; i < 1024; i++) { 104 writer.print(' '); 105 } 106 writer.print(';'); 107 writer.flush(); 108 109 final AsyncContext ac = request.startAsync(); 110 ac.addListener(new AsyncListener() { 111 public void onComplete(AsyncEvent event) throws IOException { 112 asyncContexts.remove(id); 113 } 114 115 public void onTimeout(AsyncEvent event) throws IOException { 116 asyncContexts.remove(id); 117 } 118 119 public void onError(AsyncEvent event) throws IOException { 120 asyncContexts.remove(id); 121 } 122 123 public void onStartAsync(AsyncEvent event) throws IOException { 124 125 } 126 }); 127 asyncContexts.put(id, ac); 128 } 129 130 // POST method is used to communicate with the server 131 @Override 132 protected void doPost(HttpServletRequest request, HttpServletResponse response) 133 throws ServletException, IOException { 134 request.setCharacterEncoding("utf-8"); 135 136 AsyncContext ac = asyncContexts.get(request.getParameter("metadata.id")); 137 if (ac == null) { 138 return; 139 } 140 141 // close-request 142 if ("close".equals(request.getParameter("metadata.type"))) { 143 ac.complete(); 144 return; 145 } 146 147 // send-request 148 Map<String, String> data = new LinkedHashMap<String, String>(); 149 data.put("username", request.getParameter("username")); 150 data.put("message", request.getParameter("message")); 151 152 try { 153 messages.put(new Gson().toJson(data)); 154 } catch (InterruptedException e) { 155 throw new IOException(e); 156 } 157 } 158 159 @Override 160 public WebSocket doWebSocketConnect(HttpServletRequest request, String protocol) { 161 return new ChatWebSocket(); 162 } 163 164 class ChatWebSocket implements WebSocket.OnTextMessage { 165 166 Connection connection; 167 168 @Override 169 public void onOpen(Connection connection) { 170 this.connection = connection; 171 webSockets.add(this); 172 } 173 174 @Override 175 public void onClose(int closeCode, String message) { 176 webSockets.remove(this); 177 } 178 179 @Override 180 public void onMessage(String queryString) { 181 // Parses query string 182 UrlEncoded parameters = new UrlEncoded(queryString); 183 184 Map<String, String> data = new LinkedHashMap<String, String>(); 185 data.put("username", parameters.getString("username")); 186 data.put("message", parameters.getString("message")); 187 188 try { 189 messages.put(new Gson().toJson(data)); 190 } catch (InterruptedException e) { 191 throw new RuntimeException(e); 192 } 193 } 194 195 } 196 197 @Override 198 public void destroy() { 199 messages.clear(); 200 webSockets.clear(); 201 asyncContexts.clear(); 202 notifier.interrupt(); 203 } 204 205}