Skip to content

Commit 269ac42

Browse files
committed
Choose default fields on the server side
and return 400 errors for bad requests
1 parent 29e814c commit 269ac42

File tree

6 files changed

+321
-126
lines changed

6 files changed

+321
-126
lines changed

deps/rabbit/Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ LOCAL_DEPS = sasl os_mon inets compiler public_key crypto ssl syntax_tools xmerl
136136

137137
BUILD_DEPS = rabbitmq_cli
138138
DEPS = ranch rabbit_common amqp10_common rabbitmq_prelaunch ra sysmon_handler stdout_formatter recon redbug observer_cli osiris syslog systemd seshat khepri khepri_mnesia_migration
139-
TEST_DEPS = rabbitmq_ct_helpers rabbitmq_ct_client_helpers meck proper amqp_client amqp10_client rabbitmq_amqp1_0
139+
TEST_DEPS = rabbitmq_ct_helpers rabbitmq_ct_client_helpers meck proper amqp_client rabbitmq_amqp_client rabbitmq_amqp1_0
140140

141141
PLT_APPS += mnesia
142142

deps/rabbit/src/rabbit_amqp_management.erl

Lines changed: 137 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -10,32 +10,39 @@ handle_request(Request, Vhost, User, ConnectionPid) ->
1010
ReqSections = amqp10_framing:decode_bin(Request),
1111
?DEBUG("~s Inbound request:~n ~tp",
1212
[?MODULE, [amqp10_framing:pprint(Section) || Section <- ReqSections]]),
13+
1314
{#'v1_0.properties'{
1415
message_id = MessageId,
1516
to = {utf8, HttpRequestTarget},
1617
subject = {utf8, HttpMethod},
1718
%% see Link Pair CS 01 §2.1
1819
%% https://docs.oasis-open.org/amqp/linkpair/v1.0/cs01/linkpair-v1.0-cs01.html#_Toc51331305
1920
reply_to = {utf8, <<"$me">>}},
20-
ReqPayload
21+
ReqBody
2122
} = decode_req(ReqSections, {undefined, undefined}),
22-
{PathSegments, QueryMap} = parse_uri(HttpRequestTarget),
23-
{RespProps0,
24-
RespAppProps0 = #'v1_0.application_properties'{content = C},
25-
RespBody} = handle_http_req(HttpMethod,
26-
PathSegments,
27-
QueryMap,
28-
ReqPayload,
29-
Vhost,
30-
User,
31-
ConnectionPid),
32-
RespProps = RespProps0#'v1_0.properties'{
33-
%% "To associate a response with a request, the correlation-id value of the response
34-
%% properties MUST be set to the message-id value of the request properties."
35-
%% [HTTP over AMQP WD 06 §5.1]
36-
correlation_id = MessageId},
37-
RespAppProps = RespAppProps0#'v1_0.application_properties'{
38-
content = [{{utf8, <<"http:response">>}, {utf8, <<"1.1">>}} | C]},
23+
24+
{StatusCode,
25+
RespAppProps0,
26+
RespBody} = try {PathSegments, QueryMap} = parse_uri(HttpRequestTarget),
27+
handle_http_req(HttpMethod,
28+
PathSegments,
29+
QueryMap,
30+
ReqBody,
31+
Vhost,
32+
User,
33+
ConnectionPid)
34+
catch throw:{StatusCode0, Explanation} ->
35+
{StatusCode0, [], {utf8, unicode:characters_to_binary(Explanation)}}
36+
end,
37+
38+
RespProps = #'v1_0.properties'{
39+
subject = {utf8, StatusCode},
40+
%% "To associate a response with a request, the correlation-id value of the response
41+
%% properties MUST be set to the message-id value of the request properties."
42+
%% [HTTP over AMQP WD 06 §5.1]
43+
correlation_id = MessageId},
44+
RespAppProps = #'v1_0.application_properties'{
45+
content = [{{utf8, <<"http:response">>}, {utf8, <<"1.1">>}} | RespAppProps0]},
3946
RespDataSect = #'v1_0.amqp_value'{content = RespBody},
4047
RespSections = [RespProps, RespAppProps, RespDataSect],
4148
[amqp10_framing:encode_bin(Sect) || Sect <- RespSections].
@@ -66,10 +73,7 @@ handle_http_req(<<"PUT">>,
6673
Q0 = amqqueue:new(QName, none, Durable, AutoDelete, Owner,
6774
QArgs, Vhost, #{user => Username}, QType),
6875
{new, _Q} = rabbit_queue_type:declare(Q0, node()),
69-
Props = #'v1_0.properties'{subject = {utf8, <<"201">>}},
70-
AppProps = #'v1_0.application_properties'{content = []},
71-
RespPayload = null,
72-
{Props, AppProps, RespPayload};
76+
{<<"201">>, [], null};
7377

7478
handle_http_req(<<"PUT">>,
7579
[<<"exchanges">>, XNameBinQ],
@@ -94,10 +98,7 @@ handle_http_req(<<"PUT">>,
9498
Internal,
9599
XArgs,
96100
Username),
97-
Props = #'v1_0.properties'{subject = {utf8, <<"201">>} },
98-
AppProps = #'v1_0.application_properties'{content = []},
99-
RespPayload = null,
100-
{Props, AppProps, RespPayload};
101+
{<<"201">>, [], null};
101102

102103
handle_http_req(<<"DELETE">>,
103104
[<<"queues">>, QNameBinQ, <<"messages">>],
@@ -113,10 +114,8 @@ handle_http_req(<<"DELETE">>,
113114
fun (Q) ->
114115
rabbit_queue_type:purge(Q)
115116
end),
116-
Props = #'v1_0.properties'{subject = {utf8, <<"200">>}},
117-
AppProps = #'v1_0.application_properties'{content = []},
118117
RespPayload = {map, [{{utf8, <<"message_count">>}, {ulong, NumMsgs}}]},
119-
{Props, AppProps, RespPayload};
118+
{<<"200">>, [], RespPayload};
120119

121120
handle_http_req(<<"DELETE">>,
122121
[<<"queues">>, QNameBinQ],
@@ -128,10 +127,8 @@ handle_http_req(<<"DELETE">>,
128127
QNameBin = uri_string:unquote(QNameBinQ),
129128
QName = rabbit_misc:r(Vhost, queue, QNameBin),
130129
{ok, NumMsgs} = rabbit_amqqueue:delete_with(QName, ConnPid, false, false, Username, true),
131-
Props = #'v1_0.properties'{subject = {utf8, <<"200">>}},
132-
AppProps = #'v1_0.application_properties'{content = []},
133130
RespPayload = {map, [{{utf8, <<"message_count">>}, {ulong, NumMsgs}}]},
134-
{Props, AppProps, RespPayload};
131+
{<<"200">>, [], RespPayload};
135132

136133
handle_http_req(<<"DELETE">>,
137134
[<<"exchanges">>, XNameBinQ],
@@ -150,10 +147,7 @@ handle_http_req(<<"DELETE">>,
150147
%% %% TODO return deletion failure
151148
%% {error, in_use} ->
152149
end,
153-
Props = #'v1_0.properties'{subject = {utf8, <<"204">>}},
154-
AppProps = #'v1_0.application_properties'{content = []},
155-
RespPayload = null,
156-
{Props, AppProps, RespPayload};
150+
{<<"204">>, [], null};
157151

158152
handle_http_req(<<"POST">>,
159153
[<<"bindings">>],
@@ -179,12 +173,9 @@ handle_http_req(<<"POST">>,
179173
args = Args},
180174
%%TODO If the binding already exists, return 303 with location.
181175
ok = rabbit_binding:add(Binding, Username),
182-
Props = #'v1_0.properties'{subject = {utf8, <<"201">>}},
183176
Location = compose_binding_uri(SrcXNameBin, DstKind, DstNameBin, BindingKey, Args),
184-
AppProps = #'v1_0.application_properties'{
185-
content = [{{utf8, <<"location">>}, {utf8, Location}}]},
186-
RespPayload = null,
187-
{Props, AppProps, RespPayload};
177+
AppProps = [{{utf8, <<"location">>}, {utf8, Location}}],
178+
{<<"201">>, AppProps, null};
188179

189180
handle_http_req(<<"DELETE">>,
190181
[<<"bindings">>, BindingSegment],
@@ -203,10 +194,7 @@ handle_http_req(<<"DELETE">>,
203194
false ->
204195
ok
205196
end,
206-
Props = #'v1_0.properties'{subject = {utf8, <<"204">>}},
207-
AppProps = #'v1_0.application_properties'{content = []},
208-
RespPayload = null,
209-
{Props, AppProps, RespPayload};
197+
{<<"204">>, [], null};
210198

211199
handle_http_req(<<"GET">>,
212200
[<<"bindings">>],
@@ -222,55 +210,79 @@ handle_http_req(<<"GET">>,
222210
#{<<"dstq">> := DstQ} ->
223211
{queue, DstQ};
224212
_ ->
225-
%%TODO return 400
226-
exit({bad_destination, QueryMap})
213+
throw(<<"400">>, "missing 'dste' or 'dstq' in query: ~tp", QueryMap)
227214
end,
228215
SrcXName = rabbit_misc:r(Vhost, exchange, SrcXNameBin),
229216
DstName = rabbit_misc:r(Vhost, DstKind, DstNameBin),
230217
Bindings0 = rabbit_binding:list_for_source_and_destination(SrcXName, DstName),
231218
Bindings = [B || B = #binding{key = K} <- Bindings0, K =:= Key],
232219
RespPayload = encode_bindings(Bindings),
233-
Props = #'v1_0.properties'{subject = {utf8, <<"200">>}},
234-
AppProps = #'v1_0.application_properties'{content = []},
235-
{Props, AppProps, RespPayload}.
220+
{<<"200">>, [], RespPayload}.
236221

237222
decode_queue({map, KVList}) ->
238-
lists:foldl(
239-
fun({{utf8, <<"durable">>}, V}, Acc)
240-
when is_boolean(V) ->
241-
Acc#{durable => V};
242-
({{utf8, <<"exclusive">>}, V}, Acc)
243-
when is_boolean(V) ->
244-
Acc#{exclusive => V};
245-
({{utf8, <<"auto_delete">>}, V}, Acc)
246-
when is_boolean(V) ->
247-
Acc#{auto_delete => V};
248-
({{utf8, <<"arguments">>}, {map, List}}, Acc) ->
249-
Args = [{Key, longstr, V}
250-
|| {{utf8, Key = <<"x-", _/binary>>},
251-
{utf8, V}} <- List],
252-
Acc#{arguments => Args}
253-
end, #{}, KVList).
223+
M = lists:foldl(
224+
fun({{utf8, <<"durable">>}, V}, Acc)
225+
when is_boolean(V) ->
226+
Acc#{durable => V};
227+
({{utf8, <<"exclusive">>}, V}, Acc)
228+
when is_boolean(V) ->
229+
Acc#{exclusive => V};
230+
({{utf8, <<"auto_delete">>}, V}, Acc)
231+
when is_boolean(V) ->
232+
Acc#{auto_delete => V};
233+
({{utf8, <<"arguments">>}, {map, List}}, Acc) ->
234+
Args = lists:map(fun({{utf8, Key = <<"x-", _/binary>>}, {Type, Val}})
235+
when Type =:= utf8 orelse
236+
Type =:= symbol ->
237+
{Key, longstr, Val};
238+
(Arg) ->
239+
throw(<<"400">>,
240+
"unsupported queue argument ~tp",
241+
[Arg])
242+
end, List),
243+
Acc#{arguments => Args};
244+
(Prop, _Acc) ->
245+
throw(<<"400">>, "bad queue property ~tp", [Prop])
246+
end, #{}, KVList),
247+
Defaults = #{durable => true,
248+
exclusive => false,
249+
auto_delete => false,
250+
arguments => []},
251+
maps:merge(Defaults, M).
254252

255253
decode_exchange({map, KVList}) ->
256-
lists:foldl(
257-
fun({{utf8, <<"durable">>}, V}, Acc)
258-
when is_boolean(V) ->
259-
Acc#{durable => V};
260-
({{utf8, <<"auto_delete">>}, V}, Acc)
261-
when is_boolean(V) ->
262-
Acc#{auto_delete => V};
263-
({{utf8, <<"type">>}, {utf8, V}}, Acc) ->
264-
Acc#{type => V};
265-
({{utf8, <<"internal">>}, V}, Acc)
266-
when is_boolean(V) ->
267-
Acc#{internal => V};
268-
({{utf8, <<"arguments">>}, {map, List}}, Acc) ->
269-
Args = [{Key, longstr, V}
270-
|| {{utf8, Key = <<"x-", _/binary>>},
271-
{utf8, V}} <- List],
272-
Acc#{arguments => Args}
273-
end, #{}, KVList).
254+
M = lists:foldl(
255+
fun({{utf8, <<"durable">>}, V}, Acc)
256+
when is_boolean(V) ->
257+
Acc#{durable => V};
258+
({{utf8, <<"auto_delete">>}, V}, Acc)
259+
when is_boolean(V) ->
260+
Acc#{auto_delete => V};
261+
({{utf8, <<"type">>}, {utf8, V}}, Acc) ->
262+
Acc#{type => V};
263+
({{utf8, <<"internal">>}, V}, Acc)
264+
when is_boolean(V) ->
265+
Acc#{internal => V};
266+
({{utf8, <<"arguments">>}, {map, List}}, Acc) ->
267+
Args = lists:map(fun({{utf8, Key = <<"x-", _/binary>>}, {Type, Val}})
268+
when Type =:= utf8 orelse
269+
Type =:= symbol ->
270+
{Key, longstr, Val};
271+
(Arg) ->
272+
throw(<<"400">>,
273+
"unsupported exchange argument ~tp",
274+
[Arg])
275+
end, List),
276+
Acc#{arguments => Args};
277+
(Prop, _Acc) ->
278+
throw(<<"400">>, "bad exchange property ~tp", [Prop])
279+
end, #{}, KVList),
280+
Defaults = #{durable => true,
281+
auto_delete => false,
282+
type => <<"direct">>,
283+
internal => false,
284+
arguments => []},
285+
maps:merge(Defaults, M).
274286

275287
decode_binding({map, KVList}) ->
276288
lists:foldl(
@@ -283,9 +295,18 @@ decode_binding({map, KVList}) ->
283295
({{utf8, <<"binding_key">>}, {utf8, V}}, Acc) ->
284296
Acc#{binding_key => V};
285297
({{utf8, <<"arguments">>}, {map, List}}, Acc) ->
286-
Args = [mc_amqpl:to_091(Key, TypeVal)
287-
|| {{utf8, Key}, TypeVal} <- List],
288-
Acc#{arguments => Args}
298+
Args = lists:map(fun({{T, Key}, TypeVal})
299+
when T =:= utf8 orelse
300+
T =:= symbol ->
301+
mc_amqpl:to_091(Key, TypeVal);
302+
(Arg) ->
303+
throw(<<"400">>,
304+
"unsupported binding argument ~tp",
305+
[Arg])
306+
end, List),
307+
Acc#{arguments => Args};
308+
(Field, _Acc) ->
309+
throw(<<"400">>, "bad binding field ~tp", [Field])
289310
end, #{}, KVList).
290311

291312
encode_bindings(Bindings) ->
@@ -324,21 +345,29 @@ decode_req([_IgnoreSection | Rem], Acc) ->
324345
decode_req(Rem, Acc).
325346

326347
parse_uri(Uri) ->
327-
UriMap = #{path := Path} = uri_string:normalize(Uri, [return_map]),
328-
[<<>> | Segments] = binary:split(Path, <<"/">>, [global]),
329-
QueryMap = case maps:find(query, UriMap) of
330-
{ok, Query} ->
331-
case uri_string:dissect_query(Query) of
332-
{error, _, _} = Err ->
333-
%%TODO return 400
334-
exit(Err);
335-
QueryList ->
336-
maps:from_list(QueryList)
337-
end;
338-
error ->
339-
#{}
340-
end,
341-
{Segments, QueryMap}.
348+
case uri_string:normalize(Uri, [return_map]) of
349+
UriMap = #{path := Path} ->
350+
[<<>> | Segments] = binary:split(Path, <<"/">>, [global]),
351+
QueryMap = case maps:find(query, UriMap) of
352+
{ok, Query} ->
353+
case uri_string:dissect_query(Query) of
354+
QueryList
355+
when is_list(QueryList) ->
356+
maps:from_list(QueryList);
357+
{error, Atom, Term} ->
358+
throw(<<"400">>,
359+
"failed to dissect query '~ts': ~s ~tp",
360+
[Query, Atom, Term])
361+
end;
362+
error ->
363+
#{}
364+
end,
365+
{Segments, QueryMap};
366+
{error, Atom, Term} ->
367+
throw(<<"400">>,
368+
"failed to normalize URI '~ts': ~s ~tp",
369+
[Uri, Atom, Term])
370+
end.
342371

343372
compose_binding_uri(Src, DstKind, Dst, Key, Args) ->
344373
SrcQ = uri_string:quote(Src),
@@ -384,3 +413,9 @@ args_hash(Args)
384413
Bin = <<(erlang:phash2(Args, 1 bsl 32)):32>>,
385414
base64:encode(Bin, #{mode => urlsafe,
386415
padding => false}).
416+
417+
-spec throw(binary(), io:format(), [term()]) -> no_return().
418+
throw(StatusCode, Format, Data) ->
419+
Explanation = lists:flatten(io_lib:format(Format, Data)),
420+
rabbit_log:warning(Explanation),
421+
throw({StatusCode, Explanation}).

deps/rabbitmq_amqp_client/app.bzl

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,9 @@ def all_srcs(name = "all_srcs"):
2222
srcs = ["src/rabbitmq_amqp_client.erl"],
2323
)
2424
filegroup(name = "private_hdrs")
25-
filegroup(name = "public_hdrs")
25+
filegroup(name = "public_hdrs", srcs = [
26+
"include/rabbitmq_amqp_client.hrl",
27+
])
2628
filegroup(name = "priv")
2729
filegroup(
2830
name = "license_files",
@@ -63,7 +65,8 @@ def test_suite_beam_files(name = "test_suite_beam_files"):
6365
testonly = True,
6466
srcs = ["test/management_SUITE.erl"],
6567
outs = ["test/management_SUITE.beam"],
68+
hdrs = ["include/rabbitmq_amqp_client.hrl"],
6669
app_name = "rabbitmq_amqp_client",
6770
erlc_opts = "//:test_erlc_opts",
68-
deps = ["//deps/amqp10_common:erlang_app"],
71+
deps = ["//deps/amqp10_common:erlang_app", "//deps/rabbit_common:erlang_app"],
6972
)
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
-record(link_pair, {outgoing_link :: amqp10_client:link_ref(),
2+
incoming_link :: amqp10_client:link_ref()}).
3+
-type link_pair() :: #link_pair{}.

0 commit comments

Comments
 (0)