[#8] plugin mechanism for custom handlers
This commit is contained in:
parent
e2ff4a48c7
commit
a1fde7a574
|
@ -1,30 +1,3 @@
|
||||||
# This file is responsible for configuring your application
|
|
||||||
# and its dependencies with the aid of the Mix.Config module.
|
|
||||||
use Mix.Config
|
use Mix.Config
|
||||||
|
|
||||||
# This configuration is loaded before any dependency and is restricted
|
import_config "#{Mix.env}.exs"
|
||||||
# to this project. If another project depends on this project, this
|
|
||||||
# file won't be loaded nor affect the parent project. For this reason,
|
|
||||||
# if you want to provide default values for your application for
|
|
||||||
# 3rd-party users, it should be done in your "mix.exs" file.
|
|
||||||
|
|
||||||
# You can configure your application as:
|
|
||||||
#
|
|
||||||
# config :poa_backend, key: :value
|
|
||||||
#
|
|
||||||
# and access this configuration in your application as:
|
|
||||||
#
|
|
||||||
# Application.get_env(:poa_backend, :key)
|
|
||||||
#
|
|
||||||
# You can also configure a 3rd-party app:
|
|
||||||
#
|
|
||||||
# config :logger, level: :info
|
|
||||||
#
|
|
||||||
|
|
||||||
# It is also possible to import configuration files, relative to this
|
|
||||||
# directory. For example, you can emulate configuration per environment
|
|
||||||
# by uncommenting the line below and defining dev.exs, test.exs and such.
|
|
||||||
# Configuration from the imported file will override the ones defined
|
|
||||||
# here (which is why it is important to import them last).
|
|
||||||
#
|
|
||||||
# import_config "#{Mix.env}.exs"
|
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
use Mix.Config
|
||||||
|
|
||||||
|
# configuration for custom handlers. The format is {custom_handler_name, module, args}
|
||||||
|
config :poa_backend,
|
||||||
|
:custom_handlers,
|
||||||
|
[
|
||||||
|
]
|
|
@ -0,0 +1,7 @@
|
||||||
|
use Mix.Config
|
||||||
|
|
||||||
|
# configuration for custom handlers. The format is {custom_handler_name, module, args}
|
||||||
|
config :poa_backend,
|
||||||
|
:custom_handlers,
|
||||||
|
[
|
||||||
|
]
|
|
@ -0,0 +1,7 @@
|
||||||
|
use Mix.Config
|
||||||
|
|
||||||
|
# configuration for custom handlers. The format is {custom_handler_name, module, args}
|
||||||
|
config :poa_backend,
|
||||||
|
:custom_handlers,
|
||||||
|
[
|
||||||
|
]
|
|
@ -1,14 +1,13 @@
|
||||||
defmodule POABackend.Application do
|
defmodule POABackend.Application do
|
||||||
# See https://hexdocs.pm/elixir/Application.html
|
|
||||||
# for more information on OTP Applications
|
|
||||||
@moduledoc false
|
@moduledoc false
|
||||||
|
|
||||||
use Application
|
use Application
|
||||||
|
|
||||||
def start(_type, _args) do
|
def start(_type, _args) do
|
||||||
# List all child processes to be supervised
|
import Supervisor.Spec
|
||||||
children = [
|
|
||||||
|
|
||||||
|
children = [
|
||||||
|
supervisor(POABackend.CustomHandler.Supervisor, [])
|
||||||
]
|
]
|
||||||
|
|
||||||
opts = [strategy: :one_for_one, name: POABackend.Supervisor]
|
opts = [strategy: :one_for_one, name: POABackend.Supervisor]
|
||||||
|
|
|
@ -0,0 +1,67 @@
|
||||||
|
defmodule POABackend.CustomHandler do
|
||||||
|
alias POABackend.Protocol.Message
|
||||||
|
@moduledoc """
|
||||||
|
A Custom Handler is responsible of handling data sent from Agents (i.e. REST over HTTP, WebSockets...) "speaking" the POA Protocol.
|
||||||
|
|
||||||
|
The main responsability is getting calls from Agents, transform the data into a [POABackend.Protocol.Message](POABackend.Protocol.Message.html#content)
|
||||||
|
Struct and sending it to the receivers.
|
||||||
|
|
||||||
|
### Writing your custom Handler
|
||||||
|
|
||||||
|
You must to _use_ the POABackend.CustomHandler module. That will requires you to implement the function `child_spec/1` which will be
|
||||||
|
called from the `POABackend.CustomHandler.Supervisor` and it must returns the child spec for the process you are going to spawn.
|
||||||
|
|
||||||
|
defmodule MyHandler do
|
||||||
|
use POABackend.CustomHandler
|
||||||
|
|
||||||
|
def child_spec(options) do
|
||||||
|
Plug.Adapters.Cowboy.child_spec(scheme: options[:scheme], plug: POABackend.CustomHandler.Rest.Router, options: [port: options[:port]])
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
In this example we are initializing our CustomHandler for REST requests using the Cowboy Plug and defining the endpoints in the `POABackend.CustomHandler.Rest.Router`
|
||||||
|
module.
|
||||||
|
|
||||||
|
### Configuring the handlers in the config file
|
||||||
|
|
||||||
|
So far we have created a Custom Handler but we didn't tell `poa_backend` to start it. In order to do it we have to define the new Handler in
|
||||||
|
the config file.
|
||||||
|
|
||||||
|
config :poa_backend,
|
||||||
|
:custom_handlers,
|
||||||
|
[
|
||||||
|
{:rest_custom_handler, POABackend.CustomHandler.Rest, [port: 4002]}
|
||||||
|
]
|
||||||
|
|
||||||
|
Inside the `:custom_handlers` list we define the handlers we want to start. Each Handler is defined in a triple tuple where the first argument
|
||||||
|
is the id for that handler, the second one is the Elixir module which implements the CustomHandler behaviour and the third one is a list for arguments
|
||||||
|
which will be passed to the `child_spec/1` function as a parameter
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
This function will be called from the `POABackend.CustomHandler.Supervisor` in order
|
||||||
|
to get the child specification for start the custom handler process.
|
||||||
|
"""
|
||||||
|
@callback child_spec(options :: list()) :: :supervisor.child_spec()
|
||||||
|
|
||||||
|
defmacro __using__(_opts) do
|
||||||
|
quote do
|
||||||
|
@behaviour POABackend.CustomHandler
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
This function dispatches the given Message to the appropiate receivers based on the Data Type.
|
||||||
|
|
||||||
|
The mapping between Data Types and Receivers is done in the config file.
|
||||||
|
"""
|
||||||
|
@spec send_to_receivers(Message.t) :: :ok
|
||||||
|
def send_to_receivers(%Message{} = _message) do
|
||||||
|
require Logger
|
||||||
|
# this will be implemented when working with the receivers
|
||||||
|
Logger.warn("To be implemented")
|
||||||
|
:ok
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,24 @@
|
||||||
|
defmodule POABackend.CustomHandler.Supervisor do
|
||||||
|
@moduledoc false
|
||||||
|
|
||||||
|
def start_link do
|
||||||
|
Supervisor.start_link(__MODULE__, :ok, name: __MODULE__)
|
||||||
|
end
|
||||||
|
|
||||||
|
def init(:ok) do
|
||||||
|
require Logger
|
||||||
|
# create the children from the config file
|
||||||
|
custom_handlers = Application.get_env(:poa_backend, :custom_handlers)
|
||||||
|
|
||||||
|
children = for {name, module, options} <- custom_handlers do
|
||||||
|
spec = apply(module, :child_spec, [options])
|
||||||
|
|
||||||
|
Logger.info("Custom handler #{name} started.")
|
||||||
|
|
||||||
|
spec
|
||||||
|
end
|
||||||
|
|
||||||
|
opts = [strategy: :one_for_one]
|
||||||
|
Supervisor.init(children, opts)
|
||||||
|
end
|
||||||
|
end
|
|
@ -35,13 +35,13 @@ defmodule POABackend.Protocol.Message do
|
||||||
That keeps all the message data and metadata
|
That keeps all the message data and metadata
|
||||||
"""
|
"""
|
||||||
@type t :: %__MODULE__{
|
@type t :: %__MODULE__{
|
||||||
agent_id: String.t(),
|
agent_id: String.t() | nil,
|
||||||
receivers: [atom()],
|
receivers: [atom()],
|
||||||
data_type: POABackend.CustomProtocol.DataType.t(),
|
data_type: POABackend.CustomProtocol.DataType.t() | nil,
|
||||||
message_type: POABackend.CustomProtocol.MessageType.t(),
|
message_type: POABackend.CustomProtocol.MessageType.t() | nil,
|
||||||
assigns: %{atom() => any()},
|
assigns: %{atom() => any()},
|
||||||
peer: {:inet.ip_address(), :inet.port_number()},
|
peer: {:inet.ip_address(), :inet.port_number()} | nil,
|
||||||
data: Map.t()
|
data: Map.t() | nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
|
|
8
mix.exs
8
mix.exs
|
@ -19,7 +19,7 @@ defmodule POABackend.MixProject do
|
||||||
# Run "mix help compile.app" to learn about applications.
|
# Run "mix help compile.app" to learn about applications.
|
||||||
def application do
|
def application do
|
||||||
[
|
[
|
||||||
extra_applications: [:logger, :cowboy, :plug],
|
extra_applications: [:logger],
|
||||||
mod: {POABackend.Application, []}
|
mod: {POABackend.Application, []}
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
|
@ -27,9 +27,6 @@ defmodule POABackend.MixProject do
|
||||||
# Run "mix help deps" to learn about dependencies.
|
# Run "mix help deps" to learn about dependencies.
|
||||||
defp deps do
|
defp deps do
|
||||||
[
|
[
|
||||||
{:cowboy, "~> 1.0.0"},
|
|
||||||
{:plug, "~> 1.0"},
|
|
||||||
|
|
||||||
# Tests
|
# Tests
|
||||||
{:credo, "~> 0.9", only: [:dev, :test], runtime: false},
|
{:credo, "~> 0.9", only: [:dev, :test], runtime: false},
|
||||||
{:dialyxir, "~> 0.5", only: [:dev], runtime: false},
|
{:dialyxir, "~> 0.5", only: [:dev], runtime: false},
|
||||||
|
@ -51,6 +48,9 @@ defmodule POABackend.MixProject do
|
||||||
POABackend.Protocol.Message,
|
POABackend.Protocol.Message,
|
||||||
POABackend.Protocol.MessageType,
|
POABackend.Protocol.MessageType,
|
||||||
POABackend.Protocol.DataType
|
POABackend.Protocol.DataType
|
||||||
|
],
|
||||||
|
"Custom Handler": [
|
||||||
|
POABackend.CustomHandler
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
|
|
4
mix.lock
4
mix.lock
|
@ -1,8 +1,10 @@
|
||||||
%{
|
%{
|
||||||
"bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [], [], "hexpm"},
|
"bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [], [], "hexpm"},
|
||||||
"certifi": {:hex, :certifi, "2.3.1", "d0f424232390bf47d82da8478022301c561cf6445b5b5fb6a84d49a9e76d2639", [], [{:parse_trans, "3.2.0", [hex: :parse_trans, repo: "hexpm", optional: false]}], "hexpm"},
|
"certifi": {:hex, :certifi, "2.3.1", "d0f424232390bf47d82da8478022301c561cf6445b5b5fb6a84d49a9e76d2639", [], [{:parse_trans, "3.2.0", [hex: :parse_trans, repo: "hexpm", optional: false]}], "hexpm"},
|
||||||
"credo": {:hex, :credo, "0.9.2", "841d316612f568beb22ba310d816353dddf31c2d94aa488ae5a27bb53760d0bf", [], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:poison, ">= 0.0.0", [hex: :poison, repo: "hexpm", optional: false]}], "hexpm"},
|
"credo": {:hex, :credo, "0.9.3", "76fa3e9e497ab282e0cf64b98a624aa11da702854c52c82db1bf24e54ab7c97a", [], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:poison, ">= 0.0.0", [hex: :poison, repo: "hexpm", optional: false]}], "hexpm"},
|
||||||
"dialyxir": {:hex, :dialyxir, "0.5.1", "b331b091720fd93e878137add264bac4f644e1ddae07a70bf7062c7862c4b952", [], [], "hexpm"},
|
"dialyxir": {:hex, :dialyxir, "0.5.1", "b331b091720fd93e878137add264bac4f644e1ddae07a70bf7062c7862c4b952", [], [], "hexpm"},
|
||||||
|
"earmark": {:hex, :earmark, "1.2.5", "4d21980d5d2862a2e13ec3c49ad9ad783ffc7ca5769cf6ff891a4553fbaae761", [], [], "hexpm"},
|
||||||
|
"ex_doc": {:hex, :ex_doc, "0.18.3", "f4b0e4a2ec6f333dccf761838a4b253d75e11f714b85ae271c9ae361367897b7", [], [{:earmark, "~> 1.1", [hex: :earmark, repo: "hexpm", optional: false]}], "hexpm"},
|
||||||
"excoveralls": {:hex, :excoveralls, "0.8.2", "b941a08a1842d7aa629e0bbc969186a4cefdd035bad9fe15d43aaaaaeb8fae36", [], [{:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: false]}, {:hackney, ">= 0.12.0", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"},
|
"excoveralls": {:hex, :excoveralls, "0.8.2", "b941a08a1842d7aa629e0bbc969186a4cefdd035bad9fe15d43aaaaaeb8fae36", [], [{:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: false]}, {:hackney, ">= 0.12.0", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"},
|
||||||
"exjsx": {:hex, :exjsx, "4.0.0", "60548841e0212df401e38e63c0078ec57b33e7ea49b032c796ccad8cde794b5c", [], [{:jsx, "~> 2.8.0", [hex: :jsx, repo: "hexpm", optional: false]}], "hexpm"},
|
"exjsx": {:hex, :exjsx, "4.0.0", "60548841e0212df401e38e63c0078ec57b33e7ea49b032c796ccad8cde794b5c", [], [{:jsx, "~> 2.8.0", [hex: :jsx, repo: "hexpm", optional: false]}], "hexpm"},
|
||||||
"hackney": {:hex, :hackney, "1.12.1", "8bf2d0e11e722e533903fe126e14d6e7e94d9b7983ced595b75f532e04b7fdc7", [], [{:certifi, "2.3.1", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "5.1.1", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "1.0.2", [hex: :mimerl, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.1", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm"},
|
"hackney": {:hex, :hackney, "1.12.1", "8bf2d0e11e722e533903fe126e14d6e7e94d9b7983ced595b75f532e04b7fdc7", [], [{:certifi, "2.3.1", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "5.1.1", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "1.0.2", [hex: :mimerl, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.1", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm"},
|
||||||
|
|
|
@ -0,0 +1,53 @@
|
||||||
|
defmodule CustomHandlerTest do
|
||||||
|
use ExUnit.Case
|
||||||
|
|
||||||
|
defmodule MyCustomHandler do
|
||||||
|
use POABackend.CustomHandler
|
||||||
|
use GenServer
|
||||||
|
|
||||||
|
# CustomHandler Callbacks
|
||||||
|
|
||||||
|
def child_spec(options) do
|
||||||
|
import Supervisor.Spec
|
||||||
|
|
||||||
|
worker(__MODULE__, [options[:caller_pid]])
|
||||||
|
end
|
||||||
|
|
||||||
|
# GenServer Callbacks
|
||||||
|
|
||||||
|
def start_link(args) do
|
||||||
|
GenServer.start_link(__MODULE__, args, [name: __MODULE__])
|
||||||
|
end
|
||||||
|
|
||||||
|
def init(caller_pid) do
|
||||||
|
{:ok, caller_pid}
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle_info(:hello, caller_pid) do
|
||||||
|
|
||||||
|
# simulate we send something to the receivers
|
||||||
|
|
||||||
|
POABackend.CustomHandler.send_to_receivers(POABackend.Protocol.Message.new())
|
||||||
|
|
||||||
|
send(caller_pid, :world)
|
||||||
|
{:noreply, caller_pid}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
test "initialize a custom handler in the supervisor" do
|
||||||
|
|
||||||
|
Application.stop(:poa_backend)
|
||||||
|
Application.put_env(:poa_backend, :custom_handlers, [{:my_ch, MyCustomHandler, [caller_pid: self()]}])
|
||||||
|
Application.ensure_all_started(:poa_backend)
|
||||||
|
|
||||||
|
# sending a message and wait for the reply
|
||||||
|
|
||||||
|
CustomHandlerTest.MyCustomHandler
|
||||||
|
|> Process.whereis()
|
||||||
|
|> send(:hello)
|
||||||
|
|
||||||
|
assert_receive :world, 20_000
|
||||||
|
|
||||||
|
Application.stop(:poa_backend)
|
||||||
|
end
|
||||||
|
end
|
Loading…
Reference in New Issue