diff options
Diffstat (limited to 'test/handlers')
-rw-r--r-- | test/handlers/compress_h.erl | 5 | ||||
-rw-r--r-- | test/handlers/content_types_provided_h.erl | 5 | ||||
-rw-r--r-- | test/handlers/crash_h.erl | 3 | ||||
-rw-r--r-- | test/handlers/create_resource_h.erl | 28 | ||||
-rw-r--r-- | test/handlers/decompress_h.erl | 84 | ||||
-rw-r--r-- | test/handlers/echo_h.erl | 22 | ||||
-rw-r--r-- | test/handlers/generate_etag_h.erl | 3 | ||||
-rw-r--r-- | test/handlers/loop_handler_endless_h.erl | 25 | ||||
-rw-r--r-- | test/handlers/loop_handler_timeout_hibernate_h.erl | 30 | ||||
-rw-r--r-- | test/handlers/loop_handler_timeout_info_h.erl | 23 | ||||
-rw-r--r-- | test/handlers/loop_handler_timeout_init_h.erl | 23 | ||||
-rw-r--r-- | test/handlers/read_body_h.erl | 15 | ||||
-rw-r--r-- | test/handlers/resp_h.erl | 73 | ||||
-rw-r--r-- | test/handlers/stream_handler_h.erl | 8 | ||||
-rw-r--r-- | test/handlers/stream_hello_h.erl | 15 | ||||
-rw-r--r-- | test/handlers/streamed_result_h.erl | 20 | ||||
-rw-r--r-- | test/handlers/ws_ignore.erl | 20 | ||||
-rw-r--r-- | test/handlers/ws_init_h.erl | 5 | ||||
-rw-r--r-- | test/handlers/ws_ping_h.erl | 23 | ||||
-rw-r--r-- | test/handlers/ws_set_options_commands_h.erl | 19 | ||||
-rw-r--r-- | test/handlers/wt_echo_h.erl | 103 |
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. |