反向Ajax,第3部分:Web服务器和Socket.IO
英文原文:Reverse Ajax,Part 3: Web servers and Socket.IO 前言 时至今日,用户期待的是可通过web访问快速、动态的应用。这一文章系列展示了如何使用反向Ajax(Reverse Ajax)技术来开发事件驱动的web应用。系列的第1部分介绍了反向Ajax、轮询(polling)、流(streaming)、Comet和长轮询(long polling)。你已经了解了Comet是如何使用HTTP长轮询的,这是可靠地实现反向Ajax的最好方式,因为现有的所有浏览器都提供支持。系列的第2部分说明了如何使用WebSocket来实现反向Ajax,一些代码例子被用来帮助说明WebSocket、FlashSocket、服务器端的约束、请求作用域(request-scoped)服务以及暂停长生存期请求等。 在本篇文章中,我们深入细节,讨论在web应用中使用不同web容器和API(Servlet 3.0和Jetty Continuations)的Comet和WebSocket,了解如何通过使用诸如Socket.IO一类的抽象库来透明地使用Comet和Websocket。Socket.IO使用功能检测来确定连接是使用WebSocket、Ajax长轮询、Flash还是其他方式来建立。 前提条件 理想情况下,要充分体会本文的话,你应该对JavaScrpit和Java有一定的了解。本文中创建的例子是使用Google Guice来构建的,这是一个使用Java编写的依赖注入框架。若要读懂文中所谈内容,你应该要熟悉诸如Guice、Spring或是Pico一类的依赖注入框架的概念。 若要运行本文中的例子,你还需要最新版本的Maven和JDK(参见参考资料)。 Comet和WebSocket的服务器端解决方案 你在第1部分内容中已经了解到了,Comet(长轮询或是流)需要服务器端能够暂停某个请求,并在一个可能的长延迟之后恢复或是完成该请求。第2部分内容描述了服务器端如何使用非阻塞式的I/O功能来处理大量的连接,以及它们只能使用线程来服务请求(每个请求一个线程模式)。你也已经了解到了WebSocket的使用是服务器端依赖的,且并非所有的服务器都支持WebSocket。 本节内容说明了如果适用的话,那么在Jetty、Tomcat和Grizzly等web服务器上是如何使用Comet和WebSocket的。本文提供的源代码包含了Jetty和Tomcat的一个聊天web应用例子。本节内容还讨论了下面的这些应用服务器:Jboss、Glassfish和WebSphere所支持的API。 Jetty Jetty是一个web服务器,支持Java Servlet 3.0规范、WebSocket和其他的许多集成规范。Jetty: 1. 功能强大且灵活 2. 易于嵌入 3. 支持虚拟主机、会话集群和许多可很容易地通过用于Google App Engine的托管服务的Java代码或是XML来配置的功能。 核心的Jetty项目由Eclipse Foundation打理。 从版本6开始,Jetty加入了一个被称作Jetty Continuation(Jetty延续)的异步API,该API允许请求被暂停并在之后被恢复。表1给出了所支持的规范和Jetty的主要版本系列的API之间的一个对照关系。 表1. Jetty的版本和支持
若要实现使用Comet的反向Ajax的话,你可以使用Jetty的Continuation API,如清单1所示: 清单1. 用于Comet的Jetty Continuation API // 暂停一个来自servlet方法(get、post......)的请求: protected void doGet(HttpServletRequest req,HttpServletResponse resp) throws ServletException,IOException { Continuation continuation = ContinuationSupport.getContinuation(req); // 可选的做法,设置超时以避免请求挂起过久 continuation.setTimeout(0); // 挂起请求 continuation.suspend(); // 保存引用,以备将来另一线程使用 continuations.offer(continuation); } // 然后,来自另一个想给客户发送事件的线程: while (!continuations.isEmpty()) { Continuation continuation = continuations.poll(); HttpServletResponse response = (HttpServletResponse) continuation.getServletResponse(); // 向响应中写入 continuation.complete(); } 完整的web应用在本文所带的源代码中。Jetty Continuation被打包放在一个JAR归档文件中,你需要把这一JAR文件放在web应用的WEB-INF/lib目录下才能使用Jetty的Comet功能。Jetty Continuation在Jetty 6、7和8上都是可用的。 从Jetty 7开始,你还可以访问WebSocket功能,把Jetty的WebSocket JAR文件放在web应用的WEB-INF/lib目录下以获得对Jetty的WebSocket API的访问,如清单2所示: 清单2. Jetty的Websocket API // 实现doWebSocketConnect并返回WebSocket的实现 publicfinalclass ReverseAjaxServlet extends WebSocketServlet { @Override protected WebSocket doWebSocketConnect(HttpServletRequest request,String protocol) { return [...] } } // WebSocket的示例实现 class Endpoint implements WebSocket { Outbound outbound; publicvoid onConnect(Outbound outbound) { this.outbound = outbound; } publicvoid onMessage(byte opcode,String data) { outbound.sendMessage("Echo: "+ data); if("close".equals(data)) outbound.disconnect(); } publicvoid onFragment(boolean more,byte opcode,byte[] data,int offset,int length) { } publicvoid onMessage(byte opcode,int length) { onMessage(opcode,new String(data,offset,length)); } publicvoid onDisconnect() { outbound =null; } } 在下载的源代码的jetty-websocket目录下有一个聊天应用的例子,该例子说明了如何使用Jetty的WebSocket API。 Tomcat Tomcat可能是最广为人知的web服务器了,其已经使用多年,且被作为web容器整合在Jboss应用服务器的早期版本中。Tomcat也被用作是servlet规范的参考实现,但到了servlet API 2.5后就不再是了,这时人们开始寻求另一种基于非阻塞式I/O的做法(比如说Jetty)。表2给出了所支持的规范和两个最新的Tomcat版本系列的API之间的对照。 表2. Tomcat的支持
正如表2所示的那样,Tomcat不支持WebSocket;它有一个相当于Jetty的Continuation的API,这一被称为Advanced I/O的API支持Comet。与其说Advanced I/O是一个很好地方便了Comet使用的API,倒不如说它是一个封装了NIO的低层面包装器。它缺乏文档资料,几乎没有什么使用API的应用例子。清单3给出了一个servlet例子,该例子挂起并恢复聊天web应用中的请求。你可以在本文的源代码中找到完整的web应用。 清单3. Tomcat用于Comet的API publicfinalclass ChatServlet extends HttpServlet implements CometProcessor { privatefinal BlockingQueue events = new LinkedBlockingQueue(); public void event(CometEvent evt) throws IOException,ServletException { HttpServletRequest request = evt.getHttpServletRequest(); String user = (String) request.getSession().getAttribute("user"); switch (evt.getEventType()) { case BEGIN: { if ("GET".equals(request.getMethod())) { evt.setTimeout(Integer.MAX_VALUE); events.offer(evt); } else { String message = request.getParameter("message"); if ("/disconnect".equals(message)) { broadcast(user +" disconnected"); request.getSession().removeAttribute("user"); events.remove(evt); } elseif (message !=null) { broadcast("["+ user +"]"+ message); } evt.close(); } } } } void broadcast(String message) throws IOException { Queue q =new LinkedList(); events.drainTo(q); while (!q.isEmpty()) { CometEvent event = q.poll(); HttpServletResponse resp = event.getHttpServletResponse(); resp.setStatus(HttpServletResponse.SC_OK); resp.setContentType("text/html"); resp.getWriter().write(message); event.close(); } } } 在Tomcat中,异步的servlet必须要实现CometProcessor。对于异步的servlet来说,Tomcat并不调用标准的HTTP方法(doGet、doPost等),取而代之的是,事件会被发送给event(CometEvent)方法。当请求首次到达时,例子查看是否是一个GET,是的话便挂起它,evt.close()没有被调用。如果是一个POST的话,这意味着是用户发送了一个消息,因此给其他CometEvent广播消息,然后调用evt.close()来完成该post请求。在客户这一端,广播会让所有的长轮询请求完成消息的发送,另一个长轮询请求会被立刻发送出去以接收接下来的事件。 Grizzly和Glassfish Grizzly不是一个web容器,它更像是一个用来帮助开发者构建可伸缩应用的NIO框架。它是被作为Glassfish项目的一部分开发出来的,但也可以独立或是嵌入使用。Grizzly提供了充当HTTP/HTTPS服务器的组件,除了其他的一些组件之外,其还提供了Bayeux Protocol、Servlet、HttpService OSGi和Comet组件。Grizzly支持WebSocket,其被用在Glassfish中提供Comet和WebSocket支持。 Oracle的应用服务器Glassfish是J2EE 6规范的参考实现。Glassfish是一个完整的套件,类似WebSphere和Jboss,使用Grizzly来支持NIO、Websocket和Comet。它的基于OSGI的模块化架构,使得其在更换组件时具有真正的灵活性。表3说明了Glassfish对Comet和WebSocket的支持。 表3. Glashfish的支持
|