ws_SUITE.erl 24 KB

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