/sub-projects/jquery-stream-jetty/trunk/src/main/java/flowersinthesand/example/ChatServlet.java

http://jquery-stream.googlecode.com/ · Java · 205 lines · 154 code · 38 blank · 13 comment · 7 complexity · 3e75b58a8f78f725dc166d19e5d5ab13 MD5 · raw file

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