反向 Ajax,第 4 部分: Atmosphere 和 CometD

简介

本 系列 文章向您展示如何使用反向 Ajax 技术开发事件驱动的 Web 程序。第 1 部分 介绍了 Reverse Ajax、轮询、流、Comet 和长轮询。第 2 部分 介绍了如何使用 WebSocket 实现 Reverse Ajax,还讨论了使用 Comet 和 WebSocket 的 Web 服务器的限制。第 3 部分 探讨了当您需要支持多个服务器或提供一个用户可以自己的服务器上部署的独立 Web 应用程序时,您实现自己的 Comet 或 WebSocket 通信系统的过程中会遇到的一些困难。即使客户端上的 JavaScript 代码很简单,您仍然需要一些异常处理、重新连接和确认功能。在服务器端,由于缺少全局 API 以及一些 Web 服务器 API,导致需要使用附带抽象功能的框架。第 3 部分还讨论了Socket.IO。

在本文中,您将了解 Atmosphere 和 CometD,它们是用于 Java 服务器的最有名的开源反向 Ajax 库。

您可以下载本文使用的 源代码

先决条件

在理想的情况下,如果想最大限度地利用本文,您应该了解 JavaScript 和 Java。要运行本文中的示例,则需要使用最新版的 Maven 和 JDK(参阅参考资料)。

Atmosphere 框架

Atmosphere 是一种 Java 技术框架,它提供了一个通用 API,以便使用许多 Web 服务器(包括 Tomcat、Jetty、GlassFish、Weblogic、Grizzly、JBossWeb、JBoss 和 Resin)的 Comet 和 WebSocket 特性。它支持任何支持 Servlet 3.0 Specification 的 Web 服务器。在本系列文章展示的所有框架中,Atmosphere 支持的服务器最多。

Atmosphere 可以针对 Comet,Atmosphere 检测本机服务器 API 并切换回 Servlet 3.0 的运行时环境。或者将退回到 "托管" 异步模式(但不具有 Jetty Continuations 的可伸缩性),这也适用于 Comet。Atmosphere 已面市两年多了,仍然处于积极开发阶段。它被应用于大型 Web 应用程序中,比如 JIRA,这是最有名的问题追踪器之一。图 1 显示了 Atmosphere 框架。

图 1. Atmosphere 的构架视图

ajax20151010fig01

Atmosphere 框架由 Atmosphere 运行时所组成,为各种不同 Web 服务器解决方案和标准提供了一个通用 API。在运行时上面,客户端可以通过设置 servlet 并使用 Google Web Toolkit (GWT) 来访问 API 和反向 Ajax 特性。或者,您还可以使用 Jersey,这是一种用于实现 JSR-311(JAX-RS 规范)的框架。因此,可以使用注释的方式将 Atmosphere 用于更多其它的 REST 场景中。配置您选择的模块之后,就可以通过实现一些类(本文稍后进行讨论)来访问 Atmosphere 运行时。您还可以随意使用所提供的插件,添加对集群、消息传递、依赖注入的支持。如果您正在使用一个 Web 框架(Wicket、Struts 或 Spring MVC),您可以通过使用 Atmosphere 的 MeteorServlet 明确添加对反向 Ajax 的支持。该 servlet 展示了Meteor对象,可以在控制器和服务中检索该对象,以便挂起或重新开始请求。

Atmosphere 的优势仍然在于服务器端:它提供了一个标准化 API,该 API 包含与 WebSockets 或 Comet 进行通信的所有不同的解决方案和方法。Atmosphere 没有使用客户端和服务器之间通信的协议,如 Socket.IO 和 CometD。这两个库均提供了客户端 JavaScript 和服务器端 servlet,使用特定的协议(用于握手、消息收发、确认和心跳的协议)进行通信。Atmosphere 的目标是在服务器端提供一个通用的通信信道。如果需要使用特定的协议,比如 Bayeux(CometD 使用的协议),则必须在 Atmosphere 中开发您自己的 “处理程序”。CometD 插件可以满足您的需求:它利用 Atmosphere 的 API 来挂起和重新开始请求,并通过使用 Bayeux 协议来委托 CometD 类来管理 CometD 通信。

Atmosphere 提供了一个 jQuery 客户端库,该库可以使连接设置变得更容易,它能够自动检测可以使用的最佳传输协议(WebSockets 或 CometD)。Atmosphere 的 jQuery 插件的用法与 HTML5 WebSockets API 相似。首先要连接到服务器,注册一个回调来接收消息,然后再放入一些数据。

