Reverse Ajax, Part 1: Introduction to Comet

Introduction

Web development has come a long way over the past few years, and we've moved far beyond the practice of linking together static web pages, which causes browser refreshes and waits for pages to load. What is needed now is to be able to access fully dynamic applications over the Web. These applications often need to be as fast as possible, providing near real-time components. In this new 5-part series, we learn how to use Reverse Ajax (Reverse Ajax) techniques to develop event-driven Web applications.

In this first article, we're going to learn about reverse Ajax, polling, streaming, Comet, and long polling. Learn how to implement different reverse Ajax communication techniques, and explore the advantages and disadvantages of each approach. You can  download  the corresponding source code for the examples in this article.

 

 

Ajax, Reverse Ajax and WebSockets

Asynchronous JavaScript and XML (Ajax), a browser feature accessible through JavaScript that allows scripts to send an HTTP request to a website behind the scenes without having to reload the page. Ajax has been around for over a decade, and despite the XML in its name, you can pass almost anything in an Ajax request. The most commonly used data is JSON, which is very close to JavaScript syntax and consumes less bandwidth. Listing 1 Listing 1 shows an example of an Ajax request that retrieves the name of a place by its zip code.

Listing 1. Listing 1 Ajax request example
var url = 'http://www.geonames.org/postalCodeLookupJSON?postalcode='
    + $('#postalCode').val() + '&country='
    + $('#country').val() + '&callback=?';
$.getJSON(url, function(data) {
    $('#placeName').val(data.postalcodes[0].placeName);
});

In the  downloadable source code  for this article, you can see this example in action in listing1.html.

Reverse Ajax (Reverse Ajax) is essentially such a concept: the ability to send data from the server to the client. In a standard HTTP Ajax request, the data is sent to the server, and reverse Ajax can simulate issuing an Ajax request in certain ways, which will be discussed in this article, so that the server can send the request to the server as quickly as possible. Client sends events (low latency communication).

WebSocket technology comes from HTML5 and is a relatively recent technology that is already supported by many browsers (Firefox, Google Chrome, Safari, etc.). WebSocket enables a two-way, full-duplex communication channel that opens a connection through some kind of HTTP request called the WebSocket handshake, using some special headers. The connection remains active, and you can write and receive data in JavaScript as if you were using a raw TCP socket. WebSockets will be covered in the second part of this article series.

 

Reverse Ajax Technology

The purpose of reverse Ajax is for the server to push information to the client. Ajax requests are stateless by default and can only be made from the client to the server. You can get around this limitation by using techniques to simulate reactive communication between the server side and the client side.

HTTP polling and JSONP polling

Polling involves sending a request from the client to the server to get some data, which is obviously a pure Ajax HTTP request. To get server-side events as quickly as possible, the polling interval (the time between requests) must be as small as possible. But there is such a disadvantage: if the interval is reduced, the client browser will make more requests, many of these requests will not return any useful data, and this will be a waste of bandwidth and Process resources.

Figure 1 The timeline in Figure 1 illustrates a situation where the client makes some polling requests, but no information is returned, and the client has to wait until the next poll to get the events received by the two servers.

Figure 1. Reverse Ajax using HTTP polling

Reverse Ajax using HTTP polling

JSONP polling is basically the same as HTTP polling. The difference is that with JSONP you can send cross-domain requests (requests that do not belong to your domain). Listing 1 Listing 1  uses JSONP to get place names by zip code. A JSONP request is usually identified by its callback parameters and return content, which is executable JavaScript code.

To implement polling in JavaScript, you can  setInterval make periodic Ajax requests using , as shown in Listing 2:

Listing 2. Listing 2 JavaScript polling
setInterval(function() {
    $.getJSON('events', function(events) {
        console.log(events);
    });
}, 2000);

The polling demo in the article source code  shows the bandwidth consumed by the polling method in small intervals, but you can see that some requests do not return events, and Listing 3Listing 3 shows the output of this polling example.

Listing 3. Listing 3 polling the output of the demo example
[client] checking for events...
[client] no event
[client] checking for events...
[client] 2 events
[event] At Sun Jun 05 15:17:14 EDT 2011
[event] At Sun Jun 05 15:17:14 EDT 2011
[client] checking for events...
[client] 1 events
[event] At Sun Jun 05 15:17:16 EDT 2011

The advantages and disadvantages of polling implemented in JavaScript.

  • Pros: Easy to implement, doesn't require any server-side specific functionality, and works on all browsers.
  • Disadvantage: This method is rarely used because it is not scalable at all. Consider the amount of bandwidth and resources lost in a scenario where 100 clients each issue a 2 second poll request, in which case 30% of the requests return no data.

Piggyback

