Spaces:
Sleeping
Sleeping
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 | |