本文所提供的 源代码 中包含了一个 Atmosphere 样例,该样例直接将处理程序用于 Atmosphere servlet。客户端代码始终保持不变;与本 系列 第 1 部分、第 2 部分、第 3 部分文章中所用的代码相同(都位于使用 Comet 长轮询的聊天样例代码中)。您还可以使用 Atmosphere 的 jQuery 插件,但这并不是必需的,因为 Atmosphere 不强迫使用任何通信协议。强烈建议您查看 Atmosphere 项目中的其他示例,特别是那些使用 JSR-311 注释 (Jersey) 的示例。它们确实简化了处理程序的编写。

清单 1 显示了 Atmosphere 处理程序的接口。

清单 1. AtmosphereHandler 接口
public interface AtmosphereHandler<F, G> { 
    void onRequest(AtmosphereResource<F, G> resource) 
        throws IOException; 
    void onStateChange(AtmosphereResourceEvent<F, G> event) 
        throws IOException; 
    void destroy(); 
}

onRequest 方法接收到来自客户端的所有请求,并决定是否挂起或重新开始请求(或不执行任何操作)。每次挂起或重新开始一个请求、发送一条广播或者出现超时,都会通过 onStateChange 方法发送和接收一个事件。

清单 2 中显示了 Comet 聊天应用程序的 onRequest 方法的实现。

清单 2. AtmosphereHandler 接口 - onRequest
Broadcaster broadcaster = 
        BroadcasterFactory.getDefault().lookup(
        DefaultBroadcaster.class, ChatHandler.class.getName(), true); 
broadcaster.setScope(Broadcaster.SCOPE.APPLICATION); 
resource.setBroadcaster(broadcaster); 
HttpServletRequest req = resource.getRequest(); 
String user = (String) req.getSession().getAttribute("user"); 
if (user != null) { 
    if ("GET".equals(req.getMethod())) { 
        resource.suspend(-1, false); 
    } else if ("POST".equals(req.getMethod())) { 
        String cmd = req.getParameter("cmd"); 
        String message = req.getParameter("message"); 
        if ("disconnect".equals(cmd)) { 
            close(resource); 
        } else if (message != null && message.trim().length() > 0) {
            broadcaster.broadcast("[" + user + "] " + message); 
        } 
    } 
}

典型的惯例是挂起 GET 请求,并使用 POST 请求发送消息。收到消息后,会向所有在广播装置内注册的资源进行广播。注意,该示例未向HttpServlet 输出流方面写入任何内容。广播或挂起操作只发送其他实现方法接收的事件,如 清单 3 中所示:

清单 3. AtmosphereHandler 接口 - onStateChange
Broadcaster broadcaster = 
    BroadcasterFactory.getDefault().lookup(
        DefaultBroadcaster.class, ChatHandler.class.getName(), true); 
// Client closed the connection. 
if (event.isCancelled()) { 
    close(event.getResource()); 
    return; 
} 
try { 
    String message = (String) event.getMessage(); 
    if (message != null) { 
        PrintWriter writer = 
            event.getResource().getResponse().getWriter(); 
        writer.write(message); 
        writer.flush(); 
    } 
} finally { 
    if (!event.isResumedOnTimeout()) { 
        event.getResource().resume(); 
    } 
}

现在您需要一个可正常运转的 Comet 聊天应用程序。简言之,重要的 Atmosphere 概念是:代表连接的资源对象,以及负责触发资源事件并决定挂起或重新开始请求的事件的广播装置。注意,这只是一个 Comet 示例。想要能够使用 WebSocket 和 Comet,则需要使用一个客户端库,以及一个更复杂的处理程序。

表 1 下表概述了使用 Atmosphere 框架的利弊。

表 1. Atmosphere 的利弊
如果您必须在无法控制的多个 Web 服务器中部署一个 Web 应用程序。同时因为 Atmosphere 支持使用多台 Web 服务器,所以它是让应用程序的反向 Ajax 特性正确发挥其作用的更好的选择。

当您需要在原始的反向 Ajax 通信上使用通用 API,如果没有任何已定义好的协议,那么您可能想要开发或扩展协议。

缺少有关 Atmosphere 的架构、项目、概念和 API 的文档,如果您需要研究源代码或分析提供的示例,这些文档对您会很有帮助。与其他框架的简单 API(如 Socket.IO 和 ometD)相比,该 API 的技术性更强,虽然有时有些难懂。即使用了 Atmosphere 注释,有些名称和属性仍然具有很强的技术性。

