Untabify.
This commit is contained in:
parent
bb29f8249a
commit
ad6e392939
|
|
@ -35,9 +35,9 @@
|
|||
-module(rabbit_stomp).
|
||||
|
||||
-export([kickstart/0,
|
||||
start/1,
|
||||
listener_started/2, listener_stopped/2, start_client/1,
|
||||
start_link/0, init/1, mainloop/1]).
|
||||
start/1,
|
||||
listener_started/2, listener_stopped/2, start_client/1,
|
||||
start_link/0, init/1, mainloop/1]).
|
||||
|
||||
-include("rabbit.hrl").
|
||||
-include("rabbit_framing.hrl").
|
||||
|
|
@ -64,18 +64,18 @@ start_listeners([]) ->
|
|||
ok;
|
||||
start_listeners([{Host, Port} | More]) ->
|
||||
{IPAddress, Name} = rabbit_networking:check_tcp_listener_address(rabbit_stomp_listener_sup,
|
||||
Host,
|
||||
Port),
|
||||
Host,
|
||||
Port),
|
||||
{ok,_} = supervisor:start_child(
|
||||
rabbit_sup,
|
||||
{Name,
|
||||
{tcp_listener_sup, start_link,
|
||||
[IPAddress, Port,
|
||||
[{packet, raw},
|
||||
{reuseaddr, true}],
|
||||
{?MODULE, listener_started, []},
|
||||
{?MODULE, listener_stopped, []},
|
||||
{?MODULE, start_client, []}]},
|
||||
[IPAddress, Port,
|
||||
[{packet, raw},
|
||||
{reuseaddr, true}],
|
||||
{?MODULE, listener_started, []},
|
||||
{?MODULE, listener_stopped, []},
|
||||
{?MODULE, start_client, []}]},
|
||||
transient, infinity, supervisor, [tcp_listener_sup]}),
|
||||
start_listeners(More).
|
||||
|
||||
|
|
@ -97,62 +97,62 @@ start_link() ->
|
|||
init(_Parent) ->
|
||||
receive
|
||||
{go, Sock} ->
|
||||
ok = inet:setopts(Sock, [{active, true}]),
|
||||
process_flag(trap_exit, true),
|
||||
ok = inet:setopts(Sock, [{active, true}]),
|
||||
process_flag(trap_exit, true),
|
||||
|
||||
{ok, {PeerAddress, PeerPort}} = inet:peername(Sock),
|
||||
PeerAddressS = inet_parse:ntoa(PeerAddress),
|
||||
error_logger:info_msg("starting STOMP connection ~p from ~s:~p~n",
|
||||
[self(), PeerAddressS, PeerPort]),
|
||||
{ok, {PeerAddress, PeerPort}} = inet:peername(Sock),
|
||||
PeerAddressS = inet_parse:ntoa(PeerAddress),
|
||||
error_logger:info_msg("starting STOMP connection ~p from ~s:~p~n",
|
||||
[self(), PeerAddressS, PeerPort]),
|
||||
|
||||
?MODULE:mainloop(#state{socket = Sock,
|
||||
channel = none,
|
||||
parse_state = stomp_frame:initial_state()}),
|
||||
?MODULE:mainloop(#state{socket = Sock,
|
||||
channel = none,
|
||||
parse_state = stomp_frame:initial_state()}),
|
||||
|
||||
error_logger:info_msg("ending STOMP connection ~p from ~s:~p~n",
|
||||
[self(), PeerAddressS, PeerPort])
|
||||
error_logger:info_msg("ending STOMP connection ~p from ~s:~p~n",
|
||||
[self(), PeerAddressS, PeerPort])
|
||||
end.
|
||||
|
||||
mainloop(State) ->
|
||||
receive
|
||||
E = {'EXIT', _Pid, _Reason} ->
|
||||
handle_exit(E, State);
|
||||
{tcp, _Sock, Bytes} ->
|
||||
process_received_bytes(Bytes, State);
|
||||
{tcp_closed, _Sock} ->
|
||||
case State#state.channel of
|
||||
none ->
|
||||
done;
|
||||
ChPid ->
|
||||
rabbit_channel:shutdown(ChPid),
|
||||
?MODULE:mainloop(State)
|
||||
end;
|
||||
{send_command, Command} ->
|
||||
?MODULE:mainloop(send_reply(Command, State));
|
||||
{send_command_and_notify, QPid, TxPid, Method, Content} ->
|
||||
State1 = send_reply(Method, Content, State),
|
||||
rabbit_amqqueue:notify_sent(QPid, TxPid),
|
||||
?MODULE:mainloop(State1);
|
||||
{send_command_and_shutdown, Command} ->
|
||||
send_reply(Command, State),
|
||||
done;
|
||||
shutdown ->
|
||||
%% This is the channel telling the writer to shut down. We
|
||||
%% ignore this, as the channel will exit itself shortly,
|
||||
%% which event we do respond to.
|
||||
?MODULE:mainloop(State);
|
||||
Data ->
|
||||
error_logger:error_msg("Internal error: unknown STOMP Data: ~p~n", [Data]),
|
||||
?MODULE:mainloop(State)
|
||||
E = {'EXIT', _Pid, _Reason} ->
|
||||
handle_exit(E, State);
|
||||
{tcp, _Sock, Bytes} ->
|
||||
process_received_bytes(Bytes, State);
|
||||
{tcp_closed, _Sock} ->
|
||||
case State#state.channel of
|
||||
none ->
|
||||
done;
|
||||
ChPid ->
|
||||
rabbit_channel:shutdown(ChPid),
|
||||
?MODULE:mainloop(State)
|
||||
end;
|
||||
{send_command, Command} ->
|
||||
?MODULE:mainloop(send_reply(Command, State));
|
||||
{send_command_and_notify, QPid, TxPid, Method, Content} ->
|
||||
State1 = send_reply(Method, Content, State),
|
||||
rabbit_amqqueue:notify_sent(QPid, TxPid),
|
||||
?MODULE:mainloop(State1);
|
||||
{send_command_and_shutdown, Command} ->
|
||||
send_reply(Command, State),
|
||||
done;
|
||||
shutdown ->
|
||||
%% This is the channel telling the writer to shut down. We
|
||||
%% ignore this, as the channel will exit itself shortly,
|
||||
%% which event we do respond to.
|
||||
?MODULE:mainloop(State);
|
||||
Data ->
|
||||
error_logger:error_msg("Internal error: unknown STOMP Data: ~p~n", [Data]),
|
||||
?MODULE:mainloop(State)
|
||||
end.
|
||||
|
||||
simple_method_sync_rpc(Method, State0) ->
|
||||
State = send_method(Method, State0),
|
||||
receive
|
||||
E = {'EXIT', _Pid, _Reason} ->
|
||||
handle_exit(E, State);
|
||||
{send_command, Reply} ->
|
||||
{ok, Reply, State}
|
||||
E = {'EXIT', _Pid, _Reason} ->
|
||||
handle_exit(E, State);
|
||||
{send_command, Reply} ->
|
||||
{ok, Reply, State}
|
||||
end.
|
||||
|
||||
handle_exit({'EXIT', _Pid, normal}, _State) ->
|
||||
|
|
@ -173,26 +173,26 @@ process_received_bytes([], State) ->
|
|||
?MODULE:mainloop(State);
|
||||
process_received_bytes(Bytes, State = #state{parse_state = ParseState}) ->
|
||||
case stomp_frame:parse(Bytes, ParseState) of
|
||||
{more, ParseState1} ->
|
||||
?MODULE:mainloop(State#state{parse_state = ParseState1});
|
||||
{ok, Frame = #stomp_frame{command = Command}, Rest} ->
|
||||
%% io:format("Frame: ~p~n", [Frame]),
|
||||
case catch process_frame(Command, Frame,
|
||||
State#state{parse_state = stomp_frame:initial_state()}) of
|
||||
{'EXIT', {amqp, Code, Method}} ->
|
||||
explain_amqp_death(Code, Method, State),
|
||||
done;
|
||||
{'EXIT', Reason} ->
|
||||
send_error("Processing error", "~p~n", [Reason], State),
|
||||
done;
|
||||
{ok, NewState} ->
|
||||
process_received_bytes(Rest, NewState);
|
||||
stop ->
|
||||
done
|
||||
end;
|
||||
{error, Reason} ->
|
||||
send_error("Invalid frame", "Could not parse frame: ~p~n", [Reason], State),
|
||||
done
|
||||
{more, ParseState1} ->
|
||||
?MODULE:mainloop(State#state{parse_state = ParseState1});
|
||||
{ok, Frame = #stomp_frame{command = Command}, Rest} ->
|
||||
%% io:format("Frame: ~p~n", [Frame]),
|
||||
case catch process_frame(Command, Frame,
|
||||
State#state{parse_state = stomp_frame:initial_state()}) of
|
||||
{'EXIT', {amqp, Code, Method}} ->
|
||||
explain_amqp_death(Code, Method, State),
|
||||
done;
|
||||
{'EXIT', Reason} ->
|
||||
send_error("Processing error", "~p~n", [Reason], State),
|
||||
done;
|
||||
{ok, NewState} ->
|
||||
process_received_bytes(Rest, NewState);
|
||||
stop ->
|
||||
done
|
||||
end;
|
||||
{error, Reason} ->
|
||||
send_error("Invalid frame", "Could not parse frame: ~p~n", [Reason], State),
|
||||
done
|
||||
end.
|
||||
|
||||
explain_amqp_death(Code, Method, State) ->
|
||||
|
|
@ -210,45 +210,45 @@ maybe_header(Key, Value) when is_integer(Value) -> [{Key, integer_to_list(Value)
|
|||
maybe_header(_Key, _Value) -> [].
|
||||
|
||||
send_reply(#'basic.deliver'{consumer_tag = ConsumerTag,
|
||||
delivery_tag = DeliveryTag,
|
||||
exchange = Exchange,
|
||||
routing_key = RoutingKey},
|
||||
#content{properties = #'P_basic'{headers = Headers,
|
||||
content_type = ContentType,
|
||||
content_encoding = ContentEncoding,
|
||||
delivery_mode = DeliveryMode,
|
||||
priority = Priority,
|
||||
correlation_id = CorrelationId,
|
||||
reply_to = ReplyTo,
|
||||
message_id = MessageId},
|
||||
payload_fragments_rev = BodyFragmentsRev},
|
||||
State = #state{session_id = SessionId}) ->
|
||||
delivery_tag = DeliveryTag,
|
||||
exchange = Exchange,
|
||||
routing_key = RoutingKey},
|
||||
#content{properties = #'P_basic'{headers = Headers,
|
||||
content_type = ContentType,
|
||||
content_encoding = ContentEncoding,
|
||||
delivery_mode = DeliveryMode,
|
||||
priority = Priority,
|
||||
correlation_id = CorrelationId,
|
||||
reply_to = ReplyTo,
|
||||
message_id = MessageId},
|
||||
payload_fragments_rev = BodyFragmentsRev},
|
||||
State = #state{session_id = SessionId}) ->
|
||||
send_frame("MESSAGE",
|
||||
[{"destination", binary_to_list(RoutingKey)},
|
||||
{"exchange", binary_to_list(Exchange)},
|
||||
%% TODO append ContentEncoding as ContentType; charset=ContentEncoding?
|
||||
%% The STOMP SEND handle could also parse "content-type" to split it, perhaps?
|
||||
{"message-id", SessionId ++ "_" ++ integer_to_list(DeliveryTag)}]
|
||||
++ maybe_header("content-type", ContentType)
|
||||
++ maybe_header("content-encoding", ContentEncoding)
|
||||
++ case ConsumerTag of
|
||||
<<"Q_", _/binary>> ->
|
||||
[];
|
||||
<<"T_", Id/binary>> ->
|
||||
[{"subscription", binary_to_list(Id)}]
|
||||
end
|
||||
++ adhoc_convert_headers(case Headers of
|
||||
undefined -> [];
|
||||
_ -> Headers
|
||||
end)
|
||||
++ maybe_header("delivery-mode", DeliveryMode)
|
||||
++ maybe_header("priority", Priority)
|
||||
++ maybe_header("correlation-id", CorrelationId)
|
||||
++ maybe_header("reply-to", ReplyTo)
|
||||
++ maybe_header("amqp-message-id", MessageId),
|
||||
lists:concat(lists:reverse(lists:map(fun erlang:binary_to_list/1,
|
||||
BodyFragmentsRev))),
|
||||
State);
|
||||
[{"destination", binary_to_list(RoutingKey)},
|
||||
{"exchange", binary_to_list(Exchange)},
|
||||
%% TODO append ContentEncoding as ContentType; charset=ContentEncoding?
|
||||
%% The STOMP SEND handle could also parse "content-type" to split it, perhaps?
|
||||
{"message-id", SessionId ++ "_" ++ integer_to_list(DeliveryTag)}]
|
||||
++ maybe_header("content-type", ContentType)
|
||||
++ maybe_header("content-encoding", ContentEncoding)
|
||||
++ case ConsumerTag of
|
||||
<<"Q_", _/binary>> ->
|
||||
[];
|
||||
<<"T_", Id/binary>> ->
|
||||
[{"subscription", binary_to_list(Id)}]
|
||||
end
|
||||
++ adhoc_convert_headers(case Headers of
|
||||
undefined -> [];
|
||||
_ -> Headers
|
||||
end)
|
||||
++ maybe_header("delivery-mode", DeliveryMode)
|
||||
++ maybe_header("priority", Priority)
|
||||
++ maybe_header("correlation-id", CorrelationId)
|
||||
++ maybe_header("reply-to", ReplyTo)
|
||||
++ maybe_header("amqp-message-id", MessageId),
|
||||
lists:concat(lists:reverse(lists:map(fun erlang:binary_to_list/1,
|
||||
BodyFragmentsRev))),
|
||||
State);
|
||||
send_reply(Command, Content, State) ->
|
||||
error_logger:error_msg("STOMP Reply command unhandled: ~p~n~p~n", [Command, Content]),
|
||||
State.
|
||||
|
|
@ -269,15 +269,15 @@ send_frame(Frame, State = #state{socket = Sock}) ->
|
|||
|
||||
send_frame(Command, Headers, Body, State) ->
|
||||
send_frame(#stomp_frame{command = Command,
|
||||
headers = Headers,
|
||||
body = Body},
|
||||
State).
|
||||
headers = Headers,
|
||||
body = Body},
|
||||
State).
|
||||
|
||||
send_error(Message, Detail, State) ->
|
||||
error_logger:error_msg("STOMP error frame sent:~nMessage: ~p~nDetail: ~p~n",
|
||||
[Message, Detail]),
|
||||
[Message, Detail]),
|
||||
send_frame("ERROR", [{"message", Message},
|
||||
{"content-type", "text/plain"}], Detail, State).
|
||||
{"content-type", "text/plain"}], Detail, State).
|
||||
|
||||
send_error(Message, Format, Args, State) ->
|
||||
send_error(Message, lists:flatten(io_lib:format(Format, Args)), State).
|
||||
|
|
@ -285,37 +285,37 @@ send_error(Message, Format, Args, State) ->
|
|||
process_frame("CONNECT", Frame, State = #state{channel = none}) ->
|
||||
{ok, DefaultVHost} = application:get_env(default_vhost),
|
||||
{ok, State1} = do_login(stomp_frame:header(Frame, "login"),
|
||||
stomp_frame:header(Frame, "passcode"),
|
||||
stomp_frame:header(Frame, "virtual-host", binary_to_list(DefaultVHost)),
|
||||
State),
|
||||
stomp_frame:header(Frame, "passcode"),
|
||||
stomp_frame:header(Frame, "virtual-host", binary_to_list(DefaultVHost)),
|
||||
State),
|
||||
State2 = case stomp_frame:integer_header(Frame, "prefetch") of
|
||||
{ok, PrefetchCount} ->
|
||||
{ok, #'basic.qos_ok'{}, S} =
|
||||
simple_method_sync_rpc(#'basic.qos'{prefetch_size = 0,
|
||||
prefetch_count = PrefetchCount,
|
||||
global = false},
|
||||
State1),
|
||||
S;
|
||||
not_found -> State1
|
||||
end,
|
||||
{ok, PrefetchCount} ->
|
||||
{ok, #'basic.qos_ok'{}, S} =
|
||||
simple_method_sync_rpc(#'basic.qos'{prefetch_size = 0,
|
||||
prefetch_count = PrefetchCount,
|
||||
global = false},
|
||||
State1),
|
||||
S;
|
||||
not_found -> State1
|
||||
end,
|
||||
{ok, State2};
|
||||
process_frame("DISCONNECT", _Frame, _State = #state{channel = none}) ->
|
||||
stop;
|
||||
process_frame(_Command, _Frame, State = #state{channel = none}) ->
|
||||
{ok, send_error("Illegal command",
|
||||
"You must log in using CONNECT first\n",
|
||||
State)};
|
||||
"You must log in using CONNECT first\n",
|
||||
State)};
|
||||
process_frame(Command, Frame, State) ->
|
||||
case process_command(Command, Frame, State) of
|
||||
{ok, State1} ->
|
||||
{ok, case stomp_frame:header(Frame, "receipt") of
|
||||
{ok, Id} ->
|
||||
send_frame("RECEIPT", [{"receipt-id", Id}], "", State1);
|
||||
not_found ->
|
||||
State1
|
||||
end};
|
||||
stop ->
|
||||
stop
|
||||
{ok, State1} ->
|
||||
{ok, case stomp_frame:header(Frame, "receipt") of
|
||||
{ok, Id} ->
|
||||
send_frame("RECEIPT", [{"receipt-id", Id}], "", State1);
|
||||
not_found ->
|
||||
State1
|
||||
end};
|
||||
stop ->
|
||||
stop
|
||||
end.
|
||||
|
||||
send_method(Method, State = #state{channel = ChPid}) ->
|
||||
|
|
@ -324,28 +324,28 @@ send_method(Method, State = #state{channel = ChPid}) ->
|
|||
|
||||
send_method(Method, Properties, Body, State = #state{channel = ChPid}) ->
|
||||
ok = rabbit_channel:do(ChPid,
|
||||
Method,
|
||||
#content{class_id = 60, %% basic
|
||||
properties = Properties,
|
||||
properties_bin = none,
|
||||
payload_fragments_rev = [list_to_binary(Body)]}),
|
||||
Method,
|
||||
#content{class_id = 60, %% basic
|
||||
properties = Properties,
|
||||
properties_bin = none,
|
||||
payload_fragments_rev = [list_to_binary(Body)]}),
|
||||
State.
|
||||
|
||||
do_login({ok, Login}, {ok, Passcode}, VirtualHost, State) ->
|
||||
U = rabbit_access_control:user_pass_login(list_to_binary(Login),
|
||||
list_to_binary(Passcode)),
|
||||
list_to_binary(Passcode)),
|
||||
ok = rabbit_access_control:check_vhost_access(U, list_to_binary(VirtualHost)),
|
||||
ChPid =
|
||||
rabbit_channel:start_link(?MODULE, self(), self(),
|
||||
U#user.username, list_to_binary(VirtualHost)),
|
||||
rabbit_channel:start_link(?MODULE, self(), self(),
|
||||
U#user.username, list_to_binary(VirtualHost)),
|
||||
{ok, #'channel.open_ok'{}, State1} =
|
||||
simple_method_sync_rpc(#'channel.open'{out_of_band = <<"">>},
|
||||
State#state{channel = ChPid}),
|
||||
simple_method_sync_rpc(#'channel.open'{out_of_band = <<"">>},
|
||||
State#state{channel = ChPid}),
|
||||
SessionId = rabbit_guid:string_guid("session"),
|
||||
{ok, send_frame("CONNECTED",
|
||||
[{"session", SessionId}],
|
||||
"",
|
||||
State1#state{session_id = SessionId})};
|
||||
[{"session", SessionId}],
|
||||
"",
|
||||
State1#state{session_id = SessionId})};
|
||||
do_login(_, _, _, State) ->
|
||||
{ok, send_error("Bad CONNECT", "Missing login or passcode header(s)\n", State)}.
|
||||
|
||||
|
|
@ -361,39 +361,39 @@ user_binding_header_key(_) -> false.
|
|||
make_string_table(_KeyFilter, []) -> [];
|
||||
make_string_table(KeyFilter, [{K, V} | Rest]) ->
|
||||
case KeyFilter(K) of
|
||||
false ->
|
||||
make_string_table(KeyFilter, Rest);
|
||||
NewK ->
|
||||
[{list_to_binary(NewK), longstr, list_to_binary(V)}
|
||||
| make_string_table(KeyFilter, Rest)]
|
||||
false ->
|
||||
make_string_table(KeyFilter, Rest);
|
||||
NewK ->
|
||||
[{list_to_binary(NewK), longstr, list_to_binary(V)}
|
||||
| make_string_table(KeyFilter, Rest)]
|
||||
end.
|
||||
|
||||
transactional(Frame) ->
|
||||
case stomp_frame:header(Frame, "transaction") of
|
||||
{ok, Transaction} ->
|
||||
{yes, Transaction};
|
||||
not_found ->
|
||||
no
|
||||
{ok, Transaction} ->
|
||||
{yes, Transaction};
|
||||
not_found ->
|
||||
no
|
||||
end.
|
||||
|
||||
transactional_action(Frame, Name, Fun, State) ->
|
||||
case transactional(Frame) of
|
||||
{yes, Transaction} ->
|
||||
Fun(Transaction, State);
|
||||
no ->
|
||||
{ok, send_error("Missing transaction",
|
||||
Name ++ " must include a 'transaction' header\n",
|
||||
State)}
|
||||
{yes, Transaction} ->
|
||||
Fun(Transaction, State);
|
||||
no ->
|
||||
{ok, send_error("Missing transaction",
|
||||
Name ++ " must include a 'transaction' header\n",
|
||||
State)}
|
||||
end.
|
||||
|
||||
with_transaction(Transaction, State, Fun) ->
|
||||
case get({transaction, Transaction}) of
|
||||
undefined ->
|
||||
{ok, send_error("Bad transaction",
|
||||
"Invalid transaction identifier: ~p~n", [Transaction],
|
||||
State)};
|
||||
Actions ->
|
||||
Fun(Actions, State)
|
||||
undefined ->
|
||||
{ok, send_error("Bad transaction",
|
||||
"Invalid transaction identifier: ~p~n", [Transaction],
|
||||
State)};
|
||||
Actions ->
|
||||
Fun(Actions, State)
|
||||
end.
|
||||
|
||||
begin_transaction(Transaction, State) ->
|
||||
|
|
@ -402,27 +402,27 @@ begin_transaction(Transaction, State) ->
|
|||
|
||||
extend_transaction(Transaction, Action, State0) ->
|
||||
with_transaction(Transaction, State0,
|
||||
fun (Actions, State) ->
|
||||
put({transaction, Transaction}, [Action | Actions]),
|
||||
{ok, State}
|
||||
end).
|
||||
fun (Actions, State) ->
|
||||
put({transaction, Transaction}, [Action | Actions]),
|
||||
{ok, State}
|
||||
end).
|
||||
|
||||
commit_transaction(Transaction, State0) ->
|
||||
with_transaction(Transaction, State0,
|
||||
fun (Actions, State) ->
|
||||
FinalState = lists:foldr(fun perform_transaction_action/2,
|
||||
State,
|
||||
Actions),
|
||||
erase({transaction, Transaction}),
|
||||
{ok, FinalState}
|
||||
end).
|
||||
fun (Actions, State) ->
|
||||
FinalState = lists:foldr(fun perform_transaction_action/2,
|
||||
State,
|
||||
Actions),
|
||||
erase({transaction, Transaction}),
|
||||
{ok, FinalState}
|
||||
end).
|
||||
|
||||
abort_transaction(Transaction, State0) ->
|
||||
with_transaction(Transaction, State0,
|
||||
fun (_Actions, State) ->
|
||||
erase({transaction, Transaction}),
|
||||
{ok, State}
|
||||
end).
|
||||
fun (_Actions, State) ->
|
||||
erase({transaction, Transaction}),
|
||||
{ok, State}
|
||||
end).
|
||||
|
||||
perform_transaction_action({Method}, State) ->
|
||||
send_method(Method, State);
|
||||
|
|
@ -432,144 +432,144 @@ perform_transaction_action({Method, Props, Body}, State) ->
|
|||
process_command("BEGIN", Frame, State) ->
|
||||
transactional_action(Frame, "BEGIN", fun begin_transaction/2, State);
|
||||
process_command("SEND",
|
||||
Frame = #stomp_frame{headers = Headers, body = Body},
|
||||
State) ->
|
||||
Frame = #stomp_frame{headers = Headers, body = Body},
|
||||
State) ->
|
||||
case stomp_frame:header(Frame, "destination") of
|
||||
{ok, RoutingKeyStr} ->
|
||||
ExchangeStr = stomp_frame:header(Frame, "exchange", ""),
|
||||
Props = #'P_basic'{
|
||||
content_type = stomp_frame:binary_header(Frame, "content-type", <<"text/plain">>),
|
||||
content_encoding = stomp_frame:binary_header(Frame, "content-encoding", undefined),
|
||||
headers = make_string_table(fun user_header_key/1, Headers),
|
||||
delivery_mode = stomp_frame:integer_header(Frame, "delivery-mode", undefined),
|
||||
priority = stomp_frame:integer_header(Frame, "priority", undefined),
|
||||
correlation_id = stomp_frame:binary_header(Frame, "correlation-id", undefined),
|
||||
reply_to = stomp_frame:binary_header(Frame, "reply-to", undefined),
|
||||
message_id = stomp_frame:binary_header(Frame, "amqp-message-id", undefined)
|
||||
},
|
||||
Method = #'basic.publish'{exchange = list_to_binary(ExchangeStr),
|
||||
routing_key = list_to_binary(RoutingKeyStr),
|
||||
mandatory = false,
|
||||
immediate = false},
|
||||
case transactional(Frame) of
|
||||
{yes, Transaction} ->
|
||||
extend_transaction(Transaction, {Method, Props, Body}, State);
|
||||
no ->
|
||||
{ok, send_method(Method, Props, Body, State)}
|
||||
end;
|
||||
not_found ->
|
||||
{ok, send_error("Missing destination",
|
||||
"SEND must include a 'destination', and optional 'exchange' header\n",
|
||||
State)}
|
||||
{ok, RoutingKeyStr} ->
|
||||
ExchangeStr = stomp_frame:header(Frame, "exchange", ""),
|
||||
Props = #'P_basic'{
|
||||
content_type = stomp_frame:binary_header(Frame, "content-type", <<"text/plain">>),
|
||||
content_encoding = stomp_frame:binary_header(Frame, "content-encoding", undefined),
|
||||
headers = make_string_table(fun user_header_key/1, Headers),
|
||||
delivery_mode = stomp_frame:integer_header(Frame, "delivery-mode", undefined),
|
||||
priority = stomp_frame:integer_header(Frame, "priority", undefined),
|
||||
correlation_id = stomp_frame:binary_header(Frame, "correlation-id", undefined),
|
||||
reply_to = stomp_frame:binary_header(Frame, "reply-to", undefined),
|
||||
message_id = stomp_frame:binary_header(Frame, "amqp-message-id", undefined)
|
||||
},
|
||||
Method = #'basic.publish'{exchange = list_to_binary(ExchangeStr),
|
||||
routing_key = list_to_binary(RoutingKeyStr),
|
||||
mandatory = false,
|
||||
immediate = false},
|
||||
case transactional(Frame) of
|
||||
{yes, Transaction} ->
|
||||
extend_transaction(Transaction, {Method, Props, Body}, State);
|
||||
no ->
|
||||
{ok, send_method(Method, Props, Body, State)}
|
||||
end;
|
||||
not_found ->
|
||||
{ok, send_error("Missing destination",
|
||||
"SEND must include a 'destination', and optional 'exchange' header\n",
|
||||
State)}
|
||||
end;
|
||||
process_command("ACK", Frame, State = #state{session_id = SessionId}) ->
|
||||
case stomp_frame:header(Frame, "message-id") of
|
||||
{ok, IdStr} ->
|
||||
IdPrefix = SessionId ++ "_",
|
||||
case string:substr(IdStr, 1, length(IdPrefix)) of
|
||||
IdPrefix ->
|
||||
DeliveryTag = list_to_integer(string:substr(IdStr, length(IdPrefix) + 1)),
|
||||
Method = #'basic.ack'{delivery_tag = DeliveryTag,
|
||||
multiple = false},
|
||||
case transactional(Frame) of
|
||||
{yes, Transaction} ->
|
||||
extend_transaction(Transaction, {Method}, State);
|
||||
no ->
|
||||
{ok, send_method(Method, State)}
|
||||
end;
|
||||
_ ->
|
||||
rabbit_misc:die(command_invalid, 'basic.ack')
|
||||
end;
|
||||
not_found ->
|
||||
{ok, send_error("Missing message-id",
|
||||
"ACK must include a 'message-id' header\n",
|
||||
State)}
|
||||
{ok, IdStr} ->
|
||||
IdPrefix = SessionId ++ "_",
|
||||
case string:substr(IdStr, 1, length(IdPrefix)) of
|
||||
IdPrefix ->
|
||||
DeliveryTag = list_to_integer(string:substr(IdStr, length(IdPrefix) + 1)),
|
||||
Method = #'basic.ack'{delivery_tag = DeliveryTag,
|
||||
multiple = false},
|
||||
case transactional(Frame) of
|
||||
{yes, Transaction} ->
|
||||
extend_transaction(Transaction, {Method}, State);
|
||||
no ->
|
||||
{ok, send_method(Method, State)}
|
||||
end;
|
||||
_ ->
|
||||
rabbit_misc:die(command_invalid, 'basic.ack')
|
||||
end;
|
||||
not_found ->
|
||||
{ok, send_error("Missing message-id",
|
||||
"ACK must include a 'message-id' header\n",
|
||||
State)}
|
||||
end;
|
||||
process_command("COMMIT", Frame, State) ->
|
||||
transactional_action(Frame, "COMMIT", fun commit_transaction/2, State);
|
||||
process_command("ABORT", Frame, State) ->
|
||||
transactional_action(Frame, "ABORT", fun abort_transaction/2, State);
|
||||
process_command("SUBSCRIBE",
|
||||
Frame = #stomp_frame{headers = Headers},
|
||||
State) ->
|
||||
Frame = #stomp_frame{headers = Headers},
|
||||
State) ->
|
||||
AckMode = case stomp_frame:header(Frame, "ack", "auto") of
|
||||
"auto" -> auto;
|
||||
"client" -> client
|
||||
end,
|
||||
"auto" -> auto;
|
||||
"client" -> client
|
||||
end,
|
||||
case stomp_frame:header(Frame, "destination") of
|
||||
{ok, QueueStr} ->
|
||||
ConsumerTag = case stomp_frame:header(Frame, "id") of
|
||||
{ok, Str} ->
|
||||
list_to_binary("T_" ++ Str);
|
||||
not_found ->
|
||||
list_to_binary("Q_" ++ QueueStr)
|
||||
end,
|
||||
Queue = list_to_binary(QueueStr),
|
||||
State1 = send_method(#'queue.declare'{queue = Queue,
|
||||
passive = stomp_frame:boolean_header(Frame, "passive", false),
|
||||
durable = stomp_frame:boolean_header(Frame, "durable", false),
|
||||
exclusive = stomp_frame:boolean_header(Frame, "exclusive", false),
|
||||
auto_delete = stomp_frame:boolean_header(Frame, "auto-delete", true),
|
||||
nowait = true,
|
||||
arguments =
|
||||
make_string_table(fun user_queue_header_key/1,
|
||||
Headers)},
|
||||
State),
|
||||
State2 = case stomp_frame:header(Frame, "exchange") of
|
||||
{ok, ExchangeStr } ->
|
||||
Exchange = list_to_binary(ExchangeStr),
|
||||
RoutingKeyStr = stomp_frame:header(Frame, "routing_key", ""),
|
||||
RoutingKey = list_to_binary(RoutingKeyStr),
|
||||
send_method(#'queue.bind'{queue = Queue,
|
||||
exchange = Exchange,
|
||||
routing_key = RoutingKey,
|
||||
nowait = true,
|
||||
arguments =
|
||||
make_string_table(
|
||||
fun user_binding_header_key/1,
|
||||
Headers)},
|
||||
State1);
|
||||
not_found -> State1
|
||||
end,
|
||||
State3 = send_method(#'basic.consume'{queue = Queue,
|
||||
consumer_tag = ConsumerTag,
|
||||
no_local = false,
|
||||
no_ack = (AckMode == auto),
|
||||
exclusive = false,
|
||||
nowait = true},
|
||||
State2),
|
||||
{ok, State3};
|
||||
not_found ->
|
||||
{ok, send_error("Missing destination",
|
||||
"SUBSCRIBE must include a 'destination' header\n",
|
||||
State)}
|
||||
{ok, QueueStr} ->
|
||||
ConsumerTag = case stomp_frame:header(Frame, "id") of
|
||||
{ok, Str} ->
|
||||
list_to_binary("T_" ++ Str);
|
||||
not_found ->
|
||||
list_to_binary("Q_" ++ QueueStr)
|
||||
end,
|
||||
Queue = list_to_binary(QueueStr),
|
||||
State1 = send_method(#'queue.declare'{queue = Queue,
|
||||
passive = stomp_frame:boolean_header(Frame, "passive", false),
|
||||
durable = stomp_frame:boolean_header(Frame, "durable", false),
|
||||
exclusive = stomp_frame:boolean_header(Frame, "exclusive", false),
|
||||
auto_delete = stomp_frame:boolean_header(Frame, "auto-delete", true),
|
||||
nowait = true,
|
||||
arguments =
|
||||
make_string_table(fun user_queue_header_key/1,
|
||||
Headers)},
|
||||
State),
|
||||
State2 = case stomp_frame:header(Frame, "exchange") of
|
||||
{ok, ExchangeStr } ->
|
||||
Exchange = list_to_binary(ExchangeStr),
|
||||
RoutingKeyStr = stomp_frame:header(Frame, "routing_key", ""),
|
||||
RoutingKey = list_to_binary(RoutingKeyStr),
|
||||
send_method(#'queue.bind'{queue = Queue,
|
||||
exchange = Exchange,
|
||||
routing_key = RoutingKey,
|
||||
nowait = true,
|
||||
arguments =
|
||||
make_string_table(
|
||||
fun user_binding_header_key/1,
|
||||
Headers)},
|
||||
State1);
|
||||
not_found -> State1
|
||||
end,
|
||||
State3 = send_method(#'basic.consume'{queue = Queue,
|
||||
consumer_tag = ConsumerTag,
|
||||
no_local = false,
|
||||
no_ack = (AckMode == auto),
|
||||
exclusive = false,
|
||||
nowait = true},
|
||||
State2),
|
||||
{ok, State3};
|
||||
not_found ->
|
||||
{ok, send_error("Missing destination",
|
||||
"SUBSCRIBE must include a 'destination' header\n",
|
||||
State)}
|
||||
end;
|
||||
process_command("UNSUBSCRIBE", Frame, State) ->
|
||||
ConsumerTag = case stomp_frame:header(Frame, "id") of
|
||||
{ok, IdStr} ->
|
||||
list_to_binary("T_" ++ IdStr);
|
||||
not_found ->
|
||||
case stomp_frame:header(Frame, "destination") of
|
||||
{ok, QueueStr} ->
|
||||
list_to_binary("Q_" ++ QueueStr);
|
||||
not_found ->
|
||||
missing
|
||||
end
|
||||
end,
|
||||
{ok, IdStr} ->
|
||||
list_to_binary("T_" ++ IdStr);
|
||||
not_found ->
|
||||
case stomp_frame:header(Frame, "destination") of
|
||||
{ok, QueueStr} ->
|
||||
list_to_binary("Q_" ++ QueueStr);
|
||||
not_found ->
|
||||
missing
|
||||
end
|
||||
end,
|
||||
if
|
||||
ConsumerTag == missing ->
|
||||
{ok, send_error("Missing destination or id",
|
||||
"UNSUBSCRIBE must include a 'destination' or 'id' header\n",
|
||||
State)};
|
||||
true ->
|
||||
{ok, send_method(#'basic.cancel'{consumer_tag = ConsumerTag,
|
||||
nowait = true},
|
||||
State)}
|
||||
ConsumerTag == missing ->
|
||||
{ok, send_error("Missing destination or id",
|
||||
"UNSUBSCRIBE must include a 'destination' or 'id' header\n",
|
||||
State)};
|
||||
true ->
|
||||
{ok, send_method(#'basic.cancel'{consumer_tag = ConsumerTag,
|
||||
nowait = true},
|
||||
State)}
|
||||
end;
|
||||
process_command("DISCONNECT", _Frame, State) ->
|
||||
{ok, send_method(#'channel.close'{reply_code = 200, reply_text = <<"">>,
|
||||
class_id = 0, method_id = 0}, State)};
|
||||
class_id = 0, method_id = 0}, State)};
|
||||
process_command(Command, _Frame, State) ->
|
||||
{ok, send_error("Bad command",
|
||||
"Could not interpret command " ++ Command ++ "\n",
|
||||
State)}.
|
||||
"Could not interpret command " ++ Command ++ "\n",
|
||||
State)}.
|
||||
|
|
|
|||
|
|
@ -60,14 +60,14 @@ parse_headers([$\n | Rest], ParseState = #hstate{state = command, acc = []}) ->
|
|||
parse_headers(Rest, ParseState);
|
||||
parse_headers([$\n | Rest], ParseState = #hstate{state = command, acc = Acc}) ->
|
||||
parse_headers(Rest, ParseState#hstate{state = key, acc = [],
|
||||
command = lists:reverse(Acc)});
|
||||
command = lists:reverse(Acc)});
|
||||
parse_headers([$\n | Rest], _ParseState = #hstate{state = key, acc = Acc,
|
||||
command = Command, headers = Headers}) ->
|
||||
command = Command, headers = Headers}) ->
|
||||
case Acc of
|
||||
[] ->
|
||||
{ok, Command, Headers, Rest};
|
||||
_ ->
|
||||
{error, {bad_header_key, lists:reverse(Acc)}}
|
||||
[] ->
|
||||
{ok, Command, Headers, Rest};
|
||||
_ ->
|
||||
{error, {bad_header_key, lists:reverse(Acc)}}
|
||||
end;
|
||||
parse_headers([$: | Rest], ParseState = #hstate{state = key, acc = Acc}) ->
|
||||
parse_headers(Rest, ParseState#hstate{state = eatspace, acc = [], key = lists:reverse(Acc)});
|
||||
|
|
@ -76,82 +76,82 @@ parse_headers([$ | Rest], ParseState = #hstate{state = eatspace}) ->
|
|||
parse_headers(Input, ParseState = #hstate{state = eatspace}) ->
|
||||
parse_headers(Input, ParseState#hstate{state = value});
|
||||
parse_headers([$\n | Rest], ParseState = #hstate{state = value, acc = Acc, key = Key,
|
||||
headers = Headers}) ->
|
||||
headers = Headers}) ->
|
||||
parse_headers(Rest, ParseState#hstate{state = key, acc = [],
|
||||
headers = [{Key, lists:reverse(Acc)}
|
||||
| Headers]});
|
||||
headers = [{Key, lists:reverse(Acc)}
|
||||
| Headers]});
|
||||
parse_headers([Ch | Rest], ParseState = #hstate{acc = Acc}) ->
|
||||
if
|
||||
Ch < 32 ->
|
||||
{error, {bad_character, Ch}};
|
||||
true ->
|
||||
parse_headers(Rest, ParseState#hstate{acc = [Ch | Acc]})
|
||||
Ch < 32 ->
|
||||
{error, {bad_character, Ch}};
|
||||
true ->
|
||||
parse_headers(Rest, ParseState#hstate{acc = [Ch | Acc]})
|
||||
end.
|
||||
|
||||
header(#stomp_frame{headers = Headers}, Key) ->
|
||||
case lists:keysearch(Key, 1, Headers) of
|
||||
{value, {_, Str}} ->
|
||||
{ok, Str};
|
||||
_ ->
|
||||
not_found
|
||||
{value, {_, Str}} ->
|
||||
{ok, Str};
|
||||
_ ->
|
||||
not_found
|
||||
end.
|
||||
|
||||
header(#stomp_frame{headers = Headers}, Key, DefaultValue) ->
|
||||
case lists:keysearch(Key, 1, Headers) of
|
||||
{value, {_, Str}} ->
|
||||
Str;
|
||||
_ ->
|
||||
DefaultValue
|
||||
{value, {_, Str}} ->
|
||||
Str;
|
||||
_ ->
|
||||
DefaultValue
|
||||
end.
|
||||
|
||||
boolean_header(#stomp_frame{headers = Headers}, Key) ->
|
||||
boolean_header(Headers, Key);
|
||||
boolean_header(Headers, Key) ->
|
||||
case lists:keysearch(Key, 1, Headers) of
|
||||
{value, {_, "true"}} ->
|
||||
{ok, true};
|
||||
{value, {_, "false"}} ->
|
||||
{ok, false};
|
||||
_ ->
|
||||
not_found
|
||||
{value, {_, "true"}} ->
|
||||
{ok, true};
|
||||
{value, {_, "false"}} ->
|
||||
{ok, false};
|
||||
_ ->
|
||||
not_found
|
||||
end.
|
||||
|
||||
boolean_header(H, Key, D) ->
|
||||
case boolean_header(H, Key) of
|
||||
{ok, V} ->
|
||||
V;
|
||||
not_found ->
|
||||
D
|
||||
{ok, V} ->
|
||||
V;
|
||||
not_found ->
|
||||
D
|
||||
end.
|
||||
|
||||
integer_header(#stomp_frame{headers = Headers}, Key) ->
|
||||
integer_header(Headers, Key);
|
||||
integer_header(Headers, Key) ->
|
||||
case lists:keysearch(Key, 1, Headers) of
|
||||
{value, {_, Str}} ->
|
||||
{ok, list_to_integer(string:strip(Str))};
|
||||
_ ->
|
||||
not_found
|
||||
{value, {_, Str}} ->
|
||||
{ok, list_to_integer(string:strip(Str))};
|
||||
_ ->
|
||||
not_found
|
||||
end.
|
||||
|
||||
integer_header(H, Key, D) ->
|
||||
case integer_header(H, Key) of
|
||||
{ok, V} ->
|
||||
V;
|
||||
not_found ->
|
||||
D
|
||||
{ok, V} ->
|
||||
V;
|
||||
not_found ->
|
||||
D
|
||||
end.
|
||||
|
||||
binary_header(F, K) ->
|
||||
case header(F, K) of
|
||||
{ok, Str} -> {ok, list_to_binary(Str)};
|
||||
not_found -> not_found
|
||||
{ok, Str} -> {ok, list_to_binary(Str)};
|
||||
not_found -> not_found
|
||||
end.
|
||||
|
||||
binary_header(F, K, V) ->
|
||||
case header(F, K) of
|
||||
{ok, Str} -> list_to_binary(Str);
|
||||
not_found -> V
|
||||
{ok, Str} -> list_to_binary(Str);
|
||||
not_found -> V
|
||||
end.
|
||||
|
||||
content_length(Headers) ->
|
||||
|
|
@ -159,10 +159,10 @@ content_length(Headers) ->
|
|||
|
||||
initial_body_state(Headers) ->
|
||||
case content_length(Headers) of
|
||||
{ok, ByteCount} ->
|
||||
#bstate{acc = [], remaining = ByteCount};
|
||||
not_found ->
|
||||
#bstate{acc = [], remaining = unknown}
|
||||
{ok, ByteCount} ->
|
||||
#bstate{acc = [], remaining = ByteCount};
|
||||
not_found ->
|
||||
#bstate{acc = [], remaining = unknown}
|
||||
end.
|
||||
|
||||
parse_body([], State) ->
|
||||
|
|
@ -175,10 +175,10 @@ parse_body([Ch | Rest], State = #bstate{acc = Acc, remaining = unknown}) ->
|
|||
parse_body(Rest, State#bstate{acc = [Ch | Acc]});
|
||||
parse_body([Ch | Rest], State = #bstate{acc = Acc, remaining = N}) ->
|
||||
if
|
||||
N > 0 ->
|
||||
parse_body(Rest, State#bstate{acc = [Ch | Acc], remaining = N - 1});
|
||||
true ->
|
||||
{error, missing_body_terminator}
|
||||
N > 0 ->
|
||||
parse_body(Rest, State#bstate{acc = [Ch | Acc], remaining = N - 1});
|
||||
true ->
|
||||
{error, missing_body_terminator}
|
||||
end.
|
||||
|
||||
initial_state() ->
|
||||
|
|
@ -186,36 +186,36 @@ initial_state() ->
|
|||
|
||||
parse(Rest, {headers, HState}) ->
|
||||
case parse_headers(Rest, HState) of
|
||||
{more, HState1} ->
|
||||
{more, {headers, HState1}};
|
||||
{ok, Command, Headers, Rest1} ->
|
||||
parse(Rest1, #stomp_frame{command = Command,
|
||||
headers = Headers,
|
||||
body = initial_body_state(Headers)});
|
||||
E = {error, _} ->
|
||||
E
|
||||
{more, HState1} ->
|
||||
{more, {headers, HState1}};
|
||||
{ok, Command, Headers, Rest1} ->
|
||||
parse(Rest1, #stomp_frame{command = Command,
|
||||
headers = Headers,
|
||||
body = initial_body_state(Headers)});
|
||||
E = {error, _} ->
|
||||
E
|
||||
end;
|
||||
parse(Rest, Frame = #stomp_frame{body = BState}) ->
|
||||
case parse_body(Rest, BState) of
|
||||
{more, BState1} ->
|
||||
{more, Frame#stomp_frame{body = BState1}};
|
||||
{ok, Body, Rest1} ->
|
||||
{ok, Frame#stomp_frame{body = Body}, Rest1};
|
||||
E = {error, _} ->
|
||||
E
|
||||
{more, BState1} ->
|
||||
{more, Frame#stomp_frame{body = BState1}};
|
||||
{ok, Body, Rest1} ->
|
||||
{ok, Frame#stomp_frame{body = Body}, Rest1};
|
||||
E = {error, _} ->
|
||||
E
|
||||
end.
|
||||
|
||||
serialize(#stomp_frame{command = Command,
|
||||
headers = Headers,
|
||||
body = Body}) ->
|
||||
headers = Headers,
|
||||
body = Body}) ->
|
||||
Len = length(Body),
|
||||
[Command, $\n,
|
||||
lists:map(fun serialize_header/1, lists:keydelete("content-length", 1, Headers)),
|
||||
if
|
||||
Len > 0 ->
|
||||
["content-length:", integer_to_list(length(Body)), $\n];
|
||||
true ->
|
||||
[]
|
||||
Len > 0 ->
|
||||
["content-length:", integer_to_list(length(Body)), $\n];
|
||||
true ->
|
||||
[]
|
||||
end,
|
||||
$\n,
|
||||
Body,
|
||||
|
|
|
|||
Loading…
Reference in New Issue