Ajax
簡介
ActiveMQ Classic 支援 Ajax,這是一種用於即時 Web 應用程式的非同步 Javascript 和 Xml 機制。這表示您可以建立高度即時的 Web 應用程式,充分利用 ActiveMQ Classic 的發布/訂閱特性。
Ajax 允許常規的 DHTML 客戶端(使用 JavaScript 和現代的 5 或更高版本的 Web 瀏覽器)透過 Web 發送和接收訊息。ActiveMQ Classic 中的 Ajax 支援建立在與 ActiveMQ Classic 的 REST 連接器相同的基礎上,該連接器允許任何支援 Web 的裝置透過 JMS 發送或接收訊息。
若要查看 Ajax 的實際運作,請嘗試執行範例
Servlet
需要將 AMQ AjaxServlet 安裝在您的 Web 應用程式中,以支援透過 Ajax 的 JMS
<servlet>
<servlet-name>AjaxServlet</servlet-name>
<servlet-class>org.apache.activemq.web.AjaxServlet</servlet-class>
</servlet>
...
<servlet-mapping>
<servlet-name>AjaxServlet</servlet-name>
<url-pattern>/amq/*</url-pattern>
</servlet-mapping>
此 servlet 會同時提供所需的 js 檔案,並處理 JMS 請求和回應。
Javascript API
amq 的 ajax 功能在客戶端由 amq.js 腳本提供。從 ActiveMQ Classic 5.4 開始,此腳本會利用三種不同配接器之一來支援與伺服器的 ajax 通訊。目前支援 jQuery、Prototype 和 Dojo,而且所有三個程式庫的最新版本都隨附於 ActiveMQ Classic 中。
<script type="text/javascript" src="js/jquery-1.4.2.min.js"></script>
<script type="text/javascript" src="js/amq\_jquery\_adapter.js"></script>
<script type="text/javascript" src="js/amq.js"></script>
<script type="text/javascript">
var amq = org.activemq.Amq;
amq.init({
uri: 'amq',
logging: true,
timeout: 20
});
</script>
包含這些腳本會建立名為 amq
的 Javascript 物件,此物件提供 API 來傳送訊息並訂閱頻道和主題。
傳送訊息
從 javascript 客戶端傳送 JMS 訊息所需的一切,就是呼叫方法
amq.sendMessage(myDestination,myMessage);
其中 myDestination
是目的地的 URL 字串位址(例如,topic://MY.NAME
或 channel://MY.NAME
),而 myMessage
是以 XML 內容編碼的任何格式正確的 XML 或純文字。
接收訊息
若要接收訊息,客戶端必須定義訊息處理函數,並將其註冊至 amq 物件。例如
var myHandler =
{
rcvMessage: function(message)
{
alert("received "+message);
}
};
amq.addListener(myId,myDestination,myHandler.rcvMessage);
其中 myId
是可以用於稍後呼叫 amq.removeHandler(myId)
的字串識別碼,而 myDestination
是目的地的 URL 字串位址(例如,topic://MY.NAME
或 channel://MY.NAME
)。收到訊息時,會回呼 myHandler.rcvMessage
函數,將訊息傳遞至您的處理程式碼。
「訊息」實際上是文字訊息的文字或字串表示法(toString()
),以物件訊息而言。
請注意,依預設,透過 Stomp 發布的訊息,若包含 content-length
標頭,則 ActiveMQ Classic 會將其轉換為二進位訊息,而且您的 Web 客戶端將無法看見。從 ActiveMQ Classic 5.4.0 開始,您可以透過在可能會被 Web 客戶端取用的訊息中,一律將 amq-msg-type
標頭設定為 text
來解決此問題。
選取器支援
依預設,ajax 客戶端將接收其所訂閱的主題或佇列上的所有訊息。在 ActiveMQ Classic 5.4.1 中,amq.js 支援 JMS 選取器,因為僅接收這些訊息的子集通常非常有用。選取器透過選用的第 4 個參數提供給 amq.addListener
呼叫。
amq.addListener( myId, myDestination, myHandler.rcvMessage, { selector:"identifier='TEST'" } );
當以此方式使用時,Javascript 客戶端只會接收包含設定為值 TEST
的 identifier
標頭的訊息。
在多個瀏覽器視窗中使用 AMQ Ajax
單一瀏覽器中的所有視窗或索引標籤都與 ActiveMQ Classic 伺服器共用相同的 JSESSIONID
。除非伺服器可以區分來自多個視窗的接聽程式,否則原本要給 1 個視窗的訊息將會傳遞到另一個視窗。實際上,這表示 amq.js 在任何給定時間都只能在單一瀏覽器視窗中作用。從 ActiveMQ Classic 5.4.2 開始,藉由允許每次呼叫 amq.init
時指定唯一的 clientId
來解決此問題。完成此動作後,同一個瀏覽器中的多個視窗可以順利共存。每個視窗可以在代理程式上具有一組不同的訊息訂閱,彼此之間沒有互動。
在此範例中,我們會使用目前時間(在載入網頁時)做為唯一識別碼。只要兩個瀏覽器視窗不是在同一個毫秒內開啟,此方法就有效,而且這是 ActiveMQ Classic 隨附的範例 chat.md 所使用的方法。其他確保 clientId
唯一性的配置可以輕鬆設計。請注意,此 clientId
僅需在單一工作階段中為唯一。(在不同瀏覽器中同一個毫秒內開啟的瀏覽器視窗不會互動,因為它們處於不同的工作階段中。)
org.activemq.Amq.init({
uri: 'amq',
logging: true,
timeout: 45,
clientId:(new Date()).getTime().toString()
});
請注意,此 clientId
在單一索引標籤或視窗中的所有訊息訂閱中都是通用的,而且與在 amq.addListener
呼叫中做為第一個引數提供的 clientId
完全不同。
- 在
amq.init
中,clientId
用於區分共用相同JSESSIONID
的不同 Web 客戶端。當它們呼叫amq.init
時,單一瀏覽器中的所有視窗都需要唯一的clientId
。 - 在
amq.addListener
中,clientId
用於將訊息訂閱與應該在收到該訂閱的訊息時呼叫的回呼函式建立關聯。這些clientId
值是每個網頁內部的,而且不需要在多個視窗或索引標籤之間是唯一的。
運作方式
AjaxServlet 和 MessageListenerServlet
amq 的 ajax 功能在伺服器端由 AjaxServlet 處理,此 servlet 擴充了 MessageListenerServlet。此 servlet 負責追蹤現有的客戶端(使用 HttpSesssion)並延遲建立客戶端傳送和接收訊息所需的 AMQ 和 javax.jms 物件(例如,Destination、MessageConsumer、MessageAVailableListener)。此 servlet 應在提供 Ajax 客戶端的 Web 應用程式內容中對應至 /amq/*
(這可以變更,但必須更新客戶端 javascript amq.uri
欄位以符合)。
客戶端傳送訊息
從客戶端傳送訊息時,會使用其中一個受支援的連線配接器(jQuery、Prototype 或 Dojo)的 API,針對 XmlHttpRequest,將訊息編碼為 POST 請求的內容。amq 物件可以將數個 sendMessage 呼叫組合成單一 POST,如果它可以在不增加額外延遲的情況下執行此動作(請參閱下方的輪詢)。
當 MessageListenerServlet 接收到 POST 時,訊息會使用其類型(在此情況下為 send
,而非 listen
或 unlisten
,請參閱下方)和目的地,以 application/x-www-form-urlencoded
參數的形式解碼。如果目的地頻道或主題不存在,則會建立。訊息會以 TextMessage 的形式傳送至目的地。
接聽訊息
當客戶端註冊接聽程式時,訊息訂閱請求會以 POST 的方式從客戶端傳送到伺服器,就像訊息一樣,但類型為 listen
。當 MessageListenerServlet 收到 listen
訊息時,會延遲建立 MessageAvailableConsumer 並在其上註冊接聽程式。
等待輪詢訊息
當呼叫 MessageListenerServlet 建立的接聽程式來表示訊息可用時,由於 HTTP 客戶端-伺服器模型的限制,無法直接將該訊息傳送到 ajax 客戶端。相反地,客戶端必須執行一種特殊的訊息輪詢。輪詢通常表示定期發出請求,以查看是否有可用的訊息,並且存在一種權衡:輪詢頻率高,則系統閒置時會產生過度的負載;或頻率低,則偵測新訊息的延遲時間高。
為了避免負載與延遲之間的權衡,AMQ 使用等待輪詢機制。一旦載入 amq.js 腳本,客戶端就會開始輪詢伺服器以尋找可用的訊息。輪詢請求可以以 GET 請求或 POST 請求的形式傳送(如果有其他訊息準備從客戶端傳遞到伺服器)。當 MessageListenerServlet 收到輪詢時
- 如果輪詢請求為 POST,則會處理所有
send
、listen
和unlisten
訊息 - 如果任何訂閱頻道或主題上沒有可供客戶端使用的訊息,則 servlet 會暫停請求處理,直到
- 呼叫 MessageAvailableConsumer 接聽程式以表示訊息現在可用;或
- 逾時到期(通常約 30 秒,這小於所有常見的 TCP/IP、Proxy 和瀏覽器逾時)。
- 一個 HTTP 回應會被傳回給客戶端,其中包含所有可用的訊息,這些訊息被封裝為
text/xml
格式。
當 amq.js JavaScript 接收到輪詢的回應時,它會處理所有訊息,並將它們傳遞給已註冊的處理函式。一旦它處理完所有訊息,它會立即向伺服器發送另一個輪詢。
因此,amq Ajax 功能的閒置狀態是一個「停駐」在伺服器中的輪詢請求,等待訊息被發送到客戶端。這個「停駐」的請求會定期被一個逾時機制刷新,以防止任何 TCP/IP、代理伺服器或瀏覽器逾時關閉連線。伺服器因此能夠透過喚醒「停駐」的請求並允許發送回應,來非同步地向客戶端發送訊息。
客戶端可以透過建立(或使用現有的)到伺服器的第二個連線,來非同步地向伺服器發送訊息。然而,在處理輪詢回應期間,正常的客戶端訊息發送會被暫停,因此所有要發送的訊息都會被排隊,並以單一 POST 請求與將在處理結束時(無延遲地)發送的輪詢一起發送。這確保了客戶端和伺服器之間只需要兩個連線(大多數瀏覽器的標準)。
無執行緒等待
上述的等待輪詢是使用 Jetty 6 Continuations 機制實作的。這允許與請求關聯的執行緒在等待期間被釋放,因此容器不需要每個客戶端都有一個執行緒(這可能是一個很大的數字)。如果使用其他 Servlet 容器,Continuation 機制會退回使用等待,且執行緒不會被釋放。
與 Pushlets 的比較
首先,我們可以輕鬆地為 ActiveMQ Classic 添加 Pushlets 的支援。然而,我們由於各種原因更喜歡使用 Ajax 方法。
- 使用 Ajax 表示我們為每個發送/接收使用不同的 HTTP 請求,這對網路基礎設施(防火牆、代理伺服器、快取等等)更加友好,而不是使用無限期的長 GET 請求。
- 我們仍然可以利用 HTTP 1.1 keep-alive 連線和管道處理,以獲得在客戶端和伺服器端之間進行通訊時使用單一連線的效率;儘管這種方式可以在任何支援 HTTP 的基礎設施下運作。
- 伺服器是純 REST 的,因此可以與任何客戶端一起使用(而不是綁定到 Pushlet 方法所需的頁面上使用的自訂 JavaScript 函式呼叫)。因此,Pushlets 將伺服器綁定到網頁;使用 Ajax,我們可以有一個通用的服務,可以與任何頁面一起使用。
- 客戶端可以控制輪詢的頻率和逾時時間。例如,它可以透過使用 20 秒的逾時 HTTP GET 來避免某些瀏覽器中 Pushlets 的記憶體問題。或者使用零逾時的 GET 來輪詢佇列。
- 更容易充分利用 HTTP 訊息編碼,而不是使用 JavaScript 函式呼叫作為傳輸協定。
- Pushlets 假設伺服器知道客戶端使用了哪些函式,因為伺服器基本上是在連線上寫入 JavaScript 函式呼叫 - 我們最好發送通用的 XML 數據包(或字串或任何訊息格式),並讓 JavaScript 客戶端與伺服器端完全解耦。
- Ajax 支援乾淨的 XML 支援,允許將完整的 XML 文件串流到客戶端,以實現豐富的訊息,這些訊息可以透過標準 JavaScript DOM 支援輕鬆處理。