Temporary Queues with RabbitMQ using Web Stomp

In a previous article we saw how we can push messages from server to client using Stomp protocol over web sockets. Today I would like to take this to a higher level by using RabbitMQ temporary queues. Sometimes you just need to create temporary queues to allow communication between your client and your server for a small period of time or for a very specific use case. Temporary queues are great for that, let's dive in.


We are going to use SockJS and Stomp javascript libraries for the client to server communication. I am not going into installation details of those libraries since I already covered this part in my previous blog post.

Let's start my refreshing our memories on how to subscribe to a queue from the client using Stomp library:

var ws = new SockJS('http://127.0.0.1:15674/stomp')  
var client = Stomp.over(ws)

client.subscribe('/exchange/foo', function() {...})  

If you ever tried to passe something else than /exchange/ then you might have run into couple of errors. In fact here is the list of supported queue type you can subscribe too.

  • /exchange -- subscribe to arbitrary binding pattern;
  • /queue -- subscribe to queues managed by the STOMP gateway;
  • /amq/queue -- subscribe to queues created outside the STOMP gateway;
  • /topic -- subscribe to transient and durable topics;
  • /temp-queue -- create temporary queues (using reply-to header);

Looking at this list you might just try replacing /exchange/ by /temp-queue and go home. Unfortunately it is not possible to directly subscribe to temporary queues like this.

Temp Queue Destinations

According to RabbitMQ's documentation:

Temp queue destinations allow you to define temporary destinations in the reply-to header of a SEND frame.

Meaning that you have to first initiate a SEND request (publish) given a special reply-to header that will tell the server on which queue it should push its response to.

Temp queues are managed by the broker and their identities are private to each session -- there is no need to choose distinct names for temporary queues in distinct sessions.

Meaning that temp queue's name are private between sessions and you should not worry too much about duplicated names. As a matter of fact on the client side you will define a temp queue by a name like for example /temp-queue/foo and it will actually create a queue with a random name on the broker (RabbitMQ). But we'll see that in action in a minute.

To use a temp queue, put the reply-to header on a SEND frame and use a header value starting with /temp-queue/.

I know you guys like code so let's translate that in actual JS code:

var ws = new SockJS('http://127.0.0.1:15674/stomp')  
var client = Stomp.over(ws)

client.send('/queue/some_queue', { 'reply-to': '/temp-queue/bar' }, 'Hello World!')  

As the documentation states, we issue a SEND frame on an arbitrary queue named /queue/some_queue and specify in the reply-to header the named of the temporary queue the server should respond to.

It's also worth saying that the client will automatically subscribe to the temporary queue (here /temp-queue/bar) in order to receive any responses.

As soon at the frame is sent to the broker, take a look at the queues in RabbitMQ interface and you'll see a brand new queue with a random generated name. Notice that if you disconnect the user from the session, by closing the browser for example, it automatically delete the temporary queue from the broker. As such, there is no need for explicit clean up of reply queues.

Server side

We've seen how to create a temporary queue from the client by issuing a SEND frame to the server with a special reply-to header. So far so good, let's now see how we can subscribe and respond to that request.

First let's subscribe to the SEND frame destination queue, which in our example is /queue/some_queue. For simplicity I am going to use the Bunny gem to connect to the broker:

require 'bunny'

conn = Bunny.new  
conn.start

ch = conn.create_channel  
q = ch.queue('some_queue', durable: true, auto_delete: false)  
q.subscribe(block: true, manual_ack: false) do |_, properties, payload|  
  puts "received #{payload} with properties #{properties.inspect}"
end  

At this poing your ruby console should be blocking, waiting for a new message and your RabbitMQ interface should look something like this:

image

You should be all set to publish the SEND frame:

var ws = new SockJS('http://127.0.0.1:15674/stomp')  
var client = Stomp.over(ws)

client.send('/queue/some_queue', { 'reply-to': '/temp-queue/bar' }, 'Hello World!')  

If all went well, the ruby console should output something like this:

received "Hello World!" with properties {:headers=>{"content-length"=>"14"}, :reply_to=>"amq.gen-CmzD2uA7YEevVx63jYFvAQ"}  

It's a good moment to take a look at the RabbitMQ interface and see that a new queue was created. Notice that the queue name match the string passed in the properties hash of the bunny message

:reply_to=>"amq.gen-CmzD2uA7YEevVx63jYFvAQ"

Your server will need to use that randomly generated queue name to issue its response to the client. The tricky part is that you normally can't subscribe directly to amq.* queues. If you tried to do that with bunny here is what you would get:

> ch.queue('amq.gen-CmzD2uA7YEevVx63jYFvAQ')
Bunny::AccessRefused: ACCESS_REFUSED - queue name 'amq.gen-CmzD2uA7YEevVx63jYFvAQ' contains reserved prefix 'amq.*'  

So the trick is to use the RabbitMQ default exchange to send the response to the client. As the matter of fact, the default exchange is implicitly bound to every queue, with a routing key equal to the queue name.

Let's do that in our ruby script:

require 'bunny'

conn = Bunny.new  
conn.start

ch = conn.create_channel  
q = ch.queue('some_queue', durable: true, auto_delete: false)  
x = ch.default_exchange  
q.subscribe(block: true, manual_ack: false) do |_, properties, payload|  
  puts "received #{payload} with properties #{properties.inspect}"

  x.publish("Welcome!", routing_key: properties[:reply_to])
end  

Good! You should be all set to give it a try. On the client side, just publish the initial message with the reply-to header and watch the server respond to that temporary queue with the Welcome! message. Here are my logs on the client side:

Note that my temporary queue name changed between screenshots but they should be the same ;)

The Unhandled received MESSAGE just means that we don't have any subscribers to that message, nothing to worry.

What we've learn

We saw that the Stomp protocol allows us to define different destinations behaviors supported by RabbitMQ broker. We've look into details at how to use the temporary queues for a client / server communication using Bunny. Temporary queues are great for short time behaviors or semi-private functionality. The fact that temporary queues are automatically cleaned by the broker is great and allows us to concentrate on the feature itself. Though it was not easy to setup, you now have a better understanding on how and when to use them.

Show Comments