Reverse Ajax, Phần 2: WebSockets

Đây là một giải pháp mạnh mặc dù vẫn còn một số ràng buộc từ phía máy chủ

Loạt bài này tìm hiểu cách phát triển các ứng dụng web theo hướng sự kiện (event-driven) bằng cách sử dụng các kỹ thuật Reverse Ajax. Phần 1 đã giới thiệu một vài cách giao tiếp Reverse Ajax: polling, piggyback và Comet, bằng cách sử dụng long-polling và streaming. Trong bài này, hãy tìm hiểu một cách khác để thực hiện Reverse Ajax, đó là sử dụng WebSockets, một API mới của HTML5. WebSockets có thể được thực thi bởi trình duyệt hoặc thông qua một cầu nối ủy thác để gọi đến thành phần Flash, thành phần này được gọi là FlashSockets. Bài này cũng thảo luận về một số ràng buộc bên phía máy chủ với các kỹ thuật Reverse Ajax.

Mathieu Carbou, Kiến trúc sư Java Web, Ovea

Ảnh của Mathieu CarbouMathieu Carbou là nhà tư vấn và kiến trúc web Java ở Ovea, cung cấp các dịch vụ và các giải pháp phát triển. Ông là người đứng dầu và điều hành một số dự án nguồn mở, là người phát ngôn và lãnh đạo của Nhóm người dùng Java (Java User Group) của Montreal. Mathieu có nền tảng vững chắc về thiết kế mã và có kinh nghiệm tốt, ông còn là một chuyên gia phát triển web theo hướng sự kiện (event-driven web) từ phía máy khách đến các tầng bên dưới. Ông tập trung vào việc cung cấp các giải pháp thông tin và theo hướng sự kiện của các ứng dụng web có khả năng co giãn cao. Hãy ghé thăm blog của ông.



22 01 2013

Giới thiệu

Ngày nay, người dùng thích các ứng dụng linh động và dễ dàng truy cập được từ trang web. Loạt bài này sẽ 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 và long-polling. Bạn đã tìm hiểu vì sao mà Comet sử dụng HTTP long-polling là cách tốt nhất để thực hiện Reverse Ajax khi được hỗ trợ trong tất cả các trình duyệt.

Trong bài này, hãy tìm hiểu cách thực hiện Reverse Ajax bằng cách sử dụng WebSockets. Những đoạn mã ví dụ sẽ giúp minh họa WebSockets, FlashSockets, các ràng buộc bên phía máy chủ, các dịch vụ có phạm vi-yêu cầu và tạm dừng các yêu cầu đã có từ lâu. Bạn có thể tải về mã nguồn được 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).


WebSockets

WebSockets mới xuất hiện trong HTML5, là một kỹ thuật Reverse Ajax mới hơn Comet. WebSockets cho phép các kênh giao tiếp song song hai chiều và hiện đã được hỗ trợ trong nhiều trình duyệt (Firefox, Google Chrome và Safari). Kết nối được mở thông qua một HTTP request (yêu cầu HTTP), được gọi là liên kết WebSockets với những header đặc biệt. Kết nối được duy trì để bạn có thể viết và nhận dữ liệu bằng JavaScript như khi bạn đang sử dụng một TCP socket đơn thuần.

Một URL WebSocket được bắt đầu bằng cách gõ ws:// hoặc wss:// (trên SSL).

Hình 1 cho thấy cách giao tiếp khi sử dụng WebSockets. Một liên kết HTTP được gửi đến máy chủ với các header cụ thể. Sau đó, một loại socket Javascript có sẵn trên máy chủ hoặc máy khách sẽ được sử dụng để nhận dữ liệu không đồng bộ thông qua một trình xử lý sự kiện.

Hình 1. Reverse Ajax với WebSockets
Reverse Ajax với WebSockets

Bạn có thể tải về mã nguồn cho bài này. Khi bạn chạy ví dụ này, bạn sẽ thấy kết quả tương tự như Liệt kê 1. Nó cho thấy các sự kiện đã xảy ra bên phía máy chủ và cũng xuất hiện ngay lập tức bên phía máy khách như thế nào. Khi máy khách gửi đi dữ liệu nào đó, máy chủ sẽ báo lại cho máy khách.

