ws_SUITE.erl 28 KB

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