尽管在服务器端有出色的抽象功能,但没有良好的客户端库,也没有协议,所以其他所有功能都留待开发人员来开发。如果您需要使用高级的超时检测、确认、回退和跨域等功能,特别是在移动设备上使用这些功能时,目前的库太过于简单,无法满足大型、可伸缩 Web 应用程序的需要。在这种情况下,CometD 更可靠一些;它使用了一个可用来激活某些控制流和错误检测的通信协议,CometD 中提供了所有这些功能。如果您需要额外的特性,使用 CometD JavaScript 客户端和 Atmosphere CometD 插件是一个很不错的替代方案。

CometD 框架

CometD 框架是基于 HTTP 的事件驱动通信解决方案,已面市多年。CometD 框架的第 2 版本中添加了对注释配置和 WebSocket 的支持。CometD 框架提供了一个 Java 服务器部件和一个 Java 客户端部件,还有一个基于 jQuery 和 Dojo 的 JavaScript 客户端库。CometD 使用了一个叫 Bayeux 的标准化通信协议,充许您针对于消息确认、流控制、同步、集群等方面进行扩展。

CometD 的事件驱动方法非常适合事件驱动 Web 开发的新概念。正如传统的桌面用户界面那样,所有的组件均通过总线进行通信,以便发送通知和接收事件。因此所有的通信都是异步的。

CometD 框架:

  • 有大量的相关资料。
  • 提供了一些示例和 Maven 原型,以促进项目的启动。
  • 拥有精心设计的 API,支持扩展开发。
  • 提供了一个称为 Oort 的集群模块,该模块使您能够运行多个CometD Web 服务器,将它们看作是负载均衡器背后的集群中的节点,从而扩展许多 HTTP 连接。
  • 支持安全策略,允许更具体地配置由谁通过哪条通道发送消息。
  • 更好地与 Spring 和 Google Guice(依赖注入框架)集成。

Bayeux 协议

Bayeux 通信协议主要是基于 HTTP。它提供了客户端与服务器之间的响应性双向异步通信。Bayeux 协议基于通道进行通信,通过该通道从客户端到服务器、从服务器到客户端或从客户端到客户端(但是是通过服务器)路由和发送消息。Bayeux 是一种 “发布- 订阅” 协议。CometD 实现了 Bayeux 协议,除了 Comet 和 WebSocket 传输协议之外,它还提供了一个抽象层,以便通过 Bayeux 发送请求。

服务器和内部构件

CometD 与三个传输协议绑定在一起:JSON、JSONP 和 WebSocket。他们都依赖于 Jetty Continuations 和 Jetty WebSocket API。在默认情况下,可以在 Jetty 6、Jetty 7、和 Jetty 8 中以及其他所有支持 Servlet 3.0 Specification 的服务中使用 CometD。可以使用与扩展相同的方式添加和开发传输协议。您应该能够编写支持 Grizzly WebSocket API 和其他 API 的传输协议,然后再在配置 CometD 服务器的步骤中添加这些协议。图 2 显示了主要 CometD 数据块的概述。

图 2. CometD 的构架视图

ajax20151010fig02

图 2图中没有显示访问消息通道的安全层。

本文提供的 源代码 包括一个使用 CometD 的 Web 应用程序。该 Web 应用程序的描述符包括 清单 4 中的定义,以便使用该聊天示例。

清单 4. web.xml
<servlet> 
    <servlet-name>cometd</servlet-name> 
    <servlet-class>
        org.cometd.java.annotation.AnnotationCometdServlet
    </servlet-class> 
    <async-supported>true</async-supported> 
    [...]
    <init-param> 
        <param-name>services</param-name> 
        <param-value>ChatService</param-value> 
    </init-param> 
    <init-param> 
        <param-name>transports</param-name> 
        <param-value>
            com.ovea.cometd.websocket.jetty8.Jetty8WebSocketTransport
        </param-value> 
    </init-param> 
</servlet>

CometD servlet 支持一些控制全局设置的选项,比如能够设置传输协议和服务的选项。在本示例中,假设您想要为 Jetty 8 添加 WebSocket 支持。服务器端的 CometD 服务类 ChatService 会控制每个人说话的聊天室,如 清单 5 中所示:

清单 5. CometD ChatService
@Service 
public final class ChatService { 

    @Inject 
    BayeuxServer server; 

    @PostConstruct 
    void init() { 
        server.addListener(new BayeuxServer.SessionListener() { 
            @Override 
            public void sessionAdded(ServerSession session) { 
                [...]
            } 

            @Override 
            public void sessionRemoved(ServerSession session, boolean timedout) {
                [...]
            } 
        }); 
    } 

    @Configure("/**") 
    void any(ConfigurableServerChannel channel) { 
        channel.addAuthorizer(GrantAuthorizer.GRANT_NONE); 
    } 

