/wiki/EchoExample.wiki
Unknown | 350 lines | 274 code | 76 blank | 0 comment | 0 complexity | e0a93be9a72602f344cfd9e771a33831 MD5 | raw file
1#summary Echo Example 2 3*Please, see ChatExample for practical use. This wiki will not be updated.* 4 5= Echo Example = 6<wiki:toc max_depth="3" /> 7 8== Web Page == 9Web Page is an echo client. 10 11Whenever user enters a message, the page makes !JavaScript object containing it and send it to the echo server using the jQuery Stream, and when a message returns from the server, the page displays it. 12 13According to the server support, the stream type may have to be set to {{{http}}} and its URL may have to be modified. 14 15Since the web page is plain HTML page and the jQuery Stream is plain JavaScript library, they don't need any server-side support, so choose a server implementation in [#WebSocket_/HTTP_Echo_Server WebSocket/HTTP Echo Server] according to your preference. 16 17{{{/echo.html}}} 18{{{ 19<!DOCTYPE html> 20<html> 21 <head> 22 <title>Echo</title> 23 <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> 24 <script type="text/javascript" src="./js/jquery-1.4.1.js"></script> 25 <script type="text/javascript" src="./js/jquery.stream.js"></script> 26 <script type="text/javascript"> 27 $.stream.setup({enableXDR: true}); 28 29 $(function() { 30 $.stream("./echo", { 31 open: function() { 32 $("#textfield").removeAttr("disabled").focus(); 33 }, 34 message: function(event) { 35 $("<p />").text(event.data).prependTo("#content"); 36 }, 37 error: function() { 38 $("#textfield").attr("disabled", "disabled"); 39 }, 40 close: function() { 41 $("#textfield").attr("disabled", "disabled"); 42 } 43 }); 44 45 $("#textfield").keyup(function(event) { 46 if (event.which === 13 && $.trim(this.value)) { 47 $.stream().send({message: this.value}); 48 this.value = ""; 49 } 50 }); 51 }); 52 </script> 53 <style> 54 body {padding: 0; margin: 0; font-family: 'Trebuchet MS','Malgun Gothic'; font-size: 62.5%; color: #333333} 55 #editor {margin: 15px 25px;} 56 #textfield {width: 100%; height: 28px; line-height: 28px; font-family: 'Trebuchet MS','Malgun Gothic'; 57 border: medium none; border-color: #E5E5E5 #DBDBDB #D2D2D2; border-style: solid; border-width: 1px;} 58 #content {height: 100%; overflow-y: auto; padding: 0 25px;} 59 #content p {margin: 0; padding: 0; font-size: 1.3em; color: #444444; line-height: 1.7em; word-wrap: break-word;} 60 </style> 61 </head> 62 <body> 63 <div id="editor"> 64 <input id="textfield" type="text" disabled="disabled" /> 65 </div> 66 <div id="content"></div> 67 </body> 68</html> 69}}} 70 71== !WebSocket/HTTP Echo Server == 72!WebSocket/HTTP Echo Server is just a typical echo server which sends back the client's message through ws or http protocol. 73 74See ServerSideProcessing for details about what methods do what. 75 76If you have implemented the server logic using technology or platform not listed, please annotate it and create a issue to append that code to this wiki. 77 78=== Java - Servlet 3.0 === 79The Servlet 3.0 specification includes support for asynchronous processing of request, but there is no support for !WebSocket. 80 81The following example runs with any servlet container implementing Servlet 3.0 such as Tomcat 7 and Jetty 8 only via HTTP protocol. 82 83{{{flowersinthesand.example.EchoServlet}}} 84{{{ 85package flowersinthesand.example; 86 87import java.io.IOException; 88import java.io.PrintWriter; 89import java.util.Map; 90import java.util.UUID; 91import java.util.concurrent.ConcurrentHashMap; 92 93import javax.servlet.AsyncContext; 94import javax.servlet.AsyncEvent; 95import javax.servlet.AsyncListener; 96import javax.servlet.ServletException; 97import javax.servlet.annotation.WebServlet; 98import javax.servlet.http.HttpServlet; 99import javax.servlet.http.HttpServletRequest; 100import javax.servlet.http.HttpServletResponse; 101 102// Registers a servlet by newly introduced annotation, @WebServlet 103@WebServlet(urlPatterns = "/chat", asyncSupported = true) 104public class EchoServlet extends HttpServlet { 105 106 private static final long serialVersionUID = -8823775068689773674L; 107 108 private Map<String, AsyncContext> asyncContexts = new ConcurrentHashMap<String, AsyncContext>(); 109 110 // GET method is used to open stream 111 @Override 112 protected void doGet(HttpServletRequest request, HttpServletResponse response) 113 throws ServletException, IOException { 114 115 // Rejects WebSocket opening handshake 116 if ("websocket".equalsIgnoreCase(request.getHeader("Upgrade"))) { 117 response.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED); 118 return; 119 } 120 121 response.setCharacterEncoding("utf-8"); 122 123 // Content-Type header 124 response.setContentType("text/plain"); 125 126 // Access-Control-Allow-Origin header 127 response.setHeader("Access-Control-Allow-Origin", "*"); 128 129 PrintWriter writer = response.getWriter(); 130 131 // Id 132 final String id = UUID.randomUUID().toString(); 133 writer.print(id); 134 writer.print(';'); 135 136 // Padding 137 for (int i = 0; i < 1024; i++) { 138 writer.print(' '); 139 } 140 writer.print(';'); 141 writer.flush(); 142 143 // Starts asynchronous mode 144 // AsyncContext, AsyncListener and AsyncEvent are used for asynchronous operation 145 final AsyncContext ac = request.startAsync(); 146 ac.setTimeout(5 * 60 * 1000); 147 ac.addListener(new AsyncListener() { 148 public void onComplete(AsyncEvent event) throws IOException { 149 asyncContexts.remove(id); 150 } 151 152 public void onTimeout(AsyncEvent event) throws IOException { 153 asyncContexts.remove(id); 154 } 155 156 public void onError(AsyncEvent event) throws IOException { 157 asyncContexts.remove(id); 158 } 159 160 public void onStartAsync(AsyncEvent event) throws IOException { 161 162 } 163 }); 164 165 // Manages AsyncContext instances by the id 166 asyncContexts.put(id, ac); 167 } 168 169 // POST method is used to handle data sent by user via the stream 170 @Override 171 protected void doPost(HttpServletRequest request, HttpServletResponse response) 172 throws ServletException, IOException { 173 request.setCharacterEncoding("utf-8"); 174 175 // Finds AsyncContext instance by stream id 176 AsyncContext ac = asyncContexts.get(request.getParameter("metadata.id")); 177 if (ac == null) { 178 return; 179 } 180 181 // Close request means that browser closed this stream 182 if ("close".equals(request.getParameter("metadata.type"))) { 183 ac.complete(); 184 return; 185 } 186 187 String message = request.getParameter("message"); 188 PrintWriter writer = ac.getResponse().getWriter(); 189 190 // Sends message 191 writer.print(message.length() + ";" + message + ";"); 192 writer.flush(); 193 } 194 195} 196}}} 197 198=== Java - Servlet 3.0 and Jetty 8 === 199Jetty is a servlet container, implements Servlet 3.0 and also provides !WebSocket based on servlet. 200 201Currently, Jetty 8.0.0 M3 does not seem to support new annotations. 202 203{{{web.xml}}} 204{{{ 205<web-app 206 xmlns="http://java.sun.com/xml/ns/javaee" 207 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 208 xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" 209 version="3.0"> 210 211 <servlet> 212 <servlet-name>Echo</servlet-name> 213 <servlet-class>flowersinthesand.example.EchoServlet</servlet-class> 214 <async-supported>true</async-supported> 215 </servlet> 216 217 <servlet-mapping> 218 <servlet-name>Echo</servlet-name> 219 <url-pattern>/echo</url-pattern> 220 </servlet-mapping> 221 222</web-app> 223}}} 224 225{{{flowersinthesand.example.EchoServlet}}} 226{{{ 227package flowersinthesand.example; 228 229import java.io.IOException; 230import java.io.PrintWriter; 231import java.util.Map; 232import java.util.UUID; 233import java.util.concurrent.ConcurrentHashMap; 234 235import javax.servlet.AsyncContext; 236import javax.servlet.AsyncEvent; 237import javax.servlet.AsyncListener; 238import javax.servlet.ServletException; 239import javax.servlet.http.HttpServletRequest; 240import javax.servlet.http.HttpServletResponse; 241 242import org.eclipse.jetty.util.UrlEncoded; 243import org.eclipse.jetty.websocket.WebSocket; 244import org.eclipse.jetty.websocket.WebSocketServlet; 245 246// WebSocketServlet extending HttpServlet is base class 247public class EchoServlet extends WebSocketServlet { 248 249 private static final long serialVersionUID = -8823775068689773674L; 250 251 private Map<String, AsyncContext> asyncContexts = new ConcurrentHashMap<String, AsyncContext>(); 252 253 @Override 254 protected void doGet(HttpServletRequest request, HttpServletResponse response) 255 throws ServletException, IOException { 256 response.setCharacterEncoding("utf-8"); 257 response.setContentType("text/plain"); 258 response.setHeader("Access-Control-Allow-Origin", "*"); 259 260 PrintWriter writer = response.getWriter(); 261 262 final String id = UUID.randomUUID().toString(); 263 writer.print(id); 264 writer.print(';'); 265 266 for (int i = 0; i < 1024; i++) { 267 writer.print(' '); 268 } 269 writer.print(';'); 270 writer.flush(); 271 272 final AsyncContext ac = request.startAsync(); 273 ac.setTimeout(5 * 60 * 1000); 274 ac.addListener(new AsyncListener() { 275 public void onComplete(AsyncEvent event) throws IOException { 276 asyncContexts.remove(id); 277 } 278 279 public void onTimeout(AsyncEvent event) throws IOException { 280 asyncContexts.remove(id); 281 } 282 283 public void onError(AsyncEvent event) throws IOException { 284 asyncContexts.remove(id); 285 } 286 287 public void onStartAsync(AsyncEvent event) throws IOException { 288 289 } 290 }); 291 asyncContexts.put(id, ac); 292 } 293 294 @Override 295 protected void doPost(HttpServletRequest request, HttpServletResponse response) 296 throws ServletException, IOException { 297 request.setCharacterEncoding("utf-8"); 298 299 AsyncContext ac = asyncContexts.get(request.getParameter("metadata.id")); 300 if (ac == null) { 301 return; 302 } 303 304 if ("close".equals(request.getParameter("metadata.type"))) { 305 ac.complete(); 306 return; 307 } 308 309 String message = request.getParameter("message"); 310 PrintWriter writer = ac.getResponse().getWriter(); 311 312 writer.print(message.length() + ";" + message + ";"); 313 writer.flush(); 314 } 315 316 // Handles WebSocket connection 317 @Override 318 public WebSocket doWebSocketConnect(HttpServletRequest request, String protocol) { 319 320 // WebSocket for receiving text messages 321 return new WebSocket.OnTextMessage() { 322 323 Connection connection; 324 325 @Override 326 public void onOpen(Connection connection) { 327 this.connection = connection; 328 } 329 330 @Override 331 public void onClose(int closeCode, String message) { 332 333 } 334 335 @Override 336 public void onMessage(String data) { 337 // Decodes query string 338 UrlEncoded parameters = new UrlEncoded(data); 339 try { 340 connection.sendMessage(parameters.getString("message")); 341 } catch (IOException e) { 342 throw new RuntimeException(e); 343 } 344 } 345 346 }; 347 } 348 349} 350}}}