ws_handler_SUITE.erl 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207
  1. %% Copyright (c) 2018, Loïc Hoguin <essen@ninenines.eu>
  2. %%
  3. %% Permission to use, copy, modify, and/or distribute this software for any
  4. %% purpose with or without fee is hereby granted, provided that the above
  5. %% copyright notice and this permission notice appear in all copies.
  6. %%
  7. %% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
  8. %% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
  9. %% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
  10. %% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
  11. %% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
  12. %% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  13. %% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  14. -module(ws_handler_SUITE).
  15. -compile(export_all).
  16. -compile(nowarn_export_all).
  17. -import(ct_helper, [config/2]).
  18. -import(ct_helper, [doc/1]).
  19. -import(cowboy_test, [gun_open/1]).
  20. -import(cowboy_test, [gun_down/1]).
  21. %% ct.
  22. all() ->
  23. [{group, ws}, {group, ws_hibernate}].
  24. groups() ->
  25. AllTests = ct_helper:all(?MODULE),
  26. [{ws, [parallel], AllTests}, {ws_hibernate, [parallel], AllTests}].
  27. init_per_group(Name, Config) ->
  28. cowboy_test:init_http(Name, #{
  29. env => #{dispatch => init_dispatch(Name)}
  30. }, Config).
  31. end_per_group(Name, _) ->
  32. cowboy:stop_listener(Name).
  33. %% Dispatch configuration.
  34. init_dispatch(Name) ->
  35. RunOrHibernate = case Name of
  36. ws -> run;
  37. ws_hibernate -> hibernate
  38. end,
  39. cowboy_router:compile([{'_', [
  40. {"/init", ws_init_commands_h, RunOrHibernate},
  41. {"/handle", ws_handle_commands_h, RunOrHibernate},
  42. {"/info", ws_info_commands_h, RunOrHibernate}
  43. ]}]).
  44. %% Support functions for testing using Gun.
  45. gun_open_ws(Config, Path, Commands) ->
  46. ConnPid = gun_open(Config),
  47. StreamRef = gun:ws_upgrade(ConnPid, Path, [
  48. {<<"x-commands">>, base64:encode(term_to_binary(Commands))}
  49. ]),
  50. receive
  51. {gun_upgrade, ConnPid, StreamRef, [<<"websocket">>], _} ->
  52. {ok, ConnPid, StreamRef};
  53. {gun_response, ConnPid, _, _, Status, Headers} ->
  54. exit({ws_upgrade_failed, Status, Headers});
  55. {gun_error, ConnPid, StreamRef, Reason} ->
  56. exit({ws_upgrade_failed, Reason})
  57. after 1000 ->
  58. error(timeout)
  59. end.
  60. receive_ws(ConnPid, StreamRef) ->
  61. receive
  62. {gun_ws, ConnPid, StreamRef, Frame} ->
  63. {ok, Frame}
  64. after 1000 ->
  65. {error, timeout}
  66. end.
  67. ensure_handle_is_called(ConnPid, "/handle") ->
  68. gun:ws_send(ConnPid, {text, <<"Necessary to trigger websocket_handle/2.">>});
  69. ensure_handle_is_called(_, _) ->
  70. ok.
  71. %% Tests.
  72. websocket_init_nothing(Config) ->
  73. doc("Nothing happens when websocket_init/1 returns no commands."),
  74. do_nothing(Config, "/init").
  75. websocket_handle_nothing(Config) ->
  76. doc("Nothing happens when websocket_handle/2 returns no commands."),
  77. do_nothing(Config, "/handle").
  78. websocket_info_nothing(Config) ->
  79. doc("Nothing happens when websocket_info/2 returns no commands."),
  80. do_nothing(Config, "/info").
  81. do_nothing(Config, Path) ->
  82. {ok, ConnPid, StreamRef} = gun_open_ws(Config, Path, []),
  83. ensure_handle_is_called(ConnPid, Path),
  84. {error, timeout} = receive_ws(ConnPid, StreamRef),
  85. ok.
  86. websocket_init_invalid(Config) ->
  87. doc("The connection must be closed when websocket_init/1 returns an invalid command."),
  88. do_invalid(Config, "/init").
  89. websocket_handle_invalid(Config) ->
  90. doc("The connection must be closed when websocket_handle/2 returns an invalid command."),
  91. do_invalid(Config, "/init").
  92. websocket_info_invalid(Config) ->
  93. doc("The connection must be closed when websocket_info/2 returns an invalid command."),
  94. do_invalid(Config, "/info").
  95. do_invalid(Config, Path) ->
  96. {ok, ConnPid, _} = gun_open_ws(Config, Path, bad),
  97. ensure_handle_is_called(ConnPid, Path),
  98. gun_down(ConnPid).
  99. websocket_init_one_frame(Config) ->
  100. doc("A single frame is received when websocket_init/1 returns it as a command."),
  101. do_one_frame(Config, "/init").
  102. websocket_handle_one_frame(Config) ->
  103. doc("A single frame is received when websocket_handle/2 returns it as a command."),
  104. do_one_frame(Config, "/handle").
  105. websocket_info_one_frame(Config) ->
  106. doc("A single frame is received when websocket_info/2 returns it as a command."),
  107. do_one_frame(Config, "/info").
  108. do_one_frame(Config, Path) ->
  109. {ok, ConnPid, StreamRef} = gun_open_ws(Config, Path, [
  110. {text, <<"One frame!">>}
  111. ]),
  112. ensure_handle_is_called(ConnPid, Path),
  113. {ok, {text, <<"One frame!">>}} = receive_ws(ConnPid, StreamRef),
  114. ok.
  115. websocket_init_many_frames(Config) ->
  116. doc("Multiple frames are received when websocket_init/1 returns them as commands."),
  117. do_many_frames(Config, "/init").
  118. websocket_handle_many_frames(Config) ->
  119. doc("Multiple frames are received when websocket_handle/2 returns them as commands."),
  120. do_many_frames(Config, "/handle").
  121. websocket_info_many_frames(Config) ->
  122. doc("Multiple frames are received when websocket_info/2 returns them as commands."),
  123. do_many_frames(Config, "/info").
  124. do_many_frames(Config, Path) ->
  125. {ok, ConnPid, StreamRef} = gun_open_ws(Config, Path, [
  126. {text, <<"One frame!">>},
  127. {binary, <<"Two frames!">>}
  128. ]),
  129. ensure_handle_is_called(ConnPid, Path),
  130. {ok, {text, <<"One frame!">>}} = receive_ws(ConnPid, StreamRef),
  131. {ok, {binary, <<"Two frames!">>}} = receive_ws(ConnPid, StreamRef),
  132. ok.
  133. websocket_init_close_frame(Config) ->
  134. doc("A single close frame is received when websocket_init/1 returns it as a command."),
  135. do_close_frame(Config, "/init").
  136. websocket_handle_close_frame(Config) ->
  137. doc("A single close frame is received when websocket_handle/2 returns it as a command."),
  138. do_close_frame(Config, "/handle").
  139. websocket_info_close_frame(Config) ->
  140. doc("A single close frame is received when websocket_info/2 returns it as a command."),
  141. do_close_frame(Config, "/info").
  142. do_close_frame(Config, Path) ->
  143. {ok, ConnPid, StreamRef} = gun_open_ws(Config, Path, [close]),
  144. ensure_handle_is_called(ConnPid, Path),
  145. {ok, close} = receive_ws(ConnPid, StreamRef),
  146. gun_down(ConnPid).
  147. websocket_init_many_frames_then_close_frame(Config) ->
  148. doc("Multiple frames are received followed by a close frame "
  149. "when websocket_init/1 returns them as commands."),
  150. do_many_frames_then_close_frame(Config, "/init").
  151. websocket_handle_many_frames_then_close_frame(Config) ->
  152. doc("Multiple frames are received followed by a close frame "
  153. "when websocket_handle/2 returns them as commands."),
  154. do_many_frames_then_close_frame(Config, "/handle").
  155. websocket_info_many_frames_then_close_frame(Config) ->
  156. doc("Multiple frames are received followed by a close frame "
  157. "when websocket_info/2 returns them as commands."),
  158. do_many_frames_then_close_frame(Config, "/info").
  159. do_many_frames_then_close_frame(Config, Path) ->
  160. {ok, ConnPid, StreamRef} = gun_open_ws(Config, Path, [
  161. {text, <<"One frame!">>},
  162. {binary, <<"Two frames!">>},
  163. close
  164. ]),
  165. ensure_handle_is_called(ConnPid, Path),
  166. {ok, {text, <<"One frame!">>}} = receive_ws(ConnPid, StreamRef),
  167. {ok, {binary, <<"Two frames!">>}} = receive_ws(ConnPid, StreamRef),
  168. {ok, close} = receive_ws(ConnPid, StreamRef),
  169. gun_down(ConnPid).