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/content_types_provided_h.erl5
-rw-r--r--test/handlers/crash_h.erl3
-rw-r--r--test/handlers/create_resource_h.erl28
-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/read_body_h.erl15
-rw-r--r--test/handlers/resp_h.erl73
-rw-r--r--test/handlers/stream_handler_h.erl8
-rw-r--r--test/handlers/stream_hello_h.erl15
-rw-r--r--test/handlers/streamed_result_h.erl20
-rw-r--r--test/handlers/ws_ignore.erl20
-rw-r--r--test/handlers/ws_init_h.erl5
-rw-r--r--test/handlers/ws_ping_h.erl23
-rw-r--r--test/handlers/ws_set_options_commands_h.erl19
-rw-r--r--test/handlers/wt_echo_h.erl103
21 files changed, 541 insertions, 11 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/content_types_provided_h.erl b/test/handlers/content_types_provided_h.erl
index 5220c19..397026b 100644
--- a/test/handlers/content_types_provided_h.erl
+++ b/test/handlers/content_types_provided_h.erl
@@ -11,9 +11,14 @@
init(Req, Opts) ->
{cowboy_rest, Req, Opts}.
+content_types_provided(Req=#{qs := <<"invalid-type">>}, State) ->
+ ct_helper:ignore(cowboy_rest, normalize_content_types, 2),
+ {[{{'*', '*', '*'}, get_text_plain}], Req, State};
content_types_provided(Req=#{qs := <<"wildcard-param">>}, State) ->
{[{{<<"text">>, <<"plain">>, '*'}, get_text_plain}], Req, State}.
+get_text_plain(Req=#{qs := <<"invalid-type">>}, State) ->
+ {<<"invalid-type">>, Req, State};
get_text_plain(Req=#{qs := <<"wildcard-param">>}, State) ->
{_, _, Param} = maps:get(media_type, Req),
Body = if
diff --git a/test/handlers/crash_h.erl b/test/handlers/crash_h.erl
index b687aba..57d4d85 100644
--- a/test/handlers/crash_h.erl
+++ b/test/handlers/crash_h.erl
@@ -7,6 +7,9 @@
-export([init/2]).
-spec init(_, _) -> no_return().
+init(_, external_exit) ->
+ ct_helper:ignore(?MODULE, init, 2),
+ exit(self(), ct_helper_ignore);
init(_, no_reply) ->
ct_helper:ignore(?MODULE, init, 2),
error(crash);
diff --git a/test/handlers/create_resource_h.erl b/test/handlers/create_resource_h.erl
new file mode 100644
index 0000000..f82e610
--- /dev/null
+++ b/test/handlers/create_resource_h.erl
@@ -0,0 +1,28 @@
+-module(create_resource_h).
+
+-export([init/2]).
+-export([allowed_methods/2]).
+-export([resource_exists/2]).
+-export([content_types_accepted/2]).
+-export([from_text/2]).
+
+init(Req, Opts) ->
+ {cowboy_rest, Req, Opts}.
+
+allowed_methods(Req, State) ->
+ {[<<"POST">>], Req, State}.
+
+resource_exists(Req, State) ->
+ {true, Req, State}.
+
+content_types_accepted(Req, State) ->
+ {[{{<<"application">>, <<"text">>, []}, from_text}], Req, State}.
+
+from_text(Req=#{qs := Qs}, State) ->
+ NewURI = [cowboy_req:uri(Req), "/foo"],
+ case Qs of
+ <<"created">> ->
+ {{created, NewURI}, Req, State};
+ <<"see_other">> ->
+ {{see_other, NewURI}, Req, State}
+ end.
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/read_body_h.erl b/test/handlers/read_body_h.erl
new file mode 100644
index 0000000..a0de3b3
--- /dev/null
+++ b/test/handlers/read_body_h.erl
@@ -0,0 +1,15 @@
+%% This module reads the request body fully and send a 204 response.
+
+-module(read_body_h).
+
+-export([init/2]).
+
+init(Req0, Opts) ->
+ {ok, Req} = read_body(Req0),
+ {ok, cowboy_req:reply(200, #{}, Req), Opts}.
+
+read_body(Req0) ->
+ case cowboy_req:read_body(Req0) of
+ {ok, _, Req} -> {ok, Req};
+ {more, _, Req} -> read_body(Req)
+ end.
diff --git a/test/handlers/resp_h.erl b/test/handlers/resp_h.erl
index 8031d0e..d1c46e0 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,27 @@ do(<<"set_resp_headers">>, Req0, Opts) ->
<<"content-encoding">> => <<"compress">>
}, Req0),
{ok, cowboy_req:reply(200, #{}, "OK", Req), Opts};
+do(<<"set_resp_headers_list">>, Req0, Opts) ->
+ Req = cowboy_req:set_resp_headers([
+ {<<"content-type">>, <<"text/plain">>},
+ {<<"test-header">>, <<"one">>},
+ {<<"content-encoding">>, <<"compress">>},
+ {<<"test-header">>, <<"two">>}
+ ], 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_list_cookie">>, Req0, Opts) ->
+ ct_helper:ignore(cowboy_req, set_resp_headers_list, 3),
+ Req = cowboy_req:set_resp_headers([
+ {<<"set-cookie">>, <<"name=value">>},
+ {<<"set-cookie">>, <<"name2=value2">>}
+ ], 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 +155,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 +172,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 +197,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 +208,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 +221,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 +258,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 +276,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 +434,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 +467,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/stream_hello_h.erl b/test/handlers/stream_hello_h.erl
new file mode 100644
index 0000000..e67e220
--- /dev/null
+++ b/test/handlers/stream_hello_h.erl
@@ -0,0 +1,15 @@
+%% This module is the fastest way of producing a Hello world!
+
+-module(stream_hello_h).
+
+-export([init/3]).
+-export([terminate/3]).
+
+init(_, _, State) ->
+ {[
+ {response, 200, #{<<"content-length">> => <<"12">>}, <<"Hello world!">>},
+ stop
+ ], State}.
+
+terminate(_, _, _) ->
+ ok.
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_ignore.erl b/test/handlers/ws_ignore.erl
new file mode 100644
index 0000000..9fe3322
--- /dev/null
+++ b/test/handlers/ws_ignore.erl
@@ -0,0 +1,20 @@
+%% Feel free to use, reuse and abuse the code in this file.
+
+-module(ws_ignore).
+
+-export([init/2]).
+-export([websocket_handle/2]).
+-export([websocket_info/2]).
+
+init(Req, _) ->
+ {cowboy_websocket, Req, undefined, #{
+ compress => true
+ }}.
+
+websocket_handle({text, <<"CHECK">>}, State) ->
+ {[{text, <<"CHECK">>}], State};
+websocket_handle(_Frame, State) ->
+ {[], State}.
+
+websocket_info(_Info, State) ->
+ {[], State}.
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}.
diff --git a/test/handlers/ws_set_options_commands_h.erl b/test/handlers/ws_set_options_commands_h.erl
index 88d4e72..1ab0af4 100644
--- a/test/handlers/ws_set_options_commands_h.erl
+++ b/test/handlers/ws_set_options_commands_h.erl
@@ -11,10 +11,21 @@ init(Req, RunOrHibernate) ->
{cowboy_websocket, Req, RunOrHibernate,
#{idle_timeout => infinity}}.
-websocket_handle(Frame={text, <<"idle_timeout_short">>}, State=run) ->
- {[{set_options, #{idle_timeout => 500}}, Frame], State};
-websocket_handle(Frame={text, <<"idle_timeout_short">>}, State=hibernate) ->
- {[{set_options, #{idle_timeout => 500}}, Frame], State, hibernate}.
+%% Set the idle_timeout option dynamically.
+websocket_handle({text, <<"idle_timeout_short">>}, State=run) ->
+ {[{set_options, #{idle_timeout => 500}}], State};
+websocket_handle({text, <<"idle_timeout_short">>}, State=hibernate) ->
+ {[{set_options, #{idle_timeout => 500}}], State, hibernate};
+%% Set the max_frame_size option dynamically.
+websocket_handle({text, <<"max_frame_size_small">>}, State=run) ->
+ {[{set_options, #{max_frame_size => 1000}}], State};
+websocket_handle({text, <<"max_frame_size_small">>}, State=hibernate) ->
+ {[{set_options, #{max_frame_size => 1000}}], State, hibernate};
+%% We just echo binary frames.
+websocket_handle(Frame={binary, _}, State=run) ->
+ {[Frame], State};
+websocket_handle(Frame={binary, _}, State=hibernate) ->
+ {[Frame], State, hibernate}.
websocket_info(_Info, State) ->
{[], State}.
diff --git a/test/handlers/wt_echo_h.erl b/test/handlers/wt_echo_h.erl
new file mode 100644
index 0000000..5198565
--- /dev/null
+++ b/test/handlers/wt_echo_h.erl
@@ -0,0 +1,103 @@
+%% This module echoes client events back,
+%% including creating new streams.
+
+-module(wt_echo_h).
+-behavior(cowboy_webtransport).
+
+-export([init/2]).
+-export([webtransport_handle/2]).
+-export([webtransport_info/2]).
+-export([terminate/3]).
+
+%% -define(DEBUG, 1).
+-ifdef(DEBUG).
+-define(LOG(Fmt, Args), ct:pal(Fmt, Args)).
+-else.
+-define(LOG(Fmt, Args), _ = Fmt, _ = Args, ok).
+-endif.
+
+init(Req0, _) ->
+ ?LOG("WT init ~p~n", [Req0]),
+ Req = case cowboy_req:parse_header(<<"wt-available-protocols">>, Req0) of
+ undefined ->
+ Req0;
+ [Protocol|_] ->
+ cowboy_req:set_resp_header(<<"wt-protocol">>, cow_http_hd:wt_protocol(Protocol), Req0)
+ end,
+ {cowboy_webtransport, Req, #{}}.
+
+webtransport_handle(Event = {stream_open, StreamID, bidi}, Streams) ->
+ ?LOG("WT handle ~p~n", [Event]),
+ {[], Streams#{StreamID => bidi}};
+webtransport_handle(Event = {stream_open, StreamID, unidi}, Streams) ->
+ ?LOG("WT handle ~p~n", [Event]),
+ OpenStreamRef = make_ref(),
+ {[{open_stream, OpenStreamRef, unidi, <<>>}], Streams#{
+ StreamID => {unidi_remote, OpenStreamRef},
+ OpenStreamRef => {unidi_local, StreamID}}};
+webtransport_handle(Event = {opened_stream_id, OpenStreamRef, OpenStreamID}, Streams) ->
+ ?LOG("WT handle ~p~n", [Event]),
+ case Streams of
+ #{OpenStreamRef := bidi} ->
+ {[], maps:remove(OpenStreamRef, Streams#{
+ OpenStreamID => bidi
+ })};
+ #{OpenStreamRef := {unidi_local, RemoteStreamID}} ->
+ #{RemoteStreamID := {unidi_remote, OpenStreamRef}} = Streams,
+ {[], maps:remove(OpenStreamRef, Streams#{
+ RemoteStreamID => {unidi_remote, OpenStreamID},
+ OpenStreamID => {unidi_local, RemoteStreamID}
+ })}
+ end;
+webtransport_handle(Event = {stream_data, StreamID, _IsFin, <<"TEST:", Test/bits>>}, Streams) ->
+ ?LOG("WT handle ~p~n", [Event]),
+ case Test of
+ <<"open_bidi">> ->
+ OpenStreamRef = make_ref(),
+ {[{open_stream, OpenStreamRef, bidi, <<>>}],
+ Streams#{OpenStreamRef => bidi}};
+ <<"initiate_close">> ->
+ {[initiate_close], Streams};
+ <<"close">> ->
+ {[close], Streams};
+ <<"close_app_code">> ->
+ {[{close, 1234567890}], Streams};
+ <<"close_app_code_msg">> ->
+ {[{close, 1234567890, <<"onetwothreefourfivesixseveneightnineten">>}], Streams};
+ <<"event_pid:", EventPidBin/bits>> ->
+ {[{send, StreamID, nofin, <<"event_pid_received">>}],
+ Streams#{event_pid => binary_to_term(EventPidBin)}}
+ end;
+webtransport_handle(Event = {stream_data, StreamID, IsFin, Data}, Streams) ->
+ ?LOG("WT handle ~p~n", [Event]),
+ case Streams of
+ #{StreamID := bidi} ->
+ {[{send, StreamID, IsFin, Data}], Streams};
+ #{StreamID := {unidi_remote, Ref}} when is_reference(Ref) ->
+ %% The stream isn't ready. We try again later.
+ erlang:send_after(100, self(), {try_again, Event}),
+ {[], Streams};
+ #{StreamID := {unidi_remote, LocalStreamID}} ->
+ {[{send, LocalStreamID, IsFin, Data}], Streams}
+ end;
+webtransport_handle(Event = {datagram, Data}, Streams) ->
+ ?LOG("WT handle ~p~n", [Event]),
+ {[{send, datagram, Data}], Streams};
+webtransport_handle(Event = close_initiated, Streams) ->
+ ?LOG("WT handle ~p~n", [Event]),
+ {[{send, datagram, <<"TEST:close_initiated">>}], Streams};
+webtransport_handle(Event, Streams) ->
+ ?LOG("WT handle ignore ~p~n", [Event]),
+ {[], Streams}.
+
+webtransport_info({try_again, Event}, Streams) ->
+ ?LOG("WT try_again ~p", [Event]),
+ webtransport_handle(Event, Streams).
+
+terminate(Reason, Req, State=#{event_pid := EventPid}) ->
+ ?LOG("WT terminate ~0p~n~0p~n~0p", [Reason, Req, State]),
+ EventPid ! {'$wt_echo_h', terminate, Reason, Req, State},
+ ok;
+terminate(Reason, Req, State) ->
+ ?LOG("WT terminate ~0p~n~0p~n~0p", [Reason, Req, State]),
+ ok.