Liệt kê 1. Ví dụ mẫu WebSocket bằng JavaScript
[client] WebSocket connection opened 
[server] 1 events 
[event] ClientID = 0 
[server] 1 events 
[event] At Fri Jun 17 21:12:01 EDT 2011 
[server] 1 events 
[event] From 0 : qqq 
[server] 1 events 
[event] At Fri Jun 17 21:12:05 EDT 2011 
[server] 1 events 
[event] From 0 : vv

Thông thường, trong JavaScript bạn sẽ sử dụng WebSockets như được trình bày trong Liệt kê 2, nếu trình duyệt của bạn có hỗ trợ nó.

Liệt kê 2. Mã JavaScript ở máy khách
var ws = new WebSocket('ws://127.0.0.1:8080/async'); 
ws.onopen = function() { 
    // called when connection is opened 
}; 
ws.onerror = function(e) { 
    // called in case of error, when connection is broken in example 
}; 
ws.onclose = function() { 
    // called when connexion is closed 
}; 
ws.onmessage = function(msg) { 
    // called when the server sends a message to the client. 
    // msg.data contains the message. 
}; 
// Here is how to send some data to the server 
ws.send('some data'); 
// To close the socket:
ws.close();

Dữ liệu được gửi và nhận có thể là kiểu bất kỳ nào. Có thể xem WebSockets giống như TCP socket, vì thế tùy thuộc vào máy khách và máy chủ để biết kiểu dữ liệu nào đang được gửi qua. Ví dụ ở Liệt kê 2 đang gửi các chuỗi JSON.

Khi một đối tượng Websocket Javascript được tạo ra, nếu xem kỹ các HTTP request trong giao diện trình duyệt (hoặc Firebug) của lần kết nối đó, bạn sẽ thấy các header đặc trưng của WebSocket. Liệt kê 3 là một ví dụ.

Liệt kê 3. Ví dụ mẫu về HTTP request và các header phản hồi
Request URL:ws://127.0.0.1:8080/async 
Request Method:GET 
Status Code:101 WebSocket Protocol Handshake 

Request Headers 
Connection:Upgrade 
Host:127.0.0.1:8080 
Origin:http://localhost:8080 
Sec-WebSocket-Key1:1 &1~ 33188Yd]r8dp W75q 
Sec-WebSocket-Key2:1   7;    229 *043M 8 
Upgrade:WebSocket 
(Key3):B4:BB:20:37:45:3F:BC:C7 

Response Headers 
Connection:Upgrade 
Sec-WebSocket-Location:ws://127.0.0.1:8080/async 
Sec-WebSocket-Origin:http://localhost:8080 
Upgrade:WebSocket 
(Challenge Response):AC:23:A5:7E:5D:E5:04:6A:B5:F8:CC:E7:AB:6D:1A:39

Các header được dùng trong các liên kết WebSocket để ủy quyền và thiết lập các kết nối long-lived. Đối tượng WebSocket JavaScript cũng chứa hai đặc tính hữu dụng:

ws.url
Trả về URL của máy chủ WebSocket.
ws.readyState
Trả về giá trị của trạng thái kết nối hiện tại:
  • CONNECTING = 0
  • OPEN = 1
  • CLOSED = 2

Về phía máy chủ, việc xử lý WebSockets phức tạp hơn một chút. Vẫn chưa có đặc tả Java chuẩn hỗ trợ WebSockets. Để sử dụng các tính năng WebSockets của web container (ví dụ, Tomcat hoặc Jetty), bạn phải ghép mã ứng dụng với các thư viện đặc thù.

Ví dụ trong thư mục websocket của mã nguồn mẫu sử dụng API WebSocket của Jetty do chúng tôi đang sử dụng Jetty container. Liệt kê 4 cho thấy trình xử lý WebSocket. (Phần 3 của loạt bài này sẽ sử dụng các API WebSocket khác nhau cho các tầng bên dưới).

Liệt kê 4. Trình xử lý WebSocket cho một Jetty container
public final class ReverseAjaxServlet extends WebSocketServlet { 
    @Override 
    protected WebSocket doWebSocketConnect(HttpServletRequest request,
                                           String protocol) { 
        return [...] 
    } 
}

