ws_SUITE.erl 28 KB

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