Current section
Files
Jump to
Current section
Files
lib/testing/client.ex
defmodule Spotter.Testing.AmqpBlockingClient do
@moduledoc """
A blocking AMPQ client for testing purposes and simple RPC use cases.
"""
use GenServer
alias Spotter.AMQP.Connection.Helper
@doc """
Initializes a new blocking GenServer instance.
"""
def start_link(opts, name \\ __MODULE__) do
GenServer.start_link(__MODULE__, opts, name: name)
end
@doc """
Initializes a new connection and a channel.
"""
def init(opts) do
{:ok, connection} = Helper.open_connection(opts)
{:ok, channel} = Helper.open_channel(connection)
{:ok, %{
connection: connection,
channel: channel,
channel_opts: [
queue: Keyword.get(opts, :queue, []),
exchange: Keyword.get(opts, :exchange, []),
qos: Keyword.get(opts, :qos, [])
]
}}
end
defp deinit(connection, channel) do
Helper.close_channel(channel)
AMQP.Connection.close(connection)
end
defp configure(channel, channel_opts) do
channel = configure_qos(channel, channel_opts[:qos])
{channel, queue_opts} = configure_queue(channel, channel_opts[:queue])
channel_opts = Keyword.merge(channel_opts, [queue: queue_opts])
channel = configure_exchange(channel, channel_opts[:queue], channel_opts[:exchange])
channel
end
defp configure_qos(channel, nil) do
channel
end
defp configure_qos(channel, qos_opts) do
Helper.set_channel_qos(channel, qos_opts)
channel
end
defp configure_queue(channel, nil) do
channel
end
defp configure_queue(channel, queue_opts) do
{:ok, queue} = AMQP.Queue.declare(channel, env(queue_opts[:name]), env(queue_opts))
queue_opts =
if queue_opts[:name] == "" and queue_opts[:routing_key] == "" do
Keyword.merge(queue_opts, [name: queue[:queue], routing_key: queue[:queue]])
else
queue_opts
end
{channel, queue_opts}
end
defp configure_exchange(channel, queue_opts, exchange_opts) when is_nil(queue_opts) or is_nil(exchange_opts) do
channel
end
defp configure_exchange(channel, queue_opts, exchange_opts) do
Helper.declare_exchange(channel, exchange_opts[:name], exchange_opts[:type], exchange_opts)
Helper.bind_queue(channel, queue_opts[:name], exchange_opts[:name], routing_key: queue_opts[:routing_key])
channel
end
defp env(var) do
Confex.Resolver.resolve!(var)
end
# Public API
@doc """
Stop the client and close the existing connection.
"""
def stop(pid) do
GenServer.stop(pid)
end
@doc """
Sends a new message without waiting for a response.
"""
def send(pid, data, opts, call_timeout \\ 5000) do
GenServer.call(pid, {:send, data, opts}, call_timeout)
end
@doc """
Sends a new message and wait for result.
"""
def send_and_wait(pid, data, opts, timeout \\ 1000, attempts \\ 5, call_timeout \\ 5000) do
GenServer.call(pid, {:send_and_wait, data, opts, timeout, attempts}, call_timeout)
end
@doc """
Returns the message from the certain queue if it exists.
"""
def consume(pid, queue, timeout \\ 1000, attempts \\ 5, call_timeout \\ 500) do
GenServer.call(pid, {:consume_response, queue, timeout, attempts}, call_timeout)
end
@doc """
Initializes QoS, a queue and an exchanges for the channel.
"""
def configure_channel(pid, channel_opts, call_timeout \\ 500) do
GenServer.call(pid, {:configure_channel, channel_opts}, call_timeout)
end
# Internal stuff
defp send_message(channel, routing_key, data, opts) do
exchange_request = Keyword.get(opts, :exchange_request, "")
queue_request = Keyword.get(opts, :queue_request, "")
publish_options = Keyword.merge(opts, [
persistent: Keyword.get(opts, :persistent, true),
reply_to: routing_key,
content_type: Keyword.get(opts, :content_type, "application/json")
])
AMQP.Basic.publish(channel, exchange_request, queue_request, data, publish_options)
end
defp consume_response(channel, queue_name, timeout, attempts) do
{payload, meta} = receive_message(channel, queue_name, timeout, attempts)
if meta != nil do
AMQP.Basic.ack(channel, meta.delivery_tag)
end
{payload, meta}
end
defp receive_message(channel, queue_name, timeout, attempts) do
case AMQP.Basic.get(channel, queue_name) do
{:ok, message, meta} ->
{message, meta}
{:empty, _} when is_integer(attempts) and attempts == 0 ->
{:empty, nil}
{:empty, _} when is_integer(attempts) and attempts > 0 ->
:timer.sleep(timeout)
receive_message(channel, queue_name, timeout, attempts - 1)
end
end
# Private API
def handle_call({:send, data, opts}, _from, state) do
{:reply, send_message(state[:channel], :undefined, data, opts), state}
end
def handle_call({:send_and_wait, data, opts, timeout, attempts}, _from, state) do
channel = state[:channel]
channel_opts = state[:channel_opts]
queue_name = Keyword.get(channel_opts[:queue] || [], :name, :undefined)
routing_key = Keyword.get(channel_opts[:queue] || [], :routing_key, :undefined)
configure(channel, channel_opts)
send_message(channel, routing_key, data, opts)
response = consume_response(state[:channel], queue_name, timeout, attempts)
AMQP.Queue.delete(channel, queue_name)
{:reply, response, state}
end
def handle_call({:consume_response, queue, timeout, attempts}, _from, state) do
{:reply, consume_response(state[:channel], queue, timeout, attempts), state}
end
def handle_call({:configure_channel, channel_opts}, _from, state) do
configure(state[:channel], channel_opts)
{:reply, :ok, state}
end
def handle_info({:DOWN, _ref, :process, _pid, _reason}, state) do
deinit(state[:connection], state[:channel])
{:noreply, state}
end
end