Với Jetty, có một số cách để xử lý một liên kết WebSocket. Một cách dễ dàng là phân lớp WebSocketServlet của Jetty và thực thi phương thức doWebSocketConnect Phương thức này sẽ yêu cầu bạn trả về một thể hiện của WebSocket interface. Bạn phải thực thi interface này và trả về một loại thông tin gọi là endpoint đại diện cho liên kết WebSocket. Liệt kê 5 cung cấp một ví dụ mẫu.

Liệt kê 5. Ví dụ về thực thi WebSocket
class Endpoint implements WebSocket { 

    Outbound outbound; 

    @Override 
    public void onConnect(Outbound outbound) { 
        this.outbound = outbound;    
    } 

    @Override 
    public void onMessage(byte opcode, String data) { 
        // called when a message is received 
        // you usually use this method 
    } 

    @Override 
    public void onFragment(boolean more, byte opcode, 
                           byte[] data, int offset, int length) { 
        // when a fragment is completed, onMessage is called. 
        // Usually leave this method empty. 
    } 

    @Override 
    public void onMessage(byte opcode, byte[] data, 
                          int offset, int length) { 
        onMessage(opcode, new String(data, offset, length)); 
    } 

    @Override 
    public void onDisconnect() { 
        outbound = null; 
    } 
}

Để gửi một thông báo đến máy khách, bạn xuất thông báo ra outbound, như thể hiện trong Liệt kê 6:

Liệt kê 6. Gửi một thông báo đến máy khách
if (outbound != null && outbound.isOpen()) {
    outbound.sendMessage('Hello World !');
}

Để ngắt kết nối máy khách và đóng kết nối WebSocket, hãy sử dụng outbound.disconnect();.

WebSockets là một cách rất mạnh để thực hiện giao tiếp hai chiều mà không có độ trễ nào. Firefox, Google Chrome, Opera và các trình duyệt hiện đại khác đều hỗ trợ nó. Theo trang web jWebSocket:

  • Chrome có WebSockets nguyên gốc kể từ phiên bản 4.0.249.
  • Safari 5.x có WebSockets nguyên gốc.
  • Firefox 3.7a6 và 4.0b1+ có WebSockets nguyên gốc.
  • Opera có WebSockets nguyên gốc kể từ phiên bản 10.7.9067.

Để biết thêm thông tin về jWebSocket, xem phần Tài nguyên.

Ưu điểm

WebSockets cung cấp khả năng giao tiếp hai chiều mạnh mẽ, có độ trễ thấp và dễ xử lý lỗi. Không cần phải có nhiều kết nối như phương pháp Comet long-polling và cũng không có những nhược điểm như Comet streaming. API cũng rất dễ sử dụng trực tiếp mà không cần bất kỳ các tầng bổ sung nào, so với Comet, thường đòi hỏi một thư viện tốt để xử lý kết nối lại, thời gian chờ timeout, các Ajax request (yêu cầu Ajax), các tin báo nhận và các dạng truyền tải tùy chọn khác nhau (Ajax long-polling và jsonp polling).

Nhược điểm

Những nhược điểm của WebSockets gồm có:

  • Nó là một đặc tả mới của HTML5, nên nó vẫn chưa được tất cả các trình duyệt hỗ trợ.
  • Không có phạm vi yêu cầu nào. Do WebSockets là một TCP socket chứ không phải là HTTP request, nên không dễ sử dụng các dịch vụ có phạm vi-yêu cầu, như SessionInViewFilter của Hibernate. Hibernate là một framework kinh điển cung cấp một bộ lọc xung quanh một HTTP request. Khi bắt đầu một request, nó sẽ thiết lập một contest (chứa các transaction và liên kết JDBC) được ràng buộc với luồng request. Khi request đó kết thúc, bộ lọc hủy bỏ contest này.

FlashSockets

Đối với các trình duyệt không hỗ trợ WebSockets, một số thư viện có khả năng quay lại FlashSockets (các socket thông qua Flash). Các thư viện thường cung cấp một API WebSocket chính thức tương tự, nhưng chúng thực hiện nó bằng cách ủy quyền các cuộc gọi đến một thành phần Flash ẩn được tích hợp trên trang web.

