Skip to main content

AMQP using WebSockets

At work, one of our projects needed some real-time communication between clients. Our first preference was that the clients could connect and communicate using just the browser, without any extra applications running on the client device. Having the communication done through the browser would make it easy for even mobile devices to connect and interact with the system, let alone simplifying things for desktop clients. We eventually came to a solution which uses the AMQP protocol and WebSockets.

After some research, I realized that we would need to use WebSockets to take full advantage of modern browsers, since WebSocket connections allow servers to push data to the client arbitrarily. This arbitrary pushing of data was necessary to satisfy our real-time goals, as we wanted to minimize bandwidth and have updates on clients as quickly as possible (constant polling from the clients would waste bandwidth and resources).

WebSockets

If you're not familiar with HTML5 WebSockets, I advise you to check them out. They're pretty cool! Basically, they allow you to specify an endpoint on a server (which must support WebSocket connections to that endpoint), and a WebSocket connection will be negotiated by a simple HTTP exchange. After the HTTP exchange verifies that all is well and good with the WebSockets endpoint, a WebSocket connection between the client and server is established on the appropriate port, given the protocol requested. After that, messages can be sent between client and server.

Because of the asynchronous nature of Javascript, these sends and receives are not blocking. The great utility for web applications lies in this fact; registering a function to be called whenever some new data comes in allows your application to react immediately to new data from the server, without having to make an AJAX request to get the data.

AMQP

After we knew it was possible to do real-time messaging between client and server, we needed a messaging protocol. There are many out there that we explored, including STOMP and MQTT. However, STOMP was too verbose for our liking (as it doesn't pack the data tightly, instead transferring data as text), and MQTT doesn't have all the bells and whistles we needed. AMQP, the Advanced Message Queuing Protocol, seemed to fit our requirements nicely.

AMQP is a messaging protocol that allows for multiple modes of operation, including the Publish-Subscribe messaging paradigm which was of interest to us. It also has a great binary packing format, wherein you can specify many types of data to be packed, as well as your own custom datatypes which are composed of the basic ones! This makes it easy to send arbitrary data between endpoints and have the receivers get an exact structural copy of what the sender had, regardless of whether the receivers had a priori knowledge of the format of the data.

AMQP also supports advanced message routing, and the creation, binding, subscription, and destruction of message exchanges (targets of messages) and queues (where messages wait to be read). This allows clients to create their own queues on-the-fly, rather than the server setting up the messaging structure beforehand, or manually based on client connection.

The server end of AMQP is often managed by what is called a broker. The broker manages the state behind all of the messaging structure (exchanges, queues, messages, etc.). While clients could create their own local messaging endpoints, a broker running on a server as a central node to the messaging makes it easy for clients to immediately get information from a single source, rather than establishing many peer-to-peer links. These brokers often have lots of features which allow them to act stand-alone, without the need for any wrapper code to fill in the gaps.

Our application's architecture allowed for the broker to act as a stand-alone server. We chose the Apache Qpid broker for it's maturity and stability, cross-platform implementations, and features (such as Last Value Queues and browsing consumers). In particular, the Qpid Java broker had a WebSocket plugin already written and functional! This would allow us to establish WebSocket connections directly with the broker, and then communicate with AMQP over them.

Client

Now we had an AMQP broker and an application transport layer which would allow us to get real-time communication between server and browser client, but we needed a Javascript library to send and receive the AMQP packets. This ended up being the hardest part...

There are a number of Node.js libraries for AMQP, but we wanted a library which we could load in a browser easily. Also, many of the Node.js libraries didn't work well with the Qpid broker as many were written specifically for RabbitMQ. Technically the broker shouldn't matter, but it turns out that the initial SASL authentication of the AMQP brokers differs a bit, and the Qpid broker doesn't seem to like AMQPLAIN or PLAIN authentication, which many use.

The Qpid Proton project had Javascript bindings which were actually created by cross-compiling the C source code using emscripten, but because of differences in the specification of AMQP over WebSockets and the C code, as well as some more SASL issues, these didn't work (even after a good amount of time invested in debugging the issues).

There was always the option of writing it ourselves, but the AMQP protocol is rather complicated, so we were trying to first take advantage of open source software.

I finally found the awesome Kaazing project. Kaazing aims to put a 'gateway' in front of a Qpid broker (and some other web servers) which can be communicated with over many different protocols and transport layers and acts as a proxy to the broker. This way, you could potentially have any AMQP broker in the backend, and could still use a wide range of protocols to talk to it. Kaazing also has client libraries for a handful of protocols in many languages, including just what we needed: an AMQP protocol Javascript library! It only supports the AMQP 0-9-1 standard, as opposed to the 0-10 or 1.0 standard, but that was fine for us.

Out of the box the library didn't immediately work, however after forking their source code and making some changes so that the WebSocket was using the correct subprotocol and the SASL authentication was ANONYMOUS, the client and server were talking!

I did need to make a few other modifications to the source code so that we could register browsing consumers though. A browsing consumer gets copies of the messages on a queue, but doesn't consume them (which is the default behavior, preventing other clients from getting the same message). In other words, the default behavior of consuming messages means that a particular message on a queue will only be seen by a single client, even if many clients are subscribed to that queue, whereas a browsing consumer allows every subscribed client (who is a browsing consumer) to see a copy of each message on the queue.

I couldn't find anything online documenting how, through AMQP, one could create a browsing consumer, since this sort of behavior is often broker-specific. But the Qpid documentation didn't mention it anywhere either. Sniffing packets didn't help, since the examples that did work using the Qpid Messaging API used AMQP 0-10 or 1.0, which had new fields in the protocol which specified whether the consumer subscription automatically 'acquired' messages (consume mode) or not (browse mode). So, I just went digging through the Qpid Java broker code.

Eventually, I found code that looked for the 'x-filter-no-consume' argument to be set in the message subscription method of AMQP 0-8, and I figured that it might work for AMQP 0-9-1 as well.

With some adjustments to the Javascript library, I finally was able to get browsing consumers working with the following consumer code:

channel.consumeBasic({queue: 'queueName', consumerTag: 'myTag',
    args: [
        {key: 'x-filter-no-consume', type: 'boolean', value: true}
    ]
});

And there you have it! It was a bit of a struggle, and took quite a while to get right, but for now it satisfies our needs. Hopefully more people start using AMQP through WebSockets, because it really is a great protocol, although a bit complex. We may still implement an AMQP 1.0 Javascript library if we find 0-9-1 to be lacking.

The patched AMQP 0-9-1 Javascript library that works directly with the Qpid Java broker (rather than through the Kaazing gateway) and supports browsing consumers can be found at https://github.com/afranchuk/javascript.client.

Enjoy!

Comments