This guide walks through the basics of clj-wamp and the WAMP subprotocol.

Get Started

Let's create a new clj-wamp project from scratch so you can see it in action.

The guide assumes you already have java and leiningen installed on your system.

From the command line, run the following commands:

% lein new clj-wamp wamptutorial
Created new clj-wamp project: wamptutorial

% cd wamptutorial
% lein run

...and point your browser to localhost:8080, where you'll see:

Open your browser's web developer console, and click the buttons.

You should see console debug messages of the calls being sent and events received.

WAMP Overview

You'll have a better idea of what's happening if we take a quick look at the WAMP protocol itself...

The WAMP specification defines 9 types of messages that are used between client and server. They are categorized into 3 groups: Auxiliary, RPC, and PubSub

MessageType IDDirectionCategory
WELCOME0Server-to-clientAuxiliary
PREFIX1Client-to-serverAuxiliary
CALL2Client-to-serverRPC
CALLRESULT3Server-to-clientRPC
CALLERROR4Server-to-clientRPC
SUBSCRIBE5Client-to-serverPubSub
UNSUBSCRIBE6Client-to-serverPubSub
PUBLISH7Client-to-serverPubSub
EVENT8Server-to-clientPubSub

All WAMP messages are transmitted as JSON UTF-8 encoded strings to/from the server.

For example, here is a WELCOME message that the server sends to the client once a connection has been established:

[0,"1372019248579-1",1,"clj-wamp/1.0.0"]

Which is defined as:

[ TYPE_ID_WELCOME , sessionId , protocolVersion, serverIdent ]

For more details on the WAMP specification, check out the WAMP.ws documentation. It's good stuff.

We will cover some of the message types later in the tutorial.

Project Structure

The clj-wamp new project template generates several files, but only two files really apply to this tutorial:

The JavaScript client: resources/public/index.html

and the Clojure server: src/wamptutorial/websocket.clj

wamptutorial
├── README.md
├── project.clj
├── resources
│   └── public
│       └── index.html     <- *client*
├── resources-dev
│   ├── config.clj
│   └── log4j.properties
└── src
    └── wamptutorial
        ├── config.clj
        ├── main.clj
        ├── routes.clj
        └── websocket.clj  <- *server*

JavaScript Client

The meat of the client code is the AutobahnJS JavaScript library for WAMP WebSocket communication.

AutobahnJS has some neat features, like:

  • flexible, automatic reconnect
  • pluggable promises/deferreds

To connect to a WAMP WebSocket server, specify the server URI, options, and connect/disconnect callbacks:

ab.connect(
    // WAMP server
    'ws://wamp-server-uri',

    // on connect
    function (sess) { ... },

    // on disconnect
    function (code, reason) { ... },

    // options
    {'maxRetries': 60,
     'retryDelay': 30000,
     'skipSubprotocolCheck': true}
);

On connect, the AutobahnJS library will automatically process the WELCOME message. The session ID is stored in the session object, and can be retrieved via sess.sessionid().

Clojure Server

The server uses clj-wamp's http-kit-handler to define callbacks for the open, close, call, and pubsub events.

This wamp-handler attaches to a Compojure route in src/wamptutorial/routes.clj, which provides the WebSocket endpoint: ws://localhost:8080/ws

Now that you've gotten a high-level overview of everything, let's take a closer look into WAMP's primary message categories.

WAMP Prefixes

In the next sections you will find that the PubSub topics and RPC procedures are identified in the client with CURIEs (Compact URI Expressions).

For example:

sess.prefix("event", "http://wamptutorial/event#");
sess.subscribe("event:chat", onChatEvent);

The "event:chat" string in the subscribe call is a CURIE of the "http://wamptutorial/event#chat" URI.

The PREFIX message allows the client to abbreviate fully qualified URIs in order to reduce communication volume with the server: [1,"event","http://wamptutorial/event#"]

See the PREFIX Message docs @ WAMP.ws for more info.

WAMP PubSub

The following is a basic example of PubSub events with clj-wamp, where the server relays all PUBLISH messages to subscribed clients, as-is.