Piggyback polling is a smarter approach than polling because it removes all non-essential requests (those that don't return data). There is no time interval, and the client sends requests to the server when needed. The difference is that on the part of the response, the response is split into two parts: the response to the request data and the response to the server event, if any of those parts occurred. Figure 2 Figure 2 shows an example.

Figure 2. Reverse Ajax with piggyback polling

The figure depicts the client sending a POST request but getting a mixed response.  Nothing happens on the server side, so the mixed response only contains the response of the request.  Then two events arrive on the server side, but they need to wait for the next client request, so the mixed response will contain both events and the normal request response.  If the client does not trigger any action, events arriving on the server will not be retrieved.

When implementing the piggyback technique, it is common for all Ajax requests to the server to return a mixed response. There is an example implementation in the article's download  , shown in Listing 4 Listing 4 below.

Listing 4. Listing 4 piggyback code example
$('#submit').click(function() {
    $.post('ajax', function(data) {
        var valid = data.formValid;
        // process validation results
        // then process the other part of the response (events) 
        processEvents(data.events); 
    }); 
});

清单 5清单 5 给出了一些 piggyback 输出。

清单 5. 清单 5 piggyback 输出示例
[client] checking for events... 
[server] form valid ? true 
[client] 4 events 
[event] At Sun Jun 05 16:08:32 EDT 2011 
[event] At Sun Jun 05 16:08:34 EDT 2011 
[event] At Sun Jun 05 16:08:34 EDT 2011 
[event] At Sun Jun 05 16:08:37 EDT 2011

您可以看到表单验证的结果和附加到响应上的事件。同样,这种方法也有着一些优点和缺点。

  • 优点:没有不返回数据的请求,因为客户端对何时发送请求做了控制,对资源的消耗较少。该方法也是可用在所有的浏览器上,不需要服务器端的特殊功能。
  • 缺点:当累积在服务器端的事件需要传送给客户端时,您却一点都不知道,因为这需要一个客户端行为来请求它们

 

 

 

Comet

使用了轮询或是捎带的反向 Ajax 非常受限:其不具伸缩性,不提供低延迟通信(只要事件一到达服务器端,它们就以尽可能快的速度到达浏览器端)。 Comet 是一个 Web 应用模型,在该模型中,请求被发送到服务器端并保持一个很长的存活期,直到超时或是有服务器端事件发生。在该请求完成后,另一个长生存期的 Ajax 请求就被送去等待另一个服务器端事件。使用 Comet 的话,Web 服务器就可以在无需显式请求的情况下向客户端发送数据。

Comet 的一大优点是,每个客户端始终都有一个向服务器端打开的通信链路。服务器端可以通过在事件到来时立即提交(完成)响应来把事件推给客户端,或者它甚至可以累积再连续发送。因为请求长时间保持打开的状态,故服务器端需要特别的功能来处理所有的这些长生存期请求。图 3图 3 给出了一个例子。(本系列的第 2 部分会更详细地解释服务器端的约束条件。)

图 3. 图 3.使用 Comet 的反向 Ajax

Reverse Ajax using Comet

Comet 的实现可以分成两类:使用流 (streaming) 的那些和使用长轮询 (long polling) 的那些。

 

使用 HTTP 流的 Comet

在流 (streaming) 模式中,有一个持久连接会被打开。只会存在一个长生存期请求(图 3图 3 中的 #1),因为每个到达服务器端的事件都会通过这同一连接来发送。因此,客户端需要有一种方法来把通过这同一连接发送过来的不同响应分隔开来。从技术上来讲,两种常见的流技术包括 Forever Iframe(或者 hidden IFrame),或是被用来在 JavaScript 中创建 Ajax 请求的 XMLHttpRequest 对象的多部分 (multi-part) 特性。

Forever Iframes

Forever Iframe(永存的 Iframe)技术涉及了一个置于页面中的隐藏 Iframe 标签,该标签的 src 属性指向返回服务器端事件的 servlet 路径。每次在事件到达时,servlet 写入并刷新一个新的 script 标签,该标签内部带有 JavaScript 代码,iframe 的内容被附加上这一 script 标签,标签中的内容就会得到执行。

  • 优点:实现简单,在所有支持 iframe 的浏览器上都可用。
  • 缺点:没有方法可用来实现可靠的错误处理或是跟踪连接的状态,因为所有的连接和数据都是由浏览器通过 HTML 标签来处理的,因此您没有办法知道连接何时在哪一端已被断开了。

多部分的 XMLHttpRequest

第二种技术(更加可靠)是在 XMLHttpRequest 对象上使用某些浏览器(比如 Firefox)支持的 multi-part 标志。Ajax 请求被发送给服务器端并保持打开状态,每次有事件到来时,一个多部分的响应就会通过这同一连接来写入。清单 6清单 6 给出了一个例子。

清单 6. 清单 6 设置多部分流请求的 JavaScript 代码示例
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);

在服务器端,事情要稍加复杂一些。首先您必须要设置多部分请求,然后挂起连接。清单 7清单 7 展示了如何挂起一个 HTTP 流请求。(本系列的第 3 部分会更加详细地谈及这些 API。)

清单 7. 清单 7 使用 Servlet 3 API 来在 servlet 中挂起一个 HTTP 流请求
protected void doGet(HttpServletRequest req, HttpServletResponse resp) 
        throws ServletException, IOException { 
    // 开始请求的挂起
    AsyncContext asyncContext = req.startAsync(); 
    asyncContext.setTimeout(0); 

    // 给客户端发回多部分的分隔符
    resp.setContentType("multipart/x-mixed-replace;boundary=\""
        + boundary + "\""); 
    resp.setHeader("Connection", "keep-alive"); 
    resp.getOutputStream().print("--" + boundary); 
    resp.flushBuffer(); 

    // 把异步上下文放在列表中以备将来之用
    asyncContexts.offer(asyncContext); 
}

现在,每次有事件发生时您都可以遍历所有的挂起连接并向它们写入数据,如清单 8清单 8 所示:

清单 8. 使用 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(); 
}

