Understanding WebSockets versus Ajax/REST for Java EE Developers
From Resin 4.0 Wiki
(9 intermediate revisions by one user not shown) | |||
Line 1: | Line 1: | ||
{{Cookbook}} {{WebServer}} {{Development}} | {{Cookbook}} {{WebServer}} {{Development}} | ||
− | =Understanding WebSockets versus Ajax: Tutorial for Java Developers= | + | =Understanding WebSockets versus Ajax/REST: Tutorial for Java Developers= |
− | There has been a lot of discussion lately about WebSockets. | + | There has been a lot of discussion lately about WebSockets. WebSocket client API is part of HTML 5. |
− | + | WebSocket wire protocol that handles the low-level handshaking, framing, and negotiation was just | |
− | + | released in 2012. WebSocket has been a multi year work in progress that just completed. | |
− | + | At this point (March 2012), only Chrome and FireFox support the official WebSocket wire protocol. | |
+ | Safari supports it in the nightly build so it will likely be supported in the next version of | ||
+ | Safari, and Internet Explorer will support it in Explorer 10. JSR 356 was recently formed to | ||
+ | define a standard API for creating WebSocket applications. Also even if you have both a client | ||
+ | that works with WebSocket and a server which can handle WebSockets, you still need to be able | ||
+ | to work through firewalls. Most modern firewall software handles WebSockets, but not all. The | ||
+ | most reliable way to handle WebSockets is through a TLS connection (sometimes incorrectly known | ||
+ | as SSL). | ||
− | + | You can use WebSocket from a browser like you can Ajax/REST. But when should use WebSockets and when | |
+ | should you use Ajax/REST? Also, can you use WebSocket from other clients? | ||
− | + | This tutorial is going to try to answer these questions. Its aim is to be a | |
+ | compare, contrast and learn tutorial. In order to convey the information enough code is added to make | ||
+ | this tutorial go beyond the pointy hair boss description. To do this, we slowly build a chat client and server. | ||
− | + | Let's do a quick code comparison of the JavaScript and Java involved in doing Ajax and WebSockets | |
− | + | to start off the discussion. To developers sometimes code examples are very demystifying. | |
− | + | This tutorial covers both the JavaScript client side and Java server side so it is complete. | |
− | + | The first part of this tutorial, we build a simple chat prototype. In the second part of this tutorial, | |
+ | we build a scalable chat room which could handle thousands of users. | ||
+ | |||
+ | ==Ajax simple example client (JavaScript/Browser/HTML 5) and server (Resin Java Application Server)== | ||
+ | |||
+ | The following code listing is a simple JavaScript Ajax, HTML 5 example that sends a | ||
+ | "Hello Ajax World?" message to our server. | ||
+ | |||
+ | ===Sample Ajax "Chat" with RAW JavaScript/HTML 5=== | ||
+ | <code> <pre> | ||
+ | var ajax = null; | ||
+ | |||
+ | function sendChatMessageAjax() { //(2) | ||
+ | if (ajax == null) { | ||
+ | ajax = new XMLHttpRequest(); //(1) | ||
+ | } | ||
+ | |||
if (ajax.readyState == 4 || ajax.readyState == 0) { | if (ajax.readyState == 4 || ajax.readyState == 0) { | ||
− | document.getElementById("span_result").innerHTML = "SENDING AJAX MESSAGE"; | + | document.getElementById("span_result").innerHTML = "SENDING AJAX MESSAGE"; |
ajax.open("POST", 'chat', true); | ajax.open("POST", 'chat', true); | ||
− | ajax.onreadystatechange = handleChatMessageAjaxResponse; //3 | + | ajax.onreadystatechange = handleChatMessageAjaxResponse; //(3) |
ajax.send("Hello Ajax World?"); | ajax.send("Hello Ajax World?"); | ||
} | } | ||
} | } | ||
− | function handleChatMessageAjaxResponse() { | + | function handleChatMessageAjaxResponse() { |
if (ajax.readyState == 4) { | if (ajax.readyState == 4) { | ||
document.getElementById('span_result').innerHTML = ajax.responseText; | document.getElementById('span_result').innerHTML = ajax.responseText; | ||
Line 51: | Line 77: | ||
</pre></code> | </pre></code> | ||
− | Now typically, you don't use XMLHttpRequest directly, instead you use jQuery or Prototype or any number of other JavaScript frameworks. | + | Now typically, you don't use XMLHttpRequest directly, instead you use jQuery or Prototype or |
− | But to ease the explanation, and to aid in comparison to WebSocket, let's start with raw JavaScript (later tutorials will use raw JavaScript, | + | any number of other JavaScript frameworks. But to ease the explanation, and to aid in comparison |
+ | to WebSocket, let's start with raw JavaScript (later tutorials will use raw JavaScript, and | ||
+ | jQuery). | ||
− | Quick Review of Ajax: <code>sendChatMessageAjax</code> (2) is JavaScript function that uses an instance of <code>XMLHttpRequest</code> called <code>ajax</code> (1) to send an HTTP POST request back to the server. Since this is JavaScript, and you don't want to block the user and JavaScript does not support threads, then you must register a callback called <code>handleChatMessageAjaxResponse</code> with the <code>ajax.onreadystatechange (XMLHttpRequest)</code> (3). | + | Quick Review of Ajax: <code>sendChatMessageAjax</code> (2) is JavaScript function that uses an |
+ | instance of <code>XMLHttpRequest</code> called <code>ajax</code> (1) to send an HTTP POST request | ||
+ | back to the server. Since this is JavaScript, and you don't want to block the user and JavaScript | ||
+ | does not support threads, then you must register a callback called | ||
+ | <code>handleChatMessageAjaxResponse</code> with the | ||
+ | <code>ajax.onreadystatechange (XMLHttpRequest)</code> (3). | ||
− | + | ==Servlet that handles Ajax call== | |
− | ==Sample Ajax "Chat" server with a RAW Java Servlet== | + | |
+ | Now let's cover the Java side of the house. Again, there are many Java frameworks that add layers | ||
+ | between the Java backend and the HTML/JavaScript rendering to handle Ajax nicely. But for this | ||
+ | discussion, let's start with the simplest thing that will work, and in Java that is a Java | ||
+ | HttpServlet as follows: | ||
+ | |||
+ | ===Sample Ajax "Chat" server with a RAW Java Servlet (Resin Java Application Server / Java EE)=== | ||
<code> <pre> | <code> <pre> | ||
package com.caucho.websocket.example; | package com.caucho.websocket.example; | ||
Line 84: | Line 123: | ||
System.out.println(text); //print out what the client sent | System.out.println(text); //print out what the client sent | ||
− | response.getWriter().print("Hello from Server-side | + | response.getWriter().print("Hello from Server-side Ajax : " +text); //3 |
} | } | ||
Line 90: | Line 129: | ||
</pre></code> | </pre></code> | ||
− | This is fairly basic. Read the data (1), convert data into a String (2), send the data back to the browser with "Hello from Server-side | + | This is fairly basic. Read the data (1), convert data into a String (2), send the data back to the |
+ | browser with "Hello from Server-side Ajax : " prepended to to it (3). | ||
==WebSocket simple example client and server== | ==WebSocket simple example client and server== | ||
− | Now lets compare the above client and server to a WebSocket equivalent. | + | Now lets compare the above client and server to a WebSocket equivalent. Efforts are made to keep |
+ | this JavaScript really simple and easy to understand so it is easy to compare against the previous | ||
+ | Ajax example. | ||
− | + | The following code listing is a simple JavaScript Ajax, HTML 5 example that sends a | |
+ | "Hello WebSocket World?" message to our server. | ||
− | + | The WebSocket example is a parallel as possible to the Ajax example. | |
− | + | ||
− | + | ===Sample WebSocket "Chat" server with a RAW Java Servlet (Resin Java Application Server / Java EE)=== | |
− | + | ||
− | + | ||
− | + | <code> <pre> | |
− | + | ||
− | + | ||
− | + | var socket = null; | |
− | + | ||
− | + | function sendChatMessageWebSocket() { // (2) | |
+ | if (socket == null) { | ||
+ | // (1) | ||
+ | socket = new WebSocket("ws://localhost:8080/web-socket-example/chat", "caucho-example-chat-protocol"); | ||
+ | socket.onmessage = handleChatMessageWebSocketResponse; //(3) | ||
+ | initSocket(); //Explained later | ||
+ | } | ||
+ | document.getElementById("span_result").innerHTML = "SENDING WEBSOCKET MESSAGE"; | ||
+ | socket.send("Hello WebSocket World?"); | ||
} | } | ||
− | + | ||
− | + | function handleChatMessageWebSocketResponse(msg) { | |
− | + | document.getElementById("span_result").innerHTML = msg.data; | |
+ | } | ||
+ | ... | ||
+ | </pre></code> | ||
+ | |||
+ | |||
+ | Quick Run down of the WebSocket code: <code>sendChatMessageWebSocket</code> (2) is JavaScript | ||
+ | function that uses an instance of <code>WebSocket</code> called <code>socket</code> (1) to | ||
+ | start a WebSocket connection (HTTP upgrade) with the server. Since this is JavaScript, and you don't | ||
+ | want to block the user and JavaScript does not support threads, then you must register a callback | ||
+ | called <code>handleChatMessageWebSocketResponse</code> with the | ||
+ | <code>socket.onmessage (WebSocket)</code> (3). | ||
+ | |||
+ | Ok so they look very similar. Again, I am going to start with the similarities. | ||
+ | The differences are to come. | ||
+ | |||
+ | To handle the above you need a <code>WebSocketListener</code> handler as follows: | ||
+ | |||
+ | |||
+ | <code> <pre> | ||
+ | |||
+ | public class ChatWebSocketListener implements WebSocketListener { | ||
+ | @Override | ||
+ | public void onReadText(WebSocketContext context, Reader reader) | ||
+ | throws IOException { | ||
+ | System.out.println("___________ onReadText MEHTOD ________________ " ); | ||
+ | |||
+ | char [] data = new char[4096]; | ||
+ | |||
+ | reader.read(data); // (1) | ||
+ | |||
+ | String text = new String(data); // (2) | ||
+ | System.out.println(text); | ||
+ | |||
+ | PrintWriter out = context.startTextMessage(); // (??? NEW 4) | ||
+ | out.print("Hello from Server-side WebSocket : " + text); // (3) | ||
+ | out.close(); //You have to close to send the message. // (??? NEW 5) | ||
+ | System.out.println("___________ onReadText MEHTOD END END END________________ " ); | ||
+ | |||
+ | } | ||
+ | ... | ||
+ | </pre></code> | ||
+ | |||
+ | This is fairly basic. Read the data (1), convert data into a String (2), send the data back to the | ||
+ | browser with "Hello from Server-side Ajax : " prepended to it (3). | ||
+ | |||
+ | |||
+ | At this point, these are nearly identical. You can use WebSockets similar to how you should use | ||
+ | Ajax. No real learning curve for simple cases. | ||
+ | |||
+ | ==Some noticeable differences between WebSockets, and Ajax examples== | ||
+ | |||
+ | |||
+ | The first difference between this and the Servlet version is that we are using a | ||
+ | <code>WebSocketListener</code>. The <code>WebSocketListener</code> is a Resin class as Java EE 6 | ||
+ | does not have WebSocket support. Java EE 6 predates Websocket. The <code>WebSocketListener</code> | ||
+ | looks fairly similar to the Servlet API as much as possible by design. | ||
+ | |||
+ | Note that the current plan for Java EE 7 is to include WebSocket support. | ||
+ | Caucho Technology is and has been involved in several Java EE JSRs, and was heavily involved in the | ||
+ | IETF WebSocket draft. | ||
+ | |||
+ | From a programming perspective, so far, there is no real difference between the WebSocket version | ||
+ | and the Ajax/Servlet version. There is a little bit of handshaking that the Servlet has to do which | ||
+ | we will cover later, but essentially if this is all you wanted, then WebSocket looks a lot like | ||
+ | Ajax. Now since we are developing a chat example, eventually we will want to push messages from other | ||
+ | people who are in the chat session. This is where WebSockets is going to shine. | ||
+ | |||
+ | ==What is WebSockets again?== | ||
+ | |||
+ | From a web developers viewpoint, WebSocket is a new browser feature for HTML 5 browsers. | ||
+ | This new feature enables richer user interactions. Both the browser and the server can send | ||
+ | asynchronous messages over a single TCP socket, without doing less scalable hacks like long polling | ||
+ | or comet. | ||
+ | |||
+ | The communication starts out like HTTP and then upgrades after a | ||
+ | HTTP handshake to bidirectional WebSockets. A WebSocket is a bidirectional message stream | ||
+ | between the client and the server. | ||
+ | |||
+ | While all modern browsers support some version of WebSocket as of March 2012 few application servers and web servers do. | ||
+ | In order for browsers to take advantage of WebSockets, you need to have an application server or web | ||
+ | server that can handle WebSockets. | ||
+ | |||
+ | ==Detour Streaming API versus a byte[]/String API versus a WebSocket Frame API== | ||
+ | |||
+ | |||
+ | There are several Java WebSocket implementations out in the wild. Resin WebSocket support is the most | ||
+ | mature and gets used by more high traffic sites. If you are not familiar with Resin Server, it is a scalable, | ||
+ | fast, mature Java EE certified application/web server. Its speed is faster than NginX and Apache HTTPD, | ||
+ | and it is more scalable. | ||
+ | |||
+ | All of Resin's cloud/clustering communication works on top of WebSocket. | ||
+ | Resin's WebSocket support predates most implementations. If you are serious about WebSockets | ||
+ | and Java, then Resin Server is an obvious contender. Also you can try out the Open Source version | ||
+ | for free. | ||
+ | |||
+ | Many Java WebSocket implementations do not allow stream access to WebSocket (<code>Reader</code>, | ||
+ | <code>Writer</code>, <code>InputStream</code>, <code>OutputStream</code>), and instead rely on | ||
+ | simple buffer constructs like <code>String</code> and <code>byte[]</code> or dump you | ||
+ | down into working directly with low level WebSocket frames (Frame API). Since WebSocket is a streaming, | ||
+ | framing wire protocol, naturally Resin Server supports streams. It is easy to for Java developers to | ||
+ | go from streams to <code>byte[]</code> and <code>String</code> as shown in the above examples. | ||
+ | Using <code>byte[]</code> and <code>String</code> are probably ok for some department level or | ||
+ | company level applications, but if for anything else you really need to use streams to maximize the | ||
+ | throughput and minimize contention. | ||
+ | |||
+ | Regarding a Frame oriented API, when you program with TCP/IP you never have an application developer | ||
+ | API that exposes TCP/IP packets bit mask fields, in the same way the WebSocket Frame is the wrong | ||
+ | level of abstraction for most developers. Resin's API is the right level of abstraction, i.e., streams like the Servlet API. | ||
+ | That said, we are always looking for feedback. | ||
+ | |||
+ | Now that we have the background in the why of the API, let's discuss how the streaming works. | ||
+ | |||
+ | ==WebSocket Streaming, Frames and Messaging== | ||
+ | |||
+ | WebSocket is unlike Ajax/HTTP in that the WebSocket connection stays open and it is bidirectional. | ||
+ | This is perfect for a chat application or a near real time stock price app, etc. | ||
+ | |||
+ | WebSockets have two types of messages, binary (byte[]), and text (String). If your message is bigger | ||
+ | than a single Frame then it gets sent as chunks in multiple frames. For the sake of argument let's | ||
+ | say that a frame holds 1K bytes. If you are sending a JSON payload to the browser that is 4K then | ||
+ | you are sending 4 Frames. (Actual framing varies, this are example sizes for discussion.) | ||
+ | |||
+ | To begin a message you need to send a stream of WebSocket frames. To start the message you call | ||
+ | <code>context.startTextMessage()</code> (NEW 4). If your message does not fit into one frame, | ||
+ | then a another frame is created and marked that it continues the previous frame in the series using a | ||
+ | "continues" flag. In WebSockets the final frame in a message is marked with a "finished" flag. | ||
+ | This final frame is sent when you call close on the stream. This final frame says that you are done | ||
+ | sending the current binary or text message. The <code>out.close</code> (5) is your way to tell Resin's | ||
+ | WebSocket implementation that the message is done so go ahead and mark the current frame as finished | ||
+ | and then send that as the last frame. On the client end of the wire, WebSockets client implementation | ||
+ | will see that the it got the last frame. The JavaScript WebSocket client will only call | ||
+ | <code>socket.onmessage (WebSocket)</code> after it receives the Frame marked final. HTML 5 has the | ||
+ | luxury of handling whole message at a time because efficiency is not a concern for scalability like | ||
+ | it is on the server-side of the house. | ||
+ | |||
+ | Get all of that? That is ok if you don't. Just remember to call <code>close</code>, and then Resin WebSockets will | ||
+ | complete sending the message just like you would write output to a Servlet or write to a file. | ||
+ | WebSocket frames are a nice fit with the Java IO Stream and Reader/Writer APIs. | ||
+ | |||
+ | Again the JavaScript API for WebSocket only deals with buffers like String and | ||
+ | byte[] (these are String and ArrayBuffer or Blob in JavaScript speak). WebSocket is | ||
+ | a generally purpose framing/wire protocol so expect a lot of other uses outside of HTML 5. | ||
+ | Many other protocols spend a lot of time and effort | ||
+ | creating a framing protocol (AMQP, IIOP, RMI-JRMP, etc.). If you are developing a new protocol that | ||
+ | needs framing, you can just build your new protocol on top of WebSocket. Consider framing solved. | ||
+ | |||
+ | WebSockets are extensible, and you could build other protocol on top of it. You could for example | ||
+ | build a version of IIOP on top of WebSockets. Expect extensions for flow control, multiplexing, | ||
+ | and compression. | ||
+ | |||
+ | ==Protocol negotiation== | ||
+ | |||
+ | HTTP upgrade was added to the HTTP specification to support changing to new versions of HTTP more flawlessly. | ||
+ | |||
+ | To use WebSockets which is a different wire protocol then HTTP, the client must do an HTTP upgrade, and | ||
+ | tell the server that it supports WebSockets. The Browser HTML 5 client sends a special HTTP GET that has | ||
+ | special headers. If the server supports WebSocket it sends an acknowledgement and then the conversation begins. | ||
+ | |||
+ | To register our <code>ChatWebSocketListener</code> you have to create a Servlet that handles this special | ||
+ | request as follows: | ||
+ | |||
+ | |||
+ | <pre><code> | ||
+ | protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { | ||
+ | |||
+ | String protocol = request.getHeader("Sec-WebSocket-Protocol"); //(1) | ||
+ | |||
+ | System.out.println("___________ doGet MEHTOD ________________ " + protocol); | ||
+ | |||
+ | |||
+ | if ("caucho-example-chat-protocol".equals(protocol)) { | ||
+ | |||
+ | System.out.println("___________ WEBSOCKET Handshake ________________ " + protocol); | ||
+ | |||
+ | WebSocketListener listener = new ChatWebSocketListener(); //(2) | ||
+ | |||
+ | response.setHeader("Sec-WebSocket-Protocol", "caucho-example-chat-protocol"); //(3) | ||
+ | WebSocketServletRequest wsRequest = (WebSocketServletRequest) request; | ||
+ | wsRequest.startWebSocket(listener); //(4) | ||
+ | |||
+ | } | ||
+ | |||
+ | |||
+ | } | ||
+ | </code></pre> | ||
+ | |||
+ | |||
+ | The above checks the subprotocol that the HTML 5 browser client expects via the <code>Sec-WebSocket-Protocol</code> | ||
+ | HTTP header (1). Then it creates and instance of the <code>ChatWebSocketListener</code> that we | ||
+ | covered earlier (2) and registers that with the <code>WebSocketServletRequest</code> (4) via the | ||
+ | <code>startWebSocket</code> method. | ||
+ | |||
+ | The client, HTML 5 Browser client, can request that the server use a specific subprotocol by including the | ||
+ | Sec-WebSocket-Protocol field in its handshake. The server responds with a comma delimited list of | ||
+ | of subprotocols. A subprotocol is basically the version of the wire format. It is an indication | ||
+ | of what kinds of marshaling you are doing. Let's say you worked at Caucho Technology Inc. Your | ||
+ | subprotocol could be called "chat.example.com". Subprotocols can be versioned in, e.g., "chat.example.com" | ||
+ | versus "v2.chat.example.com" are two different subprotocols. | ||
+ | |||
+ | |||
+ | The HTTP header handshake from the browser could look something like this: | ||
+ | |||
+ | <pre><code> | ||
+ | GET /chat HTTP/1.1 | ||
+ | Host: server.caucho.com | ||
+ | Upgrade: websocket | ||
+ | Connection: Upgrade | ||
+ | Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ== | ||
+ | Origin: http://example.com | ||
+ | Sec-WebSocket-Protocol: caucho-example-chat-protocol, v2.caucho-example-chat-protocol | ||
+ | Sec-WebSocket-Version: 13 | ||
+ | </code></pre> | ||
+ | |||
+ | The part of the handshake from the server might look like this: | ||
+ | |||
+ | <pre><code> | ||
+ | |||
+ | HTTP/1.1 101 Switching Protocols | ||
+ | Upgrade: websocket | ||
+ | Connection: Upgrade | ||
+ | Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo= | ||
+ | Sec-WebSocket-Protocol: caucho-example-chat-protocol | ||
+ | </code></pre> | ||
+ | |||
+ | |||
+ | |||
+ | |||
+ | |||
+ | ==Details about WebSocketContext and WebSocketListener== | ||
+ | |||
+ | The <code>WebSocketListener</code> is the main area of focus for the server-side implementation. | ||
+ | It is similar in concept to a Java Servlet at first blush. | ||
+ | Unlike a Servlet, a WebSocketListener is a single-threaded handler for events and messages from the client. | ||
+ | |||
+ | The <code>onReadText</code> of <code>WebSocketListener</code> method will only get called by one thread | ||
+ | at a time whilst a <code>HttpServlet.doGet</code> method can be called by many threads. | ||
+ | |||
+ | The <code>Reader</code>, <code>PrintWriter</code> and <code>WebSocketContext</code> are not thread | ||
+ | safe and should only be accessed by one thread at a time. It is important that you only access | ||
+ | these from one thread at a time. This is similar to the normal Servlet programming as it also works with streams and reader/writers | ||
+ | which can only be accessed by one thread at a time. | ||
+ | |||
+ | |||
+ | The <code>onReadText</code> should not do anything that is going to block if you are trying to write | ||
+ | a scalable application. Blocking might be okay for department level or company level application. | ||
+ | For applications that support higher traffic and need higher throughput blocking on an | ||
+ | <code>onReadText</code> is not ok. Higher scaling application would need to use some sort of | ||
+ | queuing mechanisms, we will cover this in a later tutorial. | ||
+ | |||
+ | The big take away that I want you to get from this discussion is this: although the programming model | ||
+ | may look the same from Ajax/Servlet to WebSocket, it is very different. Ajax/REST is request/response. | ||
+ | WebSocket is messaging. | ||
+ | |||
+ | With WebSocket the connection stays open. It is expected that the server is calling the client at will. | ||
+ | It is common in WebSockets for the reading and writing to happen in different threads. | ||
+ | |||
+ | |||
+ | |||
+ | ==Simplest possible example that shows bidirectional communication over WebSockets== | ||
+ | |||
+ | As was mentioned earlier, we are going to cover building a full HTML 5 chat client, but to whet your appetite | ||
+ | without going into crazy complexity, let's change the last example from a traditional Request/Response | ||
+ | to a full blown bidirectional connection. The HTML 5 client is sending messages on one end, while | ||
+ | the server is at the same time sending messages to the HTML 5 client. | ||
+ | |||
+ | In this example, we ask the server for a thread to fire off a chat message sender. The message sender | ||
+ | gets messages from a queue. It echoes the messages back. If it does not hear from the client in five | ||
+ | seconds, it starts nagging it with a "Are you there?" message. To do this, you do the following. | ||
+ | |||
+ | (THE EXAMPLE BELOW IS UNDER REVIEW BY PEERS. It may change.) | ||
+ | |||
+ | |||
+ | You change the client to connect as soon as the page loads as follows (full code listings will be at end of tutorial): | ||
+ | |||
+ | <pre><code> | ||
+ | function initSocket() { | ||
+ | if (socket == null) { | ||
+ | socket = new WebSocket("ws://localhost:8080/web-socket-example/chat", "caucho-example-chat-protocol"); | ||
+ | socket.onmessage = handleChatMessageWebSocketResponse; | ||
+ | } | ||
+ | |||
+ | socket.onerror = function(msg) { | ||
+ | document.getElementById("error_result").innerHTML = "ERROR:" + msg; | ||
+ | } | ||
+ | |||
+ | socket.onopen = function() { | ||
+ | document.getElementById("span_result").innerHTML = "Socket Status: " | ||
+ | + socket.readyState + " (open)"; | ||
+ | } | ||
+ | socket.onclose = function() { | ||
+ | document.getElementById("span_result").innerHTML = "Socket Status: " | ||
+ | + socket.readyState + " (Closed)"; | ||
+ | |||
+ | } | ||
+ | } | ||
+ | |||
+ | initSocket(); | ||
+ | </code></pre> | ||
+ | |||
+ | |||
+ | You are going to handle the <code>WebSocketListener.onStart</code> method to start up this new | ||
+ | message deliverer. <code>WebSocketListener.onStart</code> needs to schedule a task with the servers executor | ||
+ | (java.util.concurrent.Executor). The task will listen for items that the <code>onReadText</code> | ||
+ | puts on the queue. | ||
+ | |||
+ | Then change the <code>WebSocketListener</code> to create a <code>BlockingQueue</code> (1) | ||
+ | and <code>@Inject</code> a <code>ThreadPoolExecutor</code> (2). | ||
+ | |||
+ | <pre><code> | ||
+ | public class ChatWebSocketListener implements WebSocketListener { | ||
+ | |||
+ | |||
+ | volatile boolean close; | ||
+ | |||
+ | BlockingQueue<String> readQueue; | ||
+ | |||
+ | @Inject ThreadPoolExecutor executor; | ||
+ | |||
+ | |||
+ | @Override | ||
+ | public void onReadText(WebSocketContext context, Reader reader) | ||
+ | throws IOException { | ||
+ | System.out.println("___________ onReadText MEHTOD ________________ " ); | ||
+ | |||
+ | char [] data = new char[4096]; | ||
+ | |||
+ | reader.read(data); | ||
+ | |||
+ | String text = new String(data); | ||
+ | |||
+ | try { | ||
+ | readQueue.put(text); // (3) | ||
+ | } catch (InterruptedException e) { | ||
+ | } | ||
+ | ... | ||
+ | </code></pre> | ||
+ | |||
+ | Notice that the <code>onReadText</code> no longer handles the writing of message directly. Instead | ||
+ | it adds the text message to the queue (3). | ||
+ | |||
+ | The <code>onStart</code> method gets called as soon as WebSocket connection is established and ready | ||
+ | to receive messages (from either side). Change the <code>onStart</code> method to create the queue, then | ||
+ | execute a task (4) with the executor that handles the echoing of messages and the sending of our | ||
+ | nagging reminders. | ||
+ | |||
+ | |||
+ | |||
+ | <pre><code> | ||
+ | public class ChatWebSocketListener implements WebSocketListener { | ||
+ | |||
+ | @Override | ||
+ | public void onStart(final WebSocketContext context) throws IOException { | ||
+ | System.out.println("ON start ***********************************************"); | ||
+ | |||
+ | readQueue = new ArrayBlockingQueue<String>(2000); | ||
+ | |||
+ | ... | ||
+ | executor.execute(new Runnable() { // (4) | ||
+ | |||
+ | |||
+ | </code></pre> | ||
+ | |||
+ | The task (anonymous inner class instance of Runnable) checks to see if the connection | ||
+ | has been closed in a forever loop (6). The close flag is a volatile boolean | ||
+ | that is set by the <code>ChatWebSocketListener.onDisconnect</code> | ||
+ | which gets called when the connection is shut down. Each iteration of the loop, the task checks | ||
+ | to see if there are any more messages on the queue (7). If there is a message, then it echoes that message | ||
+ | back using the (context) <code>WebSocketContext</code> (8). Just to proove that we can send messages | ||
+ | without getting told to by the client, every five seconds we send "I have not heard from you in a while ", | ||
+ | "Are you stil there?", and "WAKE UP!!!!!!!!". | ||
+ | |||
+ | |||
+ | |||
+ | <pre><code> | ||
+ | new Runnable() { | ||
+ | public void run() { | ||
+ | PrintWriter out=null; | ||
+ | long oldTime = System.currentTimeMillis(); | ||
+ | long newTime, deltaTime; | ||
+ | |||
+ | |||
+ | while (true) { | ||
+ | if (close) break; // (6) | ||
+ | try { | ||
+ | |||
+ | |||
+ | String message = readQueue.poll(500, TimeUnit.MILLISECONDS); // (7) | ||
+ | if (message!=null) { | ||
+ | out = context.startTextMessage(); // (8) | ||
+ | out.print("Hello from Server-side WebSocket : " + message); | ||
+ | out.close(); //You have to close to send the message. | ||
+ | oldTime = System.currentTimeMillis(); | ||
+ | } | ||
+ | |||
+ | newTime = System.currentTimeMillis(); | ||
+ | |||
+ | deltaTime = newTime - oldTime; | ||
+ | if (deltaTime>5000) { //if elapsed time is greater than 5000 seconds | ||
+ | |||
+ | //Start nagging! | ||
+ | out = context.startTextMessage(); | ||
+ | out.print("I have not heard from you in a while "); | ||
+ | out.close(); | ||
+ | |||
+ | Thread.sleep(1000); | ||
+ | |||
+ | out = context.startTextMessage(); | ||
+ | out.print("Are you stil there?"); | ||
+ | out.close(); | ||
+ | |||
+ | Thread.sleep(1000); | ||
+ | out = context.startTextMessage(); | ||
+ | out.print("WAKE UP!!!!!!!!"); | ||
+ | out.close(); | ||
+ | |||
+ | oldTime = System.currentTimeMillis(); | ||
+ | |||
+ | } | ||
+ | |||
+ | </code></pre> | ||
+ | |||
+ | |||
+ | |||
+ | IMPORTANT: Note that only the runnable task's thread accesses the <code>WebSocketContext</code>. | ||
+ | Only one thread at a time can access a <code>WebSocketContext</code> so also note that the <code>onReadText</code> no longer accesses | ||
+ | the <code>WebSocketContext</code> like it did in the first example. <code>WebSocketContext</code> is not thread safe. | ||
+ | This is why only one thread can access it. In the first example the <code>onReadText</code> accessed it directly. | ||
+ | If it still did that, then this would be broke and we would have thread synchronization issues. If | ||
+ | the thread that was running <code>onReadText</code> and the thread that is running this task both wrote at | ||
+ | the same time, given the streaming nature of WebSockets and the need to sends frames in sequences, | ||
+ | bad things could happen. | ||
+ | |||
+ | Ok, we have introduced enough new concepts for WebSockets. I hope you can see the value in it, and understand | ||
+ | some key differences between Ajax and WebSockets as well as some clear similarities. Going through | ||
+ | the process of writing these tutorials has given us some insight into how to make the WebSocket API more | ||
+ | complete. We plan on updating these tutorials as we improve the API. In addition, the next installment | ||
+ | in this series is going to cover building a multi user chat system so stay tuned. | ||
+ | |||
+ | |||
+ | Now as promised here are the complete code examples. | ||
+ | |||
+ | ===Simple Ajax/WebSocket HTML 5 client for driving code=== | ||
+ | |||
+ | <pre><code> | ||
+ | <html> | ||
+ | <head> | ||
+ | <title>The Hello World of AJAX and WebSocket</title> | ||
+ | <script language="JavaScript" type="text/javascript"> | ||
+ | |||
+ | var ajax = null; | ||
+ | |||
+ | function sendChatMessageAjax() { | ||
+ | if (ajax == null) { | ||
+ | ajax = new XMLHttpRequest(); | ||
+ | } | ||
+ | |||
+ | if (ajax.readyState == 4 || ajax.readyState == 0) { | ||
+ | document.getElementById("span_result").innerHTML = "SENDING AJAX MESSAGE"; | ||
+ | |||
+ | ajax.open("POST", 'chat', true); | ||
+ | ajax.onreadystatechange = handleChatMessageAjaxResponse; | ||
+ | ajax.send("Hello Ajax World?"); | ||
+ | } | ||
} | } | ||
+ | function handleChatMessageAjaxResponse() { | ||
+ | if (ajax.readyState == 4) { | ||
+ | document.getElementById('span_result').innerHTML = ajax.responseText; | ||
+ | } | ||
+ | } | ||
+ | </script> | ||
− | + | <script language="javascript" type="text/javascript"> | |
− | + | /* http://dev.w3.org/html5/websockets/ */ | |
− | + | ||
+ | var socket = null; | ||
+ | |||
+ | function sendChatMessageWebSocket() { | ||
+ | document.getElementById("span_result").innerHTML = "SENDING WEBSOCKET MESSAGE"; | ||
+ | socket.send("Hello WebSocket World?"); | ||
+ | } | ||
+ | |||
+ | function handleChatMessageWebSocketResponse(msg) { | ||
+ | document.getElementById("span_result").innerHTML = msg.data; | ||
} | } | ||
+ | |||
+ | function initSocket() { | ||
+ | if (socket == null) { | ||
+ | socket = new WebSocket("ws://localhost:8080/web-socket-example/chat", "caucho-example-chat-protocol"); | ||
+ | socket.onmessage = handleChatMessageWebSocketResponse; | ||
+ | } | ||
+ | |||
+ | socket.onerror = function(msg) { | ||
+ | document.getElementById("error_result").innerHTML = "ERROR:" + msg; | ||
+ | } | ||
+ | |||
+ | socket.onopen = function() { | ||
+ | document.getElementById("span_result").innerHTML = "Socket Status: " | ||
+ | + socket.readyState + " (open)"; | ||
+ | } | ||
+ | socket.onclose = function() { | ||
+ | document.getElementById("span_result").innerHTML = "Socket Status: " | ||
+ | + socket.readyState + " (Closed)"; | ||
+ | |||
+ | } | ||
+ | } | ||
+ | |||
+ | initSocket(); | ||
+ | |||
+ | |||
function clearSend() { | function clearSend() { | ||
Line 130: | Line 679: | ||
} | } | ||
− | </pre></code> | + | </script> |
+ | </head> | ||
+ | <body> | ||
+ | 1st attempt | ||
+ | <br /> | ||
+ | <a href="javascript:sendChatMessageAjax();">Send Chat Message via | ||
+ | Ajax</a> | ||
+ | <br /> | ||
+ | <a href="javascript:sendChatMessageWebSocket();">Send Chat Message | ||
+ | via WebSocket</a> | ||
+ | <br /> | ||
+ | <a href="javascript:clearSend();">Clear send results</a> | ||
+ | <br /> | ||
+ | <span id="span_result"></span> | ||
+ | <span id="error_result"></span> | ||
+ | |||
+ | </body> | ||
+ | </html> | ||
+ | </code></pre> | ||
+ | |||
+ | |||
+ | ===Servlet that handles Ajax call and handles WebSocket upgrade protocol=== | ||
+ | |||
+ | <pre><code> | ||
+ | package com.caucho.websocket.example; | ||
+ | |||
+ | import java.io.IOException; | ||
+ | |||
+ | import javax.servlet.ServletException; | ||
+ | import javax.servlet.annotation.WebServlet; | ||
+ | import javax.servlet.http.HttpServlet; | ||
+ | import javax.servlet.http.HttpServletRequest; | ||
+ | import javax.servlet.http.HttpServletResponse; | ||
+ | import com.caucho.websocket.WebSocketServletRequest; | ||
+ | |||
+ | /** | ||
+ | * Servlet implementation class ChatServlet | ||
+ | */ | ||
+ | @WebServlet("/chat") | ||
+ | public class ChatServlet extends HttpServlet { | ||
+ | |||
+ | |||
+ | |||
+ | /** | ||
+ | * Handle Websocket handshake. | ||
+ | * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response) | ||
+ | */ | ||
+ | protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { | ||
+ | |||
+ | String protocol = request.getHeader("Sec-WebSocket-Protocol"); | ||
+ | |||
+ | System.out.println("___________ doGet MEHTOD ________________ " + protocol); | ||
+ | |||
+ | |||
+ | if ("caucho-example-chat-protocol".equals(protocol)) { | ||
+ | |||
+ | System.out.println("___________ WEBSOCKET Handshake ________________ " + protocol); | ||
+ | |||
+ | response.setHeader("Sec-WebSocket-Protocol", "caucho-example-chat-protocol"); | ||
+ | WebSocketServletRequest wsRequest = (WebSocketServletRequest) request; | ||
+ | wsRequest.startWebSocket(new ChatWebSocketListener()); | ||
+ | |||
+ | } | ||
+ | |||
+ | |||
+ | } | ||
+ | |||
+ | /** | ||
+ | * Handle ajax calls | ||
+ | * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response) | ||
+ | */ | ||
+ | protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { | ||
+ | System.out.println("___________ doPost MEHTOD ________________ " ); | ||
+ | |||
+ | |||
+ | char [] data = new char[4096]; | ||
+ | request.getReader().read(data); | ||
+ | String text = new String(data); | ||
+ | |||
+ | System.out.println(text); | ||
+ | |||
+ | response.getWriter().print("Hello from Server-side Ajax : "+text); | ||
+ | } | ||
+ | |||
+ | } | ||
+ | |||
+ | </code></pre> | ||
+ | |||
+ | |||
+ | ===WebSocket Handler for Chat example prototype=== | ||
+ | |||
+ | Notice we subclass AbstractWebSocketListener instead of implement WebSocketListener. | ||
+ | The AbstractWebSocketListener implements WebSocketListener so you can ignore the | ||
+ | callback handler that you do not care about. | ||
+ | |||
+ | <pre> | ||
+ | package com.caucho.websocket.example; | ||
+ | |||
+ | import java.io.IOException; | ||
+ | import java.io.InputStream; | ||
+ | import java.io.PrintWriter; | ||
+ | import java.io.Reader; | ||
+ | import java.nio.charset.Charset; | ||
+ | import java.util.concurrent.ArrayBlockingQueue; | ||
+ | import java.util.concurrent.BlockingQueue; | ||
+ | import java.util.concurrent.ThreadPoolExecutor; | ||
+ | import java.util.concurrent.TimeUnit; | ||
+ | import com.caucho.websocket.WebSocketContext; | ||
+ | import com.caucho.websocket.AbstractWebSocketListener; | ||
+ | |||
+ | public class ChatWebSocketListener extends AbstractWebSocketListener { | ||
+ | |||
+ | |||
+ | volatile boolean close; | ||
+ | |||
+ | BlockingQueue<String> readQueue; | ||
+ | |||
+ | ThreadPoolExecutor executor; | ||
+ | |||
+ | |||
+ | public ChatWebSocketListener(ThreadPoolExecutor executor) { | ||
+ | this.executor = executor; | ||
+ | } | ||
+ | @Override | ||
+ | public void onReadText(WebSocketContext context, Reader reader) | ||
+ | throws IOException { | ||
+ | System.out.println("___________ onReadText MEHTOD ________________ " ); | ||
+ | |||
+ | char [] data = new char[4096]; | ||
+ | |||
+ | reader.read(data); | ||
+ | |||
+ | String text = new String(data); | ||
+ | |||
+ | try { | ||
+ | readQueue.put(text); | ||
+ | } catch (InterruptedException e) { | ||
+ | } | ||
+ | |||
+ | System.out.println("******** ***** *****" + text); | ||
+ | |||
+ | System.out.println("___________ onReadText MEHTOD END END END________________ " ); | ||
+ | |||
+ | } | ||
+ | |||
+ | |||
+ | @Override | ||
+ | public void onReadBinary(WebSocketContext context, InputStream input) | ||
+ | throws IOException { | ||
+ | byte[] data = new byte[4096]; | ||
+ | input.read(data); | ||
+ | |||
+ | String text = new String(data, Charset.forName("UTF8")); | ||
+ | |||
+ | System.out.println("***************** ::" + text); | ||
+ | |||
+ | } | ||
+ | |||
+ | |||
+ | @Override | ||
+ | public void onDisconnect(WebSocketContext context) throws IOException { | ||
+ | System.out.println("ON DISCONNECT ***********************************************"); | ||
+ | close=true; | ||
+ | |||
+ | } | ||
+ | |||
+ | |||
+ | @Override | ||
+ | public void onStart(final WebSocketContext context) throws IOException { | ||
+ | System.out.println("ON start ***********************************************"); | ||
+ | |||
+ | readQueue = new ArrayBlockingQueue<String>(2000); | ||
+ | |||
+ | if (executor == null) { | ||
+ | this.executor = new ThreadPoolExecutor(1, 1, | ||
+ | 1, TimeUnit.SECONDS, | ||
+ | new ArrayBlockingQueue<Runnable>(1)); | ||
+ | } | ||
+ | |||
+ | executor.execute(new Runnable() { | ||
+ | |||
+ | @Override | ||
+ | public void run() { | ||
+ | PrintWriter out=null; | ||
+ | long oldTime = System.currentTimeMillis(); | ||
+ | long newTime, deltaTime; | ||
+ | |||
+ | |||
+ | while (true) { | ||
+ | if (close) break; | ||
+ | try { | ||
+ | |||
+ | |||
+ | String message = readQueue.poll(500, TimeUnit.MILLISECONDS); | ||
+ | if (message!=null) { | ||
+ | out = context.startTextMessage(); | ||
+ | out.print("Hello from Server-side WebSocket : " + message); | ||
+ | out.close(); //You have to close to send the message. | ||
+ | oldTime = System.currentTimeMillis(); | ||
+ | } | ||
+ | |||
+ | newTime = System.currentTimeMillis(); | ||
+ | |||
+ | deltaTime = newTime - oldTime; | ||
+ | if (deltaTime>5000) { //if elapsed time is greater than 5000 seconds | ||
+ | |||
+ | //Start nagging! | ||
+ | out = context.startTextMessage(); | ||
+ | out.print("I have not heard from you in a while "); | ||
+ | out.close(); | ||
+ | |||
+ | Thread.sleep(1000); | ||
+ | |||
+ | out = context.startTextMessage(); | ||
+ | out.print("Are you stil there?"); | ||
+ | out.close(); | ||
+ | |||
+ | Thread.sleep(1000); | ||
+ | out = context.startTextMessage(); | ||
+ | out.print("WAKE UP!!!!!!!!"); | ||
+ | out.close(); | ||
+ | |||
+ | oldTime = System.currentTimeMillis(); | ||
+ | |||
+ | } | ||
+ | |||
+ | } catch (InterruptedException ex) { | ||
+ | break; //Our thread was interrupted, no worries, just leave the loop. | ||
+ | } catch (IOException e) { | ||
+ | break; //Some communication or connection issue, you could log this | ||
+ | } | ||
+ | } | ||
+ | |||
+ | } | ||
+ | }); | ||
+ | |||
+ | } | ||
+ | |||
+ | |||
+ | } | ||
+ | |||
+ | </pre> | ||
+ | |||
+ | |||
+ | The key take away from this tutorial is this: WebSockets is bidirectional messaging, Servlet/Ajax/REST is request/response. | ||
+ | |||
+ | In part two of this tutorial, we build a working chat client that you can use from multiple browsers. | ||
+ | |||
+ | Part II: [[Understanding WebSockets versus Ajax/REST for Java EE Developers Part II]] |
Latest revision as of 00:00, 26 March 2012
Understanding WebSockets versus Ajax/REST: Tutorial for Java Developers
There has been a lot of discussion lately about WebSockets. WebSocket client API is part of HTML 5. WebSocket wire protocol that handles the low-level handshaking, framing, and negotiation was just released in 2012. WebSocket has been a multi year work in progress that just completed.
At this point (March 2012), only Chrome and FireFox support the official WebSocket wire protocol. Safari supports it in the nightly build so it will likely be supported in the next version of Safari, and Internet Explorer will support it in Explorer 10. JSR 356 was recently formed to define a standard API for creating WebSocket applications. Also even if you have both a client that works with WebSocket and a server which can handle WebSockets, you still need to be able to work through firewalls. Most modern firewall software handles WebSockets, but not all. The most reliable way to handle WebSockets is through a TLS connection (sometimes incorrectly known as SSL).
You can use WebSocket from a browser like you can Ajax/REST. But when should use WebSockets and when should you use Ajax/REST? Also, can you use WebSocket from other clients?
This tutorial is going to try to answer these questions. Its aim is to be a compare, contrast and learn tutorial. In order to convey the information enough code is added to make this tutorial go beyond the pointy hair boss description. To do this, we slowly build a chat client and server.
Let's do a quick code comparison of the JavaScript and Java involved in doing Ajax and WebSockets to start off the discussion. To developers sometimes code examples are very demystifying.
This tutorial covers both the JavaScript client side and Java server side so it is complete.
The first part of this tutorial, we build a simple chat prototype. In the second part of this tutorial, we build a scalable chat room which could handle thousands of users.
Ajax simple example client (JavaScript/Browser/HTML 5) and server (Resin Java Application Server)
The following code listing is a simple JavaScript Ajax, HTML 5 example that sends a "Hello Ajax World?" message to our server.
Sample Ajax "Chat" with RAW JavaScript/HTML 5
var ajax = null;
function sendChatMessageAjax() { //(2)
if (ajax == null) {
ajax = new XMLHttpRequest(); //(1)
}
if (ajax.readyState == 4 || ajax.readyState == 0) {
document.getElementById("span_result").innerHTML = "SENDING AJAX MESSAGE";
ajax.open("POST", 'chat', true);
ajax.onreadystatechange = handleChatMessageAjaxResponse; //(3)
ajax.send("Hello Ajax World?");
}
}
function handleChatMessageAjaxResponse() {
if (ajax.readyState == 4) {
document.getElementById('span_result').innerHTML = ajax.responseText;
}
}
...
<body>
<br />
<a href="javascript:sendChatMessageAjax();">Send Chat Message via
Ajax</a>
<br />
<a href="javascript:sendChatMessageWebSocket();">Send Chat Message
via WebSocket</a>
<br />
<a href="javascript:clearSend();">Clear send results</a>
<br />
<span id="span_result"></span>
<span id="error_result"></span>
</body>
Now typically, you don't use XMLHttpRequest directly, instead you use jQuery or Prototype or any number of other JavaScript frameworks. But to ease the explanation, and to aid in comparison to WebSocket, let's start with raw JavaScript (later tutorials will use raw JavaScript, and jQuery).
Quick Review of Ajax: sendChatMessageAjax
(2) is JavaScript function that uses an
instance of XMLHttpRequest
called ajax
(1) to send an HTTP POST request
back to the server. Since this is JavaScript, and you don't want to block the user and JavaScript
does not support threads, then you must register a callback called
handleChatMessageAjaxResponse
with the
ajax.onreadystatechange (XMLHttpRequest)
(3).
Servlet that handles Ajax call
Now let's cover the Java side of the house. Again, there are many Java frameworks that add layers between the Java backend and the HTML/JavaScript rendering to handle Ajax nicely. But for this discussion, let's start with the simplest thing that will work, and in Java that is a Java HttpServlet as follows:
Sample Ajax "Chat" server with a RAW Java Servlet (Resin Java Application Server / Java EE)
package com.caucho.websocket.example;
...
/**
* Servlet implementation class ChatServlet
*/
@WebServlet("/chat")
public class ChatServlet extends HttpServlet {
...
/**
* Handle ajax calls
* @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
*/
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("___________ doPost MEHTOD ________________ " );
char [] data = new char[4096];
request.getReader().read(data); // 1
String text = new String(data); // 2
System.out.println(text); //print out what the client sent
response.getWriter().print("Hello from Server-side Ajax : " +text); //3
}
}
This is fairly basic. Read the data (1), convert data into a String (2), send the data back to the browser with "Hello from Server-side Ajax : " prepended to to it (3).
WebSocket simple example client and server
Now lets compare the above client and server to a WebSocket equivalent. Efforts are made to keep this JavaScript really simple and easy to understand so it is easy to compare against the previous Ajax example.
The following code listing is a simple JavaScript Ajax, HTML 5 example that sends a "Hello WebSocket World?" message to our server.
The WebSocket example is a parallel as possible to the Ajax example.
Sample WebSocket "Chat" server with a RAW Java Servlet (Resin Java Application Server / Java EE)
var socket = null;
function sendChatMessageWebSocket() { // (2)
if (socket == null) {
// (1)
socket = new WebSocket("ws://localhost:8080/web-socket-example/chat", "caucho-example-chat-protocol");
socket.onmessage = handleChatMessageWebSocketResponse; //(3)
initSocket(); //Explained later
}
document.getElementById("span_result").innerHTML = "SENDING WEBSOCKET MESSAGE";
socket.send("Hello WebSocket World?");
}
function handleChatMessageWebSocketResponse(msg) {
document.getElementById("span_result").innerHTML = msg.data;
}
...
Quick Run down of the WebSocket code: sendChatMessageWebSocket
(2) is JavaScript
function that uses an instance of WebSocket
called socket
(1) to
start a WebSocket connection (HTTP upgrade) with the server. Since this is JavaScript, and you don't
want to block the user and JavaScript does not support threads, then you must register a callback
called handleChatMessageWebSocketResponse
with the
socket.onmessage (WebSocket)
(3).
Ok so they look very similar. Again, I am going to start with the similarities. The differences are to come.
To handle the above you need a WebSocketListener
handler as follows:
public class ChatWebSocketListener implements WebSocketListener {
@Override
public void onReadText(WebSocketContext context, Reader reader)
throws IOException {
System.out.println("___________ onReadText MEHTOD ________________ " );
char [] data = new char[4096];
reader.read(data); // (1)
String text = new String(data); // (2)
System.out.println(text);
PrintWriter out = context.startTextMessage(); // (??? NEW 4)
out.print("Hello from Server-side WebSocket : " + text); // (3)
out.close(); //You have to close to send the message. // (??? NEW 5)
System.out.println("___________ onReadText MEHTOD END END END________________ " );
}
...
This is fairly basic. Read the data (1), convert data into a String (2), send the data back to the browser with "Hello from Server-side Ajax : " prepended to it (3).
At this point, these are nearly identical. You can use WebSockets similar to how you should use
Ajax. No real learning curve for simple cases.
Some noticeable differences between WebSockets, and Ajax examples
The first difference between this and the Servlet version is that we are using a
WebSocketListener
. The WebSocketListener
is a Resin class as Java EE 6
does not have WebSocket support. Java EE 6 predates Websocket. The WebSocketListener
looks fairly similar to the Servlet API as much as possible by design.
Note that the current plan for Java EE 7 is to include WebSocket support. Caucho Technology is and has been involved in several Java EE JSRs, and was heavily involved in the IETF WebSocket draft.
From a programming perspective, so far, there is no real difference between the WebSocket version and the Ajax/Servlet version. There is a little bit of handshaking that the Servlet has to do which we will cover later, but essentially if this is all you wanted, then WebSocket looks a lot like Ajax. Now since we are developing a chat example, eventually we will want to push messages from other people who are in the chat session. This is where WebSockets is going to shine.
What is WebSockets again?
From a web developers viewpoint, WebSocket is a new browser feature for HTML 5 browsers. This new feature enables richer user interactions. Both the browser and the server can send asynchronous messages over a single TCP socket, without doing less scalable hacks like long polling or comet.
The communication starts out like HTTP and then upgrades after a HTTP handshake to bidirectional WebSockets. A WebSocket is a bidirectional message stream between the client and the server.
While all modern browsers support some version of WebSocket as of March 2012 few application servers and web servers do. In order for browsers to take advantage of WebSockets, you need to have an application server or web server that can handle WebSockets.
Detour Streaming API versus a byte[]/String API versus a WebSocket Frame API
There are several Java WebSocket implementations out in the wild. Resin WebSocket support is the most mature and gets used by more high traffic sites. If you are not familiar with Resin Server, it is a scalable, fast, mature Java EE certified application/web server. Its speed is faster than NginX and Apache HTTPD, and it is more scalable.
All of Resin's cloud/clustering communication works on top of WebSocket. Resin's WebSocket support predates most implementations. If you are serious about WebSockets and Java, then Resin Server is an obvious contender. Also you can try out the Open Source version for free.
Many Java WebSocket implementations do not allow stream access to WebSocket (Reader
,
Writer
, InputStream
, OutputStream
), and instead rely on
simple buffer constructs like String
and byte[]
or dump you
down into working directly with low level WebSocket frames (Frame API). Since WebSocket is a streaming,
framing wire protocol, naturally Resin Server supports streams. It is easy to for Java developers to
go from streams to byte[]
and String
as shown in the above examples.
Using byte[]
and String
are probably ok for some department level or
company level applications, but if for anything else you really need to use streams to maximize the
throughput and minimize contention.
Regarding a Frame oriented API, when you program with TCP/IP you never have an application developer API that exposes TCP/IP packets bit mask fields, in the same way the WebSocket Frame is the wrong level of abstraction for most developers. Resin's API is the right level of abstraction, i.e., streams like the Servlet API. That said, we are always looking for feedback.
Now that we have the background in the why of the API, let's discuss how the streaming works.
WebSocket Streaming, Frames and Messaging
WebSocket is unlike Ajax/HTTP in that the WebSocket connection stays open and it is bidirectional. This is perfect for a chat application or a near real time stock price app, etc.
WebSockets have two types of messages, binary (byte[]), and text (String). If your message is bigger than a single Frame then it gets sent as chunks in multiple frames. For the sake of argument let's say that a frame holds 1K bytes. If you are sending a JSON payload to the browser that is 4K then you are sending 4 Frames. (Actual framing varies, this are example sizes for discussion.)
To begin a message you need to send a stream of WebSocket frames. To start the message you call
context.startTextMessage()
(NEW 4). If your message does not fit into one frame,
then a another frame is created and marked that it continues the previous frame in the series using a
"continues" flag. In WebSockets the final frame in a message is marked with a "finished" flag.
This final frame is sent when you call close on the stream. This final frame says that you are done
sending the current binary or text message. The out.close
(5) is your way to tell Resin's
WebSocket implementation that the message is done so go ahead and mark the current frame as finished
and then send that as the last frame. On the client end of the wire, WebSockets client implementation
will see that the it got the last frame. The JavaScript WebSocket client will only call
socket.onmessage (WebSocket)
after it receives the Frame marked final. HTML 5 has the
luxury of handling whole message at a time because efficiency is not a concern for scalability like
it is on the server-side of the house.
Get all of that? That is ok if you don't. Just remember to call close
, and then Resin WebSockets will
complete sending the message just like you would write output to a Servlet or write to a file.
WebSocket frames are a nice fit with the Java IO Stream and Reader/Writer APIs.
Again the JavaScript API for WebSocket only deals with buffers like String and byte[] (these are String and ArrayBuffer or Blob in JavaScript speak). WebSocket is a generally purpose framing/wire protocol so expect a lot of other uses outside of HTML 5. Many other protocols spend a lot of time and effort creating a framing protocol (AMQP, IIOP, RMI-JRMP, etc.). If you are developing a new protocol that needs framing, you can just build your new protocol on top of WebSocket. Consider framing solved.
WebSockets are extensible, and you could build other protocol on top of it. You could for example build a version of IIOP on top of WebSockets. Expect extensions for flow control, multiplexing, and compression.
Protocol negotiation
HTTP upgrade was added to the HTTP specification to support changing to new versions of HTTP more flawlessly.
To use WebSockets which is a different wire protocol then HTTP, the client must do an HTTP upgrade, and tell the server that it supports WebSockets. The Browser HTML 5 client sends a special HTTP GET that has special headers. If the server supports WebSocket it sends an acknowledgement and then the conversation begins.
To register our ChatWebSocketListener
you have to create a Servlet that handles this special
request as follows:
<code> protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String protocol = request.getHeader("Sec-WebSocket-Protocol"); //(1) System.out.println("___________ doGet MEHTOD ________________ " + protocol); if ("caucho-example-chat-protocol".equals(protocol)) { System.out.println("___________ WEBSOCKET Handshake ________________ " + protocol); WebSocketListener listener = new ChatWebSocketListener(); //(2) response.setHeader("Sec-WebSocket-Protocol", "caucho-example-chat-protocol"); //(3) WebSocketServletRequest wsRequest = (WebSocketServletRequest) request; wsRequest.startWebSocket(listener); //(4) } } </code>
The above checks the subprotocol that the HTML 5 browser client expects via the Sec-WebSocket-Protocol
HTTP header (1). Then it creates and instance of the ChatWebSocketListener
that we
covered earlier (2) and registers that with the WebSocketServletRequest
(4) via the
startWebSocket
method.
The client, HTML 5 Browser client, can request that the server use a specific subprotocol by including the Sec-WebSocket-Protocol field in its handshake. The server responds with a comma delimited list of of subprotocols. A subprotocol is basically the version of the wire format. It is an indication of what kinds of marshaling you are doing. Let's say you worked at Caucho Technology Inc. Your subprotocol could be called "chat.example.com". Subprotocols can be versioned in, e.g., "chat.example.com" versus "v2.chat.example.com" are two different subprotocols.
The HTTP header handshake from the browser could look something like this:
<code> GET /chat HTTP/1.1 Host: server.caucho.com Upgrade: websocket Connection: Upgrade Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ== Origin: http://example.com Sec-WebSocket-Protocol: caucho-example-chat-protocol, v2.caucho-example-chat-protocol Sec-WebSocket-Version: 13 </code>
The part of the handshake from the server might look like this:
<code> HTTP/1.1 101 Switching Protocols Upgrade: websocket Connection: Upgrade Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo= Sec-WebSocket-Protocol: caucho-example-chat-protocol </code>
Details about WebSocketContext and WebSocketListener
The WebSocketListener
is the main area of focus for the server-side implementation.
It is similar in concept to a Java Servlet at first blush.
Unlike a Servlet, a WebSocketListener is a single-threaded handler for events and messages from the client.
The onReadText
of WebSocketListener
method will only get called by one thread
at a time whilst a HttpServlet.doGet
method can be called by many threads.
The Reader
, PrintWriter
and WebSocketContext
are not thread
safe and should only be accessed by one thread at a time. It is important that you only access
these from one thread at a time. This is similar to the normal Servlet programming as it also works with streams and reader/writers
which can only be accessed by one thread at a time.
The onReadText
should not do anything that is going to block if you are trying to write
a scalable application. Blocking might be okay for department level or company level application.
For applications that support higher traffic and need higher throughput blocking on an
onReadText
is not ok. Higher scaling application would need to use some sort of
queuing mechanisms, we will cover this in a later tutorial.
The big take away that I want you to get from this discussion is this: although the programming model may look the same from Ajax/Servlet to WebSocket, it is very different. Ajax/REST is request/response. WebSocket is messaging.
With WebSocket the connection stays open. It is expected that the server is calling the client at will. It is common in WebSockets for the reading and writing to happen in different threads.
Simplest possible example that shows bidirectional communication over WebSockets
As was mentioned earlier, we are going to cover building a full HTML 5 chat client, but to whet your appetite without going into crazy complexity, let's change the last example from a traditional Request/Response to a full blown bidirectional connection. The HTML 5 client is sending messages on one end, while the server is at the same time sending messages to the HTML 5 client.
In this example, we ask the server for a thread to fire off a chat message sender. The message sender gets messages from a queue. It echoes the messages back. If it does not hear from the client in five seconds, it starts nagging it with a "Are you there?" message. To do this, you do the following.
(THE EXAMPLE BELOW IS UNDER REVIEW BY PEERS. It may change.)
You change the client to connect as soon as the page loads as follows (full code listings will be at end of tutorial):
<code> function initSocket() { if (socket == null) { socket = new WebSocket("ws://localhost:8080/web-socket-example/chat", "caucho-example-chat-protocol"); socket.onmessage = handleChatMessageWebSocketResponse; } socket.onerror = function(msg) { document.getElementById("error_result").innerHTML = "ERROR:" + msg; } socket.onopen = function() { document.getElementById("span_result").innerHTML = "Socket Status: " + socket.readyState + " (open)"; } socket.onclose = function() { document.getElementById("span_result").innerHTML = "Socket Status: " + socket.readyState + " (Closed)"; } } initSocket(); </code>
You are going to handle the WebSocketListener.onStart
method to start up this new
message deliverer. WebSocketListener.onStart
needs to schedule a task with the servers executor
(java.util.concurrent.Executor). The task will listen for items that the onReadText
puts on the queue.
Then change the WebSocketListener
to create a BlockingQueue
(1)
and @Inject
a ThreadPoolExecutor
(2).
<code> public class ChatWebSocketListener implements WebSocketListener { volatile boolean close; BlockingQueue<String> readQueue; @Inject ThreadPoolExecutor executor; @Override public void onReadText(WebSocketContext context, Reader reader) throws IOException { System.out.println("___________ onReadText MEHTOD ________________ " ); char [] data = new char[4096]; reader.read(data); String text = new String(data); try { readQueue.put(text); // (3) } catch (InterruptedException e) { } ... </code>
Notice that the onReadText
no longer handles the writing of message directly. Instead
it adds the text message to the queue (3).
The onStart
method gets called as soon as WebSocket connection is established and ready
to receive messages (from either side). Change the onStart
method to create the queue, then
execute a task (4) with the executor that handles the echoing of messages and the sending of our
nagging reminders.
<code> public class ChatWebSocketListener implements WebSocketListener { @Override public void onStart(final WebSocketContext context) throws IOException { System.out.println("ON start ***********************************************"); readQueue = new ArrayBlockingQueue<String>(2000); ... executor.execute(new Runnable() { // (4) </code>
The task (anonymous inner class instance of Runnable) checks to see if the connection
has been closed in a forever loop (6). The close flag is a volatile boolean
that is set by the ChatWebSocketListener.onDisconnect
which gets called when the connection is shut down. Each iteration of the loop, the task checks
to see if there are any more messages on the queue (7). If there is a message, then it echoes that message
back using the (context) WebSocketContext
(8). Just to proove that we can send messages
without getting told to by the client, every five seconds we send "I have not heard from you in a while ",
"Are you stil there?", and "WAKE UP!!!!!!!!".
<code> new Runnable() { public void run() { PrintWriter out=null; long oldTime = System.currentTimeMillis(); long newTime, deltaTime; while (true) { if (close) break; // (6) try { String message = readQueue.poll(500, TimeUnit.MILLISECONDS); // (7) if (message!=null) { out = context.startTextMessage(); // (8) out.print("Hello from Server-side WebSocket : " + message); out.close(); //You have to close to send the message. oldTime = System.currentTimeMillis(); } newTime = System.currentTimeMillis(); deltaTime = newTime - oldTime; if (deltaTime>5000) { //if elapsed time is greater than 5000 seconds //Start nagging! out = context.startTextMessage(); out.print("I have not heard from you in a while "); out.close(); Thread.sleep(1000); out = context.startTextMessage(); out.print("Are you stil there?"); out.close(); Thread.sleep(1000); out = context.startTextMessage(); out.print("WAKE UP!!!!!!!!"); out.close(); oldTime = System.currentTimeMillis(); } </code>
IMPORTANT: Note that only the runnable task's thread accesses the WebSocketContext
.
Only one thread at a time can access a WebSocketContext
so also note that the onReadText
no longer accesses
the WebSocketContext
like it did in the first example. WebSocketContext
is not thread safe.
This is why only one thread can access it. In the first example the onReadText
accessed it directly.
If it still did that, then this would be broke and we would have thread synchronization issues. If
the thread that was running onReadText
and the thread that is running this task both wrote at
the same time, given the streaming nature of WebSockets and the need to sends frames in sequences,
bad things could happen.
Ok, we have introduced enough new concepts for WebSockets. I hope you can see the value in it, and understand some key differences between Ajax and WebSockets as well as some clear similarities. Going through the process of writing these tutorials has given us some insight into how to make the WebSocket API more complete. We plan on updating these tutorials as we improve the API. In addition, the next installment in this series is going to cover building a multi user chat system so stay tuned.
Now as promised here are the complete code examples.
Simple Ajax/WebSocket HTML 5 client for driving code
<code> <html> <head> <title>The Hello World of AJAX and WebSocket</title> <script language="JavaScript" type="text/javascript"> var ajax = null; function sendChatMessageAjax() { if (ajax == null) { ajax = new XMLHttpRequest(); } if (ajax.readyState == 4 || ajax.readyState == 0) { document.getElementById("span_result").innerHTML = "SENDING AJAX MESSAGE"; ajax.open("POST", 'chat', true); ajax.onreadystatechange = handleChatMessageAjaxResponse; ajax.send("Hello Ajax World?"); } } function handleChatMessageAjaxResponse() { if (ajax.readyState == 4) { document.getElementById('span_result').innerHTML = ajax.responseText; } } </script> <script language="javascript" type="text/javascript"> /* http://dev.w3.org/html5/websockets/ */ var socket = null; function sendChatMessageWebSocket() { document.getElementById("span_result").innerHTML = "SENDING WEBSOCKET MESSAGE"; socket.send("Hello WebSocket World?"); } function handleChatMessageWebSocketResponse(msg) { document.getElementById("span_result").innerHTML = msg.data; } function initSocket() { if (socket == null) { socket = new WebSocket("ws://localhost:8080/web-socket-example/chat", "caucho-example-chat-protocol"); socket.onmessage = handleChatMessageWebSocketResponse; } socket.onerror = function(msg) { document.getElementById("error_result").innerHTML = "ERROR:" + msg; } socket.onopen = function() { document.getElementById("span_result").innerHTML = "Socket Status: " + socket.readyState + " (open)"; } socket.onclose = function() { document.getElementById("span_result").innerHTML = "Socket Status: " + socket.readyState + " (Closed)"; } } initSocket(); function clearSend() { document.getElementById("span_result").innerHTML = ""; } </script> </head> <body> 1st attempt <br /> <a href="javascript:sendChatMessageAjax();">Send Chat Message via Ajax</a> <br /> <a href="javascript:sendChatMessageWebSocket();">Send Chat Message via WebSocket</a> <br /> <a href="javascript:clearSend();">Clear send results</a> <br /> <span id="span_result"></span> <span id="error_result"></span> </body> </html> </code>
Servlet that handles Ajax call and handles WebSocket upgrade protocol
<code> package com.caucho.websocket.example; import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import com.caucho.websocket.WebSocketServletRequest; /** * Servlet implementation class ChatServlet */ @WebServlet("/chat") public class ChatServlet extends HttpServlet { /** * Handle Websocket handshake. * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response) */ protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String protocol = request.getHeader("Sec-WebSocket-Protocol"); System.out.println("___________ doGet MEHTOD ________________ " + protocol); if ("caucho-example-chat-protocol".equals(protocol)) { System.out.println("___________ WEBSOCKET Handshake ________________ " + protocol); response.setHeader("Sec-WebSocket-Protocol", "caucho-example-chat-protocol"); WebSocketServletRequest wsRequest = (WebSocketServletRequest) request; wsRequest.startWebSocket(new ChatWebSocketListener()); } } /** * Handle ajax calls * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response) */ protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { System.out.println("___________ doPost MEHTOD ________________ " ); char [] data = new char[4096]; request.getReader().read(data); String text = new String(data); System.out.println(text); response.getWriter().print("Hello from Server-side Ajax : "+text); } } </code>
WebSocket Handler for Chat example prototype
Notice we subclass AbstractWebSocketListener instead of implement WebSocketListener. The AbstractWebSocketListener implements WebSocketListener so you can ignore the callback handler that you do not care about.
package com.caucho.websocket.example; import java.io.IOException; import java.io.InputStream; import java.io.PrintWriter; import java.io.Reader; import java.nio.charset.Charset; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import com.caucho.websocket.WebSocketContext; import com.caucho.websocket.AbstractWebSocketListener; public class ChatWebSocketListener extends AbstractWebSocketListener { volatile boolean close; BlockingQueue<String> readQueue; ThreadPoolExecutor executor; public ChatWebSocketListener(ThreadPoolExecutor executor) { this.executor = executor; } @Override public void onReadText(WebSocketContext context, Reader reader) throws IOException { System.out.println("___________ onReadText MEHTOD ________________ " ); char [] data = new char[4096]; reader.read(data); String text = new String(data); try { readQueue.put(text); } catch (InterruptedException e) { } System.out.println("******** ***** *****" + text); System.out.println("___________ onReadText MEHTOD END END END________________ " ); } @Override public void onReadBinary(WebSocketContext context, InputStream input) throws IOException { byte[] data = new byte[4096]; input.read(data); String text = new String(data, Charset.forName("UTF8")); System.out.println("***************** ::" + text); } @Override public void onDisconnect(WebSocketContext context) throws IOException { System.out.println("ON DISCONNECT ***********************************************"); close=true; } @Override public void onStart(final WebSocketContext context) throws IOException { System.out.println("ON start ***********************************************"); readQueue = new ArrayBlockingQueue<String>(2000); if (executor == null) { this.executor = new ThreadPoolExecutor(1, 1, 1, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(1)); } executor.execute(new Runnable() { @Override public void run() { PrintWriter out=null; long oldTime = System.currentTimeMillis(); long newTime, deltaTime; while (true) { if (close) break; try { String message = readQueue.poll(500, TimeUnit.MILLISECONDS); if (message!=null) { out = context.startTextMessage(); out.print("Hello from Server-side WebSocket : " + message); out.close(); //You have to close to send the message. oldTime = System.currentTimeMillis(); } newTime = System.currentTimeMillis(); deltaTime = newTime - oldTime; if (deltaTime>5000) { //if elapsed time is greater than 5000 seconds //Start nagging! out = context.startTextMessage(); out.print("I have not heard from you in a while "); out.close(); Thread.sleep(1000); out = context.startTextMessage(); out.print("Are you stil there?"); out.close(); Thread.sleep(1000); out = context.startTextMessage(); out.print("WAKE UP!!!!!!!!"); out.close(); oldTime = System.currentTimeMillis(); } } catch (InterruptedException ex) { break; //Our thread was interrupted, no worries, just leave the loop. } catch (IOException e) { break; //Some communication or connection issue, you could log this } } } }); } }
The key take away from this tutorial is this: WebSockets is bidirectional messaging, Servlet/Ajax/REST is request/response.
In part two of this tutorial, we build a working chat client that you can use from multiple browsers.
Part II: Understanding WebSockets versus Ajax/REST for Java EE Developers Part II