Ưu điểm

FlashSockets cung cấp tính năng WebSockets một cách trong suốt, ngay cả trên các trình duyệt không hỗ trợ WebSockets của HTML5.

Nhược điểm

FlashSockets có những nhược điểm sau đây:

  • Cần phải cài thêm Flash plug-in (thường thì tất cả các trình duyệt đều có sẵn).
  • Cần phải mở port 843 trong tường lửa để cho thành phần Flash có thể thực hiện một HTTP request để lấy ra một tệp chính sách có chứa thông tin ủy quyền.

    Nếu không thể thông qua được port 843, thư viện cần quay trở lại hoặc đưa ra một lỗi. Tất cả quá trình xử lý này đều mất thời gian (nhiều nhất lên đến 3 giây, tùy thuộc vào thư viện), sẽ làm chậm trang web.

  • Nếu máy khách có sử dụng proxy, thì có thể kết nối tới port 843 sẽ bị từ chối.

Dự án WebSocketJS có thể giúp hỗ trợ WebSockets cho các trình duyệt Firefox 3, Internet Explorer 8 và Internet Explorer 9, tuy nhiên nó yêu cầu phải cài đặt Flash từ bản 10 trở đi.

Khuyến cáo

So với Comet, WebSockets mang lại nhiều lợi ích hơn và ngày càng được phát triển để nhanh chóng hỗ trợ trên máy khách và tạo ra ít request hơn (do đó phương pháp này tiêu thụ ít băng thông hơn). Tuy nhiên, do không phải tất cả các trình duyệt đều đang hỗ trợ WebSockets, nên tốt nhất khi sử dụng kỹ thuật Reverse Ajax là ta sẽ thêm một tính năng giúp phát hiện xem WebSocket có được hỗ trợ hay không, nếu không thì chuyển sang phương pháp Comet (long-polling).

Do hai kỹ thuật này là cần thiết để nhận được sự lựa chọn tốt nhất trong tất cả các trình duyệt và duy trì tính tương thích, nên điều quan trọng là bạn sử dụng một thư viện JavaScript máy khách, cung cấp một tầng trừu tượng dựa trên các kỹ thuật này. Phần 3 và Phần 4 của loạt bài này sẽ tìm hiểu một số thư viện và Phần 5 sẽ cho bạn thấy cách ứng dụng chúng. Về phía máy chủ, những việc này đều phức tạp hơn một chút, như được thảo luận trong phần tiếp theo.


Các ràng buộc Reverse Ajax ở phía máy chủ

Bây giờ bạn có một tổng quan về các giải pháp Reverse Ajax có sẵn bên phía máy khách, chúng ta hãy tìm hiểu các giải pháp Reverse Ajax trên máy chủ. Cho tới giờ thì các ví dụ trong bài chỉ được sử dụng chủ yếu trên máy khách. Về phía máy chủ, để chấp nhận các kết nối Reverse Ajax, một số kỹ thuật yêu cầu các tính năng đặc trưng để xử lý các kết nối long-lived so với các HTTP request ngắn mà bạn đã quen thuộc. Để có thể linh động hơn, ta nên sử dụng một mô hình luồng mới, nó đòi hỏi phải có một API Java đặc trưng để có thể tạm dừng các yêu cầu. Ngoài ra, đối với WebSockets, bạn phải quản lý đúng phạm vi của các dịch vụ được dùng trong ứng dụng.

I/O threading và I/O non-blocking

Thông thường, một máy chủ web liên kết một luồng hoặc một quá trình cho mỗi kết nối HTTP đến. Kết nối này có thể tồn tại lâu bền (được duy trì) sao cho một số yêu cầu đi qua cùng một kết nối. Trong ví dụ của bài này, có thể cấu hình máy chủ web Apache theo các mô hình mpm_fork hoặc mpm_worker để thay đổi hành vi này. Các máy chủ Java web (bao gồm cả các máy chủ ứng dụng tương tự) thường sử dụng một luồng cho mỗi kết nối đến.

