Hello everyone,
I’m a new in elexir and functional language. I’m trying to implement Websocket interraction with server.
On first layer I implement module based on GenServer
with functionality like WebSockex
and use ninenines/gun as Websocket client. This module name egun
, below are the code snippets:
defmodule Egun do
use GenServer
@callback handle_connect(state :: term) :: {:ok, new_state :: term}
@callback handle_message(msg :: term, state :: term) :: {:ok, new_state} | ...
@callback handle_send(msg :: term, state :: term) :: {:ok, new_state} | ...
...
defmacro __using__(_opts) do
quote location: :keep do
@behaviour Egun
@doc false
def handle_connect(state) do
{:ok, state}
end
@doc false
def handle_message(message, _state) do
raise "No handle_message/2 clause in #{__MODULE__} provided for #{inspect(message)}"
end
@doc false
def handle_send(message, state) do
{:reply, {:text, message}, state}
end
defoverridable handle_connect: 1,
handle_message: 2,
handle_send: 2
end
def start_link(conn = %Conn{}) do
GenServer.start_link(__MODULE__, %__MODULE__{
conn: conn,
})
end
def init(state) do
...
send(self(), :ws_connected)
{:ok, state}
end
def send_message(client, message) do
GenServer.call(client, {:ws_syncsend, message})
end
def handle_call({:ws_syncsend, message}, sender, state) do
message = compose_msg(message, state.conn)
...
{:reply, message, state}
end
def handle_info(:ws_connected, state) do
common_handle(:handle_connect, state)
end
def handle_info(
{:gun_ws, _gun_pid, _ws_stream, {:text, message}}, state
) do
case Jason.decode(message, keys: :atoms) do
{:ok, msg} ->
common_handle({:handle_message, msg}, state)
error ->
{:stop, error, state}
end
end
defp common_handle({function, msg}, state) when is_atom(function) do
result = apply(state.module, function, [msg, state])
common_handle_ret(result, {function, msg}, state)
end
defp common_handle(function, state) when is_atom(function) do
result = apply(state.module, function, [state])
common_handle_ret(result, {function, nil}, state)
end
defp common_handle_ret(result, {function, msg}, state) do
case result do
{:ok, new_state} ->
{:noreply, new_state}
{:reply, frame, new_state} ->
:gun.ws_send(new_state.gun_pid, frame_encode(frame))
{:noreply, new_state}
....
end
end
This implementation provides messaging through an open Websocket. Next I need extend functionality egun
. And create module session
which adds business logic methods:
defmodule Session do
@moduledoc nil
use Egun
def create(client) when is_pid(client) do
tr = Egun.send_message(client, %{
session: "create",
transaction: "yes"
})
GenServer.call(client, {:push_transaction, tr, client})
end
@impl true
def handle_call({:push_transaction, transaction, callback_pid}, _from, state) do
trs = Map.put(state.transactions, transaction, callback_pid)
{:noreply, %{state | transactions: trs}}
end
@impl Egun
def handle_message(message, state) do
IO.puts("Handle message: #{inspect(message)}")
IO.puts("State: #{inspect(state)}")
{:ok, state}
end
end
After compile I get warning message:
warning: got "@impl Egun" for function handle_call/3 but this behaviour does not specify such callback. The known callbacks are:
* Egun.handle_connect/1 (function)
* Egun.handle_message/2 (function)
* Egun.handle_send/2 (function)
How can I modify my modules so that the Session
module can implement the behavior of the GenServer
and Egan
without using an additional GenServer
. On object oriented programming language Session
can get access to all methods from GenServer
and Egan
?