ws_SUITE.erl 20 KB

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