ws_SUITE.erl 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433
  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_many/1]).
  30. -export([ws_text_fragments/1]).
  31. -export([ws_timeout_hibernate/1]).
  32. %% ct.
  33. all() ->
  34. [{group, ws}].
  35. groups() ->
  36. BaseTests = [
  37. ws0,
  38. ws8,
  39. ws8_init_shutdown,
  40. ws8_single_bytes,
  41. ws13,
  42. ws_send_many,
  43. ws_text_fragments,
  44. ws_timeout_hibernate
  45. ],
  46. [{ws, [], BaseTests}].
  47. init_per_suite(Config) ->
  48. application:start(inets),
  49. application:start(crypto),
  50. application:start(ranch),
  51. application:start(cowboy),
  52. Config.
  53. end_per_suite(_Config) ->
  54. application:stop(cowboy),
  55. application:stop(ranch),
  56. application:stop(crypto),
  57. application:stop(inets),
  58. ok.
  59. init_per_group(ws, Config) ->
  60. Port = 33080,
  61. cowboy:start_http(ws, 100, [{port, Port}], [
  62. {dispatch, init_dispatch()}
  63. ]),
  64. [{port, Port}|Config].
  65. end_per_group(Listener, _Config) ->
  66. cowboy:stop_listener(Listener),
  67. ok.
  68. %% Dispatch configuration.
  69. init_dispatch() ->
  70. [
  71. {[<<"localhost">>], [
  72. {[<<"websocket">>], websocket_handler, []},
  73. {[<<"ws_echo_handler">>], websocket_echo_handler, []},
  74. {[<<"ws_init_shutdown">>], websocket_handler_init_shutdown, []},
  75. {[<<"ws_send_many">>], ws_send_many_handler, []},
  76. {[<<"ws_timeout_hibernate">>], ws_timeout_hibernate_handler, []}
  77. ]}
  78. ].
  79. %% ws and wss.
  80. %% This test makes sure the code works even if we wait for a reply
  81. %% before sending the third challenge key in the GET body.
  82. %%
  83. %% This ensures that Cowboy will work fine with proxies on hixie.
  84. ws0(Config) ->
  85. {port, Port} = lists:keyfind(port, 1, Config),
  86. {ok, Socket} = gen_tcp:connect("localhost", Port,
  87. [binary, {active, false}, {packet, raw}]),
  88. ok = gen_tcp:send(Socket,
  89. "GET /websocket HTTP/1.1\r\n"
  90. "Host: localhost\r\n"
  91. "Connection: Upgrade\r\n"
  92. "Upgrade: WebSocket\r\n"
  93. "Origin: http://localhost\r\n"
  94. "Sec-Websocket-Key1: Y\" 4 1Lj!957b8@0H756!i\r\n"
  95. "Sec-Websocket-Key2: 1711 M;4\\74 80<6\r\n"
  96. "\r\n"),
  97. {ok, Handshake} = gen_tcp:recv(Socket, 0, 6000),
  98. {ok, {http_response, {1, 1}, 101, "WebSocket Protocol Handshake"}, Rest}
  99. = erlang:decode_packet(http, Handshake, []),
  100. [Headers, <<>>] = websocket_headers(
  101. erlang:decode_packet(httph, Rest, []), []),
  102. {'Connection', "Upgrade"} = lists:keyfind('Connection', 1, Headers),
  103. {'Upgrade', "WebSocket"} = lists:keyfind('Upgrade', 1, Headers),
  104. {"sec-websocket-location", "ws://localhost/websocket"}
  105. = lists:keyfind("sec-websocket-location", 1, Headers),
  106. {"sec-websocket-origin", "http://localhost"}
  107. = lists:keyfind("sec-websocket-origin", 1, Headers),
  108. ok = gen_tcp:send(Socket, <<15,245,8,18,2,204,133,33>>),
  109. {ok, Body} = gen_tcp:recv(Socket, 0, 6000),
  110. <<169,244,191,103,146,33,149,59,74,104,67,5,99,118,171,236>> = Body,
  111. ok = gen_tcp:send(Socket, << 0, "client_msg", 255 >>),
  112. {ok, << 0, "client_msg", 255 >>} = gen_tcp:recv(Socket, 0, 6000),
  113. {ok, << 0, "websocket_init", 255 >>} = gen_tcp:recv(Socket, 0, 6000),
  114. {ok, << 0, "websocket_handle", 255 >>} = gen_tcp:recv(Socket, 0, 6000),
  115. {ok, << 0, "websocket_handle", 255 >>} = gen_tcp:recv(Socket, 0, 6000),
  116. {ok, << 0, "websocket_handle", 255 >>} = gen_tcp:recv(Socket, 0, 6000),
  117. %% We try to send another HTTP request to make sure
  118. %% the server closed the request.
  119. ok = gen_tcp:send(Socket, [
  120. << 255, 0 >>, %% Close websocket command.
  121. "GET / HTTP/1.1\r\nHost: localhost\r\n\r\n" %% Server should ignore it.
  122. ]),
  123. {ok, << 255, 0 >>} = gen_tcp:recv(Socket, 0, 6000),
  124. {error, closed} = gen_tcp:recv(Socket, 0, 6000),
  125. ok.
  126. ws8(Config) ->
  127. {port, Port} = lists:keyfind(port, 1, Config),
  128. {ok, Socket} = gen_tcp:connect("localhost", Port,
  129. [binary, {active, false}, {packet, raw}]),
  130. ok = gen_tcp:send(Socket, [
  131. "GET /websocket HTTP/1.1\r\n"
  132. "Host: localhost\r\n"
  133. "Connection: Upgrade\r\n"
  134. "Upgrade: websocket\r\n"
  135. "Sec-WebSocket-Origin: http://localhost\r\n"
  136. "Sec-WebSocket-Version: 8\r\n"
  137. "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
  138. "\r\n"]),
  139. {ok, Handshake} = gen_tcp:recv(Socket, 0, 6000),
  140. {ok, {http_response, {1, 1}, 101, "Switching Protocols"}, Rest}
  141. = erlang:decode_packet(http, Handshake, []),
  142. [Headers, <<>>] = websocket_headers(
  143. erlang:decode_packet(httph, Rest, []), []),
  144. {'Connection', "Upgrade"} = lists:keyfind('Connection', 1, Headers),
  145. {'Upgrade', "websocket"} = lists:keyfind('Upgrade', 1, Headers),
  146. {"sec-websocket-accept", "s3pPLMBiTxaQ9kYGzzhZRbK+xOo="}
  147. = lists:keyfind("sec-websocket-accept", 1, Headers),
  148. ok = gen_tcp:send(Socket, << 16#81, 16#85, 16#37, 16#fa, 16#21, 16#3d,
  149. 16#7f, 16#9f, 16#4d, 16#51, 16#58 >>),
  150. {ok, << 1:1, 0:3, 1:4, 0:1, 5:7, "Hello" >>}
  151. = gen_tcp:recv(Socket, 0, 6000),
  152. {ok, << 1:1, 0:3, 1:4, 0:1, 14:7, "websocket_init" >>}
  153. = gen_tcp:recv(Socket, 0, 6000),
  154. {ok, << 1:1, 0:3, 1:4, 0:1, 16:7, "websocket_handle" >>}
  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 = gen_tcp:send(Socket, << 1:1, 0:3, 9:4, 0:8 >>), %% ping
  161. {ok, << 1:1, 0:3, 10:4, 0:8 >>} = gen_tcp:recv(Socket, 0, 6000), %% pong
  162. ok = gen_tcp:send(Socket, << 1:1, 0:3, 8:4, 0:8 >>), %% close
  163. {ok, << 1:1, 0:3, 8:4, 0:8 >>} = gen_tcp:recv(Socket, 0, 6000),
  164. {error, closed} = gen_tcp:recv(Socket, 0, 6000),
  165. ok.
  166. ws8_init_shutdown(Config) ->
  167. {port, Port} = lists:keyfind(port, 1, Config),
  168. {ok, Socket} = gen_tcp:connect("localhost", Port,
  169. [binary, {active, false}, {packet, raw}]),
  170. ok = gen_tcp:send(Socket, [
  171. "GET /ws_init_shutdown HTTP/1.1\r\n"
  172. "Host: localhost\r\n"
  173. "Connection: Upgrade\r\n"
  174. "Upgrade: websocket\r\n"
  175. "Sec-WebSocket-Origin: http://localhost\r\n"
  176. "Sec-WebSocket-Version: 8\r\n"
  177. "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
  178. "\r\n"]),
  179. {ok, Handshake} = gen_tcp:recv(Socket, 0, 6000),
  180. {ok, {http_response, {1, 1}, 403, "Forbidden"}, _Rest}
  181. = erlang:decode_packet(http, Handshake, []),
  182. {error, closed} = gen_tcp:recv(Socket, 0, 6000),
  183. ok.
  184. ws8_single_bytes(Config) ->
  185. {port, Port} = lists:keyfind(port, 1, Config),
  186. {ok, Socket} = gen_tcp:connect("localhost", Port,
  187. [binary, {active, false}, {packet, raw}]),
  188. ok = gen_tcp:send(Socket, [
  189. "GET /websocket HTTP/1.1\r\n"
  190. "Host: localhost\r\n"
  191. "Connection: Upgrade\r\n"
  192. "Upgrade: websocket\r\n"
  193. "Sec-WebSocket-Origin: http://localhost\r\n"
  194. "Sec-WebSocket-Version: 8\r\n"
  195. "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
  196. "\r\n"]),
  197. {ok, Handshake} = gen_tcp:recv(Socket, 0, 6000),
  198. {ok, {http_response, {1, 1}, 101, "Switching Protocols"}, Rest}
  199. = erlang:decode_packet(http, Handshake, []),
  200. [Headers, <<>>] = websocket_headers(
  201. erlang:decode_packet(httph, Rest, []), []),
  202. {'Connection', "Upgrade"} = lists:keyfind('Connection', 1, Headers),
  203. {'Upgrade', "websocket"} = lists:keyfind('Upgrade', 1, Headers),
  204. {"sec-websocket-accept", "s3pPLMBiTxaQ9kYGzzhZRbK+xOo="}
  205. = lists:keyfind("sec-websocket-accept", 1, Headers),
  206. ok = gen_tcp:send(Socket, << 16#81 >>), %% send one byte
  207. ok = timer:sleep(100), %% sleep for a period
  208. ok = gen_tcp:send(Socket, << 16#85 >>), %% send another and so on
  209. ok = timer:sleep(100),
  210. ok = gen_tcp:send(Socket, << 16#37 >>),
  211. ok = timer:sleep(100),
  212. ok = gen_tcp:send(Socket, << 16#fa >>),
  213. ok = timer:sleep(100),
  214. ok = gen_tcp:send(Socket, << 16#21 >>),
  215. ok = timer:sleep(100),
  216. ok = gen_tcp:send(Socket, << 16#3d >>),
  217. ok = timer:sleep(100),
  218. ok = gen_tcp:send(Socket, << 16#7f >>),
  219. ok = timer:sleep(100),
  220. ok = gen_tcp:send(Socket, << 16#9f >>),
  221. ok = timer:sleep(100),
  222. ok = gen_tcp:send(Socket, << 16#4d >>),
  223. ok = timer:sleep(100),
  224. ok = gen_tcp:send(Socket, << 16#51 >>),
  225. ok = timer:sleep(100),
  226. ok = gen_tcp:send(Socket, << 16#58 >>),
  227. {ok, << 1:1, 0:3, 1:4, 0:1, 14:7, "websocket_init" >>}
  228. = gen_tcp:recv(Socket, 0, 6000),
  229. {ok, << 1:1, 0:3, 1:4, 0:1, 5:7, "Hello" >>}
  230. = gen_tcp:recv(Socket, 0, 6000),
  231. {ok, << 1:1, 0:3, 1:4, 0:1, 16:7, "websocket_handle" >>}
  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 = gen_tcp:send(Socket, << 1:1, 0:3, 9:4, 0:8 >>), %% ping
  238. {ok, << 1:1, 0:3, 10:4, 0:8 >>} = gen_tcp:recv(Socket, 0, 6000), %% pong
  239. ok = gen_tcp:send(Socket, << 1:1, 0:3, 8:4, 0:8 >>), %% close
  240. {ok, << 1:1, 0:3, 8:4, 0:8 >>} = gen_tcp:recv(Socket, 0, 6000),
  241. {error, closed} = gen_tcp:recv(Socket, 0, 6000),
  242. ok.
  243. ws13(Config) ->
  244. {port, Port} = lists:keyfind(port, 1, Config),
  245. {ok, Socket} = gen_tcp:connect("localhost", Port,
  246. [binary, {active, false}, {packet, raw}]),
  247. ok = gen_tcp:send(Socket, [
  248. "GET /websocket HTTP/1.1\r\n"
  249. "Host: localhost\r\n"
  250. "Connection: Upgrade\r\n"
  251. "Origin: http://localhost\r\n"
  252. "Sec-WebSocket-Version: 13\r\n"
  253. "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
  254. "Upgrade: websocket\r\n"
  255. "\r\n"]),
  256. {ok, Handshake} = gen_tcp:recv(Socket, 0, 6000),
  257. {ok, {http_response, {1, 1}, 101, "Switching Protocols"}, Rest}
  258. = erlang:decode_packet(http, Handshake, []),
  259. [Headers, <<>>] = websocket_headers(
  260. erlang:decode_packet(httph, Rest, []), []),
  261. {'Connection', "Upgrade"} = lists:keyfind('Connection', 1, Headers),
  262. {'Upgrade', "websocket"} = lists:keyfind('Upgrade', 1, Headers),
  263. {"sec-websocket-accept", "s3pPLMBiTxaQ9kYGzzhZRbK+xOo="}
  264. = lists:keyfind("sec-websocket-accept", 1, Headers),
  265. %% text
  266. ok = gen_tcp:send(Socket, << 16#81, 16#85, 16#37, 16#fa, 16#21, 16#3d,
  267. 16#7f, 16#9f, 16#4d, 16#51, 16#58 >>),
  268. {ok, << 1:1, 0:3, 1:4, 0:1, 5:7, "Hello" >>}
  269. = gen_tcp:recv(Socket, 0, 6000),
  270. %% binary (empty)
  271. ok = gen_tcp:send(Socket, << 1:1, 0:3, 2:4, 0:8 >>),
  272. {ok, << 1:1, 0:3, 2:4, 0:8 >>} = gen_tcp:recv(Socket, 0, 6000),
  273. %% binary
  274. ok = gen_tcp:send(Socket, << 16#82, 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, 2:4, 0:1, 5:7, "Hello" >>}
  277. = gen_tcp:recv(Socket, 0, 6000),
  278. %% Receives.
  279. {ok, << 1:1, 0:3, 1:4, 0:1, 14:7, "websocket_init" >>}
  280. = gen_tcp:recv(Socket, 0, 6000),
  281. {ok, << 1:1, 0:3, 1:4, 0:1, 16:7, "websocket_handle" >>}
  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 = gen_tcp:send(Socket, << 1:1, 0:3, 9:4, 0:8 >>), %% ping
  288. {ok, << 1:1, 0:3, 10:4, 0:8 >>} = gen_tcp:recv(Socket, 0, 6000), %% pong
  289. ok = gen_tcp:send(Socket, << 1:1, 0:3, 8:4, 0:8 >>), %% close
  290. {ok, << 1:1, 0:3, 8:4, 0:8 >>} = gen_tcp:recv(Socket, 0, 6000),
  291. {error, closed} = gen_tcp:recv(Socket, 0, 6000),
  292. ok.
  293. ws_send_many(Config) ->
  294. {port, Port} = lists:keyfind(port, 1, Config),
  295. {ok, Socket} = gen_tcp:connect("localhost", Port,
  296. [binary, {active, false}, {packet, raw}]),
  297. ok = gen_tcp:send(Socket, [
  298. "GET /ws_send_many HTTP/1.1\r\n"
  299. "Host: localhost\r\n"
  300. "Connection: Upgrade\r\n"
  301. "Upgrade: websocket\r\n"
  302. "Sec-WebSocket-Origin: http://localhost\r\n"
  303. "Sec-WebSocket-Version: 8\r\n"
  304. "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
  305. "\r\n"]),
  306. {ok, Handshake} = gen_tcp:recv(Socket, 0, 6000),
  307. {ok, {http_response, {1, 1}, 101, "Switching Protocols"}, Rest}
  308. = erlang:decode_packet(http, Handshake, []),
  309. [Headers, <<>>] = websocket_headers(
  310. erlang:decode_packet(httph, Rest, []), []),
  311. {'Connection', "Upgrade"} = lists:keyfind('Connection', 1, Headers),
  312. {'Upgrade', "websocket"} = lists:keyfind('Upgrade', 1, Headers),
  313. {"sec-websocket-accept", "s3pPLMBiTxaQ9kYGzzhZRbK+xOo="}
  314. = lists:keyfind("sec-websocket-accept", 1, Headers),
  315. %% We catch all frames at once and check them directly.
  316. {ok, Many} = gen_tcp:recv(Socket, 18, 6000),
  317. << 1:1, 0:3, 1:4, 0:1, 3:7, "one",
  318. 1:1, 0:3, 1:4, 0:1, 3:7, "two",
  319. 1:1, 0:3, 1:4, 0:1, 6:7, "seven!" >> = Many,
  320. ok = gen_tcp:send(Socket, << 1:1, 0:3, 8:4, 0:8 >>), %% close
  321. {ok, << 1:1, 0:3, 8:4, 0:8 >>} = gen_tcp:recv(Socket, 0, 6000),
  322. {error, closed} = gen_tcp:recv(Socket, 0, 6000),
  323. ok.
  324. ws_text_fragments(Config) ->
  325. {port, Port} = lists:keyfind(port, 1, Config),
  326. {ok, Socket} = gen_tcp:connect("localhost", Port,
  327. [binary, {active, false}, {packet, raw}]),
  328. ok = gen_tcp:send(Socket, [
  329. "GET /ws_echo_handler HTTP/1.1\r\n"
  330. "Host: localhost\r\n"
  331. "Connection: Upgrade\r\n"
  332. "Upgrade: websocket\r\n"
  333. "Sec-WebSocket-Origin: http://localhost\r\n"
  334. "Sec-WebSocket-Version: 8\r\n"
  335. "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
  336. "\r\n"]),
  337. {ok, Handshake} = gen_tcp:recv(Socket, 0, 6000),
  338. {ok, {http_response, {1, 1}, 101, "Switching Protocols"}, Rest}
  339. = erlang:decode_packet(http, Handshake, []),
  340. [Headers, <<>>] = websocket_headers(
  341. erlang:decode_packet(httph, Rest, []), []),
  342. {'Connection', "Upgrade"} = lists:keyfind('Connection', 1, Headers),
  343. {'Upgrade', "websocket"} = lists:keyfind('Upgrade', 1, Headers),
  344. {"sec-websocket-accept", "s3pPLMBiTxaQ9kYGzzhZRbK+xOo="}
  345. = lists:keyfind("sec-websocket-accept", 1, Headers),
  346. ok = gen_tcp:send(Socket, [
  347. << 0:1, 0:3, 1:4, 1:1, 5:7 >>,
  348. << 16#37 >>, << 16#fa >>, << 16#21 >>, << 16#3d >>, << 16#7f >>,
  349. << 16#9f >>, << 16#4d >>, << 16#51 >>, << 16#58 >>]),
  350. ok = gen_tcp:send(Socket, [
  351. << 1:1, 0:3, 0:4, 1:1, 5:7 >>,
  352. << 16#37 >>, << 16#fa >>, << 16#21 >>, << 16#3d >>, << 16#7f >>,
  353. << 16#9f >>, << 16#4d >>, << 16#51 >>, << 16#58 >>]),
  354. {ok, << 1:1, 0:3, 1:4, 0:1, 10:7, "HelloHello" >>}
  355. = gen_tcp:recv(Socket, 0, 6000),
  356. ok = gen_tcp:send(Socket, [
  357. %% #1
  358. << 0:1, 0:3, 1:4, 1:1, 5:7 >>,
  359. << 16#37 >>, << 16#fa >>, << 16#21 >>, << 16#3d >>, << 16#7f >>,
  360. << 16#9f >>, << 16#4d >>, << 16#51 >>, << 16#58 >>,
  361. %% #2
  362. << 0:1, 0:3, 0:4, 1:1, 5:7 >>,
  363. << 16#37 >>, << 16#fa >>, << 16#21 >>, << 16#3d >>, << 16#7f >>,
  364. << 16#9f >>, << 16#4d >>, << 16#51 >>, << 16#58 >>,
  365. %% #3
  366. << 1:1, 0:3, 0:4, 1:1, 5:7 >>,
  367. << 16#37 >>, << 16#fa >>, << 16#21 >>, << 16#3d >>, << 16#7f >>,
  368. << 16#9f >>, << 16#4d >>, << 16#51 >>, << 16#58 >>]),
  369. {ok, << 1:1, 0:3, 1:4, 0:1, 15:7, "HelloHelloHello" >>}
  370. = gen_tcp:recv(Socket, 0, 6000),
  371. ok = gen_tcp:send(Socket, << 1:1, 0:3, 8:4, 0:8 >>), %% close
  372. {ok, << 1:1, 0:3, 8:4, 0:8 >>} = gen_tcp:recv(Socket, 0, 6000),
  373. {error, closed} = gen_tcp:recv(Socket, 0, 6000),
  374. ok.
  375. ws_timeout_hibernate(Config) ->
  376. {port, Port} = lists:keyfind(port, 1, Config),
  377. {ok, Socket} = gen_tcp:connect("localhost", Port,
  378. [binary, {active, false}, {packet, raw}]),
  379. ok = gen_tcp:send(Socket, [
  380. "GET /ws_timeout_hibernate HTTP/1.1\r\n"
  381. "Host: localhost\r\n"
  382. "Connection: Upgrade\r\n"
  383. "Upgrade: websocket\r\n"
  384. "Sec-WebSocket-Origin: http://localhost\r\n"
  385. "Sec-WebSocket-Version: 8\r\n"
  386. "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
  387. "\r\n"]),
  388. {ok, Handshake} = gen_tcp:recv(Socket, 0, 6000),
  389. {ok, {http_response, {1, 1}, 101, "Switching Protocols"}, Rest}
  390. = erlang:decode_packet(http, Handshake, []),
  391. [Headers, <<>>] = websocket_headers(
  392. erlang:decode_packet(httph, Rest, []), []),
  393. {'Connection', "Upgrade"} = lists:keyfind('Connection', 1, Headers),
  394. {'Upgrade', "websocket"} = lists:keyfind('Upgrade', 1, Headers),
  395. {"sec-websocket-accept", "s3pPLMBiTxaQ9kYGzzhZRbK+xOo="}
  396. = lists:keyfind("sec-websocket-accept", 1, Headers),
  397. {ok, << 1:1, 0:3, 8:4, 0:8 >>} = gen_tcp:recv(Socket, 0, 6000),
  398. {error, closed} = gen_tcp:recv(Socket, 0, 6000),
  399. ok.
  400. %% Internal.
  401. websocket_headers({ok, http_eoh, Rest}, Acc) ->
  402. [Acc, Rest];
  403. websocket_headers({ok, {http_header, _I, Key, _R, Value}, Rest}, Acc) ->
  404. F = fun(S) when is_atom(S) -> S; (S) -> string:to_lower(S) end,
  405. websocket_headers(erlang:decode_packet(httph, Rest, []),
  406. [{F(Key), Value}|Acc]).