ws_SUITE.erl 15 KB

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