PageRenderTime 85ms CodeModel.GetById 2ms app.highlight 77ms RepoModel.GetById 1ms app.codeStats 0ms

/test/ws_SUITE.erl

https://github.com/si14/cowboy
Erlang | 579 lines | 516 code | 31 blank | 32 comment | 1 complexity | 2361f12f6d6185b760368523b171dd21 MD5 | raw file
  1%% Copyright (c) 2011-2013, 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
 17-include_lib("common_test/include/ct.hrl").
 18
 19%% ct.
 20-export([all/0]).
 21-export([groups/0]).
 22-export([init_per_suite/1]).
 23-export([end_per_suite/1]).
 24-export([init_per_group/2]).
 25-export([end_per_group/2]).
 26
 27%% Tests.
 28-export([ws0/1]).
 29-export([ws8/1]).
 30-export([ws8_init_shutdown/1]).
 31-export([ws8_single_bytes/1]).
 32-export([ws13/1]).
 33-export([ws_send_close/1]).
 34-export([ws_send_close_payload/1]).
 35-export([ws_send_many/1]).
 36-export([ws_text_fragments/1]).
 37-export([ws_timeout_hibernate/1]).
 38-export([ws_timeout_cancel/1]).
 39-export([ws_timeout_reset/1]).
 40-export([ws_upgrade_with_opts/1]).
 41
 42%% ct.
 43
 44all() ->
 45	[{group, ws}].
 46
 47groups() ->
 48	BaseTests = [
 49		ws0,
 50		ws8,
 51		ws8_init_shutdown,
 52		ws8_single_bytes,
 53		ws13,
 54		ws_send_close,
 55		ws_send_close_payload,
 56		ws_send_many,
 57		ws_text_fragments,
 58		ws_timeout_hibernate,
 59		ws_timeout_cancel,
 60		ws_timeout_reset,
 61		ws_upgrade_with_opts
 62	],
 63	[{ws, [], BaseTests}].
 64
 65init_per_suite(Config) ->
 66	application:start(crypto),
 67	application:start(ranch),
 68	application:start(cowboy),
 69	Config.
 70
 71end_per_suite(_Config) ->
 72	application:stop(cowboy),
 73	application:stop(ranch),
 74	application:stop(crypto),
 75	ok.
 76
 77init_per_group(ws, Config) ->
 78	Port = 33080,
 79	cowboy:start_http(ws, 100, [{port, Port}], [
 80		{env, [{dispatch, init_dispatch()}]}
 81	]),
 82	[{port, Port}|Config].
 83
 84end_per_group(Listener, _Config) ->
 85	cowboy:stop_listener(Listener),
 86	ok.
 87
 88%% Dispatch configuration.
 89
 90init_dispatch() ->
 91	cowboy_router:compile([
 92		{"localhost", [
 93			{"/websocket", websocket_handler, []},
 94			{"/ws_echo_handler", websocket_echo_handler, []},
 95			{"/ws_init_shutdown", websocket_handler_init_shutdown, []},
 96			{"/ws_send_many", ws_send_many_handler, [
 97				{sequence, [
 98					{text, <<"one">>},
 99					{text, <<"two">>},
100					{text, <<"seven!">>}]}
101			]},
102			{"/ws_send_close", ws_send_many_handler, [
103				{sequence, [
104					{text, <<"send">>},
105					close,
106					{text, <<"won't be received">>}]}
107			]},
108			{"/ws_send_close_payload", ws_send_many_handler, [
109				{sequence, [
110					{text, <<"send">>},
111					{close, 1001, <<"some text!">>},
112					{text, <<"won't be received">>}]}
113			]},
114			{"/ws_timeout_hibernate", ws_timeout_hibernate_handler, []},
115			{"/ws_timeout_cancel", ws_timeout_cancel_handler, []},
116			{"/ws_upgrade_with_opts", ws_upgrade_with_opts_handler,
117				<<"failure">>}
118		]}
119	]).
120
121%% ws and wss.
122
123%% We do not support hixie76 anymore.
124ws0(Config) ->
125	{port, Port} = lists:keyfind(port, 1, Config),
126	{ok, Socket} = gen_tcp:connect("localhost", Port,
127		[binary, {active, false}, {packet, raw}]),
128	ok = gen_tcp:send(Socket,
129		"GET /websocket HTTP/1.1\r\n"
130		"Host: localhost\r\n"
131		"Connection: Upgrade\r\n"
132		"Upgrade: WebSocket\r\n"
133		"Origin: http://localhost\r\n"
134		"Sec-Websocket-Key1: Y\" 4 1Lj!957b8@0H756!i\r\n"
135		"Sec-Websocket-Key2: 1711 M;4\\74  80<6\r\n"
136		"\r\n"),
137	{ok, Handshake} = gen_tcp:recv(Socket, 0, 6000),
138	{ok, {http_response, {1, 1}, 400, _}, _}
139		= erlang:decode_packet(http, Handshake, []).
140
141ws8(Config) ->
142	{port, Port} = lists:keyfind(port, 1, Config),
143	{ok, Socket} = gen_tcp:connect("localhost", Port,
144		[binary, {active, false}, {packet, raw}]),
145	ok = gen_tcp:send(Socket, [
146		"GET /websocket HTTP/1.1\r\n"
147		"Host: localhost\r\n"
148		"Connection: Upgrade\r\n"
149		"Upgrade: websocket\r\n"
150		"Sec-WebSocket-Origin: http://localhost\r\n"
151		"Sec-WebSocket-Version: 8\r\n"
152		"Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
153		"\r\n"]),
154	{ok, Handshake} = gen_tcp:recv(Socket, 0, 6000),
155	{ok, {http_response, {1, 1}, 101, "Switching Protocols"}, Rest}
156		= erlang:decode_packet(http, Handshake, []),
157	[Headers, <<>>] = websocket_headers(
158		erlang:decode_packet(httph, Rest, []), []),
159	{'Connection', "Upgrade"} = lists:keyfind('Connection', 1, Headers),
160	{'Upgrade', "websocket"} = lists:keyfind('Upgrade', 1, Headers),
161	{"sec-websocket-accept", "s3pPLMBiTxaQ9kYGzzhZRbK+xOo="}
162		= lists:keyfind("sec-websocket-accept", 1, Headers),
163	ok = gen_tcp:send(Socket, << 16#81, 16#85, 16#37, 16#fa, 16#21, 16#3d,
164		16#7f, 16#9f, 16#4d, 16#51, 16#58 >>),
165	{ok, << 1:1, 0:3, 1:4, 0:1, 5:7, "Hello" >>}
166		= gen_tcp:recv(Socket, 0, 6000),
167	{ok, << 1:1, 0:3, 1:4, 0:1, 14:7, "websocket_init" >>}
168		= gen_tcp:recv(Socket, 0, 6000),
169	{ok, << 1:1, 0:3, 1:4, 0:1, 16:7, "websocket_handle" >>}
170		= gen_tcp:recv(Socket, 0, 6000),
171	{ok, << 1:1, 0:3, 1:4, 0:1, 16:7, "websocket_handle" >>}
172		= gen_tcp:recv(Socket, 0, 6000),
173	{ok, << 1:1, 0:3, 1:4, 0:1, 16:7, "websocket_handle" >>}
174		= gen_tcp:recv(Socket, 0, 6000),
175	ok = gen_tcp:send(Socket, << 1:1, 0:3, 9:4, 1:1, 0:7, 0:32 >>), %% ping
176	{ok, << 1:1, 0:3, 10:4, 0:8 >>} = gen_tcp:recv(Socket, 0, 6000), %% pong
177	ok = gen_tcp:send(Socket, << 1:1, 0:3, 8:4, 1:1, 0:7, 0:32 >>), %% close
178	{ok, << 1:1, 0:3, 8:4, 0:8 >>} = gen_tcp:recv(Socket, 0, 6000),
179	{error, closed} = gen_tcp:recv(Socket, 0, 6000),
180	ok.
181
182ws8_init_shutdown(Config) ->
183	{port, Port} = lists:keyfind(port, 1, Config),
184	{ok, Socket} = gen_tcp:connect("localhost", Port,
185		[binary, {active, false}, {packet, raw}]),
186	ok = gen_tcp:send(Socket, [
187		"GET /ws_init_shutdown HTTP/1.1\r\n"
188		"Host: localhost\r\n"
189		"Connection: Upgrade\r\n"
190		"Upgrade: websocket\r\n"
191		"Sec-WebSocket-Origin: http://localhost\r\n"
192		"Sec-WebSocket-Version: 8\r\n"
193		"Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
194		"\r\n"]),
195	{ok, Handshake} = gen_tcp:recv(Socket, 0, 6000),
196	{ok, {http_response, {1, 1}, 403, "Forbidden"}, _Rest}
197		= erlang:decode_packet(http, Handshake, []),
198	{error, closed} = gen_tcp:recv(Socket, 0, 6000),
199	ok.
200
201ws8_single_bytes(Config) ->
202	{port, Port} = lists:keyfind(port, 1, Config),
203	{ok, Socket} = gen_tcp:connect("localhost", Port,
204		[binary, {active, false}, {packet, raw}]),
205	ok = gen_tcp:send(Socket, [
206		"GET /websocket HTTP/1.1\r\n"
207		"Host: localhost\r\n"
208		"Connection: Upgrade\r\n"
209		"Upgrade: websocket\r\n"
210		"Sec-WebSocket-Origin: http://localhost\r\n"
211		"Sec-WebSocket-Version: 8\r\n"
212		"Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
213		"\r\n"]),
214	{ok, Handshake} = gen_tcp:recv(Socket, 0, 6000),
215	{ok, {http_response, {1, 1}, 101, "Switching Protocols"}, Rest}
216		= erlang:decode_packet(http, Handshake, []),
217	[Headers, <<>>] = websocket_headers(
218		erlang:decode_packet(httph, Rest, []), []),
219	{'Connection', "Upgrade"} = lists:keyfind('Connection', 1, Headers),
220	{'Upgrade', "websocket"} = lists:keyfind('Upgrade', 1, Headers),
221	{"sec-websocket-accept", "s3pPLMBiTxaQ9kYGzzhZRbK+xOo="}
222		= lists:keyfind("sec-websocket-accept", 1, Headers),
223	ok = gen_tcp:send(Socket, << 16#81 >>), %% send one byte
224	ok = timer:sleep(100), %% sleep for a period
225	ok = gen_tcp:send(Socket, << 16#85 >>), %% send another and so on
226	ok = timer:sleep(100),
227	ok = gen_tcp:send(Socket, << 16#37 >>),
228	ok = timer:sleep(100),
229	ok = gen_tcp:send(Socket, << 16#fa >>),
230	ok = timer:sleep(100),
231	ok = gen_tcp:send(Socket, << 16#21 >>),
232	ok = timer:sleep(100),
233	ok = gen_tcp:send(Socket, << 16#3d >>),
234	ok = timer:sleep(100),
235	ok = gen_tcp:send(Socket, << 16#7f >>),
236	ok = timer:sleep(100),
237	ok = gen_tcp:send(Socket, << 16#9f >>),
238	ok = timer:sleep(100),
239	ok = gen_tcp:send(Socket, << 16#4d >>),
240	ok = timer:sleep(100),
241	ok = gen_tcp:send(Socket, << 16#51 >>),
242	ok = timer:sleep(100),
243	ok = gen_tcp:send(Socket, << 16#58 >>),
244	{ok, << 1:1, 0:3, 1:4, 0:1, 14:7, "websocket_init" >>}
245		= gen_tcp:recv(Socket, 0, 6000),
246	{ok, << 1:1, 0:3, 1:4, 0:1, 5:7, "Hello" >>}
247		= gen_tcp:recv(Socket, 0, 6000),
248	{ok, << 1:1, 0:3, 1:4, 0:1, 16:7, "websocket_handle" >>}
249		= gen_tcp:recv(Socket, 0, 6000),
250	{ok, << 1:1, 0:3, 1:4, 0:1, 16:7, "websocket_handle" >>}
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 = gen_tcp:send(Socket, << 1:1, 0:3, 9:4, 1:1, 0:7, 0:32 >>), %% ping
255	{ok, << 1:1, 0:3, 10:4, 0:8 >>} = gen_tcp:recv(Socket, 0, 6000), %% pong
256	ok = gen_tcp:send(Socket, << 1:1, 0:3, 8:4, 1:1, 0:7, 0:32 >>), %% close
257	{ok, << 1:1, 0:3, 8:4, 0:8 >>} = gen_tcp:recv(Socket, 0, 6000),
258	{error, closed} = gen_tcp:recv(Socket, 0, 6000),
259	ok.
260
261ws13(Config) ->
262	{port, Port} = lists:keyfind(port, 1, Config),
263	{ok, Socket} = gen_tcp:connect("localhost", Port,
264		[binary, {active, false}, {packet, raw}]),
265	ok = gen_tcp:send(Socket, [
266		"GET /websocket HTTP/1.1\r\n"
267		"Host: localhost\r\n"
268		"Connection: Upgrade\r\n"
269		"Origin: http://localhost\r\n"
270		"Sec-WebSocket-Version: 13\r\n"
271		"Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
272		"Upgrade: websocket\r\n"
273		"\r\n"]),
274	{ok, Handshake} = gen_tcp:recv(Socket, 0, 6000),
275	{ok, {http_response, {1, 1}, 101, "Switching Protocols"}, Rest}
276		= erlang:decode_packet(http, Handshake, []),
277	[Headers, <<>>] = websocket_headers(
278		erlang:decode_packet(httph, Rest, []), []),
279	{'Connection', "Upgrade"} = lists:keyfind('Connection', 1, Headers),
280	{'Upgrade', "websocket"} = lists:keyfind('Upgrade', 1, Headers),
281	{"sec-websocket-accept", "s3pPLMBiTxaQ9kYGzzhZRbK+xOo="}
282		= lists:keyfind("sec-websocket-accept", 1, Headers),
283	%% text
284	ok = gen_tcp:send(Socket, << 16#81, 16#85, 16#37, 16#fa, 16#21, 16#3d,
285		16#7f, 16#9f, 16#4d, 16#51, 16#58 >>),
286	{ok, << 1:1, 0:3, 1:4, 0:1, 5:7, "Hello" >>}
287		= gen_tcp:recv(Socket, 0, 6000),
288	%% binary (empty)
289	ok = gen_tcp:send(Socket, << 1:1, 0:3, 2:4, 1:1, 0:7, 0:32 >>),
290	{ok, << 1:1, 0:3, 2:4, 0:8 >>} = gen_tcp:recv(Socket, 0, 6000),
291	%% binary
292	ok = gen_tcp:send(Socket, << 16#82, 16#85, 16#37, 16#fa, 16#21, 16#3d,
293		16#7f, 16#9f, 16#4d, 16#51, 16#58 >>),
294	{ok, << 1:1, 0:3, 2:4, 0:1, 5:7, "Hello" >>}
295		= gen_tcp:recv(Socket, 0, 6000),
296	%% Receives.
297	{ok, << 1:1, 0:3, 1:4, 0:1, 14:7, "websocket_init" >>}
298		= gen_tcp:recv(Socket, 0, 6000),
299	{ok, << 1:1, 0:3, 1:4, 0:1, 16:7, "websocket_handle" >>}
300		= gen_tcp:recv(Socket, 0, 6000),
301	{ok, << 1:1, 0:3, 1:4, 0:1, 16:7, "websocket_handle" >>}
302		= gen_tcp:recv(Socket, 0, 6000),
303	{ok, << 1:1, 0:3, 1:4, 0:1, 16:7, "websocket_handle" >>}
304		= gen_tcp:recv(Socket, 0, 6000),
305	ok = gen_tcp:send(Socket, << 1:1, 0:3, 9:4, 1:1, 0:7, 0:32 >>), %% ping
306	{ok, << 1:1, 0:3, 10:4, 0:8 >>} = gen_tcp:recv(Socket, 0, 6000), %% pong
307	ok = gen_tcp:send(Socket, << 1:1, 0:3, 8:4, 1:1, 0:7, 0:32 >>), %% close
308	{ok, << 1:1, 0:3, 8:4, 0:8 >>} = gen_tcp:recv(Socket, 0, 6000),
309	{error, closed} = gen_tcp:recv(Socket, 0, 6000),
310	ok.
311
312ws_send_close(Config) ->
313	{port, Port} = lists:keyfind(port, 1, Config),
314	{ok, Socket} = gen_tcp:connect("localhost", Port,
315		[binary, {active, false}, {packet, raw}]),
316	ok = gen_tcp:send(Socket, [
317		"GET /ws_send_close HTTP/1.1\r\n"
318		"Host: localhost\r\n"
319		"Connection: Upgrade\r\n"
320		"Upgrade: websocket\r\n"
321		"Sec-WebSocket-Origin: http://localhost\r\n"
322		"Sec-WebSocket-Version: 8\r\n"
323		"Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
324		"\r\n"]),
325	{ok, Handshake} = gen_tcp:recv(Socket, 0, 6000),
326	{ok, {http_response, {1, 1}, 101, "Switching Protocols"}, Rest}
327		= erlang:decode_packet(http, Handshake, []),
328	[Headers, <<>>] = websocket_headers(
329		erlang:decode_packet(httph, Rest, []), []),
330	{'Connection', "Upgrade"} = lists:keyfind('Connection', 1, Headers),
331	{'Upgrade', "websocket"} = lists:keyfind('Upgrade', 1, Headers),
332	{"sec-websocket-accept", "s3pPLMBiTxaQ9kYGzzhZRbK+xOo="}
333		= lists:keyfind("sec-websocket-accept", 1, Headers),
334	%% We catch all frames at once and check them directly.
335	{ok, Many} = gen_tcp:recv(Socket, 8, 6000),
336	<< 1:1, 0:3, 1:4, 0:1, 4:7, "send",
337		1:1, 0:3, 8:4, 0:8 >> = Many,
338	{error, closed} = gen_tcp:recv(Socket, 0, 6000),
339	ok.
340
341ws_send_close_payload(Config) ->
342	{port, Port} = lists:keyfind(port, 1, Config),
343	{ok, Socket} = gen_tcp:connect("localhost", Port,
344		[binary, {active, false}, {packet, raw}]),
345	ok = gen_tcp:send(Socket, [
346		"GET /ws_send_close_payload HTTP/1.1\r\n"
347		"Host: localhost\r\n"
348		"Connection: Upgrade\r\n"
349		"Upgrade: websocket\r\n"
350		"Sec-WebSocket-Origin: http://localhost\r\n"
351		"Sec-WebSocket-Version: 8\r\n"
352		"Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
353		"\r\n"]),
354	{ok, Handshake} = gen_tcp:recv(Socket, 0, 6000),
355	{ok, {http_response, {1, 1}, 101, "Switching Protocols"}, Rest}
356		= erlang:decode_packet(http, Handshake, []),
357	[Headers, <<>>] = websocket_headers(
358		erlang:decode_packet(httph, Rest, []), []),
359	{'Connection', "Upgrade"} = lists:keyfind('Connection', 1, Headers),
360	{'Upgrade', "websocket"} = lists:keyfind('Upgrade', 1, Headers),
361	{"sec-websocket-accept", "s3pPLMBiTxaQ9kYGzzhZRbK+xOo="}
362		= lists:keyfind("sec-websocket-accept", 1, Headers),
363	%% We catch all frames at once and check them directly.
364	{ok, Many} = gen_tcp:recv(Socket, 20, 6000),
365	<< 1:1, 0:3, 1:4, 0:1, 4:7, "send",
366		1:1, 0:3, 8:4, 0:1, 12:7, 1001:16, "some text!" >> = Many,
367	{error, closed} = gen_tcp:recv(Socket, 0, 6000),
368	ok.
369
370ws_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, 1:1, 0:7, 0:32 >>), %% 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
402ws_text_fragments(Config) ->
403	{port, Port} = lists:keyfind(port, 1, Config),
404	{ok, Socket} = gen_tcp:connect("localhost", Port,
405		[binary, {active, false}, {packet, raw}]),
406	ok = gen_tcp:send(Socket, [
407		"GET /ws_echo_handler HTTP/1.1\r\n"
408		"Host: localhost\r\n"
409		"Connection: Upgrade\r\n"
410		"Upgrade: websocket\r\n"
411		"Sec-WebSocket-Origin: http://localhost\r\n"
412		"Sec-WebSocket-Version: 8\r\n"
413		"Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
414		"\r\n"]),
415	{ok, Handshake} = gen_tcp:recv(Socket, 0, 6000),
416	{ok, {http_response, {1, 1}, 101, "Switching Protocols"}, Rest}
417		= erlang:decode_packet(http, Handshake, []),
418	[Headers, <<>>] = websocket_headers(
419		erlang:decode_packet(httph, Rest, []), []),
420	{'Connection', "Upgrade"} = lists:keyfind('Connection', 1, Headers),
421	{'Upgrade', "websocket"} = lists:keyfind('Upgrade', 1, Headers),
422	{"sec-websocket-accept", "s3pPLMBiTxaQ9kYGzzhZRbK+xOo="}
423		= lists:keyfind("sec-websocket-accept", 1, Headers),
424
425	ok = gen_tcp:send(Socket, [
426		<< 0:1, 0:3, 1:4, 1:1, 5:7 >>,
427		<< 16#37 >>, << 16#fa >>, << 16#21 >>, << 16#3d >>, << 16#7f >>,
428		<< 16#9f >>, << 16#4d >>, << 16#51 >>, << 16#58 >>]),
429	ok = gen_tcp:send(Socket, [
430		<< 1:1, 0:3, 0:4, 1:1, 5:7 >>,
431		<< 16#37 >>, << 16#fa >>, << 16#21 >>, << 16#3d >>, << 16#7f >>,
432		<< 16#9f >>, << 16#4d >>, << 16#51 >>, << 16#58 >>]),
433	{ok, << 1:1, 0:3, 1:4, 0:1, 10:7, "HelloHello" >>}
434		= gen_tcp:recv(Socket, 0, 6000),
435
436	ok = gen_tcp:send(Socket, [
437		%% #1
438		<< 0:1, 0:3, 1:4, 1:1, 5:7 >>,
439		<< 16#37 >>, << 16#fa >>, << 16#21 >>, << 16#3d >>, << 16#7f >>,
440		<< 16#9f >>, << 16#4d >>, << 16#51 >>, << 16#58 >>,
441		%% #2
442		<< 0:1, 0:3, 0:4, 1:1, 5:7 >>,
443		<< 16#37 >>, << 16#fa >>, << 16#21 >>, << 16#3d >>, << 16#7f >>,
444		<< 16#9f >>, << 16#4d >>, << 16#51 >>, << 16#58 >>,
445		%% #3
446		<< 1:1, 0:3, 0:4, 1:1, 5:7 >>,
447		<< 16#37 >>, << 16#fa >>, << 16#21 >>, << 16#3d >>, << 16#7f >>,
448		<< 16#9f >>, << 16#4d >>, << 16#51 >>, << 16#58 >>]),
449	{ok, << 1:1, 0:3, 1:4, 0:1, 15:7, "HelloHelloHello" >>}
450		= gen_tcp:recv(Socket, 0, 6000),
451	ok = gen_tcp:send(Socket, << 1:1, 0:3, 8:4, 1:1, 0:7, 0:32 >>), %% close
452	{ok, << 1:1, 0:3, 8:4, 0:8 >>} = gen_tcp:recv(Socket, 0, 6000),
453	{error, closed} = gen_tcp:recv(Socket, 0, 6000),
454	ok.
455
456ws_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:1, 2:7, 1000:16 >>} = gen_tcp:recv(Socket, 0, 6000),
479	{error, closed} = gen_tcp:recv(Socket, 0, 6000),
480	ok.
481
482ws_timeout_cancel(Config) ->
483	%% Erlang messages to a socket should not cancel the timeout	
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_timeout_cancel 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, <<>>] = websocket_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	{ok, << 1:1, 0:3, 8:4, 0:1, 2:7, 1000:16 >>} = gen_tcp:recv(Socket, 0, 6000),
506	{error, closed} = gen_tcp:recv(Socket, 0, 6000),
507	ok.
508
509ws_timeout_reset(Config) ->
510	%% Erlang messages across a socket should reset the timeout	
511	{port, Port} = lists:keyfind(port, 1, Config),
512	{ok, Socket} = gen_tcp:connect("localhost", Port,
513		[binary, {active, false}, {packet, raw}]),
514	ok = gen_tcp:send(Socket, [
515		"GET /ws_timeout_cancel HTTP/1.1\r\n"
516		"Host: localhost\r\n"
517		"Connection: Upgrade\r\n"
518		"Upgrade: websocket\r\n"
519		"Sec-WebSocket-Origin: http://localhost\r\n"
520		"Sec-Websocket-Version: 13\r\n"
521		"Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
522		"\r\n"]),
523	{ok, Handshake} = gen_tcp:recv(Socket, 0, 6000),
524	{ok, {http_response, {1, 1}, 101, "Switching Protocols"}, Rest}
525		= erlang:decode_packet(http, Handshake, []),
526	[Headers, <<>>] = websocket_headers(
527		erlang:decode_packet(httph, Rest, []), []),
528	{'Connection', "Upgrade"} = lists:keyfind('Connection', 1, Headers),
529	{'Upgrade', "websocket"} = lists:keyfind('Upgrade', 1, Headers),
530	{"sec-websocket-accept", "s3pPLMBiTxaQ9kYGzzhZRbK+xOo="}
531		= lists:keyfind("sec-websocket-accept", 1, Headers),
532	[begin
533		ok = gen_tcp:send(Socket, << 16#81, 16#85, 16#37, 16#fa, 16#21, 16#3d,
534			16#7f, 16#9f, 16#4d, 16#51, 16#58 >>),
535		{ok, << 1:1, 0:3, 1:4, 0:1, 5:7, "Hello" >>}
536			= gen_tcp:recv(Socket, 0, 6000),
537		ok = timer:sleep(500)
538	end || _ <- [1, 2, 3, 4]],
539	{ok, << 1:1, 0:3, 8:4, 0:1, 2:7, 1000:16 >>} = gen_tcp:recv(Socket, 0, 6000),
540	{error, closed} = gen_tcp:recv(Socket, 0, 6000),
541	ok.
542
543ws_upgrade_with_opts(Config) ->
544	{port, Port} = lists:keyfind(port, 1, Config),
545	{ok, Socket} = gen_tcp:connect("localhost", Port,
546		[binary, {active, false}, {packet, raw}]),
547	ok = gen_tcp:send(Socket, [
548		"GET /ws_upgrade_with_opts HTTP/1.1\r\n"
549		"Host: localhost\r\n"
550		"Connection: Upgrade\r\n"
551		"Upgrade: websocket\r\n"
552		"Sec-WebSocket-Origin: http://localhost\r\n"
553		"Sec-WebSocket-Version: 8\r\n"
554		"Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
555		"\r\n"]),
556	{ok, Handshake} = gen_tcp:recv(Socket, 0, 6000),
557	{ok, {http_response, {1, 1}, 101, "Switching Protocols"}, Rest}
558		= erlang:decode_packet(http, Handshake, []),
559	[Headers, <<>>] = websocket_headers(
560		erlang:decode_packet(httph, Rest, []), []),
561	{'Connection', "Upgrade"} = lists:keyfind('Connection', 1, Headers),
562	{'Upgrade', "websocket"} = lists:keyfind('Upgrade', 1, Headers),
563	{"sec-websocket-accept", "s3pPLMBiTxaQ9kYGzzhZRbK+xOo="}
564		= lists:keyfind("sec-websocket-accept", 1, Headers),
565	{ok, Response} = gen_tcp:recv(Socket, 9, 6000),
566	<< 1:1, 0:3, 1:4, 0:1, 7:7, "success" >> = Response,
567	ok = gen_tcp:send(Socket, << 1:1, 0:3, 8:4, 1:1, 0:7, 0:32 >>), %% close
568	{ok, << 1:1, 0:3, 8:4, 0:8 >>} = gen_tcp:recv(Socket, 0, 6000),
569	{error, closed} = gen_tcp:recv(Socket, 0, 6000),
570	ok.
571
572%% Internal.
573
574websocket_headers({ok, http_eoh, Rest}, Acc) ->
575	[Acc, Rest];
576websocket_headers({ok, {http_header, _I, Key, _R, Value}, Rest}, Acc) ->
577	F = fun(S) when is_atom(S) -> S; (S) -> string:to_lower(S) end,
578	websocket_headers(erlang:decode_packet(httph, Rest, []),
579		[{F(Key), Value}|Acc]).