2020-07-15 08:19:24 +08:00
|
|
|
%% This Source Code Form is subject to the terms of the Mozilla Public
|
|
|
|
%% License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
|
|
%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
2015-08-10 22:21:13 +08:00
|
|
|
%%
|
2024-01-02 11:02:20 +08:00
|
|
|
%% Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term “Broadcom” refers to Broadcom Inc. and/or its subsidiaries. All rights reserved.
|
2015-08-10 22:21:13 +08:00
|
|
|
%%
|
|
|
|
|
2016-07-22 20:58:55 +08:00
|
|
|
-module(cowboy_websocket_SUITE).
|
|
|
|
|
|
|
|
-compile(export_all).
|
|
|
|
|
|
|
|
-include_lib("common_test/include/ct.hrl").
|
2015-08-10 22:21:13 +08:00
|
|
|
-include_lib("eunit/include/eunit.hrl").
|
|
|
|
|
2016-07-22 20:58:55 +08:00
|
|
|
all() ->
|
2019-04-22 23:11:06 +08:00
|
|
|
[{group, integration},
|
|
|
|
{group, default_login_enabled},
|
|
|
|
{group, default_login_disabled}].
|
2017-10-23 17:24:00 +08:00
|
|
|
|
|
|
|
groups() ->
|
2016-07-22 20:58:55 +08:00
|
|
|
[
|
2019-04-22 23:11:06 +08:00
|
|
|
{integration, [],
|
2017-10-23 17:24:00 +08:00
|
|
|
[
|
2019-04-22 23:11:06 +08:00
|
|
|
connection_succeeds,
|
|
|
|
connection_fails,
|
2017-10-23 17:24:00 +08:00
|
|
|
pubsub,
|
|
|
|
pubsub_binary,
|
2022-12-29 08:11:00 +08:00
|
|
|
sub_non_existent,
|
2017-10-23 17:24:00 +08:00
|
|
|
disconnect,
|
2025-09-24 20:29:49 +08:00
|
|
|
http_auth,
|
|
|
|
wss_http2
|
2019-04-22 23:11:06 +08:00
|
|
|
]},
|
|
|
|
%% rabbitmq/rabbitmq-web-stomp#110
|
|
|
|
{default_login_enabled, [],
|
|
|
|
[
|
|
|
|
connection_with_explicitly_provided_correct_credentials,
|
|
|
|
connection_with_default_login_succeeds
|
|
|
|
]},
|
|
|
|
%% rabbitmq/rabbitmq-web-stomp#110
|
|
|
|
{default_login_disabled, [],
|
|
|
|
[
|
|
|
|
connection_with_explicitly_provided_correct_credentials,
|
|
|
|
connection_without_credentials_fails
|
|
|
|
]}
|
|
|
|
].
|
2016-07-22 20:58:55 +08:00
|
|
|
|
|
|
|
init_per_suite(Config) ->
|
|
|
|
Config1 = rabbit_ct_helpers:set_config(Config,
|
2017-10-23 17:24:00 +08:00
|
|
|
[{rmq_nodename_suffix, ?MODULE},
|
|
|
|
{protocol, "ws"}]),
|
2016-07-22 20:58:55 +08:00
|
|
|
rabbit_ct_helpers:log_environment(),
|
2025-09-24 20:29:49 +08:00
|
|
|
Config2 = rabbit_ct_helpers:run_setup_steps(Config1),
|
|
|
|
{rmq_certsdir, CertsDir} = proplists:lookup(rmq_certsdir, Config2),
|
|
|
|
Config3 = rabbit_ct_helpers:merge_app_env(
|
|
|
|
Config2,
|
|
|
|
{rabbitmq_web_stomp,
|
|
|
|
[{ssl_config,
|
|
|
|
[{cacertfile, filename:join([CertsDir, "testca", "cacert.pem"])},
|
|
|
|
{certfile, filename:join([CertsDir, "server", "cert.pem"])},
|
|
|
|
{keyfile, filename:join([CertsDir, "server", "key.pem"])},
|
|
|
|
%% We only want to ensure HTTP/2 Websocket is working.
|
|
|
|
{fail_if_no_peer_cert, false},
|
|
|
|
{versions, ['tlsv1.3']},
|
|
|
|
%% We hard code this port number here because it will be computed later by
|
|
|
|
%% rabbit_ct_broker_helpers:init_tcp_port_numbers/3 when we start the broker.
|
|
|
|
%% (The alternative is to first start the broker, stop the rabbitmq_web_amqp app,
|
|
|
|
%% configure tls_config, and then start the app again.)
|
|
|
|
{port, 21014}
|
|
|
|
]}]}),
|
|
|
|
rabbit_ct_helpers:run_setup_steps(Config3,
|
2016-09-05 22:09:06 +08:00
|
|
|
rabbit_ct_broker_helpers:setup_steps()).
|
2016-07-22 20:58:55 +08:00
|
|
|
|
|
|
|
end_per_suite(Config) ->
|
2016-09-05 22:09:06 +08:00
|
|
|
rabbit_ct_helpers:run_teardown_steps(Config,
|
|
|
|
rabbit_ct_broker_helpers:teardown_steps()).
|
2016-07-22 20:58:55 +08:00
|
|
|
|
|
|
|
init_per_testcase(pubsub_binary, Config) ->
|
|
|
|
rabbit_ws_test_util:update_app_env(Config, ws_frame, binary),
|
|
|
|
Config;
|
|
|
|
init_per_testcase(http_auth, Config) ->
|
|
|
|
rabbit_ws_test_util:update_app_env(Config, use_http_auth, true),
|
|
|
|
Config;
|
|
|
|
init_per_testcase(_, Config) -> Config.
|
|
|
|
|
|
|
|
end_per_testcase(pubsub_binary, Config) ->
|
|
|
|
rabbit_ws_test_util:update_app_env(Config, ws_frame, text),
|
|
|
|
Config;
|
|
|
|
end_per_testcase(http_auth, Config) ->
|
|
|
|
rabbit_ws_test_util:update_app_env(Config, use_http_auth, false),
|
|
|
|
Config;
|
|
|
|
end_per_testcase(_, Config) -> Config.
|
|
|
|
|
|
|
|
|
2019-04-22 23:11:06 +08:00
|
|
|
connection_succeeds(Config) ->
|
2016-07-22 20:58:55 +08:00
|
|
|
PortStr = rabbit_ws_test_util:get_web_stomp_port_str(Config),
|
2017-10-23 17:24:00 +08:00
|
|
|
Protocol = ?config(protocol, Config),
|
|
|
|
WS = rfc6455_client:new(Protocol ++ "://127.0.0.1:" ++ PortStr ++ "/ws", self()),
|
2015-08-10 22:21:13 +08:00
|
|
|
{ok, _} = rfc6455_client:open(WS),
|
|
|
|
{close, _} = rfc6455_client:close(WS),
|
|
|
|
ok.
|
|
|
|
|
2019-04-22 23:11:06 +08:00
|
|
|
connection_fails(Config) ->
|
|
|
|
PortStr = rabbit_ws_test_util:get_web_stomp_port_str(Config),
|
|
|
|
Protocol = ?config(protocol, Config),
|
|
|
|
WS = rfc6455_client:new(Protocol ++ "://127.0.0.1:" ++ PortStr ++ "/ws", self()),
|
|
|
|
{ok, _} = rfc6455_client:open(WS),
|
|
|
|
ok = raw_send(WS, "CONNECT", [{"login", "uncorrect_$55"}, {"passcode", "uncorrect_$88"}]),
|
2023-06-04 05:10:35 +08:00
|
|
|
{<<"ERROR">>, _, <<"Access refused for user 'uncorrect_$55'">>} = raw_recv(WS),
|
2019-04-22 23:11:06 +08:00
|
|
|
{close, _} = rfc6455_client:close(WS),
|
|
|
|
ok.
|
|
|
|
|
|
|
|
connection_with_explicitly_provided_correct_credentials(Config) ->
|
|
|
|
PortStr = rabbit_ws_test_util:get_web_stomp_port_str(Config),
|
|
|
|
Protocol = ?config(protocol, Config),
|
|
|
|
WS = rfc6455_client:new(Protocol ++ "://127.0.0.1:" ++ PortStr ++ "/ws", self()),
|
|
|
|
{ok, _} = rfc6455_client:open(WS),
|
|
|
|
ok = raw_send(WS, "CONNECT", [{"login", "guest"}, {"passcode", "guest"}]),
|
|
|
|
|
|
|
|
{<<"CONNECTED">>, _, <<>>} = raw_recv(WS),
|
|
|
|
{close, _} = rfc6455_client:close(WS),
|
|
|
|
ok.
|
|
|
|
|
|
|
|
connection_with_default_login_succeeds(Config) ->
|
|
|
|
PortStr = rabbit_ws_test_util:get_web_stomp_port_str(Config),
|
|
|
|
Protocol = ?config(protocol, Config),
|
|
|
|
WS = rfc6455_client:new(Protocol ++ "://127.0.0.1:" ++ PortStr ++ "/ws", self()),
|
|
|
|
{ok, _} = rfc6455_client:open(WS),
|
|
|
|
ok = raw_send(WS, "CONNECT", []),
|
|
|
|
|
|
|
|
{<<"CONNECTED">>, _, <<>>} = raw_recv(WS),
|
|
|
|
{close, _} = rfc6455_client:close(WS),
|
|
|
|
ok.
|
|
|
|
connection_without_credentials_fails(Config) ->
|
|
|
|
ok = rabbit_ct_broker_helpers:rpc(Config, 0,
|
|
|
|
application, set_env,
|
|
|
|
[rabbitmq_stomp, default_user, undefined]),
|
|
|
|
|
|
|
|
PortStr = rabbit_ws_test_util:get_web_stomp_port_str(Config),
|
|
|
|
Protocol = ?config(protocol, Config),
|
|
|
|
WS = rfc6455_client:new(Protocol ++ "://127.0.0.1:" ++ PortStr ++ "/ws", self()),
|
|
|
|
|
|
|
|
{ok, _} = rfc6455_client:open(WS),
|
|
|
|
ok = raw_send(WS, "CONNECT"),
|
|
|
|
|
|
|
|
{close, _} = raw_recv(WS),
|
|
|
|
ok.
|
|
|
|
|
|
|
|
raw_send(WS, Command) ->
|
|
|
|
raw_send(WS, Command, [], <<>>).
|
2015-08-10 22:21:13 +08:00
|
|
|
|
|
|
|
raw_send(WS, Command, Headers) ->
|
|
|
|
raw_send(WS, Command, Headers, <<>>).
|
|
|
|
raw_send(WS, Command, Headers, Body) ->
|
|
|
|
Frame = stomp:marshal(Command, Headers, Body),
|
|
|
|
rfc6455_client:send(WS, Frame).
|
|
|
|
|
|
|
|
raw_recv(WS) ->
|
2019-04-22 23:11:06 +08:00
|
|
|
case rfc6455_client:recv(WS) of
|
|
|
|
{ok, P} -> stomp:unmarshal(P);
|
|
|
|
Other -> Other
|
|
|
|
end.
|
2015-08-10 22:21:13 +08:00
|
|
|
|
2016-07-22 20:58:55 +08:00
|
|
|
pubsub(Config) ->
|
|
|
|
PortStr = rabbit_ws_test_util:get_web_stomp_port_str(Config),
|
2017-10-23 17:24:00 +08:00
|
|
|
Protocol = ?config(protocol, Config),
|
|
|
|
WS = rfc6455_client:new(Protocol ++ "://127.0.0.1:" ++ PortStr ++ "/ws", self()),
|
2015-08-10 22:21:13 +08:00
|
|
|
{ok, _} = rfc6455_client:open(WS),
|
2017-06-09 06:40:30 +08:00
|
|
|
ok = raw_send(WS, "CONNECT", [{"login", "guest"}, {"passcode", "guest"}]),
|
2015-08-10 22:21:13 +08:00
|
|
|
|
|
|
|
{<<"CONNECTED">>, _, <<>>} = raw_recv(WS),
|
|
|
|
|
2017-05-31 16:59:13 +08:00
|
|
|
Dst = "/topic/test-" ++ stomp:list_to_hex(binary_to_list(crypto:strong_rand_bytes(8))),
|
2015-08-10 22:21:13 +08:00
|
|
|
|
|
|
|
ok = raw_send(WS, "SUBSCRIBE", [{"destination", Dst},
|
|
|
|
{"id", "s0"}]),
|
|
|
|
|
2017-06-09 06:40:30 +08:00
|
|
|
CustHdr1K = "x-custom-hdr-1",
|
|
|
|
CustHdr1 = {CustHdr1K, "value1"},
|
|
|
|
CustHdr2K = "x-custom-hdr-2",
|
|
|
|
CustHdr2 = {CustHdr2K, "value2"},
|
|
|
|
CustHdr3K = "custom-hdr-3",
|
|
|
|
CustHdr3 = {CustHdr3K, "value3"},
|
|
|
|
ok = raw_send(WS, "SEND", [{"destination", Dst}, {"content-length", "3"},
|
|
|
|
CustHdr1, CustHdr2, CustHdr3], <<"a\x00a">>),
|
2015-08-10 22:21:13 +08:00
|
|
|
|
|
|
|
{<<"MESSAGE">>, H, <<"a\x00a">>} = raw_recv(WS),
|
2017-06-09 06:40:30 +08:00
|
|
|
|
2015-08-10 22:21:13 +08:00
|
|
|
Dst = binary_to_list(proplists:get_value(<<"destination">>, H)),
|
2017-06-09 06:40:30 +08:00
|
|
|
CustHdr1 = {CustHdr1K, binary_to_list(proplists:get_value(list_to_binary(CustHdr1K), H))},
|
|
|
|
CustHdr2 = {CustHdr2K, binary_to_list(proplists:get_value(list_to_binary(CustHdr2K), H))},
|
|
|
|
CustHdr3 = {CustHdr3K, binary_to_list(proplists:get_value(list_to_binary(CustHdr3K), H))},
|
2015-08-10 22:21:13 +08:00
|
|
|
|
|
|
|
{close, _} = rfc6455_client:close(WS),
|
|
|
|
ok.
|
|
|
|
|
2022-12-29 08:11:00 +08:00
|
|
|
%% Issue #6737
|
|
|
|
sub_non_existent(Config) ->
|
|
|
|
PortStr = rabbit_ws_test_util:get_web_stomp_port_str(Config),
|
|
|
|
Protocol = ?config(protocol, Config),
|
|
|
|
WS = rfc6455_client:new(Protocol ++ "://127.0.0.1:" ++ PortStr ++ "/ws", self()),
|
|
|
|
{ok, _} = rfc6455_client:open(WS),
|
|
|
|
ok = raw_send(WS, "CONNECT", [{"login", "guest"}, {"passcode", "guest"}]),
|
|
|
|
|
|
|
|
{<<"CONNECTED">>, _, <<>>} = raw_recv(WS),
|
|
|
|
|
|
|
|
ok = raw_send(WS, "SUBSCRIBE", [{"destination", "/exchange/doesnotexist"},
|
|
|
|
{"id", "s0"}]),
|
|
|
|
|
|
|
|
{<<"ERROR">>, [{<<"message">>,<<"not_found">>} | _Tail ], <<"NOT_FOUND - no exchange 'doesnotexist' in vhost '/'">>} = raw_recv(WS),
|
|
|
|
|
2023-01-10 05:16:42 +08:00
|
|
|
{close, _} = raw_recv(WS),
|
2022-12-29 08:11:00 +08:00
|
|
|
ok.
|
|
|
|
|
2015-08-10 22:21:13 +08:00
|
|
|
|
2015-09-09 01:50:13 +08:00
|
|
|
raw_send_binary(WS, Command, Headers) ->
|
|
|
|
raw_send_binary(WS, Command, Headers, <<>>).
|
|
|
|
raw_send_binary(WS, Command, Headers, Body) ->
|
|
|
|
Frame = stomp:marshal(Command, Headers, Body),
|
|
|
|
rfc6455_client:send_binary(WS, Frame).
|
|
|
|
|
|
|
|
raw_recv_binary(WS) ->
|
|
|
|
{binary, P} = rfc6455_client:recv(WS),
|
|
|
|
stomp:unmarshal(P).
|
|
|
|
|
|
|
|
|
2016-07-22 20:58:55 +08:00
|
|
|
pubsub_binary(Config) ->
|
|
|
|
PortStr = rabbit_ws_test_util:get_web_stomp_port_str(Config),
|
2017-10-23 17:24:00 +08:00
|
|
|
Protocol = ?config(protocol, Config),
|
|
|
|
WS = rfc6455_client:new(Protocol ++ "://127.0.0.1:" ++ PortStr ++ "/ws", self()),
|
2015-09-09 01:50:13 +08:00
|
|
|
{ok, _} = rfc6455_client:open(WS),
|
|
|
|
ok = raw_send(WS, "CONNECT", [{"login","guest"}, {"passcode", "guest"}]),
|
|
|
|
|
|
|
|
{<<"CONNECTED">>, _, <<>>} = raw_recv_binary(WS),
|
|
|
|
|
2017-05-31 16:59:13 +08:00
|
|
|
Dst = "/topic/test-" ++ stomp:list_to_hex(binary_to_list(crypto:strong_rand_bytes(8))),
|
2015-09-09 01:50:13 +08:00
|
|
|
|
|
|
|
ok = raw_send(WS, "SUBSCRIBE", [{"destination", Dst},
|
|
|
|
{"id", "s0"}]),
|
|
|
|
|
|
|
|
ok = raw_send(WS, "SEND", [{"destination", Dst},
|
|
|
|
{"content-length", "3"}], <<"a\x00a">>),
|
|
|
|
|
|
|
|
{<<"MESSAGE">>, H, <<"a\x00a">>} = raw_recv_binary(WS),
|
|
|
|
Dst = binary_to_list(proplists:get_value(<<"destination">>, H)),
|
|
|
|
|
2016-07-22 20:58:55 +08:00
|
|
|
{close, _} = rfc6455_client:close(WS).
|
2015-09-09 01:50:13 +08:00
|
|
|
|
2016-07-22 20:58:55 +08:00
|
|
|
disconnect(Config) ->
|
|
|
|
PortStr = rabbit_ws_test_util:get_web_stomp_port_str(Config),
|
2017-10-23 17:24:00 +08:00
|
|
|
Protocol = ?config(protocol, Config),
|
|
|
|
WS = rfc6455_client:new(Protocol ++ "://127.0.0.1:" ++ PortStr ++ "/ws", self()),
|
2015-08-10 22:21:13 +08:00
|
|
|
{ok, _} = rfc6455_client:open(WS),
|
|
|
|
ok = raw_send(WS, "CONNECT", [{"login","guest"}, {"passcode", "guest"}]),
|
|
|
|
|
|
|
|
{<<"CONNECTED">>, _, <<>>} = raw_recv(WS),
|
|
|
|
|
|
|
|
ok = raw_send(WS, "DISCONNECT", []),
|
|
|
|
{close, {1000, _}} = rfc6455_client:recv(WS),
|
|
|
|
|
|
|
|
ok.
|
2016-03-09 23:32:44 +08:00
|
|
|
|
2016-07-22 20:58:55 +08:00
|
|
|
http_auth(Config) ->
|
2016-03-09 23:32:44 +08:00
|
|
|
%% Intentionally put bad credentials in the CONNECT frame,
|
|
|
|
%% and good credentials in the Authorization header, to
|
|
|
|
%% confirm that the right credentials are picked.
|
2016-07-22 20:58:55 +08:00
|
|
|
PortStr = rabbit_ws_test_util:get_web_stomp_port_str(Config),
|
2017-10-23 17:24:00 +08:00
|
|
|
Protocol = ?config(protocol, Config),
|
|
|
|
WS = rfc6455_client:new(Protocol ++ "://127.0.0.1:" ++ PortStr ++ "/ws", self(),
|
2016-03-09 23:32:44 +08:00
|
|
|
[{login, "guest"}, {passcode, "guest"}]),
|
|
|
|
{ok, _} = rfc6455_client:open(WS),
|
|
|
|
ok = raw_send(WS, "CONNECT", [{"login", "bad"}, {"passcode", "bad"}]),
|
|
|
|
{<<"CONNECTED">>, _, <<>>} = raw_recv(WS),
|
|
|
|
{close, _} = rfc6455_client:close(WS),
|
|
|
|
|
|
|
|
%% Confirm that if no Authorization header is provided,
|
|
|
|
%% the default STOMP plugin credentials are used. We
|
|
|
|
%% expect an error because the default credentials are
|
|
|
|
%% left undefined.
|
2016-12-07 00:33:48 +08:00
|
|
|
ok = rabbit_ct_broker_helpers:rpc(Config, 0,
|
|
|
|
application, set_env,
|
|
|
|
[rabbitmq_stomp, default_user,
|
|
|
|
[{login, "bad-default"}, {passcode, "bad-default"}]
|
|
|
|
]),
|
|
|
|
|
2017-10-23 17:24:00 +08:00
|
|
|
Protocol = ?config(protocol, Config),
|
|
|
|
WS2 = rfc6455_client:new(Protocol ++ "://127.0.0.1:" ++ PortStr ++ "/ws", self()),
|
2016-03-09 23:32:44 +08:00
|
|
|
{ok, _} = rfc6455_client:open(WS2),
|
|
|
|
ok = raw_send(WS2, "CONNECT", [{"login", "bad"}, {"passcode", "bad"}]),
|
|
|
|
{<<"ERROR">>, _, _} = raw_recv(WS2),
|
2016-12-07 00:33:48 +08:00
|
|
|
{close, _} = rfc6455_client:close(WS2),
|
|
|
|
|
|
|
|
%% Confirm that we can connect if the default STOMP
|
|
|
|
%% credentials are used.
|
|
|
|
ok = rabbit_ct_broker_helpers:rpc(Config, 0,
|
|
|
|
application, set_env,
|
|
|
|
[rabbitmq_stomp, default_user,
|
|
|
|
[{login, "guest"}, {passcode, "guest"}]
|
|
|
|
]),
|
|
|
|
|
2017-10-23 17:24:00 +08:00
|
|
|
Protocol = ?config(protocol, Config),
|
|
|
|
WS3 = rfc6455_client:new(Protocol ++ "://127.0.0.1:" ++ PortStr ++ "/ws", self()),
|
2016-12-07 00:33:48 +08:00
|
|
|
{ok, _} = rfc6455_client:open(WS3),
|
|
|
|
ok = raw_send(WS3, "CONNECT", [{"login", "bad"}, {"passcode", "bad"}]),
|
|
|
|
{<<"CONNECTED">>, _, <<>>} = raw_recv(WS3),
|
|
|
|
{close, _} = rfc6455_client:close(WS3),
|
|
|
|
|
|
|
|
ok.
|
2025-09-24 20:29:49 +08:00
|
|
|
|
|
|
|
wss_http2(Config) ->
|
|
|
|
{ok, _} = application:ensure_all_started(gun),
|
|
|
|
Port = rabbit_ct_broker_helpers:get_node_config(Config, 0, tcp_port_web_stomp_tls),
|
|
|
|
{ok, ConnPid} = gun:open("localhost", Port, #{
|
|
|
|
transport => tls,
|
|
|
|
tls_opts => [{verify, verify_none}],
|
|
|
|
protocols => [http2],
|
|
|
|
http2_opts => #{notify_settings_changed => true},
|
|
|
|
ws_opts => #{protocols => [{<<"v12.stomp">>, gun_ws_h}]}
|
|
|
|
}),
|
|
|
|
{ok, http2} = gun:await_up(ConnPid),
|
|
|
|
{notify, settings_changed, #{enable_connect_protocol := true}}
|
|
|
|
= gun:await(ConnPid, undefined),
|
|
|
|
StreamRef = gun:ws_upgrade(ConnPid, "/ws", []),
|
|
|
|
{upgrade, [<<"websocket">>], _} = gun:await(ConnPid, StreamRef),
|
|
|
|
gun:ws_send(ConnPid, StreamRef, {text, stomp:marshal("CONNECT", [{"login","guest"}, {"passcode", "guest"}])}),
|
|
|
|
{ws, {text, P}} = gun:await(ConnPid, StreamRef),
|
|
|
|
{<<"CONNECTED">>, _, <<>>} = stomp:unmarshal(P),
|
|
|
|
ok.
|