blob: 475c3dad59f6457cdd8acadccb8d6eab499ac5d0 [file] [log] [blame]
<div class="wiki-content maincontent"><h1>Introduction</h1>
<p>ActiveMQ supports <a shape="rect" href="http://en.wikipedia.org/wiki/Ajax_%28programming%29">Ajax</a> which is an Asychronous Javascript And Xml mechanism for real time web applications. This means you can create highly real time web applications taking full advantage of the publish/subscribe nature of ActiveMQ</p>
<p>Ajax allows a regular DHTML client (with JavaScript and a modern version 5 or later web browser) to send and receive messages over the web. Ajax support in ActiveMQ builds on the same basis as the <link><page ri:content-title="REST"></page></link> connector for ActiveMQ which allows any web capable device to send or receive messages over JMS.</p>
<p>To see Ajax in action, try <link><page ri:content-title="Web Samples"></page><link-body>running the examples</link-body></link></p>
<h1>The Servlet</h1>
<p>The AMQ AjaxServlet needs to be installed in your webapplications to support JMS over Ajax:</p>
<structured-macro ac:macro-id="cf8a34e3-339b-4d2e-abcd-14a0b1ec263c" ac:name="code" ac:schema-version="1"><plain-text-body>
...
&lt;servlet&gt;
&lt;servlet-name&gt;AjaxServlet&lt;/servlet-name&gt;
&lt;servlet-class&gt;org.apache.activemq.web.AjaxServlet&lt;/servlet-class&gt;
&lt;/servlet&gt;
...
&lt;servlet-mapping&gt;
&lt;servlet-name&gt;AjaxServlet&lt;/servlet-name&gt;
&lt;url-pattern&gt;/amq/*&lt;/url-pattern&gt;
&lt;/servlet-mapping&gt;
</plain-text-body></structured-macro>
<p>The servlet both serves the required js files and handles the JMS requests and responses.</p>
<h1>Javascript API</h1>
<p>The ajax featues of amq are provided on the client side by the <a shape="rect" href="https://svn.apache.org/repos/asf/activemq/trunk/activemq-web-demo/src/main/webapp/js/amq.js">amq.js</a> script. Beginning with ActiveMQ 5.4, this script utilizes one of three different adapters to support ajax communication with the server. Current <a shape="rect" href="http://jquery.org">jQuery</a>, <a shape="rect" href="http://prototypejs.org">Prototype</a>, and <a shape="rect" href="http://www.dojotoolkit.org">Dojo</a> are supported, and recent versions of all three libraries are shipped with ActiveMQ.</p>
<structured-macro ac:macro-id="ea161b97-6cb1-43f7-a745-306923234dd2" ac:name="code" ac:schema-version="1"><plain-text-body>
&lt;script type="text/javascript" src="js/jquery-1.4.2.min.js"&gt;&lt;/script&gt;
&lt;script type="text/javascript" src="js/amq_jquery_adapter.js"&gt;&lt;/script&gt;
&lt;script type="text/javascript" src="js/amq.js"&gt;&lt;/script&gt;
&lt;script type="text/javascript"&gt;
var amq = org.activemq.Amq;
amq.init({
uri: 'amq',
logging: true,
timeout: 20
});
&lt;/script&gt;
</plain-text-body></structured-macro>
<p>Including these scripts results in the creation of a javascript object called <code>amq</code>, which provides the API to send messages and to subscribe to channels and topics.</p>
<h2>Sending a message</h2>
<p>All that is required to send a JMS message from the javascript client, is to call the method:</p>
<structured-macro ac:macro-id="8b2d1f60-54ee-4a0f-bc71-dd9306c9cf82" ac:name="code" ac:schema-version="1"><plain-text-body>
amq.sendMessage(myDestination,myMessage);
</plain-text-body></structured-macro>
<p>where <code>myDestination</code> is the URL string address of the destination (e.g. "topic://MY.NAME" or "channel://MY.NAME") and <code>myMessage</code> is any well formed XML or plain text encoded as XML content.</p>
<h2>Receiving messages</h2>
<p>To receive messages, the client must define a message handling function and register it with the <a shape="rect" href="https://svn.apache.org/repos/asf/activemq/trunk/activemq-web-demo/src/main/webapp/js/amq.js">amq</a> object. For example:</p>
<structured-macro ac:macro-id="a977b685-ac94-4ee9-847d-a46f1e798cff" ac:name="code" ac:schema-version="1"><plain-text-body>
var myHandler =
{
rcvMessage: function(message)
{
alert("received "+message);
}
};
amq.addListener(myId,myDestination,myHandler.rcvMessage);
</plain-text-body></structured-macro>
<p>where <code>myId</code> is a string identifier that can be used for a later call to <code>amq.removeHandler(myId)</code> and <code>myDestination</code> is a URL string address of the destination (e.g. "topic://MY.NAME" or "channel://MY.NAME"). When a message is received, a call back to the <code>myHandler.rcvMessage</code> function passes the message to your handling code.<br clear="none">
The "message" is actually a text of the Text message or a String representation (<code>toString()</code>) in case of Object messages.</p>
<p>Be aware that, by default, messages published via <a shape="rect" href="http://activemq.apache.org/stomp.html">Stomp</a> which include a <code>content-length</code> header will be converted by ActiveMQ to binary messages, and will not be visible to your web clients. Beginning with ActiveMQ 5.4.0, you can resolve this problem by always setting the <a shape="rect" href="https://issues.apache.org/jira/browse/AMQ-2833"><code>amq-msg-type</code> header</a> to <code>text</code> in messages which will may be consumed by web clients.</p>
<h3>Selector support</h3>
<p>By default, an ajax client will receive all messages on a topic or queue it is subscribed to. In <a shape="rect" href="http://activemq.apache.org/activemq-541-release.html">ActiveMQ 5.4.1</a> amq.js supports <a shape="rect" href="http://activemq.apache.org/selectors.html">JMS selectors</a> since it is frequently useful to receive only a subset of these messages. Selectors are supplied to an <code>amq.addListener</code> call by way of an optional 4th parameter.</p>
<structured-macro ac:macro-id="d3cf34a9-2e36-44e3-8610-62926d13eb50" ac:name="code" ac:schema-version="1"><plain-text-body>
amq.addListener( myId, myDestination, myHandler.rcvMessage, { selector:"identifier='TEST'" } );
</plain-text-body></structured-macro>
<p>When used in this way, the Javascript client will receive only messages containing an <code>identifier</code> header set to the value <code>TEST</code>.</p>
<h2>Using AMQ Ajax in Multiple Browser Windows</h2>
<p>All windows or tabs in a single browser share the same <code>JSESSIONID</code> on the ActiveMQ server. Unless the server can distinguish listeners from multiple windows, messages which were intended for 1 window will be delivered to another one instead. Effectively, this means that amq.js could be active in only a single browser window at any given time. Beginning in <a shape="rect" href="http://activemq.apache.org/activemq-542-release.html">ActiveMQ 5.4.2</a>, this is resolved by allowing each call to <code>amq.init</code> to specify a unique <code>clientId</code>. When this is done, multiple windows in the same browser can happily co-exist. Each can have a separate set of message subscriptions on the broker with no interactions between them.</p>
<p>In this example, we use the current time (at the time the web page is loaded) as a unique identifier. This is effective as long as two browser windows are not opened within the same millisecond, and is the approach used by the example <a shape="rect" href="https://svn.apache.org/repos/asf/activemq/trunk/activemq-web-demo/src/main/webapp/chat.html">chat.html</a> included with ActiveMQ. Other schemes to ensure the uniqueness of <code>clientId</code> can easily be devised. Note that this <code>clientId</code> need only be unique within a single session. (Browser windows opened in the same millisecond in separate browsers will not interact, since they are in different sessions.)</p>
<structured-macro ac:macro-id="0bed7262-f3fb-4f5b-8df6-0036f4e9432c" ac:name="code" ac:schema-version="1"><plain-text-body>
org.activemq.Amq.init({
uri: 'amq',
logging: true,
timeout: 45,
clientId:(new Date()).getTime().toString()
});
</plain-text-body></structured-macro>
<p>Note that this <code>clientId</code> is common to all message subscriptions in a single tab or window, and is entirely different from the <code>clientId</code> which is supplied as a first argument in <code>amq.addListener</code> calls.</p>
<ul><li>In <code>amq.init</code>, <code>clientId</code> serves to distinguish different web clients sharing the same <code>JSESSIONID</code>. All windows in a single browser need a unique <code>clientId</code> when they call <code>amq.init</code>.</li><li>In <code>amq.addListener</code>, <code>clientId</code> is used to associate a message subscription with the callback function which should be invoked when a message is received for that subscription. These <code>clientId</code> values are internal to each web page, and do not need to be unique across multiple windows or tabs.</li></ul>
<h1>How it works</h1>
<h2>AjaxServlet and MessageListenerServlet</h2>
<p>The ajax featues of amq are handled on the server side by the <a shape="rect" href="https://svn.apache.org/repos/asf/activemq/trunk/activemq-web/src/main/java/org/apache/activemq/web/AjaxServlet.java">AjaxServlet</a> which extends the <a shape="rect" href="https://svn.apache.org/repos/asf/activemq/trunk/activemq-web/src/main/java/org/apache/activemq/web/MessageListenerServlet.java">MessageListenerServlet</a>. This servlet is responsible for tracking the existing clients (using a HttpSesssion) and lazily creating the AMQ and javax.jms objects required by the client to send and receive messages (eg. Destination, MessageConsumer, MessageAVailableListener). This servlet should be mapped to <code>/amq/*</code> in the web application context serving the Ajax client (this can be changed, but the client javascript <code>amq.uri</code> field needs to be updated to match.)</p>
<h2>Client Sending messages</h2>
<p>When a message is sent from the client it is encoded as the content of a POST request, using the API of one of the supported connection adapters (jQuery, Prototype, or Dojo) for <a shape="rect" href="http://jibbering.com/2002/4/httprequest.html">XmlHttpRequest</a>. The <a shape="rect" href="https://svn.apache.org/repos/asf/activemq/trunk/activemq-web-demo/src/main/webapp/js/amq.js">amq</a> object may combine several sendMessage calls into a single POST if it can do so without adding additional delays (see polling below).</p>
<p>When the MessageListenerServlet receives a POST, the messages are decoded as <code>application/x-www-form-urlencoded</code> parameters with their type (in this case <code>send</code> as opposed to <code>listen</code> or <code>unlisten</code> see below) and destination. If a destination channel or topic do not exist, it is created. The message is sent to the destination as a TextMessage.</p>
<h2>Listening for messages</h2>
<p>When a client registers a listener, a message subscription request is sent from the client to the server in a POST in the same way as a message, but with a type of <code>listen</code>. When the MessageListenerServlet receives a <code>listen</code> message, it lazily creates a MessageAvailableConsumer and registers a Listener on it.</p>
<h2>Waiting Poll for messages</h2>
<p>When a Listener created by the MessageListenerServlet is called to indicate that a message is available, due to the limitations of the HTTP client-server model, it is not possible to send that message directly to the ajax client. Instead the client must perform a special type of <strong>Poll</strong> for messages. Polling normally means periodically making a request to see if there are messages available and there is a trade off: either the poll frequency is high and excessive load is generated when the system is idle; or the frequency is low and the latency for detecting new messages is high.</p>
<p>To avoid the load vs latency tradeoff, AMQ uses a waiting poll mechanism. As soon as the amq.js script is loaded, the client begins polling the server for available messages. A poll request can be sent as a GET request or as a POST if there are other messages ready to be delivered from the client to the server. When the MessageListenerServlet receives a poll it:</p>
<ol><li>if the poll request is a POST, all <code>send</code>, <code>listen</code> and <code>unlisten</code> messages are processed</li><li>if there are no messages available for the client on any of the subscribed channels or topic, the servlet suspends the request handling until:
<ul><li>A MessageAvailableConsumer Listener is called to indicate that a message is now available; or</li><li>A timeout expires (normally around 30 seconds, which is less than all common TCP/IP, proxy and browser timeouts).</li></ul>
</li><li>A HTTP response is returned to the client containing all available messages encapsulated as <code>text/xml</code>.</li></ol>
<p>When the amq.js javascipt receives the response to the poll, it processes all the messages by passing them to the registered handler functions. Once it has processed all the messages, it immediately sends another poll to the server.</p>
<p>Thus the idle state of the amq ajax feature is a poll request "parked" in the server, waiting for messages to be sent to the client. Periodically this "parked" request is refreshed by a timeout that prevents any TCP/IP, proxy or browser timeout closing the connection. The server is thus able to asynchronously send a message to the client by waking up the "parked" request and allowing the response to be sent.</p>
<p>The client is able to asynchronously send a message to the server by creating (or using an existing) second connection to the server. However, during the processing of the poll response, normal client message sending is suspended, so that all messages to be sent are queued and sent as a single POST with the poll that will be sent (with no delay) at the end of the processing. This ensures that only two connections are required between client and server (the normal for most browsers).</p>
<h2>Threadless Waiting</h2>
<p>The waiting poll described above is implemented using the <a shape="rect" href="http://docs.codehaus.org/display/JETTY/Continuations">Jetty 6 Continuations</a> mechanism. This allows the thread associated with the request to be released during the wait, so that the container does not need to have a thread per client (which may be a large number). If another servlet container is used, the Continuation mechanism falls back to use a wait and the thread is not released.</p>
<h1>Comparison to Pushlets</h1>
<p>Firstly we could easily add support for pushlets to ActiveMQ. However we prefer the Ajax approach for various reasons</p>
<ul><li>using Ajax means that we use a distinct HTTP request for each send/receive which is much more friendly to web infrastructure (firewalls, proxies, caches and so forth) rather than having an infinitely-long GET.</li></ul>
<ul><li>we can still take advantage of HTTP 1.1 keep-alive sockets and pipeline processing to gain the efficiency of a single socket used for communication between the client and server side; though in a way that works with any HTTP-capable infrastructure</li></ul>
<ul><li>the server is pure REST and so will work with any client side (rather than being tied to custom JavaScript function calls used on the page which the Pushlet approach requires). So Pushlets tie the server to the web page; with Ajax we can have a generic service which works with any page.</li></ul>
<ul><li>the client can be in control over frequency of polling &amp; timeouts. e.g. it can avoid the memory issues of Pushlets in some browsers by using a 20-second timeout HTTP GET. Or using a zero timeout GET to poll queues.</li></ul>
<ul><li>its easier to take full advantage of HTTP encoding of messages, rather than using JavaScript function calls as the transfer protocol.</li></ul>
<ul><li>pushlets assume the server knows what functions are used on the client side as the server basically writes JavaScript function calls down the scoket - it's better for us to send generic XML packets (or strings or whatever the message format is) and let the JavaScript client side be totally decoupled from the server side</li></ul>
<ul><li>Ajax supports clean XML support allowing full XML documents to be streamed to the client for rich messages which are easy to process via standard JavaScript DOM support</li></ul>
</div>