Việc sinh ra một luồng mới dẫn đến việc tiêu thụ bộ nhớ và lãng phí tài nguyên vì nó không đảm bảo sẽ sử dụng luồng mới được sinh ra. Kết nối có thể thiết lập được, nhưng không có dữ liệu nào từ máy khách hoặc máy chủ được gửi đi. Cho dù có sử dụng luồng này hay không, thì nó vẫn tiêu thụ bộ nhớ và tài nguyên CPU cho việc lập lịch trình và các khóa chuyển đổi contest. Và, khi cấu hình một máy chủ bằng cách sử dụng một mô hình luồng, bạn thường phải cấu hình một nhóm luồng (thiết lập một số lượng tối đa các luồng để xử lý các kết nối đến). Nếu giá trị này bị cấu hình sai và quá thấp, bạn sẽ kết thúc bằng một vấn đề thiếu luồng; các yêu cầu sẽ chờ cho đến khi có một luồng khác để xử lý chúng. Thời gian đáp ứng sẽ chậm khi đạt được kết nối tối đa đồng thời. Mặt khác, việc cấu hình một giá trị cao có thể dẫn đến một trường hợp ngoại lệ là thiếu bộ nhớ. Việc sinh ra quá nhiều luồng sẽ tiêu thụ tất cả các kích cỡ heap (vùng lưu trữ đặc biệt trong bộ nhớ) của JVM và dẫn đến sự cố cho máy chủ.

Gần đây Java đã giới thiệu một API I/O (API vào/ra) mới được gọi là I/O non-blocking. API này sử dụng một bộ lựa chọn để tránh ràng buộc với một luồng mỗi khi thực hiện một kết nối HTTP tới máy chủ. Khi dữ liệu đến, một sự kiện được thu nhận và một luồng được cấp phát để xử lý yêu cầu. Như vậy, việc này được gọi là một mô hình luồng cho mỗi yêu cầu. Nó cho phép các máy chủ web, chẳng hạn như WebSphere và Jetty, điều chỉnh co giãn và xử lý một số lượng lớn các kết nối người dùng gia tăng với một số luồng cố định. Với cùng một cấu hình phần cứng, các máy chủ web đang chạy trong chế độ này điều chỉnh co giãn tốt hơn nhiều so với ở chế độ luồng cho mỗi kết nối.

Trong blog của mình, Philip McCarthy (tác giả của Comet and Reverse Ajax) có một đánh giá thú vị về khả năng điều chỉnh co giãn của hai mô hình luồng (xem phần Tài nguyên để thấy liên kết tới bài viết). Trong Hình 2 bạn sẽ thấy một mẫu tương tự: một mô hình luồng ngừng làm việc với quá nhiều các kết nối.

Hình 2. Đánh giá về các mô hình luồng
Đánh giá về các mô hình luồng

Mô hình luồng cho mỗi kết nối (đường Threads trong Hình 2) thường có thời gian đáp ứng tốt hơn, do tất cả các luồng đã thiết lập, sẵn sàng và chờ đợi, nhưng nó dừng phục vụ khi số lượng kết nối quá cao. Trong mô hình luồng cho mỗi yêu cầu (đường Continuations trong Hình 2), một luồng được sử dụng để phục vụ yêu cầu đến và kết nối được xử lý thông qua một bộ lựa chọn NIO. Thời gian đáp ứng có thể chậm hơn một chút, nhưng vì các luồng được sử dụng lại nên giải pháp này co giãn tốt hơn với nhiều kết nối.

Để hiểu đằng sau việc tạo các luồng ra sao, hãy tưởng tượng một khối LEGO™ như là bộ lựa chọn. Mỗi kết nối đến đi tới khối LEGO này và được xác định bằng một chân. Khối LEGO/bộ lựa chọn sẽ có nhiều chân (nhiều khóa) như các kết nối. Sau đó, chỉ có một luồng cần thiết để lặp lại qua các chân khi nó chờ các sự kiện mới xảy ra. Khi có một điều gì đó xảy ra, luồng của bộ lựa chọn sẽ lấy ra các khóa dùng cho các sự kiện đã xảy ra và một luồng có thể được sử dụng để phục vụ yêu cầu đến.

"Hướng dẫn Rox Java NIO" là ví dụ về việc sử dụng NIO trong Java (xem phần Tài nguyên).