    @Configure("/chatroom") 
    void configure(ConfigurableServerChannel channel) { 
        channel.addAuthorizer(new Authorizer() { 
            @Override 
            public Result authorize(
                [..] // check that the user is in session
            } 
        }); 
    } 

    @Listener("/chatroom") 
    void appendUser(ServerSession remote, 
                    ServerMessage.Mutable message) { 
        [...]
    } 

}

清单 5 演示了 CometD 的一些重要特性,其中包括 :

  • 依赖注入
  • 生命周期管理
  • 全局信道配置
  • 安全管理
  • 消息转换(在所有消息前添加用户名)
  • 会话管理

在客户端,该示例没有进行任何扩展,只是使用原始的 CometD 代码,如 清单 6 中所示:

清单 6. CometD 客户端代码
// First create t cometd object and configure it

var cometd = new $.Cometd('CometD chat client'); 
cometd.configure({ 
    url: document.location + 'cometd', 
    logLevel: 'debug' 
}); 
cometd.websocketEnabled = 'WebSocket' in window;

// Then we register some listeners. Meta channels (those with 
// the form /meta/<name> are specific reserved channels)

cometd.addListener('/meta/disconnect', function(message) { 
    [...]
}); 

cometd.addListener('/meta/connect', function(message) { 
    [...]
});

// Then, starting a connexion can be done using:

cometd.handshake();

// And subscriptions with:

cometd.subscribe('/chatroom', function(event) { 
    [...] //  event.data holds the message
});

// We finally send data to the chatroom like this:

cometd.publish('/chatroom', msg);

CometD 的客户端 API 功能强大、可以扩展并且易于理解和使用。本文只介绍了 Web 应用程序的主要部件,所以请参见 示例应用程序,以便更好地了解 CometD 的强大。

表 2 概括了使用 CometD 框架的利与弊。

表 2. CometD 的利与弊
CometD 提供了一套从客户端到服务器端以及从独立 Java 客户端到服务器的完整解决方案。该框架拥有大量的相关文档、一个出色的 API,并且易于使用。最重要的是,它拥有一个事件驱动方法。CometD 和 Bayeux 是许多事件驱动 Web 应用程序的组成部分。其他的反向 Ajax 框架并不提供任何事件驱动机制,这迫使最终用户必须开发自己的定制解决方案。

CometD 支持许多必要的特性,如重新连接、可靠的超时检测、回退、批处理、消息确认以及更多您在其他反向 Ajax 框架中无法找到的特性。CometD 允许实现最可靠、延迟最短的通信。

CometD 目前除了支持 Jetty for Comet (Tomcat) 之外,并不支持任何 Servlet 2.5 容器,也不支持 Glassfish/Grizzly WebSocket。

结束语

Atmosphere 和 CometD 都是可靠的开源反向 Ajax 解决方案。在 Ovea,我们的选择是 CometD,因为我们要为集群环境中的移动设备开发可伸缩的事件驱动 Web 应用程序,并且我们可以完全控制基础架构(我们使用了 Jetty)。然而,如果您要销售 Web 应用程序,并且想让您的反向 Ajax 特性可以在尽可能多的服务器上正常运作,不进行额外开发的话,CometD 未必是最好的选择。但是,现在越来越多的 Web 容器都支持 Servlet Specification 3.0,这极大地减少了 CometD 的限制。说起传输层,主要的区别目前仍然取决于 WebSocket 支持。

下载

描述 名字 大小
文章的源代码 reverse_ajaxpt4_source 28KB

参考资料

