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.
12 trang |
Chia sẻ: lylyngoc | Lượt xem: 1844 | Lượt tải: 1
Bạn đang xem nội dung tài liệu Reverse Ajax, Phần 2: WebSockets, để tải tài liệu về máy bạn click vào nút DOWNLOAD ở trên
Reverse Ajax, Phần 2: WebSockets
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
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:
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:
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.
Về đầu trang
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.
Về đầu trang
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
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).
Về đầu trang
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
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, Object> bindings = new HashMap, 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() {
@Override
public Object call() throws Exception {
// call your repository or any service using the scoped objects
outbound.sendMessage([...]);
return null;
}
}, bindings).call();
Về đầu trang
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
<web-app version="3.0" xmlns=""
xmlns:j2ee=""
xmlns:xsi=""
xsi:schemaLocation="
/ns/j2ee/web-app_3.0.xsd">
events
ReverseAjaxServlet
true
events
/ajax
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