aboutsummaryrefslogtreecommitdiffstats
path: root/test
diff options
context:
space:
mode:
Diffstat (limited to 'test')
-rw-r--r--test/examples_SUITE.erl22
-rw-r--r--test/handlers/echo_h.erl46
-rw-r--r--test/handlers/long_polling_h.erl1
-rw-r--r--test/handlers/loop_handler_body_h.erl7
-rw-r--r--test/handlers/multipart_h.erl65
-rw-r--r--test/handlers/resp_h.erl151
-rw-r--r--test/http_SUITE.erl2
-rw-r--r--test/http_SUITE_data/http_body_qs.erl2
-rw-r--r--test/http_SUITE_data/http_loop_stream_recv.erl2
-rw-r--r--test/http_SUITE_data/http_multipart.erl4
-rw-r--r--test/http_SUITE_data/http_multipart_stream.erl6
-rw-r--r--test/req_SUITE.erl444
-rw-r--r--test/ws_SUITE.erl66
13 files changed, 751 insertions, 67 deletions
diff --git a/test/examples_SUITE.erl b/test/examples_SUITE.erl
index b9cc7ba..56f612f 100644
--- a/test/examples_SUITE.erl
+++ b/test/examples_SUITE.erl
@@ -167,6 +167,7 @@ echo_get(Config) ->
do_echo_get(Transport, Protocol, Config) ->
{200, _, <<"this is fun">>} = do_get(Transport, Protocol, "/?echo=this+is+fun", Config),
+ {400, _, _} = do_get(Transport, Protocol, "/", Config),
ok.
%% Echo POST.
@@ -393,12 +394,27 @@ websocket(_) ->
Msg1 ->
exit({connection_failed, Msg1})
end,
+ %% Check that we receive the message sent on timer on init.
+ receive
+ {gun_ws, Pid, {text, <<"Hello!">>}} ->
+ ok
+ after 2000 ->
+ exit(timeout)
+ end,
+ %% Check that we receive subsequent messages sent on timer.
+ receive
+ {gun_ws, Pid, {text, <<"How' you doin'?">>}} ->
+ ok
+ after 2000 ->
+ exit(timeout)
+ end,
+ %% Check that we receive the echoed message.
gun:ws_send(Pid, {text, <<"hello">>}),
receive
{gun_ws, Pid, {text, <<"That's what she said! hello">>}} ->
- ok;
- Msg2 ->
- exit({receive_failed, Msg2})
+ ok
+ after 500 ->
+ exit(timeout)
end,
gun:ws_send(Pid, close)
after
diff --git a/test/handlers/echo_h.erl b/test/handlers/echo_h.erl
index fd45c5f..fb7d8a8 100644
--- a/test/handlers/echo_h.erl
+++ b/test/handlers/echo_h.erl
@@ -12,10 +12,30 @@ init(Req, Opts) ->
echo_arg(Arg, Req, Opts)
end.
-echo(<<"body">>, Req0, Opts) ->
- {ok, Body, Req} = cowboy_req:read_body(Req0),
- cowboy_req:reply(200, #{}, Body, Req),
- {ok, Req, Opts};
+echo(<<"read_body">>, Req0, Opts) ->
+ case Opts of
+ #{crash := true} -> ct_helper:ignore(cowboy_req, read_body, 2);
+ _ -> ok
+ end,
+ {_, Body, Req} = case cowboy_req:path(Req0) of
+ <<"/full", _/bits>> -> read_body(Req0, <<>>);
+ <<"/opts", _/bits>> -> cowboy_req:read_body(Req0, Opts);
+ _ -> cowboy_req:read_body(Req0)
+ end,
+ {ok, cowboy_req:reply(200, #{}, Body, Req), Opts};
+echo(<<"read_urlencoded_body">>, Req0, Opts) ->
+ Path = cowboy_req:path(Req0),
+ case {Path, Opts} of
+ {<<"/opts", _/bits>>, #{crash := true}} -> ct_helper:ignore(cowboy_req, read_body, 2);
+ {_, #{crash := true}} -> ct_helper:ignore(cowboy_req, read_urlencoded_body, 2);
+ _ -> ok
+ end,
+ {ok, Body, Req} = case Path of
+ <<"/opts", _/bits>> -> cowboy_req:read_urlencoded_body(Req0, Opts);
+ <<"/crash", _/bits>> -> cowboy_req:read_urlencoded_body(Req0, Opts);
+ _ -> cowboy_req:read_urlencoded_body(Req0)
+ end,
+ {ok, cowboy_req:reply(200, #{}, value_to_iodata(Body), Req), Opts};
echo(<<"uri">>, Req, Opts) ->
Value = case cowboy_req:path_info(Req) of
[<<"origin">>] -> cowboy_req:uri(Req, #{host => undefined});
@@ -25,8 +45,7 @@ echo(<<"uri">>, Req, Opts) ->
[<<"set-port">>] -> cowboy_req:uri(Req, #{port => 123});
[] -> cowboy_req:uri(Req)
end,
- cowboy_req:reply(200, #{}, Value, Req),
- {ok, Req, Opts};
+ {ok, cowboy_req:reply(200, #{}, Value, Req), Opts};
echo(<<"match">>, Req, Opts) ->
[Type|Fields0] = cowboy_req:path_info(Req),
Fields = [binary_to_atom(F, latin1) || F <- Fields0],
@@ -34,13 +53,11 @@ echo(<<"match">>, Req, Opts) ->
<<"qs">> -> cowboy_req:match_qs(Fields, Req);
<<"cookies">> -> cowboy_req:match_cookies(Fields, Req)
end,
- cowboy_req:reply(200, #{}, value_to_iodata(Value), Req),
- {ok, Req, Opts};
+ {ok, cowboy_req:reply(200, #{}, value_to_iodata(Value), Req), Opts};
echo(What, Req, Opts) ->
F = binary_to_atom(What, latin1),
Value = cowboy_req:F(Req),
- cowboy_req:reply(200, #{}, value_to_iodata(Value), Req),
- {ok, Req, Opts}.
+ {ok, cowboy_req:reply(200, #{}, value_to_iodata(Value), Req), Opts}.
echo_arg(Arg0, Req, Opts) ->
F = binary_to_atom(cowboy_req:binding(key, Req), latin1),
@@ -52,8 +69,13 @@ echo_arg(Arg0, Req, Opts) ->
undefined -> cowboy_req:F(Arg, Req);
Default -> cowboy_req:F(Arg, Req, Default)
end,
- cowboy_req:reply(200, #{}, value_to_iodata(Value), Req),
- {ok, Req, Opts}.
+ {ok, cowboy_req:reply(200, #{}, value_to_iodata(Value), Req), Opts}.
+
+read_body(Req0, Acc) ->
+ case cowboy_req:read_body(Req0) of
+ {ok, Data, Req} -> {ok, << Acc/binary, Data/binary >>, Req};
+ {more, Data, Req} -> read_body(Req, << Acc/binary, Data/binary >>)
+ end.
value_to_iodata(V) when is_integer(V) -> integer_to_binary(V);
value_to_iodata(V) when is_atom(V) -> atom_to_binary(V, latin1);
diff --git a/test/handlers/long_polling_h.erl b/test/handlers/long_polling_h.erl
index 4f8e23f..17afa0a 100644
--- a/test/handlers/long_polling_h.erl
+++ b/test/handlers/long_polling_h.erl
@@ -14,6 +14,7 @@ init(Req, _) ->
{cowboy_loop, Req, 2, 5000, hibernate}.
info(timeout, Req, 0) ->
+ %% @todo Why 102?
{stop, cowboy_req:reply(102, Req), 0};
info(timeout, Req, Count) ->
erlang:send_after(200, self(), timeout),
diff --git a/test/handlers/loop_handler_body_h.erl b/test/handlers/loop_handler_body_h.erl
index 38ba2c0..e0ea41b 100644
--- a/test/handlers/loop_handler_body_h.erl
+++ b/test/handlers/loop_handler_body_h.erl
@@ -13,11 +13,10 @@ init(Req, _) ->
self() ! timeout,
{cowboy_loop, Req, undefined, 5000, hibernate}.
-info(timeout, Req, State) ->
- {ok, Body, Req2} = cowboy_req:read_body(Req),
+info(timeout, Req0, State) ->
+ {ok, Body, Req} = cowboy_req:read_body(Req0),
100000 = byte_size(Body),
- cowboy_req:reply(200, Req2),
- {stop, Req, State}.
+ {stop, cowboy_req:reply(200, Req), State}.
terminate(stop, _, _) ->
ok.
diff --git a/test/handlers/multipart_h.erl b/test/handlers/multipart_h.erl
new file mode 100644
index 0000000..1b9297b
--- /dev/null
+++ b/test/handlers/multipart_h.erl
@@ -0,0 +1,65 @@
+%% This module reads a multipart body and echoes it back as an Erlang term.
+
+-module(multipart_h).
+
+-export([init/2]).
+
+init(Req0, State) ->
+ {Result, Req} = case cowboy_req:binding(key, Req0) of
+ undefined -> acc_multipart(Req0, []);
+ <<"skip_body">> -> skip_body_multipart(Req0, []);
+ <<"read_part2">> -> read_part2_multipart(Req0, []);
+ <<"read_part_body2">> -> read_part_body2_multipart(Req0, [])
+ end,
+ {ok, cowboy_req:reply(200, #{}, term_to_binary(Result), Req), State}.
+
+acc_multipart(Req0, Acc) ->
+ case cowboy_req:read_part(Req0) of
+ {ok, Headers, Req1} ->
+ {ok, Body, Req} = stream_body(Req1, <<>>),
+ acc_multipart(Req, [{Headers, Body}|Acc]);
+ {done, Req} ->
+ {lists:reverse(Acc), Req}
+ end.
+
+stream_body(Req0, Acc) ->
+ case cowboy_req:read_part_body(Req0) of
+ {more, Data, Req} ->
+ stream_body(Req, << Acc/binary, Data/binary >>);
+ {ok, Data, Req} ->
+ {ok, << Acc/binary, Data/binary >>, Req}
+ end.
+
+skip_body_multipart(Req0, Acc) ->
+ case cowboy_req:read_part(Req0) of
+ {ok, Headers, Req} ->
+ skip_body_multipart(Req, [Headers|Acc]);
+ {done, Req} ->
+ {lists:reverse(Acc), Req}
+ end.
+
+read_part2_multipart(Req0, Acc) ->
+ case cowboy_req:read_part(Req0, #{length => 1, period => 1}) of
+ {ok, Headers, Req1} ->
+ {ok, Body, Req} = stream_body(Req1, <<>>),
+ acc_multipart(Req, [{Headers, Body}|Acc]);
+ {done, Req} ->
+ {lists:reverse(Acc), Req}
+ end.
+
+read_part_body2_multipart(Req0, Acc) ->
+ case cowboy_req:read_part(Req0) of
+ {ok, Headers, Req1} ->
+ {ok, Body, Req} = stream_body2(Req1, <<>>),
+ acc_multipart(Req, [{Headers, Body}|Acc]);
+ {done, Req} ->
+ {lists:reverse(Acc), Req}
+ end.
+
+stream_body2(Req0, Acc) ->
+ case cowboy_req:read_part_body(Req0, #{length => 1, period => 1}) of
+ {more, Data, Req} ->
+ stream_body(Req, << Acc/binary, Data/binary >>);
+ {ok, Data, Req} ->
+ {ok, << Acc/binary, Data/binary >>, Req}
+ end.
diff --git a/test/handlers/resp_h.erl b/test/handlers/resp_h.erl
new file mode 100644
index 0000000..bb64906
--- /dev/null
+++ b/test/handlers/resp_h.erl
@@ -0,0 +1,151 @@
+%% This module echoes back the value the test is interested in.
+
+-module(resp_h).
+
+-export([init/2]).
+
+init(Req, Opts) ->
+ do(cowboy_req:binding(key, Req), Req, Opts).
+
+do(<<"set_resp_cookie3">>, Req0, Opts) ->
+ Req = case cowboy_req:binding(arg, Req0) of
+ undefined ->
+ cowboy_req:set_resp_cookie(<<"mycookie">>, "myvalue", Req0);
+ <<"multiple">> ->
+ Req1 = cowboy_req:set_resp_cookie(<<"mycookie">>, "myvalue", Req0),
+ cowboy_req:set_resp_cookie(<<"yourcookie">>, <<"yourvalue">>, Req1);
+ <<"overwrite">> ->
+ Req1 = cowboy_req:set_resp_cookie(<<"mycookie">>, "myvalue", Req0),
+ cowboy_req:set_resp_cookie(<<"mycookie">>, <<"overwrite">>, Req1)
+ end,
+ {ok, cowboy_req:reply(200, #{}, "OK", Req), Opts};
+do(<<"set_resp_cookie4">>, Req0, Opts) ->
+ Req = cowboy_req:set_resp_cookie(<<"mycookie">>, "myvalue", #{path => cowboy_req:path(Req0)}, Req0),
+ {ok, cowboy_req:reply(200, #{}, "OK", Req), Opts};
+do(<<"set_resp_header">>, Req0, Opts) ->
+ Req = cowboy_req:set_resp_header(<<"content-type">>, <<"text/plain">>, Req0),
+ {ok, cowboy_req:reply(200, #{}, "OK", Req), Opts};
+do(<<"set_resp_body">>, Req0, Opts) ->
+ Arg = cowboy_req:binding(arg, Req0),
+ Req1 = case Arg of
+ <<"sendfile">> ->
+ AppFile = code:where_is_file("cowboy.app"),
+ cowboy_req:set_resp_body({sendfile, 0, filelib:file_size(AppFile), AppFile}, Req0);
+ _ ->
+ cowboy_req:set_resp_body(<<"OK">>, Req0)
+ end,
+ Req = case Arg of
+ <<"override">> ->
+ cowboy_req:reply(200, #{}, <<"OVERRIDE">>, Req1);
+ _ ->
+ cowboy_req:reply(200, Req1)
+ end,
+ {ok, Req, Opts};
+do(<<"has_resp_header">>, Req0, Opts) ->
+ false = cowboy_req:has_resp_header(<<"content-type">>, Req0),
+ Req = cowboy_req:set_resp_header(<<"content-type">>, <<"text/plain">>, Req0),
+ true = cowboy_req:has_resp_header(<<"content-type">>, Req),
+ {ok, cowboy_req:reply(200, #{}, "OK", Req), Opts};
+do(<<"has_resp_body">>, Req0, Opts) ->
+ case cowboy_req:binding(arg, Req0) of
+ <<"sendfile">> ->
+ %% @todo Cases for sendfile. Note that sendfile 0 is unallowed.
+ false = cowboy_req:has_resp_body(Req0),
+ Req = cowboy_req:set_resp_body({sendfile, 0, 10, code:where_is_file("cowboy.app")}, Req0),
+ true = cowboy_req:has_resp_body(Req),
+ {ok, cowboy_req:reply(200, #{}, <<"OK">>, Req), Opts};
+ undefined ->
+ false = cowboy_req:has_resp_body(Req0),
+ Req = cowboy_req:set_resp_body(<<"OK">>, Req0),
+ true = cowboy_req:has_resp_body(Req),
+ {ok, cowboy_req:reply(200, #{}, Req), Opts}
+ end;
+do(<<"delete_resp_header">>, Req0, Opts) ->
+ false = cowboy_req:has_resp_header(<<"content-type">>, Req0),
+ Req1 = cowboy_req:set_resp_header(<<"content-type">>, <<"text/plain">>, Req0),
+ true = cowboy_req:has_resp_header(<<"content-type">>, Req1),
+ Req = cowboy_req:delete_resp_header(<<"content-type">>, Req1),
+ false = cowboy_req:has_resp_header(<<"content-type">>, Req),
+ {ok, cowboy_req:reply(200, #{}, "OK", Req), Opts};
+do(<<"reply2">>, Req0, Opts) ->
+ Req = case cowboy_req:binding(arg, Req0) of
+ <<"binary">> ->
+ cowboy_req:reply(<<"200 GOOD">>, Req0);
+ <<"error">> ->
+ ct_helper:ignore(cowboy_req, reply, 4),
+ cowboy_req:reply(ok, Req0);
+ <<"twice">> ->
+ ct_helper:ignore(cowboy_req, reply, 4),
+ Req1 = cowboy_req:reply(200, Req0),
+ cowboy_req:reply(200, Req1);
+ Status ->
+ cowboy_req:reply(binary_to_integer(Status), Req0)
+ end,
+ {ok, Req, Opts};
+do(<<"reply3">>, Req0, Opts) ->
+ Req = case cowboy_req:binding(arg, Req0) of
+ <<"error">> ->
+ ct_helper:ignore(cowboy_req, reply, 4),
+ cowboy_req:reply(200, ok, Req0);
+ Status ->
+ cowboy_req:reply(binary_to_integer(Status),
+ #{<<"content-type">> => <<"text/plain">>}, Req0)
+ end,
+ {ok, Req, Opts};
+do(<<"reply4">>, Req0, Opts) ->
+ Req = case cowboy_req:binding(arg, Req0) of
+ <<"error">> ->
+ ct_helper:ignore(erlang, iolist_size, 1),
+ cowboy_req:reply(200, #{}, ok, Req0);
+ Status ->
+ cowboy_req:reply(binary_to_integer(Status), #{}, <<"OK">>, Req0)
+ end,
+ {ok, Req, Opts};
+do(<<"stream_reply2">>, Req0, Opts) ->
+ Req = case cowboy_req:binding(arg, Req0) of
+ <<"binary">> ->
+ cowboy_req:stream_reply(<<"200 GOOD">>, Req0);
+ <<"error">> ->
+ ct_helper:ignore(cowboy_req, stream_reply, 3),
+ cowboy_req:stream_reply(ok, Req0);
+ Status ->
+ cowboy_req:stream_reply(binary_to_integer(Status), Req0)
+ end,
+ stream_body(Req),
+ {ok, Req, Opts};
+do(<<"stream_reply3">>, Req0, Opts) ->
+ Req = case cowboy_req:binding(arg, Req0) of
+ <<"error">> ->
+ ct_helper:ignore(cowboy_req, stream_reply, 3),
+ cowboy_req:stream_reply(200, ok, Req0);
+ Status ->
+ cowboy_req:stream_reply(binary_to_integer(Status),
+ #{<<"content-type">> => <<"text/plain">>}, Req0)
+ end,
+ stream_body(Req),
+ {ok, Req, Opts};
+do(<<"stream_body">>, Req, Opts) ->
+ %% Call stream_body without initiating streaming.
+ cowboy_req:stream_body(<<0:800000>>, fin, Req),
+ {ok, Req, Opts};
+do(<<"push">>, Req, Opts) ->
+ case cowboy_req:binding(arg, Req) of
+ <<"method">> ->
+ cowboy_req:push("/static/style.css", #{<<"accept">> => <<"text/css">>}, Req,
+ #{method => <<"HEAD">>});
+ <<"origin">> ->
+ cowboy_req:push("/static/style.css", #{<<"accept">> => <<"text/css">>}, Req,
+ #{scheme => <<"ftp">>, host => <<"127.0.0.1">>, port => 21});
+ <<"qs">> ->
+ cowboy_req:push("/static/style.css", #{<<"accept">> => <<"text/css">>}, Req,
+ #{qs => <<"server=cowboy&version=2.0">>});
+ _ ->
+ cowboy_req:push("/static/style.css", #{<<"accept">> => <<"text/css">>}, Req),
+ %% The text/plain mime is not defined by default, so a 406 will be returned.
+ cowboy_req:push("/static/plain.txt", #{<<"accept">> => <<"text/plain">>}, Req)
+ end,
+ {ok, cowboy_req:reply(200, Req), Opts}.
+
+stream_body(Req) ->
+ _ = [cowboy_req:stream_body(<<0:800000>>, nofin, Req) || _ <- lists:seq(1,9)],
+ cowboy_req:stream_body(<<0:800000>>, fin, Req).
diff --git a/test/http_SUITE.erl b/test/http_SUITE.erl
index 1b39feb..f0670a9 100644
--- a/test/http_SUITE.erl
+++ b/test/http_SUITE.erl
@@ -945,7 +945,7 @@ do_body_to_chunks(ChunkSize, Body, Acc) ->
false -> ChunkSize
end,
<< Chunk:ChunkSize2/binary, Rest/binary >> = Body,
- ChunkSizeBin = list_to_binary(integer_to_list(ChunkSize2, 16)),
+ ChunkSizeBin = integer_to_binary(ChunkSize2, 16),
do_body_to_chunks(ChunkSize, Rest,
[<< ChunkSizeBin/binary, "\r\n", Chunk/binary, "\r\n" >>|Acc]).
diff --git a/test/http_SUITE_data/http_body_qs.erl b/test/http_SUITE_data/http_body_qs.erl
index e0673cf..09ca5e4 100644
--- a/test/http_SUITE_data/http_body_qs.erl
+++ b/test/http_SUITE_data/http_body_qs.erl
@@ -10,7 +10,7 @@ init(Req, Opts) ->
{ok, maybe_echo(Method, HasBody, Req), Opts}.
maybe_echo(<<"POST">>, true, Req) ->
- case cowboy_req:body_qs(Req) of
+ case cowboy_req:read_urlencoded_body(Req) of
{badlength, Req2} ->
echo(badlength, Req2);
{ok, PostVals, Req2} ->
diff --git a/test/http_SUITE_data/http_loop_stream_recv.erl b/test/http_SUITE_data/http_loop_stream_recv.erl
index c006b6d..18b3d29 100644
--- a/test/http_SUITE_data/http_loop_stream_recv.erl
+++ b/test/http_SUITE_data/http_loop_stream_recv.erl
@@ -15,7 +15,7 @@ info(stream, Req, undefined) ->
stream(Req, 1, <<>>).
stream(Req, ID, Acc) ->
- case cowboy_req:body(Req) of
+ case cowboy_req:read_body(Req) of
{ok, <<>>, Req2} ->
{stop, cowboy_req:reply(200, Req2), undefined};
{_, Data, Req2} ->
diff --git a/test/http_SUITE_data/http_multipart.erl b/test/http_SUITE_data/http_multipart.erl
index 212f569..5a4c6be 100644
--- a/test/http_SUITE_data/http_multipart.erl
+++ b/test/http_SUITE_data/http_multipart.erl
@@ -9,9 +9,9 @@ init(Req, Opts) ->
{ok, cowboy_req:reply(200, #{}, term_to_binary(Result), Req2), Opts}.
acc_multipart(Req, Acc) ->
- case cowboy_req:part(Req) of
+ case cowboy_req:read_part(Req) of
{ok, Headers, Req2} ->
- {ok, Body, Req3} = cowboy_req:part_body(Req2),
+ {ok, Body, Req3} = cowboy_req:read_part_body(Req2),
acc_multipart(Req3, [{Headers, Body}|Acc]);
{done, Req2} ->
{lists:reverse(Acc), Req2}
diff --git a/test/http_SUITE_data/http_multipart_stream.erl b/test/http_SUITE_data/http_multipart_stream.erl
index 82662ad..88cf611 100644
--- a/test/http_SUITE_data/http_multipart_stream.erl
+++ b/test/http_SUITE_data/http_multipart_stream.erl
@@ -9,9 +9,9 @@ init(Req, Opts) ->
{ok, cowboy_req:reply(200, Req2), Opts}.
multipart(Req) ->
- case cowboy_req:part(Req) of
+ case cowboy_req:read_part(Req) of
{ok, [{<<"content-length">>, BinLength}], Req2} ->
- Length = list_to_integer(binary_to_list(BinLength)),
+ Length = binary_to_integer(BinLength),
{Length, Req3} = stream_body(Req2, 0),
multipart(Req3);
{done, Req2} ->
@@ -19,7 +19,7 @@ multipart(Req) ->
end.
stream_body(Req, N) ->
- case cowboy_req:part_body(Req) of
+ case cowboy_req:read_part_body(Req) of
{ok, Data, Req2} ->
{N + byte_size(Data), Req2};
{more, Data, Req2} ->
diff --git a/test/req_SUITE.erl b/test/req_SUITE.erl
index 648ebcd..b0aabad 100644
--- a/test/req_SUITE.erl
+++ b/test/req_SUITE.erl
@@ -34,6 +34,13 @@ groups() ->
%% @todo With compression enabled.
].
+init_per_suite(Config) ->
+ ct_helper:create_static_dir(config(priv_dir, Config) ++ "/static"),
+ Config.
+
+end_per_suite(Config) ->
+ ct_helper:delete_static_dir(config(priv_dir, Config) ++ "/static").
+
init_per_group(Name, Config) ->
cowboy_test:init_common_groups(Name, Config, ?MODULE).
@@ -42,10 +49,20 @@ end_per_group(Name, _) ->
%% Routes.
-init_dispatch(_) ->
+init_dispatch(Config) ->
cowboy_router:compile([{"[...]", [
- {"/no/:key", echo_h, []},
+ {"/static/[...]", cowboy_static, {dir, config(priv_dir, Config) ++ "/static"}},
+ %% @todo Seriously InitialState should be optional.
+ {"/resp/:key[/:arg]", resp_h, []},
+ {"/multipart[/:key]", multipart_h, []},
{"/args/:key/:arg[/:default]", echo_h, []},
+ {"/crash/:key/period", echo_h, #{length => infinity, period => 1000, crash => true}},
+ {"/no-opts/:key", echo_h, #{crash => true}},
+ {"/opts/:key/length", echo_h, #{length => 1000}},
+ {"/opts/:key/period", echo_h, #{length => infinity, period => 1000}},
+ {"/opts/:key/timeout", echo_h, #{timeout => 1000, crash => true}},
+ {"/full/:key", echo_h, []},
+ {"/no/:key", echo_h, []},
{"/:key/[...]", echo_h, []}
]}]).
@@ -55,15 +72,32 @@ do_body(Method, Path, Config) ->
do_body(Method, Path, [], Config).
do_body(Method, Path, Headers, Config) ->
+ do_body(Method, Path, Headers, <<>>, Config).
+
+do_body(Method, Path, Headers, Body, Config) ->
ConnPid = gun_open(Config),
- Ref = gun:request(ConnPid, Method, Path, Headers),
+ Ref = case Body of
+ <<>> -> gun:request(ConnPid, Method, Path, Headers);
+ _ -> gun:request(ConnPid, Method, Path, Headers, Body)
+ end,
{response, IsFin, 200, _} = gun:await(ConnPid, Ref),
- {ok, Body} = case IsFin of
+ {ok, RespBody} = case IsFin of
nofin -> gun:await_body(ConnPid, Ref);
fin -> {ok, <<>>}
end,
gun:close(ConnPid),
- Body.
+ RespBody.
+
+do_get(Path, Config) ->
+ ConnPid = gun_open(Config),
+ Ref = gun:get(ConnPid, Path, []),
+ {response, IsFin, Status, Headers} = gun:await(ConnPid, Ref),
+ {ok, RespBody} = case IsFin of
+ nofin -> gun:await_body(ConnPid, Ref);
+ fin -> {ok, <<>>}
+ end,
+ gun:close(ConnPid),
+ {Status, Headers, RespBody}.
do_get_body(Path, Config) ->
do_get_body(Path, [], Config).
@@ -71,7 +105,7 @@ do_get_body(Path, Config) ->
do_get_body(Path, Headers, Config) ->
do_body("GET", Path, Headers, Config).
-%% Tests.
+%% Tests: Request.
binding(Config) ->
doc("Value bound from request URI path with/without default."),
@@ -109,6 +143,7 @@ host_info(Config) ->
<<"[<<\"localhost\">>]">> = do_get_body("/host_info", Config),
ok.
+%% @todo Actually write the related unit tests.
match_cookies(Config) ->
doc("Matched request cookies."),
<<"#{}">> = do_get_body("/match/cookies", [{<<"cookie">>, "a=b; c=d"}], Config),
@@ -119,6 +154,7 @@ match_cookies(Config) ->
%% This function is tested more extensively through unit tests.
ok.
+%% @todo Actually write the related unit tests.
match_qs(Config) ->
doc("Matched request URI query string."),
<<"#{}">> = do_get_body("/match/qs?a=b&c=d", Config),
@@ -131,7 +167,7 @@ match_qs(Config) ->
method(Config) ->
doc("Request method."),
<<"GET">> = do_body("GET", "/method", Config),
- <<"HEAD">> = do_body("HEAD", "/method", Config),
+ <<>> = do_body("HEAD", "/method", Config),
<<"OPTIONS">> = do_body("OPTIONS", "/method", Config),
<<"PATCH">> = do_body("PATCH", "/method", Config),
<<"POST">> = do_body("POST", "/method", Config),
@@ -147,6 +183,9 @@ parse_cookies(Config) ->
= do_get_body("/parse_cookies", [{<<"cookie">>, "cake=strawberry"}], Config),
<<"[{<<\"cake\">>,<<\"strawberry\">>},{<<\"color\">>,<<\"blue\">>}]">>
= do_get_body("/parse_cookies", [{<<"cookie">>, "cake=strawberry; color=blue"}], Config),
+ <<"[{<<\"cake\">>,<<\"strawberry\">>},{<<\"color\">>,<<\"blue\">>}]">>
+ = do_get_body("/parse_cookies",
+ [{<<"cookie">>, "cake=strawberry"}, {<<"cookie">>, "color=blue"}], Config),
ok.
parse_header(Config) ->
@@ -252,3 +291,394 @@ version(Config) ->
<<"HTTP/1.1">> when Protocol =:= http -> ok;
<<"HTTP/2">> when Protocol =:= http2 -> ok
end.
+
+%% Tests: Request body.
+
+body_length(Config) ->
+ doc("Request body length."),
+ <<"0">> = do_get_body("/body_length", Config),
+ <<"12">> = do_body("POST", "/body_length", [], "hello world!", Config),
+ ok.
+
+has_body(Config) ->
+ doc("Has a request body?"),
+ <<"false">> = do_get_body("/has_body", Config),
+ <<"true">> = do_body("POST", "/has_body", [], "hello world!", Config),
+ ok.
+
+read_body(Config) ->
+ doc("Request body."),
+ <<>> = do_get_body("/read_body", Config),
+ <<"hello world!">> = do_body("POST", "/read_body", [], "hello world!", Config),
+ %% We expect to have read *at least* 1000 bytes.
+ <<0:8000, _/bits>> = do_body("POST", "/opts/read_body/length", [], <<0:8000000>>, Config),
+ %% We read any length for at most 1 second.
+ %%
+ %% The body is sent twice, first with nofin, then wait 2 seconds, then again with fin.
+ <<0:8000000>> = do_read_body_period("/opts/read_body/period", <<0:8000000>>, Config),
+ %% The timeout value is set too low on purpose to ensure a crash occurs.
+ ok = do_read_body_timeout("/opts/read_body/timeout", <<0:8000000>>, Config),
+ %% 10MB body larger than default length.
+ <<0:80000000>> = do_body("POST", "/full/read_body", [], <<0:80000000>>, Config),
+ ok.
+
+do_read_body_period(Path, Body, Config) ->
+ ConnPid = gun_open(Config),
+ Ref = gun:request(ConnPid, "POST", Path, [
+ {<<"content-length">>, integer_to_binary(byte_size(Body) * 2)}
+ ]),
+ gun:data(ConnPid, Ref, nofin, Body),
+ timer:sleep(2000),
+ gun:data(ConnPid, Ref, fin, Body),
+ {response, nofin, 200, _} = gun:await(ConnPid, Ref),
+ {ok, RespBody} = gun:await_body(ConnPid, Ref),
+ gun:close(ConnPid),
+ RespBody.
+
+%% We expect a crash.
+do_read_body_timeout(Path, Body, Config) ->
+ ConnPid = gun_open(Config),
+ Ref = gun:request(ConnPid, "POST", Path, [
+ {<<"content-length">>, integer_to_binary(byte_size(Body))}
+ ]),
+ {response, _, 500, _} = gun:await(ConnPid, Ref),
+ gun:close(ConnPid).
+
+%% @todo Do we really want a key/value list here instead of a map?
+read_urlencoded_body(Config) ->
+ doc("application/x-www-form-urlencoded request body."),
+ <<"[]">> = do_body("POST", "/read_urlencoded_body", [], <<>>, Config),
+ <<"[{<<\"abc\">>,true}]">> = do_body("POST", "/read_urlencoded_body", [], "abc", Config),
+ <<"[{<<\"a\">>,<<\"b\">>},{<<\"c\">>,<<\"d e\">>}]">>
+ = do_body("POST", "/read_urlencoded_body", [], "a=b&c=d+e", Config),
+ %% Send a 10MB body, larger than the default length, to ensure a crash occurs.
+ ok = do_read_urlencoded_body_too_large("/no-opts/read_urlencoded_body",
+ string:chars($a, 10000000), Config),
+ %% We read any length for at most 1 second.
+ %%
+ %% The body is sent twice, first with nofin, then wait 1.1 second, then again with fin.
+ %% We expect the handler to crash because read_urlencoded_body expects the full body.
+ ok = do_read_urlencoded_body_too_long("/crash/read_urlencoded_body/period", <<"abc">>, Config),
+ %% The timeout value is set too low on purpose to ensure a crash occurs.
+ ok = do_read_body_timeout("/opts/read_urlencoded_body/timeout", <<"abc">>, Config),
+ ok.
+
+%% We expect a crash.
+do_read_urlencoded_body_too_large(Path, Body, Config) ->
+ ConnPid = gun_open(Config),
+ Ref = gun:request(ConnPid, "POST", Path, [
+ {<<"content-length">>, integer_to_binary(iolist_size(Body))}
+ ]),
+ gun:data(ConnPid, Ref, fin, Body),
+ {response, _, 500, _} = gun:await(ConnPid, Ref),
+ gun:close(ConnPid).
+
+%% We expect a crash.
+do_read_urlencoded_body_too_long(Path, Body, Config) ->
+ ConnPid = gun_open(Config),
+ Ref = gun:request(ConnPid, "POST", Path, [
+ {<<"content-length">>, integer_to_binary(byte_size(Body) * 2)}
+ ]),
+ gun:data(ConnPid, Ref, nofin, Body),
+ timer:sleep(1100),
+ gun:data(ConnPid, Ref, fin, Body),
+ {response, _, 500, _} = gun:await(ConnPid, Ref),
+ gun:close(ConnPid).
+
+multipart(Config) ->
+ doc("Multipart request body."),
+ do_multipart("/multipart", Config).
+
+do_multipart(Path, Config) ->
+ LargeBody = iolist_to_binary(string:chars($a, 10000000)),
+ ReqBody = [
+ "--deadbeef\r\nContent-Type: text/plain\r\n\r\nCowboy is an HTTP server.\r\n"
+ "--deadbeef\r\nContent-Type: application/octet-stream\r\nX-Custom: value\r\n\r\n", LargeBody, "\r\n"
+ "--deadbeef--"
+ ],
+ RespBody = do_body("POST", Path, [
+ {<<"content-type">>, <<"multipart/mixed; boundary=deadbeef">>}
+ ], ReqBody, Config),
+ [
+ {[{<<"content-type">>, <<"text/plain">>}], <<"Cowboy is an HTTP server.">>},
+ {LargeHeaders, LargeBody}
+ ] = binary_to_term(RespBody),
+ %% @todo Multipart header order is currently undefined.
+ [
+ {<<"content-type">>, <<"application/octet-stream">>},
+ {<<"x-custom">>, <<"value">>}
+ ] = lists:sort(LargeHeaders),
+ ok.
+
+read_part_skip_body(Config) ->
+ doc("Multipart request body skipping part bodies."),
+ LargeBody = iolist_to_binary(string:chars($a, 10000000)),
+ ReqBody = [
+ "--deadbeef\r\nContent-Type: text/plain\r\n\r\nCowboy is an HTTP server.\r\n"
+ "--deadbeef\r\nContent-Type: application/octet-stream\r\nX-Custom: value\r\n\r\n", LargeBody, "\r\n"
+ "--deadbeef--"
+ ],
+ RespBody = do_body("POST", "/multipart/skip_body", [
+ {<<"content-type">>, <<"multipart/mixed; boundary=deadbeef">>}
+ ], ReqBody, Config),
+ [
+ [{<<"content-type">>, <<"text/plain">>}],
+ LargeHeaders
+ ] = binary_to_term(RespBody),
+ %% @todo Multipart header order is currently undefined.
+ [
+ {<<"content-type">>, <<"application/octet-stream">>},
+ {<<"x-custom">>, <<"value">>}
+ ] = lists:sort(LargeHeaders),
+ ok.
+
+%% @todo When reading a multipart body, length and period
+%% only apply to a single read_body call. We may want a
+%% separate option to know how many reads we want to do
+%% before we give up.
+
+read_part2(Config) ->
+ doc("Multipart request body using read_part/2."),
+ %% Override the length and period values only, making
+ %% the request process use more read_body calls.
+ %%
+ %% We do not try a custom timeout value since this would
+ %% be the same test as read_body/2.
+ do_multipart("/multipart/read_part2", Config).
+
+read_part_body2(Config) ->
+ doc("Multipart request body using read_part_body/2."),
+ %% Override the length and period values only, making
+ %% the request process use more read_body calls.
+ %%
+ %% We do not try a custom timeout value since this would
+ %% be the same test as read_body/2.
+ do_multipart("/multipart/read_part_body2", Config).
+
+%% Tests: Response.
+
+%% @todo We want to crash when calling set_resp_* or related
+%% functions after the reply has been sent.
+
+set_resp_cookie(Config) ->
+ doc("Response using set_resp_cookie."),
+ %% Single cookie, no options.
+ {200, Headers1, _} = do_get("/resp/set_resp_cookie3", Config),
+ {_, <<"mycookie=myvalue; Version=1">>}
+ = lists:keyfind(<<"set-cookie">>, 1, Headers1),
+ %% Single cookie, with options.
+ {200, Headers2, _} = do_get("/resp/set_resp_cookie4", Config),
+ {_, <<"mycookie=myvalue; Version=1; Path=/resp/set_resp_cookie4">>}
+ = lists:keyfind(<<"set-cookie">>, 1, Headers2),
+ %% Multiple cookies.
+ {200, Headers3, _} = do_get("/resp/set_resp_cookie3/multiple", Config),
+ [_, _] = [H || H={<<"set-cookie">>, _} <- Headers3],
+ %% Overwrite previously set cookie.
+ {200, Headers4, _} = do_get("/resp/set_resp_cookie3/overwrite", Config),
+ {_, <<"mycookie=overwrite; Version=1">>}
+ = lists:keyfind(<<"set-cookie">>, 1, Headers4),
+ ok.
+
+set_resp_header(Config) ->
+ doc("Response using set_resp_header."),
+ {200, Headers, <<"OK">>} = do_get("/resp/set_resp_header", Config),
+ true = lists:keymember(<<"content-type">>, 1, Headers),
+ ok.
+
+set_resp_body(Config) ->
+ doc("Response using set_resp_body."),
+ {200, _, <<"OK">>} = do_get("/resp/set_resp_body", Config),
+ {200, _, <<"OVERRIDE">>} = do_get("/resp/set_resp_body/override", Config),
+ {ok, AppFile} = file:read_file(code:where_is_file("cowboy.app")),
+ {200, _, AppFile} = do_get("/resp/set_resp_body/sendfile", Config),
+ ok.
+
+has_resp_header(Config) ->
+ doc("Has response header?"),
+ {200, Headers, <<"OK">>} = do_get("/resp/has_resp_header", Config),
+ true = lists:keymember(<<"content-type">>, 1, Headers),
+ ok.
+
+has_resp_body(Config) ->
+ doc("Has response body?"),
+ {200, _, <<"OK">>} = do_get("/resp/has_resp_body", Config),
+ {200, _, <<"OK">>} = do_get("/resp/has_resp_body/sendfile", Config),
+ ok.
+
+delete_resp_header(Config) ->
+ doc("Delete response header."),
+ {200, Headers, <<"OK">>} = do_get("/resp/delete_resp_header", Config),
+ false = lists:keymember(<<"content-type">>, 1, Headers),
+ ok.
+
+reply2(Config) ->
+ doc("Response with default headers and no body."),
+ {200, _, _} = do_get("/resp/reply2/200", Config),
+ {201, _, _} = do_get("/resp/reply2/201", Config),
+ {404, _, _} = do_get("/resp/reply2/404", Config),
+ {200, _, _} = do_get("/resp/reply2/binary", Config),
+ {500, _, _} = do_get("/resp/reply2/error", Config),
+ %% @todo We want to crash when reply or stream_reply is called twice.
+ %% How to test this properly? This isn't enough.
+ {200, _, _} = do_get("/resp/reply2/twice", Config),
+ ok.
+
+reply3(Config) ->
+ doc("Response with additional headers and no body."),
+ {200, Headers1, _} = do_get("/resp/reply3/200", Config),
+ true = lists:keymember(<<"content-type">>, 1, Headers1),
+ {201, Headers2, _} = do_get("/resp/reply3/201", Config),
+ true = lists:keymember(<<"content-type">>, 1, Headers2),
+ {404, Headers3, _} = do_get("/resp/reply3/404", Config),
+ true = lists:keymember(<<"content-type">>, 1, Headers3),
+ {500, _, _} = do_get("/resp/reply3/error", Config),
+ ok.
+
+reply4(Config) ->
+ doc("Response with additional headers and body."),
+ {200, _, <<"OK">>} = do_get("/resp/reply4/200", Config),
+ {201, _, <<"OK">>} = do_get("/resp/reply4/201", Config),
+ {404, _, <<"OK">>} = do_get("/resp/reply4/404", Config),
+ {500, _, _} = do_get("/resp/reply4/error", Config),
+ ok.
+
+%% @todo Crash when stream_reply is called twice.
+
+stream_reply2(Config) ->
+ doc("Response with default headers and streamed body."),
+ Body = <<0:8000000>>,
+ {200, _, Body} = do_get("/resp/stream_reply2/200", Config),
+ {201, _, Body} = do_get("/resp/stream_reply2/201", Config),
+ {404, _, Body} = do_get("/resp/stream_reply2/404", Config),
+ {200, _, Body} = do_get("/resp/stream_reply2/binary", Config),
+ {500, _, _} = do_get("/resp/stream_reply2/error", Config),
+ ok.
+
+stream_reply3(Config) ->
+ doc("Response with additional headers and streamed body."),
+ Body = <<0:8000000>>,
+ {200, Headers1, Body} = do_get("/resp/stream_reply3/200", Config),
+ true = lists:keymember(<<"content-type">>, 1, Headers1),
+ {201, Headers2, Body} = do_get("/resp/stream_reply3/201", Config),
+ true = lists:keymember(<<"content-type">>, 1, Headers2),
+ {404, Headers3, Body} = do_get("/resp/stream_reply3/404", Config),
+ true = lists:keymember(<<"content-type">>, 1, Headers3),
+ {500, _, _} = do_get("/resp/stream_reply3/error", Config),
+ ok.
+
+%% @todo Crash when calling stream_body after the fin flag has been set.
+%% @todo Crash when calling stream_body after calling reply.
+%% @todo Crash when calling stream_body before calling stream_reply.
+
+%% Tests: Push.
+
+%% @todo We want to crash when push is called after reply has been initiated.
+
+push(Config) ->
+ case config(protocol, Config) of
+ http -> do_push_http("/resp/push", Config);
+ http2 -> do_push_http2(Config)
+ end.
+
+push_method(Config) ->
+ case config(protocol, Config) of
+ http -> do_push_http("/resp/push/method", Config);
+ http2 -> do_push_http2_method(Config)
+ end.
+
+
+push_origin(Config) ->
+ case config(protocol, Config) of
+ http -> do_push_http("/resp/push/origin", Config);
+ http2 -> do_push_http2_origin(Config)
+ end.
+
+push_qs(Config) ->
+ case config(protocol, Config) of
+ http -> do_push_http("/resp/push/qs", Config);
+ http2 -> do_push_http2_qs(Config)
+ end.
+
+do_push_http(Path, Config) ->
+ doc("Ignore pushed responses when protocol is HTTP/1.1."),
+ ConnPid = gun_open(Config),
+ Ref = gun:get(ConnPid, Path, []),
+ {response, fin, 200, _} = gun:await(ConnPid, Ref),
+ ok.
+
+do_push_http2(Config) ->
+ doc("Pushed responses."),
+ ConnPid = gun_open(Config),
+ Ref = gun:get(ConnPid, "/resp/push", []),
+ %% We expect two pushed resources.
+ Origin = iolist_to_binary([
+ case config(type, Config) of
+ tcp -> "http";
+ ssl -> "https"
+ end,
+ "://localhost:",
+ integer_to_binary(config(port, Config))
+ ]),
+ OriginLen = byte_size(Origin),
+ {push, PushCSS, <<"GET">>, <<Origin:OriginLen/binary, "/static/style.css">>,
+ [{<<"accept">>,<<"text/css">>}]} = gun:await(ConnPid, Ref),
+ {push, PushTXT, <<"GET">>, <<Origin:OriginLen/binary, "/static/plain.txt">>,
+ [{<<"accept">>,<<"text/plain">>}]} = gun:await(ConnPid, Ref),
+ %% Pushed CSS.
+ {response, nofin, 200, HeadersCSS} = gun:await(ConnPid, PushCSS),
+ {_, <<"text/css">>} = lists:keyfind(<<"content-type">>, 1, HeadersCSS),
+ {ok, <<"body{color:red}\n">>} = gun:await_body(ConnPid, PushCSS),
+ %% Pushed TXT is 406 because the pushed accept header uses an undefined type.
+ {response, fin, 406, _} = gun:await(ConnPid, PushTXT),
+ %% Let's not forget about the response to the client's request.
+ {response, fin, 200, _} = gun:await(ConnPid, Ref),
+ gun:close(ConnPid).
+
+do_push_http2_method(Config) ->
+ doc("Pushed response with non-GET method."),
+ ConnPid = gun_open(Config),
+ Ref = gun:get(ConnPid, "/resp/push/method", []),
+ %% Pushed CSS.
+ {push, PushCSS, <<"HEAD">>, _, [{<<"accept">>,<<"text/css">>}]} = gun:await(ConnPid, Ref),
+ {response, fin, 200, HeadersCSS} = gun:await(ConnPid, PushCSS),
+ {_, <<"text/css">>} = lists:keyfind(<<"content-type">>, 1, HeadersCSS),
+ %% Let's not forget about the response to the client's request.
+ {response, fin, 200, _} = gun:await(ConnPid, Ref),
+ gun:close(ConnPid).
+
+do_push_http2_origin(Config) ->
+ doc("Pushed response with custom scheme/host/port."),
+ ConnPid = gun_open(Config),
+ Ref = gun:get(ConnPid, "/resp/push/origin", []),
+ %% Pushed CSS.
+ {push, PushCSS, <<"GET">>, <<"ftp://127.0.0.1:21/static/style.css">>,
+ [{<<"accept">>,<<"text/css">>}]} = gun:await(ConnPid, Ref),
+ {response, nofin, 200, HeadersCSS} = gun:await(ConnPid, PushCSS),
+ {_, <<"text/css">>} = lists:keyfind(<<"content-type">>, 1, HeadersCSS),
+ {ok, <<"body{color:red}\n">>} = gun:await_body(ConnPid, PushCSS),
+ %% Let's not forget about the response to the client's request.
+ {response, fin, 200, _} = gun:await(ConnPid, Ref),
+ gun:close(ConnPid).
+
+do_push_http2_qs(Config) ->
+ doc("Pushed response with query string."),
+ ConnPid = gun_open(Config),
+ Ref = gun:get(ConnPid, "/resp/push/qs", []),
+ %% Pushed CSS.
+ Origin = iolist_to_binary([
+ case config(type, Config) of
+ tcp -> "http";
+ ssl -> "https"
+ end,
+ "://localhost:",
+ integer_to_binary(config(port, Config))
+ ]),
+ OriginLen = byte_size(Origin),
+ {push, PushCSS, <<"GET">>, <<Origin:OriginLen/binary, "/static/style.css?server=cowboy&version=2.0">>,
+ [{<<"accept">>,<<"text/css">>}]} = gun:await(ConnPid, Ref),
+ {response, nofin, 200, HeadersCSS} = gun:await(ConnPid, PushCSS),
+ {_, <<"text/css">>} = lists:keyfind(<<"content-type">>, 1, HeadersCSS),
+ {ok, <<"body{color:red}\n">>} = gun:await_body(ConnPid, PushCSS),
+ %% Let's not forget about the response to the client's request.
+ {response, fin, 200, _} = gun:await(ConnPid, Ref),
+ gun:close(ConnPid).
diff --git a/test/ws_SUITE.erl b/test/ws_SUITE.erl
index f15c9fe..b4eebf7 100644
--- a/test/ws_SUITE.erl
+++ b/test/ws_SUITE.erl
@@ -39,14 +39,14 @@ init_per_group(Name = autobahn, Config) ->
_ ->
{ok, _} = cowboy:start_clear(Name, 100, [{port, 33080}], #{
env => #{dispatch => init_dispatch()},
- compress => true %% @todo Use a separate option for HTTP and Websocket compression.
+ websocket_compress => true
}),
Config
end;
init_per_group(Name = ws, Config) ->
cowboy_test:init_http(Name, #{
env => #{dispatch => init_dispatch()},
- compress => true %% @todo Use a separate option for HTTP and Websocket compression.
+ websocket_compress => true
}, Config).
end_per_group(Listener, _Config) ->
@@ -78,9 +78,9 @@ init_dispatch() ->
{close, 1001, <<"some text!">>},
{text, <<"won't be received">>}]}
]},
+ {"/ws_subprotocol", ws_subprotocol, []},
{"/ws_timeout_hibernate", ws_timeout_hibernate, []},
- {"/ws_timeout_cancel", ws_timeout_cancel, []},
- {"/ws_subprotocol", ws_subprotocol, []}
+ {"/ws_timeout_cancel", ws_timeout_cancel, []}
]}
]).
@@ -523,6 +523,35 @@ ws_send_many(Config) ->
{error, closed} = gen_tcp:recv(Socket, 0, 6000),
ok.
+ws_subprotocol(Config) ->
+ {port, Port} = lists:keyfind(port, 1, Config),
+ {ok, Socket} = gen_tcp:connect("localhost", Port,
+ [binary, {active, false}, {packet, raw}]),
+ ok = gen_tcp:send(Socket, [
+ "GET /ws_subprotocol HTTP/1.1\r\n"
+ "Host: localhost\r\n"
+ "Connection: Upgrade\r\n"
+ "Upgrade: websocket\r\n"
+ "Sec-WebSocket-Origin: http://localhost\r\n"
+ "Sec-WebSocket-Version: 13\r\n"
+ "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
+ "Sec-WebSocket-Protocol: foo, bar\r\n"
+ "\r\n"]),
+ {ok, Handshake} = gen_tcp:recv(Socket, 0, 6000),
+ {ok, {http_response, {1, 1}, 101, "Switching Protocols"}, Rest}
+ = erlang:decode_packet(http, Handshake, []),
+ [Headers, <<>>] = do_decode_headers(
+ erlang:decode_packet(httph, Rest, []), []),
+ {'Connection', "Upgrade"} = lists:keyfind('Connection', 1, Headers),
+ {'Upgrade', "websocket"} = lists:keyfind('Upgrade', 1, Headers),
+ {"sec-websocket-accept", "s3pPLMBiTxaQ9kYGzzhZRbK+xOo="}
+ = lists:keyfind("sec-websocket-accept", 1, Headers),
+ {"sec-websocket-protocol", "foo"}
+ = lists:keyfind("sec-websocket-protocol", 1, Headers),
+ {ok, << 1:1, 0:3, 8:4, 0:1, 2:7, 1000:16 >>} = gen_tcp:recv(Socket, 0, 6000),
+ {error, closed} = gen_tcp:recv(Socket, 0, 6000),
+ ok.
+
ws_text_fragments(Config) ->
{port, Port} = lists:keyfind(port, 1, Config),
{ok, Socket} = gen_tcp:connect("localhost", Port,
@@ -664,35 +693,6 @@ ws_timeout_reset(Config) ->
{error, closed} = gen_tcp:recv(Socket, 0, 6000),
ok.
-ws_subprotocol(Config) ->
- {port, Port} = lists:keyfind(port, 1, Config),
- {ok, Socket} = gen_tcp:connect("localhost", Port,
- [binary, {active, false}, {packet, raw}]),
- ok = gen_tcp:send(Socket, [
- "GET /ws_subprotocol HTTP/1.1\r\n"
- "Host: localhost\r\n"
- "Connection: Upgrade\r\n"
- "Upgrade: websocket\r\n"
- "Sec-WebSocket-Origin: http://localhost\r\n"
- "Sec-WebSocket-Version: 13\r\n"
- "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
- "Sec-WebSocket-Protocol: foo, bar\r\n"
- "\r\n"]),
- {ok, Handshake} = gen_tcp:recv(Socket, 0, 6000),
- {ok, {http_response, {1, 1}, 101, "Switching Protocols"}, Rest}
- = erlang:decode_packet(http, Handshake, []),
- [Headers, <<>>] = do_decode_headers(
- erlang:decode_packet(httph, Rest, []), []),
- {'Connection', "Upgrade"} = lists:keyfind('Connection', 1, Headers),
- {'Upgrade', "websocket"} = lists:keyfind('Upgrade', 1, Headers),
- {"sec-websocket-accept", "s3pPLMBiTxaQ9kYGzzhZRbK+xOo="}
- = lists:keyfind("sec-websocket-accept", 1, Headers),
- {"sec-websocket-protocol", "foo"}
- = lists:keyfind("sec-websocket-protocol", 1, Headers),
- {ok, << 1:1, 0:3, 8:4, 0:1, 2:7, 1000:16 >>} = gen_tcp:recv(Socket, 0, 6000),
- {error, closed} = gen_tcp:recv(Socket, 0, 6000),
- ok.
-
%% Internal.
do_decode_headers({ok, http_eoh, Rest}, Acc) ->