From d5f1a47e9ab758a51b23440eb72a0251527f3f7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Hoguin?= Date: Thu, 22 Oct 2020 18:48:06 +0200 Subject: Initial implementation of Websocket over HTTP/2 --- test/ws_SUITE.erl | 139 +++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 95 insertions(+), 44 deletions(-) (limited to 'test/ws_SUITE.erl') diff --git a/test/ws_SUITE.erl b/test/ws_SUITE.erl index c04acc2..201403e 100644 --- a/test/ws_SUITE.erl +++ b/test/ws_SUITE.erl @@ -22,19 +22,30 @@ %% ct. all() -> - [{group, ws}]. + [{group, http}, {group, http2}]. groups() -> - [{ws, [], ct_helper:all(?MODULE)}]. + Tests = ct_helper:all(?MODULE), + HTTP1Tests = [ + http10_upgrade_error, + http11_request_error, + http11_keepalive, + http11_keepalive_default_silence_pings + ], + [ + {http, [], Tests}, + {http2, [], Tests -- HTTP1Tests} + ]. init_per_suite(Config) -> Routes = [ {"/", ws_echo_h, []}, {"/reject", ws_reject_h, []} ], - {ok, _} = cowboy:start_clear(ws, [], #{env => #{ - dispatch => cowboy_router:compile([{'_', Routes}]) - }}), + {ok, _} = cowboy:start_clear(ws, [], #{ + enable_connect_protocol => true, + env => #{dispatch => cowboy_router:compile([{'_', Routes}])} + }), Port = ranch:get_port(ws), [{port, Port}|Config]. @@ -45,16 +56,37 @@ end_per_suite(_) -> await(Config) -> doc("Ensure gun:await/2 can be used to receive Websocket frames."), - {ok, ConnPid} = gun:open("localhost", config(port, Config)), - {ok, _} = gun:await_up(ConnPid), + Protocol = config(name, config(tc_group_properties, Config)), + {ok, ConnPid} = gun:open("localhost", config(port, Config), #{ + protocols => [Protocol], + http2_opts => #{notify_settings_changed => true} + }), + {ok, Protocol} = gun:await_up(ConnPid), + do_await_enable_connect_protocol(Protocol, ConnPid), StreamRef = gun:ws_upgrade(ConnPid, "/", []), {upgrade, [<<"websocket">>], _} = gun:await(ConnPid, StreamRef), Frame = {text, <<"Hello!">>}, - gun:ws_send(ConnPid, Frame), + gun:ws_send(ConnPid, StreamRef, Frame), {ws, Frame} = gun:await(ConnPid, StreamRef), gun:close(ConnPid). -error_http10_upgrade(Config) -> +headers_normalized_upgrade(Config) -> + doc("Headers passed to ws_upgrade are normalized before being used."), + Protocol = config(name, config(tc_group_properties, Config)), + {ok, ConnPid} = gun:open("localhost", config(port, Config), #{ + protocols => [Protocol], + http2_opts => #{notify_settings_changed => true} + }), + {ok, Protocol} = gun:await_up(ConnPid), + do_await_enable_connect_protocol(Protocol, ConnPid), + StreamRef = gun:ws_upgrade(ConnPid, "/", #{ + atom_header_name => <<"value">>, + "string_header_name" => <<"value">> + }), + {upgrade, [<<"websocket">>], _} = gun:await(ConnPid, StreamRef), + gun:close(ConnPid). + +http10_upgrade_error(Config) -> doc("Attempting to upgrade HTTP/1.0 to Websocket produces an error."), {ok, ConnPid} = gun:open("localhost", config(port, Config), #{ http_opts => #{version => 'HTTP/1.0'} @@ -70,28 +102,7 @@ error_http10_upgrade(Config) -> error(timeout) end. -headers_normalized_upgrade(Config) -> - doc("Headers passed to ws_upgrade are normalized before being used."), - {ok, ConnPid} = gun:open("localhost", config(port, Config)), - {ok, _} = gun:await_up(ConnPid), - StreamRef = gun:ws_upgrade(ConnPid, "/", #{ - atom_header_name => <<"value">>, - "string_header_name" => <<"value">> - }), - {upgrade, [<<"websocket">>], _} = gun:await(ConnPid, StreamRef), - gun:close(ConnPid). - -error_http_request(Config) -> - doc("Ensure that requests are rejected while using Websocket."), - {ok, ConnPid} = gun:open("localhost", config(port, Config)), - {ok, _} = gun:await_up(ConnPid), - StreamRef1 = gun:ws_upgrade(ConnPid, "/", []), - {upgrade, [<<"websocket">>], _} = gun:await(ConnPid, StreamRef1), - StreamRef2 = gun:get(ConnPid, "/"), - {error, {connection_error, {badstate, _}}} = gun:await(ConnPid, StreamRef2), - gun:close(ConnPid). - -keepalive(Config) -> +http11_keepalive(Config) -> doc("Ensure that Gun automatically sends ping frames."), {ok, ConnPid} = gun:open("localhost", config(port, Config), #{ ws_opts => #{ @@ -106,7 +117,7 @@ keepalive(Config) -> {ws, pong} = gun:await(ConnPid, StreamRef), gun:close(ConnPid). -keepalive_default_silence_pings(Config) -> +http11_keepalive_default_silence_pings(Config) -> doc("Ensure that Gun does not forward ping/pong by default."), {ok, ConnPid} = gun:open("localhost", config(port, Config), #{ ws_opts => #{keepalive => 100} @@ -118,10 +129,25 @@ keepalive_default_silence_pings(Config) -> {error, timeout} = gun:await(ConnPid, StreamRef, 1000), gun:close(ConnPid). -reject_upgrade(Config) -> - doc("Ensure Websocket connections can be rejected."), +http11_request_error(Config) -> + doc("Ensure that HTTP/1.1 requests are rejected while using Websocket."), {ok, ConnPid} = gun:open("localhost", config(port, Config)), {ok, _} = gun:await_up(ConnPid), + StreamRef1 = gun:ws_upgrade(ConnPid, "/", []), + {upgrade, [<<"websocket">>], _} = gun:await(ConnPid, StreamRef1), + StreamRef2 = gun:get(ConnPid, "/"), + {error, {connection_error, {badstate, _}}} = gun:await(ConnPid, StreamRef2), + gun:close(ConnPid). + +reject_upgrade(Config) -> + doc("Ensure Websocket connections can be rejected."), + Protocol = config(name, config(tc_group_properties, Config)), + {ok, ConnPid} = gun:open("localhost", config(port, Config), #{ + protocols => [Protocol], + http2_opts => #{notify_settings_changed => true} + }), + {ok, Protocol} = gun:await_up(ConnPid), + do_await_enable_connect_protocol(Protocol, ConnPid), StreamRef = gun:ws_upgrade(ConnPid, "/reject", []), receive {gun_response, ConnPid, StreamRef, nofin, 400, _} -> @@ -134,7 +160,7 @@ reject_upgrade(Config) -> end. reply_to(Config) -> - doc("Ensure we can send a list of frames in one gun:ws_send call."), + doc("Ensure the reply_to request option is respected."), Self = self(), Frame = {text, <<"Hello!">>}, ReplyTo = spawn(fun() -> @@ -144,36 +170,61 @@ reply_to(Config) -> {ws, Frame} = gun:await(ConnPid, StreamRef), Self ! {self(), ok} end), - {ok, ConnPid} = gun:open("localhost", config(port, Config)), - {ok, _} = gun:await_up(ConnPid), + Protocol = config(name, config(tc_group_properties, Config)), + {ok, ConnPid} = gun:open("localhost", config(port, Config), #{ + protocols => [Protocol], + http2_opts => #{notify_settings_changed => true} + }), + {ok, Protocol} = gun:await_up(ConnPid), + do_await_enable_connect_protocol(Protocol, ConnPid), StreamRef = gun:ws_upgrade(ConnPid, "/", [], #{reply_to => ReplyTo}), ReplyTo ! {ConnPid, StreamRef}, - receive {ReplyTo, ready} -> gun:ws_send(ConnPid, Frame) after 1000 -> error(timeout) end, + receive {ReplyTo, ready} -> gun:ws_send(ConnPid, StreamRef, Frame) after 1000 -> error(timeout) end, receive {ReplyTo, ok} -> gun:close(ConnPid) after 1000 -> error(timeout) end. send_many(Config) -> doc("Ensure we can send a list of frames in one gun:ws_send call."), - {ok, ConnPid} = gun:open("localhost", config(port, Config)), - {ok, _} = gun:await_up(ConnPid), + Protocol = config(name, config(tc_group_properties, Config)), + {ok, ConnPid} = gun:open("localhost", config(port, Config), #{ + protocols => [Protocol], + http2_opts => #{notify_settings_changed => true} + }), + {ok, Protocol} = gun:await_up(ConnPid), + do_await_enable_connect_protocol(Protocol, ConnPid), StreamRef = gun:ws_upgrade(ConnPid, "/", []), {upgrade, [<<"websocket">>], _} = gun:await(ConnPid, StreamRef), Frame1 = {text, <<"Hello!">>}, Frame2 = {binary, <<"World!">>}, - gun:ws_send(ConnPid, [Frame1, Frame2]), + gun:ws_send(ConnPid, StreamRef, [Frame1, Frame2]), {ws, Frame1} = gun:await(ConnPid, StreamRef), {ws, Frame2} = gun:await(ConnPid, StreamRef), gun:close(ConnPid). send_many_close(Config) -> doc("Ensure we can send a list of frames in one gun:ws_send call, including a close frame."), - {ok, ConnPid} = gun:open("localhost", config(port, Config)), - {ok, _} = gun:await_up(ConnPid), + Protocol = config(name, config(tc_group_properties, Config)), + {ok, ConnPid} = gun:open("localhost", config(port, Config), #{ + protocols => [Protocol], + http2_opts => #{notify_settings_changed => true} + }), + {ok, Protocol} = gun:await_up(ConnPid), + do_await_enable_connect_protocol(Protocol, ConnPid), StreamRef = gun:ws_upgrade(ConnPid, "/", []), {upgrade, [<<"websocket">>], _} = gun:await(ConnPid, StreamRef), Frame1 = {text, <<"Hello!">>}, Frame2 = {binary, <<"World!">>}, - gun:ws_send(ConnPid, [Frame1, Frame2, close]), + gun:ws_send(ConnPid, StreamRef, [Frame1, Frame2, close]), {ws, Frame1} = gun:await(ConnPid, StreamRef), {ws, Frame2} = gun:await(ConnPid, StreamRef), {ws, close} = gun:await(ConnPid, StreamRef), gun:close(ConnPid). + +%% Internal. + +do_await_enable_connect_protocol(http, _) -> + ok; +%% We cannot do a CONNECT :protocol request until the server tells us we can. +do_await_enable_connect_protocol(http2, ConnPid) -> + {notify, settings_changed, #{enable_connect_protocol := true}} + = gun:await(ConnPid, undefined), %% @todo Maybe have a gun:await/1? + ok. -- cgit v1.2.3