Reverse Ajax, Phần 3: Các máy chủ Web và Socket.IO

Người dùng hiện nay mong đợi các ứng dụng linh động, nhanh chóng có thể truy cập được từ trang web. Loạt bài này cho bạn thấy cách phát triển các ứng dụng web theo hướng sự kiện bằng cách sử dụng các kỹ thuật Reverse Ajax. Phần 1 đã giới thiệu Reverse Ajax, polling, streaming, Comet, long-polling. Bạn đã tìm hiểu vì sao Comet sử dụng HTTP long-polling là cách tốt nhất để thực hiện Reverse Ajax, khi hiện nay tất cả các trình duyệt đều hỗ trợ nó. Phần 2 đã mô tả cách thực hiện Reverse Ajax bằng cách sử dụng WebSockets. Các ví dụ mã giúp minh họa các ràng buộc WebSockets, FlashSocket bên phía máy chủ, các dịch vụ phạm vi-yêu cầu, và cách để tạm dừng các yêu cầu long-lived.

pdf11 trang | Chia sẻ: lylyngoc | Lượt xem: 1528 | Lượt tải: 1download
Bạn đang xem nội dung tài liệu Reverse Ajax, Phần 3: Các máy chủ Web và Socket.IO, để tải tài liệu về máy bạn click vào nút DOWNLOAD ở trên
Reverse Ajax, Phần 3: Các máy chủ Web và Socket.IO Giới thiệu Người dùng hiện nay mong đợi các ứng dụng linh động, nhanh chóng có thể truy cập được từ trang web. Loạt bài này cho bạn thấy cách phát triển các ứng dụng web theo hướng sự kiện bằng cách sử dụng các kỹ thuật Reverse Ajax. Phần 1 đã giới thiệu Reverse Ajax, polling, streaming, Comet, long-polling. Bạn đã tìm hiểu vì sao Comet sử dụng HTTP long-polling là cách tốt nhất để thực hiện Reverse Ajax, khi hiện nay tất cả các trình duyệt đều hỗ trợ nó. Phần 2 đã mô tả cách thực hiện Reverse Ajax bằng cách sử dụng WebSockets. Các ví dụ mã giúp minh họa các ràng buộc WebSockets, FlashSocket bên phía máy chủ, các dịch vụ phạm vi-yêu cầu, và cách để tạm dừng các yêu cầu long-lived. Trong bài này, hãy đi chi tiết về cách sử dụng Comet và WebSockets trong ứng dụng web của bạn với các API và các web container khác nhau (Servlet 3.0 và Continuations của Jetty). Hãy tìm hiểu cách sử dụng Comet và WebSockets một cách trong suốt bằng cách sử dụng các thư viện trừu tượng hóa, chẳng hạn như Socket.IO. Socket.IO sử dụng tính năng nhận dạng để quyết định xem kết nối đó sẽ được thiết lập với WebSocket, AJAX long-polling, Flash và v.v hay không. Bạn có thể tải về mã nguồn được sử dụng trong bài này. Điều kiện tiên quyết Tốt nhất bạn nên biết trước về JavaScript và Java. Các ví dụ trong bài này đã được xây dựng bằng cách sử dụng Google Guice, một framework tích hợp phụ thuộc (dependency injection framework) được viết bằng Java. Để làm theo cùng với bài này, bạn nên hiểu rõ các khái niệm về dependency injection framework, chẳng hạn như Guice, Spring hoặc Pico. Để chạy được các ví dụ trong bài này, bạn cần có phiên bản mới nhất của Maven và JDK (xem phần Tài nguyên). Về đầu trang Các giải pháp máy chủ cho Comet và WebSocket Ở Phần 1, bạn đã tìm hiểu về phương pháp Comet (long-polling hay streaming) có khả năng yêu cầu máy chủ có thể tạm dừng một request và tiếp tục hay hoàn thành nó sau một thời gian nắm giữ. Phần 2 đã mô tả cách mà máy chủ cần sử dụng tính năng I/O non-blocking như thế nào để xử lý nhiều kết nối và chúng chỉ sử dụng các luồng để phục vụ các yêu cầu (mô hình luồng cho mỗi yêu cầu). Bạn cũng đã biết rằng việc sử dụng WebSocket phụ thuộc vào máy chủ và không phải tất cả các máy chủ đều hỗ trợ WebSockets. Phần này sẽ cho bạn thấy cách sử dụng Comet và WebSockets, nếu có, trên các máy chủ web Jetty, Tomcat và Grizzly. Mã nguồn được cung cấp cùng với bài này có một ứng dụng web chat (trò chuyện trực tuyến) là ví dụ mẫu cho Jetty và Tomcat. Phần này cũng thảo luận về các API hỗ trợ cho các máy chủ ứng dụng sau: Jboss, Glassfish và WebSphere. Jetty Jetty là một máy chủ web hỗ trợ đặc tả Java Servlet 3.0, WebSockets và nhiều đặc tả tích hợp khác. Jetty là:  Mạnh mẽ và linh hoạt.  Dễ dàng nhúng.  Hỗ trợ các máy chủ ảo, phân cụm session và rất nhiều tính năng có thể được cấu hình dễ dàng thông qua mã Java hoặc XML.  Được dùng cho dịch vụ lưu trữ của Google App Engine. Dự án Jetty được quản lý bởi Quỹ Eclipse. Kể từ phiên bản 6, Jetty bao gồm một API không đồng bộ được gọi là Jetty Continuations, cho phép một yêu cầu được tạm dừng và có thể tiếp tục lại sau đó. Bảng 1 cho thấy bản đồ về đặc tả và API hỗ trợ với các họ phiên bản Jetty chính. Bảng 1. Các phiên bản Jetty và khả năng hỗ trợ Các sự hỗ trợ Jetty 6Jetty 7Jetty 8 Non-blocking I/O X X X Servlet 2.5 X X X Servlet 3.0 X X Jetty Continuations (Comet)X X X WebSockets X X Để thực hiện Reverse Ajax với Comet, bạn có thể sử dụng các API Continuation của Jetty, như thể hiện trong Liệt kê 1: Liệt kê 1. API Continuation của Jetty cho Comet // Pausing a request from a servlet's method (get, post, ...): protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { Continuation continuation = ContinuationSupport.getContinuation(req); // optionally set a timeout to avoid suspending requests for too long continuation.setTimeout(0); // suspend the request continuation.suspend(); // then hold a reference for future usage from another thread continuations.offer(continuation); } // Then, from another thread which wants to send an event to the client: while (!continuations.isEmpty()) { Continuation continuation = continuations.poll(); HttpServletResponse response = (HttpServletResponse) continuation.getServletResponse(); // write to the response continuation.complete(); } Ứng dụng web hoàn chỉnh có trong mã nguồn đi kèm với bài này. Jetty Continuations được đóng gói trong một tệp JAR. Bạn phải đặt tệp JAR này trong thư mục WEB-INF/lib của ứng dụng web của mình để có thể sử dụng các tính năng Comet của Jetty. Continuations của Jetty sẽ làm việc trên Jetty 6, 7 và 8. Bắt đầu với Jetty 7, bạn cũng có quyền truy cập vào tính năng WebSockets. Đặt tệp JAR WebSocket của Jetty vào thư mục WEB-INF/lib của ứng dụng web của bạn để có quyền truy cập vào API WebSocket của Jetty, như trong Liệt kê 2: Liệt kê 2. API WebSocket của Jetty // Implement the doWebSocketConnect and returns an implementation of // WebSocket: public final class ReverseAjaxServlet extends WebSocketServlet { @Override protected WebSocket doWebSocketConnect(HttpServletRequest request, String protocol) { return [...] } } // Sample implementation of WebSocket: class Endpoint implements WebSocket { Outbound outbound; public void onConnect(Outbound outbound) { this.outbound = outbound; } public void onMessage(byte opcode, String data) { outbound.sendMessage("Echo: " + data); if("close".equals(data)) outbound.disconnect(); } public void onFragment(boolean more, byte opcode, byte[] data, int offset, int length) { } public void onMessage(byte opcode, byte[] data, int offset, int length) { onMessage(opcode, new String(data, offset, length)); } public void onDisconnect() { outbound = null; } } Trong thư mục jetty-websocket củamã nguồn có một ví dụ chat mẫu để trình bày cách sử dụng API WebSocket của Jetty. Tomcat Có lẽ Tomcat là máy chủ web nổi tiếng nhất. Nó đã được sử dụng trong nhiều năm và đã được tích hợp như là một web container vào các phiên bản đầu tiên của máy chủ ứng dụng Jboss. Ngoài ra, Tomcat còn được dùng cho việc thực thi tham khảo đặc tả servlet. Nó đã được loại bỏ khỏi phiên bản API servlet 2.5 khi mọi người bắt đầu xem xét các lựa chọn thay thế dựa trên I/O non-blocking (như Jetty). Bảng 2 cho thấy API và các đặc tả được hỗ trợ cho hai họ phiên bản Tomcat mới nhất. Bảng 2. Sự hỗ trợ của Tomcat Các sự hỗ trợ Tomcat 6 Tomcat 7 Non-blocking I/O X X Servlet 2.5 X X Servlet 3.0 X Advanced I/O (Comet)X X WebSockets Như thể hiện trong Bảng 2, Tomcat không hỗ trợ WebSockets; nó có một bản tương đương với Continuations của Jetty được gọi là I/O nâng cao (Advanced I/O) để hỗ trợ Comet. I/O nâng cao là một trình bao bọc mức thấp xung quanh NIO mạnh hơn một API tốt để làm cho việc sử dụng Comet dễ dàng hơn. Nó có ít tài liệu và chỉ có một vài ví dụ mô tả cách sử dụng API này. Liệt kê 3 cho thấy một ví dụ về servlet được sử dụng để treo và tiếp tục lại các yêu cầu trong một ứng dụng web chat. Bạn có thể tìm thấy ứng dụng web hoàn chỉnh trong mã nguồn đi kèm với bài này. Liệt kê 3. API của Tomcat cho Comet public final class ChatServlet extends HttpServlet implements CometProcessor { private final 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); } else if (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(); } } } Trong Tomcat, một servlet không đồng bộ phải thực hiện một CometProcessor. Đối với các servlet không đồng bộ, Tomcat không gọi các phương thức HTTP tiêu chuẩn (doGet, doPost và v.v). Thay vào đó, nó gửi một sự kiện đến phương thức event(CometdEvent). Khi yêu cầu đầu tiên gửi đến, ví dụ sẽ kiểm tra để xem liệu nó có là một GET để treo nó không; evt.close() không được gọi. Nếu nó là một POST, có nghĩa là người dùng đã gửi một thông báo, do đó, nó được broadcast (phát hàng loạt) đến CometEvent khác và evt.close() được gọi để hoàn thành yêu cầu đăng thông báo. Về phía máy khách, việc broadcast sẽ thực hiện tất cả các yêu cầu long- polling để hoàn thành thông báo đã gửi và một yêu cầu long-polling khác được gửi ngay để nhận được các sự kiện tiếp theo. Grizzly và Glassfish Grizzly không phải là web container, nhưng hơn một framework NIO giúp các nhà phát triển xây dựng các ứng dụng có khả năng co giãn. Nó được phát triển như là một phần trong dự án Glassfish, nhưng nó cũng có thể được sử dụng độc lập hoặc nhúng vào hệ thống khác. Grizzly cung cấp các thành phần như một máy chủ HTTP/HTTPS và các thành phần cho Bayeux Protocol, Servlet, HTTPService OSGi và Comet, trong số những thứ khác. Grizzly hỗ trợ WebSockets và được sử dụng trong Glassfish để cung cấp Comet và hỗ trợ WebSocket. Glassfish, Application Server (Máy chủ ứng dụng) của Oracle, là cách thực hiện tham khảo của các đặc tả J2EE 6. Glassfish là một gói hoàn chỉnh, như WebSphere và Jboss, khi sử dụng Grizzly để hỗ trợ NIO, WebSocket và Comet. Kiến trúc mô đun của nó, dựa trên OSGI, giúp nó linh hoạt trong việc thay đổi các thành phần. Bảng 3 cho thấy Glassfish hỗ trợ Comet và WebSockets. Bảng 3. Sự hỗ trợ của Glassfish Các sự hỗ trợ Glassfish 2Glassfish 3 Non-blocking I/OX X Servlet 2.5 X X Servlet 3.0 X Comet X X WebSockets X Cách sử dụng Grizzly cũng rất quan trọng, khi người ta dự định sử dụng nó theo cách nhúng hoặc trực tiếp từ mã Java. Nó được sử dụng rộng rãi như một framework để hỗ trợ Comet và WebSockets, mà nó có thể được nhúng trong một ứng dụng lớn hơn, ví dụ như Glassfish, cung cấp các khả năng triển khai web và API đặc tả Servlet. Xem phần Tài nguyên để biết các liên kết đến các ví dụ của WebSockets và Comet trong Grizzly hoặc Glassfish. Vì Glassfish sử dụng Grizzly, nên các ví dụ sẽ làm việc trên cả hai. API WebSocket cũng rất giống như một API trong Jetty, nhưng API của Comet phức tạp hơn. Jboss Jboss là một máy chủ ứng dụng được xây dựng dựa trên Tomcat. Nó đã hỗ trợ Comet và NIO kể từ phiên bản 5. Jboss 7 vẫn còn đang phát triển, nhưng được đưa vào trong Bảng 4 dưới đây. Bảng 4. Sự hỗ trợ của Jboss Các sự hỗ trợ Jboss 5Jboss 6 Jboss 7 Non-blocking I/OX X X Servlet 2.5 X X X Servlet 3.0 X X Comet X X X WebSockets WebSphere WebSphere là một máy chủ ứng dụng của IBM. Phiên bản 8 của WebSphere đã hỗ trợ của API Servlet 3 (có chứa API không đồng bộ tiêu chuẩn hóa cho Comet) (xem phần Tài nguyên để đọc thông báo). Bảng 5. Sự hỗ trợ của WebSphere Các sự hỗ trợ WebSphere 8 Non-blocking I/OX Servlet 2.5 X Servlet 3.0 X Comet X WebSockets Về đầu trang Thế còn các API chung thì như thế nào? Mỗi máy chủ đưa ra API nguyên gốc của riêng mình cho Comet và WebSocket. Như bạn có thể đoán là việc viết một ứng dụng web di động có thể khó khăn. Đặc tả Servlet 3.0 gồm có các phương thức bổ sung để treo và tiếp tục lại một yêu cầu sau đó, cho phép tất cả các web container hỗ trợ Đặc tả Servlet 3.0 đều hỗ trợ các yêu cầu Comet long-polling. Nhóm phát triển Jetty cung cấp một thư viện được gọi là Continuation của Jetty, độc lập với Jetty container. Thư viện Continuation của Jetty đủ thông minh để phát hiện ra container hoặc đặc tả có sẵn. Nếu bạn đang chạy trên một máy chủ Jetty, API của Jetty nguyên gốc sẽ được sử dụng. Nếu bạn đang chạy trên một container hỗ trợ Đặc tả Servlet3.0, API chung (common API) này sẽ được sử dụng. Nếu không, việc thực hiện không có khả năng co giãn sẽ được sử dụng. Về WebSockets, vẫn chưa có tiêu chuẩn nào trong Java và do đó bạn cần sử dụng API container của nhà cung cấp trong ứng dụng web của mình nếu bạn muốn sử dụng WebSockets. Bảng 6 tóm tắt các công nghệ được các máy chủ khác nhau hỗ trợ. Bảng 6. Các công nghệ được các máy chủ hỗ trợ Container Comet WebSocket Jetty 6 Jetty Continuations Không có Jetty 7 Servlet 3.0 Jetty Continuations Native Jetty API (API Jetty nguyên gốc) Jetty 8 Servlet 3.0 Jetty Continuations Native Jetty API Tomcat 6 Advanced I/O (Vào/ra nâng cao) Không có Tomcat 7 Servlet 3.0 Advanced I/O Jetty Continuations Không có Glassfish 2 Native Grizzly API (API Grizzly nguyên gốc) Không có Glassfish 3 Servlet 3.0 Native Grizzly API Jetty Continuations Native Grizzly API Jboss 5 Native Jboss API (API Jboss nguyên gốc) Không có Jboss 6 Servlet 3.0 Native Jboss API Jetty Continuations Không có Jboss 7 Servlet 3.0 Native Jboss API Jetty Continuations Không có WebSphere 8 Servlet 3.0 Jetty Continuations Không có Không có giải pháp rõ ràng nào cho WebSockets ngoại trừ việc sử dụng API container. Như với Comet, mỗi container hỗ trợ Đặc tả Servlet 3.0 đều hỗ trợ Comet. Ở đây lợi thế của Jetty Continuations là cung cấp hỗ trợ Comet trên tất cả các container này. Vì vậy, một số thư viện Reverse Ajax (được tiếp tục thảo luận dưới đây và trong phần tiếp theo của loạt bài này) đang sử dụng Jetty Continuations API phía máy chủ. API Jetty Continuation được hiển thị trong ví dụ mẫu trong bài này. Đặc tả Servlet 3.0 đã được mô tả và được sử dụng trong hai ví dụ Comet trong Phần 1 của loạt bài này. Về đầu trang Các thư viện trừu tượng hóa Khi xem xét tất cả các API chủ yếu (Servlet 3.0 và Jetty Continuations), cộng với tất cả sự hỗ trợ bên phía máy chủ và hai cách chủ yếu để thực hiện Reverse Ajax bên phía máy khách (Comet và WebSocket) sẽ thấy thật khó để viết JavaScript và mã Java riêng của bạn để nối chúng lại với nhau. Bạn cũng phải tính đến các yếu tố như thời gian chờ timeout, các lỗi kết nối, tin báo nhận, chỉ dẫn, giữ dữ liệu trong bộ đệm và v.v. Phần còn lại của bài này sẽ cho bạn thấy Socket.IO hoạt động như thế nào. Phần 4 của loạt bài này sẽ tìm hiểu Atmosphere và CometD. Tất cả ba thư viện này đều là nguồn mở và tất cả chúng đều hỗ trợ Comet và WebSocket trên nhiều máy chủ. Socket.IO Socket.IO là một thư viện máy khách JavaScript cung cấp một API riêng, tương tự như WebSocket, để kết nối đến một máy chủ từ xa để gửi và nhận thông báo không đồng bộ. Bằng cách cung cấp một API chung, Socket.IO hỗ trợ một số kiểu truyền tải: WebSocket, Flash Sockets, long-polling, streaming, forever Iframes (Các khung nội tuyến vô hạn) và JSONP polling. Socket.IO phát hiện ra các khả năng của trình duyệt và cố gắng lựa chọn kiểu truyền tải tốt nhất có thể. Thư viện Socket.IO tương thích với hầu hết tất cả các trình duyệt (bao gồm cả các trình duyệt cũ, chẳng hạn như IE 5.5) cũng như các trình duyệt di động. Nó cũng có các tính năng như các nhịp hoạt động (heartbeat), thời gian chờ, ngắt kết nối và xử lý lỗi. Trang web Socket.IO (xem phần Tài nguyên) mô tả chi tiết thư viện này hoạt động ra sao và trình duyệt và kỹ thuật Reverse Ajax nào được sử dụng. Về cơ bản, Socket.IO sử dụng một giao thức truyền thông cho phép thư viện máy khách truyền thông với một điểm cuối ở phía máy chủ, mà điểm này có thể hiểu được giao thức Socket.IO. Đầu tiên người ta phát triển Socket.IO cho Node JS, một công cụ JavaScript được sử dụng để xây dựng các máy chủ nhanh hơn. Nhưng giờ đây, nhiều dự án đã mang đến sự hỗ trợ cho các ngôn ngữ khác, bao gồm cả Java. Liệt kê 4 cho thấy một ví dụ về sử dụng thư viện JavaScript Socket.IO bên phía máy khách. Trang web Socket.IO có tài liệu hướng dẫn và các ví dụ. Liệt kê 4. Sử dụng thư viện máy khách Socket.IO var socket = new io.Socket(document.domain, { resource: 'chat' }); socket.on('connect', function() { // Socket.IO is connected }); socket.on('disconnect', function(disconnectReason, errorMessage) { // Socket.IO disconnected }); socket.on('message', function(mtype, data, error) { // The server sent an event }); // Now that the handlers are defined, establish the connection: socket.connect(); Để sử dụng thư viện JavaScript Socket.IO, bạn sẽ cần một thành phần Java tương ứng được gọi là Socket.IO Java (xem phần Tài nguyên). Ban đầu dự án này được nhóm công tác Apache Wave khởi động để đem đến sự hỗ trợ Reverse Ajax cho Wave trước khi WebSockets được hỗ trợ. Socket.IO Java được chia làm hai nhánh và được duy trì bởi Ovea (một công ty chuyên về phát triển web theo hướng sự kiện) và sau đó bị bỏ rơi. Việc phát triển tầng sau để hỗ trợ thư viện máy khách Socket.IO có phức tạp là do có nhiều kiểu truyền tải. Phần 4 của loạt bài này sẽ chỉ ra vì sao việc hỗ trợ nhiều kiểu truyền tải trong một thư viện máy khách là không cần thiết để đạt được khả năng co giãn và hỗ trợ trình duyệt tốt hơn, do chỉ cần long-polling và WebSockets là đủ. Khi chưa hỗ trợ WebSockets, Socket.IO đã là một lựa chọn tốt. Socket.IO Java sử dụng API Jetty Continuation để treo và tiếp tục lại các yêu cầu. Nó sử dụng API WebSockets của Jetty nguyên gốc để hỗ trợ WebSockets. Bạn có thể xác định máy chủ nào sẽ làm việc đúng với một ứng dụng web khi sử dụng Socket.IO Java. Liệt kê 5, dưới đây, cho thấy một ví dụ về cách sử dụng Socket.IO trên máy chủ. Bạn phải định nghĩa một SocketIOServlet mở rộng của servlet và thực hiện một phương thức để trả về một loại đại diện điểm cuối. API này rất giống với API WebSockets. Ưu điểm là ở chỗ API này được sử dụng bên phía máy chủ, độc lập với việc truyền tải được chọn bên phía máy khách. Socket.IO chuyển dịch tất cả các kiểu truyền tải tới API tương tự bên phía máy chủ. Liệt kê 5. Thư viện Socket.IO Java được sử dụng cho ví dụ mẫu chat - servlet public final class ChatServlet extends SocketIOServlet { private final BlockingQueue endpoints = new LinkedBlockingQueue(); @Override protected SocketIOInbound doSocketIOConnect (HttpServletRequest request) { String user = (String) request.getSession().getAttribute("user"); return user == null ? null : new Endpoint(this, user, request); } void broadcast(String data) { for (Endpoint endpoint : endpoints) { endpoint.send(data); } } void add(Endpoint endpoint) { endpoints.offer(endpoint); } void remove(Endpoint endpoint) { endpoints.remove(endpoint); } } Liệt kê 6 cho thấy cách trả về điểm cuối. Liệt kê 6. Sử dụng thư viện Socket.IO Java cho ví dụ mẫu chat - điểm cuối class Endpoint implements SocketIOInbound { [...] private SocketIOOutbound outbound; [...] @Override public void onConnect(SocketIOOutbound outbound) { this.outbound = outbound; servlet.add(this); servlet.broadcast(user + " connected"); } @Override public void onDisconnect(DisconnectReason reason, String errorMessage) { outbound = null; request.getSession().removeAttribute("user"); servlet.remove(this); servlet.broadcast(user + " disconnected"); } @Override public void onMessage(int messageType, String message) { if ("/disconnect".equals(message)) { outbound.close(); } else