ws_SUITE.erl 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581
  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_send_close/1]).
  30. -export([ws_send_close_payload/1]).
  31. -export([ws_send_many/1]).
  32. -export([ws_text_fragments/1]).
  33. -export([ws_timeout_hibernate/1]).
  34. -export([ws_timeout_cancel/1]).
  35. -export([ws_timeout_reset/1]).
  36. -export([ws_upgrade_with_opts/1]).
  37. %% ct.
  38. all() ->
  39. [{group, ws}].
  40. groups() ->
  41. BaseTests = [
  42. ws0,
  43. ws8,
  44. ws8_init_shutdown,
  45. ws8_single_bytes,
  46. ws13,
  47. ws_send_close,
  48. ws_send_close_payload,
  49. ws_send_many,
  50. ws_text_fragments,
  51. ws_timeout_hibernate,
  52. ws_timeout_cancel,
  53. ws_timeout_reset,
  54. ws_upgrade_with_opts
  55. ],
  56. [{ws, [], BaseTests}].
  57. init_per_suite(Config) ->
  58. application:start(inets),
  59. application:start(crypto),
  60. application:start(ranch),
  61. application:start(cowboy),
  62. Config.
  63. end_per_suite(_Config) ->
  64. application:stop(cowboy),
  65. application:stop(ranch),
  66. application:stop(crypto),
  67. application:stop(inets),
  68. ok.
  69. init_per_group(ws, Config) ->
  70. Port = 33080,
  71. cowboy:start_http(ws, 100, [{port, Port}], [
  72. {env, [{dispatch, init_dispatch()}]}
  73. ]),
  74. [{port, Port}|Config].
  75. end_per_group(Listener, _Config) ->
  76. cowboy:stop_listener(Listener),
  77. ok.
  78. %% Dispatch configuration.
  79. init_dispatch() ->
  80. [
  81. {[<<"localhost">>], [
  82. {[<<"websocket">>], websocket_handler, []},
  83. {[<<"ws_echo_handler">>], websocket_echo_handler, []},
  84. {[<<"ws_init_shutdown">>], websocket_handler_init_shutdown, []},
  85. {[<<"ws_send_many">>], ws_send_many_handler, [
  86. {sequence, [
  87. {text, <<"one">>},
  88. {text, <<"two">>},
  89. {text, <<"seven!">>}]}
  90. ]},
  91. {[<<"ws_send_close">>], ws_send_many_handler, [
  92. {sequence, [
  93. {text, <<"send">>},
  94. close,
  95. {text, <<"won't be received">>}]}
  96. ]},
  97. {[<<"ws_send_close_payload">>], ws_send_many_handler, [
  98. {sequence, [
  99. {text, <<"send">>},
  100. {close, 1001, <<"some text!">>},
  101. {text, <<"won't be received">>}]}
  102. ]},
  103. {[<<"ws_timeout_hibernate">>], ws_timeout_hibernate_handler, []},
  104. {[<<"ws_timeout_cancel">>], ws_timeout_cancel_handler, []},
  105. {[<<"ws_upgrade_with_opts">>], ws_upgrade_with_opts_handler,
  106. <<"failure">>}
  107. ]}
  108. ].
  109. %% ws and wss.
  110. %% We do not support hixie76 anymore.
  111. ws0(Config) ->
  112. {port, Port} = lists:keyfind(port, 1, Config),
  113. {ok, Socket} = gen_tcp:connect("localhost", Port,
  114. [binary, {active, false}, {packet, raw}]),
  115. ok = gen_tcp:send(Socket,
  116. "GET /websocket HTTP/1.1\r\n"
  117. "Host: localhost\r\n"
  118. "Connection: Upgrade\r\n"
  119. "Upgrade: WebSocket\r\n"
  120. "Origin: http://localhost\r\n"
  121. "Sec-Websocket-Key1: Y\" 4 1Lj!957b8@0H756!i\r\n"
  122. "Sec-Websocket-Key2: 1711 M;4\\74 80<6\r\n"
  123. "\r\n"),
  124. {ok, Handshake} = gen_tcp:recv(Socket, 0, 6000),
  125. {ok, {http_response, {1, 1}, 400, _}, _}
  126. = erlang:decode_packet(http, Handshake, []).
  127. ws8(Config) ->
  128. {port, Port} = lists:keyfind(port, 1, Config),
  129. {ok, Socket} = gen_tcp:connect("localhost", Port,
  130. [binary, {active, false}, {packet, raw}]),
  131. ok = gen_tcp:send(Socket, [
  132. "GET /websocket HTTP/1.1\r\n"
  133. "Host: localhost\r\n"
  134. "Connection: Upgrade\r\n"
  135. "Upgrade: websocket\r\n"
  136. "Sec-WebSocket-Origin: http://localhost\r\n"
  137. "Sec-WebSocket-Version: 8\r\n"
  138. "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
  139. "\r\n"]),
  140. {ok, Handshake} = gen_tcp:recv(Socket, 0, 6000),
  141. {ok, {http_response, {1, 1}, 101, "Switching Protocols"}, Rest}
  142. = erlang:decode_packet(http, Handshake, []),
  143. [Headers, <<>>] = websocket_headers(
  144. erlang:decode_packet(httph, Rest, []), []),
  145. {'Connection', "Upgrade"} = lists:keyfind('Connection', 1, Headers),
  146. {'Upgrade', "websocket"} = lists:keyfind('Upgrade', 1, Headers),
  147. {"sec-websocket-accept", "s3pPLMBiTxaQ9kYGzzhZRbK+xOo="}
  148. = lists:keyfind("sec-websocket-accept", 1, Headers),
  149. ok = gen_tcp:send(Socket, << 16#81, 16#85, 16#37, 16#fa, 16#21, 16#3d,
  150. 16#7f, 16#9f, 16#4d, 16#51, 16#58 >>),
  151. {ok, << 1:1, 0:3, 1:4, 0:1, 5:7, "Hello" >>}
  152. = gen_tcp:recv(Socket, 0, 6000),
  153. {ok, << 1:1, 0:3, 1:4, 0:1, 14:7, "websocket_init" >>}
  154. = gen_tcp:recv(Socket, 0, 6000),
  155. {ok, << 1:1, 0:3, 1:4, 0:1, 16:7, "websocket_handle" >>}
  156. = gen_tcp:recv(Socket, 0, 6000),
  157. {ok, << 1:1, 0:3, 1:4, 0:1, 16:7, "websocket_handle" >>}
  158. = gen_tcp:recv(Socket, 0, 6000),
  159. {ok, << 1:1, 0:3, 1:4, 0:1, 16:7, "websocket_handle" >>}
  160. = gen_tcp:recv(Socket, 0, 6000),
  161. ok = gen_tcp:send(Socket, << 1:1, 0:3, 9:4, 1:1, 0:7, 0:32 >>), %% ping
  162. {ok, << 1:1, 0:3, 10:4, 0:8 >>} = gen_tcp:recv(Socket, 0, 6000), %% pong
  163. ok = gen_tcp:send(Socket, << 1:1, 0:3, 8:4, 1:1, 0:7, 0:32 >>), %% close
  164. {ok, << 1:1, 0:3, 8:4, 0:8 >>} = gen_tcp:recv(Socket, 0, 6000),
  165. {error, closed} = gen_tcp:recv(Socket, 0, 6000),
  166. ok.
  167. ws8_init_shutdown(Config) ->
  168. {port, Port} = lists:keyfind(port, 1, Config),
  169. {ok, Socket} = gen_tcp:connect("localhost", Port,
  170. [binary, {active, false}, {packet, raw}]),
  171. ok = gen_tcp:send(Socket, [
  172. "GET /ws_init_shutdown HTTP/1.1\r\n"
  173. "Host: localhost\r\n"
  174. "Connection: Upgrade\r\n"
  175. "Upgrade: websocket\r\n"
  176. "Sec-WebSocket-Origin: http://localhost\r\n"
  177. "Sec-WebSocket-Version: 8\r\n"
  178. "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
  179. "\r\n"]),
  180. {ok, Handshake} = gen_tcp:recv(Socket, 0, 6000),
  181. {ok, {http_response, {1, 1}, 403, "Forbidden"}, _Rest}
  182. = erlang:decode_packet(http, Handshake, []),
  183. {error, closed} = gen_tcp:recv(Socket, 0, 6000),
  184. ok.
  185. ws8_single_bytes(Config) ->
  186. {port, Port} = lists:keyfind(port, 1, Config),
  187. {ok, Socket} = gen_tcp:connect("localhost", Port,
  188. [binary, {active, false}, {packet, raw}]),
  189. ok = gen_tcp:send(Socket, [
  190. "GET /websocket HTTP/1.1\r\n"
  191. "Host: localhost\r\n"
  192. "Connection: Upgrade\r\n"
  193. "Upgrade: websocket\r\n"
  194. "Sec-WebSocket-Origin: http://localhost\r\n"
  195. "Sec-WebSocket-Version: 8\r\n"
  196. "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
  197. "\r\n"]),
  198. {ok, Handshake} = gen_tcp:recv(Socket, 0, 6000),
  199. {ok, {http_response, {1, 1}, 101, "Switching Protocols"}, Rest}
  200. = erlang:decode_packet(http, Handshake, []),
  201. [Headers, <<>>] = websocket_headers(
  202. erlang:decode_packet(httph, Rest, []), []),
  203. {'Connection', "Upgrade"} = lists:keyfind('Connection', 1, Headers),
  204. {'Upgrade', "websocket"} = lists:keyfind('Upgrade', 1, Headers),
  205. {"sec-websocket-accept", "s3pPLMBiTxaQ9kYGzzhZRbK+xOo="}
  206. = lists:keyfind("sec-websocket-accept", 1, Headers),
  207. ok = gen_tcp:send(Socket, << 16#81 >>), %% send one byte
  208. ok = timer:sleep(100), %% sleep for a period
  209. ok = gen_tcp:send(Socket, << 16#85 >>), %% send another and so on
  210. ok = timer:sleep(100),
  211. ok = gen_tcp:send(Socket, << 16#37 >>),
  212. ok = timer:sleep(100),
  213. ok = gen_tcp:send(Socket, << 16#fa >>),
  214. ok = timer:sleep(100),
  215. ok = gen_tcp:send(Socket, << 16#21 >>),
  216. ok = timer:sleep(100),
  217. ok = gen_tcp:send(Socket, << 16#3d >>),
  218. ok = timer:sleep(100),
  219. ok = gen_tcp:send(Socket, << 16#7f >>),
  220. ok = timer:sleep(100),
  221. ok = gen_tcp:send(Socket, << 16#9f >>),
  222. ok = timer:sleep(100),
  223. ok = gen_tcp:send(Socket, << 16#4d >>),
  224. ok = timer:sleep(100),
  225. ok = gen_tcp:send(Socket, << 16#51 >>),
  226. ok = timer:sleep(100),
  227. ok = gen_tcp:send(Socket, << 16#58 >>),
  228. {ok, << 1:1, 0:3, 1:4, 0:1, 14:7, "websocket_init" >>}
  229. = gen_tcp:recv(Socket, 0, 6000),
  230. {ok, << 1:1, 0:3, 1:4, 0:1, 5:7, "Hello" >>}
  231. = gen_tcp:recv(Socket, 0, 6000),
  232. {ok, << 1:1, 0:3, 1:4, 0:1, 16:7, "websocket_handle" >>}
  233. = gen_tcp:recv(Socket, 0, 6000),
  234. {ok, << 1:1, 0:3, 1:4, 0:1, 16:7, "websocket_handle" >>}
  235. = gen_tcp:recv(Socket, 0, 6000),
  236. {ok, << 1:1, 0:3, 1:4, 0:1, 16:7, "websocket_handle" >>}
  237. = gen_tcp:recv(Socket, 0, 6000),
  238. ok = gen_tcp:send(Socket, << 1:1, 0:3, 9:4, 1:1, 0:7, 0:32 >>), %% ping
  239. {ok, << 1:1, 0:3, 10:4, 0:8 >>} = gen_tcp:recv(Socket, 0, 6000), %% pong
  240. ok = gen_tcp:send(Socket, << 1:1, 0:3, 8:4, 1:1, 0:7, 0:32 >>), %% close
  241. {ok, << 1:1, 0:3, 8:4, 0:8 >>} = gen_tcp:recv(Socket, 0, 6000),
  242. {error, closed} = gen_tcp:recv(Socket, 0, 6000),
  243. ok.
  244. ws13(Config) ->
  245. {port, Port} = lists:keyfind(port, 1, Config),
  246. {ok, Socket} = gen_tcp:connect("localhost", Port,
  247. [binary, {active, false}, {packet, raw}]),
  248. ok = gen_tcp:send(Socket, [
  249. "GET /websocket HTTP/1.1\r\n"
  250. "Host: localhost\r\n"
  251. "Connection: Upgrade\r\n"
  252. "Origin: http://localhost\r\n"
  253. "Sec-WebSocket-Version: 13\r\n"
  254. "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
  255. "Upgrade: websocket\r\n"
  256. "\r\n"]),
  257. {ok, Handshake} = gen_tcp:recv(Socket, 0, 6000),
  258. {ok, {http_response, {1, 1}, 101, "Switching Protocols"}, Rest}
  259. = erlang:decode_packet(http, Handshake, []),
  260. [Headers, <<>>] = websocket_headers(
  261. erlang:decode_packet(httph, Rest, []), []),
  262. {'Connection', "Upgrade"} = lists:keyfind('Connection', 1, Headers),
  263. {'Upgrade', "websocket"} = lists:keyfind('Upgrade', 1, Headers),
  264. {"sec-websocket-accept", "s3pPLMBiTxaQ9kYGzzhZRbK+xOo="}
  265. = lists:keyfind("sec-websocket-accept", 1, Headers),
  266. %% text
  267. ok = gen_tcp:send(Socket, << 16#81, 16#85, 16#37, 16#fa, 16#21, 16#3d,
  268. 16#7f, 16#9f, 16#4d, 16#51, 16#58 >>),
  269. {ok, << 1:1, 0:3, 1:4, 0:1, 5:7, "Hello" >>}
  270. = gen_tcp:recv(Socket, 0, 6000),
  271. %% binary (empty)
  272. ok = gen_tcp:send(Socket, << 1:1, 0:3, 2:4, 1:1, 0:7, 0:32 >>),
  273. {ok, << 1:1, 0:3, 2:4, 0:8 >>} = gen_tcp:recv(Socket, 0, 6000),
  274. %% binary
  275. ok = gen_tcp:send(Socket, << 16#82, 16#85, 16#37, 16#fa, 16#21, 16#3d,
  276. 16#7f, 16#9f, 16#4d, 16#51, 16#58 >>),
  277. {ok, << 1:1, 0:3, 2:4, 0:1, 5:7, "Hello" >>}
  278. = gen_tcp:recv(Socket, 0, 6000),
  279. %% Receives.
  280. {ok, << 1:1, 0:3, 1:4, 0:1, 14:7, "websocket_init" >>}
  281. = gen_tcp:recv(Socket, 0, 6000),
  282. {ok, << 1:1, 0:3, 1:4, 0:1, 16:7, "websocket_handle" >>}
  283. = gen_tcp:recv(Socket, 0, 6000),
  284. {ok, << 1:1, 0:3, 1:4, 0:1, 16:7, "websocket_handle" >>}
  285. = gen_tcp:recv(Socket, 0, 6000),
  286. {ok, << 1:1, 0:3, 1:4, 0:1, 16:7, "websocket_handle" >>}
  287. = gen_tcp:recv(Socket, 0, 6000),
  288. ok = gen_tcp:send(Socket, << 1:1, 0:3, 9:4, 1:1, 0:7, 0:32 >>), %% ping
  289. {ok, << 1:1, 0:3, 10:4, 0:8 >>} = gen_tcp:recv(Socket, 0, 6000), %% pong
  290. ok = gen_tcp:send(Socket, << 1:1, 0:3, 8:4, 1:1, 0:7, 0:32 >>), %% close
  291. {ok, << 1:1, 0:3, 8:4, 0:8 >>} = gen_tcp:recv(Socket, 0, 6000),
  292. {error, closed} = gen_tcp:recv(Socket, 0, 6000),
  293. ok.
  294. ws_send_close(Config) ->
  295. {port, Port} = lists:keyfind(port, 1, Config),
  296. {ok, Socket} = gen_tcp:connect("localhost", Port,
  297. [binary, {active, false}, {packet, raw}]),
  298. ok = gen_tcp:send(Socket, [
  299. "GET /ws_send_close HTTP/1.1\r\n"
  300. "Host: localhost\r\n"
  301. "Connection: Upgrade\r\n"
  302. "Upgrade: websocket\r\n"
  303. "Sec-WebSocket-Origin: http://localhost\r\n"
  304. "Sec-WebSocket-Version: 8\r\n"
  305. "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
  306. "\r\n"]),
  307. {ok, Handshake} = gen_tcp:recv(Socket, 0, 6000),
  308. {ok, {http_response, {1, 1}, 101, "Switching Protocols"}, Rest}
  309. = erlang:decode_packet(http, Handshake, []),
  310. [Headers, <<>>] = websocket_headers(
  311. erlang:decode_packet(httph, Rest, []), []),
  312. {'Connection', "Upgrade"} = lists:keyfind('Connection', 1, Headers),
  313. {'Upgrade', "websocket"} = lists:keyfind('Upgrade', 1, Headers),
  314. {"sec-websocket-accept", "s3pPLMBiTxaQ9kYGzzhZRbK+xOo="}
  315. = lists:keyfind("sec-websocket-accept", 1, Headers),
  316. %% We catch all frames at once and check them directly.
  317. {ok, Many} = gen_tcp:recv(Socket, 8, 6000),
  318. << 1:1, 0:3, 1:4, 0:1, 4:7, "send",
  319. 1:1, 0:3, 8:4, 0:8 >> = Many,
  320. {error, closed} = gen_tcp:recv(Socket, 0, 6000),
  321. ok.
  322. ws_send_close_payload(Config) ->
  323. {port, Port} = lists:keyfind(port, 1, Config),
  324. {ok, Socket} = gen_tcp:connect("localhost", Port,
  325. [binary, {active, false}, {packet, raw}]),
  326. ok = gen_tcp:send(Socket, [
  327. "GET /ws_send_close_payload HTTP/1.1\r\n"
  328. "Host: localhost\r\n"
  329. "Connection: Upgrade\r\n"
  330. "Upgrade: websocket\r\n"
  331. "Sec-WebSocket-Origin: http://localhost\r\n"
  332. "Sec-WebSocket-Version: 8\r\n"
  333. "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
  334. "\r\n"]),
  335. {ok, Handshake} = gen_tcp:recv(Socket, 0, 6000),
  336. {ok, {http_response, {1, 1}, 101, "Switching Protocols"}, Rest}
  337. = erlang:decode_packet(http, Handshake, []),
  338. [Headers, <<>>] = websocket_headers(
  339. erlang:decode_packet(httph, Rest, []), []),
  340. {'Connection', "Upgrade"} = lists:keyfind('Connection', 1, Headers),
  341. {'Upgrade', "websocket"} = lists:keyfind('Upgrade', 1, Headers),
  342. {"sec-websocket-accept", "s3pPLMBiTxaQ9kYGzzhZRbK+xOo="}
  343. = lists:keyfind("sec-websocket-accept", 1, Headers),
  344. %% We catch all frames at once and check them directly.
  345. {ok, Many} = gen_tcp:recv(Socket, 20, 6000),
  346. << 1:1, 0:3, 1:4, 0:1, 4:7, "send",
  347. 1:1, 0:3, 8:4, 0:1, 12:7, 1001:16, "some text!" >> = Many,
  348. {error, closed} = gen_tcp:recv(Socket, 0, 6000),
  349. ok.
  350. ws_send_many(Config) ->
  351. {port, Port} = lists:keyfind(port, 1, Config),
  352. {ok, Socket} = gen_tcp:connect("localhost", Port,
  353. [binary, {active, false}, {packet, raw}]),
  354. ok = gen_tcp:send(Socket, [
  355. "GET /ws_send_many HTTP/1.1\r\n"
  356. "Host: localhost\r\n"
  357. "Connection: Upgrade\r\n"
  358. "Upgrade: websocket\r\n"
  359. "Sec-WebSocket-Origin: http://localhost\r\n"
  360. "Sec-WebSocket-Version: 8\r\n"
  361. "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
  362. "\r\n"]),
  363. {ok, Handshake} = gen_tcp:recv(Socket, 0, 6000),
  364. {ok, {http_response, {1, 1}, 101, "Switching Protocols"}, Rest}
  365. = erlang:decode_packet(http, Handshake, []),
  366. [Headers, <<>>] = websocket_headers(
  367. erlang:decode_packet(httph, Rest, []), []),
  368. {'Connection', "Upgrade"} = lists:keyfind('Connection', 1, Headers),
  369. {'Upgrade', "websocket"} = lists:keyfind('Upgrade', 1, Headers),
  370. {"sec-websocket-accept", "s3pPLMBiTxaQ9kYGzzhZRbK+xOo="}
  371. = lists:keyfind("sec-websocket-accept", 1, Headers),
  372. %% We catch all frames at once and check them directly.
  373. {ok, Many} = gen_tcp:recv(Socket, 18, 6000),
  374. << 1:1, 0:3, 1:4, 0:1, 3:7, "one",
  375. 1:1, 0:3, 1:4, 0:1, 3:7, "two",
  376. 1:1, 0:3, 1:4, 0:1, 6:7, "seven!" >> = Many,
  377. ok = gen_tcp:send(Socket, << 1:1, 0:3, 8:4, 1:1, 0:7, 0:32 >>), %% close
  378. {ok, << 1:1, 0:3, 8:4, 0:8 >>} = gen_tcp:recv(Socket, 0, 6000),
  379. {error, closed} = gen_tcp:recv(Socket, 0, 6000),
  380. ok.
  381. ws_text_fragments(Config) ->
  382. {port, Port} = lists:keyfind(port, 1, Config),
  383. {ok, Socket} = gen_tcp:connect("localhost", Port,
  384. [binary, {active, false}, {packet, raw}]),
  385. ok = gen_tcp:send(Socket, [
  386. "GET /ws_echo_handler HTTP/1.1\r\n"
  387. "Host: localhost\r\n"
  388. "Connection: Upgrade\r\n"
  389. "Upgrade: websocket\r\n"
  390. "Sec-WebSocket-Origin: http://localhost\r\n"
  391. "Sec-WebSocket-Version: 8\r\n"
  392. "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
  393. "\r\n"]),
  394. {ok, Handshake} = gen_tcp:recv(Socket, 0, 6000),
  395. {ok, {http_response, {1, 1}, 101, "Switching Protocols"}, Rest}
  396. = erlang:decode_packet(http, Handshake, []),
  397. [Headers, <<>>] = websocket_headers(
  398. erlang:decode_packet(httph, Rest, []), []),
  399. {'Connection', "Upgrade"} = lists:keyfind('Connection', 1, Headers),
  400. {'Upgrade', "websocket"} = lists:keyfind('Upgrade', 1, Headers),
  401. {"sec-websocket-accept", "s3pPLMBiTxaQ9kYGzzhZRbK+xOo="}
  402. = lists:keyfind("sec-websocket-accept", 1, Headers),
  403. ok = gen_tcp:send(Socket, [
  404. << 0:1, 0:3, 1:4, 1:1, 5:7 >>,
  405. << 16#37 >>, << 16#fa >>, << 16#21 >>, << 16#3d >>, << 16#7f >>,
  406. << 16#9f >>, << 16#4d >>, << 16#51 >>, << 16#58 >>]),
  407. ok = gen_tcp:send(Socket, [
  408. << 1:1, 0:3, 0:4, 1:1, 5:7 >>,
  409. << 16#37 >>, << 16#fa >>, << 16#21 >>, << 16#3d >>, << 16#7f >>,
  410. << 16#9f >>, << 16#4d >>, << 16#51 >>, << 16#58 >>]),
  411. {ok, << 1:1, 0:3, 1:4, 0:1, 10:7, "HelloHello" >>}
  412. = gen_tcp:recv(Socket, 0, 6000),
  413. ok = gen_tcp:send(Socket, [
  414. %% #1
  415. << 0:1, 0:3, 1:4, 1:1, 5:7 >>,
  416. << 16#37 >>, << 16#fa >>, << 16#21 >>, << 16#3d >>, << 16#7f >>,
  417. << 16#9f >>, << 16#4d >>, << 16#51 >>, << 16#58 >>,
  418. %% #2
  419. << 0:1, 0:3, 0:4, 1:1, 5:7 >>,
  420. << 16#37 >>, << 16#fa >>, << 16#21 >>, << 16#3d >>, << 16#7f >>,
  421. << 16#9f >>, << 16#4d >>, << 16#51 >>, << 16#58 >>,
  422. %% #3
  423. << 1:1, 0:3, 0:4, 1:1, 5:7 >>,
  424. << 16#37 >>, << 16#fa >>, << 16#21 >>, << 16#3d >>, << 16#7f >>,
  425. << 16#9f >>, << 16#4d >>, << 16#51 >>, << 16#58 >>]),
  426. {ok, << 1:1, 0:3, 1:4, 0:1, 15:7, "HelloHelloHello" >>}
  427. = gen_tcp:recv(Socket, 0, 6000),
  428. ok = gen_tcp:send(Socket, << 1:1, 0:3, 8:4, 1:1, 0:7, 0:32 >>), %% close
  429. {ok, << 1:1, 0:3, 8:4, 0:8 >>} = gen_tcp:recv(Socket, 0, 6000),
  430. {error, closed} = gen_tcp:recv(Socket, 0, 6000),
  431. ok.
  432. ws_timeout_hibernate(Config) ->
  433. {port, Port} = lists:keyfind(port, 1, Config),
  434. {ok, Socket} = gen_tcp:connect("localhost", Port,
  435. [binary, {active, false}, {packet, raw}]),
  436. ok = gen_tcp:send(Socket, [
  437. "GET /ws_timeout_hibernate HTTP/1.1\r\n"
  438. "Host: localhost\r\n"
  439. "Connection: Upgrade\r\n"
  440. "Upgrade: websocket\r\n"
  441. "Sec-WebSocket-Origin: http://localhost\r\n"
  442. "Sec-WebSocket-Version: 8\r\n"
  443. "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
  444. "\r\n"]),
  445. {ok, Handshake} = gen_tcp:recv(Socket, 0, 6000),
  446. {ok, {http_response, {1, 1}, 101, "Switching Protocols"}, Rest}
  447. = erlang:decode_packet(http, Handshake, []),
  448. [Headers, <<>>] = websocket_headers(
  449. erlang:decode_packet(httph, Rest, []), []),
  450. {'Connection', "Upgrade"} = lists:keyfind('Connection', 1, Headers),
  451. {'Upgrade', "websocket"} = lists:keyfind('Upgrade', 1, Headers),
  452. {"sec-websocket-accept", "s3pPLMBiTxaQ9kYGzzhZRbK+xOo="}
  453. = lists:keyfind("sec-websocket-accept", 1, Headers),
  454. {ok, << 1:1, 0:3, 8:4, 0:1, 2:7, 1000:16 >>} = gen_tcp:recv(Socket, 0, 6000),
  455. {error, closed} = gen_tcp:recv(Socket, 0, 6000),
  456. ok.
  457. ws_timeout_cancel(Config) ->
  458. %% Erlang messages to a socket should not cancel the timeout
  459. {port, Port} = lists:keyfind(port, 1, Config),
  460. {ok, Socket} = gen_tcp:connect("localhost", Port,
  461. [binary, {active, false}, {packet, raw}]),
  462. ok = gen_tcp:send(Socket, [
  463. "GET /ws_timeout_cancel HTTP/1.1\r\n"
  464. "Host: localhost\r\n"
  465. "Connection: Upgrade\r\n"
  466. "Upgrade: websocket\r\n"
  467. "Sec-WebSocket-Origin: http://localhost\r\n"
  468. "Sec-WebSocket-Version: 8\r\n"
  469. "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
  470. "\r\n"]),
  471. {ok, Handshake} = gen_tcp:recv(Socket, 0, 6000),
  472. {ok, {http_response, {1, 1}, 101, "Switching Protocols"}, Rest}
  473. = erlang:decode_packet(http, Handshake, []),
  474. [Headers, <<>>] = websocket_headers(
  475. erlang:decode_packet(httph, Rest, []), []),
  476. {'Connection', "Upgrade"} = lists:keyfind('Connection', 1, Headers),
  477. {'Upgrade', "websocket"} = lists:keyfind('Upgrade', 1, Headers),
  478. {"sec-websocket-accept", "s3pPLMBiTxaQ9kYGzzhZRbK+xOo="}
  479. = lists:keyfind("sec-websocket-accept", 1, Headers),
  480. {ok, << 1:1, 0:3, 8:4, 0:1, 2:7, 1000:16 >>} = gen_tcp:recv(Socket, 0, 6000),
  481. {error, closed} = gen_tcp:recv(Socket, 0, 6000),
  482. ok.
  483. ws_timeout_reset(Config) ->
  484. %% Erlang messages across a socket should reset the timeout
  485. {port, Port} = lists:keyfind(port, 1, Config),
  486. {ok, Socket} = gen_tcp:connect("localhost", Port,
  487. [binary, {active, false}, {packet, raw}]),
  488. ok = gen_tcp:send(Socket, [
  489. "GET /ws_timeout_cancel HTTP/1.1\r\n"
  490. "Host: localhost\r\n"
  491. "Connection: Upgrade\r\n"
  492. "Upgrade: websocket\r\n"
  493. "Sec-WebSocket-Origin: http://localhost\r\n"
  494. "Sec-Websocket-Version: 13\r\n"
  495. "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
  496. "\r\n"]),
  497. {ok, Handshake} = gen_tcp:recv(Socket, 0, 6000),
  498. {ok, {http_response, {1, 1}, 101, "Switching Protocols"}, Rest}
  499. = erlang:decode_packet(http, Handshake, []),
  500. [Headers, <<>>] = websocket_headers(
  501. erlang:decode_packet(httph, Rest, []), []),
  502. {'Connection', "Upgrade"} = lists:keyfind('Connection', 1, Headers),
  503. {'Upgrade', "websocket"} = lists:keyfind('Upgrade', 1, Headers),
  504. {"sec-websocket-accept", "s3pPLMBiTxaQ9kYGzzhZRbK+xOo="}
  505. = lists:keyfind("sec-websocket-accept", 1, Headers),
  506. [begin
  507. ok = gen_tcp:send(Socket, << 16#81, 16#85, 16#37, 16#fa, 16#21, 16#3d,
  508. 16#7f, 16#9f, 16#4d, 16#51, 16#58 >>),
  509. {ok, << 1:1, 0:3, 1:4, 0:1, 5:7, "Hello" >>}
  510. = gen_tcp:recv(Socket, 0, 6000),
  511. ok = timer:sleep(500)
  512. end || _ <- [1, 2, 3, 4]],
  513. {ok, << 1:1, 0:3, 8:4, 0:1, 2:7, 1000:16 >>} = gen_tcp:recv(Socket, 0, 6000),
  514. {error, closed} = gen_tcp:recv(Socket, 0, 6000),
  515. ok.
  516. ws_upgrade_with_opts(Config) ->
  517. {port, Port} = lists:keyfind(port, 1, Config),
  518. {ok, Socket} = gen_tcp:connect("localhost", Port,
  519. [binary, {active, false}, {packet, raw}]),
  520. ok = gen_tcp:send(Socket, [
  521. "GET /ws_upgrade_with_opts HTTP/1.1\r\n"
  522. "Host: localhost\r\n"
  523. "Connection: Upgrade\r\n"
  524. "Upgrade: websocket\r\n"
  525. "Sec-WebSocket-Origin: http://localhost\r\n"
  526. "Sec-WebSocket-Version: 8\r\n"
  527. "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
  528. "\r\n"]),
  529. {ok, Handshake} = gen_tcp:recv(Socket, 0, 6000),
  530. {ok, {http_response, {1, 1}, 101, "Switching Protocols"}, Rest}
  531. = erlang:decode_packet(http, Handshake, []),
  532. [Headers, <<>>] = websocket_headers(
  533. erlang:decode_packet(httph, Rest, []), []),
  534. {'Connection', "Upgrade"} = lists:keyfind('Connection', 1, Headers),
  535. {'Upgrade', "websocket"} = lists:keyfind('Upgrade', 1, Headers),
  536. {"sec-websocket-accept", "s3pPLMBiTxaQ9kYGzzhZRbK+xOo="}
  537. = lists:keyfind("sec-websocket-accept", 1, Headers),
  538. {ok, Response} = gen_tcp:recv(Socket, 9, 6000),
  539. << 1:1, 0:3, 1:4, 0:1, 7:7, "success" >> = Response,
  540. ok = gen_tcp:send(Socket, << 1:1, 0:3, 8:4, 1:1, 0:7, 0:32 >>), %% close
  541. {ok, << 1:1, 0:3, 8:4, 0:8 >>} = gen_tcp:recv(Socket, 0, 6000),
  542. {error, closed} = gen_tcp:recv(Socket, 0, 6000),
  543. ok.
  544. %% Internal.
  545. websocket_headers({ok, http_eoh, Rest}, Acc) ->
  546. [Acc, Rest];
  547. websocket_headers({ok, {http_header, _I, Key, _R, Value}, Rest}, Acc) ->
  548. F = fun(S) when is_atom(S) -> S; (S) -> string:to_lower(S) end,
  549. websocket_headers(erlang:decode_packet(httph, Rest, []),
  550. [{F(Key), Value}|Acc]).