aboutsummaryrefslogtreecommitdiffstats
path: root/test/handlers
diff options
context:
space:
mode:
Diffstat (limited to 'test/handlers')
-rw-r--r--test/handlers/compress_h.erl5
-rw-r--r--test/handlers/decompress_h.erl84
-rw-r--r--test/handlers/echo_h.erl22
-rw-r--r--test/handlers/generate_etag_h.erl3
-rw-r--r--test/handlers/loop_handler_endless_h.erl25
-rw-r--r--test/handlers/loop_handler_timeout_hibernate_h.erl30
-rw-r--r--test/handlers/loop_handler_timeout_info_h.erl23
-rw-r--r--test/handlers/loop_handler_timeout_init_h.erl23
-rw-r--r--test/handlers/resp_h.erl58
-rw-r--r--test/handlers/stream_handler_h.erl8
-rw-r--r--test/handlers/streamed_result_h.erl20
-rw-r--r--test/handlers/ws_init_h.erl5
-rw-r--r--test/handlers/ws_ping_h.erl23
13 files changed, 322 insertions, 7 deletions
diff --git a/test/handlers/compress_h.erl b/test/handlers/compress_h.erl
index 27edbd3..658c834 100644
--- a/test/handlers/compress_h.erl
+++ b/test/handlers/compress_h.erl
@@ -19,6 +19,9 @@ init(Req0, State=reply) ->
<<"content-encoding">> ->
cowboy_req:reply(200, #{<<"content-encoding">> => <<"compress">>},
lists:duplicate(100000, $a), Req0);
+ <<"etag">> ->
+ cowboy_req:reply(200, #{<<"etag">> => <<"\"STRONK\"">>},
+ lists:duplicate(100000, $a), Req0);
<<"sendfile">> ->
AppFile = code:where_is_file("cowboy.app"),
Size = filelib:file_size(AppFile),
@@ -34,6 +37,8 @@ init(Req0, State=stream_reply) ->
stream_reply(#{}, Req0);
<<"content-encoding">> ->
stream_reply(#{<<"content-encoding">> => <<"compress">>}, Req0);
+ <<"etag">> ->
+ stream_reply(#{<<"etag">> => <<"\"STRONK\"">>}, Req0);
<<"sendfile">> ->
Data = lists:duplicate(10000, $a),
AppFile = code:where_is_file("cowboy.app"),
diff --git a/test/handlers/decompress_h.erl b/test/handlers/decompress_h.erl
new file mode 100644
index 0000000..deb6de0
--- /dev/null
+++ b/test/handlers/decompress_h.erl
@@ -0,0 +1,84 @@
+%% This module echoes a request body of to test
+%% the cowboy_decompress_h stream handler.
+
+-module(decompress_h).
+
+-export([init/2]).
+
+init(Req0, State=echo) ->
+ case cowboy_req:binding(what, Req0) of
+ <<"decompress_disable">> ->
+ cowboy_req:cast({set_options, #{decompress_enabled => false}}, Req0);
+ <<"decompress_ratio_limit">> ->
+ cowboy_req:cast({set_options, #{decompress_ratio_limit => 0.5}}, Req0);
+ <<"normal">> -> ok
+ end,
+ {ok, Body, Req1} = read_body(Req0),
+ Req = cowboy_req:reply(200, #{}, Body, Req1),
+ {ok, Req, State};
+init(Req0, State=test) ->
+ Req = test(Req0, cowboy_req:binding(what, Req0)),
+ {ok, Req, State}.
+
+test(Req, <<"content-encoding">>) ->
+ cowboy_req:reply(200, #{},
+ cowboy_req:header(<<"content-encoding">>, Req, <<"undefined">>),
+ Req);
+test(Req, <<"content-decoded">>) ->
+ cowboy_req:reply(200, #{},
+ io_lib:format("~0p", [maps:get(content_decoded, Req, undefined)]),
+ Req);
+test(Req0, <<"disable-in-the-middle">>) ->
+ {Status, Data, Req1} = cowboy_req:read_body(Req0, #{length => 1000}),
+ cowboy_req:cast({set_options, #{decompress_enabled => false}}, Req1),
+ {ok, Body, Req} = do_read_body(Status, Req1, Data),
+ cowboy_req:reply(200, #{}, Body, Req);
+test(Req0, <<"enable-in-the-middle">>) ->
+ {Status, Data, Req1} = cowboy_req:read_body(Req0, #{length => 1000}),
+ cowboy_req:cast({set_options, #{decompress_enabled => true}}, Req1),
+ {ok, Body, Req} = do_read_body(Status, Req1, Data),
+ cowboy_req:reply(200, #{}, Body, Req);
+test(Req0, <<"header-command">>) ->
+ {ok, Body, Req1} = read_body(Req0),
+ Req = cowboy_req:stream_reply(200, #{}, Req1),
+ cowboy_req:stream_body(Body, fin, Req);
+test(Req0, <<"accept-identity">>) ->
+ {ok, Body, Req} = read_body(Req0),
+ cowboy_req:reply(200,
+ #{<<"accept-encoding">> => <<"identity">>},
+ Body, Req);
+test(Req0, <<"invalid-header">>) ->
+ {ok, Body, Req} = read_body(Req0),
+ cowboy_req:reply(200,
+ #{<<"accept-encoding">> => <<";">>},
+ Body, Req);
+test(Req0, <<"reject-explicit-header">>) ->
+ {ok, Body, Req} = read_body(Req0),
+ cowboy_req:reply(200,
+ #{<<"accept-encoding">> => <<"identity, gzip;q=0">>},
+ Body, Req);
+test(Req0, <<"reject-implicit-header">>) ->
+ {ok, Body, Req} = read_body(Req0),
+ cowboy_req:reply(200,
+ #{<<"accept-encoding">> => <<"identity, *;q=0">>},
+ Body, Req);
+test(Req0, <<"accept-explicit-header">>) ->
+ {ok, Body, Req} = read_body(Req0),
+ cowboy_req:reply(200,
+ #{<<"accept-encoding">> => <<"identity, gzip;q=0.5">>},
+ Body, Req);
+test(Req0, <<"accept-implicit-header">>) ->
+ {ok, Body, Req} = read_body(Req0),
+ cowboy_req:reply(200,
+ #{<<"accept-encoding">> => <<"identity, *;q=0.5">>},
+ Body, Req).
+
+read_body(Req0) ->
+ {Status, Data, Req} = cowboy_req:read_body(Req0, #{length => 1000}),
+ do_read_body(Status, Req, Data).
+
+do_read_body(more, Req0, Acc) ->
+ {Status, Data, Req} = cowboy_req:read_body(Req0),
+ do_read_body(Status, Req, << Acc/binary, Data/binary >>);
+do_read_body(ok, Req, Acc) ->
+ {ok, Acc, Req}.
diff --git a/test/handlers/echo_h.erl b/test/handlers/echo_h.erl
index 1b672d1..d04d531 100644
--- a/test/handlers/echo_h.erl
+++ b/test/handlers/echo_h.erl
@@ -25,6 +25,8 @@ echo(<<"read_body">>, Req0, Opts) ->
timer:sleep(500),
cowboy_req:read_body(Req0);
<<"/full", _/bits>> -> read_body(Req0, <<>>);
+ <<"/auto-sync", _/bits>> -> read_body_auto_sync(Req0, <<>>);
+ <<"/auto-async", _/bits>> -> read_body_auto_async(Req0, <<>>);
<<"/length", _/bits>> ->
{_, _, Req1} = read_body(Req0, <<>>),
Length = cowboy_req:body_length(Req1),
@@ -84,6 +86,7 @@ echo(<<"match">>, Req, Opts) ->
Fields = [binary_to_atom(F, latin1) || F <- Fields0],
Value = case Type of
<<"qs">> -> cowboy_req:match_qs(Fields, Req);
+ <<"qs_with_constraints">> -> cowboy_req:match_qs([{id, integer}], Req);
<<"cookies">> -> cowboy_req:match_cookies(Fields, Req);
<<"body_qs">> ->
%% Note that the Req should not be discarded but for the
@@ -122,6 +125,25 @@ read_body(Req0, Acc) ->
{more, Data, Req} -> read_body(Req, << Acc/binary, Data/binary >>)
end.
+read_body_auto_sync(Req0, Acc) ->
+ Opts = #{length => auto, period => infinity},
+ case cowboy_req:read_body(Req0, Opts) of
+ {ok, Data, Req} -> {ok, << Acc/binary, Data/binary >>, Req};
+ {more, Data, Req} -> read_body_auto_sync(Req, << Acc/binary, Data/binary >>)
+ end.
+
+read_body_auto_async(Req, Acc) ->
+ read_body_auto_async(Req, make_ref(), Acc).
+
+read_body_auto_async(Req, ReadBodyRef, Acc) ->
+ cowboy_req:cast({read_body, self(), ReadBodyRef, auto, infinity}, Req),
+ receive
+ {request_body, ReadBodyRef, nofin, Data} ->
+ read_body_auto_async(Req, ReadBodyRef, <<Acc/binary, Data/binary>>);
+ {request_body, ReadBodyRef, fin, _, Data} ->
+ {ok, <<Acc/binary, Data/binary>>, Req}
+ 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);
value_to_iodata(V) when is_list(V); is_tuple(V); is_map(V) -> io_lib:format("~999999p", [V]);
diff --git a/test/handlers/generate_etag_h.erl b/test/handlers/generate_etag_h.erl
index 97ee82b..b9e1302 100644
--- a/test/handlers/generate_etag_h.erl
+++ b/test/handlers/generate_etag_h.erl
@@ -34,6 +34,9 @@ generate_etag(Req=#{qs := <<"binary-weak-unquoted">>}, State) ->
generate_etag(Req=#{qs := <<"binary-strong-unquoted">>}, State) ->
ct_helper_error_h:ignore(cow_http_hd, parse_etag, 1),
{<<"etag-header-value">>, Req, State};
+%% Returning 'undefined' to indicate no etag.
+generate_etag(Req=#{qs := <<"undefined">>}, State) ->
+ {undefined, Req, State};
%% Simulate the callback being missing in other cases.
generate_etag(#{qs := <<"missing">>}, _) ->
no_call.
diff --git a/test/handlers/loop_handler_endless_h.erl b/test/handlers/loop_handler_endless_h.erl
new file mode 100644
index 0000000..d8c8ab5
--- /dev/null
+++ b/test/handlers/loop_handler_endless_h.erl
@@ -0,0 +1,25 @@
+%% This module implements a loop handler that streams endless data.
+
+-module(loop_handler_endless_h).
+
+-export([init/2]).
+-export([info/3]).
+
+init(Req0, #{delay := Delay} = Opts) ->
+ case cowboy_req:header(<<"x-test-pid">>, Req0) of
+ BinPid when is_binary(BinPid) ->
+ Pid = list_to_pid(binary_to_list(BinPid)),
+ Pid ! {Pid, self(), init},
+ ok;
+ _ ->
+ ok
+ end,
+ erlang:send_after(Delay, self(), timeout),
+ Req = cowboy_req:stream_reply(200, Req0),
+ {cowboy_loop, Req, Opts}.
+
+info(timeout, Req, State) ->
+ cowboy_req:stream_body(<<0:10000/unit:8>>, nofin, Req),
+ %% Equivalent to a 0 timeout.
+ self() ! timeout,
+ {ok, Req, State}.
diff --git a/test/handlers/loop_handler_timeout_hibernate_h.erl b/test/handlers/loop_handler_timeout_hibernate_h.erl
new file mode 100644
index 0000000..0485208
--- /dev/null
+++ b/test/handlers/loop_handler_timeout_hibernate_h.erl
@@ -0,0 +1,30 @@
+%% This module implements a loop handler that first
+%% sets a timeout, then hibernates, then ensures
+%% that the timeout initially set no longer triggers.
+%% If everything goes fine a 200 is returned. If the
+%% timeout triggers again a 299 is.
+
+-module(loop_handler_timeout_hibernate_h).
+
+-export([init/2]).
+-export([info/3]).
+-export([terminate/3]).
+
+init(Req, _) ->
+ self() ! message1,
+ {cowboy_loop, Req, undefined, 100}.
+
+info(message1, Req, State) ->
+ erlang:send_after(200, self(), message2),
+ {ok, Req, State, hibernate};
+info(message2, Req, State) ->
+ erlang:send_after(200, self(), message3),
+ %% Don't set a timeout now.
+ {ok, Req, State};
+info(message3, Req, State) ->
+ {stop, cowboy_req:reply(200, Req), State};
+info(timeout, Req, State) ->
+ {stop, cowboy_req:reply(<<"299 OK!">>, Req), State}.
+
+terminate(stop, _, _) ->
+ ok.
diff --git a/test/handlers/loop_handler_timeout_info_h.erl b/test/handlers/loop_handler_timeout_info_h.erl
new file mode 100644
index 0000000..7a1ccba
--- /dev/null
+++ b/test/handlers/loop_handler_timeout_info_h.erl
@@ -0,0 +1,23 @@
+%% This module implements a loop handler that changes
+%% the timeout value to 500ms after the first message
+%% then sends itself another message after 1000ms.
+%% It is expected to timeout, that is, reply a 299.
+
+-module(loop_handler_timeout_info_h).
+
+-export([init/2]).
+-export([info/3]).
+-export([terminate/3]).
+
+init(Req, _) ->
+ self() ! message,
+ {cowboy_loop, Req, undefined}.
+
+info(message, Req, State) ->
+ erlang:send_after(500, self(), message),
+ {ok, Req, State, 100};
+info(timeout, Req, State) ->
+ {stop, cowboy_req:reply(<<"299 OK!">>, Req), State}.
+
+terminate(stop, _, _) ->
+ ok.
diff --git a/test/handlers/loop_handler_timeout_init_h.erl b/test/handlers/loop_handler_timeout_init_h.erl
new file mode 100644
index 0000000..7908fda
--- /dev/null
+++ b/test/handlers/loop_handler_timeout_init_h.erl
@@ -0,0 +1,23 @@
+%% This module implements a loop handler that reads
+%% the request query for a timeout value, then sends
+%% itself a message after 1000ms. It replies a 200 when
+%% the message does not timeout and a 299 otherwise.
+
+-module(loop_handler_timeout_init_h).
+
+-export([init/2]).
+-export([info/3]).
+-export([terminate/3]).
+
+init(Req, _) ->
+ #{timeout := Timeout} = cowboy_req:match_qs([{timeout, int}], Req),
+ erlang:send_after(500, self(), message),
+ {cowboy_loop, Req, undefined, Timeout}.
+
+info(message, Req, State) ->
+ {stop, cowboy_req:reply(200, Req), State};
+info(timeout, Req, State) ->
+ {stop, cowboy_req:reply(<<"299 OK!">>, Req), State}.
+
+terminate(stop, _, _) ->
+ ok.
diff --git a/test/handlers/resp_h.erl b/test/handlers/resp_h.erl
index 8031d0e..6e9b5f7 100644
--- a/test/handlers/resp_h.erl
+++ b/test/handlers/resp_h.erl
@@ -30,6 +30,10 @@ do(<<"set_resp_cookie4">>, Req0, 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_header_cookie">>, Req0, Opts) ->
+ ct_helper:ignore(cowboy_req, set_resp_header, 3),
+ Req = cowboy_req:set_resp_header(<<"set-cookie">>, <<"name=value">>, Req0),
+ {ok, cowboy_req:reply(200, #{}, "OK", Req), Opts};
do(<<"set_resp_header_server">>, Req0, Opts) ->
Req = cowboy_req:set_resp_header(<<"server">>, <<"nginx">>, Req0),
{ok, cowboy_req:reply(200, #{}, "OK", Req), Opts};
@@ -39,6 +43,12 @@ do(<<"set_resp_headers">>, Req0, Opts) ->
<<"content-encoding">> => <<"compress">>
}, Req0),
{ok, cowboy_req:reply(200, #{}, "OK", Req), Opts};
+do(<<"set_resp_headers_cookie">>, Req0, Opts) ->
+ ct_helper:ignore(cowboy_req, set_resp_headers, 2),
+ Req = cowboy_req:set_resp_headers(#{
+ <<"set-cookie">> => <<"name=value">>
+ }, Req0),
+ {ok, cowboy_req:reply(200, #{}, "OK", Req), Opts};
do(<<"set_resp_headers_http11">>, Req0, Opts) ->
Req = cowboy_req:set_resp_headers(#{
<<"connection">> => <<"custom-header, close">>,
@@ -130,6 +140,10 @@ do(<<"inform2">>, Req0, Opts) ->
<<"twice">> ->
cowboy_req:inform(102, Req0),
cowboy_req:inform(102, Req0);
+ <<"after_reply">> ->
+ ct_helper:ignore(cowboy_req, inform, 3),
+ Req1 = cowboy_req:reply(200, Req0),
+ cowboy_req:inform(102, Req1);
Status ->
cowboy_req:inform(binary_to_integer(Status), Req0)
end,
@@ -143,9 +157,16 @@ do(<<"inform3">>, Req0, Opts) ->
<<"error">> ->
ct_helper:ignore(cowboy_req, inform, 3),
cowboy_req:inform(ok, Headers, Req0);
+ <<"set_cookie">> ->
+ ct_helper:ignore(cowboy_req, inform, 3),
+ cowboy_req:inform(102, #{<<"set-cookie">> => <<"name=value">>}, Req0);
<<"twice">> ->
cowboy_req:inform(102, Headers, Req0),
cowboy_req:inform(102, Headers, Req0);
+ <<"after_reply">> ->
+ ct_helper:ignore(cowboy_req, inform, 3),
+ Req1 = cowboy_req:reply(200, Req0),
+ cowboy_req:inform(102, Headers, Req1);
Status ->
cowboy_req:inform(binary_to_integer(Status), Headers, Req0)
end,
@@ -161,6 +182,7 @@ do(<<"reply2">>, Req0, Opts) ->
<<"twice">> ->
ct_helper:ignore(cowboy_req, reply, 4),
Req1 = cowboy_req:reply(200, Req0),
+ timer:sleep(100),
cowboy_req:reply(200, Req1);
Status ->
cowboy_req:reply(binary_to_integer(Status), Req0)
@@ -171,6 +193,9 @@ do(<<"reply3">>, Req0, Opts) ->
<<"error">> ->
ct_helper:ignore(cowboy_req, reply, 4),
cowboy_req:reply(200, ok, Req0);
+ <<"set_cookie">> ->
+ ct_helper:ignore(cowboy_req, reply, 4),
+ cowboy_req:reply(200, #{<<"set-cookie">> => <<"name=value">>}, Req0);
Status ->
cowboy_req:reply(binary_to_integer(Status),
#{<<"content-type">> => <<"text/plain">>}, Req0)
@@ -181,11 +206,14 @@ do(<<"reply4">>, Req0, Opts) ->
<<"error">> ->
ct_helper:ignore(erlang, iolist_size, 1),
cowboy_req:reply(200, #{}, ok, Req0);
- <<"204body">> ->
+ <<"set_cookie">> ->
ct_helper:ignore(cowboy_req, reply, 4),
+ cowboy_req:reply(200, #{<<"set-cookie">> => <<"name=value">>}, <<"OK">>, Req0);
+ <<"204body">> ->
+ ct_helper:ignore(cowboy_req, do_reply_ensure_no_body, 4),
cowboy_req:reply(204, #{}, <<"OK">>, Req0);
<<"304body">> ->
- ct_helper:ignore(cowboy_req, reply, 4),
+ ct_helper:ignore(cowboy_req, do_reply_ensure_no_body, 4),
cowboy_req:reply(304, #{}, <<"OK">>, Req0);
Status ->
cowboy_req:reply(binary_to_integer(Status), #{}, <<"OK">>, Req0)
@@ -215,6 +243,14 @@ do(<<"stream_reply2">>, Req0, Opts) ->
Req = cowboy_req:stream_reply(304, Req0),
stream_body(Req),
{ok, Req, Opts};
+ <<"twice">> ->
+ ct_helper:ignore(cowboy_req, stream_reply, 3),
+ Req1 = cowboy_req:stream_reply(200, Req0),
+ timer:sleep(100),
+ %% We will crash here so the body shouldn't be sent.
+ Req = cowboy_req:stream_reply(200, Req1),
+ stream_body(Req),
+ {ok, Req, Opts};
Status ->
Req = cowboy_req:stream_reply(binary_to_integer(Status), Req0),
stream_body(Req),
@@ -225,6 +261,9 @@ do(<<"stream_reply3">>, Req0, Opts) ->
<<"error">> ->
ct_helper:ignore(cowboy_req, stream_reply, 3),
cowboy_req:stream_reply(200, ok, Req0);
+ <<"set_cookie">> ->
+ ct_helper:ignore(cowboy_req, stream_reply, 3),
+ cowboy_req:stream_reply(200, #{<<"set-cookie">> => <<"name=value">>}, Req0);
Status ->
cowboy_req:stream_reply(binary_to_integer(Status),
#{<<"content-type">> => <<"text/plain">>}, Req0)
@@ -380,6 +419,16 @@ do(<<"stream_trailers">>, Req0, Opts) ->
<<"grpc-status">> => <<"0">>
}, Req),
{ok, Req, Opts};
+ <<"set_cookie">> ->
+ ct_helper:ignore(cowboy_req, stream_trailers, 2),
+ Req = cowboy_req:stream_reply(200, #{
+ <<"trailer">> => <<"set-cookie">>
+ }, Req0),
+ cowboy_req:stream_body(<<"Hello world!">>, nofin, Req),
+ cowboy_req:stream_trailers(#{
+ <<"set-cookie">> => <<"name=value">>
+ }, Req),
+ {ok, Req, Opts};
_ ->
Req = cowboy_req:stream_reply(200, #{
<<"trailer">> => <<"grpc-status">>
@@ -403,6 +452,11 @@ do(<<"push">>, Req, Opts) ->
<<"qs">> ->
cowboy_req:push("/static/style.css", #{<<"accept">> => <<"text/css">>}, Req,
#{qs => <<"server=cowboy&version=2.0">>});
+ <<"after_reply">> ->
+ ct_helper:ignore(cowboy_req, push, 4),
+ Req1 = cowboy_req:reply(200, Req),
+ %% We will crash here so no need to worry about propagating Req1.
+ cowboy_req:push("/static/style.css", #{<<"accept">> => <<"text/css">>}, Req1);
_ ->
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.
diff --git a/test/handlers/stream_handler_h.erl b/test/handlers/stream_handler_h.erl
index 370d15a..7a1e5ec 100644
--- a/test/handlers/stream_handler_h.erl
+++ b/test/handlers/stream_handler_h.erl
@@ -44,16 +44,16 @@ init_commands(_, _, #state{test=set_options_ignore_unknown}) ->
];
init_commands(_, _, State=#state{test=shutdown_on_stream_stop}) ->
Spawn = init_process(false, State),
- [{headers, 200, #{}}, {spawn, Spawn, 5000}, stop];
+ [{spawn, Spawn, 5000}, {headers, 200, #{}}, stop];
init_commands(_, _, State=#state{test=shutdown_on_socket_close}) ->
Spawn = init_process(false, State),
- [{headers, 200, #{}}, {spawn, Spawn, 5000}];
+ [{spawn, Spawn, 5000}, {headers, 200, #{}}];
init_commands(_, _, State=#state{test=shutdown_timeout_on_stream_stop}) ->
Spawn = init_process(true, State),
- [{headers, 200, #{}}, {spawn, Spawn, 2000}, stop];
+ [{spawn, Spawn, 2000}, {headers, 200, #{}}, stop];
init_commands(_, _, State=#state{test=shutdown_timeout_on_socket_close}) ->
Spawn = init_process(true, State),
- [{headers, 200, #{}}, {spawn, Spawn, 2000}];
+ [{spawn, Spawn, 2000}, {headers, 200, #{}}];
init_commands(_, _, State=#state{test=switch_protocol_after_headers}) ->
[{headers, 200, #{}}, {switch_protocol, #{}, ?MODULE, State}];
init_commands(_, _, State=#state{test=switch_protocol_after_headers_data}) ->
diff --git a/test/handlers/streamed_result_h.erl b/test/handlers/streamed_result_h.erl
new file mode 100644
index 0000000..ea6f492
--- /dev/null
+++ b/test/handlers/streamed_result_h.erl
@@ -0,0 +1,20 @@
+-module(streamed_result_h).
+
+-export([init/2]).
+
+init(Req, Opts) ->
+ N = list_to_integer(binary_to_list(cowboy_req:binding(n, Req))),
+ Interval = list_to_integer(binary_to_list(cowboy_req:binding(interval, Req))),
+ chunked(N, Interval, Req, Opts).
+
+chunked(N, Interval, Req0, Opts) ->
+ Req = cowboy_req:stream_reply(200, Req0),
+ {ok, loop(N, Interval, Req), Opts}.
+
+loop(0, _Interval, Req) ->
+ ok = cowboy_req:stream_body("Finished!\n", fin, Req),
+ Req;
+loop(N, Interval, Req) ->
+ ok = cowboy_req:stream_body(iolist_to_binary([integer_to_list(N), <<"\n">>]), nofin, Req),
+ timer:sleep(Interval),
+ loop(N-1, Interval, Req).
diff --git a/test/handlers/ws_init_h.erl b/test/handlers/ws_init_h.erl
index db5307b..bbe9ef9 100644
--- a/test/handlers/ws_init_h.erl
+++ b/test/handlers/ws_init_h.erl
@@ -36,7 +36,10 @@ do_websocket_init(State=reply_many_hibernate) ->
do_websocket_init(State=reply_many_close) ->
{[{text, "Hello"}, close], State};
do_websocket_init(State=reply_many_close_hibernate) ->
- {[{text, "Hello"}, close], State, hibernate}.
+ {[{text, "Hello"}, close], State, hibernate};
+do_websocket_init(State=reply_trap_exit) ->
+ Text = "trap_exit: " ++ atom_to_list(element(2, process_info(self(), trap_exit))),
+ {[{text, Text}, close], State, hibernate}.
websocket_handle(_, State) ->
{[], State}.
diff --git a/test/handlers/ws_ping_h.erl b/test/handlers/ws_ping_h.erl
new file mode 100644
index 0000000..a5848fe
--- /dev/null
+++ b/test/handlers/ws_ping_h.erl
@@ -0,0 +1,23 @@
+%% This module sends an empty ping to the client and
+%% waits for a pong before sending a text frame. It
+%% is used to confirm server-initiated pings work.
+
+-module(ws_ping_h).
+-behavior(cowboy_websocket).
+
+-export([init/2]).
+-export([websocket_init/1]).
+-export([websocket_handle/2]).
+-export([websocket_info/2]).
+
+init(Req, _) ->
+ {cowboy_websocket, Req, undefined}.
+
+websocket_init(State) ->
+ {[{ping, <<>>}], State}.
+
+websocket_handle(pong, State) ->
+ {[{text, <<"OK!!">>}], State}.
+
+websocket_info(_, State) ->
+ {[], State}.