/test/ws_SUITE.erl
Erlang | 710 lines | 629 code | 43 blank | 38 comment | 2 complexity | 70f502aa88ca66a2ae25d51e3a56f487 MD5 | raw file
1%% Copyright (c) 2011-2014, 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 15-module(ws_SUITE). 16-compile(export_all). 17 18-import(cowboy_test, [config/2]). 19 20%% ct. 21 22all() -> 23 [{group, autobahn}, {group, ws}]. 24 25groups() -> 26 BaseTests = cowboy_test:all(?MODULE) -- [autobahn_fuzzingclient], 27 [{autobahn, [], [autobahn_fuzzingclient]}, {ws, [parallel], BaseTests}]. 28 29init_per_suite(Config) -> 30 Config. 31 32init_per_group(Name = autobahn, Config) -> 33 %% Some systems have it named pip2. 34 Out = os:cmd("pip show autobahntestsuite ; pip2 show autobahntestsuite"), 35 case string:str(Out, "autobahntestsuite") of 36 0 -> 37 ct:print("Skipping the autobahn group because the " 38 "Autobahn Test Suite is not installed.~nTo install it, " 39 "please follow the instructions on this page:~n~n " 40 "http://autobahn.ws/testsuite/installation.html"), 41 {skip, "Autobahn Test Suite not installed."}; 42 _ -> 43 {ok, _} = cowboy:start_http(Name, 100, [{port, 33080}], [ 44 {env, [{dispatch, init_dispatch()}]}]), 45 Config 46 end; 47init_per_group(Name = ws, Config) -> 48 cowboy_test:init_http(Name, [ 49 {env, [{dispatch, init_dispatch()}]}, 50 {compress, true} 51 ], Config). 52 53end_per_group(Listener, _Config) -> 54 cowboy:stop_listener(Listener). 55 56%% Dispatch configuration. 57 58init_dispatch() -> 59 cowboy_router:compile([ 60 {"localhost", [ 61 {"/ws_echo", ws_echo, []}, 62 {"/ws_echo_timer", ws_echo_timer, []}, 63 {"/ws_init_shutdown", ws_init_shutdown, []}, 64 {"/ws_send_many", ws_send_many, [ 65 {sequence, [ 66 {text, <<"one">>}, 67 {text, <<"two">>}, 68 {text, <<"seven!">>}]} 69 ]}, 70 {"/ws_send_close", ws_send_many, [ 71 {sequence, [ 72 {text, <<"send">>}, 73 close, 74 {text, <<"won't be received">>}]} 75 ]}, 76 {"/ws_send_close_payload", ws_send_many, [ 77 {sequence, [ 78 {text, <<"send">>}, 79 {close, 1001, <<"some text!">>}, 80 {text, <<"won't be received">>}]} 81 ]}, 82 {"/ws_timeout_hibernate", ws_timeout_hibernate, []}, 83 {"/ws_timeout_cancel", ws_timeout_cancel, []}, 84 {"/ws_upgrade_with_opts", ws_upgrade_with_opts, 85 <<"failure">>} 86 ]} 87 ]). 88 89%% Tests. 90 91autobahn_fuzzingclient(Config) -> 92 Out = os:cmd("cd " ++ config(priv_dir, Config) 93 ++ " && wstest -m fuzzingclient -s " 94 ++ config(data_dir, Config) ++ "client.json"), 95 Report = config(priv_dir, Config) ++ "reports/servers/index.html", 96 ct:log("<h2><a href=\"~s\">Full report</a></h2>~n", [Report]), 97 ct:print("Autobahn Test Suite report: file://~s~n", [Report]), 98 ct:log("~s~n", [Out]), 99 {ok, HTML} = file:read_file(Report), 100 case length(binary:matches(HTML, <<"case_failed">>)) > 2 of 101 true -> error(failed); 102 false -> ok 103 end. 104 105%% We do not support hixie76 anymore. 106ws0(Config) -> 107 {port, Port} = lists:keyfind(port, 1, Config), 108 {ok, Socket} = gen_tcp:connect("localhost", Port, 109 [binary, {active, false}, {packet, raw}]), 110 ok = gen_tcp:send(Socket, 111 "GET /ws_echo_timer HTTP/1.1\r\n" 112 "Host: localhost\r\n" 113 "Connection: Upgrade\r\n" 114 "Upgrade: WebSocket\r\n" 115 "Origin: http://localhost\r\n" 116 "Sec-Websocket-Key1: Y\" 4 1Lj!957b8@0H756!i\r\n" 117 "Sec-Websocket-Key2: 1711 M;4\\74 80<6\r\n" 118 "\r\n"), 119 {ok, Handshake} = gen_tcp:recv(Socket, 0, 6000), 120 {ok, {http_response, {1, 1}, 400, _}, _} 121 = erlang:decode_packet(http, Handshake, []). 122 123ws8(Config) -> 124 {port, Port} = lists:keyfind(port, 1, Config), 125 {ok, Socket} = gen_tcp:connect("localhost", Port, 126 [binary, {active, false}, {packet, raw}]), 127 ok = gen_tcp:send(Socket, [ 128 "GET /ws_echo_timer HTTP/1.1\r\n" 129 "Host: localhost\r\n" 130 "Connection: Upgrade\r\n" 131 "Upgrade: websocket\r\n" 132 "Sec-WebSocket-Origin: http://localhost\r\n" 133 "Sec-WebSocket-Version: 8\r\n" 134 "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" 135 "\r\n"]), 136 {ok, Handshake} = gen_tcp:recv(Socket, 0, 6000), 137 {ok, {http_response, {1, 1}, 101, "Switching Protocols"}, Rest} 138 = erlang:decode_packet(http, Handshake, []), 139 [Headers, <<>>] = do_decode_headers( 140 erlang:decode_packet(httph, Rest, []), []), 141 {'Connection', "Upgrade"} = lists:keyfind('Connection', 1, Headers), 142 {'Upgrade', "websocket"} = lists:keyfind('Upgrade', 1, Headers), 143 {"sec-websocket-accept", "s3pPLMBiTxaQ9kYGzzhZRbK+xOo="} 144 = lists:keyfind("sec-websocket-accept", 1, Headers), 145 ok = gen_tcp:send(Socket, << 16#81, 16#85, 16#37, 16#fa, 16#21, 16#3d, 146 16#7f, 16#9f, 16#4d, 16#51, 16#58 >>), 147 {ok, << 1:1, 0:3, 1:4, 0:1, 5:7, "Hello" >>} 148 = gen_tcp:recv(Socket, 0, 6000), 149 {ok, << 1:1, 0:3, 1:4, 0:1, 14:7, "websocket_init" >>} 150 = gen_tcp:recv(Socket, 0, 6000), 151 {ok, << 1:1, 0:3, 1:4, 0:1, 16:7, "websocket_handle" >>} 152 = gen_tcp:recv(Socket, 0, 6000), 153 {ok, << 1:1, 0:3, 1:4, 0:1, 16:7, "websocket_handle" >>} 154 = gen_tcp:recv(Socket, 0, 6000), 155 {ok, << 1:1, 0:3, 1:4, 0:1, 16:7, "websocket_handle" >>} 156 = gen_tcp:recv(Socket, 0, 6000), 157 ok = gen_tcp:send(Socket, << 1:1, 0:3, 9:4, 1:1, 0:7, 0:32 >>), %% ping 158 {ok, << 1:1, 0:3, 10:4, 0:8 >>} = gen_tcp:recv(Socket, 0, 6000), %% pong 159 ok = gen_tcp:send(Socket, << 1:1, 0:3, 8:4, 1:1, 0:7, 0:32 >>), %% close 160 {ok, << 1:1, 0:3, 8:4, 0:8 >>} = gen_tcp:recv(Socket, 0, 6000), 161 {error, closed} = gen_tcp:recv(Socket, 0, 6000), 162 ok. 163 164ws8_init_shutdown(Config) -> 165 {port, Port} = lists:keyfind(port, 1, Config), 166 {ok, Socket} = gen_tcp:connect("localhost", Port, 167 [binary, {active, false}, {packet, raw}]), 168 ok = gen_tcp:send(Socket, [ 169 "GET /ws_init_shutdown HTTP/1.1\r\n" 170 "Host: localhost\r\n" 171 "Connection: Upgrade\r\n" 172 "Upgrade: websocket\r\n" 173 "Sec-WebSocket-Origin: http://localhost\r\n" 174 "Sec-WebSocket-Version: 8\r\n" 175 "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" 176 "\r\n"]), 177 {ok, Handshake} = gen_tcp:recv(Socket, 0, 6000), 178 {ok, {http_response, {1, 1}, 403, "Forbidden"}, _Rest} 179 = erlang:decode_packet(http, Handshake, []), 180 {error, closed} = gen_tcp:recv(Socket, 0, 6000), 181 ok. 182 183ws8_single_bytes(Config) -> 184 {port, Port} = lists:keyfind(port, 1, Config), 185 {ok, Socket} = gen_tcp:connect("localhost", Port, 186 [binary, {active, false}, {packet, raw}]), 187 ok = gen_tcp:send(Socket, [ 188 "GET /ws_echo_timer HTTP/1.1\r\n" 189 "Host: localhost\r\n" 190 "Connection: Upgrade\r\n" 191 "Upgrade: websocket\r\n" 192 "Sec-WebSocket-Origin: http://localhost\r\n" 193 "Sec-WebSocket-Version: 8\r\n" 194 "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" 195 "\r\n"]), 196 {ok, Handshake} = gen_tcp:recv(Socket, 0, 6000), 197 {ok, {http_response, {1, 1}, 101, "Switching Protocols"}, Rest} 198 = erlang:decode_packet(http, Handshake, []), 199 [Headers, <<>>] = do_decode_headers( 200 erlang:decode_packet(httph, Rest, []), []), 201 {'Connection', "Upgrade"} = lists:keyfind('Connection', 1, Headers), 202 {'Upgrade', "websocket"} = lists:keyfind('Upgrade', 1, Headers), 203 {"sec-websocket-accept", "s3pPLMBiTxaQ9kYGzzhZRbK+xOo="} 204 = lists:keyfind("sec-websocket-accept", 1, Headers), 205 ok = gen_tcp:send(Socket, << 16#81 >>), %% send one byte 206 ok = timer:sleep(100), %% sleep for a period 207 ok = gen_tcp:send(Socket, << 16#85 >>), %% send another and so on 208 ok = timer:sleep(100), 209 ok = gen_tcp:send(Socket, << 16#37 >>), 210 ok = timer:sleep(100), 211 ok = gen_tcp:send(Socket, << 16#fa >>), 212 ok = timer:sleep(100), 213 ok = gen_tcp:send(Socket, << 16#21 >>), 214 ok = timer:sleep(100), 215 ok = gen_tcp:send(Socket, << 16#3d >>), 216 ok = timer:sleep(100), 217 ok = gen_tcp:send(Socket, << 16#7f >>), 218 ok = timer:sleep(100), 219 ok = gen_tcp:send(Socket, << 16#9f >>), 220 ok = timer:sleep(100), 221 ok = gen_tcp:send(Socket, << 16#4d >>), 222 ok = timer:sleep(100), 223 ok = gen_tcp:send(Socket, << 16#51 >>), 224 ok = timer:sleep(100), 225 ok = gen_tcp:send(Socket, << 16#58 >>), 226 {ok, << 1:1, 0:3, 1:4, 0:1, 14:7, "websocket_init" >>} 227 = gen_tcp:recv(Socket, 0, 6000), 228 {ok, << 1:1, 0:3, 1:4, 0:1, 5:7, "Hello" >>} 229 = gen_tcp:recv(Socket, 0, 6000), 230 {ok, << 1:1, 0:3, 1:4, 0:1, 16:7, "websocket_handle" >>} 231 = gen_tcp:recv(Socket, 0, 6000), 232 {ok, << 1:1, 0:3, 1:4, 0:1, 16:7, "websocket_handle" >>} 233 = gen_tcp:recv(Socket, 0, 6000), 234 {ok, << 1:1, 0:3, 1:4, 0:1, 16:7, "websocket_handle" >>} 235 = gen_tcp:recv(Socket, 0, 6000), 236 ok = gen_tcp:send(Socket, << 1:1, 0:3, 9:4, 1:1, 0:7, 0:32 >>), %% ping 237 {ok, << 1:1, 0:3, 10:4, 0:8 >>} = gen_tcp:recv(Socket, 0, 6000), %% pong 238 ok = gen_tcp:send(Socket, << 1:1, 0:3, 8:4, 1:1, 0:7, 0:32 >>), %% close 239 {ok, << 1:1, 0:3, 8:4, 0:8 >>} = gen_tcp:recv(Socket, 0, 6000), 240 {error, closed} = gen_tcp:recv(Socket, 0, 6000), 241 ok. 242 243ws13(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 /ws_echo_timer 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, <<>>] = do_decode_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, 1:1, 0:7, 0:32 >>), 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, 1:1, 0:7, 0:32 >>), %% 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, 1:1, 0:7, 0:32 >>), %% 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 294ws_deflate(Config) -> 295 {port, Port} = lists:keyfind(port, 1, Config), 296 {ok, Socket} = gen_tcp:connect("localhost", Port, 297 [binary, {active, false}, {packet, raw}, {nodelay, true}]), 298 ok = gen_tcp:send(Socket, [ 299 "GET /ws_echo 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 "Sec-WebSocket-Extensions: x-webkit-deflate-frame\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, <<>>] = do_decode_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 {"sec-websocket-extensions", "x-webkit-deflate-frame"} 318 = lists:keyfind("sec-websocket-extensions", 1, Headers), 319 320 Mask = 16#11223344, 321 Hello = << 242, 72, 205, 201, 201, 7, 0 >>, 322 MaskedHello = do_mask(Hello, Mask, <<>>), 323 324 % send compressed text frame containing the Hello string 325 ok = gen_tcp:send(Socket, << 1:1, 1:1, 0:2, 1:4, 1:1, 7:7, Mask:32, 326 MaskedHello/binary >>), 327 % receive compressed text frame containing the Hello string 328 {ok, << 1:1, 1:1, 0:2, 1:4, 0:1, 7:7, Hello/binary >>} 329 = gen_tcp:recv(Socket, 0, 6000), 330 331 ok = gen_tcp:send(Socket, << 1:1, 0:3, 8:4, 1:1, 0:7, 0:32 >>), %% close 332 {ok, << 1:1, 0:3, 8:4, 0:8 >>} = gen_tcp:recv(Socket, 0, 6000), 333 {error, closed} = gen_tcp:recv(Socket, 0, 6000), 334 ok. 335 336ws_deflate_chunks(Config) -> 337 {port, Port} = lists:keyfind(port, 1, Config), 338 {ok, Socket} = gen_tcp:connect("localhost", Port, 339 [binary, {active, false}, {packet, raw}, {nodelay, true}]), 340 ok = gen_tcp:send(Socket, [ 341 "GET /ws_echo HTTP/1.1\r\n" 342 "Host: localhost\r\n" 343 "Connection: Upgrade\r\n" 344 "Upgrade: websocket\r\n" 345 "Sec-WebSocket-Origin: http://localhost\r\n" 346 "Sec-WebSocket-Version: 8\r\n" 347 "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" 348 "Sec-WebSocket-Extensions: x-webkit-deflate-frame\r\n" 349 "\r\n"]), 350 {ok, Handshake} = gen_tcp:recv(Socket, 0, 6000), 351 {ok, {http_response, {1, 1}, 101, "Switching Protocols"}, Rest} 352 = erlang:decode_packet(http, Handshake, []), 353 [Headers, <<>>] = do_decode_headers( 354 erlang:decode_packet(httph, Rest, []), []), 355 {'Connection', "Upgrade"} = lists:keyfind('Connection', 1, Headers), 356 {'Upgrade', "websocket"} = lists:keyfind('Upgrade', 1, Headers), 357 {"sec-websocket-accept", "s3pPLMBiTxaQ9kYGzzhZRbK+xOo="} 358 = lists:keyfind("sec-websocket-accept", 1, Headers), 359 {"sec-websocket-extensions", "x-webkit-deflate-frame"} 360 = lists:keyfind("sec-websocket-extensions", 1, Headers), 361 362 Mask = 16#11223344, 363 Hello = << 242, 72, 205, 201, 201, 7, 0 >>, 364 MaskedHello = do_mask(Hello, Mask, <<>>), 365 366 % send compressed text frame containing the Hello string 367 ok = gen_tcp:send(Socket, << 1:1, 1:1, 0:2, 1:4, 1:1, 7:7, Mask:32, 368 (binary:part(MaskedHello, 0, 4))/binary >>), 369 ok = timer:sleep(100), 370 ok = gen_tcp:send(Socket, binary:part(MaskedHello, 4, 3)), 371 372 % receive compressed text frame containing the Hello string 373 {ok, << 1:1, 1:1, 0:2, 1:4, 0:1, 7:7, Hello/binary >>} 374 = gen_tcp:recv(Socket, 0, 6000), 375 376 ok = gen_tcp:send(Socket, << 1:1, 0:3, 8:4, 1:1, 0:7, 0:32 >>), %% close 377 {ok, << 1:1, 0:3, 8:4, 0:8 >>} = gen_tcp:recv(Socket, 0, 6000), 378 {error, closed} = gen_tcp:recv(Socket, 0, 6000), 379 ok. 380 381ws_deflate_fragments(Config) -> 382 {port, Port} = lists:keyfind(port, 1, Config), 383 {ok, Socket} = gen_tcp:connect("localhost", Port, 384 [binary, {active, false}, {packet, raw}, {nodelay, true}]), 385 ok = gen_tcp:send(Socket, [ 386 "GET /ws_echo HTTP/1.1\r\n" 387 "Host: localhost\r\n" 388 "Connection: Upgrade\r\n" 389 "Upgrade: websocket\r\n" 390 "Sec-WebSocket-Origin: http://localhost\r\n" 391 "Sec-WebSocket-Version: 8\r\n" 392 "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" 393 "Sec-WebSocket-Extensions: x-webkit-deflate-frame\r\n" 394 "\r\n"]), 395 {ok, Handshake} = gen_tcp:recv(Socket, 0, 6000), 396 {ok, {http_response, {1, 1}, 101, "Switching Protocols"}, Rest} 397 = erlang:decode_packet(http, Handshake, []), 398 [Headers, <<>>] = do_decode_headers( 399 erlang:decode_packet(httph, Rest, []), []), 400 {'Connection', "Upgrade"} = lists:keyfind('Connection', 1, Headers), 401 {'Upgrade', "websocket"} = lists:keyfind('Upgrade', 1, Headers), 402 {"sec-websocket-accept", "s3pPLMBiTxaQ9kYGzzhZRbK+xOo="} 403 = lists:keyfind("sec-websocket-accept", 1, Headers), 404 {"sec-websocket-extensions", "x-webkit-deflate-frame"} 405 = lists:keyfind("sec-websocket-extensions", 1, Headers), 406 407 Mask = 16#11223344, 408 Hello = << 242, 72, 205, 201, 201, 7, 0 >>, 409 410 % send compressed text frame containing the Hello string 411 % as 2 separate fragments 412 ok = gen_tcp:send(Socket, << 0:1, 1:1, 0:2, 1:4, 1:1, 4:7, Mask:32, 413 (do_mask(binary:part(Hello, 0, 4), Mask, <<>>))/binary >>), 414 ok = gen_tcp:send(Socket, << 1:1, 1:1, 0:2, 0:4, 1:1, 3:7, Mask:32, 415 (do_mask(binary:part(Hello, 4, 3), Mask, <<>>))/binary >>), 416 % receive compressed text frame containing the Hello string 417 {ok, << 1:1, 1:1, 0:2, 1:4, 0:1, 7:7, Hello/binary >>} 418 = gen_tcp:recv(Socket, 0, 6000), 419 420 ok = gen_tcp:send(Socket, << 1:1, 0:3, 8:4, 1:1, 0:7, 0:32 >>), %% close 421 {ok, << 1:1, 0:3, 8:4, 0:8 >>} = gen_tcp:recv(Socket, 0, 6000), 422 {error, closed} = gen_tcp:recv(Socket, 0, 6000), 423 ok. 424 425ws_send_close(Config) -> 426 {port, Port} = lists:keyfind(port, 1, Config), 427 {ok, Socket} = gen_tcp:connect("localhost", Port, 428 [binary, {active, false}, {packet, raw}]), 429 ok = gen_tcp:send(Socket, [ 430 "GET /ws_send_close HTTP/1.1\r\n" 431 "Host: localhost\r\n" 432 "Connection: Upgrade\r\n" 433 "Upgrade: websocket\r\n" 434 "Sec-WebSocket-Origin: http://localhost\r\n" 435 "Sec-WebSocket-Version: 8\r\n" 436 "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" 437 "\r\n"]), 438 {ok, Handshake} = gen_tcp:recv(Socket, 0, 6000), 439 {ok, {http_response, {1, 1}, 101, "Switching Protocols"}, Rest} 440 = erlang:decode_packet(http, Handshake, []), 441 [Headers, <<>>] = do_decode_headers( 442 erlang:decode_packet(httph, Rest, []), []), 443 {'Connection', "Upgrade"} = lists:keyfind('Connection', 1, Headers), 444 {'Upgrade', "websocket"} = lists:keyfind('Upgrade', 1, Headers), 445 {"sec-websocket-accept", "s3pPLMBiTxaQ9kYGzzhZRbK+xOo="} 446 = lists:keyfind("sec-websocket-accept", 1, Headers), 447 %% We catch all frames at once and check them directly. 448 {ok, Many} = gen_tcp:recv(Socket, 8, 6000), 449 << 1:1, 0:3, 1:4, 0:1, 4:7, "send", 450 1:1, 0:3, 8:4, 0:8 >> = Many, 451 {error, closed} = gen_tcp:recv(Socket, 0, 6000), 452 ok. 453 454ws_send_close_payload(Config) -> 455 {port, Port} = lists:keyfind(port, 1, Config), 456 {ok, Socket} = gen_tcp:connect("localhost", Port, 457 [binary, {active, false}, {packet, raw}]), 458 ok = gen_tcp:send(Socket, [ 459 "GET /ws_send_close_payload HTTP/1.1\r\n" 460 "Host: localhost\r\n" 461 "Connection: Upgrade\r\n" 462 "Upgrade: websocket\r\n" 463 "Sec-WebSocket-Origin: http://localhost\r\n" 464 "Sec-WebSocket-Version: 8\r\n" 465 "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" 466 "\r\n"]), 467 {ok, Handshake} = gen_tcp:recv(Socket, 0, 6000), 468 {ok, {http_response, {1, 1}, 101, "Switching Protocols"}, Rest} 469 = erlang:decode_packet(http, Handshake, []), 470 [Headers, <<>>] = do_decode_headers( 471 erlang:decode_packet(httph, Rest, []), []), 472 {'Connection', "Upgrade"} = lists:keyfind('Connection', 1, Headers), 473 {'Upgrade', "websocket"} = lists:keyfind('Upgrade', 1, Headers), 474 {"sec-websocket-accept", "s3pPLMBiTxaQ9kYGzzhZRbK+xOo="} 475 = lists:keyfind("sec-websocket-accept", 1, Headers), 476 %% We catch all frames at once and check them directly. 477 {ok, Many} = gen_tcp:recv(Socket, 20, 6000), 478 << 1:1, 0:3, 1:4, 0:1, 4:7, "send", 479 1:1, 0:3, 8:4, 0:1, 12:7, 1001:16, "some text!" >> = Many, 480 {error, closed} = gen_tcp:recv(Socket, 0, 6000), 481 ok. 482 483ws_send_many(Config) -> 484 {port, Port} = lists:keyfind(port, 1, Config), 485 {ok, Socket} = gen_tcp:connect("localhost", Port, 486 [binary, {active, false}, {packet, raw}]), 487 ok = gen_tcp:send(Socket, [ 488 "GET /ws_send_many HTTP/1.1\r\n" 489 "Host: localhost\r\n" 490 "Connection: Upgrade\r\n" 491 "Upgrade: websocket\r\n" 492 "Sec-WebSocket-Origin: http://localhost\r\n" 493 "Sec-WebSocket-Version: 8\r\n" 494 "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" 495 "\r\n"]), 496 {ok, Handshake} = gen_tcp:recv(Socket, 0, 6000), 497 {ok, {http_response, {1, 1}, 101, "Switching Protocols"}, Rest} 498 = erlang:decode_packet(http, Handshake, []), 499 [Headers, <<>>] = do_decode_headers( 500 erlang:decode_packet(httph, Rest, []), []), 501 {'Connection', "Upgrade"} = lists:keyfind('Connection', 1, Headers), 502 {'Upgrade', "websocket"} = lists:keyfind('Upgrade', 1, Headers), 503 {"sec-websocket-accept", "s3pPLMBiTxaQ9kYGzzhZRbK+xOo="} 504 = lists:keyfind("sec-websocket-accept", 1, Headers), 505 %% We catch all frames at once and check them directly. 506 {ok, Many} = gen_tcp:recv(Socket, 18, 6000), 507 << 1:1, 0:3, 1:4, 0:1, 3:7, "one", 508 1:1, 0:3, 1:4, 0:1, 3:7, "two", 509 1:1, 0:3, 1:4, 0:1, 6:7, "seven!" >> = Many, 510 ok = gen_tcp:send(Socket, << 1:1, 0:3, 8:4, 1:1, 0:7, 0:32 >>), %% close 511 {ok, << 1:1, 0:3, 8:4, 0:8 >>} = gen_tcp:recv(Socket, 0, 6000), 512 {error, closed} = gen_tcp:recv(Socket, 0, 6000), 513 ok. 514 515ws_text_fragments(Config) -> 516 {port, Port} = lists:keyfind(port, 1, Config), 517 {ok, Socket} = gen_tcp:connect("localhost", Port, 518 [binary, {active, false}, {packet, raw}]), 519 ok = gen_tcp:send(Socket, [ 520 "GET /ws_echo HTTP/1.1\r\n" 521 "Host: localhost\r\n" 522 "Connection: Upgrade\r\n" 523 "Upgrade: websocket\r\n" 524 "Sec-WebSocket-Origin: http://localhost\r\n" 525 "Sec-WebSocket-Version: 8\r\n" 526 "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" 527 "\r\n"]), 528 {ok, Handshake} = gen_tcp:recv(Socket, 0, 6000), 529 {ok, {http_response, {1, 1}, 101, "Switching Protocols"}, Rest} 530 = erlang:decode_packet(http, Handshake, []), 531 [Headers, <<>>] = do_decode_headers( 532 erlang:decode_packet(httph, Rest, []), []), 533 {'Connection', "Upgrade"} = lists:keyfind('Connection', 1, Headers), 534 {'Upgrade', "websocket"} = lists:keyfind('Upgrade', 1, Headers), 535 {"sec-websocket-accept", "s3pPLMBiTxaQ9kYGzzhZRbK+xOo="} 536 = lists:keyfind("sec-websocket-accept", 1, Headers), 537 538 ok = gen_tcp:send(Socket, [ 539 << 0:1, 0:3, 1:4, 1:1, 5:7 >>, 540 << 16#37 >>, << 16#fa >>, << 16#21 >>, << 16#3d >>, << 16#7f >>, 541 << 16#9f >>, << 16#4d >>, << 16#51 >>, << 16#58 >>]), 542 ok = gen_tcp:send(Socket, [ 543 << 1:1, 0:3, 0:4, 1:1, 5:7 >>, 544 << 16#37 >>, << 16#fa >>, << 16#21 >>, << 16#3d >>, << 16#7f >>, 545 << 16#9f >>, << 16#4d >>, << 16#51 >>, << 16#58 >>]), 546 {ok, << 1:1, 0:3, 1:4, 0:1, 10:7, "HelloHello" >>} 547 = gen_tcp:recv(Socket, 0, 6000), 548 549 ok = gen_tcp:send(Socket, [ 550 %% #1 551 << 0:1, 0:3, 1:4, 1:1, 5:7 >>, 552 << 16#37 >>, << 16#fa >>, << 16#21 >>, << 16#3d >>, << 16#7f >>, 553 << 16#9f >>, << 16#4d >>, << 16#51 >>, << 16#58 >>, 554 %% #2 555 << 0:1, 0:3, 0:4, 1:1, 5:7 >>, 556 << 16#37 >>, << 16#fa >>, << 16#21 >>, << 16#3d >>, << 16#7f >>, 557 << 16#9f >>, << 16#4d >>, << 16#51 >>, << 16#58 >>, 558 %% #3 559 << 1:1, 0:3, 0:4, 1:1, 5:7 >>, 560 << 16#37 >>, << 16#fa >>, << 16#21 >>, << 16#3d >>, << 16#7f >>, 561 << 16#9f >>, << 16#4d >>, << 16#51 >>, << 16#58 >>]), 562 {ok, << 1:1, 0:3, 1:4, 0:1, 15:7, "HelloHelloHello" >>} 563 = gen_tcp:recv(Socket, 0, 6000), 564 ok = gen_tcp:send(Socket, << 1:1, 0:3, 8:4, 1:1, 0:7, 0:32 >>), %% close 565 {ok, << 1:1, 0:3, 8:4, 0:8 >>} = gen_tcp:recv(Socket, 0, 6000), 566 {error, closed} = gen_tcp:recv(Socket, 0, 6000), 567 ok. 568 569ws_timeout_hibernate(Config) -> 570 {port, Port} = lists:keyfind(port, 1, Config), 571 {ok, Socket} = gen_tcp:connect("localhost", Port, 572 [binary, {active, false}, {packet, raw}]), 573 ok = gen_tcp:send(Socket, [ 574 "GET /ws_timeout_hibernate HTTP/1.1\r\n" 575 "Host: localhost\r\n" 576 "Connection: Upgrade\r\n" 577 "Upgrade: websocket\r\n" 578 "Sec-WebSocket-Origin: http://localhost\r\n" 579 "Sec-WebSocket-Version: 8\r\n" 580 "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" 581 "\r\n"]), 582 {ok, Handshake} = gen_tcp:recv(Socket, 0, 6000), 583 {ok, {http_response, {1, 1}, 101, "Switching Protocols"}, Rest} 584 = erlang:decode_packet(http, Handshake, []), 585 [Headers, <<>>] = do_decode_headers( 586 erlang:decode_packet(httph, Rest, []), []), 587 {'Connection', "Upgrade"} = lists:keyfind('Connection', 1, Headers), 588 {'Upgrade', "websocket"} = lists:keyfind('Upgrade', 1, Headers), 589 {"sec-websocket-accept", "s3pPLMBiTxaQ9kYGzzhZRbK+xOo="} 590 = lists:keyfind("sec-websocket-accept", 1, Headers), 591 {ok, << 1:1, 0:3, 8:4, 0:1, 2:7, 1000:16 >>} = gen_tcp:recv(Socket, 0, 6000), 592 {error, closed} = gen_tcp:recv(Socket, 0, 6000), 593 ok. 594 595ws_timeout_cancel(Config) -> 596 %% Erlang messages to a socket should not cancel the timeout 597 {port, Port} = lists:keyfind(port, 1, Config), 598 {ok, Socket} = gen_tcp:connect("localhost", Port, 599 [binary, {active, false}, {packet, raw}]), 600 ok = gen_tcp:send(Socket, [ 601 "GET /ws_timeout_cancel HTTP/1.1\r\n" 602 "Host: localhost\r\n" 603 "Connection: Upgrade\r\n" 604 "Upgrade: websocket\r\n" 605 "Sec-WebSocket-Origin: http://localhost\r\n" 606 "Sec-WebSocket-Version: 8\r\n" 607 "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" 608 "\r\n"]), 609 {ok, Handshake} = gen_tcp:recv(Socket, 0, 6000), 610 {ok, {http_response, {1, 1}, 101, "Switching Protocols"}, Rest} 611 = erlang:decode_packet(http, Handshake, []), 612 [Headers, <<>>] = do_decode_headers( 613 erlang:decode_packet(httph, Rest, []), []), 614 {'Connection', "Upgrade"} = lists:keyfind('Connection', 1, Headers), 615 {'Upgrade', "websocket"} = lists:keyfind('Upgrade', 1, Headers), 616 {"sec-websocket-accept", "s3pPLMBiTxaQ9kYGzzhZRbK+xOo="} 617 = lists:keyfind("sec-websocket-accept", 1, Headers), 618 {ok, << 1:1, 0:3, 8:4, 0:1, 2:7, 1000:16 >>} = gen_tcp:recv(Socket, 0, 6000), 619 {error, closed} = gen_tcp:recv(Socket, 0, 6000), 620 ok. 621 622ws_timeout_reset(Config) -> 623 %% Erlang messages across a socket should reset the timeout 624 {port, Port} = lists:keyfind(port, 1, Config), 625 {ok, Socket} = gen_tcp:connect("localhost", Port, 626 [binary, {active, false}, {packet, raw}]), 627 ok = gen_tcp:send(Socket, [ 628 "GET /ws_timeout_cancel HTTP/1.1\r\n" 629 "Host: localhost\r\n" 630 "Connection: Upgrade\r\n" 631 "Upgrade: websocket\r\n" 632 "Sec-WebSocket-Origin: http://localhost\r\n" 633 "Sec-Websocket-Version: 13\r\n" 634 "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" 635 "\r\n"]), 636 {ok, Handshake} = gen_tcp:recv(Socket, 0, 6000), 637 {ok, {http_response, {1, 1}, 101, "Switching Protocols"}, Rest} 638 = erlang:decode_packet(http, Handshake, []), 639 [Headers, <<>>] = do_decode_headers( 640 erlang:decode_packet(httph, Rest, []), []), 641 {'Connection', "Upgrade"} = lists:keyfind('Connection', 1, Headers), 642 {'Upgrade', "websocket"} = lists:keyfind('Upgrade', 1, Headers), 643 {"sec-websocket-accept", "s3pPLMBiTxaQ9kYGzzhZRbK+xOo="} 644 = lists:keyfind("sec-websocket-accept", 1, Headers), 645 [begin 646 ok = gen_tcp:send(Socket, << 16#81, 16#85, 16#37, 16#fa, 16#21, 16#3d, 647 16#7f, 16#9f, 16#4d, 16#51, 16#58 >>), 648 {ok, << 1:1, 0:3, 1:4, 0:1, 5:7, "Hello" >>} 649 = gen_tcp:recv(Socket, 0, 6000), 650 ok = timer:sleep(500) 651 end || _ <- [1, 2, 3, 4]], 652 {ok, << 1:1, 0:3, 8:4, 0:1, 2:7, 1000:16 >>} = gen_tcp:recv(Socket, 0, 6000), 653 {error, closed} = gen_tcp:recv(Socket, 0, 6000), 654 ok. 655 656ws_upgrade_with_opts(Config) -> 657 {port, Port} = lists:keyfind(port, 1, Config), 658 {ok, Socket} = gen_tcp:connect("localhost", Port, 659 [binary, {active, false}, {packet, raw}]), 660 ok = gen_tcp:send(Socket, [ 661 "GET /ws_upgrade_with_opts HTTP/1.1\r\n" 662 "Host: localhost\r\n" 663 "Connection: Upgrade\r\n" 664 "Upgrade: websocket\r\n" 665 "Sec-WebSocket-Origin: http://localhost\r\n" 666 "Sec-WebSocket-Version: 8\r\n" 667 "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" 668 "\r\n"]), 669 {ok, Handshake} = gen_tcp:recv(Socket, 0, 6000), 670 {ok, {http_response, {1, 1}, 101, "Switching Protocols"}, Rest} 671 = erlang:decode_packet(http, Handshake, []), 672 [Headers, <<>>] = do_decode_headers( 673 erlang:decode_packet(httph, Rest, []), []), 674 {'Connection', "Upgrade"} = lists:keyfind('Connection', 1, Headers), 675 {'Upgrade', "websocket"} = lists:keyfind('Upgrade', 1, Headers), 676 {"sec-websocket-accept", "s3pPLMBiTxaQ9kYGzzhZRbK+xOo="} 677 = lists:keyfind("sec-websocket-accept", 1, Headers), 678 {ok, Response} = gen_tcp:recv(Socket, 9, 6000), 679 << 1:1, 0:3, 1:4, 0:1, 7:7, "success" >> = Response, 680 ok = gen_tcp:send(Socket, << 1:1, 0:3, 8:4, 1:1, 0:7, 0:32 >>), %% close 681 {ok, << 1:1, 0:3, 8:4, 0:8 >>} = gen_tcp:recv(Socket, 0, 6000), 682 {error, closed} = gen_tcp:recv(Socket, 0, 6000), 683 ok. 684 685%% Internal. 686 687do_decode_headers({ok, http_eoh, Rest}, Acc) -> 688 [Acc, Rest]; 689do_decode_headers({ok, {http_header, _I, Key, _R, Value}, Rest}, Acc) -> 690 F = fun(S) when is_atom(S) -> S; (S) -> string:to_lower(S) end, 691 do_decode_headers(erlang:decode_packet(httph, Rest, []), 692 [{F(Key), Value}|Acc]). 693 694do_mask(<<>>, _, Acc) -> 695 Acc; 696do_mask(<< O:32, Rest/bits >>, MaskKey, Acc) -> 697 T = O bxor MaskKey, 698 do_mask(Rest, MaskKey, << Acc/binary, T:32 >>); 699do_mask(<< O:24 >>, MaskKey, Acc) -> 700 << MaskKey2:24, _:8 >> = << MaskKey:32 >>, 701 T = O bxor MaskKey2, 702 << Acc/binary, T:24 >>; 703do_mask(<< O:16 >>, MaskKey, Acc) -> 704 << MaskKey2:16, _:16 >> = << MaskKey:32 >>, 705 T = O bxor MaskKey2, 706 << Acc/binary, T:16 >>; 707do_mask(<< O:8 >>, MaskKey, Acc) -> 708 << MaskKey2:8, _:24 >> = << MaskKey:32 >>, 709 T = O bxor MaskKey2, 710 << Acc/binary, T:8 >>.