Các dịch vụ phạm vi-yêu cầu

Nhiều framework cung cấp các dịch vụ hoặc các bộ lọc, xử lý một yêu cầu web đến trong một servlet. Ví dụ, một bộ lọc sẽ:

  • Ràng buộc một kết nối JDBC đến một luồng yêu cầu sao cho chỉ có một kết nối được sử dụng cho toàn bộ yêu cầu.
  • Cam kết các thay đổi vào cuối mỗi yêu cầu.

Một ví dụ khác là phần mở rộng Guice Servlet của Google Guice (một thư viện dependency injection). Giống như Spring, Guice có thể kết buộc các dịch vụ trong một phạm vi yêu cầu. Một cá thể sẽ được tạo ra nhiều nhất là một lần cho mỗi yêu cầu mới (xem phần Tài nguyên để biết thêm thông tin).

Cách sử dụng điển hình sẽ gồm lưu trữ trong bộ nhớ đệm một đối tượng người dùng được lấy ra từ một kho lưu trữ (ví dụ, một cơ sở dữ liệu) theo yêu cầu bằng cách sử dụng id (mã định danh) của người dùng được lấy từ vùng session HTTP. Trong Google Guice, bạn có thể có mã tương tự như Liệt kê 7.

Liệt kê 7. Ràng buộc có phạm vi-yêu cầu
@Provides 
@RequestScoped 
Member member(AuthManager authManager, 
              MemberRepository memberRepository) { 
    return memberRepository.findById(authManager.getCurrentUserId());
}

Khi một thành viên được tích hợp vào trong một lớp, Guice sẽ cố gắng tìm nạp nó từ yêu cầu đó. Nếu không tìm thấy, nó sẽ thực hiện cuộc gọi kho lưu trữ và giao kết quả cho yêu cầu đó.

Có thể sử dụng các dịch vụ phạm vi-yêu cầu với bất kỳ giải pháp Reverse Ajax nào trừ WebSockets. Bất kỳ giải pháp khác nào dựa trên các HTTP request ngắn hoặc long-lived, nên mỗi yêu cầu thường có hệ thống gửi servlet và các bộ lọc được thực thi. Khi tạm dừng một HTTP request (long-lived), bạn sẽ thấy trong các phần tiếp theo của loạt bài này cũng có một tùy chọn để thực hiện yêu cầu thông qua chuỗi bộ lọc lại.

Đối với WebSockets, dữ liệu trực tiếp đi tới sự kiện onMessage, giống như trong một TCP socket. Do vẫn không có bất kỳ HTTP request nào gửi dữ liệu này đến, nên không có yêu cầu contest nào mà từ đó có thể nhận và lưu trữ các đối tượng có phạm vi. Như vậy, việc sử dụng các dịch vụ yêu cầu các đối tượng có phạm vi từ một sự kiện onMessage sẽ thất bại.

Ví dụ mẫu guice và websocket trong mã nguồn cho thấy cách vượt qua hạn chế và vẫn sử dụng các đối tượng có phạm vi-yêu cầu trong một sự kiện onMessage. Khi bạn chạy ví dụ mẫu này và nhấn vào từng button trên trang web để kiểm tra một cuộc gọi Ajax (có phạm vi-yêu cầu) và một cuộc gọi WebSocket. Một cuộc gọi WebSocket có một yêu cầu mô phỏng được quy định phạm vi, bạn sẽ nhận được kết quả như trong Hình 3.

Hình 3. Kết quả của một trình xử lý WebSocket khi sử dụng các dịch vụ có phạm vi-yêu cầu
Kết quả của một trình xử lý WebSocket khi sử dụng các dịch vụ có phạm vi-yêu cầu

Bạn có thể gặp các vấn đề như vậy khi bạn dùng:

  • Spring.
  • Hibernate.
  • Bất kỳ framework khác nào đòi hỏi mô hình có phạm vi-yêu cầu hoặc một mô hình cho mỗi yêu cầu, chẳng hạn như OpenSessionInViewFilter.
  • Bất kỳ hệ thống nào đang sử dụng phương tiện ThreadLocal để quy định phạm vi các biến cho một luồng yêu cầu trong một bộ lọc và truy cập chúng sau này.