In the client, during connection of the WebSocket, we will subscribe an event handler for the event:chat topic:

...and upon click of the chat button, a PUBLISH message will be sent to the server and broadcast out to all subscribed clients. Open up an additional browser window and watch the events be delivered to both.

Taking a look at the server code:

The boolean in the :on-subscribe map {(evt-url "chat") true} tells clj-wamp to allow all clients to subscribe to this topic.

Similarly, the boolean in the :on-publish map tells clj-wamp to relay all received messages to subscribed clients.

Note: You can also use a callback function, instead of a boolean, to restrict certain clients from subscribing, or to broker/rewrite messages that are published.

See the API docs for more information, specifically the on-subscribe-fn? and on-publish-fn callbacks.

In our example, if we were to trace the PubSub messages from the client to the server and back again, we would see the following order of events:

  1. Client sends a SUBSCRIBE message to the server: [5,"event:chat"]
  2. Server receives the SUBSCRIBE message, and adds the client to its internal list of subscribers on the "event:chat" topic.
  3. Client sends a PUBLISH message to the server: [7,"event:chat","foo"] [via sess.publish("event:chat", "foo");]
  4. Server receives the PUBLISH message and checks it's map for a topic match. Since there is a match with a value of true, the server allows the EVENT message to be sent to all subscribed clients: [8,"http://wamptutorial/event#chat","foo"]
  5. Client receives the EVENT message (since it's subscribed), and the onChatEvent JS handler is called.

Also note that during a client-side publish, you have additional options for whitelisting and blacklisting other clients.

WAMP Remote Procedure Calls

RPC calls are different than PubSub messages in that they apply only to the server and the one client who made the call.

The following is a example of a remote "echo" procedure call, where the parameter is echoed back to the client in the response.

In the client, upon click of a button, we send a CALL message to the server:

The sess.call() method returns a promise that will trigger callbacks upon a CALLRESULT or CALLERROR response.

In the server, we've mapped an RPC topic to the function identity:

In our example, if we were to trace the RPC messages from the client to the server and back again, we would see the following order of events:

  1. Client sends a CALL message to the server: [2,"0.arg98rkv3aowp14i","rpc:echo","test"] [via sess.call("rpc:echo", "test");]
  2. Server receives the CALL message and checks it's :on-call map for a topic match. Since there is a match with a function, the server applys the call parameters to the identity function and returns a CALLRESULT message to the client: [3,"0.arg98rkv3aowp14i","test"]
  3. Client receives the CALLRESULT message, and triggers the appropriate callback handler.

Note: if any exceptions are thrown during a function call on the server, a CALLERROR result will be sent out to the client: [4,"0.wg3eowm7t6pf1or","http://api.wamp.ws/error#internal","internal error","An exception"]

WAMP RPC calls are asynchronous. It is possible that multiple calls could be in progress, where the client has yet to receive a result.

WAMP Challenge-Response Authentication

AutobahnJS provides a simple way of doing authentication using the WAMP protocol. Here's how it works...

  1. Client obtains the user's credentials (ie. username) and password through some method, like a prompt or via the web session.
  2. A RPC call authreq is made to the server with the credential key, and a response is sent back to the client with a challenge hash string.
  3. Using the challenge string, the client encrypts the user's password and makes a second RPC call auth with this digital signature.
  4. The server validates the signature against the stored credentials and returns back a map of RPC/PubSub permissions.

To enable WAMP-CRA in clj-wamp, add a :on-auth map with:

  • :secret A callback function for obtaining the user's secret (typically from a database).
  • :permissions A callback function that returns the PubSub/RPC permissions for the user.

While the permissions are sent back to inform the client of what is allowed, they are primarily used on the server to allow/deny access to the various RPC/PubSub topics.

If an RPC topic is not allowed, an unauthorized error is sent back.

And if a PubSub topic is not allowed, the publish or subscription is dropped/ignored.

In the client, we issue the two authreq and auth RPC calls during the connection process:

More Information