ws_SUITE.erl 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679
  1. %% Copyright (c) 2011-2014, 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_SUITE).
  15. -compile(export_all).
  16. -import(cowboy_test, [config/2]).
  17. %% ct.
  18. all() ->
  19. [{group, autobahn}, {group, ws}].
  20. groups() ->
  21. BaseTests = cowboy_test:all(?MODULE) -- [autobahn_fuzzingclient],
  22. [{autobahn, [], [autobahn_fuzzingclient]}, {ws, [parallel], BaseTests}].
  23. init_per_suite(Config) ->
  24. Config.
  25. init_per_group(Name = autobahn, Config) ->
  26. %% Some systems have it named pip2.
  27. Out = os:cmd("pip show autobahntestsuite ; pip2 show autobahntestsuite"),
  28. case string:str(Out, "autobahntestsuite") of
  29. 0 ->
  30. ct:print("Skipping the autobahn group because the "
  31. "Autobahn Test Suite is not installed.~nTo install it, "
  32. "please follow the instructions on this page:~n~n "
  33. "http://autobahn.ws/testsuite/installation.html"),
  34. {skip, "Autobahn Test Suite not installed."};
  35. _ ->
  36. {ok, _} = cowboy:start_http(Name, 100, [{port, 33080}], [
  37. {env, [{dispatch, init_dispatch()}]}]),
  38. Config
  39. end;
  40. init_per_group(Name = ws, Config) ->
  41. cowboy_test:init_http(Name, [
  42. {env, [{dispatch, init_dispatch()}]},
  43. {compress, true}
  44. ], Config).
  45. end_per_group(Listener, _Config) ->
  46. cowboy:stop_listener(Listener).
  47. %% Dispatch configuration.
  48. init_dispatch() ->
  49. cowboy_router:compile([
  50. {"localhost", [
  51. {"/ws_echo", ws_echo, []},
  52. {"/ws_echo_timer", ws_echo_timer, []},
  53. {"/ws_init_shutdown", ws_init_shutdown, []},
  54. {"/ws_send_many", ws_send_many, [
  55. {sequence, [
  56. {text, <<"one">>},
  57. {text, <<"two">>},
  58. {text, <<"seven!">>}]}
  59. ]},
  60. {"/ws_send_close", ws_send_many, [
  61. {sequence, [
  62. {text, <<"send">>},
  63. close,
  64. {text, <<"won't be received">>}]}
  65. ]},
  66. {"/ws_send_close_payload", ws_send_many, [
  67. {sequence, [
  68. {text, <<"send">>},
  69. {close, 1001, <<"some text!">>},
  70. {text, <<"won't be received">>}]}
  71. ]},
  72. {"/ws_timeout_hibernate", ws_timeout_hibernate, []},
  73. {"/ws_timeout_cancel", ws_timeout_cancel, []}
  74. ]}
  75. ]).
  76. %% Tests.
  77. autobahn_fuzzingclient(Config) ->
  78. Out = os:cmd("cd " ++ config(priv_dir, Config)
  79. ++ " && wstest -m fuzzingclient -s "
  80. ++ config(data_dir, Config) ++ "client.json"),
  81. Report = config(priv_dir, Config) ++ "reports/servers/index.html",
  82. ct:log("<h2><a href=\"~s\">Full report</a></h2>~n", [Report]),
  83. ct:print("Autobahn Test Suite report: file://~s~n", [Report]),
  84. ct:log("~s~n", [Out]),
  85. {ok, HTML} = file:read_file(Report),
  86. case length(binary:matches(HTML, <<"case_failed">>)) > 2 of
  87. true -> error(failed);
  88. false -> ok
  89. end.
  90. %% We do not support hixie76 anymore.
  91. ws0(Config) ->
  92. {port, Port} = lists:keyfind(port, 1, Config),
  93. {ok, Socket} = gen_tcp:connect("localhost", Port,
  94. [binary, {active, false}, {packet, raw}]),
  95. ok = gen_tcp:send(Socket,
  96. "GET /ws_echo_timer HTTP/1.1\r\n"
  97. "Host: localhost\r\n"
  98. "Connection: Upgrade\r\n"
  99. "Upgrade: WebSocket\r\n"
  100. "Origin: http://localhost\r\n"
  101. "Sec-Websocket-Key1: Y\" 4 1Lj!957b8@0H756!i\r\n"
  102. "Sec-Websocket-Key2: 1711 M;4\\74 80<6\r\n"
  103. "\r\n"),
  104. {ok, Handshake} = gen_tcp:recv(Socket, 0, 6000),
  105. {ok, {http_response, {1, 1}, 400, _}, _}
  106. = erlang:decode_packet(http, Handshake, []).
  107. ws8(Config) ->
  108. {port, Port} = lists:keyfind(port, 1, Config),
  109. {ok, Socket} = gen_tcp:connect("localhost", Port,
  110. [binary, {active, false}, {packet, raw}]),
  111. ok = gen_tcp:send(Socket, [
  112. "GET /ws_echo_timer HTTP/1.1\r\n"
  113. "Host: localhost\r\n"
  114. "Connection: Upgrade\r\n"
  115. "Upgrade: websocket\r\n"
  116. "Sec-WebSocket-Origin: http://localhost\r\n"
  117. "Sec-WebSocket-Version: 8\r\n"
  118. "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
  119. "\r\n"]),
  120. {ok, Handshake} = gen_tcp:recv(Socket, 0, 6000),
  121. {ok, {http_response, {1, 1}, 101, "Switching Protocols"}, Rest}
  122. = erlang:decode_packet(http, Handshake, []),
  123. [Headers, <<>>] = do_decode_headers(
  124. erlang:decode_packet(httph, Rest, []), []),
  125. {'Connection', "Upgrade"} = lists:keyfind('Connection', 1, Headers),
  126. {'Upgrade', "websocket"} = lists:keyfind('Upgrade', 1, Headers),
  127. {"sec-websocket-accept", "s3pPLMBiTxaQ9kYGzzhZRbK+xOo="}
  128. = lists:keyfind("sec-websocket-accept", 1, Headers),
  129. ok = gen_tcp:send(Socket, << 16#81, 16#85, 16#37, 16#fa, 16#21, 16#3d,
  130. 16#7f, 16#9f, 16#4d, 16#51, 16#58 >>),
  131. {ok, << 1:1, 0:3, 1:4, 0:1, 5:7, "Hello" >>}
  132. = gen_tcp:recv(Socket, 0, 6000),
  133. {ok, << 1:1, 0:3, 1:4, 0:1, 14:7, "websocket_init" >>}
  134. = gen_tcp:recv(Socket, 0, 6000),
  135. {ok, << 1:1, 0:3, 1:4, 0:1, 16:7, "websocket_handle" >>}
  136. = gen_tcp:recv(Socket, 0, 6000),
  137. {ok, << 1:1, 0:3, 1:4, 0:1, 16:7, "websocket_handle" >>}
  138. = gen_tcp:recv(Socket, 0, 6000),
  139. {ok, << 1:1, 0:3, 1:4, 0:1, 16:7, "websocket_handle" >>}
  140. = gen_tcp:recv(Socket, 0, 6000),
  141. ok = gen_tcp:send(Socket, << 1:1, 0:3, 9:4, 1:1, 0:7, 0:32 >>), %% ping
  142. {ok, << 1:1, 0:3, 10:4, 0:8 >>} = gen_tcp:recv(Socket, 0, 6000), %% pong
  143. ok = gen_tcp:send(Socket, << 1:1, 0:3, 8:4, 1:1, 0:7, 0:32 >>), %% close
  144. {ok, << 1:1, 0:3, 8:4, 0:8 >>} = gen_tcp:recv(Socket, 0, 6000),
  145. {error, closed} = gen_tcp:recv(Socket, 0, 6000),
  146. ok.
  147. ws8_init_shutdown(Config) ->
  148. {port, Port} = lists:keyfind(port, 1, Config),
  149. {ok, Socket} = gen_tcp:connect("localhost", Port,
  150. [binary, {active, false}, {packet, raw}]),
  151. ok = gen_tcp:send(Socket, [
  152. "GET /ws_init_shutdown HTTP/1.1\r\n"
  153. "Host: localhost\r\n"
  154. "Connection: Upgrade\r\n"
  155. "Upgrade: websocket\r\n"
  156. "Sec-WebSocket-Origin: http://localhost\r\n"
  157. "Sec-WebSocket-Version: 8\r\n"
  158. "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
  159. "\r\n"]),
  160. {ok, Handshake} = gen_tcp:recv(Socket, 0, 6000),
  161. {ok, {http_response, {1, 1}, 403, "Forbidden"}, _Rest}
  162. = erlang:decode_packet(http, Handshake, []),
  163. {error, closed} = gen_tcp:recv(Socket, 0, 6000),
  164. ok.
  165. ws8_single_bytes(Config) ->
  166. {port, Port} = lists:keyfind(port, 1, Config),
  167. {ok, Socket} = gen_tcp:connect("localhost", Port,
  168. [binary, {active, false}, {packet, raw}]),
  169. ok = gen_tcp:send(Socket, [
  170. "GET /ws_echo_timer HTTP/1.1\r\n"
  171. "Host: localhost\r\n"
  172. "Connection: Upgrade\r\n"
  173. "Upgrade: websocket\r\n"
  174. "Sec-WebSocket-Origin: http://localhost\r\n"
  175. "Sec-WebSocket-Version: 8\r\n"
  176. "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
  177. "\r\n"]),
  178. {ok, Handshake} = gen_tcp:recv(Socket, 0, 6000),
  179. {ok, {http_response, {1, 1}, 101, "Switching Protocols"}, Rest}
  180. = erlang:decode_packet(http, Handshake, []),
  181. [Headers, <<>>] = do_decode_headers(
  182. erlang:decode_packet(httph, Rest, []), []),
  183. {'Connection', "Upgrade"} = lists:keyfind('Connection', 1, Headers),
  184. {'Upgrade', "websocket"} = lists:keyfind('Upgrade', 1, Headers),
  185. {"sec-websocket-accept", "s3pPLMBiTxaQ9kYGzzhZRbK+xOo="}
  186. = lists:keyfind("sec-websocket-accept", 1, Headers),
  187. ok = gen_tcp:send(Socket, << 16#81 >>), %% send one byte
  188. ok = timer:sleep(100), %% sleep for a period
  189. ok = gen_tcp:send(Socket, << 16#85 >>), %% send another and so on
  190. ok = timer:sleep(100),
  191. ok = gen_tcp:send(Socket, << 16#37 >>),
  192. ok = timer:sleep(100),
  193. ok = gen_tcp:send(Socket, << 16#fa >>),
  194. ok = timer:sleep(100),
  195. ok = gen_tcp:send(Socket, << 16#21 >>),
  196. ok = timer:sleep(100),
  197. ok = gen_tcp:send(Socket, << 16#3d >>),
  198. ok = timer:sleep(100),
  199. ok = gen_tcp:send(Socket, << 16#7f >>),
  200. ok = timer:sleep(100),
  201. ok = gen_tcp:send(Socket, << 16#9f >>),
  202. ok = timer:sleep(100),
  203. ok = gen_tcp:send(Socket, << 16#4d >>),
  204. ok = timer:sleep(100),
  205. ok = gen_tcp:send(Socket, << 16#51 >>),
  206. ok = timer:sleep(100),
  207. ok = gen_tcp:send(Socket, << 16#58 >>),
  208. {ok, << 1:1, 0:3, 1:4, 0:1, 14:7, "websocket_init" >>}
  209. = gen_tcp:recv(Socket, 0, 6000),
  210. {ok, << 1:1, 0:3, 1:4, 0:1, 5:7, "Hello" >>}
  211. = gen_tcp:recv(Socket, 0, 6000),
  212. {ok, << 1:1, 0:3, 1:4, 0:1, 16:7, "websocket_handle" >>}
  213. = gen_tcp:recv(Socket, 0, 6000),
  214. {ok, << 1:1, 0:3, 1:4, 0:1, 16:7, "websocket_handle" >>}
  215. = gen_tcp:recv(Socket, 0, 6000),
  216. {ok, << 1:1, 0:3, 1:4, 0:1, 16:7, "websocket_handle" >>}
  217. = gen_tcp:recv(Socket, 0, 6000),
  218. ok = gen_tcp:send(Socket, << 1:1, 0:3, 9:4, 1:1, 0:7, 0:32 >>), %% ping
  219. {ok, << 1:1, 0:3, 10:4, 0:8 >>} = gen_tcp:recv(Socket, 0, 6000), %% pong
  220. ok = gen_tcp:send(Socket, << 1:1, 0:3, 8:4, 1:1, 0:7, 0:32 >>), %% close
  221. {ok, << 1:1, 0:3, 8:4, 0:8 >>} = gen_tcp:recv(Socket, 0, 6000),
  222. {error, closed} = gen_tcp:recv(Socket, 0, 6000),
  223. ok.
  224. ws13(Config) ->
  225. {port, Port} = lists:keyfind(port, 1, Config),
  226. {ok, Socket} = gen_tcp:connect("localhost", Port,
  227. [binary, {active, false}, {packet, raw}]),
  228. ok = gen_tcp:send(Socket, [
  229. "GET /ws_echo_timer HTTP/1.1\r\n"
  230. "Host: localhost\r\n"
  231. "Connection: Upgrade\r\n"
  232. "Origin: http://localhost\r\n"
  233. "Sec-WebSocket-Version: 13\r\n"
  234. "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
  235. "Upgrade: websocket\r\n"
  236. "\r\n"]),
  237. {ok, Handshake} = gen_tcp:recv(Socket, 0, 6000),
  238. {ok, {http_response, {1, 1}, 101, "Switching Protocols"}, Rest}
  239. = erlang:decode_packet(http, Handshake, []),
  240. [Headers, <<>>] = do_decode_headers(
  241. erlang:decode_packet(httph, Rest, []), []),
  242. {'Connection', "Upgrade"} = lists:keyfind('Connection', 1, Headers),
  243. {'Upgrade', "websocket"} = lists:keyfind('Upgrade', 1, Headers),
  244. {"sec-websocket-accept", "s3pPLMBiTxaQ9kYGzzhZRbK+xOo="}
  245. = lists:keyfind("sec-websocket-accept", 1, Headers),
  246. %% text
  247. ok = gen_tcp:send(Socket, << 16#81, 16#85, 16#37, 16#fa, 16#21, 16#3d,
  248. 16#7f, 16#9f, 16#4d, 16#51, 16#58 >>),
  249. {ok, << 1:1, 0:3, 1:4, 0:1, 5:7, "Hello" >>}
  250. = gen_tcp:recv(Socket, 0, 6000),
  251. %% binary (empty)
  252. ok = gen_tcp:send(Socket, << 1:1, 0:3, 2:4, 1:1, 0:7, 0:32 >>),
  253. {ok, << 1:1, 0:3, 2:4, 0:8 >>} = gen_tcp:recv(Socket, 0, 6000),
  254. %% binary
  255. ok = gen_tcp:send(Socket, << 16#82, 16#85, 16#37, 16#fa, 16#21, 16#3d,
  256. 16#7f, 16#9f, 16#4d, 16#51, 16#58 >>),
  257. {ok, << 1:1, 0:3, 2:4, 0:1, 5:7, "Hello" >>}
  258. = gen_tcp:recv(Socket, 0, 6000),
  259. %% Receives.
  260. {ok, << 1:1, 0:3, 1:4, 0:1, 14:7, "websocket_init" >>}
  261. = gen_tcp:recv(Socket, 0, 6000),
  262. {ok, << 1:1, 0:3, 1:4, 0:1, 16:7, "websocket_handle" >>}
  263. = gen_tcp:recv(Socket, 0, 6000),
  264. {ok, << 1:1, 0:3, 1:4, 0:1, 16:7, "websocket_handle" >>}
  265. = gen_tcp:recv(Socket, 0, 6000),
  266. {ok, << 1:1, 0:3, 1:4, 0:1, 16:7, "websocket_handle" >>}
  267. = gen_tcp:recv(Socket, 0, 6000),
  268. ok = gen_tcp:send(Socket, << 1:1, 0:3, 9:4, 1:1, 0:7, 0:32 >>), %% ping
  269. {ok, << 1:1, 0:3, 10:4, 0:8 >>} = gen_tcp:recv(Socket, 0, 6000), %% pong
  270. ok = gen_tcp:send(Socket, << 1:1, 0:3, 8:4, 1:1, 0:7, 0:32 >>), %% close
  271. {ok, << 1:1, 0:3, 8:4, 0:8 >>} = gen_tcp:recv(Socket, 0, 6000),
  272. {error, closed} = gen_tcp:recv(Socket, 0, 6000),
  273. ok.
  274. ws_deflate(Config) ->
  275. {port, Port} = lists:keyfind(port, 1, Config),
  276. {ok, Socket} = gen_tcp:connect("localhost", Port,
  277. [binary, {active, false}, {packet, raw}, {nodelay, true}]),
  278. ok = gen_tcp:send(Socket, [
  279. "GET /ws_echo HTTP/1.1\r\n"
  280. "Host: localhost\r\n"
  281. "Connection: Upgrade\r\n"
  282. "Upgrade: websocket\r\n"
  283. "Sec-WebSocket-Origin: http://localhost\r\n"
  284. "Sec-WebSocket-Version: 8\r\n"
  285. "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
  286. "Sec-WebSocket-Extensions: x-webkit-deflate-frame\r\n"
  287. "\r\n"]),
  288. {ok, Handshake} = gen_tcp:recv(Socket, 0, 6000),
  289. {ok, {http_response, {1, 1}, 101, "Switching Protocols"}, Rest}
  290. = erlang:decode_packet(http, Handshake, []),
  291. [Headers, <<>>] = do_decode_headers(
  292. erlang:decode_packet(httph, Rest, []), []),
  293. {'Connection', "Upgrade"} = lists:keyfind('Connection', 1, Headers),
  294. {'Upgrade', "websocket"} = lists:keyfind('Upgrade', 1, Headers),
  295. {"sec-websocket-accept", "s3pPLMBiTxaQ9kYGzzhZRbK+xOo="}
  296. = lists:keyfind("sec-websocket-accept", 1, Headers),
  297. {"sec-websocket-extensions", "x-webkit-deflate-frame"}
  298. = lists:keyfind("sec-websocket-extensions", 1, Headers),
  299. Mask = 16#11223344,
  300. Hello = << 242, 72, 205, 201, 201, 7, 0 >>,
  301. MaskedHello = do_mask(Hello, Mask, <<>>),
  302. % send compressed text frame containing the Hello string
  303. ok = gen_tcp:send(Socket, << 1:1, 1:1, 0:2, 1:4, 1:1, 7:7, Mask:32,
  304. MaskedHello/binary >>),
  305. % receive compressed text frame containing the Hello string
  306. {ok, << 1:1, 1:1, 0:2, 1:4, 0:1, 7:7, Hello/binary >>}
  307. = gen_tcp:recv(Socket, 0, 6000),
  308. ok = gen_tcp:send(Socket, << 1:1, 0:3, 8:4, 1:1, 0:7, 0:32 >>), %% close
  309. {ok, << 1:1, 0:3, 8:4, 0:8 >>} = gen_tcp:recv(Socket, 0, 6000),
  310. {error, closed} = gen_tcp:recv(Socket, 0, 6000),
  311. ok.
  312. ws_deflate_chunks(Config) ->
  313. {port, Port} = lists:keyfind(port, 1, Config),
  314. {ok, Socket} = gen_tcp:connect("localhost", Port,
  315. [binary, {active, false}, {packet, raw}, {nodelay, true}]),
  316. ok = gen_tcp:send(Socket, [
  317. "GET /ws_echo HTTP/1.1\r\n"
  318. "Host: localhost\r\n"
  319. "Connection: Upgrade\r\n"
  320. "Upgrade: websocket\r\n"
  321. "Sec-WebSocket-Origin: http://localhost\r\n"
  322. "Sec-WebSocket-Version: 8\r\n"
  323. "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
  324. "Sec-WebSocket-Extensions: x-webkit-deflate-frame\r\n"
  325. "\r\n"]),
  326. {ok, Handshake} = gen_tcp:recv(Socket, 0, 6000),
  327. {ok, {http_response, {1, 1}, 101, "Switching Protocols"}, Rest}
  328. = erlang:decode_packet(http, Handshake, []),
  329. [Headers, <<>>] = do_decode_headers(
  330. erlang:decode_packet(httph, Rest, []), []),
  331. {'Connection', "Upgrade"} = lists:keyfind('Connection', 1, Headers),
  332. {'Upgrade', "websocket"} = lists:keyfind('Upgrade', 1, Headers),
  333. {"sec-websocket-accept", "s3pPLMBiTxaQ9kYGzzhZRbK+xOo="}
  334. = lists:keyfind("sec-websocket-accept", 1, Headers),
  335. {"sec-websocket-extensions", "x-webkit-deflate-frame"}
  336. = lists:keyfind("sec-websocket-extensions", 1, Headers),
  337. Mask = 16#11223344,
  338. Hello = << 242, 72, 205, 201, 201, 7, 0 >>,
  339. MaskedHello = do_mask(Hello, Mask, <<>>),
  340. % send compressed text frame containing the Hello string
  341. ok = gen_tcp:send(Socket, << 1:1, 1:1, 0:2, 1:4, 1:1, 7:7, Mask:32,
  342. (binary:part(MaskedHello, 0, 4))/binary >>),
  343. ok = timer:sleep(100),
  344. ok = gen_tcp:send(Socket, binary:part(MaskedHello, 4, 3)),
  345. % receive compressed text frame containing the Hello string
  346. {ok, << 1:1, 1:1, 0:2, 1:4, 0:1, 7:7, Hello/binary >>}
  347. = gen_tcp:recv(Socket, 0, 6000),
  348. ok = gen_tcp:send(Socket, << 1:1, 0:3, 8:4, 1:1, 0:7, 0:32 >>), %% close
  349. {ok, << 1:1, 0:3, 8:4, 0:8 >>} = gen_tcp:recv(Socket, 0, 6000),
  350. {error, closed} = gen_tcp:recv(Socket, 0, 6000),
  351. ok.
  352. ws_deflate_fragments(Config) ->
  353. {port, Port} = lists:keyfind(port, 1, Config),
  354. {ok, Socket} = gen_tcp:connect("localhost", Port,
  355. [binary, {active, false}, {packet, raw}, {nodelay, true}]),
  356. ok = gen_tcp:send(Socket, [
  357. "GET /ws_echo HTTP/1.1\r\n"
  358. "Host: localhost\r\n"
  359. "Connection: Upgrade\r\n"
  360. "Upgrade: websocket\r\n"
  361. "Sec-WebSocket-Origin: http://localhost\r\n"
  362. "Sec-WebSocket-Version: 8\r\n"
  363. "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
  364. "Sec-WebSocket-Extensions: x-webkit-deflate-frame\r\n"
  365. "\r\n"]),
  366. {ok, Handshake} = gen_tcp:recv(Socket, 0, 6000),
  367. {ok, {http_response, {1, 1}, 101, "Switching Protocols"}, Rest}
  368. = erlang:decode_packet(http, Handshake, []),
  369. [Headers, <<>>] = do_decode_headers(
  370. erlang:decode_packet(httph, Rest, []), []),
  371. {'Connection', "Upgrade"} = lists:keyfind('Connection', 1, Headers),
  372. {'Upgrade', "websocket"} = lists:keyfind('Upgrade', 1, Headers),
  373. {"sec-websocket-accept", "s3pPLMBiTxaQ9kYGzzhZRbK+xOo="}
  374. = lists:keyfind("sec-websocket-accept", 1, Headers),
  375. {"sec-websocket-extensions", "x-webkit-deflate-frame"}
  376. = lists:keyfind("sec-websocket-extensions", 1, Headers),
  377. Mask = 16#11223344,
  378. Hello = << 242, 72, 205, 201, 201, 7, 0 >>,
  379. % send compressed text frame containing the Hello string
  380. % as 2 separate fragments
  381. ok = gen_tcp:send(Socket, << 0:1, 1:1, 0:2, 1:4, 1:1, 4:7, Mask:32,
  382. (do_mask(binary:part(Hello, 0, 4), Mask, <<>>))/binary >>),
  383. ok = gen_tcp:send(Socket, << 1:1, 1:1, 0:2, 0:4, 1:1, 3:7, Mask:32,
  384. (do_mask(binary:part(Hello, 4, 3), Mask, <<>>))/binary >>),
  385. % receive compressed text frame containing the Hello string
  386. {ok, << 1:1, 1:1, 0:2, 1:4, 0:1, 7:7, Hello/binary >>}
  387. = gen_tcp:recv(Socket, 0, 6000),
  388. ok = gen_tcp:send(Socket, << 1:1, 0:3, 8:4, 1:1, 0:7, 0:32 >>), %% close
  389. {ok, << 1:1, 0:3, 8:4, 0:8 >>} = gen_tcp:recv(Socket, 0, 6000),
  390. {error, closed} = gen_tcp:recv(Socket, 0, 6000),
  391. ok.
  392. ws_send_close(Config) ->
  393. {port, Port} = lists:keyfind(port, 1, Config),
  394. {ok, Socket} = gen_tcp:connect("localhost", Port,
  395. [binary, {active, false}, {packet, raw}]),
  396. ok = gen_tcp:send(Socket, [
  397. "GET /ws_send_close HTTP/1.1\r\n"
  398. "Host: localhost\r\n"
  399. "Connection: Upgrade\r\n"
  400. "Upgrade: websocket\r\n"
  401. "Sec-WebSocket-Origin: http://localhost\r\n"
  402. "Sec-WebSocket-Version: 8\r\n"
  403. "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
  404. "\r\n"]),
  405. {ok, Handshake} = gen_tcp:recv(Socket, 0, 6000),
  406. {ok, {http_response, {1, 1}, 101, "Switching Protocols"}, Rest}
  407. = erlang:decode_packet(http, Handshake, []),
  408. [Headers, <<>>] = do_decode_headers(
  409. erlang:decode_packet(httph, Rest, []), []),
  410. {'Connection', "Upgrade"} = lists:keyfind('Connection', 1, Headers),
  411. {'Upgrade', "websocket"} = lists:keyfind('Upgrade', 1, Headers),
  412. {"sec-websocket-accept", "s3pPLMBiTxaQ9kYGzzhZRbK+xOo="}
  413. = lists:keyfind("sec-websocket-accept", 1, Headers),
  414. %% We catch all frames at once and check them directly.
  415. {ok, Many} = gen_tcp:recv(Socket, 8, 6000),
  416. << 1:1, 0:3, 1:4, 0:1, 4:7, "send",
  417. 1:1, 0:3, 8:4, 0:8 >> = Many,
  418. {error, closed} = gen_tcp:recv(Socket, 0, 6000),
  419. ok.
  420. ws_send_close_payload(Config) ->
  421. {port, Port} = lists:keyfind(port, 1, Config),
  422. {ok, Socket} = gen_tcp:connect("localhost", Port,
  423. [binary, {active, false}, {packet, raw}]),
  424. ok = gen_tcp:send(Socket, [
  425. "GET /ws_send_close_payload HTTP/1.1\r\n"
  426. "Host: localhost\r\n"
  427. "Connection: Upgrade\r\n"
  428. "Upgrade: websocket\r\n"
  429. "Sec-WebSocket-Origin: http://localhost\r\n"
  430. "Sec-WebSocket-Version: 8\r\n"
  431. "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
  432. "\r\n"]),
  433. {ok, Handshake} = gen_tcp:recv(Socket, 0, 6000),
  434. {ok, {http_response, {1, 1}, 101, "Switching Protocols"}, Rest}
  435. = erlang:decode_packet(http, Handshake, []),
  436. [Headers, <<>>] = do_decode_headers(
  437. erlang:decode_packet(httph, Rest, []), []),
  438. {'Connection', "Upgrade"} = lists:keyfind('Connection', 1, Headers),
  439. {'Upgrade', "websocket"} = lists:keyfind('Upgrade', 1, Headers),
  440. {"sec-websocket-accept", "s3pPLMBiTxaQ9kYGzzhZRbK+xOo="}
  441. = lists:keyfind("sec-websocket-accept", 1, Headers),
  442. %% We catch all frames at once and check them directly.
  443. {ok, Many} = gen_tcp:recv(Socket, 20, 6000),
  444. << 1:1, 0:3, 1:4, 0:1, 4:7, "send",
  445. 1:1, 0:3, 8:4, 0:1, 12:7, 1001:16, "some text!" >> = Many,
  446. {error, closed} = gen_tcp:recv(Socket, 0, 6000),
  447. ok.
  448. ws_send_many(Config) ->
  449. {port, Port} = lists:keyfind(port, 1, Config),
  450. {ok, Socket} = gen_tcp:connect("localhost", Port,
  451. [binary, {active, false}, {packet, raw}]),
  452. ok = gen_tcp:send(Socket, [
  453. "GET /ws_send_many HTTP/1.1\r\n"
  454. "Host: localhost\r\n"
  455. "Connection: Upgrade\r\n"
  456. "Upgrade: websocket\r\n"
  457. "Sec-WebSocket-Origin: http://localhost\r\n"
  458. "Sec-WebSocket-Version: 8\r\n"
  459. "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
  460. "\r\n"]),
  461. {ok, Handshake} = gen_tcp:recv(Socket, 0, 6000),
  462. {ok, {http_response, {1, 1}, 101, "Switching Protocols"}, Rest}
  463. = erlang:decode_packet(http, Handshake, []),
  464. [Headers, <<>>] = do_decode_headers(
  465. erlang:decode_packet(httph, Rest, []), []),
  466. {'Connection', "Upgrade"} = lists:keyfind('Connection', 1, Headers),
  467. {'Upgrade', "websocket"} = lists:keyfind('Upgrade', 1, Headers),
  468. {"sec-websocket-accept", "s3pPLMBiTxaQ9kYGzzhZRbK+xOo="}
  469. = lists:keyfind("sec-websocket-accept", 1, Headers),
  470. %% We catch all frames at once and check them directly.
  471. {ok, Many} = gen_tcp:recv(Socket, 18, 6000),
  472. << 1:1, 0:3, 1:4, 0:1, 3:7, "one",
  473. 1:1, 0:3, 1:4, 0:1, 3:7, "two",
  474. 1:1, 0:3, 1:4, 0:1, 6:7, "seven!" >> = Many,
  475. ok = gen_tcp:send(Socket, << 1:1, 0:3, 8:4, 1:1, 0:7, 0:32 >>), %% close
  476. {ok, << 1:1, 0:3, 8:4, 0:8 >>} = gen_tcp:recv(Socket, 0, 6000),
  477. {error, closed} = gen_tcp:recv(Socket, 0, 6000),
  478. ok.
  479. ws_text_fragments(Config) ->
  480. {port, Port} = lists:keyfind(port, 1, Config),
  481. {ok, Socket} = gen_tcp:connect("localhost", Port,
  482. [binary, {active, false}, {packet, raw}]),
  483. ok = gen_tcp:send(Socket, [
  484. "GET /ws_echo HTTP/1.1\r\n"
  485. "Host: localhost\r\n"
  486. "Connection: Upgrade\r\n"
  487. "Upgrade: websocket\r\n"
  488. "Sec-WebSocket-Origin: http://localhost\r\n"
  489. "Sec-WebSocket-Version: 8\r\n"
  490. "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
  491. "\r\n"]),
  492. {ok, Handshake} = gen_tcp:recv(Socket, 0, 6000),
  493. {ok, {http_response, {1, 1}, 101, "Switching Protocols"}, Rest}
  494. = erlang:decode_packet(http, Handshake, []),
  495. [Headers, <<>>] = do_decode_headers(
  496. erlang:decode_packet(httph, Rest, []), []),
  497. {'Connection', "Upgrade"} = lists:keyfind('Connection', 1, Headers),
  498. {'Upgrade', "websocket"} = lists:keyfind('Upgrade', 1, Headers),
  499. {"sec-websocket-accept", "s3pPLMBiTxaQ9kYGzzhZRbK+xOo="}
  500. = lists:keyfind("sec-websocket-accept", 1, Headers),
  501. ok = gen_tcp:send(Socket, [
  502. << 0:1, 0:3, 1:4, 1:1, 5:7 >>,
  503. << 16#37 >>, << 16#fa >>, << 16#21 >>, << 16#3d >>, << 16#7f >>,
  504. << 16#9f >>, << 16#4d >>, << 16#51 >>, << 16#58 >>]),
  505. ok = gen_tcp:send(Socket, [
  506. << 1:1, 0:3, 0:4, 1:1, 5:7 >>,
  507. << 16#37 >>, << 16#fa >>, << 16#21 >>, << 16#3d >>, << 16#7f >>,
  508. << 16#9f >>, << 16#4d >>, << 16#51 >>, << 16#58 >>]),
  509. {ok, << 1:1, 0:3, 1:4, 0:1, 10:7, "HelloHello" >>}
  510. = gen_tcp:recv(Socket, 0, 6000),
  511. ok = gen_tcp:send(Socket, [
  512. %% #1
  513. << 0:1, 0:3, 1:4, 1:1, 5:7 >>,
  514. << 16#37 >>, << 16#fa >>, << 16#21 >>, << 16#3d >>, << 16#7f >>,
  515. << 16#9f >>, << 16#4d >>, << 16#51 >>, << 16#58 >>,
  516. %% #2
  517. << 0:1, 0:3, 0:4, 1:1, 5:7 >>,
  518. << 16#37 >>, << 16#fa >>, << 16#21 >>, << 16#3d >>, << 16#7f >>,
  519. << 16#9f >>, << 16#4d >>, << 16#51 >>, << 16#58 >>,
  520. %% #3
  521. << 1:1, 0:3, 0:4, 1:1, 5:7 >>,
  522. << 16#37 >>, << 16#fa >>, << 16#21 >>, << 16#3d >>, << 16#7f >>,
  523. << 16#9f >>, << 16#4d >>, << 16#51 >>, << 16#58 >>]),
  524. {ok, << 1:1, 0:3, 1:4, 0:1, 15:7, "HelloHelloHello" >>}
  525. = gen_tcp:recv(Socket, 0, 6000),
  526. ok = gen_tcp:send(Socket, << 1:1, 0:3, 8:4, 1:1, 0:7, 0:32 >>), %% close
  527. {ok, << 1:1, 0:3, 8:4, 0:8 >>} = gen_tcp:recv(Socket, 0, 6000),
  528. {error, closed} = gen_tcp:recv(Socket, 0, 6000),
  529. ok.
  530. ws_timeout_hibernate(Config) ->
  531. {port, Port} = lists:keyfind(port, 1, Config),
  532. {ok, Socket} = gen_tcp:connect("localhost", Port,
  533. [binary, {active, false}, {packet, raw}]),
  534. ok = gen_tcp:send(Socket, [
  535. "GET /ws_timeout_hibernate HTTP/1.1\r\n"
  536. "Host: localhost\r\n"
  537. "Connection: Upgrade\r\n"
  538. "Upgrade: websocket\r\n"
  539. "Sec-WebSocket-Origin: http://localhost\r\n"
  540. "Sec-WebSocket-Version: 8\r\n"
  541. "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
  542. "\r\n"]),
  543. {ok, Handshake} = gen_tcp:recv(Socket, 0, 6000),
  544. {ok, {http_response, {1, 1}, 101, "Switching Protocols"}, Rest}
  545. = erlang:decode_packet(http, Handshake, []),
  546. [Headers, <<>>] = do_decode_headers(
  547. erlang:decode_packet(httph, Rest, []), []),
  548. {'Connection', "Upgrade"} = lists:keyfind('Connection', 1, Headers),
  549. {'Upgrade', "websocket"} = lists:keyfind('Upgrade', 1, Headers),
  550. {"sec-websocket-accept", "s3pPLMBiTxaQ9kYGzzhZRbK+xOo="}
  551. = lists:keyfind("sec-websocket-accept", 1, Headers),
  552. {ok, << 1:1, 0:3, 8:4, 0:1, 2:7, 1000:16 >>} = gen_tcp:recv(Socket, 0, 6000),
  553. {error, closed} = gen_tcp:recv(Socket, 0, 6000),
  554. ok.
  555. ws_timeout_cancel(Config) ->
  556. %% Erlang messages to a socket should not cancel the timeout
  557. {port, Port} = lists:keyfind(port, 1, Config),
  558. {ok, Socket} = gen_tcp:connect("localhost", Port,
  559. [binary, {active, false}, {packet, raw}]),
  560. ok = gen_tcp:send(Socket, [
  561. "GET /ws_timeout_cancel HTTP/1.1\r\n"
  562. "Host: localhost\r\n"
  563. "Connection: Upgrade\r\n"
  564. "Upgrade: websocket\r\n"
  565. "Sec-WebSocket-Origin: http://localhost\r\n"
  566. "Sec-WebSocket-Version: 8\r\n"
  567. "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
  568. "\r\n"]),
  569. {ok, Handshake} = gen_tcp:recv(Socket, 0, 6000),
  570. {ok, {http_response, {1, 1}, 101, "Switching Protocols"}, Rest}
  571. = erlang:decode_packet(http, Handshake, []),
  572. [Headers, <<>>] = do_decode_headers(
  573. erlang:decode_packet(httph, Rest, []), []),
  574. {'Connection', "Upgrade"} = lists:keyfind('Connection', 1, Headers),
  575. {'Upgrade', "websocket"} = lists:keyfind('Upgrade', 1, Headers),
  576. {"sec-websocket-accept", "s3pPLMBiTxaQ9kYGzzhZRbK+xOo="}
  577. = lists:keyfind("sec-websocket-accept", 1, Headers),
  578. {ok, << 1:1, 0:3, 8:4, 0:1, 2:7, 1000:16 >>} = gen_tcp:recv(Socket, 0, 6000),
  579. {error, closed} = gen_tcp:recv(Socket, 0, 6000),
  580. ok.
  581. ws_timeout_reset(Config) ->
  582. %% Erlang messages across a socket should reset the timeout
  583. {port, Port} = lists:keyfind(port, 1, Config),
  584. {ok, Socket} = gen_tcp:connect("localhost", Port,
  585. [binary, {active, false}, {packet, raw}]),
  586. ok = gen_tcp:send(Socket, [
  587. "GET /ws_timeout_cancel HTTP/1.1\r\n"
  588. "Host: localhost\r\n"
  589. "Connection: Upgrade\r\n"
  590. "Upgrade: websocket\r\n"
  591. "Sec-WebSocket-Origin: http://localhost\r\n"
  592. "Sec-Websocket-Version: 13\r\n"
  593. "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
  594. "\r\n"]),
  595. {ok, Handshake} = gen_tcp:recv(Socket, 0, 6000),
  596. {ok, {http_response, {1, 1}, 101, "Switching Protocols"}, Rest}
  597. = erlang:decode_packet(http, Handshake, []),
  598. [Headers, <<>>] = do_decode_headers(
  599. erlang:decode_packet(httph, Rest, []), []),
  600. {'Connection', "Upgrade"} = lists:keyfind('Connection', 1, Headers),
  601. {'Upgrade', "websocket"} = lists:keyfind('Upgrade', 1, Headers),
  602. {"sec-websocket-accept", "s3pPLMBiTxaQ9kYGzzhZRbK+xOo="}
  603. = lists:keyfind("sec-websocket-accept", 1, Headers),
  604. [begin
  605. ok = gen_tcp:send(Socket, << 16#81, 16#85, 16#37, 16#fa, 16#21, 16#3d,
  606. 16#7f, 16#9f, 16#4d, 16#51, 16#58 >>),
  607. {ok, << 1:1, 0:3, 1:4, 0:1, 5:7, "Hello" >>}
  608. = gen_tcp:recv(Socket, 0, 6000),
  609. ok = timer:sleep(500)
  610. end || _ <- [1, 2, 3, 4]],
  611. {ok, << 1:1, 0:3, 8:4, 0:1, 2:7, 1000:16 >>} = gen_tcp:recv(Socket, 0, 6000),
  612. {error, closed} = gen_tcp:recv(Socket, 0, 6000),
  613. ok.
  614. %% Internal.
  615. do_decode_headers({ok, http_eoh, Rest}, Acc) ->
  616. [Acc, Rest];
  617. do_decode_headers({ok, {http_header, _I, Key, _R, Value}, Rest}, Acc) ->
  618. F = fun(S) when is_atom(S) -> S; (S) -> string:to_lower(S) end,
  619. do_decode_headers(erlang:decode_packet(httph, Rest, []),
  620. [{F(Key), Value}|Acc]).
  621. do_mask(<<>>, _, Acc) ->
  622. Acc;
  623. do_mask(<< O:32, Rest/bits >>, MaskKey, Acc) ->
  624. T = O bxor MaskKey,
  625. do_mask(Rest, MaskKey, << Acc/binary, T:32 >>);
  626. do_mask(<< O:24 >>, MaskKey, Acc) ->
  627. << MaskKey2:24, _:8 >> = << MaskKey:32 >>,
  628. T = O bxor MaskKey2,
  629. << Acc/binary, T:24 >>;
  630. do_mask(<< O:16 >>, MaskKey, Acc) ->
  631. << MaskKey2:16, _:16 >> = << MaskKey:32 >>,
  632. T = O bxor MaskKey2,
  633. << Acc/binary, T:16 >>;
  634. do_mask(<< O:8 >>, MaskKey, Acc) ->
  635. << MaskKey2:8, _:24 >> = << MaskKey:32 >>,
  636. T = O bxor MaskKey2,
  637. << Acc/binary, T:8 >>.