Guice có độ phân giải đẹp, như thể hiện trong Liệt kê 8:

Liệt kê 8. Mô phỏng một phạm vi-yêu cầu từ sự kiện onMessage của WebSocket
// The reference to the request is hold when the 
// doWebSocketMethod is called 
HttpServletRequest request = [...] 
Map<Key<?>, Object> bindings = new HashMap<Key<?>, Object>(); 
// I have a service which needs a request to get the session, 
// so I provide the request, but you could provide any other 
// binding that may be needed 
bindings.put(Key.get(HttpServletRequest.class), request); 
ServletScopes.scopeRequest(new Callable<Object>() { 
    @Override 
    public Object call() throws Exception { 
        // call your repository or any service using the scoped objects
        outbound.sendMessage([...]); 
        return null; 
    } 
}, bindings).call();

Tạm dừng các yêu cầu long-lived

Đối với Comet, có một trở ngại khác. Một máy chủ có thể tạm dừng một yêu cầu long-lived như thế nào mà không ảnh hưởng đến hiệu năng và sau đó phục hồi và hoàn thành nó ngay khi một sự kiện máy chủ đến?

Rõ ràng, bạn không thể chỉ đơn giản giữ các yêu cầu và đáp ứng, mà nó có thể gây ra thiếu luồng và tiêu thụ bộ nhớ cao. Việc tạm dừng một yêu cầu long-polling, trong số thiết bị I/O non-blocking, đòi hỏi phải có một API đặc trưng. Theo Java, đặc tả Servlet 3.0 cung cấp một API như vậy (xem Phần 1 của loạt bài này). Liệt kê 9 thể hiện ví dụ.

Liệt kê 9. Định nghĩa một servlet không đồng bộ với Servlet 3.0
<?xml version="1.0" encoding="UTF-8"?> 

<web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee" 
         xmlns:j2ee="http://java.sun.com/xml/ns/javaee" 
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
         xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml
/ns/j2ee/web-app_3.0.xsd"> 

    <servlet> 
        <servlet-name>events</servlet-name> 
        <servlet-class>ReverseAjaxServlet</servlet-class> 
        <async-supported>true</async-supported> 
    </servlet> 

    <servlet-mapping> 
        <servlet-name>events</servlet-name> 
        <url-pattern>/ajax</url-pattern> 
    </servlet-mapping> 

</web-app>

Khi bạn đã xác định một servlet không đồng bộ, bạn có thể sử dụng API Servlet 3.0 để tạm dừng và tiếp tục lại một yêu cầu, như trong Liệt kê 10:

Liệt kê 10. Treo và tiếp tục lại một yêu cầu
AsyncContext asyncContext = req.startAsync(); 
// Hold the asyncContext reference somewhere

// Then when needed, in another thread you can resume or complete
HttpServletResponse req = 
    (HttpServletResponse) asyncContext.getResponse(); 
req.getWriter().write("data"); 
req.setContentType([...]); 
asyncContext.complete();

Trước Servlet 3.0, mỗi container đã có cơ chế riêng của mình. Continuations của Jetty là một ví dụ nổi tiếng; nhiều thư viện Reverse Ajax theo Java phụ thuộc vào các continuations của Jetty. Đây không phải là một vấn đề được quan tâm và không đòi hỏi bạn chạy ứng dụng của mình trong một container Jetty. API có đủ thông minh để phát hiện ra container mà bạn đang chạy và quay lại API Servlet 3.0, nếu có, khi chạy trong một container khác như Tomcat hay Grizzly. Điều này đúng với Comet, nhưng nếu bạn muốn tận dụng lợi thế của WebSockets, hiện tại bạn không có sự lựa chọn nào khác trừ sử dụng các tính năng container đặc trưng.

Đặc tả Servlet 3.0 vẫn chưa được phát hành, nhưng rất nhiều container đang triển khai thực hiện API này vì đó là một cách tiêu chuẩn để thực hiện Reverse Ajax.


Kết luận

