/tags/1.0/src/main/java/flowersinthesand/example/ChatServlet.java

http://jquery-stream.googlecode.com/ · Java · 165 lines · 119 code · 30 blank · 16 comment · 6 complexity · d4ca9a4476b23ae690cfebfbdf59d916 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.UUID;
  7. import java.util.concurrent.BlockingQueue;
  8. import java.util.concurrent.ConcurrentHashMap;
  9. import java.util.concurrent.LinkedBlockingQueue;
  10. import javax.servlet.AsyncContext;
  11. import javax.servlet.AsyncEvent;
  12. import javax.servlet.AsyncListener;
  13. import javax.servlet.ServletConfig;
  14. import javax.servlet.ServletException;
  15. import javax.servlet.annotation.WebServlet;
  16. import javax.servlet.http.HttpServlet;
  17. import javax.servlet.http.HttpServletRequest;
  18. import javax.servlet.http.HttpServletResponse;
  19. import com.google.gson.Gson;
  20. @WebServlet(urlPatterns = "/chat", asyncSupported = true)
  21. public class ChatServlet extends HttpServlet {
  22. private static final long serialVersionUID = -2919167206889576860L;
  23. private Map<String, AsyncContext> contexts = new ConcurrentHashMap<String, AsyncContext>();
  24. private BlockingQueue<String> messages = new LinkedBlockingQueue<String>();
  25. private Thread notifier = new Thread(new Runnable() {
  26. public void run() {
  27. boolean done = false;
  28. while (!done) {
  29. String message = null;
  30. try {
  31. message = messages.take();
  32. for (AsyncContext ac : contexts.values()) {
  33. try {
  34. sendMessage(ac.getResponse().getWriter(), message);
  35. } catch (IOException e) {
  36. contexts.remove(ac);
  37. }
  38. }
  39. } catch (InterruptedException e) {
  40. done = true;
  41. }
  42. }
  43. }
  44. });
  45. private void sendMessage(PrintWriter writer, String message) {
  46. // Sends a message according to the message format(message-size ; message-data ;)
  47. writer.print(message.length());
  48. writer.print(";");
  49. writer.print(message);
  50. writer.print(";");
  51. writer.flush();
  52. }
  53. @Override
  54. public void init(ServletConfig config) throws ServletException {
  55. super.init(config);
  56. notifier.start();
  57. }
  58. // GET method is used to open stream
  59. @Override
  60. protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException,
  61. IOException {
  62. response.setCharacterEncoding("utf-8");
  63. // Sets the Content-Type header to 'text/plain' that is the only one satisfying all streaming transport
  64. response.setContentType("text/plain");
  65. // For XDomainRequest transport, sets the Access-Control-Allow-Origin header
  66. response.setHeader("Access-Control-Allow-Origin", "*");
  67. // Prints the stream connection id for further communication and one kilobyte padding at the top of the response
  68. // each part must end with a semicolon
  69. PrintWriter writer = response.getWriter();
  70. // UUID is very suitable
  71. final String id = UUID.randomUUID().toString();
  72. writer.print(id);
  73. writer.print(';');
  74. // The padding is needed by XMLHttpRequest of WebKit, XDomainRequest and Hidden iframe transport
  75. for (int i = 0; i < 1024; i++) {
  76. writer.print(' ');
  77. }
  78. writer.print(';');
  79. writer.flush();
  80. System.out.println(id + ": open");
  81. // Starts asynchronous mode
  82. final AsyncContext ac = request.startAsync();
  83. ac.setTimeout(5 * 60 * 1000);
  84. ac.addListener(new AsyncListener() {
  85. public void onComplete(AsyncEvent event) throws IOException {
  86. contexts.remove(id);
  87. }
  88. public void onTimeout(AsyncEvent event) throws IOException {
  89. contexts.remove(id);
  90. }
  91. public void onError(AsyncEvent event) throws IOException {
  92. contexts.remove(id);
  93. }
  94. public void onStartAsync(AsyncEvent event) throws IOException {
  95. }
  96. });
  97. contexts.put(id, ac);
  98. }
  99. // POST method is used to handle data sent by user through the stream
  100. @Override
  101. protected void doPost(HttpServletRequest request, HttpServletResponse response)
  102. throws ServletException, IOException {
  103. request.setCharacterEncoding("utf-8");
  104. // POST request always has metadata parameters
  105. // The stream connection id generated by the server
  106. String id = request.getParameter("metadata.id");
  107. // Request type such as send and close
  108. String type = request.getParameter("metadata.type");
  109. System.out.println(id + ": " + type);
  110. // Handles a special case
  111. if ("close".equals(type)) {
  112. // Closes connection by its id
  113. AsyncContext ac = contexts.get(id);
  114. if (ac != null) {
  115. ac.complete();
  116. }
  117. return;
  118. }
  119. // Handles data sent from a client
  120. Map<String, String> data = new LinkedHashMap<String, String>();
  121. data.put("username", request.getParameter("username"));
  122. data.put("message", request.getParameter("message"));
  123. try {
  124. messages.put(new Gson().toJson(data));
  125. } catch (Exception e) {
  126. throw new IOException(e);
  127. }
  128. }
  129. @Override
  130. public void destroy() {
  131. messages.clear();
  132. contexts.clear();
  133. notifier.interrupt();
  134. }
  135. }