Packages
gen_amqp
3.2.1
7.0.0
6.0.0
5.0.6
5.0.5
5.0.4
5.0.3
5.0.2
5.0.1
5.0.0
4.5.1
4.5.0
4.4.1
4.4.0
4.3.2
4.3.1
4.3.0
4.2.1
4.2.0
4.1.0
4.0.1
4.0.0
3.6.3
3.6.2
3.6.1
3.6.0
3.5.1
3.5.0
3.4.0
3.3.1
3.3.0
3.2.1
3.2.0
3.1.0
3.0.0
2.8.0
2.7.0
2.6.0
2.5.0
2.4.0
2.3.0
2.2.0
2.1.0
2.0.4
2.0.2
2.0.0
1.0.0
0.7.1
0.7.0
0.6.6
0.6.5
0.6.0
GenAMQP is a set of utilities to make microservices using the worker pattern
Current section
Files
Jump to
Current section
Files
lib/gen_amqp/conn.ex
defmodule GenAMQP.Conn do
@moduledoc """
Handles the internals for AMQP connections
"""
use GenServer
require Logger
# Public API
@doc """
Starts the connection
"""
# @spec start_link(binary(), GenServer.name()) :: GenServer.on_start()
def start_link(conn_url, name) do
Logger.info("Crearing conn with name: #{name} and url #{conn_url}")
GenServer.start_link(__MODULE__, [name, conn_url], name: name)
end
@doc """
Creates a new channel
"""
@spec create_chan(GenServer.name(), atom) :: any
def create_chan(name, chan_name) when is_atom(chan_name) do
GenServer.call(name, {:create_chan, chan_name})
end
@doc """
Closes a channel
"""
@spec close_chan(GenServer.name(), atom) :: any
def close_chan(name, chan_name) when is_atom(chan_name) do
GenServer.call(name, {:close_chan, chan_name})
end
@doc """
Publish a message in an asynchronous way
"""
@spec publish(GenServer.name(), String.t(), String.t(), atom) :: any
def publish(name, exchange, payload, chan_name, opts \\ []) do
GenServer.call(name, {:publish, exchange, payload, chan_name, opts})
end
@doc """
Works like a request respone
"""
@spec request(GenServer.name(), String.t(), String.t(), atom) :: any
def request(name, exchange, payload, chan_name, opts \\ []) do
GenServer.call(name, {:request, exchange, payload, chan_name, opts})
end
@doc """
Response a given request
"""
@spec response(GenServer.name(), map, String.t(), atom) :: any
def response(name, meta, payload, chan_name) do
GenServer.call(name, {:response, meta, payload, chan_name})
end
@doc """
Subscribes to an specific queue
"""
@spec subscribe(GenServer.name(), String.t(), atom) :: :ok
def subscribe(name, exchange, chan_name) do
GenServer.call(name, {:subscribe, exchange, chan_name})
end
@spec unsubscribe(GenServer.name(), String.t(), atom) :: any
def unsubscribe(name, exchange, chan_name) do
GenServer.call(name, {:unsubscribe, exchange, chan_name})
end
# Private API
def init([name, amqp_url]) do
Logger.info("Starting connection")
Process.flag(:trap_exit, true)
{:ok, conn} = AMQP.Connection.open(amqp_url)
{:ok, chan} = AMQP.Channel.open(conn)
if name != nil do
case :ets.lookup(:conns, name) do
[{_, connected}] -> reconnect(connected)
_ -> :ok
end
end
{:ok,
%{conn: conn, conn_name: name, chans: %{default: chan}, subscriptions: %{}, queues: %{}}}
end
defp reconnect(connected) do
Logger.info("restarting #{inspect(connected)}")
for gen_name <- connected, gen_name != :default do
GenServer.cast(gen_name, :reconnect)
end
end
def handle_call({:create_chan, name}, _from, %{conn: conn} = state) do
{:ok, chan} = AMQP.Channel.open(conn)
new_state = update_in(state.chans, &Map.put(&1, name, chan))
{:reply, :ok, new_state}
end
def handle_call({:close_chan, name}, _from, state) do
:ok = AMQP.Channel.close(state.chans[name])
new_state = update_in(state.chans, &Map.delete(&1, name))
{:reply, :ok, new_state}
end
def handle_call({:publish, exchange, payload, chan_name, opts}, _from, %{chans: chans} = state) do
chan = chans[chan_name]
AMQP.Basic.publish(chan, "", exchange, payload, opts)
{:reply, :ok, state}
end
def handle_call({:response, meta, payload, chan_name}, _from, %{chans: chans} = state) do
chan = chans[chan_name]
AMQP.Basic.publish(
chan,
"",
meta.reply_to,
payload,
correlation_id: meta.correlation_id
)
{:reply, :ok, state}
end
def handle_call(
{:request, exchange, payload, chan_name, opts},
{pid_from, _},
%{chans: chans} = state
) do
chan = chans[chan_name]
correlation_id =
:erlang.unique_integer()
|> :erlang.integer_to_binary()
|> Base.encode64()
{:ok, %{queue: queue_name}} =
AMQP.Queue.declare(chan, "", exclusive: true, auto_delete: true, durable: false)
AMQP.Basic.consume(chan, queue_name, pid_from, no_ack: true)
AMQP.Basic.publish(
chan,
"",
exchange,
payload,
Keyword.merge(
[reply_to: queue_name, correlation_id: correlation_id],
opts
)
)
{:reply, {:ok, correlation_id}, state}
end
def handle_call(
{:subscribe, exchange, chan_name},
{pid_from, _},
%{chans: chans, subscriptions: subscriptions, queues: queues} = state
) do
chan = chans[chan_name]
new_queues = Map.put_new(queues, pid_from, consume(pid_from, chan, exchange))
new_subscriptions =
subscriptions
|> Map.put_new(exchange, [])
|> Map.update!(exchange, fn subscribers ->
case Enum.find_value(subscribers, nil, &(&1 == pid_from)) do
nil ->
subscribers
_ ->
[pid_from | subscribers]
end
end)
new_state = %{state | subscriptions: new_subscriptions, queues: new_queues}
{:reply, :ok, new_state}
end
def handle_call(
{:unsubscribe, exchange, chan_name},
{pid_from, _},
%{chans: chans, subscriptions: subscriptions, queues: queues} = state
) do
chan = chans[chan_name]
new_queues =
case Map.fetch(queues, pid_from) do
{:ok, queue_name} ->
AMQP.Queue.delete(chan, queue_name)
Map.delete(queues, pid_from)
_ ->
queues
end
new_subscriptions =
subscriptions
|> Map.put_new(exchange, [])
|> Map.update!(exchange, fn subscribers ->
Enum.reject(subscribers, &(&1 == pid_from))
end)
new_state = %{state | subscriptions: new_subscriptions, queues: new_queues}
{:reply, :ok, new_state}
end
def handle_info({:EXIT, _pid, reason}, state) do
{:stop, reason, state}
end
def terminate(reason, %{conn_name: conn_name, chans: chans} = state) do
Logger.info("Closing AMQP Connection reason: #{inspect(reason)}")
if conn_name != nil do
if reason in [:normal, :shutdown] or match?({:shutdown, _}, reason) do
:ets.delete(:conns, conn_name)
else
:ets.insert(:conns, {conn_name, Map.keys(chans)})
end
end
AMQP.Connection.close(state.conn)
:ok
end
@spec consume(pid, struct, String.t()) :: String.t()
defp consume(pid, chan, exchange) do
{:ok, %{queue: queue_name}} = AMQP.Queue.declare(chan, exchange, durable: false)
{:ok, _} = AMQP.Basic.consume(chan, queue_name, pid, no_ack: true)
queue_name
end
end