ws_SUITE.erl 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624
  1. %% Copyright (c) 2011, 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. {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. %% This test makes sure the code works even if we wait for a reply
  111. %% before sending the third challenge key in the GET body.
  112. %%
  113. %% This ensures that Cowboy will work fine with proxies on hixie.
  114. ws0(Config) ->
  115. {port, Port} = lists:keyfind(port, 1, Config),
  116. {ok, Socket} = gen_tcp:connect("localhost", Port,
  117. [binary, {active, false}, {packet, raw}]),
  118. ok = gen_tcp:send(Socket,
  119. "GET /websocket HTTP/1.1\r\n"
  120. "Host: localhost\r\n"
  121. "Connection: Upgrade\r\n"
  122. "Upgrade: WebSocket\r\n"
  123. "Origin: http://localhost\r\n"
  124. "Sec-Websocket-Key1: Y\" 4 1Lj!957b8@0H756!i\r\n"
  125. "Sec-Websocket-Key2: 1711 M;4\\74 80<6\r\n"
  126. "\r\n"),
  127. {ok, Handshake} = gen_tcp:recv(Socket, 0, 6000),
  128. {ok, {http_response, {1, 1}, 101, "WebSocket Protocol Handshake"}, Rest}
  129. = erlang:decode_packet(http, Handshake, []),
  130. [Headers, <<>>] = websocket_headers(
  131. erlang:decode_packet(httph, Rest, []), []),
  132. {'Connection', "Upgrade"} = lists:keyfind('Connection', 1, Headers),
  133. {'Upgrade', "WebSocket"} = lists:keyfind('Upgrade', 1, Headers),
  134. {"sec-websocket-location", "ws://localhost/websocket"}
  135. = lists:keyfind("sec-websocket-location", 1, Headers),
  136. {"sec-websocket-origin", "http://localhost"}
  137. = lists:keyfind("sec-websocket-origin", 1, Headers),
  138. ok = gen_tcp:send(Socket, <<15,245,8,18,2,204,133,33>>),
  139. {ok, Body} = gen_tcp:recv(Socket, 0, 6000),
  140. <<169,244,191,103,146,33,149,59,74,104,67,5,99,118,171,236>> = Body,
  141. ok = gen_tcp:send(Socket, << 0, "client_msg", 255 >>),
  142. {ok, << 0, "client_msg", 255 >>} = gen_tcp:recv(Socket, 0, 6000),
  143. {ok, << 0, "websocket_init", 255 >>} = gen_tcp:recv(Socket, 0, 6000),
  144. {ok, << 0, "websocket_handle", 255 >>} = gen_tcp:recv(Socket, 0, 6000),
  145. {ok, << 0, "websocket_handle", 255 >>} = gen_tcp:recv(Socket, 0, 6000),
  146. {ok, << 0, "websocket_handle", 255 >>} = gen_tcp:recv(Socket, 0, 6000),
  147. %% We try to send another HTTP request to make sure
  148. %% the server closed the request.
  149. ok = gen_tcp:send(Socket, [
  150. << 255, 0 >>, %% Close websocket command.
  151. "GET / HTTP/1.1\r\nHost: localhost\r\n\r\n" %% Server should ignore it.
  152. ]),
  153. {ok, << 255, 0 >>} = gen_tcp:recv(Socket, 0, 6000),
  154. {error, closed} = gen_tcp:recv(Socket, 0, 6000),
  155. ok.
  156. ws8(Config) ->
  157. {port, Port} = lists:keyfind(port, 1, Config),
  158. {ok, Socket} = gen_tcp:connect("localhost", Port,
  159. [binary, {active, false}, {packet, raw}]),
  160. ok = gen_tcp:send(Socket, [
  161. "GET /websocket HTTP/1.1\r\n"
  162. "Host: localhost\r\n"
  163. "Connection: Upgrade\r\n"
  164. "Upgrade: websocket\r\n"
  165. "Sec-WebSocket-Origin: http://localhost\r\n"
  166. "Sec-WebSocket-Version: 8\r\n"
  167. "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
  168. "\r\n"]),
  169. {ok, Handshake} = gen_tcp:recv(Socket, 0, 6000),
  170. {ok, {http_response, {1, 1}, 101, "Switching Protocols"}, Rest}
  171. = erlang:decode_packet(http, Handshake, []),
  172. [Headers, <<>>] = websocket_headers(
  173. erlang:decode_packet(httph, Rest, []), []),
  174. {'Connection', "Upgrade"} = lists:keyfind('Connection', 1, Headers),
  175. {'Upgrade', "websocket"} = lists:keyfind('Upgrade', 1, Headers),
  176. {"sec-websocket-accept", "s3pPLMBiTxaQ9kYGzzhZRbK+xOo="}
  177. = lists:keyfind("sec-websocket-accept", 1, Headers),
  178. ok = gen_tcp:send(Socket, << 16#81, 16#85, 16#37, 16#fa, 16#21, 16#3d,
  179. 16#7f, 16#9f, 16#4d, 16#51, 16#58 >>),
  180. {ok, << 1:1, 0:3, 1:4, 0:1, 5:7, "Hello" >>}
  181. = gen_tcp:recv(Socket, 0, 6000),
  182. {ok, << 1:1, 0:3, 1:4, 0:1, 14:7, "websocket_init" >>}
  183. = gen_tcp:recv(Socket, 0, 6000),
  184. {ok, << 1:1, 0:3, 1:4, 0:1, 16:7, "websocket_handle" >>}
  185. = gen_tcp:recv(Socket, 0, 6000),
  186. {ok, << 1:1, 0:3, 1:4, 0:1, 16:7, "websocket_handle" >>}
  187. = gen_tcp:recv(Socket, 0, 6000),
  188. {ok, << 1:1, 0:3, 1:4, 0:1, 16:7, "websocket_handle" >>}
  189. = gen_tcp:recv(Socket, 0, 6000),
  190. ok = gen_tcp:send(Socket, << 1:1, 0:3, 9:4, 0:8 >>), %% ping
  191. {ok, << 1:1, 0:3, 10:4, 0:8 >>} = gen_tcp:recv(Socket, 0, 6000), %% pong
  192. ok = gen_tcp:send(Socket, << 1:1, 0:3, 8:4, 0:8 >>), %% close
  193. {ok, << 1:1, 0:3, 8:4, 0:8 >>} = gen_tcp:recv(Socket, 0, 6000),
  194. {error, closed} = gen_tcp:recv(Socket, 0, 6000),
  195. ok.
  196. ws8_init_shutdown(Config) ->
  197. {port, Port} = lists:keyfind(port, 1, Config),
  198. {ok, Socket} = gen_tcp:connect("localhost", Port,
  199. [binary, {active, false}, {packet, raw}]),
  200. ok = gen_tcp:send(Socket, [
  201. "GET /ws_init_shutdown HTTP/1.1\r\n"
  202. "Host: localhost\r\n"
  203. "Connection: Upgrade\r\n"
  204. "Upgrade: websocket\r\n"
  205. "Sec-WebSocket-Origin: http://localhost\r\n"
  206. "Sec-WebSocket-Version: 8\r\n"
  207. "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
  208. "\r\n"]),
  209. {ok, Handshake} = gen_tcp:recv(Socket, 0, 6000),
  210. {ok, {http_response, {1, 1}, 403, "Forbidden"}, _Rest}
  211. = erlang:decode_packet(http, Handshake, []),
  212. {error, closed} = gen_tcp:recv(Socket, 0, 6000),
  213. ok.
  214. ws8_single_bytes(Config) ->
  215. {port, Port} = lists:keyfind(port, 1, Config),
  216. {ok, Socket} = gen_tcp:connect("localhost", Port,
  217. [binary, {active, false}, {packet, raw}]),
  218. ok = gen_tcp:send(Socket, [
  219. "GET /websocket HTTP/1.1\r\n"
  220. "Host: localhost\r\n"
  221. "Connection: Upgrade\r\n"
  222. "Upgrade: websocket\r\n"
  223. "Sec-WebSocket-Origin: http://localhost\r\n"
  224. "Sec-WebSocket-Version: 8\r\n"
  225. "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
  226. "\r\n"]),
  227. {ok, Handshake} = gen_tcp:recv(Socket, 0, 6000),
  228. {ok, {http_response, {1, 1}, 101, "Switching Protocols"}, Rest}
  229. = erlang:decode_packet(http, Handshake, []),
  230. [Headers, <<>>] = websocket_headers(
  231. erlang:decode_packet(httph, Rest, []), []),
  232. {'Connection', "Upgrade"} = lists:keyfind('Connection', 1, Headers),
  233. {'Upgrade', "websocket"} = lists:keyfind('Upgrade', 1, Headers),
  234. {"sec-websocket-accept", "s3pPLMBiTxaQ9kYGzzhZRbK+xOo="}
  235. = lists:keyfind("sec-websocket-accept", 1, Headers),
  236. ok = gen_tcp:send(Socket, << 16#81 >>), %% send one byte
  237. ok = timer:sleep(100), %% sleep for a period
  238. ok = gen_tcp:send(Socket, << 16#85 >>), %% send another and so on
  239. ok = timer:sleep(100),
  240. ok = gen_tcp:send(Socket, << 16#37 >>),
  241. ok = timer:sleep(100),
  242. ok = gen_tcp:send(Socket, << 16#fa >>),
  243. ok = timer:sleep(100),
  244. ok = gen_tcp:send(Socket, << 16#21 >>),
  245. ok = timer:sleep(100),
  246. ok = gen_tcp:send(Socket, << 16#3d >>),
  247. ok = timer:sleep(100),
  248. ok = gen_tcp:send(Socket, << 16#7f >>),
  249. ok = timer:sleep(100),
  250. ok = gen_tcp:send(Socket, << 16#9f >>),
  251. ok = timer:sleep(100),
  252. ok = gen_tcp:send(Socket, << 16#4d >>),
  253. ok = timer:sleep(100),
  254. ok = gen_tcp:send(Socket, << 16#51 >>),
  255. ok = timer:sleep(100),
  256. ok = gen_tcp:send(Socket, << 16#58 >>),
  257. {ok, << 1:1, 0:3, 1:4, 0:1, 14:7, "websocket_init" >>}
  258. = gen_tcp:recv(Socket, 0, 6000),
  259. {ok, << 1:1, 0:3, 1:4, 0:1, 5:7, "Hello" >>}
  260. = gen_tcp:recv(Socket, 0, 6000),
  261. {ok, << 1:1, 0:3, 1:4, 0:1, 16:7, "websocket_handle" >>}
  262. = gen_tcp:recv(Socket, 0, 6000),
  263. {ok, << 1:1, 0:3, 1:4, 0:1, 16:7, "websocket_handle" >>}
  264. = gen_tcp:recv(Socket, 0, 6000),
  265. {ok, << 1:1, 0:3, 1:4, 0:1, 16:7, "websocket_handle" >>}
  266. = gen_tcp:recv(Socket, 0, 6000),
  267. ok = gen_tcp:send(Socket, << 1:1, 0:3, 9:4, 0:8 >>), %% ping
  268. {ok, << 1:1, 0:3, 10:4, 0:8 >>} = gen_tcp:recv(Socket, 0, 6000), %% pong
  269. ok = gen_tcp:send(Socket, << 1:1, 0:3, 8:4, 0:8 >>), %% close
  270. {ok, << 1:1, 0:3, 8:4, 0:8 >>} = gen_tcp:recv(Socket, 0, 6000),
  271. {error, closed} = gen_tcp:recv(Socket, 0, 6000),
  272. ok.
  273. ws13(Config) ->
  274. {port, Port} = lists:keyfind(port, 1, Config),
  275. {ok, Socket} = gen_tcp:connect("localhost", Port,
  276. [binary, {active, false}, {packet, raw}]),
  277. ok = gen_tcp:send(Socket, [
  278. "GET /websocket HTTP/1.1\r\n"
  279. "Host: localhost\r\n"
  280. "Connection: Upgrade\r\n"
  281. "Origin: http://localhost\r\n"
  282. "Sec-WebSocket-Version: 13\r\n"
  283. "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
  284. "Upgrade: websocket\r\n"
  285. "\r\n"]),
  286. {ok, Handshake} = gen_tcp:recv(Socket, 0, 6000),
  287. {ok, {http_response, {1, 1}, 101, "Switching Protocols"}, Rest}
  288. = erlang:decode_packet(http, Handshake, []),
  289. [Headers, <<>>] = websocket_headers(
  290. erlang:decode_packet(httph, Rest, []), []),
  291. {'Connection', "Upgrade"} = lists:keyfind('Connection', 1, Headers),
  292. {'Upgrade', "websocket"} = lists:keyfind('Upgrade', 1, Headers),
  293. {"sec-websocket-accept", "s3pPLMBiTxaQ9kYGzzhZRbK+xOo="}
  294. = lists:keyfind("sec-websocket-accept", 1, Headers),
  295. %% text
  296. ok = gen_tcp:send(Socket, << 16#81, 16#85, 16#37, 16#fa, 16#21, 16#3d,
  297. 16#7f, 16#9f, 16#4d, 16#51, 16#58 >>),
  298. {ok, << 1:1, 0:3, 1:4, 0:1, 5:7, "Hello" >>}
  299. = gen_tcp:recv(Socket, 0, 6000),
  300. %% binary (empty)
  301. ok = gen_tcp:send(Socket, << 1:1, 0:3, 2:4, 0:8 >>),
  302. {ok, << 1:1, 0:3, 2:4, 0:8 >>} = gen_tcp:recv(Socket, 0, 6000),
  303. %% binary
  304. ok = gen_tcp:send(Socket, << 16#82, 16#85, 16#37, 16#fa, 16#21, 16#3d,
  305. 16#7f, 16#9f, 16#4d, 16#51, 16#58 >>),
  306. {ok, << 1:1, 0:3, 2:4, 0:1, 5:7, "Hello" >>}
  307. = gen_tcp:recv(Socket, 0, 6000),
  308. %% Receives.
  309. {ok, << 1:1, 0:3, 1:4, 0:1, 14:7, "websocket_init" >>}
  310. = gen_tcp:recv(Socket, 0, 6000),
  311. {ok, << 1:1, 0:3, 1:4, 0:1, 16:7, "websocket_handle" >>}
  312. = gen_tcp:recv(Socket, 0, 6000),
  313. {ok, << 1:1, 0:3, 1:4, 0:1, 16:7, "websocket_handle" >>}
  314. = gen_tcp:recv(Socket, 0, 6000),
  315. {ok, << 1:1, 0:3, 1:4, 0:1, 16:7, "websocket_handle" >>}
  316. = gen_tcp:recv(Socket, 0, 6000),
  317. ok = gen_tcp:send(Socket, << 1:1, 0:3, 9:4, 0:8 >>), %% ping
  318. {ok, << 1:1, 0:3, 10:4, 0:8 >>} = gen_tcp:recv(Socket, 0, 6000), %% pong
  319. ok = gen_tcp:send(Socket, << 1:1, 0:3, 8:4, 0:8 >>), %% close
  320. {ok, << 1:1, 0:3, 8:4, 0:8 >>} = gen_tcp:recv(Socket, 0, 6000),
  321. {error, closed} = gen_tcp:recv(Socket, 0, 6000),
  322. ok.
  323. ws_send_close(Config) ->
  324. {port, Port} = lists:keyfind(port, 1, Config),
  325. {ok, Socket} = gen_tcp:connect("localhost", Port,
  326. [binary, {active, false}, {packet, raw}]),
  327. ok = gen_tcp:send(Socket, [
  328. "GET /ws_send_close HTTP/1.1\r\n"
  329. "Host: localhost\r\n"
  330. "Connection: Upgrade\r\n"
  331. "Upgrade: websocket\r\n"
  332. "Sec-WebSocket-Origin: http://localhost\r\n"
  333. "Sec-WebSocket-Version: 8\r\n"
  334. "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
  335. "\r\n"]),
  336. {ok, Handshake} = gen_tcp:recv(Socket, 0, 6000),
  337. {ok, {http_response, {1, 1}, 101, "Switching Protocols"}, Rest}
  338. = erlang:decode_packet(http, Handshake, []),
  339. [Headers, <<>>] = websocket_headers(
  340. erlang:decode_packet(httph, Rest, []), []),
  341. {'Connection', "Upgrade"} = lists:keyfind('Connection', 1, Headers),
  342. {'Upgrade', "websocket"} = lists:keyfind('Upgrade', 1, Headers),
  343. {"sec-websocket-accept", "s3pPLMBiTxaQ9kYGzzhZRbK+xOo="}
  344. = lists:keyfind("sec-websocket-accept", 1, Headers),
  345. %% We catch all frames at once and check them directly.
  346. {ok, Many} = gen_tcp:recv(Socket, 8, 6000),
  347. << 1:1, 0:3, 1:4, 0:1, 4:7, "send",
  348. 1:1, 0:3, 8:4, 0:8 >> = Many,
  349. {error, closed} = gen_tcp:recv(Socket, 0, 6000),
  350. ok.
  351. ws_send_close_payload(Config) ->
  352. {port, Port} = lists:keyfind(port, 1, Config),
  353. {ok, Socket} = gen_tcp:connect("localhost", Port,
  354. [binary, {active, false}, {packet, raw}]),
  355. ok = gen_tcp:send(Socket, [
  356. "GET /ws_send_close_payload HTTP/1.1\r\n"
  357. "Host: localhost\r\n"
  358. "Connection: Upgrade\r\n"
  359. "Upgrade: websocket\r\n"
  360. "Sec-WebSocket-Origin: http://localhost\r\n"
  361. "Sec-WebSocket-Version: 8\r\n"
  362. "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
  363. "\r\n"]),
  364. {ok, Handshake} = gen_tcp:recv(Socket, 0, 6000),
  365. {ok, {http_response, {1, 1}, 101, "Switching Protocols"}, Rest}
  366. = erlang:decode_packet(http, Handshake, []),
  367. [Headers, <<>>] = websocket_headers(
  368. erlang:decode_packet(httph, Rest, []), []),
  369. {'Connection', "Upgrade"} = lists:keyfind('Connection', 1, Headers),
  370. {'Upgrade', "websocket"} = lists:keyfind('Upgrade', 1, Headers),
  371. {"sec-websocket-accept", "s3pPLMBiTxaQ9kYGzzhZRbK+xOo="}
  372. = lists:keyfind("sec-websocket-accept", 1, Headers),
  373. %% We catch all frames at once and check them directly.
  374. {ok, Many} = gen_tcp:recv(Socket, 20, 6000),
  375. << 1:1, 0:3, 1:4, 0:1, 4:7, "send",
  376. 1:1, 0:3, 8:4, 0:1, 12:7, 1001:16, "some text!" >> = Many,
  377. {error, closed} = gen_tcp:recv(Socket, 0, 6000),
  378. ok.
  379. ws_send_many(Config) ->
  380. {port, Port} = lists:keyfind(port, 1, Config),
  381. {ok, Socket} = gen_tcp:connect("localhost", Port,
  382. [binary, {active, false}, {packet, raw}]),
  383. ok = gen_tcp:send(Socket, [
  384. "GET /ws_send_many 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. "\r\n"]),
  392. {ok, Handshake} = gen_tcp:recv(Socket, 0, 6000),
  393. {ok, {http_response, {1, 1}, 101, "Switching Protocols"}, Rest}
  394. = erlang:decode_packet(http, Handshake, []),
  395. [Headers, <<>>] = websocket_headers(
  396. erlang:decode_packet(httph, Rest, []), []),
  397. {'Connection', "Upgrade"} = lists:keyfind('Connection', 1, Headers),
  398. {'Upgrade', "websocket"} = lists:keyfind('Upgrade', 1, Headers),
  399. {"sec-websocket-accept", "s3pPLMBiTxaQ9kYGzzhZRbK+xOo="}
  400. = lists:keyfind("sec-websocket-accept", 1, Headers),
  401. %% We catch all frames at once and check them directly.
  402. {ok, Many} = gen_tcp:recv(Socket, 18, 6000),
  403. << 1:1, 0:3, 1:4, 0:1, 3:7, "one",
  404. 1:1, 0:3, 1:4, 0:1, 3:7, "two",
  405. 1:1, 0:3, 1:4, 0:1, 6:7, "seven!" >> = Many,
  406. ok = gen_tcp:send(Socket, << 1:1, 0:3, 8:4, 0:8 >>), %% close
  407. {ok, << 1:1, 0:3, 8:4, 0:8 >>} = gen_tcp:recv(Socket, 0, 6000),
  408. {error, closed} = gen_tcp:recv(Socket, 0, 6000),
  409. ok.
  410. ws_text_fragments(Config) ->
  411. {port, Port} = lists:keyfind(port, 1, Config),
  412. {ok, Socket} = gen_tcp:connect("localhost", Port,
  413. [binary, {active, false}, {packet, raw}]),
  414. ok = gen_tcp:send(Socket, [
  415. "GET /ws_echo_handler HTTP/1.1\r\n"
  416. "Host: localhost\r\n"
  417. "Connection: Upgrade\r\n"
  418. "Upgrade: websocket\r\n"
  419. "Sec-WebSocket-Origin: http://localhost\r\n"
  420. "Sec-WebSocket-Version: 8\r\n"
  421. "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
  422. "\r\n"]),
  423. {ok, Handshake} = gen_tcp:recv(Socket, 0, 6000),
  424. {ok, {http_response, {1, 1}, 101, "Switching Protocols"}, Rest}
  425. = erlang:decode_packet(http, Handshake, []),
  426. [Headers, <<>>] = websocket_headers(
  427. erlang:decode_packet(httph, Rest, []), []),
  428. {'Connection', "Upgrade"} = lists:keyfind('Connection', 1, Headers),
  429. {'Upgrade', "websocket"} = lists:keyfind('Upgrade', 1, Headers),
  430. {"sec-websocket-accept", "s3pPLMBiTxaQ9kYGzzhZRbK+xOo="}
  431. = lists:keyfind("sec-websocket-accept", 1, Headers),
  432. ok = gen_tcp:send(Socket, [
  433. << 0:1, 0:3, 1:4, 1:1, 5:7 >>,
  434. << 16#37 >>, << 16#fa >>, << 16#21 >>, << 16#3d >>, << 16#7f >>,
  435. << 16#9f >>, << 16#4d >>, << 16#51 >>, << 16#58 >>]),
  436. ok = gen_tcp:send(Socket, [
  437. << 1:1, 0:3, 0:4, 1:1, 5:7 >>,
  438. << 16#37 >>, << 16#fa >>, << 16#21 >>, << 16#3d >>, << 16#7f >>,
  439. << 16#9f >>, << 16#4d >>, << 16#51 >>, << 16#58 >>]),
  440. {ok, << 1:1, 0:3, 1:4, 0:1, 10:7, "HelloHello" >>}
  441. = gen_tcp:recv(Socket, 0, 6000),
  442. ok = gen_tcp:send(Socket, [
  443. %% #1
  444. << 0:1, 0:3, 1:4, 1:1, 5:7 >>,
  445. << 16#37 >>, << 16#fa >>, << 16#21 >>, << 16#3d >>, << 16#7f >>,
  446. << 16#9f >>, << 16#4d >>, << 16#51 >>, << 16#58 >>,
  447. %% #2
  448. << 0:1, 0:3, 0:4, 1:1, 5:7 >>,
  449. << 16#37 >>, << 16#fa >>, << 16#21 >>, << 16#3d >>, << 16#7f >>,
  450. << 16#9f >>, << 16#4d >>, << 16#51 >>, << 16#58 >>,
  451. %% #3
  452. << 1:1, 0:3, 0:4, 1:1, 5:7 >>,
  453. << 16#37 >>, << 16#fa >>, << 16#21 >>, << 16#3d >>, << 16#7f >>,
  454. << 16#9f >>, << 16#4d >>, << 16#51 >>, << 16#58 >>]),
  455. {ok, << 1:1, 0:3, 1:4, 0:1, 15:7, "HelloHelloHello" >>}
  456. = gen_tcp:recv(Socket, 0, 6000),
  457. ok = gen_tcp:send(Socket, << 1:1, 0:3, 8:4, 0:8 >>), %% close
  458. {ok, << 1:1, 0:3, 8:4, 0:8 >>} = gen_tcp:recv(Socket, 0, 6000),
  459. {error, closed} = gen_tcp:recv(Socket, 0, 6000),
  460. ok.
  461. ws_timeout_hibernate(Config) ->
  462. {port, Port} = lists:keyfind(port, 1, Config),
  463. {ok, Socket} = gen_tcp:connect("localhost", Port,
  464. [binary, {active, false}, {packet, raw}]),
  465. ok = gen_tcp:send(Socket, [
  466. "GET /ws_timeout_hibernate HTTP/1.1\r\n"
  467. "Host: localhost\r\n"
  468. "Connection: Upgrade\r\n"
  469. "Upgrade: websocket\r\n"
  470. "Sec-WebSocket-Origin: http://localhost\r\n"
  471. "Sec-WebSocket-Version: 8\r\n"
  472. "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
  473. "\r\n"]),
  474. {ok, Handshake} = gen_tcp:recv(Socket, 0, 6000),
  475. {ok, {http_response, {1, 1}, 101, "Switching Protocols"}, Rest}
  476. = erlang:decode_packet(http, Handshake, []),
  477. [Headers, <<>>] = websocket_headers(
  478. erlang:decode_packet(httph, Rest, []), []),
  479. {'Connection', "Upgrade"} = lists:keyfind('Connection', 1, Headers),
  480. {'Upgrade', "websocket"} = lists:keyfind('Upgrade', 1, Headers),
  481. {"sec-websocket-accept", "s3pPLMBiTxaQ9kYGzzhZRbK+xOo="}
  482. = lists:keyfind("sec-websocket-accept", 1, Headers),
  483. {ok, << 1:1, 0:3, 8:4, 0:8 >>} = gen_tcp:recv(Socket, 0, 6000),
  484. {error, closed} = gen_tcp:recv(Socket, 0, 6000),
  485. ok.
  486. ws_timeout_cancel(Config) ->
  487. %% Erlang messages to a socket should not cancel the timeout
  488. {port, Port} = lists:keyfind(port, 1, Config),
  489. {ok, Socket} = gen_tcp:connect("localhost", Port,
  490. [binary, {active, false}, {packet, raw}]),
  491. ok = gen_tcp:send(Socket, [
  492. "GET /ws_timeout_cancel HTTP/1.1\r\n"
  493. "Host: localhost\r\n"
  494. "Connection: Upgrade\r\n"
  495. "Upgrade: websocket\r\n"
  496. "Sec-WebSocket-Origin: http://localhost\r\n"
  497. "Sec-WebSocket-Version: 8\r\n"
  498. "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
  499. "\r\n"]),
  500. {ok, Handshake} = gen_tcp:recv(Socket, 0, 6000),
  501. {ok, {http_response, {1, 1}, 101, "Switching Protocols"}, Rest}
  502. = erlang:decode_packet(http, Handshake, []),
  503. [Headers, <<>>] = websocket_headers(
  504. erlang:decode_packet(httph, Rest, []), []),
  505. {'Connection', "Upgrade"} = lists:keyfind('Connection', 1, Headers),
  506. {'Upgrade', "websocket"} = lists:keyfind('Upgrade', 1, Headers),
  507. {"sec-websocket-accept", "s3pPLMBiTxaQ9kYGzzhZRbK+xOo="}
  508. = lists:keyfind("sec-websocket-accept", 1, Headers),
  509. {ok, << 1:1, 0:3, 8:4, 0:8 >>} = gen_tcp:recv(Socket, 0, 6000),
  510. {error, closed} = gen_tcp:recv(Socket, 0, 6000),
  511. ok.
  512. ws_timeout_reset(Config) ->
  513. %% Erlang messages across a socket should reset the timeout
  514. {port, Port} = lists:keyfind(port, 1, Config),
  515. {ok, Socket} = gen_tcp:connect("localhost", Port,
  516. [binary, {active, false}, {packet, raw}]),
  517. ok = gen_tcp:send(Socket, [
  518. "GET /ws_timeout_cancel HTTP/1.1\r\n"
  519. "Host: localhost\r\n"
  520. "Connection: Upgrade\r\n"
  521. "Upgrade: WebSocket\r\n"
  522. "Origin: http://localhost\r\n"
  523. "Sec-Websocket-Key1: Y\" 4 1Lj!957b8@0H756!i\r\n"
  524. "Sec-Websocket-Key2: 1711 M;4\\74 80<6\r\n"
  525. "\r\n"]),
  526. {ok, Handshake} = gen_tcp:recv(Socket, 0, 6000),
  527. {ok, {http_response, {1, 1}, 101, "WebSocket Protocol Handshake"}, Rest}
  528. = erlang:decode_packet(http, Handshake, []),
  529. [Headers, <<>>] = websocket_headers(
  530. erlang:decode_packet(httph, Rest, []), []),
  531. {'Connection', "Upgrade"} = lists:keyfind('Connection', 1, Headers),
  532. {'Upgrade', "WebSocket"} = lists:keyfind('Upgrade', 1, Headers),
  533. {"sec-websocket-location", "ws://localhost/ws_timeout_cancel"}
  534. = lists:keyfind("sec-websocket-location", 1, Headers),
  535. {"sec-websocket-origin", "http://localhost"}
  536. = lists:keyfind("sec-websocket-origin", 1, Headers),
  537. ok = gen_tcp:send(Socket, <<15,245,8,18,2,204,133,33>>),
  538. {ok, Body} = gen_tcp:recv(Socket, 0, 6000),
  539. <<169,244,191,103,146,33,149,59,74,104,67,5,99,118,171,236>> = Body,
  540. ok = gen_tcp:send(Socket, << 0, "msg sent", 255 >>),
  541. {ok, << 0, "msg sent", 255 >>}
  542. = gen_tcp:recv(Socket, 0, 6000),
  543. ok = timer:sleep(500),
  544. ok = gen_tcp:send(Socket, << 0, "msg sent", 255 >>),
  545. {ok, << 0, "msg sent", 255 >>}
  546. = gen_tcp:recv(Socket, 0, 6000),
  547. ok = timer:sleep(500),
  548. ok = gen_tcp:send(Socket, << 0, "msg sent", 255 >>),
  549. {ok, << 0, "msg sent", 255 >>}
  550. = gen_tcp:recv(Socket, 0, 6000),
  551. ok = timer:sleep(500),
  552. ok = gen_tcp:send(Socket, << 0, "msg sent", 255 >>),
  553. {ok, << 0, "msg sent", 255 >>}
  554. = gen_tcp:recv(Socket, 0, 6000),
  555. {ok, << 255, 0 >>} = gen_tcp:recv(Socket, 0, 6000),
  556. {error, closed} = gen_tcp:recv(Socket, 0, 6000),
  557. ok.
  558. ws_upgrade_with_opts(Config) ->
  559. {port, Port} = lists:keyfind(port, 1, Config),
  560. {ok, Socket} = gen_tcp:connect("localhost", Port,
  561. [binary, {active, false}, {packet, raw}]),
  562. ok = gen_tcp:send(Socket, [
  563. "GET /ws_upgrade_with_opts HTTP/1.1\r\n"
  564. "Host: localhost\r\n"
  565. "Connection: Upgrade\r\n"
  566. "Upgrade: websocket\r\n"
  567. "Sec-WebSocket-Origin: http://localhost\r\n"
  568. "Sec-WebSocket-Version: 8\r\n"
  569. "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
  570. "\r\n"]),
  571. {ok, Handshake} = gen_tcp:recv(Socket, 0, 6000),
  572. {ok, {http_response, {1, 1}, 101, "Switching Protocols"}, Rest}
  573. = erlang:decode_packet(http, Handshake, []),
  574. [Headers, <<>>] = websocket_headers(
  575. erlang:decode_packet(httph, Rest, []), []),
  576. {'Connection', "Upgrade"} = lists:keyfind('Connection', 1, Headers),
  577. {'Upgrade', "websocket"} = lists:keyfind('Upgrade', 1, Headers),
  578. {"sec-websocket-accept", "s3pPLMBiTxaQ9kYGzzhZRbK+xOo="}
  579. = lists:keyfind("sec-websocket-accept", 1, Headers),
  580. {ok, Response} = gen_tcp:recv(Socket, 9, 6000),
  581. << 1:1, 0:3, 1:4, 0:1, 7:7, "success" >> = Response,
  582. ok = gen_tcp:send(Socket, << 1:1, 0:3, 8:4, 0:8 >>), %% close
  583. {ok, << 1:1, 0:3, 8:4, 0:8 >>} = gen_tcp:recv(Socket, 0, 6000),
  584. {error, closed} = gen_tcp:recv(Socket, 0, 6000),
  585. ok.
  586. %% Internal.
  587. websocket_headers({ok, http_eoh, Rest}, Acc) ->
  588. [Acc, Rest];
  589. websocket_headers({ok, {http_header, _I, Key, _R, Value}, Rest}, Acc) ->
  590. F = fun(S) when is_atom(S) -> S; (S) -> string:to_lower(S) end,
  591. websocket_headers(erlang:decode_packet(httph, Rest, []),
  592. [{F(Key), Value}|Acc]).