ws_SUITE.erl 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375
  1. %% Copyright (c) 2011, Loïc Hoguin <essen@dev-extend.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. -export([all/0, groups/0, init_per_suite/1, end_per_suite/1,
  17. init_per_group/2, end_per_group/2]). %% ct.
  18. -export([ws0/1, ws8/1, ws8_single_bytes/1, ws8_init_shutdown/1,
  19. ws13/1, ws_timeout_hibernate/1, ws_text_fragments/1]). %% ws.
  20. %% ct.
  21. all() ->
  22. [{group, ws}].
  23. groups() ->
  24. BaseTests = [ws0, ws8, ws8_single_bytes, ws8_init_shutdown, ws13,
  25. ws_timeout_hibernate, ws_text_fragments],
  26. [{ws, [], BaseTests}].
  27. init_per_suite(Config) ->
  28. application:start(inets),
  29. application:start(cowboy),
  30. Config.
  31. end_per_suite(_Config) ->
  32. application:stop(cowboy),
  33. application:stop(inets),
  34. ok.
  35. init_per_group(ws, Config) ->
  36. Port = 33080,
  37. cowboy:start_listener(ws, 100,
  38. cowboy_tcp_transport, [{port, Port}],
  39. cowboy_http_protocol, [{dispatch, init_dispatch()}]
  40. ),
  41. [{port, Port}|Config].
  42. end_per_group(Listener, _Config) ->
  43. cowboy:stop_listener(Listener),
  44. ok.
  45. %% Dispatch configuration.
  46. init_dispatch() ->
  47. [
  48. {[<<"localhost">>], [
  49. {[<<"websocket">>], websocket_handler, []},
  50. {[<<"ws_timeout_hibernate">>], ws_timeout_hibernate_handler, []},
  51. {[<<"ws_init_shutdown">>], websocket_handler_init_shutdown, []},
  52. {[<<"ws_echo_handler">>], websocket_echo_handler, []}
  53. ]}
  54. ].
  55. %% ws and wss.
  56. %% This test makes sure the code works even if we wait for a reply
  57. %% before sending the third challenge key in the GET body.
  58. %%
  59. %% This ensures that Cowboy will work fine with proxies on hixie.
  60. ws0(Config) ->
  61. {port, Port} = lists:keyfind(port, 1, Config),
  62. {ok, Socket} = gen_tcp:connect("localhost", Port,
  63. [binary, {active, false}, {packet, raw}]),
  64. ok = gen_tcp:send(Socket,
  65. "GET /websocket HTTP/1.1\r\n"
  66. "Host: localhost\r\n"
  67. "Connection: Upgrade\r\n"
  68. "Upgrade: WebSocket\r\n"
  69. "Origin: http://localhost\r\n"
  70. "Sec-Websocket-Key1: Y\" 4 1Lj!957b8@0H756!i\r\n"
  71. "Sec-Websocket-Key2: 1711 M;4\\74 80<6\r\n"
  72. "\r\n"),
  73. {ok, Handshake} = gen_tcp:recv(Socket, 0, 6000),
  74. {ok, {http_response, {1, 1}, 101, "WebSocket Protocol Handshake"}, Rest}
  75. = erlang:decode_packet(http, Handshake, []),
  76. [Headers, <<>>] = websocket_headers(
  77. erlang:decode_packet(httph, Rest, []), []),
  78. {'Connection', "Upgrade"} = lists:keyfind('Connection', 1, Headers),
  79. {'Upgrade', "WebSocket"} = lists:keyfind('Upgrade', 1, Headers),
  80. {"sec-websocket-location", "ws://localhost/websocket"}
  81. = lists:keyfind("sec-websocket-location", 1, Headers),
  82. {"sec-websocket-origin", "http://localhost"}
  83. = lists:keyfind("sec-websocket-origin", 1, Headers),
  84. ok = gen_tcp:send(Socket, <<15,245,8,18,2,204,133,33>>),
  85. {ok, Body} = gen_tcp:recv(Socket, 0, 6000),
  86. <<169,244,191,103,146,33,149,59,74,104,67,5,99,118,171,236>> = Body,
  87. ok = gen_tcp:send(Socket, << 0, "client_msg", 255 >>),
  88. {ok, << 0, "client_msg", 255 >>} = gen_tcp:recv(Socket, 0, 6000),
  89. {ok, << 0, "websocket_init", 255 >>} = gen_tcp:recv(Socket, 0, 6000),
  90. {ok, << 0, "websocket_handle", 255 >>} = gen_tcp:recv(Socket, 0, 6000),
  91. {ok, << 0, "websocket_handle", 255 >>} = gen_tcp:recv(Socket, 0, 6000),
  92. {ok, << 0, "websocket_handle", 255 >>} = gen_tcp:recv(Socket, 0, 6000),
  93. %% We try to send another HTTP request to make sure
  94. %% the server closed the request.
  95. ok = gen_tcp:send(Socket, [
  96. << 255, 0 >>, %% Close websocket command.
  97. "GET / HTTP/1.1\r\nHost: localhost\r\n\r\n" %% Server should ignore it.
  98. ]),
  99. {ok, << 255, 0 >>} = gen_tcp:recv(Socket, 0, 6000),
  100. {error, closed} = gen_tcp:recv(Socket, 0, 6000),
  101. ok.
  102. ws8(Config) ->
  103. {port, Port} = lists:keyfind(port, 1, Config),
  104. {ok, Socket} = gen_tcp:connect("localhost", Port,
  105. [binary, {active, false}, {packet, raw}]),
  106. ok = gen_tcp:send(Socket, [
  107. "GET /websocket HTTP/1.1\r\n"
  108. "Host: localhost\r\n"
  109. "Connection: Upgrade\r\n"
  110. "Upgrade: websocket\r\n"
  111. "Sec-WebSocket-Origin: http://localhost\r\n"
  112. "Sec-WebSocket-Version: 8\r\n"
  113. "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
  114. "\r\n"]),
  115. {ok, Handshake} = gen_tcp:recv(Socket, 0, 6000),
  116. {ok, {http_response, {1, 1}, 101, "Switching Protocols"}, Rest}
  117. = erlang:decode_packet(http, Handshake, []),
  118. [Headers, <<>>] = websocket_headers(
  119. erlang:decode_packet(httph, Rest, []), []),
  120. {'Connection', "Upgrade"} = lists:keyfind('Connection', 1, Headers),
  121. {'Upgrade', "websocket"} = lists:keyfind('Upgrade', 1, Headers),
  122. {"sec-websocket-accept", "s3pPLMBiTxaQ9kYGzzhZRbK+xOo="}
  123. = lists:keyfind("sec-websocket-accept", 1, Headers),
  124. ok = gen_tcp:send(Socket, << 16#81, 16#85, 16#37, 16#fa, 16#21, 16#3d,
  125. 16#7f, 16#9f, 16#4d, 16#51, 16#58 >>),
  126. {ok, << 1:1, 0:3, 1:4, 0:1, 5:7, "Hello" >>}
  127. = gen_tcp:recv(Socket, 0, 6000),
  128. {ok, << 1:1, 0:3, 1:4, 0:1, 14:7, "websocket_init" >>}
  129. = gen_tcp:recv(Socket, 0, 6000),
  130. {ok, << 1:1, 0:3, 1:4, 0:1, 16:7, "websocket_handle" >>}
  131. = gen_tcp:recv(Socket, 0, 6000),
  132. {ok, << 1:1, 0:3, 1:4, 0:1, 16:7, "websocket_handle" >>}
  133. = gen_tcp:recv(Socket, 0, 6000),
  134. {ok, << 1:1, 0:3, 1:4, 0:1, 16:7, "websocket_handle" >>}
  135. = gen_tcp:recv(Socket, 0, 6000),
  136. ok = gen_tcp:send(Socket, << 1:1, 0:3, 9:4, 0:8 >>), %% ping
  137. {ok, << 1:1, 0:3, 10:4, 0:8 >>} = gen_tcp:recv(Socket, 0, 6000), %% pong
  138. ok = gen_tcp:send(Socket, << 1:1, 0:3, 8:4, 0:8 >>), %% close
  139. {ok, << 1:1, 0:3, 8:4, 0:8 >>} = gen_tcp:recv(Socket, 0, 6000),
  140. {error, closed} = gen_tcp:recv(Socket, 0, 6000),
  141. ok.
  142. ws8_single_bytes(Config) ->
  143. {port, Port} = lists:keyfind(port, 1, Config),
  144. {ok, Socket} = gen_tcp:connect("localhost", Port,
  145. [binary, {active, false}, {packet, raw}]),
  146. ok = gen_tcp:send(Socket, [
  147. "GET /websocket HTTP/1.1\r\n"
  148. "Host: localhost\r\n"
  149. "Connection: Upgrade\r\n"
  150. "Upgrade: websocket\r\n"
  151. "Sec-WebSocket-Origin: http://localhost\r\n"
  152. "Sec-WebSocket-Version: 8\r\n"
  153. "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
  154. "\r\n"]),
  155. {ok, Handshake} = gen_tcp:recv(Socket, 0, 6000),
  156. {ok, {http_response, {1, 1}, 101, "Switching Protocols"}, Rest}
  157. = erlang:decode_packet(http, Handshake, []),
  158. [Headers, <<>>] = websocket_headers(
  159. erlang:decode_packet(httph, Rest, []), []),
  160. {'Connection', "Upgrade"} = lists:keyfind('Connection', 1, Headers),
  161. {'Upgrade', "websocket"} = lists:keyfind('Upgrade', 1, Headers),
  162. {"sec-websocket-accept", "s3pPLMBiTxaQ9kYGzzhZRbK+xOo="}
  163. = lists:keyfind("sec-websocket-accept", 1, Headers),
  164. ok = gen_tcp:send(Socket, << 16#81 >>), %% send one byte
  165. ok = timer:sleep(100), %% sleep for a period
  166. ok = gen_tcp:send(Socket, << 16#85 >>), %% send another and so on
  167. ok = timer:sleep(100),
  168. ok = gen_tcp:send(Socket, << 16#37 >>),
  169. ok = timer:sleep(100),
  170. ok = gen_tcp:send(Socket, << 16#fa >>),
  171. ok = timer:sleep(100),
  172. ok = gen_tcp:send(Socket, << 16#21 >>),
  173. ok = timer:sleep(100),
  174. ok = gen_tcp:send(Socket, << 16#3d >>),
  175. ok = timer:sleep(100),
  176. ok = gen_tcp:send(Socket, << 16#7f >>),
  177. ok = timer:sleep(100),
  178. ok = gen_tcp:send(Socket, << 16#9f >>),
  179. ok = timer:sleep(100),
  180. ok = gen_tcp:send(Socket, << 16#4d >>),
  181. ok = timer:sleep(100),
  182. ok = gen_tcp:send(Socket, << 16#51 >>),
  183. ok = timer:sleep(100),
  184. ok = gen_tcp:send(Socket, << 16#58 >>),
  185. {ok, << 1:1, 0:3, 1:4, 0:1, 14:7, "websocket_init" >>}
  186. = gen_tcp:recv(Socket, 0, 6000),
  187. {ok, << 1:1, 0:3, 1:4, 0:1, 5:7, "Hello" >>}
  188. = gen_tcp:recv(Socket, 0, 6000),
  189. {ok, << 1:1, 0:3, 1:4, 0:1, 16:7, "websocket_handle" >>}
  190. = gen_tcp:recv(Socket, 0, 6000),
  191. {ok, << 1:1, 0:3, 1:4, 0:1, 16:7, "websocket_handle" >>}
  192. = gen_tcp:recv(Socket, 0, 6000),
  193. {ok, << 1:1, 0:3, 1:4, 0:1, 16:7, "websocket_handle" >>}
  194. = gen_tcp:recv(Socket, 0, 6000),
  195. ok = gen_tcp:send(Socket, << 1:1, 0:3, 9:4, 0:8 >>), %% ping
  196. {ok, << 1:1, 0:3, 10:4, 0:8 >>} = gen_tcp:recv(Socket, 0, 6000), %% pong
  197. ok = gen_tcp:send(Socket, << 1:1, 0:3, 8:4, 0:8 >>), %% close
  198. {ok, << 1:1, 0:3, 8:4, 0:8 >>} = gen_tcp:recv(Socket, 0, 6000),
  199. {error, closed} = gen_tcp:recv(Socket, 0, 6000),
  200. ok.
  201. ws_timeout_hibernate(Config) ->
  202. {port, Port} = lists:keyfind(port, 1, Config),
  203. {ok, Socket} = gen_tcp:connect("localhost", Port,
  204. [binary, {active, false}, {packet, raw}]),
  205. ok = gen_tcp:send(Socket, [
  206. "GET /ws_timeout_hibernate HTTP/1.1\r\n"
  207. "Host: localhost\r\n"
  208. "Connection: Upgrade\r\n"
  209. "Upgrade: websocket\r\n"
  210. "Sec-WebSocket-Origin: http://localhost\r\n"
  211. "Sec-WebSocket-Version: 8\r\n"
  212. "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
  213. "\r\n"]),
  214. {ok, Handshake} = gen_tcp:recv(Socket, 0, 6000),
  215. {ok, {http_response, {1, 1}, 101, "Switching Protocols"}, Rest}
  216. = erlang:decode_packet(http, Handshake, []),
  217. [Headers, <<>>] = websocket_headers(
  218. erlang:decode_packet(httph, Rest, []), []),
  219. {'Connection', "Upgrade"} = lists:keyfind('Connection', 1, Headers),
  220. {'Upgrade', "websocket"} = lists:keyfind('Upgrade', 1, Headers),
  221. {"sec-websocket-accept", "s3pPLMBiTxaQ9kYGzzhZRbK+xOo="}
  222. = lists:keyfind("sec-websocket-accept", 1, Headers),
  223. {ok, << 1:1, 0:3, 8:4, 0:8 >>} = gen_tcp:recv(Socket, 0, 6000),
  224. {error, closed} = gen_tcp:recv(Socket, 0, 6000),
  225. ok.
  226. ws8_init_shutdown(Config) ->
  227. {port, Port} = lists:keyfind(port, 1, Config),
  228. {ok, Socket} = gen_tcp:connect("localhost", Port,
  229. [binary, {active, false}, {packet, raw}]),
  230. ok = gen_tcp:send(Socket, [
  231. "GET /ws_init_shutdown HTTP/1.1\r\n"
  232. "Host: localhost\r\n"
  233. "Connection: Upgrade\r\n"
  234. "Upgrade: websocket\r\n"
  235. "Sec-WebSocket-Origin: http://localhost\r\n"
  236. "Sec-WebSocket-Version: 8\r\n"
  237. "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
  238. "\r\n"]),
  239. {ok, Handshake} = gen_tcp:recv(Socket, 0, 6000),
  240. {ok, {http_response, {1, 1}, 403, "Forbidden"}, _Rest}
  241. = erlang:decode_packet(http, Handshake, []),
  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, 0:8 >>),
  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, 0:8 >>), %% 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, 0:8 >>), %% 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_text_fragments(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_echo_handler 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. ok = gen_tcp:send(Socket, [
  317. << 0:1, 0:3, 1:4, 1:1, 5:7 >>,
  318. << 16#37 >>, << 16#fa >>, << 16#21 >>, << 16#3d >>, << 16#7f >>,
  319. << 16#9f >>, << 16#4d >>, << 16#51 >>, << 16#58 >>]),
  320. ok = gen_tcp:send(Socket, [
  321. << 1:1, 0:3, 0:4, 1:1, 5:7 >>,
  322. << 16#37 >>, << 16#fa >>, << 16#21 >>, << 16#3d >>, << 16#7f >>,
  323. << 16#9f >>, << 16#4d >>, << 16#51 >>, << 16#58 >>]),
  324. {ok, << 1:1, 0:3, 1:4, 0:1, 10:7, "HelloHello" >>}
  325. = gen_tcp:recv(Socket, 0, 6000),
  326. ok = gen_tcp:send(Socket, [
  327. %% #1
  328. << 0:1, 0:3, 1:4, 1:1, 5:7 >>,
  329. << 16#37 >>, << 16#fa >>, << 16#21 >>, << 16#3d >>, << 16#7f >>,
  330. << 16#9f >>, << 16#4d >>, << 16#51 >>, << 16#58 >>,
  331. %% #2
  332. << 0:1, 0:3, 0:4, 1:1, 5:7 >>,
  333. << 16#37 >>, << 16#fa >>, << 16#21 >>, << 16#3d >>, << 16#7f >>,
  334. << 16#9f >>, << 16#4d >>, << 16#51 >>, << 16#58 >>,
  335. %% #3
  336. << 1:1, 0:3, 0:4, 1:1, 5:7 >>,
  337. << 16#37 >>, << 16#fa >>, << 16#21 >>, << 16#3d >>, << 16#7f >>,
  338. << 16#9f >>, << 16#4d >>, << 16#51 >>, << 16#58 >>]),
  339. {ok, << 1:1, 0:3, 1:4, 0:1, 15:7, "HelloHelloHello" >>}
  340. = gen_tcp:recv(Socket, 0, 6000),
  341. ok = gen_tcp:send(Socket, << 1:1, 0:3, 8:4, 0:8 >>), %% close
  342. {ok, << 1:1, 0:3, 8:4, 0:8 >>} = gen_tcp:recv(Socket, 0, 6000),
  343. {error, closed} = gen_tcp:recv(Socket, 0, 6000),
  344. ok.
  345. websocket_headers({ok, http_eoh, Rest}, Acc) ->
  346. [Acc, Rest];
  347. websocket_headers({ok, {http_header, _I, Key, _R, Value}, Rest}, Acc) ->
  348. F = fun(S) when is_atom(S) -> S; (S) -> string:to_lower(S) end,
  349. websocket_headers(erlang:decode_packet(httph, Rest, []),
  350. [{F(Key), Value}|Acc]).