WebSockets and SockJS with Immutant and Vert.x - Part 2
This is a followup to our post last week on WebSockets with Vert.x. If you haven't read it, you should do so now. In that post, we set up a simple echo service in Vert.x that bridged the Vert.x EventBus to the browser. But that echo service wasn't very useful - there was no way to process incoming messages outside of the daemon, and no way to send messages down to the browser client from other parts of the application. Today, we're going to look at bridging the EventBus over to Immutant messaging, allowing us to actually interact with the client from anywhere within our application.
Our application
We'll be using the same application we used in the last post, but will be working off of a branch.
To get started, clone the app and run it:1
cd /path/to/simple-immutant-vertx-demo
git checkout with-messaging
lein do immutant deploy, immutant run
Then browse to http://localhost:8080/. You should see a UI that lets you send messages and see those messages echoed back, but now they come back in uppercase:
Let's see some code!
Most of the application remains the same as it did before. But instead
of just copying messages from the request address to the response
address, we've now wired our
demo.bridge
namespace
to the Immutant messaging system. We now have functions that bridge
EventBus addresses to Immutant messaging destinations, and vice-versa,
and have modified the init-bridge
function to map the appropriate
addresses and destinations:
(ns demo.bridge (:require [vertx.embed :as vembed :refer [with-vertx]] [vertx.http :as http] [vertx.http.sockjs :as sockjs] [vertx.eventbus :as eb] [immutant.messaging :as msg])) (defn dest->eventbus "Sets up a bridge to copy messages from an Immutant messaging dest to a Vertx address." [vertx dest address] (msg/listen dest #(with-vertx vertx (eb/publish address %)))) (defn eventbus->dest "Sets up a bridge to copy messages from a Vertx address to an Immutant messaging dest." [vertx address dest] (with-vertx vertx (eb/on-message address (partial msg/publish dest)))) (defn- start-sockjs-bridge "Creates a Vert.x http server, a sockjs server within that http server, then installs an eventbus bridge in the sockjs server." [vertx host port path] (println (format "Starting SockJS bridge at http://%s:%s%s" host port path)) (with-vertx vertx (let [server (http/server)] (-> server (sockjs/sockjs-server) (sockjs/bridge {:prefix path} [{}] [{}])) (http/listen server port host)))) (defn init-bridge "Initializes the embedded vertx instance, bridges to Immutant destinations, and starts the sockjs bridge." [{:keys [request-dest response-dest]}] (let [vertx (vembed/vertx)] (eventbus->dest vertx "demo.request" request-dest) (dest->eventbus vertx response-dest "demo.response") {:vertx vertx :server (start-sockjs-bridge vertx "localhost" 8081 "/eventbus")}))
Now that demo.bridge
no longer echos, but instead expects something
on the other end of the request-dest
, we need something listening on
the other end to do the work. We've added this to the
demo.init
namespace,
which is also where we define the request/response destination
names. Our listener here just watches queue.request
, uppercases each
message, and publishes it to topic.response
. Since we have bridged
those same destinations in demo.bridge
, we again have a completed
circle from the client and back:
(ns demo.init (:require [demo.web :as web] [demo.daemon :as daemon] [immutant.messaging :as msg])) (def config {:response-dest "topic.response" :request-dest "queue.request" :process-fn (memfn toUpperCase)}) (defn init [] (let [{:keys [request-dest response-dest process-fn]} config] (msg/start request-dest) (msg/start response-dest) (msg/listen request-dest #(msg/publish response-dest (process-fn %)))) (web/start) (daemon/start config))
Touch the UI from anywhere
Now that we've bridged the EventBus to the Immutant messaging system,
we can interact with our client UI from anywhere within our
application. Just to beat the horse a bit more, let's do it from the
repl. Connect to the nREPL endpoint in the application running on
port 5309
2 using your favorite client, then try sending messages
directly to the response topic, or to the request queue to have them
uppercased first:
user> (require '[immutant.messaging :as msg]) nil user> (msg/publish "topic.response" "ahoyhoy") #<HornetQTextMessage HornetQMessage[ID:8af51642-2478-11e3-9deb-25745b71356d]:PERSISTENT> user> (msg/publish "queue.request" "ahoyhoy") #<HornetQTextMessage HornetQMessage[ID:90e4b5b8-2478-11e3-9deb-25745b71356d]:PERSISTENT> user>
You can also send structured messages:
user> (msg/publish "topic.response" {:x :y}) #<HornetQTextMessage HornetQMessage[ID:e09bf794-2478-11e3-9deb-25745b71356d]:PERSISTENT>
And see them all displayed in the client UI:
Fare thee well
We've extended our prior example to make it actually useful, and
maintained a separation of concerns within our application - code
outside of the demo.bridge
namespace has no knowledge of Vert.x, nor
of the UI communication mechanism. We think this provides a compelling
way to provide dynamic updates to the browser, but if you don't, or
have any other questions, comments, or feedback, please
get in touch.
- ^ This assumes you have a recent Immutant installed.
- ^ The demo application specifies
5309
as the:nrepl-port
in itsproject.clj
. If you have:immutant {:nrepl-port some-port}
set in your~/.lein/profiles.clj
, that will override5309
and you'll need to connect to whatever port the endpoint is bound to.