After building Hookout a little while back, I’ve been considering other things you could do to funnel clients through a server without them necessarily being reachable, or having an entire address space of their own. Hookout was working within the constraints of the reverse http protocol, where clients could speak only http. I wanted to explore a space where this wasn’t necessarily a requirement. At the same time, Rabbit gained a plugin mechanism.
The combination of these is Trapeze. Trapeze provides HTTP request routing via AMQP. Requests are received by a RabbitMQ plugin, which then forwards these requests via AMQP to listening applications. Applications then respond via a private reply queue, and this is then forwarded back onto the web caller. Whilst this idea isn’t entirely novel (after writing Trapeze, I found http://somic.org/2008/12/18/introducing-rabbitbal/), it is an interesting area to explore, and provides a very different use case for Rabbit versus the “I want 100% guarantees of message delivery at the expense of speed” case.
Trapeze is available at http://github.com/paulj/trapeze, along with a Ruby application runner at http://github.com/paulj/trapeze-rb. Installation instructions are provided at http://github.com/paulj/trapeze/blob/master/README.rdoc.
Once installed, one can start playing with some of the interesting features of the routing algorithm. Some of the more interesting points are:
When applications are started, a routing key is provided that the client uses to subscribe to the “trapeze” exchange. When incoming requests arrive, a routing key is generated in the form: <verb>.<host parts>.<port>./.<path parts>. An AMQP topic exchange is used, allowing RabbitMQ to handle all of the logic involved in selecting the appropriate listener based on their declared subscription. The use of topic key syntax provides the ability for arbitrarily complex routing keys to be used, starting with simple subscriptions:
- “*.localhost.55672./.#” to receive everything sent to http://localhost:55672
- “*.#.example.com.*./.#” to receive everything sent to example.com and its subdomains, on any port
Through to more complex subscriptions, such as:
- “post.#./.listener” to receive only post requests to a /listener path, but on any server/port combination
- Trapeze utilises AMQP Mandatory routing to ensure that if no application has registered a listener that matches a given incoming request, a basic.return will be received, and result in a 404 being returned to the client. This single flag prevents the need for Trapeze itself to have any understanding of the connected clients – it can rely entirely on the broker to inform it whether or not a connector is attached that can handle a given request.
- To create the binding from the trapeze exchange, a named auto-delete queue is created. If multiple applications are started using the same queue name, queue load-balancing takes effect, allowing for requests to be balanced between a number of consumers. The use of QOS even opens the possibility of balancing to consumers able to process requests at different rates.
Trapeze has already spurred some interesting reflections about changes that could be made to Rabbit to do interesting things in these kinds of cases. I’m quite interested to hear if anyone else has any suggestions that could make Trapeze more useful or interesting. Also, if you’d like to write an adapter for a different language (for example, Python WSGI or even a Tomcat connector), then do get in touch. And please do check out the source on Github.