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

    6
    Java Ajax XML C/C++ Go 20822 次瀏覽
    英文原文:Reverse Ajax, Part 4: Atmosphere and CometD

      前言

      這一系列文章展示了如何使用反向Ajax技術開發事件驅動的web應用,第1部分內容介紹了反向Ajax(Reverse Ajax)、polling(輪詢)、streaming(流)、Comet和長輪詢(long polling);第2部分內容介紹了如何使用WebSocket來實現反向Ajax,并討論了使用Comet和WebSocket的web服務器的局限性;第3部分內容說明的是,如果需要支持多種服務器或是為用戶提供一個部署在他們自己的服務器上的獨立web應用的話,實現自己的Comet或是WebSocket通信系統會存在一些難處。即使客戶端的JavaScript代碼很簡單,但你需要用到一些異常處理、重連接和確認功能。在服務器端,全局性API的缺失和多種web服務器API導致了對框架的需求,這帶來了一層抽象,第3部分內容還談到了Socket.IO。

      在本文中,我們了解Atmosphere和CometD,它們是最廣為人知的Java服務器的開源反向Ajax庫。

      你可以下載本文中使用的源代碼。

      前提條件

      理想情況下,要充分體會本文的話,你應該對JavaScrpit和Java有一定的了解。若要運行本文中的例子,你還需要最新版本的Maven和JDK。

      Atmosphere框架

      Atmosphere是一個Java技術框架,其提供了通用的API來使用許多web服務器的Comet和WebSocket,這些web服務器包括了Tomcat、Jetty、GlassFish、Weblogic、Grizzly、JBossWeb、JBoss和Resin,其還支持任何支持Servlet 3.0規范的web服務器。在本系列文章提到的各個框架中,Atmosphere支持的服務器最多。

      Atmosphere可以檢測本地化的服務器端API(針對Comet和WebSocket),對于Comet來說,如果可用的話,就切換回Servlet3.0;或者,依然是針對Comet,其會回退到一種“受管”的異步模式中(但沒有達到Jetty Continuation的那種可伸縮性)。Atmosphere的存在已經超過了兩年的時間,現在依然在處在活躍的發展階段。其被用在大型的web應用中,比如說JIRA,這是一個最有名的問題追蹤器。圖1給出了Atmosphere的架構。

      圖1. Atmosphere的架構一覽

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

      Atmosphere由Atmosphere運行時組成,其為所有不同的web服務器解決方案和標準提供了一個通用的API。在這之上,客戶端可以設置一個簡單的servlet來通過Google Web Toolkit(GWT)訪問該API和反向Ajax功能。或者,你也可以使用Jersey,一個實現了JSR-311(JAX-RS規范)的框架。有了所提供的額外注解,因此Atmosphere可用在RESTful服務中。在配置了所選擇的模塊后,你就可以通過實現一些類來訪問Atomsphere運行時(本文稍后會討論到)。你還可以選擇使用一些提供的插件,這些插件增加了對集群、消息、依賴注入等的支持。如果你正在使用一個web框架(Wecket、Struts、Spring MVC)的話,則可以使用Atmosphere的MeteorServlet來透明地添加反向Ajax支持。這一Servlet暴露出一個Meteor對象,該對象可在你的控制器內部檢索到,用來掛起或是恢復請求。

      Atmosphere的強大停留在服務器端:其提供一個了標準的API,該API覆蓋了所有與WebSocket或是Comet通信的不同解決方案和方法。Atmosphere并未用到客戶端和服務器端之間的協議,比如說Socket.IO和CometD等,這兩種庫都提供了一個客戶端的JavaScript和一個服務器端的servlet,它們的通信用到了一種特定的協議(握手、消息、確認和心跳)。Atmosphere的目標是在服務器端提供一種通用的通信信道。如果你需要用到某種特定協議的話,比如說Bayeux(CometD用到的一個協議),就需要在Atmosphere中開發自己的“處理程序”。CometD插件就是這樣做的:其利用了Atmosphere的API來掛起和恢復請求,并委托CometD的類來管理使用了Bayeux協議的CometD通信。

      Atmosphere所帶的JQuery客戶端庫方便了連接的建立,其能夠自動檢測最好的可用傳輸方式(WebSocket或是CometD)。Atmosphere的jQuery插件的用法類似于HTML5 WebSocket API,首先你連接到服務器端,注冊一個回調來接收信息,然后就可以推一些數據了。

      本文中的源代碼包含了一個Atmosphere例子,該類直接用到了一個使用Atmosphere servlet的處理程序。客戶端的代碼則始終是相同的;與本系列的第1、2和3部分用戶的代碼一樣(使用Comet長輪詢的聊天例子)。你有可能使用了Atmosphere的JQuery插件,但這不是必須的,因為Atmosphere并不強制使用任何的通信協議。強烈建議你研究一下Atmosphere項目中的其他例子,特別是用到了JSR-311注解(Jersey)的那些,它們真正地簡化了處理程序的編寫。

      清單1. AtmosphereHandler接口

    public interface AtmosphereHandler {  void onRequest(AtmosphereResource resource)  throws IOException;  void onStateChange(AtmosphereResourceEvent event)  throws IOException;  void destroy();
    }

      onRequest方法接收來自客戶端的所有請求并決定是掛起還是恢復它們(或什么也不做),每次掛起或是恢復一個請求、發送一個廣播或是有超時發生時,就會發送一個由onStateChange方法接收的事件。

      Comet聊天例子的onRequest方法實現如清單2所示。

      清單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();
      }
    }

      現在你已經具備了用來運作Camet聊天例子的所有所需,概括來說,Atmosphere的一些重要概念是:資源對象描述連接,廣播器負責觸發資源事件并決定何時掛起或是恢復一個請求。需要注意的是,該例子只適用于Comet,若要能夠使用WebSocket和Comet兩者的話,應該要使用某種客戶端庫,且需要一個更復雜的處理程序。

      表1列出了使用Atmosphere框架的利弊。

      表1. Atmosphere的優點和缺點 

    1. 優點

    如果需要把web應用部署在你不能自己決定的幾種web服務器上,那么因為Atmosphere支持許多種web服務器,所以你的應用的反向Ajax功能能夠正確工作的機會大大增加。

    在沒有定義了任何協議的原始的反向Ajax通信之上,因為想要開發或是擴展它,這時你會需要一個通用的API。

    2. 缺點

    缺乏關于Atmosphere的架構、項目、概念和API的文檔,如果需要深入源代碼或是分析一些提供的例子的話,這些文檔很有幫助。相比于諸如Socket.IO和CometD一類的其他框架的簡單API來說,其API的技術性很強,有些很晦澀。即使是在使用Atmosphere的注解時,某些名稱和屬性也過于專業了。

    雖然在服務器端有很好的抽象,但卻沒有一個很好的客戶端庫。因為沒有協議,故所有的其他功能都留給了開發者來實現。對于一個大的、可伸縮的web應用來說,如果你需要高級的時間檢測、確認、回退、跨域等功能的話,特別是在移動設備上運行時,那么目前的庫就太過簡單了。在這種情況下,CometD更為可靠一些;其利用了一個能夠用來激活某些控制流和錯誤檢測的通信協議,所有的這些都在CometD內部提供。如果你需要額外的功能的話,使用Atomsphere CometD插件所帶的CometD JavaScript客戶端是另一個不錯的選擇。

      CometD框架

      CometD框架是一個已經存在了好幾年的基于HTTP的事件驅動的通信解決方案,其版本2增加了對注解配置和WebSocket的支持。CometD框架提供了一個Java服務器端的部分和一個Java客戶端的部分,以及基于JQuery和Dojo的JavaScript客戶端庫。CometD使用了一個被稱作Bayeux的標準的通信協議,允許你激活消息確認、流程控制、同步以及集群等的某些擴展。

      CometD的事件驅動方法非常適合事件驅動的web開發這一新概念,和傳統的桌面用戶界面一樣,所有的組件通信通過一個總線來發送通知和接收事件,因此所有的通信都是異步的。

      CometD框架:

      1. 有詳盡的文檔說明

      2. 提供了例子和Maven原型來方便項目的啟動

      3. 提供了一個精心設計的API來支持擴展開發

      4. 提供了被稱為Oort的集群模塊,該模塊提供了在一個集群中把多個CometD web服務器當作節點來運行的能力,在其之前是一個負載均衡器調節大數據量的HTTP連接。

      5. 支持細粒度的安全策略配置,我們可以指定通過哪一個信道來發送消息。

      6. 很好地整合了Spring和Google Guice(依賴注入框架)

      Bayeux協議

      Byeux通信協議主要是通過HTTP來實現的,其在客戶端和服務器端之間以異步的方式提供一個響應式的雙向通信。Bayeux協議以消息路由的信道為基礎,從客戶端向服務器端傳遞,從服務器端向客戶端傳遞,或是客戶端之間傳遞(但要通過服務器端),Bayeux是一種發布-訂閱式的協議。CometD實現了Bayeux協議,從而在Comet和WebSocket傳輸之上提供了一個抽象層來通過Bayeux路由請求。

      服務器端及其內部

      CometD捆綁了三種傳輸:JSON、JSONP和WebSocket,它們依賴于Jetty Continuation和Jetty WebSocket API。缺省情況下,CometD可用在Jetty 6、7和8中,以及任何支持Servlet 3.0規范的服務器中。可以通過與擴展一樣的方式來增加和開發傳輸,你應該能夠編寫傳輸來支持Grizzly WebSocket API及其他的一些協議,然后在配置CometD服務器的相關步驟中加入它們。圖2給出了主要的CometD塊的一個概覽。

      圖2. CometD的架構概覽

    反向Ajax,第4部分:Atmosphere和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

    @Servicepublic final class ChatService {
    
    @Inject
    BayeuxServer server;
    
    @PostConstructvoid init() {
    server.addListener(new BayeuxServer.SessionListener() {
    @Overridepublic void sessionAdded(ServerSession session) {
    [...]
    }
    
    @Overridepublic 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() {
    @Overridepublic Result authorize(
    [..] // check that the user is in session }
    });
    }
    
    @Listener("/chatroom")void appendUser(ServerSession remote,
    ServerMessage.Mutable message) {
    [...]
    }
    }

      清單5說明了CometD的一些主要特征,其中包括:

      1. 依賴注入

      2. 生命周期管理

      3. 全局信道配置

      4. 安全管理

      5. 消息轉換(把用戶名稱添加到所有消息之前)

      6. 會話管理

      在客戶端,該例子不會激活任何的擴展——只是原始的CometD代碼,如清單6所示:

      清單6. CometD的客戶端代碼

    // 先創建cometd對象并配置它 var cometd = new $.Cometd('CometD chat client');
    cometd.configure({
    url: document.location + 'cometd',
    logLevel: 'debug'
    });
    cometd.websocketEnabled = 'WebSocket' in window;// 然后注冊一些監聽器。 說明信道 (有著// /meta/格式的那些是具體的預留通道) cometd.addListener('/meta/disconnect', function(message) {
    [...]
    });
    
    cometd.addListener('/meta/connect', function(message) {
    [...]
    });// 然后啟動一個可以使用的連接: cometd.handshake();// 然后訂閱: cometd.subscribe('/chatroom', function(event) {
    [...] // event.data存放消息 });// 我們最終以這種方式來發送數據給聊天室: cometd.publish('/chatroom', msg);

      CometD的客戶端API很容易使用和理解,同時又保留了強大的功能和可擴展性。本文只是涵蓋了web應用的主要部分,因此可以通過研究例子應用來更好地了解CometD的強大功能。

      表2列出了使用CometD框架的利弊。

      表2. CometD的優點和缺點

    1. 優點

    從客戶端到服務器端,以及從一個獨立的Java客戶端到服務器端,CometD提供了一個完整的解決方案。框架有詳盡的文檔說明,有一個很好的API,且非常容易使用。最重要的是,它擁有一種事件驅動的方法。CometD和Bayeux是許多事件驅動應用的構成部分,其他的反向Ajax框架并未提供任何的事件驅動機制,使得最終用戶不得不開發自己的定制解決方案。

    CometD支持許多必需的功能,比如說重連接、可靠的超時檢測、回退、批處理、消息確認,以及更多你不會在其他反向Ajax框架中找得到的功能。CometD可讓你實現最可靠的、低延時的通信。

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

      結束語

      Atmosphere和CometD都是穩定的、開源的反向Ajax解決方案,我們在Ovea選用的是CometD,因為我們在一個集群環境內部為移動設備開發可伸縮的事件驅動應用,我們完全掌控基礎設施(我們使用Jetty)。不過,在沒有額外開發的情況下,如果你正在出售web應用且希望你的反向Ajax功能在盡可能多的服務器上都可運作的話,CometD可能不是最好的選擇。但現在隨著越來越多的Web應用開始支持Servlet 3.0規范,CometD的局限性呈下降趨勢。說到傳輸層,余下的主要差異則取決于對WebSocket的支持。
           轉自http://select.yeeyan.org/view/213582/217948

      代碼下載

      reverse_ajaxpt4_source.zip

    相似問題

    相關經驗

    相關資訊

    相關文檔

  • sesese色