kiola / lib /srh /http /command_handler.ex
hiett
Remove old logs
7edc4a0
defmodule Srh.Http.CommandHandler do
alias Srh.Http.RequestValidator
alias Srh.Auth.TokenResolver
alias Srh.Redis.Client
alias Srh.Redis.ClientWorker
def handle_command(conn, token) do
case RequestValidator.validate_redis_body(conn.body_params) do
{:ok, command_array} ->
do_handle_command(command_array, token)
{:error, error_message} ->
{:malformed_data, error_message}
end
end
def handle_command_array(conn, token) do
case RequestValidator.validate_pipeline_redis_body(conn.body_params) do
{:ok, array_of_command_arrays} ->
do_handle_command_array(array_of_command_arrays, token)
{:error, error_message} ->
{:malformed_data, error_message}
end
end
def handle_command_transaction_array(conn, token) do
# Transactions use the same body format as pipelines, so we can use the same validator
case RequestValidator.validate_pipeline_redis_body(conn.body_params) do
{:ok, array_of_command_arrays} ->
do_handle_command_transaction_array(array_of_command_arrays, token)
{:error, error_message} ->
{:malformed_data, error_message}
end
end
defp do_handle_command(command_array, token) do
case TokenResolver.resolve(token) do
{:ok, connection_info} ->
dispatch_command(command_array, connection_info)
{:error, msg} ->
{:not_authorized, msg}
end
end
defp do_handle_command_array(array_of_command_arrays, token) do
case TokenResolver.resolve(token) do
{:ok, connection_info} ->
dispatch_command_array(array_of_command_arrays, connection_info)
{:error, msg} ->
{:not_authorized, msg}
end
end
defp do_handle_command_transaction_array(array_of_command_arrays, token) do
case TokenResolver.resolve(token) do
{:ok, connection_info} ->
dispatch_command_transaction_array(array_of_command_arrays, connection_info)
{:error, msg} ->
{:not_authorized, msg}
end
end
defp dispatch_command_array(_arr, _connection_info, responses \\ [])
defp dispatch_command_array([current | rest], connection_info, responses) do
updated_responses =
case dispatch_command(current, connection_info) do
{:ok, result_map} ->
[result_map | responses]
{:connection_error, result} ->
{:connection_error, result}
{:redis_error, result} ->
[result | responses]
end
case updated_responses do
{:connection_error, result} ->
{:connection_error, result}
_ ->
dispatch_command_array(rest, connection_info, updated_responses)
end
end
defp dispatch_command_array([], _connection_info, responses) do
# The responses will be in reverse order, as we're adding them to the list with the faster method of putting them at head.
{:ok, Enum.reverse(responses)}
end
defp dispatch_command_transaction_array(
command_array,
%{"srh_id" => srh_id, "max_connections" => max_connections} = connection_info,
responses \\ []
) do
case GenRegistry.lookup_or_start(Client, srh_id, [max_connections, connection_info]) do
{:ok, client_pid} ->
# Borrow a client, then run all of the commands (wrapped in MULTI and EXEC)
worker_pid = Client.borrow_worker(client_pid)
# We are manually going to invoke the MULTI, because there might be a connection error to the Redis server.
# In that case, we don't want the error to be wound up in the array of errors,
# we instead want to return the error immediately.
case ClientWorker.redis_command(worker_pid, ["MULTI"]) do
{:ok, _} ->
do_dispatch_command_transaction_array(command_array, worker_pid, responses)
# Now manually run the EXEC - this is what contains the information to form the response, not the above
result =
case ClientWorker.redis_command(worker_pid, ["EXEC"]) do
{:ok, res} ->
{
:ok,
res
|> Enum.map(&%{result: &1})
}
{:error, error} ->
decode_error(error, srh_id)
end
Client.return_worker(client_pid, worker_pid)
# Fire back the result here, because the initial Multi was successful
result
{:error, %{reason: :closed} = error} ->
# Ensure that this pool is killed, but still pass the error up the chain for the response
Client.destroy_workers(client_pid)
decode_error(error, srh_id)
{:error, error} ->
decode_error(error, srh_id)
end
{:error, msg} ->
{:server_error, msg}
end
end
defp do_dispatch_command_transaction_array([current | rest], worker_pid, responses)
when is_pid(worker_pid) do
updated_responses =
case ClientWorker.redis_command(worker_pid, current) do
{:ok, res} ->
[%{result: res} | responses]
{:error, error} ->
[
%{
error: error.message
}
| responses
]
end
do_dispatch_command_transaction_array(rest, worker_pid, updated_responses)
end
defp do_dispatch_command_transaction_array([], worker_pid, responses) when is_pid(worker_pid) do
{:ok, Enum.reverse(responses)}
end
defp dispatch_command(
command_array,
%{"srh_id" => srh_id, "max_connections" => max_connections} = connection_info
)
when is_number(max_connections) do
case GenRegistry.lookup_or_start(Client, srh_id, [max_connections, connection_info]) do
{:ok, pid} ->
# Run the command
case Client.find_worker(pid)
|> ClientWorker.redis_command(command_array) do
{:ok, res} ->
{:ok, %{result: res}}
# Jedix connection error
{:error, %{reason: :closed} = error} ->
# Ensure that this pool is killed, but still pass the error up the chain for the response
Client.destroy_workers(pid)
decode_error(error, srh_id)
{:error, error} ->
decode_error(error, srh_id)
end
{:error, msg} ->
{:server_error, msg}
end
end
# Figure out if it's an actual Redis error or a Redix error
defp decode_error(error, srh_id) do
case error do
%{reason: :closed} ->
IO.puts(
"WARNING: SRH was unable to connect to the Redis server. Please make sure it is running, and the connection information is correct. SRH ID: #{srh_id}"
)
{
:connection_error,
"SRH: Unable to connect to the Redis server. See SRH logs for more information."
}
_ ->
{
:redis_error,
%{
error: error.message
}
}
end
end
end