[Reverse Ajax, Mathieu Carbou, Java Web Architect, Ovea]
Part 1: Introduction to Comet
Part 2: WebSockets
Part 3: Web servers and Socket.IO
Part 4: Atmosphere and CometD
http://www.ibm.com/developerworks/web/library/wa-reverseajax1/
http://www.ibm.com/developerworks/web/library/wa-reverseajax2/http://www.ibm.com/developerworks/web/library/wa-reverseajax3/index.html
http://www.ibm.com/developerworks/web/library/wa-reverseajax4/
http://www.ibm.com/developerworks/web/library/wa-reverseajax2/http://www.ibm.com/developerworks/web/library/wa-reverseajax3/index.html
http://www.ibm.com/developerworks/web/library/wa-reverseajax4/
* IBM article이 그렇듯이 충실하다.. 이것만 제대로 보면 OK.
setInterval(function() { $.getJSON('events', function(events) { console.log(events); }); }, 2000); |
The second technique, which is more reliable, is to use the multi-part flag supported by some browsers (such as Firefox) on the
XMLHttpRequest
object. An Ajax request is sent and kept open on the server side. Each time an event comes, a multi-part response is written through the same connection. Listing 6 shows an example.Listing 6. Sample JavaScript code to set up a multi-part streaming request
var xhr = $.ajaxSettings.xhr(); xhr.multipart = true; xhr.open('GET', 'ajax', true); xhr.onreadystatechange = function() { if (xhr.readyState == 4) { processEvents($.parseJSON(xhr.responseText)); } }; xhr.send(null); |
On the server side, things are a little more complicated. You must first set up the multi-part request, and then suspend the connection. Listing 7 shows how to suspend an HTTP streaming request. (Part 3 of this series will cover the APIs in more detail.)
Listing 7. Suspending an HTTP streaming request in a servlet using Servlet 3 API
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // start the suspension of the request AsyncContext asyncContext = req.startAsync(); asyncContext.setTimeout(0); // send the multipart separator back to the client resp.setContentType("multipart/x-mixed-replace;boundary=\"" + boundary + "\""); resp.setHeader("Connection", "keep-alive"); resp.getOutputStream().print("--" + boundary); resp.flushBuffer(); // put the async context in a list for future usage asyncContexts.offer(asyncContext); } |
Now, each time an event occurs you can iterate over all suspended connections and write the data to them, as shown in Listing 8:
Listing 8. Send events to a suspended multi-part request using Servlet 3 API
for (AsyncContext asyncContext : asyncContexts) { HttpServletResponse peer = (HttpServletResponse) asyncContext.getResponse(); peer.getOutputStream().println("Content-Type: application/json"); peer.getOutputStream().println(); peer.getOutputStream().println(new JSONArray() .put("At " + new Date()).toString()); peer.getOutputStream().println("--" + boundary); peer.flushBuffer(); } |
As usual, there are advantages and disadvantages.
- Advantage: Only one persistent connection is opened. This is the Comet technique that saves the most bandwidth usage.
- Disadvantage: The multi-part flag is not supported by all browsers. Some widely used libraries, such as CometD in Java, reported issues in buffering. For example, chunks of data (multi-parts) may be buffered and sent only when the connection is completed or the buffer is full, which can create higher latency than expected.
The second, and recommended, method to implement Comet is to open an Ajax request to the server and wait for the response. The server requires specific features on the server side to allow the request to be suspended. As soon as an event occurs, the server sends back the response in the suspended request and closes it, exactly like you close the output stream of a servlet response. The client then consumes the response and opens a new long-lived Ajax request to the server, as shown in Listing 9:
Listing 9. Sample JavaScript code to set up long polling requests
function long_polling() { $.getJSON('ajax', function(events) { processEvents(events); long_polling(); }); } long_polling(); |
On the back end, the code also uses the Servlet 3 API to suspend the request, as in HTTP streaming, but you don't need all the multi-part handling code. Listing 10 shows an example.
Listing 10. Suspending a long polling Ajax request
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { AsyncContext asyncContext = req.startAsync(); asyncContext.setTimeout(0); asyncContexts.offer(asyncContext); } |
When an event is received, simply take all of the suspended requests and complete them, as shown in Listing 11:
Listing 11. Completing a long polling Ajax request when an event occurs
while (!asyncContexts.isEmpty()) { AsyncContext asyncContext = asyncContexts.poll(); HttpServletResponse peer = (HttpServletResponse) asyncContext.getResponse(); peer.getWriter().write( new JSONArray().put("At " + new Date()).toString()); peer.setStatus(HttpServletResponse.SC_OK); peer.setContentType("application/json"); asyncContext.complete(); } |
In the accompanying downloadable source files, the comet-long-polling folder contains a long polling sample web application that you can run using the mvn
jetty:run
command.- Advantages: It's easy to implement on the client side with a good error-handling system and timeout management. This reliable technique also allows a round-trip between connections on the server side, since connections are not persistent (a good thing, when you have a lot of clients on your application). It also works on all browsers; you only make use of the
XMLHttpRequest
object by issuing a simple Ajax request. - Disadvantage: There is no main disadvantage compared to other techniques. But, like all techniques we've discussed, this one still relies on a stateless HTTP connection, which requires special features on the server side to be able to temporarily suspend it
Listing 2. JavaScript client code
var ws = new WebSocket('ws://127.0.0.1:8080/async'); ws.onopen = function() { // called when connection is opened }; ws.onerror = function(e) { // called in case of error, when connection is broken in example }; ws.onclose = function() { // called when connexion is closed }; ws.onmessage = function(msg) { // called when the server sends a message to the client. // msg.data contains the message. }; // Here is how to send some data to the server ws.send('some data'); // To close the socket: ws.close(); |
Request URL:ws://127.0.0.1:8080/async Request Method:GET Status Code:101 WebSocket Protocol Handshake Request Headers Connection:Upgrade Host:127.0.0.1:8080 Origin:http://localhost:8080 Sec-WebSocket-Key1:1 &1~ 33188Yd]r8dp W75q Sec-WebSocket-Key2:1 7; 229 *043M 8 Upgrade:WebSocket (Key3):B4:BB:20:37:45:3F:BC:C7 Response Headers Connection:Upgrade Sec-WebSocket-Location:ws://127.0.0.1:8080/async Sec-WebSocket-Origin:http://localhost:8080 Upgrade:WebSocket (Challenge Response):AC:23:A5:7E:5D:E5:04:6A:B5:F8:CC:E7:AB:6D:1A:39 |
public final class ReverseAjaxServlet extends WebSocketServlet { @Override protected WebSocket doWebSocketConnect(HttpServletRequest request, String protocol) { return [...] } } |
With Jetty, there are several ways to handle a WebSocket handshake. The easier way is to subclass Jetty's
WebSocketServlet
and implement the doWebSocketConnect
method. This method asks you to return an instance of the Jetty's WebSocket interface. You have to implement the interface and return a sort of endpoint representing the WebSocket connection. Listing 5 provides a sample.Listing 5. WebSocket implementation sample
class Endpoint implements WebSocket { Outbound outbound; @Override public void onConnect(Outbound outbound) { this.outbound = outbound; } @Override public void onMessage(byte opcode, String data) { // called when a message is received // you usually use this method } @Override public void onFragment(boolean more, byte opcode, byte[] data, int offset, int length) { // when a fragment is completed, onMessage is called. // Usually leave this method empty. } @Override public void onMessage(byte opcode, byte[] data, int offset, int length) { onMessage(opcode, new String(data, offset, length)); } @Override public void onDisconnect() { outbound = null; } } |
To send a message to the client, you write to the outbound, as shown in Listing 6:
Listing 6. Sending a message to the client
if (outbound != null && outbound.isOpen()) { outbound.sendMessage('Hello World !'); } |
To disconnect the client and close the WebSocket connection, use
outbound.disconnect();
.
WebSockets is a very powerful way to implement a bi-directional communication with no latency. It is supported by Firefox, Google Chrome, Opera, and other modern browsers. According to the jWebSocket website:
- Chrome includes native WebSockets since 4.0.249.
- Safari 5.x includes native WebSockets.
- Firefox 3.7a6 and 4.0b1+ includes native WebSockets.
- Opera includes native WebSockets since 10.7.9067.
For more information about jWebSocket, see Resources.
WebSockets provides powerful, bi-directional, low-latency, and easy-to-handle errors. There isn't a lot of connection, like Comet long polling, and it doesn't have the drawbacks of Comet streaming. The API is also very easy to use directly without any additional layers, compared to Comet, which requires a good library to handle reconnection, timeout, Ajax requests, acknowledgments, and the optionally different transports (Ajax long polling and jsonp polling).
댓글 없음:
댓글 쓰기