彗星http

本系列向您展示如何使用反向Ajax技术开发事件驱动的Web应用程序。 第1部分介绍了反向Ajax,轮询,流传输,Comet和长轮询。 第2部分解释了如何使用WebSockets实现反向Ajax,并讨论了使用Comet和WebSockets的Web服务器的局限性。 第3部分展示了如果需要支持多台服务器或为用户提供独立的Web应用程序以供用户部署在自己的服务器上,则实现自己的Comet或WebSockets通信系统可能会很困难。 即使客户端JavaScript代码很简单,您仍需要一些异常处理,重新连接和确认功能。 在服务器端,缺少全局API和多个Web服务器API导致需要框架,从而带来了抽象。 第3部分还讨论了Socket.IO。

在本文中,了解Atmosphere和CometD。 它们是Java服务器上最广为人知的开源反向Ajax库。

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

先决条件

理想情况下,要充分利用本文,您应该了解JavaScript和Java。 要运行本文中的示例,您还将需要最新版本的Maven和JDK(请参阅参考资料 )。

大气框架

Atmosphere是一个Java技术框架,提供了用于使用许多Web服务器(包括Tomcat,Jetty,GlassFish,Weblogic,Grizzly,JBossWeb,JBoss和Resin)的Comet和WebSocket功能的通用API。 也支持任何支持Servlet 3.0规范的Web服务器。 在本系列文章中介绍的框架中,Atmosphere支持最多的服务器。

Atmosphere可以检测本机服务器API(适用于Comet和WebSocket),并切换回Servlet 3.0(如果适用)以支持Comet。 或者,同样对于Comet,它将退回到“托管”异步模式(但可扩展性不如Jetty Continuations)。 大气已经存在了两年多,并且仍在积极发展中。 它用于大型Web应用程序中,例如JIRA,它是最著名的问题跟踪器之一。 图1显示了Atmosphere架构。

图1.大气的架构视图

Atmosphere框架由Atmosphere运行时组成,该运行时为所有不同的Web服务器解决方案和标准提供了通用的API。 除此之外,客户端只需设置一个servlet就可以通过Google Web Toolkit(GWT)访问API和Reverse Ajax功能。 或者,您也可以使用Jersey(实现JSR-311(JAX-RS规范)的框架)。 因此,可以在提供额外注释的静态服务中使用Atmosphere。 配置所选模块后,您可以通过实现一些类(在本文后面讨论)来访问Atmosphere运行时。 您还可以选择使用某些提供的插件来添加对集群,消息传递,依赖项注入等的支持。 如果您使用的是Web框架(Wicket,Struts,Spring MVC),则可以使用Atmosphere的MeteorServlet透明地添加反向Ajax支持。 该Servlet公开了一个Meteor对象,该对象可以在您的控制器和服务中检索以暂停或恢复请求。

Atmosphere的优势仍然在于服务器端:它提供了一个标准化的API,涵盖了所有不同的解决方案以及与WebSockets或Comet进行通信的方式。 Atmosphere不使用客户端和服务器之间的协议,例如Socket.IO和CometD。 这两个库都提供了客户端JavaScript和服务器servlet,它们使用特定的协议(用于握手,消息传递,确认和心跳)进行通信。 Atmosphere的目标是在服务器端提供一个通用的通信通道。 如果需要使用特定协议,例如Bayeux(CometD使用的协议),则必须在Atmosphere中开发自己的“处理程序”。 CometD插件正是这样做的:它利用Atmosphere的API来暂停和恢复请求,并委托给CometD类来使用Bayeux协议管理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(); 
    } 
}

现在,您拥有进行彗星聊天所需的全部内容。 总而言之,重要的Atmosphere概念是:代表连接的资源对象,以及负责向资源触发事件并决定何时暂停或恢复请求的广播公司。 请注意,该示例仅是Comet。 为了能够使用WebSockets和Comet,应该使用客户端库,并且需要更复杂的处理程序。

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

表1.大气的利弊

优点

缺点

如果必须在无法控制的多个Web服务器中部署Web应用程序。 由于Atmosphere支持的Web服务器数量众多,因此您更有可能使应用程序的Reverse Ajax功能正常工作。

