This repo is a basic example of writing a VerneMQ plugin in Elixir. It's based on info provided in this issue.
If you want try and build the plugin yourself you can follow the guide below. The code in this repo is identical to what you'll have when you're done.
The guide comes with a Vagrantfile that sets up an environment with:
- Ubuntu 16.04
- VerneMQ 1.2.0 (installed with binary from VerneMQ's download page)
- Erlang 19.3.6
- Elixir 1.4.5
- Mosquitto clients (for testing)
However, the guide should be applicable to any OS that VerneMQ runs on, and work with any Erlang/Elixir version as long as they are compatible with the Erlang version VerneMQ has been compiled against.
If you want to use the Vagrant environment, you'll need to install vagrant if you don't have it already. Then clone this project, create the environment and SSH into it with:
git clone https://github.com/trarbr/vernemq_elixir_plugin.git
cd vernemq_elixir_plugin
vagrant up
vagrant ssh
First create a new project by running mix new vernemq_elixir_plugin --sup.
We'll use distillery to compile our project into an Erlang release. This makes it easier to use as a VerneMQ plugin as it will bundle Elixir with your code.
Add distillery as a dependency by adding it to your mix.exs file:
defp deps do
[
{:distillery, "~> 1.5", runtime: false},
]
endThen run mix deps.get and generate the release configuration file with mix release.init.
VerneMQ is going to load your application into its own instance of the Erlang VM. For this reason, we don't need to include ERTS. Open rel/config.exs and specify:
environment :prod do
set include_erts: false
...
endThe first thing VerneMQ will do when starting your plugin is to call the start/2 function in your application module. Let's leave a print statement so we can check it's been started. Open lib/vernemq_elixir_plugin/application.ex and add this line at the top of the start/2 function:
def start(_type, _args) do
IO.puts("*** VernemqElixirPlugin starting")
...
endWe'll implement a handler for all VerneMQ hooks - see the docs for a description of each hook. For this basic plugin we'll simply print a message to stdout whenever a hook is called. Open lib/vernemq_elixir_plugin.exand replace the text there with the following:
defmodule VernemqElixirPlugin do
# Session lifecycle
def auth_on_register(_peer, {_mountpoint, clientid}, _username, _password, _clean_session?) do
IO.puts("*** auth_on_register #{clientid}")
{:ok, []}
end
def on_register(_peer, {_mountpoint, clientid}, username) do
IO.puts("*** on_register #{clientid} / #{username}")
:ok
end
def on_client_wakeup({_mountpoint, clientid}) do
IO.puts("*** on_client_wakeup #{clientid}")
:ok
end
def on_client_offline({_mountpoint, clientid}) do
IO.puts("*** on_client_offline #{clientid}")
:ok
end
def on_client_gone({_mountpoint, clientid}) do
IO.puts("*** on_client_gone #{clientid}")
:ok
end
# Subscribe flow
def auth_on_subscribe(_username, {_mountpoint, clientid}, topics) do
IO.puts("*** auth_on_subscribe #{clientid}")
{:ok, topics}
end
def on_subscribe(_username, {_mountpoint, clientid}, _topics) do
IO.puts("*** on_subscribe #{clientid}")
:ok
end
def on_unsubscribe(_username, {_mountpoint, clientid}, _topics) do
IO.puts("*** on_unsubscribe #{clientid}")
:ok
end
# Publish flow
def auth_on_publish(_username, {_mountpoint, clientid}, _qos, topic, payload, _flag) do
IO.puts("*** auth_on_publish #{clientid} / #{topic} / #{payload}")
{:ok, payload}
end
def on_publish(_username, {_mountpoint, clientid}, _qos, topic, payload, _retain?) do
IO.puts("*** on_publish #{clientid} / #{topic} / #{payload}")
:ok
end
def on_deliver(_username, {_mountpoint, clientid}, topic, payload) do
IO.puts("*** on_deliver #{clientid} / #{topic} / #{payload}")
:ok
end
def on_offline_message({_mountpoint, clientid}, _qos, topic, payload, _retain?) do
IO.puts("*** on_offline_message #{clientid} / #{topic} / #{payload}")
:ok
end
endYou'll also need to tell VerneMQ which hooks are implemented in the plugin. This is done by adding the following in the mix.exs file:
def application do
[
env: [vmq_plugin_hooks()],
...
]
end
defp vmq_plugin_hooks do
hooks = [
{VernemqElixirPlugin, :auth_on_register, 5, []},
{VernemqElixirPlugin, :on_register, 3, []},
{VernemqElixirPlugin, :on_client_wakeup, 1, []},
{VernemqElixirPlugin, :on_client_offline, 1, []},
{VernemqElixirPlugin, :on_client_gone, 1, []},
{VernemqElixirPlugin, :auth_on_subscribe, 3, []},
{VernemqElixirPlugin, :on_subscribe, 3, []},
{VernemqElixirPlugin, :on_unsubscribe, 3, []},
{VernemqElixirPlugin, :auth_on_publish, 6, []},
{VernemqElixirPlugin, :on_publish, 6, []},
{VernemqElixirPlugin, :on_deliver, 4, []},
{VernemqElixirPlugin, :on_offline_message, 5, []}
]
{:vmq_plugin_hooks, hooks}
endNow all we need is to bundle the plugin. This is done with MIX_ENV=prod mix release --env prod.
The console output from the release command above should tell you the path to directory containing your release. In my case it's /home/ubuntu/vernemq_elixir_plugin/_build/prod/rel/vernemq_elixir_plugin. To enable the plugin you'll have to add this path to the VerneMQ configuration file. Add the following to /etc/vernemq/vernemq.conf:
plugins.vernemq_elixir_plugin = on
plugins.vernemq_elixir_plugin.path = /home/ubuntu/vernemq_elixir_plugin/_build/prod/rel/vernemq_elixir_plugin
Then start VerneMQ:
sudo systemctl enable vernemq
sudo systemctl start vernemqThe output from the calls to IO.puts will be visible in the file /var/log/vernemq/erlang.log.1. If you open it, you should see a line with the text *** VernemqElixirPlugin starting.
Next, we can check that the other hooks are executed by starting a subscriber and publisher. Start a subscriber in one terminal:
mosquitto_sub -t '#'Use another terminal to publish messages:
mosquitto_pub -t 'hello' -m 'vernemq plugin in elixir!'If you check /var/log/vernemq/erlang.log.1 you should see that all the messages for the hooks have been printed!