ws_SUITE.erl 19 KB

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