当您需要通过原始Reverse Ajax通信进行通用API且未定义任何协议时,因为您要开发或扩展它。

缺少有关Atmosphere的架构,项目,概念和API的文档,如果您需要进入源代码或分析几个提供的示例,这将很有帮助。 与其他框架(如Socket.IO和CometD)的简单API相比,该API具有很高的技术性,有时甚至是晦涩的。 即使使用Atmosphere批注,某些名称和属性也太技术化。

尽管在服务器端是一个很好的抽象,但是没有一个好的客户端库。 没有协议,因此所有其他功能都留给开发人员。 如果您需要高级超时检测,确认,退避,跨域等等,尤其是在使用移动设备时,当前的库对于大型可伸缩Web应用程序的需求而言太简单了。 在这种情况下,CometD更可靠。 它利用了可用于激活一些控制流和错误检测的通信协议,所有这些都在CometD中提供。 如果需要其他功能,将CometD JavaScript客户端与Atmosphere CometD插件一起使用可能是一个不错的选择。

CometD框架

CometD框架是基于HTTP的事件驱动的通信解决方案,已经存在了几年。 版本2增加了对注释配置和WebSockets的支持。 CometD框架提供了Java服务器部分和Java客户端部分,以及基于jQuery和DojoJavaScript客户端库。 CometD使用一种称为Bayeux的标准化通信协议,使您可以激活某些扩展,以进行消息确认,流控制,同步,群集等。

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

CometD框架:

  • 有据可查。
  • 提供示例和Maven原型以促进项目的启动。
  • 具有精心设计的API,可以进行扩展开发。
  • 提供一个称为Oort的群集模块,该模块使您能够将多个CometD Web服务器作为负载平衡器后面的群集中的节点运行,以扩展到更多的HTTP连接。
  • 支持安全策略,以允许细粒度配置谁可以通过哪个通道发送消息。
  • 与Spring和Google Guice(依赖注入框架)的集成非常好。

贝叶协议

Bayeux通信协议主要基于HTTP。 它异步提供客户端和服务器之间的响应双向通信。 Bayeux协议基于消息路由的通道,这些消息被路由并从客户端到服务器,服务器到客户端或客户端到客户端(但通过服务器)传递。 Bayeux是一种发布-订阅协议。 CometD实现了Bayeux协议,因此在Comet和WebSocket传输之上提供了一个抽象层,以通过Bayeux路由请求。

服务器和内部

CometD与三种传输方式捆绑在一起:JSON,JSONP和WebSocket。 它们取决于Jetty Continuations和Jetty WebSocket API。 默认情况下,CometD可以在Jetty 6、7和8以及支持Servlet 3.0规范的任何其他服务器中使用。 可以通过与扩展相同的方式来添加和开发传输。 您应该能够编写支持Grizzly WebSocket API等的传输,然后在配置CometD服务器的步骤中添加它们。 图2显示了CometD主要块的概述。

图2. CometD的体系结构视图

图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应用程序的一部分。 其他Reverse Ajax框架不提供任何事件驱动的机制,迫使最终用户开发自己的自定义解决方案。

CometD支持许多必需的功能,例如重新连接,可靠的超时检测,后退,批处理,消息确认以及其他在其他Reverse Ajax框架中找不到的功能。 CometD使您可以实现最可靠,低延迟的通信。

CometD当前不支持Jetty for Comet(Tomcat)以外的任何Servlet 2.5容器,并且不支持Glassfish / Grizzly WebSocket。

结论

Atmosphere和CometD都是可靠的开源反向Ajax解决方案。 在Ovea,我们之所以选择CometD,是因为我们为集群环境中的移动设备开发了可伸缩的事件驱动的Web应用程序,并且我们对基础架构拥有完全的控制权(我们使用Jetty)。 但是,如果没有其他开发,如果您要出售Web应用程序并希望您的Reverse Ajax功能在尽可能多的服务器上运行,CometD可能不是最佳选择。 但是,知道现在有越来越多的Web容器支持Servlet Specification 3.0,CometD的限制趋于减少。 说到传输层,剩下的主要区别现在取决于WebSocket支持。


翻译自: https://www.ibm.com/developerworks/web/library/wa-reverseajax4/index.html

彗星http