彗星http
在过去的几年中,Web开发有了长足的发展。 我们超越了链接在一起的静态网页,这导致浏览器刷新并等待页面加载。 现在,需求是可以从Web访问的完全动态的应用程序。 这些应用程序通常需要尽可能快并提供几乎实时的组件。 在这个由五部分组成的新系列中,学习如何使用反向Ajax技术开发事件驱动的Web应用程序。
在第一篇文章中,了解反向Ajax,轮询,流,Comet和长轮询。 了解如何实现不同的Reverse Ajax通信技术,并探讨每种方法的优缺点。 您可以下载源代码以遵循本文中的示例。
异步JavaScript和XML(Ajax),一种可在JavaScript中访问的浏览器功能,允许脚本向幕后的网站发出HTTP请求,而无需重新加载页面。 Ajax已有十多年的历史了。 尽管名称包括XML,但是您几乎可以在Ajax请求中传输任何内容。 最常用的数据是JSON,它接近JavaScript语法并且消耗的带宽更少。 清单1显示了一个Ajax请求示例,该请求从其邮政编码中检索一个地点的名称。
var url = 'http://www.geonames.org/postalCodeLookupJSON?postalcode='
+ $('#postalCode').val() + '&country='
+ $('#country').val() + '&callback=?';
$.getJSON(url, function(data) {
$('#placeName').val(data.postalcodes[0].placeName);
});
您可以在本文的可下载源代码中的listing1.html中看到此示例工作。
反向Ajax本质上是一个概念:能够从服务器向客户端发送数据。 在标准HTTP Ajax请求中,数据被发送到服务器。 可以通过本文中介绍的特定方式模拟反向Ajax发出Ajax请求,以便服务器可以尽快将事件发送给客户端(低延迟通信)。
来自HTML5的WebSockets是一种更新得多的技术。 许多浏览器已经支持它(Firefox,Google Chrome,Safari和其他)。 WebSockets启用双向全双工通信通道。 该连接是通过一种称为WebSocket握手的HTTP请求打开的,带有一些特殊的标头。 连接保持活动状态,您可以使用JavaScript编写和接收数据,就像使用原始TCP套接字一样。 WebSocket将在本系列的第2部分中得到更多介绍。
反向Ajax的目标是让服务器将信息推送到客户端。 默认情况下,Ajax请求是无状态的,并且只能从客户端打开到服务器。 您可以通过使用模拟服务器和客户端之间响应式通信的技术来绕过此限制。
轮询涉及从客户端向服务器发出请求以请求一些数据。 这显然只是一个Ajax HTTP请求。 为了尽快获取服务器事件,轮询间隔(请求之间的时间)必须尽可能短。 有一个缺点:如果缩短此间隔,则客户端浏览器将发出更多请求,其中许多请求将不会返回任何有用的数据,并且会浪费带宽和处理资源而无所作为。
图1中的时间轴显示了客户端发出的一些轮询请求如何返回但没有信息。 客户端必须等待下一次轮询才能获取服务器接收到的两个事件。
JSONP轮询本质上与HTTP轮询相同。 但是,区别在于,使用JSONP可以发出跨域请求(请求不在您的域中)。 清单1中使用JSONP从邮政编码获取地名。 JSONP请求通常可以通过其回调参数和返回的内容(可执行JavaScript代码)来识别。
要在JavaScript中实现轮询,可以使用setInterval
定期发出Ajax请求,如清单2所示:
setInterval(function() {
$.getJSON('events', function(events) {
console.log(events);
});
}, 2000);
文章源代码中的轮询演示通过轮询方法显示了带宽消耗。 间隔很短,但是您可以看到一些请求不返回任何事件。 清单3显示了示例轮询的输出。
[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
使用JavaScript进行轮询有其优点和缺点。
ggy带式轮询比轮询更聪明,因为它倾向于删除所有不需要的请求(不返回任何数据)。 没有间隔; 当客户端需要向服务器发送请求时,发送请求。 区别在于响应,该响应分为两部分:请求的数据的响应和服务器事件(如果有的话)。 图2显示了一个示例。
在实施搭载技术时,通常,所有针对服务器的Ajax请求都可能返回混合响应。 文章下载和下面的清单4中提供了一个实现示例。
$('#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显示了一些搭载输出。
[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
您可以看到表单验证的结果以及附加到响应的事件。 同样,该方法也有优点和缺点。
带有轮询或背负的反向Ajax非常有限:它不能扩展并且不提供低延迟通信(当事件一到达服务器就到达浏览器)。 Comet是一种Web应用程序模型,在该模型中,请求被发送到服务器并保持很长一段时间,直到发生超时或服务器事件。 请求完成后,将发送另一个长期存在的Ajax请求,以等待其他服务器事件。 使用Comet,Web服务器可以将数据发送到客户端,而无需显式请求它。
Comet的最大优势在于,每个客户端始终都有一条通向服务器的通信链接。 服务器可以通过在客户端到达时立即提交(完成)响应来在客户端上推送事件,或者甚至可以累积并发送突发。 由于请求长时间保持打开状态,因此服务器端需要特殊功能来处理所有这些长期存在的请求。 图3显示了一个示例。 (本系列的第2部分将更详细地说明服务器约束。)
Comet的实现可以分为两种类型:使用流模式的实现和使用长轮询的实现 。
在流模式下,将打开一个持久连接。 因为到达服务器端的每个事件都是通过同一连接发送的,所以只有一个长期请求( 图3中的 #1)。 因此,它在客户端需要一种方法来分离通过同一连接的不同响应。 从技术上讲,两种常见的流技术包括Forever Iframe(隐藏的IFrame)或XMLHttpRequest
对象的多部分功能,用于在JavaScript中创建Ajax请求。
Forever Iframes技术涉及在页面中放置一个隐藏的Iframe标签,其src
属性指向返回服务器事件的servlet路径。 每次接收到一个事件时,该servlet都会编写并刷新其中包含JavaScript代码的新脚本标签。 iframe内容将被附加此脚本标记,该脚本标记将被执行。
XMLHttpRequest
第二种更为可靠的技术是在XMLHttpRequest
对象上使用某些浏览器(例如Firefox)支持的多部分标志。 发送Ajax请求,并在服务器端保持打开状态。 每次事件到来时,都会通过相同的连接写入多部分响应。 清单6显示了一个示例。
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显示了如何暂停HTTP流请求。 (本系列的第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);
}
现在,每次事件发生时,您都可以遍历所有挂起的连接并将数据写入它们,如清单8所示:
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-streaming文件夹中的HTTP流。 运行示例并打开主页时,您将看到事件一到达服务器便立即异步异步出现。 另外,如果打开Firebug控制台,则可以看到仅打开了一个Ajax请求。 如果您更深入地看,您将在Response选项卡中看到JSON响应,如图4所示:
像往常一样,有优点和缺点。
长轮询模式涉及打开连接的技术。 服务器将保持连接的打开状态,一旦发生事件,将提交响应并关闭连接。 然后,客户端会立即重新打开新的长轮询连接,以等待新事件到达。
您可以使用脚本标记或仅XMLHttpRequest
对象来实现HTTP长轮询。
与iframe一样,目标是在页面中附加一个脚本代码以使脚本得以执行。 服务器将:暂停连接直到发生事件,将脚本内容发送回浏览器,然后重新打开另一个脚本标签以获取下一个事件。
XMLHttpRequest
不允许在其他域或子域上进行请求)。 XMLHttpRequest
长轮询 推荐的第二个实现Comet的方法是向服务器打开Ajax请求,然后等待响应。 服务器需要服务器端具有特定功能才能暂停请求。 一旦事件发生,服务器就将挂起的请求中的响应发送回并关闭它,就像关闭Servlet响应的输出流一样。 然后,客户机使用响应并向服务器打开一个新的长期Ajax请求,如清单9所示:
function long_polling() {
$.getJSON('ajax', function(events) {
processEvents(events);
long_polling();
});
}
long_polling();
在后端,该代码还使用Servlet 3 API来暂停请求,就像在HTTP流中一样,但是您不需要所有的多部分处理代码。 清单10显示了一个示例。
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
AsyncContext asyncContext = req.startAsync();
asyncContext.setTimeout(0);
asyncContexts.offer(asyncContext);
}
收到事件后,只需处理所有暂停的请求并完成它们,如清单11所示:
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();
}
在随附的可下载源文件中 ,彗星长轮询文件夹包含一个长轮询示例Web应用程序,您可以使用mvn jetty:run
命令运行该应用程序。
XMLHttpRequest
对象。 由于所有现代浏览器都支持跨域资源共享(CORS)规范,该规范允许XHR执行跨域请求,因此不再需要基于脚本和基于iframe的技术。
为反向Ajax实现和使用Comet的最佳方法是通过XMLHttpRequest
对象,该对象提供了真正的连接句柄和错误处理。 考虑到并非所有浏览器都支持多部分标志,并且多部分流可能会遇到缓冲问题,因此建议您通过HTTP长轮询将Comet与XMLHttpRequest
对象一起使用(服务器上已挂起的简单Ajax请求)侧)。 所有支持Ajax的浏览器也都支持此方法。
本文介绍了反向Ajax技术。 它探索了实现反向Ajax通信的不同方法,并解释了每种实现的优点和缺点。 您的特定情况和应用程序要求将影响哪种方法最适合您。 但是总的来说,如果需要最大的折衷,Comet与Ajax长轮询请求是最好的选择。 超时和错误检测; 简单; 并获得所有浏览器和平台的良好支持。
请继续关注本系列的第2部分,它将探讨第三种反向Ajax技术:WebSockets。 尽管并不是所有的浏览器都支持它,但是WebSockets绝对是Reverse Ajax的一种很好的通信媒介。 WebSocket将删除所有与HTTP连接的无状态特性相关的约束。 第2部分还将介绍由Comet和WebSocket技术引起的服务器端约束。
翻译自: https://www.ibm.com/developerworks/web/library/wa-reverseajax1/index.html
彗星http