ws_SUITE.erl 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318
  1. %% Copyright (c) 2011, Loïc Hoguin <essen@dev-extend.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_SUITE).
  15. -include_lib("common_test/include/ct.hrl").
  16. -export([all/0, groups/0, init_per_suite/1, end_per_suite/1,
  17. init_per_group/2, end_per_group/2]). %% ct.
  18. -export([ws0/1, ws8/1, ws8_single_bytes/1, ws8_init_shutdown/1,
  19. ws13/1, ws_timeout_hibernate/1]). %% ws.
  20. %% ct.
  21. all() ->
  22. [{group, ws}].
  23. groups() ->
  24. BaseTests = [ws0, ws8, ws8_single_bytes, ws8_init_shutdown, ws13,
  25. ws_timeout_hibernate],
  26. [{ws, [], BaseTests}].
  27. init_per_suite(Config) ->
  28. application:start(inets),
  29. application:start(cowboy),
  30. Config.
  31. end_per_suite(_Config) ->
  32. application:stop(cowboy),
  33. application:stop(inets),
  34. ok.
  35. init_per_group(ws, Config) ->
  36. Port = 33080,
  37. cowboy:start_listener(ws, 100,
  38. cowboy_tcp_transport, [{port, Port}],
  39. cowboy_http_protocol, [{dispatch, init_dispatch()}]
  40. ),
  41. [{port, Port}|Config].
  42. end_per_group(Listener, _Config) ->
  43. cowboy:stop_listener(Listener),
  44. ok.
  45. %% Dispatch configuration.
  46. init_dispatch() ->
  47. [
  48. {[<<"localhost">>], [
  49. {[<<"websocket">>], websocket_handler, []},
  50. {[<<"ws_timeout_hibernate">>], ws_timeout_hibernate_handler, []},
  51. {[<<"ws_init_shutdown">>], websocket_handler_init_shutdown, []}
  52. ]}
  53. ].
  54. %% ws and wss.
  55. %% This test makes sure the code works even if we wait for a reply
  56. %% before sending the third challenge key in the GET body.
  57. %%
  58. %% This ensures that Cowboy will work fine with proxies on hixie.
  59. ws0(Config) ->
  60. {port, Port} = lists:keyfind(port, 1, Config),
  61. {ok, Socket} = gen_tcp:connect("localhost", Port,
  62. [binary, {active, false}, {packet, raw}]),
  63. ok = gen_tcp:send(Socket,
  64. "GET /websocket HTTP/1.1\r\n"
  65. "Host: localhost\r\n"
  66. "Connection: Upgrade\r\n"
  67. "Upgrade: WebSocket\r\n"
  68. "Origin: http://localhost\r\n"
  69. "Sec-Websocket-Key1: Y\" 4 1Lj!957b8@0H756!i\r\n"
  70. "Sec-Websocket-Key2: 1711 M;4\\74 80<6\r\n"
  71. "\r\n"),
  72. {ok, Handshake} = gen_tcp:recv(Socket, 0, 6000),
  73. {ok, {http_response, {1, 1}, 101, "WebSocket Protocol Handshake"}, Rest}
  74. = erlang:decode_packet(http, Handshake, []),
  75. [Headers, <<>>] = websocket_headers(
  76. erlang:decode_packet(httph, Rest, []), []),
  77. {'Connection', "Upgrade"} = lists:keyfind('Connection', 1, Headers),
  78. {'Upgrade', "WebSocket"} = lists:keyfind('Upgrade', 1, Headers),
  79. {"sec-websocket-location", "ws://localhost/websocket"}
  80. = lists:keyfind("sec-websocket-location", 1, Headers),
  81. {"sec-websocket-origin", "http://localhost"}
  82. = lists:keyfind("sec-websocket-origin", 1, Headers),
  83. ok = gen_tcp:send(Socket, <<15,245,8,18,2,204,133,33>>),
  84. {ok, Body} = gen_tcp:recv(Socket, 0, 6000),
  85. <<169,244,191,103,146,33,149,59,74,104,67,5,99,118,171,236>> = Body,
  86. ok = gen_tcp:send(Socket, << 0, "client_msg", 255 >>),
  87. {ok, << 0, "client_msg", 255 >>} = gen_tcp:recv(Socket, 0, 6000),
  88. {ok, << 0, "websocket_init", 255 >>} = gen_tcp:recv(Socket, 0, 6000),
  89. {ok, << 0, "websocket_handle", 255 >>} = gen_tcp:recv(Socket, 0, 6000),
  90. {ok, << 0, "websocket_handle", 255 >>} = gen_tcp:recv(Socket, 0, 6000),
  91. {ok, << 0, "websocket_handle", 255 >>} = gen_tcp:recv(Socket, 0, 6000),
  92. %% We try to send another HTTP request to make sure
  93. %% the server closed the request.
  94. ok = gen_tcp:send(Socket, [
  95. << 255, 0 >>, %% Close websocket command.
  96. "GET / HTTP/1.1\r\nHost: localhost\r\n\r\n" %% Server should ignore it.
  97. ]),
  98. {ok, << 255, 0 >>} = gen_tcp:recv(Socket, 0, 6000),
  99. {error, closed} = gen_tcp:recv(Socket, 0, 6000),
  100. ok.
  101. ws8(Config) ->
  102. {port, Port} = lists:keyfind(port, 1, Config),
  103. {ok, Socket} = gen_tcp:connect("localhost", Port,
  104. [binary, {active, false}, {packet, raw}]),
  105. ok = gen_tcp:send(Socket, [
  106. "GET /websocket HTTP/1.1\r\n"
  107. "Host: localhost\r\n"
  108. "Connection: Upgrade\r\n"
  109. "Upgrade: websocket\r\n"
  110. "Sec-WebSocket-Origin: http://localhost\r\n"
  111. "Sec-WebSocket-Version: 8\r\n"
  112. "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
  113. "\r\n"]),
  114. {ok, Handshake} = gen_tcp:recv(Socket, 0, 6000),
  115. {ok, {http_response, {1, 1}, 101, "Switching Protocols"}, Rest}
  116. = erlang:decode_packet(http, Handshake, []),
  117. [Headers, <<>>] = websocket_headers(
  118. erlang:decode_packet(httph, Rest, []), []),
  119. {'Connection', "Upgrade"} = lists:keyfind('Connection', 1, Headers),
  120. {'Upgrade', "websocket"} = lists:keyfind('Upgrade', 1, Headers),
  121. {"sec-websocket-accept", "s3pPLMBiTxaQ9kYGzzhZRbK+xOo="}
  122. = lists:keyfind("sec-websocket-accept", 1, Headers),
  123. ok = gen_tcp:send(Socket, << 16#81, 16#85, 16#37, 16#fa, 16#21, 16#3d,
  124. 16#7f, 16#9f, 16#4d, 16#51, 16#58 >>),
  125. {ok, << 1:1, 0:3, 1:4, 0:1, 5:7, "Hello" >>}
  126. = gen_tcp:recv(Socket, 0, 6000),
  127. {ok, << 1:1, 0:3, 1:4, 0:1, 14:7, "websocket_init" >>}
  128. = gen_tcp:recv(Socket, 0, 6000),
  129. {ok, << 1:1, 0:3, 1:4, 0:1, 16:7, "websocket_handle" >>}
  130. = gen_tcp:recv(Socket, 0, 6000),
  131. {ok, << 1:1, 0:3, 1:4, 0:1, 16:7, "websocket_handle" >>}
  132. = gen_tcp:recv(Socket, 0, 6000),
  133. {ok, << 1:1, 0:3, 1:4, 0:1, 16:7, "websocket_handle" >>}
  134. = gen_tcp:recv(Socket, 0, 6000),
  135. ok = gen_tcp:send(Socket, << 1:1, 0:3, 9:4, 0:8 >>), %% ping
  136. {ok, << 1:1, 0:3, 10:4, 0:8 >>} = gen_tcp:recv(Socket, 0, 6000), %% pong
  137. ok = gen_tcp:send(Socket, << 1:1, 0:3, 8:4, 0:8 >>), %% close
  138. {ok, << 1:1, 0:3, 8:4, 0:8 >>} = gen_tcp:recv(Socket, 0, 6000),
  139. {error, closed} = gen_tcp:recv(Socket, 0, 6000),
  140. ok.
  141. ws8_single_bytes(Config) ->
  142. {port, Port} = lists:keyfind(port, 1, Config),
  143. {ok, Socket} = gen_tcp:connect("localhost", Port,
  144. [binary, {active, false}, {packet, raw}]),
  145. ok = gen_tcp:send(Socket, [
  146. "GET /websocket HTTP/1.1\r\n"
  147. "Host: localhost\r\n"
  148. "Connection: Upgrade\r\n"
  149. "Upgrade: websocket\r\n"
  150. "Sec-WebSocket-Origin: http://localhost\r\n"
  151. "Sec-WebSocket-Version: 8\r\n"
  152. "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
  153. "\r\n"]),
  154. {ok, Handshake} = gen_tcp:recv(Socket, 0, 6000),
  155. {ok, {http_response, {1, 1}, 101, "Switching Protocols"}, Rest}
  156. = erlang:decode_packet(http, Handshake, []),
  157. [Headers, <<>>] = websocket_headers(
  158. erlang:decode_packet(httph, Rest, []), []),
  159. {'Connection', "Upgrade"} = lists:keyfind('Connection', 1, Headers),
  160. {'Upgrade', "websocket"} = lists:keyfind('Upgrade', 1, Headers),
  161. {"sec-websocket-accept", "s3pPLMBiTxaQ9kYGzzhZRbK+xOo="}
  162. = lists:keyfind("sec-websocket-accept", 1, Headers),
  163. ok = gen_tcp:send(Socket, << 16#81 >>), %% send one byte
  164. ok = timer:sleep(100), %% sleep for a period
  165. ok = gen_tcp:send(Socket, << 16#85 >>), %% send another and so on
  166. ok = timer:sleep(100),
  167. ok = gen_tcp:send(Socket, << 16#37 >>),
  168. ok = timer:sleep(100),
  169. ok = gen_tcp:send(Socket, << 16#fa >>),
  170. ok = timer:sleep(100),
  171. ok = gen_tcp:send(Socket, << 16#21 >>),
  172. ok = timer:sleep(100),
  173. ok = gen_tcp:send(Socket, << 16#3d >>),
  174. ok = timer:sleep(100),
  175. ok = gen_tcp:send(Socket, << 16#7f >>),
  176. ok = timer:sleep(100),
  177. ok = gen_tcp:send(Socket, << 16#9f >>),
  178. ok = timer:sleep(100),
  179. ok = gen_tcp:send(Socket, << 16#4d >>),
  180. ok = timer:sleep(100),
  181. ok = gen_tcp:send(Socket, << 16#51 >>),
  182. ok = timer:sleep(100),
  183. ok = gen_tcp:send(Socket, << 16#58 >>),
  184. {ok, << 1:1, 0:3, 1:4, 0:1, 14:7, "websocket_init" >>}
  185. = gen_tcp:recv(Socket, 0, 6000),
  186. {ok, << 1:1, 0:3, 1:4, 0:1, 5:7, "Hello" >>}
  187. = gen_tcp:recv(Socket, 0, 6000),
  188. {ok, << 1:1, 0:3, 1:4, 0:1, 16:7, "websocket_handle" >>}
  189. = gen_tcp:recv(Socket, 0, 6000),
  190. {ok, << 1:1, 0:3, 1:4, 0:1, 16:7, "websocket_handle" >>}
  191. = gen_tcp:recv(Socket, 0, 6000),
  192. {ok, << 1:1, 0:3, 1:4, 0:1, 16:7, "websocket_handle" >>}
  193. = gen_tcp:recv(Socket, 0, 6000),
  194. ok = gen_tcp:send(Socket, << 1:1, 0:3, 9:4, 0:8 >>), %% ping
  195. {ok, << 1:1, 0:3, 10:4, 0:8 >>} = gen_tcp:recv(Socket, 0, 6000), %% pong
  196. ok = gen_tcp:send(Socket, << 1:1, 0:3, 8:4, 0:8 >>), %% close
  197. {ok, << 1:1, 0:3, 8:4, 0:8 >>} = gen_tcp:recv(Socket, 0, 6000),
  198. {error, closed} = gen_tcp:recv(Socket, 0, 6000),
  199. ok.
  200. ws_timeout_hibernate(Config) ->
  201. {port, Port} = lists:keyfind(port, 1, Config),
  202. {ok, Socket} = gen_tcp:connect("localhost", Port,
  203. [binary, {active, false}, {packet, raw}]),
  204. ok = gen_tcp:send(Socket, [
  205. "GET /ws_timeout_hibernate HTTP/1.1\r\n"
  206. "Host: localhost\r\n"
  207. "Connection: Upgrade\r\n"
  208. "Upgrade: websocket\r\n"
  209. "Sec-WebSocket-Origin: http://localhost\r\n"
  210. "Sec-WebSocket-Version: 8\r\n"
  211. "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
  212. "\r\n"]),
  213. {ok, Handshake} = gen_tcp:recv(Socket, 0, 6000),
  214. {ok, {http_response, {1, 1}, 101, "Switching Protocols"}, Rest}
  215. = erlang:decode_packet(http, Handshake, []),
  216. [Headers, <<>>] = websocket_headers(
  217. erlang:decode_packet(httph, Rest, []), []),
  218. {'Connection', "Upgrade"} = lists:keyfind('Connection', 1, Headers),
  219. {'Upgrade', "websocket"} = lists:keyfind('Upgrade', 1, Headers),
  220. {"sec-websocket-accept", "s3pPLMBiTxaQ9kYGzzhZRbK+xOo="}
  221. = lists:keyfind("sec-websocket-accept", 1, Headers),
  222. {ok, << 1:1, 0:3, 8:4, 0:8 >>} = gen_tcp:recv(Socket, 0, 6000),
  223. {error, closed} = gen_tcp:recv(Socket, 0, 6000),
  224. ok.
  225. ws8_init_shutdown(Config) ->
  226. {port, Port} = lists:keyfind(port, 1, Config),
  227. {ok, Socket} = gen_tcp:connect("localhost", Port,
  228. [binary, {active, false}, {packet, raw}]),
  229. ok = gen_tcp:send(Socket, [
  230. "GET /ws_init_shutdown HTTP/1.1\r\n"
  231. "Host: localhost\r\n"
  232. "Connection: Upgrade\r\n"
  233. "Upgrade: websocket\r\n"
  234. "Sec-WebSocket-Origin: http://localhost\r\n"
  235. "Sec-WebSocket-Version: 8\r\n"
  236. "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
  237. "\r\n"]),
  238. {ok, Handshake} = gen_tcp:recv(Socket, 0, 6000),
  239. {ok, {http_response, {1, 1}, 403, "Forbidden"}, _Rest}
  240. = erlang:decode_packet(http, Handshake, []),
  241. {error, closed} = gen_tcp:recv(Socket, 0, 6000),
  242. ok.
  243. ws13(Config) ->
  244. {port, Port} = lists:keyfind(port, 1, Config),
  245. {ok, Socket} = gen_tcp:connect("localhost", Port,
  246. [binary, {active, false}, {packet, raw}]),
  247. ok = gen_tcp:send(Socket, [
  248. "GET /websocket HTTP/1.1\r\n"
  249. "Host: localhost\r\n"
  250. "Connection: Upgrade\r\n"
  251. "Origin: http://localhost\r\n"
  252. "Sec-WebSocket-Version: 13\r\n"
  253. "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
  254. "Upgrade: websocket\r\n"
  255. "\r\n"]),
  256. {ok, Handshake} = gen_tcp:recv(Socket, 0, 6000),
  257. {ok, {http_response, {1, 1}, 101, "Switching Protocols"}, Rest}
  258. = erlang:decode_packet(http, Handshake, []),
  259. [Headers, <<>>] = websocket_headers(
  260. erlang:decode_packet(httph, Rest, []), []),
  261. {'Connection', "Upgrade"} = lists:keyfind('Connection', 1, Headers),
  262. {'Upgrade', "websocket"} = lists:keyfind('Upgrade', 1, Headers),
  263. {"sec-websocket-accept", "s3pPLMBiTxaQ9kYGzzhZRbK+xOo="}
  264. = lists:keyfind("sec-websocket-accept", 1, Headers),
  265. %% text
  266. ok = gen_tcp:send(Socket, << 16#81, 16#85, 16#37, 16#fa, 16#21, 16#3d,
  267. 16#7f, 16#9f, 16#4d, 16#51, 16#58 >>),
  268. {ok, << 1:1, 0:3, 1:4, 0:1, 5:7, "Hello" >>}
  269. = gen_tcp:recv(Socket, 0, 6000),
  270. %% binary (empty)
  271. ok = gen_tcp:send(Socket, << 1:1, 0:3, 2:4, 0:8 >>),
  272. {ok, << 1:1, 0:3, 2:4, 0:8 >>} = gen_tcp:recv(Socket, 0, 6000),
  273. %% binary
  274. ok = gen_tcp:send(Socket, << 16#82, 16#85, 16#37, 16#fa, 16#21, 16#3d,
  275. 16#7f, 16#9f, 16#4d, 16#51, 16#58 >>),
  276. {ok, << 1:1, 0:3, 2:4, 0:1, 5:7, "Hello" >>}
  277. = gen_tcp:recv(Socket, 0, 6000),
  278. %% Receives.
  279. {ok, << 1:1, 0:3, 1:4, 0:1, 14:7, "websocket_init" >>}
  280. = gen_tcp:recv(Socket, 0, 6000),
  281. {ok, << 1:1, 0:3, 1:4, 0:1, 16:7, "websocket_handle" >>}
  282. = gen_tcp:recv(Socket, 0, 6000),
  283. {ok, << 1:1, 0:3, 1:4, 0:1, 16:7, "websocket_handle" >>}
  284. = gen_tcp:recv(Socket, 0, 6000),
  285. {ok, << 1:1, 0:3, 1:4, 0:1, 16:7, "websocket_handle" >>}
  286. = gen_tcp:recv(Socket, 0, 6000),
  287. ok = gen_tcp:send(Socket, << 1:1, 0:3, 9:4, 0:8 >>), %% ping
  288. {ok, << 1:1, 0:3, 10:4, 0:8 >>} = gen_tcp:recv(Socket, 0, 6000), %% pong
  289. ok = gen_tcp:send(Socket, << 1:1, 0:3, 8:4, 0:8 >>), %% close
  290. {ok, << 1:1, 0:3, 8:4, 0:8 >>} = gen_tcp:recv(Socket, 0, 6000),
  291. {error, closed} = gen_tcp:recv(Socket, 0, 6000),
  292. ok.
  293. websocket_headers({ok, http_eoh, Rest}, Acc) ->
  294. [Acc, Rest];
  295. websocket_headers({ok, {http_header, _I, Key, _R, Value}, Rest}, Acc) ->
  296. F = fun(S) when is_atom(S) -> S; (S) -> string:to_lower(S) end,
  297. websocket_headers(erlang:decode_packet(httph, Rest, []),
  298. [{F(Key), Value}|Acc]).