aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Makefile2
-rw-r--r--src/cowboy_spdy.erl47
-rw-r--r--test/cowboy_ct_hook.erl2
-rw-r--r--test/cowboy_error_h.erl65
-rw-r--r--test/cowboy_test.erl76
-rw-r--r--test/handlers/long_polling_h.erl27
-rw-r--r--test/handlers/loop_handler_body_h.erl24
-rw-r--r--test/handlers/loop_handler_timeout_h.erl23
-rw-r--r--test/http_SUITE.erl17
-rw-r--r--test/http_SUITE_data/http_long_polling.erl24
-rw-r--r--test/http_SUITE_data/http_loop_recv.erl18
-rw-r--r--test/http_SUITE_data/http_loop_timeout.erl16
-rw-r--r--test/loop_handler_SUITE.erl87
13 files changed, 334 insertions, 94 deletions
diff --git a/Makefile b/Makefile
index d225bed..2116f34 100644
--- a/Makefile
+++ b/Makefile
@@ -7,7 +7,7 @@ PROJECT = cowboy
ERLC_OPTS ?= -Werror +debug_info +warn_export_all +warn_export_vars \
+warn_shadow_vars +warn_obsolete_guard +warn_missing_spec
COMPILE_FIRST = cowboy_middleware cowboy_sub_protocol
-CT_SUITES = eunit http spdy ws
+CT_SUITES = eunit http loop_handler spdy ws
CT_OPTS += -pa test -ct_hooks cowboy_ct_hook []
PLT_APPS = crypto public_key ssl
diff --git a/src/cowboy_spdy.erl b/src/cowboy_spdy.erl
index e5aeb21..ce75419 100644
--- a/src/cowboy_spdy.erl
+++ b/src/cowboy_spdy.erl
@@ -33,9 +33,11 @@
%% Internal transport functions.
-export([name/0]).
+-export([messages/0]).
-export([recv/3]).
-export([send/2]).
-export([sendfile/2]).
+-export([setopts/2]).
-type streamid() :: non_neg_integer().
-type socket() :: {pid(), streamid()}.
@@ -45,8 +47,8 @@
pid :: pid(),
input = nofin :: fin | nofin,
in_buffer = <<>> :: binary(),
- is_recv = false :: {true, {non_neg_integer(), pid()},
- pid(), non_neg_integer(), reference()} | false,
+ is_recv = false :: false | {active, socket(), pid()}
+ | {passive, socket(), pid(), non_neg_integer(), reference()},
output = nofin :: fin | nofin
}).
@@ -138,15 +140,15 @@ loop(State=#state{parent=Parent, socket=Socket, transport=Transport,
TRef = erlang:send_after(Timeout, self(),
{recv_timeout, FromSocket}),
loop(replace_child(Child#child{
- is_recv={true, FromSocket, FromPid, Length, TRef}},
+ is_recv={passive, FromSocket, FromPid, Length, TRef}},
State))
end;
{recv_timeout, {Pid, StreamID}}
when Pid =:= self() ->
- Child = #child{is_recv={true, FromSocket, FromPid, _, _}}
+ Child = #child{is_recv={passive, FromSocket, FromPid, _, _}}
= get_child(StreamID, State),
FromPid ! {recv, FromSocket, {error, timeout}},
- loop(replace_child(Child#child{is_recv=false}, State));
+ loop(replace_child(Child#child{is_recv=passive}, State));
{reply, {Pid, StreamID}, Status, Headers}
when Pid =:= self() ->
Child = #child{output=nofin} = get_child(StreamID, State),
@@ -178,6 +180,22 @@ loop(State=#state{parent=Parent, socket=Socket, transport=Transport,
Child = #child{output=nofin} = get_child(StreamID, State),
data_from_file(State, StreamID, Filepath),
loop(replace_child(Child#child{output=fin}, State));
+ {active, FromSocket = {Pid, StreamID}, FromPid} when Pid =:= self() ->
+ Child = #child{in_buffer=InBuffer, is_recv=false}
+ = get_child(StreamID, State),
+ case InBuffer of
+ <<>> ->
+ loop(replace_child(Child#child{
+ is_recv={active, FromSocket, FromPid}}, State));
+ _ ->
+ FromPid ! {spdy, FromSocket, InBuffer},
+ loop(replace_child(Child#child{in_buffer= <<>>}, State))
+ end;
+ {passive, FromSocket = {Pid, StreamID}, FromPid} when Pid =:= self() ->
+ Child = #child{is_recv=IsRecv} = get_child(StreamID, State),
+ %% Make sure we aren't in the middle of a recv call.
+ case IsRecv of false -> ok; {active, FromSocket, FromPid} -> ok end,
+ loop(replace_child(Child#child{is_recv=false}, State));
{'EXIT', Parent, Reason} ->
exit(Reason);
{'EXIT', Pid, _} ->
@@ -262,11 +280,14 @@ handle_frame(State, {data, StreamID, IsFin, Data}) ->
Data2 = << Buffer/binary, Data/binary >>,
IsFin2 = if IsFin -> fin; true -> nofin end,
Child2 = case IsRecv of
- {true, FromSocket, FromPid, 0, TRef} ->
+ {active, FromSocket, FromPid} ->
+ FromPid ! {spdy, FromSocket, Data},
+ Child#child{input=IsFin2, is_recv=false};
+ {passive, FromSocket, FromPid, 0, TRef} ->
FromPid ! {recv, FromSocket, {ok, Data2}},
cancel_recv_timeout(StreamID, TRef),
Child#child{input=IsFin2, in_buffer= <<>>, is_recv=false};
- {true, FromSocket, FromPid, Length, TRef}
+ {passive, FromSocket, FromPid, Length, TRef}
when byte_size(Data2) >= Length ->
<< Data3:Length/binary, Rest/binary >> = Data2,
FromPid ! {recv, FromSocket, {ok, Data3}},
@@ -443,6 +464,10 @@ stream_close(Socket = {Pid, _}) ->
name() ->
spdy.
+-spec messages() -> {spdy, spdy_closed, spdy_error}.
+messages() ->
+ {spdy, spdy_closed, spdy_error}.
+
-spec recv(socket(), non_neg_integer(), timeout())
-> {ok, binary()} | {error, timeout}.
recv(Socket = {Pid, _}, Length, Timeout) ->
@@ -463,3 +488,11 @@ send(Socket, Data) ->
sendfile(Socket = {Pid, _}, Filepath) ->
_ = Pid ! {sendfile, Socket, Filepath},
{ok, undefined}.
+
+-spec setopts(inet:socket(), list()) -> ok.
+setopts(Socket = {Pid, _}, [{active, once}]) ->
+ _ = Pid ! {active, Socket, self()},
+ ok;
+setopts(Socket = {Pid, _}, [{active, false}]) ->
+ _ = Pid ! {passive, Socket, self()},
+ ok.
diff --git a/test/cowboy_ct_hook.erl b/test/cowboy_ct_hook.erl
index 89f480d..1586412 100644
--- a/test/cowboy_ct_hook.erl
+++ b/test/cowboy_ct_hook.erl
@@ -18,6 +18,6 @@
init(_, _) ->
cowboy_test:start([cowboy, gun]),
- error_logger:tty(false),
+ cowboy_test:make_certs(),
error_logger:add_report_handler(cowboy_error_h),
{ok, undefined}.
diff --git a/test/cowboy_error_h.erl b/test/cowboy_error_h.erl
index fe79645..b4ae78f 100644
--- a/test/cowboy_error_h.erl
+++ b/test/cowboy_error_h.erl
@@ -30,11 +30,12 @@
%% Ignore crashes from Pid occuring in M:F/A.
ignore(M, F, A) ->
- gen_event:call(error_logger, ?MODULE, {expect, self(), M, F, A}).
+ gen_event:call(error_logger, ?MODULE, {expect, {self(), M, F, A}}).
%% gen_event.
init(_) ->
+ spawn(fun() -> error_logger:tty(false) end),
{ok, []}.
%% Ignore supervisor and progress reports.
@@ -50,9 +51,41 @@ handle_event({error_report, _, {_, crash_report,
{error_info, {error, gone, _}}|_]|_]}},
State) ->
{ok, State};
-%% Ignore emulator reports, they are a duplicate of what Ranch gives us.
-handle_event({error, _, {emulator, _, _}}, State) ->
- {ok, State};
+%% Ignore emulator reports that are a duplicate of what Ranch gives us.
+%%
+%% The emulator always sends strings for errors, which makes it very
+%% difficult to extract the information we need, hence the regexps.
+handle_event(Event = {error, GL, {emulator, _, Msg}}, State)
+ when node(GL) =:= node() ->
+ Result = re:run(Msg,
+ "Error in process ([^\s]+).+? with exit value: "
+ ".+?{stacktrace,\\[{([^,]+),([^,]+),(.+)",
+ [{capture, all_but_first, list}]),
+ case Result of
+ nomatch ->
+ write_event(Event),
+ {ok, State};
+ {match, [PidStr, MStr, FStr, Rest]} ->
+ A = case Rest of
+ "[]" ++ _ ->
+ 0;
+ "[" ++ Rest2 ->
+ count_args(Rest2, 1, 0);
+ _ ->
+ {match, [AStr]} = re:run(Rest, "([^,]+).+",
+ [{capture, all_but_first, list}]),
+ list_to_integer(AStr)
+ end,
+ Crash = {list_to_pid(PidStr), list_to_existing_atom(MStr),
+ list_to_existing_atom(FStr), A},
+ case lists:member(Crash, State) of
+ true ->
+ {ok, lists:delete(Crash, State)};
+ false ->
+ write_event(Event),
+ {ok, State}
+ end
+ end;
handle_event(Event = {error, GL,
{_, "Ranch listener" ++ _, [_, _, Pid, {[_, _,
{stacktrace, [{M, F, A, _}|_]}|_], _}]}},
@@ -72,8 +105,8 @@ handle_event(Event = {_, GL, _}, State) when node(GL) =:= node() ->
handle_event(_, State) ->
{ok, State}.
-handle_call({expect, Pid, M, F, A}, State) ->
- {ok, ok, [{Pid, M, F, A}|State]};
+handle_call({expect, Crash}, State) ->
+ {ok, ok, [Crash, Crash|State]};
handle_call(_, State) ->
{ok, {error, bad_query}, State}.
@@ -81,12 +114,32 @@ handle_info(_, State) ->
{ok, State}.
terminate(_, _) ->
+ spawn(fun() -> error_logger:tty(true) end),
ok.
code_change(_, State, _) ->
{ok, State}.
+%% Internal.
+
write_event(Event) ->
error_logger_tty_h:write_event(
{erlang:universaltime(), Event},
io).
+
+count_args("]" ++ _, N, 0) ->
+ N;
+count_args("]" ++ Tail, N, Levels) ->
+ count_args(Tail, N, Levels - 1);
+count_args("[" ++ Tail, N, Levels) ->
+ count_args(Tail, N, Levels + 1);
+count_args("}" ++ Tail, N, Levels) ->
+ count_args(Tail, N, Levels - 1);
+count_args("{" ++ Tail, N, Levels) ->
+ count_args(Tail, N, Levels + 1);
+count_args("," ++ Tail, N, Levels = 0) ->
+ count_args(Tail, N + 1, Levels);
+count_args("," ++ Tail, N, Levels) ->
+ count_args(Tail, N, Levels);
+count_args([_|Tail], N, Levels) ->
+ count_args(Tail, N, Levels).
diff --git a/test/cowboy_test.erl b/test/cowboy_test.erl
index 4cb2a33..f4a5706 100644
--- a/test/cowboy_test.erl
+++ b/test/cowboy_test.erl
@@ -30,12 +30,32 @@ do_start(App) ->
do_start(App)
end.
+%% SSL certificate creation and safekeeping.
+
+make_certs() ->
+ {_, Cert, Key} = ct_helper:make_certs(),
+ CertOpts = [{cert, Cert}, {key, Key}],
+ Pid = spawn(fun() -> receive after infinity -> ok end end),
+ ?MODULE = ets:new(?MODULE, [ordered_set, public, named_table,
+ {heir, Pid, undefined}]),
+ ets:insert(?MODULE, {cert_opts, CertOpts}),
+ ok.
+
+get_certs() ->
+ ets:lookup_element(?MODULE, cert_opts, 2).
+
%% Quick configuration value retrieval.
config(Key, Config) ->
{_, Value} = lists:keyfind(Key, 1, Config),
Value.
+%% Test case description.
+
+doc(String) ->
+ ct:comment(String),
+ ct:log(String).
+
%% List of all test cases in the suite.
all(Suite) ->
@@ -60,8 +80,7 @@ init_http(Ref, ProtoOpts, Config) ->
[{type, tcp}, {port, Port}, {opts, []}|Config].
init_https(Ref, ProtoOpts, Config) ->
- {_, Cert, Key} = ct_helper:make_certs(),
- Opts = [{cert, Cert}, {key, Key}],
+ Opts = get_certs(),
{ok, _} = cowboy:start_https(Ref, 100, Opts ++ [{port, 0}], [
{max_keepalive, 50},
{timeout, 500}
@@ -70,13 +89,62 @@ init_https(Ref, ProtoOpts, Config) ->
[{type, ssl}, {port, Port}, {opts, Opts}|Config].
init_spdy(Ref, ProtoOpts, Config) ->
- {_, Cert, Key} = ct_helper:make_certs(),
- Opts = [{cert, Cert}, {key, Key}],
+ Opts = get_certs(),
{ok, _} = cowboy:start_spdy(Ref, 100, Opts ++ [{port, 0}],
ProtoOpts),
Port = ranch:get_port(Ref),
[{type, ssl}, {port, Port}, {opts, Opts}|Config].
+%% Common group of listeners used by most suites.
+
+common_all() ->
+ [
+ {group, http},
+ {group, https},
+ {group, spdy},
+ {group, http_compress},
+ {group, https_compress},
+ {group, spdy_compress}
+ ].
+
+common_groups(Tests) ->
+ [
+ {http, [parallel], Tests},
+ {https, [parallel], Tests},
+ {spdy, [parallel], Tests},
+ {http_compress, [parallel], Tests},
+ {https_compress, [parallel], Tests},
+ {spdy_compress, [parallel], Tests}
+ ].
+
+init_common_groups(Name = http, Config, Mod) ->
+ init_http(Name, [
+ {env, [{dispatch, Mod:init_dispatch(Config)}]}
+ ], Config);
+init_common_groups(Name = https, Config, Mod) ->
+ init_https(Name, [
+ {env, [{dispatch, Mod:init_dispatch(Config)}]}
+ ], Config);
+init_common_groups(Name = spdy, Config, Mod) ->
+ init_spdy(Name, [
+ {env, [{dispatch, Mod:init_dispatch(Config)}]}
+ ], Config);
+init_common_groups(Name = http_compress, Config, Mod) ->
+ init_http(Name, [
+ {env, [{dispatch, Mod:init_dispatch(Config)}]},
+ {compress, true}
+ ], Config);
+init_common_groups(Name = https_compress, Config, Mod) ->
+ init_https(Name, [
+ {env, [{dispatch, Mod:init_dispatch(Config)}]},
+ {compress, true}
+ ], Config);
+init_common_groups(Name = spdy_compress, Config, Mod) ->
+ init_spdy(Name, [
+ {env, [{dispatch, Mod:init_dispatch(Config)}]},
+ {compress, true}
+ ], Config).
+
%% Support functions for testing using Gun.
gun_open(Config) ->
diff --git a/test/handlers/long_polling_h.erl b/test/handlers/long_polling_h.erl
new file mode 100644
index 0000000..21f1d4d
--- /dev/null
+++ b/test/handlers/long_polling_h.erl
@@ -0,0 +1,27 @@
+%% This module implements a loop handler for long-polling.
+%% It starts by sending itself a message after 200ms,
+%% then sends another after that for a total of 3 messages.
+%% When it receives the last message, it sends a 102 reply back.
+
+-module(long_polling_h).
+-behaviour(cowboy_loop_handler).
+
+-export([init/3]).
+-export([info/3]).
+-export([terminate/3]).
+
+init(_, Req, _) ->
+ erlang:send_after(200, self(), timeout),
+ {loop, Req, 2, 5000, hibernate}.
+
+info(timeout, Req, 0) ->
+ {ok, Req2} = cowboy_req:reply(102, Req),
+ {ok, Req2, 0};
+info(timeout, Req, Count) ->
+ erlang:send_after(200, self(), timeout),
+ {loop, Req, Count - 1, hibernate}.
+
+terminate({normal, shutdown}, _, 0) ->
+ ok;
+terminate({error, overflow}, _, _) ->
+ ok.
diff --git a/test/handlers/loop_handler_body_h.erl b/test/handlers/loop_handler_body_h.erl
new file mode 100644
index 0000000..db69b02
--- /dev/null
+++ b/test/handlers/loop_handler_body_h.erl
@@ -0,0 +1,24 @@
+%% This module implements a loop handler that reads
+%% the request body after sending itself a message,
+%% checks that its size is exactly 100000 bytes,
+%% then sends a 200 reply back.
+
+-module(loop_handler_body_h).
+-behaviour(cowboy_loop_handler).
+
+-export([init/3]).
+-export([info/3]).
+-export([terminate/3]).
+
+init(_, Req, _) ->
+ self() ! timeout,
+ {loop, Req, undefined, 5000, hibernate}.
+
+info(timeout, Req, State) ->
+ {ok, Body, Req2} = cowboy_req:body(Req),
+ 100000 = byte_size(Body),
+ {ok, Req3} = cowboy_req:reply(200, Req2),
+ {ok, Req3, State}.
+
+terminate({normal, shutdown}, _, _) ->
+ ok.
diff --git a/test/handlers/loop_handler_timeout_h.erl b/test/handlers/loop_handler_timeout_h.erl
new file mode 100644
index 0000000..1125046
--- /dev/null
+++ b/test/handlers/loop_handler_timeout_h.erl
@@ -0,0 +1,23 @@
+%% This module implements a loop handler that sends
+%% itself a timeout that will intentionally arrive
+%% too late, as it configures itself to only wait
+%% 200ms before closing the connection in init/3.
+%% This results in a 204 reply being sent back by Cowboy.
+
+-module(loop_handler_timeout_h).
+-behaviour(cowboy_loop_handler).
+
+-export([init/3]).
+-export([info/3]).
+-export([terminate/3]).
+
+init(_, Req, _) ->
+ erlang:send_after(1000, self(), timeout),
+ {loop, Req, undefined, 200, hibernate}.
+
+info(timeout, Req, State) ->
+ {ok, Req2} = cowboy_req:reply(500, Req),
+ {ok, Req2, State}.
+
+terminate({normal, timeout}, _, _) ->
+ ok.
diff --git a/test/http_SUITE.erl b/test/http_SUITE.erl
index 1c89b1a..4e4c058 100644
--- a/test/http_SUITE.erl
+++ b/test/http_SUITE.erl
@@ -158,7 +158,6 @@ init_dispatch(Config) ->
{"/chunked_response", http_chunked, []},
{"/streamed_response", http_streamed, []},
{"/init_shutdown", http_init_shutdown, []},
- {"/long_polling", http_long_polling, []},
{"/headers/dupe", http_handler,
[{headers, [{<<"connection">>, <<"close">>}]}]},
{"/set_resp/header", http_set_resp,
@@ -209,9 +208,7 @@ init_dispatch(Config) ->
{"/resetags", rest_resource_etags, []},
{"/rest_expires", rest_expires, []},
{"/rest_empty_resource", rest_empty_resource, []},
- {"/loop_recv", http_loop_recv, []},
{"/loop_stream_recv", http_loop_stream_recv, []},
- {"/loop_timeout", http_loop_timeout, []},
{"/", http_handler, []}
]}
]).
@@ -266,14 +263,10 @@ The document has moved
<A HREF=\"http://www.google.co.il/\">here</A>.
</BODY></HTML>",
Tests = [
- {102, <<"GET /long_polling HTTP/1.1\r\nHost: localhost\r\n"
- "Content-Length: 5000\r\n\r\n", 0:5000/unit:8 >>},
{200, ["GET / HTTP/1.0\r\nHost: localhost\r\n"
"Set-Cookie: ", HugeCookie, "\r\n\r\n"]},
{200, "\r\n\r\n\r\n\r\n\r\nGET / HTTP/1.1\r\nHost: localhost\r\n\r\n"},
{200, "GET http://proxy/ HTTP/1.1\r\nHost: localhost\r\n\r\n"},
- {200, <<"POST /loop_recv HTTP/1.1\r\nHost: localhost\r\n"
- "Content-Length: 100000\r\n\r\n", 0:100000/unit:8 >>},
{400, "\n"},
{400, "Garbage\r\n\r\n"},
{400, "\r\n\r\n\r\n\r\n\r\n\r\n"},
@@ -287,8 +280,6 @@ The document has moved
{408, "GET / HTTP/1.1\r\nHost: localhost\r\n\r"},
{414, Huge},
{400, "GET / HTTP/1.1\r\n" ++ Huge},
- {500, <<"GET /long_polling HTTP/1.1\r\nHost: localhost\r\n"
- "Content-Length: 100000\r\n\r\n", 0:100000/unit:8 >>},
{505, "GET / HTTP/1.2\r\nHost: localhost\r\n\r\n"},
{closed, ""},
{closed, "\r\n"},
@@ -303,10 +294,8 @@ The document has moved
check_status(Config) ->
Tests = [
- {102, "/long_polling"},
{200, "/"},
{200, "/simple"},
- {204, "/loop_timeout"},
{400, "/static/%2f"},
{400, "/static/%2e"},
{400, "/static/%2e%2e"},
@@ -618,12 +607,6 @@ pipeline(Config) ->
_ = [{response, nofin, 200, _} = gun:await(ConnPid, Ref) || Ref <- Refs],
ok.
-pipeline_long_polling(Config) ->
- ConnPid = gun_open(Config),
- Refs = [gun:get(ConnPid, "/long_polling") || _ <- lists:seq(1, 2)],
- _ = [{response, fin, 102, _} = gun:await(ConnPid, Ref) || Ref <- Refs],
- ok.
-
rest_param_all(Config) ->
ConnPid = gun_open(Config),
%% Accept without param.
diff --git a/test/http_SUITE_data/http_long_polling.erl b/test/http_SUITE_data/http_long_polling.erl
deleted file mode 100644
index ad4e66e..0000000
--- a/test/http_SUITE_data/http_long_polling.erl
+++ /dev/null
@@ -1,24 +0,0 @@
-%% Feel free to use, reuse and abuse the code in this file.
-
--module(http_long_polling).
--behaviour(cowboy_http_handler).
--export([init/3, handle/2, info/3, terminate/3]).
-
-init({_Transport, http}, Req, _Opts) ->
- erlang:send_after(500, self(), timeout),
- {loop, Req, 5, 5000, hibernate}.
-
-handle(_Req, _State) ->
- exit(badarg).
-
-info(timeout, Req, 0) ->
- {ok, Req2} = cowboy_req:reply(102, Req),
- {ok, Req2, 0};
-info(timeout, Req, State) ->
- erlang:send_after(500, self(), timeout),
- {loop, Req, State - 1, hibernate}.
-
-terminate({normal, shutdown}, _, _) ->
- ok;
-terminate({error, overflow}, _, _) ->
- ok.
diff --git a/test/http_SUITE_data/http_loop_recv.erl b/test/http_SUITE_data/http_loop_recv.erl
deleted file mode 100644
index d0577f0..0000000
--- a/test/http_SUITE_data/http_loop_recv.erl
+++ /dev/null
@@ -1,18 +0,0 @@
-%% Feel free to use, reuse and abuse the code in this file.
-
--module(http_loop_recv).
--behaviour(cowboy_loop_handler).
--export([init/3, info/3, terminate/3]).
-
-init({_, http}, Req, _) ->
- self() ! recv_timeout,
- {loop, Req, undefined, 500, hibernate}.
-
-info(recv_timeout, Req, State) ->
- {ok, Body, Req1} = cowboy_req:body(Req),
- 100000 = byte_size(Body),
- {ok, Req2} = cowboy_req:reply(200, Req1),
- {ok, Req2, State}.
-
-terminate({normal, shutdown}, _, _) ->
- ok.
diff --git a/test/http_SUITE_data/http_loop_timeout.erl b/test/http_SUITE_data/http_loop_timeout.erl
deleted file mode 100644
index dd3472c..0000000
--- a/test/http_SUITE_data/http_loop_timeout.erl
+++ /dev/null
@@ -1,16 +0,0 @@
-%% Feel free to use, reuse and abuse the code in this file.
-
--module(http_loop_timeout).
--behaviour(cowboy_loop_handler).
--export([init/3, info/3, terminate/3]).
-
-init({_, http}, Req, _) ->
- erlang:send_after(1000, self(), error_timeout),
- {loop, Req, undefined, 500, hibernate}.
-
-info(error_timeout, Req, State) ->
- {ok, Req2} = cowboy_req:reply(500, Req),
- {ok, Req2, State}.
-
-terminate({normal, timeout}, _, _) ->
- ok.
diff --git a/test/loop_handler_SUITE.erl b/test/loop_handler_SUITE.erl
new file mode 100644
index 0000000..5f69490
--- /dev/null
+++ b/test/loop_handler_SUITE.erl
@@ -0,0 +1,87 @@
+%% Copyright (c) 2011-2014, Loïc Hoguin <[email protected]>
+%%
+%% Permission to use, copy, modify, and/or distribute this software for any
+%% purpose with or without fee is hereby granted, provided that the above
+%% copyright notice and this permission notice appear in all copies.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+-module(loop_handler_SUITE).
+-compile(export_all).
+
+-import(cowboy_test, [config/2]).
+-import(cowboy_test, [doc/1]).
+-import(cowboy_test, [gun_open/1]).
+
+%% ct.
+
+all() ->
+ cowboy_test:common_all().
+
+groups() ->
+ cowboy_test:common_groups(cowboy_test:all(?MODULE)).
+
+init_per_group(Name, Config) ->
+ cowboy_test:init_common_groups(Name, Config, ?MODULE).
+
+end_per_group(Name, _) ->
+ cowboy:stop_listener(Name).
+
+%% Dispatch configuration.
+
+init_dispatch(_) ->
+ cowboy_router:compile([{'_', [
+ {"/long_polling", long_polling_h, []},
+ {"/loop_body", loop_handler_body_h, []},
+ {"/loop_timeout", loop_handler_timeout_h, []}
+ ]}]).
+
+%% Tests.
+
+long_polling(Config) ->
+ doc("Simple long-polling."),
+ ConnPid = gun_open(Config),
+ Ref = gun:get(ConnPid, "/long_polling"),
+ {response, fin, 102, _} = gun:await(ConnPid, Ref),
+ ok.
+
+long_polling_body(Config) ->
+ doc("Long-polling with a body that falls within the configurable limits."),
+ ConnPid = gun_open(Config),
+ Ref = gun:post(ConnPid, "/long_polling", [], << 0:5000/unit:8 >>),
+ {response, fin, 102, _} = gun:await(ConnPid, Ref),
+ ok.
+
+long_polling_body_too_large(Config) ->
+ doc("Long-polling with a body that exceeds the configurable limits."),
+ ConnPid = gun_open(Config),
+ Ref = gun:post(ConnPid, "/long_polling", [], << 0:100000/unit:8 >>),
+ {response, fin, 500, _} = gun:await(ConnPid, Ref),
+ ok.
+
+long_polling_pipeline(Config) ->
+ doc("Pipeline of long-polling calls."),
+ ConnPid = gun_open(Config),
+ Refs = [gun:get(ConnPid, "/long_polling") || _ <- lists:seq(1, 2)],
+ _ = [{response, fin, 102, _} = gun:await(ConnPid, Ref) || Ref <- Refs],
+ ok.
+
+loop_body(Config) ->
+ doc("Check that a loop handler can read the request body in info/3."),
+ ConnPid = gun_open(Config),
+ Ref = gun:post(ConnPid, "/loop_body", [], << 0:100000/unit:8 >>),
+ {response, fin, 200, _} = gun:await(ConnPid, Ref),
+ ok.
+
+loop_timeout(Config) ->
+ doc("Ensure that the loop handler timeout results in a 204 response."),
+ ConnPid = gun_open(Config),
+ Ref = gun:get(ConnPid, "/loop_timeout"),
+ {response, fin, 204, _} = gun:await(ConnPid, Ref),
+ ok.