WebSockets là một giải pháp Reverse Ajax mạnh, mặc dù còn một vài nhược điểm. Hiện tại nó không được hỗ trợ trên tất cả các trình duyệt và không dễ sử dụng bên phía máy chủ bằng Java mà không có sự giúp đỡ của một thư viện Reverse Ajax. Vì bạn vẫn không sử dụng một kiểu dáng yêu cầu-đáp ứng tiêu chuẩn, nên bạn không thể dựa vào việc thực thi chuỗi bộ lọc cho các phạm vi. Comet và WebSockets yêu cầu các tính năng đặc trưng phía máy chủ của các container, nên bạn cần phải chú ý khi ứng dụng một container mới bởi vì nó có thể sẽ không co giãn được.

Hãy tiếp tục Phần 3 của loạt bài, này, sẽ khám phá các API khác nhau bên phía máy chủ với Comet và WebSockets. Ngoài ra bạn sẽ tìm hiểu về Atmosphere, một framework Reverse Ajax.


Tải về

Mô tảTênKích thước
Article source codereverse_ajaxpt2_source.zip14KB

Tài nguyên

Học tập

Lấy sản phẩm và công nghệ

  • WebSocketJS (WebSocket Flash Bridge): Dùng thử công cụ HTML5 WebSocket này do Flash cung cấp.
  • Google Guice: Tìm hiểu Google Guice, framework dependency injection nhẹ cho Java 5 trở lên.
  • Jetty: Tìm hiểu Jetty, một web container và javax.servlet container, cộng thêm sự hỗ trợ cho WebSockets.
  • Apache Maven: Nhận Maven, một công cụ gộp và quản lý dự án phần mềm.
  • Bộ công cụ phát triển Java, Phiên bản 6: Nhận nền tảng Java, ấn bản chuẩn (Java SE), cho phép bạn phát triển và triển khai các ứng dụng Java trên các máy tính để bàn và các máy chủ, cũng như trong các môi trường nhúng đòi hỏi khắt khe hiện nay.
  • Hãy dùng thử phần mềm IBM miễn phí. Tải về một phiên bản dùng thử, đăng nhập vào một bản dùng thử trực tuyến, làm việc với một sản phẩm trong một môi trường sandbox hoặc truy cập nó thông qua đám mây. Chọn từ hơn 100 bản dùng thử sản phẩm của IBM.

Thảo luận

Bình luận

developerWorks: Đăng nhập

Các trường được đánh dấu hoa thị là bắt buộc (*).


Bạn cần một ID của IBM?
Bạn quên định danh?


Bạn quên mật khẩu?
Đổi mật khẩu

Bằng việc nhấn Gửi, bạn đã đồng ý với các điều khoản sử dụng developerWorks Điều khoản sử dụng.

 


Ở lần bạn đăng nhập đầu tiên vào trang developerWorks, một hồ sơ cá nhân của bạn được tạo ra. Thông tin trong bản hồ sơ này (tên bạn, nước/vùng lãnh thổ, và tên cơ quan) sẽ được trưng ra cho mọi người và sẽ đi cùng các nội dung mà bạn đăng, trừ khi bạn chọn việc ẩn tên cơ quan của bạn. Bạn có thể cập nhật tài khoản trên trang IBM bất cứ khi nào.

Thông tin gửi đi được đảm bảo an toàn.

Chọn tên hiển thị của bạn



Lần đầu tiên bạn đăng nhập vào trang developerWorks, một bản trích ngang được tạo ra cho bạn, bạn cần phải chọn một tên để hiển thị. Tên hiển thị của bạn sẽ đi kèm theo các nội dung mà bạn đăng tải trên developerWorks.

Tên hiển thị cần có từ 3 đến 30 ký tự. Tên xuất hiện của bạn phải là duy nhất trên trang Cộng đồng developerWorks và vì lí do an ninh nó không phải là địa chỉ email của bạn.

Các trường được đánh dấu hoa thị là bắt buộc (*).

(Tên hiển thị cần có từ 3 đến 30 ký tự)

Bằng việc nhấn Gửi, bạn đã đồng ý với các điều khoản sử dụng developerWorks Điều khoản sử dụng.

 


Thông tin gửi đi được đảm bảo an toàn.


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=70
Zone=Nguồn mở
ArticleID=855814
ArticleTitle=Reverse Ajax, Phần 2: WebSockets
publish-date=01222013