学习

  • 阅读本系列前先的文章:
  • Atmosphere:了解更多关于便携式 AjaxPush/Comet 和 WebSocket 框架的信息。
  • CometD:阅读了解所有有关 CometD 项目的信息。 @ The Dojo Foundation.
  • JSR 315: Java Servlet 3.0 Specification 是 2.5 规范的升级版。
  • 阅读 IBM WebSphere Application Server V8.0 发布公告。
  • "Google AppEngine uses Jetty!" 描述了 Google 的 Jetty 定制。
  • 阅读关于 Jetty Continuations 及其特性的更多信息。
  • 查看使用 Grizzly 的 Glassfish WebSockets 示例。
  • WebSphere Application Server:了解 IBM WebSphere 中的更多旗舰产品。
  • WebSphere 8(支持 Comet ):提供应用程序环境的智能管理并有助于快速交付丰富的用户体验。
  • Socket.IO:的目标是使得在每个浏览器和移动设备上实现实时应用程序成为可能,减少不同传输协议机制之间的差异。
  • Starting With WebSockets 教程查看了在 PHP 中运行 WebSocket 服务器的过程,并介绍了如何构建客户端,以便通过 WebSocket 协议来发送和接收消息。
  • "The WebSocket API"(W3C0,2011 年 8 月):该规范定义了一个 API,使 Web 页面能够使用 WebSocket 协议与远程主机进行双向通信。
  • 阅读了解关于 Servlet 3.0 中的异步处理支持 的更多信息。
  • Philip McCarthy 的博客提供了更多关于 Comet & Java: Threaded Vs Nonblocking I/O 的更多信息。
  • The Rox Java NIO Tutorial 收集了作者使用 Java NIO 库的经验,以及扰乱 Internet 的几十种提示、技巧、建议和警告。
  • 在 Wikipedia 上,阅读关于以下各项的信息:
    • AJAX
    • Reverse-Ajax
    • WebSockets
  • Exploring Reverse AJAX:介绍了一些 Reverse-Ajax 技巧。
  • "使用 JSONP 实现跨域通信,第 1 部分: 结合 JSONP 和 jQuery 快速构建强大的 mashup" (developerWorks, 2009 年 2 月):了解如何结合使用晦涩的交叉域调用技巧 (JSONP) 和灵活的 JavaScript 库 (jQuery) 快速构建强大的混搭。
  • "用 Ext JS 构建 Ajax 应用程序"(developerWorks,2008 年 7 月):获得对 Ext JS 之后出现的一些面向对象的 JavaScript 设计概念的概述,并展示了如何使用 Ext JS 框架获得丰富的 Internet 应用程序 UI 元素。
  • "JavaScript 框架比较" (developerWorks,2010 年 2 月):获得明显改进 JavaScript 开发的框架的概述。
  • "掌握 Ajax,第 2 部分:使用 JavaScript 和 Ajax 进行异步请求"(developerWorks,2006 年 1 月):学习如何使用 Ajax 和 XMLHttpRequest 对象创建不会让用户等待服务器响应的请求/响应模型。
  • "开发移动 Web Ajax 应用" (developerWorks,2010 年 3 月):了解如何使用 Ajax 构建交叉浏览器智能手机的 Web 应用程序。
  • "在应用程序中使用 Ajax 的时机" (developerWorks,2008 年 2 月):了解如何使用 Ajax 改善您的网站,同时避免糟糕的用户体验。
  • "改善 Web 2.0 应用程序的性能" (developerWorks,2009 年 12 月):探索不同的浏览器端缓存机制。
  • "介绍 JSON" (JSON.org):获得 JSON 句法的介绍。
  • developerWorks 中国网站 Web 开发专区:寻找介绍各种基于 Web 的解决方案的文章。
  • 查看 HTML5 专题,了解更多和 HTML5 相关的知识和动向。

获得产品和技术

  • Atmosphere 源代码:获取 Atmoshphere,这是一个使用 Inversion of Control (IoC) 的基于 POJO 的框架,它将 push/Comet 和 Websocket 带给了大众。
  • CometD 源代码:获取在 Web 消息收发中实现可伸缩 Comet(服务器推送技术 )的项目。
  • CometD 扩展:从 Ovea 获取对 Jetty 8 WebSocket 和对 Google Guice 的支持。
  • Jetty:获得 Jetty、Web 服务器和 javax.servlet 容器,此外,提供对 WebSockets 的支持。
  • Apache Tomcat Advanced I/O documentation:获得有价值的链接、用户指南、参考和 Apache Tomcat Development 说明。
  • Grizzly NIO 框架:帮助您充分利用 Java NIO API 的优势。
  • Grizzly Comet 例子:了解在重新构建新的应用程序之前需要对现有应用程序进行的修改。
  • Glassfish Application 服务器:获得主要的 GlassFish Server 开源版本。
  • Socket.IO Java:获得原项目和最新的 Ovea 派生产品。
  • Apache Maven:获得 Maven,这是一个软件项目管理和理解工具。
  • Java Development Kit, Version 6:获得 Java Platform, Standard Edition (Java SE),它使您能够在桌面和服务器以及当今充满挑战的嵌入环境中开发和部署 Java 应用程序。
  • 免费试用 IBM 软件。下载试用版本,登录在线试用版本,在沙箱环境中使用产品,或是通过云来访问它。有超过 100 种 IBM 产品试用版可供您选择。

讨论

  • 加入 developerWorks 中文社区。查看开发人员推动的博客、论坛、组和维基,并与其他 developerWorks 用户交流。

本文来源:http://www.ibm.com/developerworks/cn/web/wa-reverseajax4/


如果给你带来帮助,欢迎微信或支付宝扫一扫,赞一下。