Spell is an Elixir WAMP client implementing the basic profile specification.
For spellchecking old symposium
Why WAMP?
WAMP is the Web Application Message Protocol supported by Tavendo. It's an open standard WebSocket subprotocol that provides two application messaging patterns in one unified protocol: Remote Procedure Calls + Publish & Subscribe.
Why Spell?
- Flexible interface: one line blocking calls or raw message handling -- use whichever tool works best.
- Robust peers: peer processes are supervised, and will restart or retry when appropriate.
- Easy to extend: add new roles, transports, or serializers without changing the core library:
Spell uses GitHub issues and pull requests for development.
Spell has a mailing list at [email protected]; general questions or ideas are welcome there. See librelist for how to sign up.
To use Spell from within your Elixir library add, add it to your mix.exs
deps,
along with the required transport and serialization libraries:
defp deps do
[
...
# Required:
{:spell, "~> 0.1"},
# Required if using the websocket transport:
{:websocket_client, github: "jeremyong/websocket_client", tag: "v0.7"},
# Required if using the JSON serializer:
{:poison, "~> 1.4"},
# Required if using the msgpack serializer:
{:msgpax, "~> 0.7"}
]
end
Fetch the dependencies by running:
$ mix deps.get
You can run the examples you're about to run into, though first you'll need crossbar.io. You might install it via pip:
$ pip install crossbar
If you are going to use MessagePack you will need to install the optional crossbar package.
$ pip install crossbar[msgpack]
Start an Elixir shell:
$ iex -S mix
iex> Crossbar.start()
To stop Crossbar.io from IEx:
iex> Crossbar.stop()
You can find more detailed documentation at any time by checking
the source code documentation. Spell
provides an entry point:
iex> h Spell
You can hit C-c C-c to exit the shell. Be sure to stop Crossbar.io first.
In WAMP, messages are exchanged between peers. Peers are assigned a set of roles which define how they handle messages. A client peer (Spell!) may have any choice of client roles, and a server peer may have any choice of server roles.
There are two functional groupings of roles:
- PubSub
- Publisher
- Subscriber
- Broker [Server]
- RPC
- Caller
- Callee
- Dealer [Server]
By default a client peer is started with the above four client roles:
Spell.connect("ws:https://example.org", realm: "realm1")
See Spell.Peer
and Spell.Role
.
Spell supports WAMP Challenge Response Authentication (CRA). This is flexible, and can be used to back a variety of authentication schemes.
To setup a Spell peer with authentication:
alias Spell.Authentication.CRA
authentication = [id: "harry", schemes: [{CRA, [secret: "alohamora"]}]]
Spell.connect("ws:https://example.org", realm: "realm1",
authentication: authentication)
See Spell.Authentication
.
Once subscribed to a topic, the subscriber will receive all messages published to that topic.
# Events must be published to a topic.
topic = "com.spell.example.pubsub.topic"
# Create a peer with the subscriber role.
subscriber = Spell.connect(Crossbar.uri,
realm: Crossbar.get_realm(),
roles: [Spell.Role.Subscriber])
# `call_subscribe/2,3` synchronously subscribes to the topic.
{:ok, subscription} = Spell.call_subscribe(subscriber, topic)
# Create a peer with the publisher role.
publisher = Spell.connect(Crossbar.uri,
realm: Crossbar.get_realm(),
roles: [Spell.Role.Publisher])
# `call_publish/2,3` synchronously publishes a message to the topic.
{:ok, publication} = Spell.call_publish(publisher, topic)
# `receive_event/2,3` blocks to receive the event.
case Spell.receive_event(publisher, subscription) do
{:ok, event} -> handle_event(event)
{:error, reason} -> {:error, reason}
end
# Cleanup.
for peer <- [subscriber, publisher], do: Spell.close(peer)
See Spell.Role.Publisher
and Spell.Role.Subscriber
for more information.
By default, peers subscribe to topics using an exact matching policy. Using pattern-based subscriptions a peer can receive messages that match certain criteria.
# assuming we have a subscriber peer connected
# exact match
{:ok, subscription} = Spell.call_subscribe(subscriber, "com.spell.my_topic")
# subscriber will receive messages published in the "com.spell.my_topic" topic
# prefix match
{:ok, subscription} = Spell.call_subscribe(subscriber, "com.spell.my_topic_prefix", options: %{match: :prefix})
# subscriber will receive messages published in topics:
# "com.spell.my_topic_prefix.foo", "com.spell.my_topic_prefix.bar", ...
# wildcard match
{:ok, subscription} = Spell.call_subscribe(subscriber, "com.spell..my_topic", options: %{match: :wildcard})
# subscriber will receive messages published in topics:
# "com.spell.foo.my_topic", "com.spell.bar.my_topic", ...
RPC allows a caller to call a procedure using a remote callee.
Let's start with the caller's half:
# Calls are sent to a particular procedure.
procedure = "com.spell.example.rpc.procedure"
# Create a peer with the callee role.
caller = Spell.connect(Crossbar.uri,
realm: Crossbar.get_realm(),
roles: [Spell.Role.Callee])
# `call_register/2,3` synchronously calls the procedure with the arguments.
{:ok, registration} = Spell.call(subscriber, procedure,
arguments: ["args"],
arguments_kw: %{})
Spell.close(caller)
In addition to the synchronous Spell.call_...
type functions described so far,
Spell includes asynchronous Spell.cast_...
functions. To handle the result of
these messages you can use a Spell.receive_...
helper, or, most flexibly,
use a receive
clause.
Next is a contrived example showing the RPC caller and the callee being used from a single process. Note how asynchronous casts and receive functions are used to avoid a deadlock.
Note: I omitted the arguments
and arguments_kw
options for brevity's sake.
# Calls are sent to a particular procedure.
procedure = "com.spell.example.rpc.procedure"
# Create a peer with the callee role.
callee = Spell.connect(Crossbar.uri,
realm: Crossbar.get_realm(),
roles: [Spell.Role.Callee])
# `call_register/2,3` synchronously registers the procedure.
{:ok, registration} = Spell.call_register(callee, procedure)
# Create a peer with the caller role.
caller = Spell.connect(Crossbar.uri,
realm: Crossbar.get_realm(),
roles: [Spell.Role.Caller])
# `cast_call/2,3` asynchronously calls the procedure.
{:ok, call} = Spell.cast_call(caller, procedure)
# `receive_invocation/2,3` blocks until it receives the call invocation.
{:ok, invocation} = Spell.receive_invocation(callee, call)
# `cast_yield/2,3` asynchronously yields the result back to the caller
:ok = Spell.cast_yield(callee, invocation.id, handle_invocation(invocation))
# `receive_event/2,3` blocks until timeout to receive the result.
case Spell.receive_result(publisher, call) do
{:ok, result} -> handle_result(result)
{:error, reason} -> {:error, reason}
end
# Cleanup.
for peer <- [callee, caller], do: Spell.close(peer)
See Spell.Role.Caller
and Spell.Role.Callee
for more information.
In Spell, Roles are middleware for handling messages. Technically they're most
similar to GenEvent handlers: callbacks which are hooked into a manager. In this
case, the manager is a Spell.Peer
process.
It's easy to get started:
defmodule Broker do
use Spell.Role
def get_features(_options), do: {:broker, %{}}
def handle_message(%Message{type: :publish} = message, peer, state) do
publish(message, peer, state)
end
... shamelessly skipping the real work.
end
Spell.Peer.connect(Crossbar.uri, realm: Crossbar.get_realm(), roles: [Broker])
See Spell.Role
for descriptions of the Role callbacks.
Look in examples/
for the scripts.
There are shortcuts to run the examples -- run the following from Spell's root:
$ mix spell.example.pubsub
To run Spell's integration tests, you must have crossbar installed.
To run the tests:
# run unit and integration tests with default configuration
$ mix test
# run only unit tests
$ mix test.unit
# run integration tests using a specific serializer
$ SERIALIZER=json mix test.integration
$ SERIALIZER=msgpack mix test.integration
# run integration tests with all possible serializers
$ SERIALIZER=all mix test.integration
$ mix test.integration
# run unit and integration tests with all possible configurations
$ mix test.all
The Crossbar.io test server can be configured to listen on a different port by running:
$ CROSSBAR_PORT=8000 mix ...
To generate HTML from the source code documentation you can run:
$ mix docs