ws_SUITE.erl 27 KB

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