本文可 下载 文件的 Comet-straming 文件夹中的部分说明了 HTTP 流,在运行例子并打开主页时,您会看到只要事件一到达服务器端,虽然不同步但它们几乎立刻会出现在页面上。而且,如果打开 Firebug 控制台的话,您就能看到只有一个 Ajax 请求是打开的。如果再往下看一些,您会看到 JSON 响应被附在 Response 选项卡中,如图 4图 4 所示:

图 4. HTTP 流请求的 Firebug 视图

Screenshot of FireBug view of HTTP streaming request.  The FireBug plugin in Firefox shows consecutive responses in the same Ajax streaming request.

照例,做法存在着一些优点和缺点。

  • 优点:只打开了一个持久连接,这就是节省了大部分带宽使用率的 Comet 技术。
  • 缺点:并非所有的浏览器都支持 multi-part 标志。某些被广泛使用的库,比如说用 Java 实现的 CometD,被报告在缓冲方面有问题。例如,一些数据块(多个部分)可能被缓冲,然后只有在连接完成或是缓冲区已满时才被发送,而这有可能会带来比预期要高的延迟。

 

 

 

使用 HTTP 长轮询的 Comet

长轮询 (long polling) 模式涉及了打开连接的技术。连接由服务器端保持着打开的状态,只要一有事件发生,响应就会被提交,然后连接关闭。接下来。一个新的长轮询连接就会被正在等待新事件到达的客户端重新打开。

您可以使用 script 标签或是单纯的 XMLHttpRequest 对象来实现 HTTP 长轮询。

script 标签

正如 iframe 一样,其目标是把 script 标签附加到页面上以让脚本执行。服务器端则会:挂起连接直到有事件发生,接着把脚本内容发送回浏览器,然后重新打开另一个 script 标签来获取下一个事件。

  • 优点:因为是基于 HTML 标签的,所有这一技术非常容易实现,且可跨域工作(默认情况下,XMLHttpRequest 不允许向其他域或是子域发送请求)。
  • 缺点:类似于 iframe 技术,错误处理缺失,您不能获得连接的状态或是有干涉连接的能力。

 

 

 

XMLHttpRequest 长轮询

第二种,也是一种推荐的实现 Comet 的做法是打开一个到服务器端的 Ajax 请求然后等待响应。服务器端需要一些特定的功能来允许请求被挂起,只要一有事件发生,服务器端就会在挂起的请求中送回响应并关闭该请求,完全就像是您关闭了 servlet 响应的输出流。然后客户端就会使用这一响应并打开一个新的到服务器端的长生存期的 Ajax 请求,如清单 9清单 9 所示:

清单 9. 清单 9 设置长轮询请求的 JavaScript 代码示例
function long_polling() { 
    $.getJSON('ajax', function(events) { 
        processEvents(events); 
        long_polling(); 
    }); 
} 

long_polling();

在后端,代码也是使用 Servlet 3 API 来挂起请求,正如 HTTP 流的做法一样,但是您不需要所有的多部分处理代码。清单 10清单 10 给出了一个例子。

清单 10. 清单 10 挂起一个长轮询 Ajax 请求
protected void doGet(HttpServletRequest req, HttpServletResponse resp) 
        throws ServletException, IOException { 
    AsyncContext asyncContext = req.startAsync(); 
    asyncContext.setTimeout(0); 
    asyncContexts.offer(asyncContext); 
}

在接收到事件时,只是取出所有的挂起请求并完成它们,如清单 11清单 11 所示:

清单 11. 清单 11 在有事件发生时完成长轮询 Ajax 请求
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  download source code  , the comet-long-polling folder contains a long polling sample web application that you can  jetty:run run using the mvn command.

  • Pros: It's easy for the client to implement a good error handling system and timeout management. This reliable technique also allows a round-trip between the connection to the server, even if the connection is not persistent (a good thing when your application has many clients). It works on all browsers; you just need to make sure that the  XMLHttpRequest object you use is sent to a simple Ajax request.
  • Disadvantages: There are no significant disadvantages compared to other techniques, like all the techniques we have discussed, this method still relies on stateless HTTP connections, which requires special functionality on the server side to temporarily suspend connect.

Guess you like

Origin http://10.200.1.11:23101/article/api/json?id=326613128&siteId=291194637