0
Fork 0
mirror of https://github.com/ninenines/cowboy.git synced 2025-07-14 20:30:23 +00:00

Add {switch_handler, Module} return value to cowboy_rest

Also {switch_handler, Module, Opts}.

Allows switching to a different handler type. This is
particularly useful for processing most of the request
with cowboy_rest and then streaming the response body
using cowboy_loop.
This commit is contained in:
Loïc Hoguin 2017-11-01 16:27:26 +00:00
parent 5e88a9b394
commit 836342abb8
No known key found for this signature in database
GPG key ID: 8A9DF795F6FED764
5 changed files with 191 additions and 11 deletions

View file

@ -43,8 +43,12 @@ you need.
All callbacks take two arguments, the Req object and the State, All callbacks take two arguments, the Req object and the State,
and return a three-element tuple of the form `{Value, Req, State}`. and return a three-element tuple of the form `{Value, Req, State}`.
All callbacks can also return `{stop, Req, State}` to stop execution Nearly all callbacks can also return `{stop, Req, State}` to
of the request. stop execution of the request, and
`{{switch_handler, Module}, Req, State}` or
`{{switch_handler, Module, Opts}, Req, State}` to switch to
a different handler type. The exceptions are `expires`
`generate_etag`, `last_modified` and `variances`.
The following table summarizes the callbacks and their default values. The following table summarizes the callbacks and their default values.
If the callback isn't defined, then the default value will be used. If the callback isn't defined, then the default value will be used.

View file

@ -25,11 +25,15 @@ init(Req, State)
Callback(Req, State) Callback(Req, State)
-> {Result, Req, State} -> {Result, Req, State}
| {stop, Req, State} | {stop, Req, State}
| {{switch_handler, Module}, Req, State}
| {{switch_handler, Module, Opts}, Req, State}
terminate(Reason, Req, State) -> ok %% optional terminate(Reason, Req, State) -> ok %% optional
Req :: cowboy_req:req() Req :: cowboy_req:req()
State :: any() State :: any()
Module :: module()
Opts :: any()
Reason :: normal Reason :: normal
| {crash, error | exit | throw, any()} | {crash, error | exit | throw, any()}
@ -51,7 +55,14 @@ implemented. They otherwise all follow the same interface.
The `stop` tuple can be returned to stop REST processing. The `stop` tuple can be returned to stop REST processing.
If no response was sent before then, Cowboy will send a If no response was sent before then, Cowboy will send a
'204 No Content'. '204 No Content'. The `stop` tuple can be returned from
any callback, excluding `expires`, `generate_etag`,
`last_modified` and `variances`.
A `switch_handler` tuple can be returned from these same
callbacks to stop REST processing and switch to a different
handler type. This is very useful to, for example, to stream
the response body.
The optional `terminate/3` callback will ultimately be called The optional `terminate/3` callback will ultimately be called
with the reason for the termination of the handler. with the reason for the termination of the handler.

View file

@ -20,6 +20,9 @@
-export([upgrade/4]). -export([upgrade/4]).
-export([upgrade/5]). -export([upgrade/5]).
-type switch_handler() :: {switch_handler, module()}
| {switch_handler, module(), any()}.
%% Common handler callbacks. %% Common handler callbacks.
-callback init(Req, any()) -callback init(Req, any())
@ -35,162 +38,181 @@
-callback allowed_methods(Req, State) -callback allowed_methods(Req, State)
-> {[binary()], Req, State} -> {[binary()], Req, State}
| {stop, Req, State} | {stop, Req, State}
| {switch_handler(), Req, State}
when Req::cowboy_req:req(), State::any(). when Req::cowboy_req:req(), State::any().
-optional_callbacks([allowed_methods/2]). -optional_callbacks([allowed_methods/2]).
-callback allow_missing_post(Req, State) -callback allow_missing_post(Req, State)
-> {boolean(), Req, State} -> {boolean(), Req, State}
| {stop, Req, State} | {stop, Req, State}
| {switch_handler(), Req, State}
when Req::cowboy_req:req(), State::any(). when Req::cowboy_req:req(), State::any().
-optional_callbacks([allow_missing_post/2]). -optional_callbacks([allow_missing_post/2]).
-callback charsets_provided(Req, State) -callback charsets_provided(Req, State)
-> {[binary()], Req, State} -> {[binary()], Req, State}
| {stop, Req, State} | {stop, Req, State}
| {switch_handler(), Req, State}
when Req::cowboy_req:req(), State::any(). when Req::cowboy_req:req(), State::any().
-optional_callbacks([charsets_provided/2]). -optional_callbacks([charsets_provided/2]).
-callback content_types_accepted(Req, State) -callback content_types_accepted(Req, State)
-> {[{binary() | {binary(), binary(), '*' | [{binary(), binary()}]}, atom()}], Req, State} -> {[{binary() | {binary(), binary(), '*' | [{binary(), binary()}]}, atom()}], Req, State}
| {stop, Req, State} | {stop, Req, State}
| {switch_handler(), Req, State}
when Req::cowboy_req:req(), State::any(). when Req::cowboy_req:req(), State::any().
-optional_callbacks([content_types_accepted/2]). -optional_callbacks([content_types_accepted/2]).
-callback content_types_provided(Req, State) -callback content_types_provided(Req, State)
-> {[{binary() | {binary(), binary(), '*' | [{binary(), binary()}]}, atom()}], Req, State} -> {[{binary() | {binary(), binary(), '*' | [{binary(), binary()}]}, atom()}], Req, State}
| {stop, Req, State} | {stop, Req, State}
| {switch_handler(), Req, State}
when Req::cowboy_req:req(), State::any(). when Req::cowboy_req:req(), State::any().
-optional_callbacks([content_types_provided/2]). -optional_callbacks([content_types_provided/2]).
-callback delete_completed(Req, State) -callback delete_completed(Req, State)
-> {boolean(), Req, State} -> {boolean(), Req, State}
| {stop, Req, State} | {stop, Req, State}
| {switch_handler(), Req, State}
when Req::cowboy_req:req(), State::any(). when Req::cowboy_req:req(), State::any().
-optional_callbacks([delete_completed/2]). -optional_callbacks([delete_completed/2]).
-callback delete_resource(Req, State) -callback delete_resource(Req, State)
-> {boolean(), Req, State} -> {boolean(), Req, State}
| {stop, Req, State} | {stop, Req, State}
| {switch_handler(), Req, State}
when Req::cowboy_req:req(), State::any(). when Req::cowboy_req:req(), State::any().
-optional_callbacks([delete_resource/2]). -optional_callbacks([delete_resource/2]).
-callback expires(Req, State) -callback expires(Req, State)
-> {calendar:datetime() | binary() | undefined, Req, State} -> {calendar:datetime() | binary() | undefined, Req, State}
| {stop, Req, State}
when Req::cowboy_req:req(), State::any(). when Req::cowboy_req:req(), State::any().
-optional_callbacks([expires/2]). -optional_callbacks([expires/2]).
-callback forbidden(Req, State) -callback forbidden(Req, State)
-> {boolean(), Req, State} -> {boolean(), Req, State}
| {stop, Req, State} | {stop, Req, State}
| {switch_handler(), Req, State}
when Req::cowboy_req:req(), State::any(). when Req::cowboy_req:req(), State::any().
-optional_callbacks([forbidden/2]). -optional_callbacks([forbidden/2]).
-callback generate_etag(Req, State) -callback generate_etag(Req, State)
-> {binary() | {weak | strong, binary()}, Req, State} -> {binary() | {weak | strong, binary()}, Req, State}
| {stop, Req, State}
when Req::cowboy_req:req(), State::any(). when Req::cowboy_req:req(), State::any().
-optional_callbacks([generate_etag/2]). -optional_callbacks([generate_etag/2]).
-callback is_authorized(Req, State) -callback is_authorized(Req, State)
-> {true | {false, iodata()}, Req, State} -> {true | {false, iodata()}, Req, State}
| {stop, Req, State} | {stop, Req, State}
| {switch_handler(), Req, State}
when Req::cowboy_req:req(), State::any(). when Req::cowboy_req:req(), State::any().
-optional_callbacks([is_authorized/2]). -optional_callbacks([is_authorized/2]).
-callback is_conflict(Req, State) -callback is_conflict(Req, State)
-> {boolean(), Req, State} -> {boolean(), Req, State}
| {stop, Req, State} | {stop, Req, State}
| {switch_handler(), Req, State}
when Req::cowboy_req:req(), State::any(). when Req::cowboy_req:req(), State::any().
-optional_callbacks([is_conflict/2]). -optional_callbacks([is_conflict/2]).
-callback known_methods(Req, State) -callback known_methods(Req, State)
-> {[binary()], Req, State} -> {[binary()], Req, State}
| {stop, Req, State} | {stop, Req, State}
| {switch_handler(), Req, State}
when Req::cowboy_req:req(), State::any(). when Req::cowboy_req:req(), State::any().
-optional_callbacks([known_methods/2]). -optional_callbacks([known_methods/2]).
-callback languages_provided(Req, State) -callback languages_provided(Req, State)
-> {[binary()], Req, State} -> {[binary()], Req, State}
| {stop, Req, State} | {stop, Req, State}
| {switch_handler(), Req, State}
when Req::cowboy_req:req(), State::any(). when Req::cowboy_req:req(), State::any().
-optional_callbacks([languages_provided/2]). -optional_callbacks([languages_provided/2]).
-callback last_modified(Req, State) -callback last_modified(Req, State)
-> {calendar:datetime(), Req, State} -> {calendar:datetime(), Req, State}
| {stop, Req, State}
when Req::cowboy_req:req(), State::any(). when Req::cowboy_req:req(), State::any().
-optional_callbacks([last_modified/2]). -optional_callbacks([last_modified/2]).
-callback malformed_request(Req, State) -callback malformed_request(Req, State)
-> {boolean(), Req, State} -> {boolean(), Req, State}
| {stop, Req, State} | {stop, Req, State}
| {switch_handler(), Req, State}
when Req::cowboy_req:req(), State::any(). when Req::cowboy_req:req(), State::any().
-optional_callbacks([malformed_request/2]). -optional_callbacks([malformed_request/2]).
-callback moved_permanently(Req, State) -callback moved_permanently(Req, State)
-> {{true, iodata()} | false, Req, State} -> {{true, iodata()} | false, Req, State}
| {stop, Req, State} | {stop, Req, State}
| {switch_handler(), Req, State}
when Req::cowboy_req:req(), State::any(). when Req::cowboy_req:req(), State::any().
-optional_callbacks([moved_permanently/2]). -optional_callbacks([moved_permanently/2]).
-callback moved_temporarily(Req, State) -callback moved_temporarily(Req, State)
-> {{true, iodata()} | false, Req, State} -> {{true, iodata()} | false, Req, State}
| {stop, Req, State} | {stop, Req, State}
| {switch_handler(), Req, State}
when Req::cowboy_req:req(), State::any(). when Req::cowboy_req:req(), State::any().
-optional_callbacks([moved_temporarily/2]). -optional_callbacks([moved_temporarily/2]).
-callback multiple_choices(Req, State) -callback multiple_choices(Req, State)
-> {boolean(), Req, State} -> {boolean(), Req, State}
| {stop, Req, State} | {stop, Req, State}
| {switch_handler(), Req, State}
when Req::cowboy_req:req(), State::any(). when Req::cowboy_req:req(), State::any().
-optional_callbacks([multiple_choices/2]). -optional_callbacks([multiple_choices/2]).
-callback options(Req, State) -callback options(Req, State)
-> {ok, Req, State} -> {ok, Req, State}
| {stop, Req, State} | {stop, Req, State}
| {switch_handler(), Req, State}
when Req::cowboy_req:req(), State::any(). when Req::cowboy_req:req(), State::any().
-optional_callbacks([options/2]). -optional_callbacks([options/2]).
-callback previously_existed(Req, State) -callback previously_existed(Req, State)
-> {boolean(), Req, State} -> {boolean(), Req, State}
| {stop, Req, State} | {stop, Req, State}
| {switch_handler(), Req, State}
when Req::cowboy_req:req(), State::any(). when Req::cowboy_req:req(), State::any().
-optional_callbacks([previously_existed/2]). -optional_callbacks([previously_existed/2]).
-callback resource_exists(Req, State) -callback resource_exists(Req, State)
-> {boolean(), Req, State} -> {boolean(), Req, State}
| {stop, Req, State} | {stop, Req, State}
| {switch_handler(), Req, State}
when Req::cowboy_req:req(), State::any(). when Req::cowboy_req:req(), State::any().
-optional_callbacks([resource_exists/2]). -optional_callbacks([resource_exists/2]).
-callback service_available(Req, State) -callback service_available(Req, State)
-> {boolean(), Req, State} -> {boolean(), Req, State}
| {stop, Req, State} | {stop, Req, State}
| {switch_handler(), Req, State}
when Req::cowboy_req:req(), State::any(). when Req::cowboy_req:req(), State::any().
-optional_callbacks([service_available/2]). -optional_callbacks([service_available/2]).
-callback uri_too_long(Req, State) -callback uri_too_long(Req, State)
-> {boolean(), Req, State} -> {boolean(), Req, State}
| {stop, Req, State} | {stop, Req, State}
| {switch_handler(), Req, State}
when Req::cowboy_req:req(), State::any(). when Req::cowboy_req:req(), State::any().
-optional_callbacks([uri_too_long/2]). -optional_callbacks([uri_too_long/2]).
-callback valid_content_headers(Req, State) -callback valid_content_headers(Req, State)
-> {boolean(), Req, State} -> {boolean(), Req, State}
| {stop, Req, State} | {stop, Req, State}
| {switch_handler(), Req, State}
when Req::cowboy_req:req(), State::any(). when Req::cowboy_req:req(), State::any().
-optional_callbacks([valid_content_headers/2]). -optional_callbacks([valid_content_headers/2]).
-callback valid_entity_length(Req, State) -callback valid_entity_length(Req, State)
-> {boolean(), Req, State} -> {boolean(), Req, State}
| {stop, Req, State} | {stop, Req, State}
| {switch_handler(), Req, State}
when Req::cowboy_req:req(), State::any(). when Req::cowboy_req:req(), State::any().
-optional_callbacks([valid_entity_length/2]). -optional_callbacks([valid_entity_length/2]).
-callback variances(Req, State) -callback variances(Req, State)
-> {[binary()], Req, State} -> {[binary()], Req, State}
| {stop, Req, State}
when Req::cowboy_req:req(), State::any(). when Req::cowboy_req:req(), State::any().
-optional_callbacks([variances/2]). -optional_callbacks([variances/2]).
@ -233,11 +255,17 @@
-spec upgrade(Req, Env, module(), any()) -spec upgrade(Req, Env, module(), any())
-> {ok, Req, Env} when Req::cowboy_req:req(), Env::cowboy_middleware:env(). -> {ok, Req, Env} when Req::cowboy_req:req(), Env::cowboy_middleware:env().
upgrade(Req0, Env, Handler, HandlerState) -> upgrade(Req0, Env, Handler, HandlerState0) ->
Method = cowboy_req:method(Req0), Method = cowboy_req:method(Req0),
{ok, Req, Result} = service_available(Req0, #state{method=Method, case service_available(Req0, #state{method=Method,
handler=Handler, handler_state=HandlerState}), handler=Handler, handler_state=HandlerState0}) of
{ok, Req, Env#{result => Result}}. {ok, Req, Result} ->
{ok, Req, Env#{result => Result}};
{Mod, Req, HandlerState} ->
Mod:upgrade(Req, Env, Handler, HandlerState);
{Mod, Req, HandlerState, Opts} ->
Mod:upgrade(Req, Env, Handler, HandlerState, Opts)
end.
-spec upgrade(Req, Env, module(), any(), any()) -spec upgrade(Req, Env, module(), any(), any())
-> {ok, Req, Env} when Req::cowboy_req:req(), Env::cowboy_middleware:env(). -> {ok, Req, Env} when Req::cowboy_req:req(), Env::cowboy_middleware:env().
@ -260,6 +288,8 @@ known_methods(Req, State=#state{method=Method}) ->
next(Req, State, 501); next(Req, State, 501);
{stop, Req2, HandlerState} -> {stop, Req2, HandlerState} ->
terminate(Req2, State#state{handler_state=HandlerState}); terminate(Req2, State#state{handler_state=HandlerState});
{Switch, Req2, HandlerState} when element(1, Switch) =:= switch_handler ->
switch_handler(Switch, Req2, HandlerState);
{List, Req2, HandlerState} -> {List, Req2, HandlerState} ->
State2 = State#state{handler_state=HandlerState}, State2 = State#state{handler_state=HandlerState},
case lists:member(Method, List) of case lists:member(Method, List) of
@ -285,6 +315,8 @@ allowed_methods(Req, State=#state{method=Method}) ->
[<<"HEAD">>, <<"GET">>, <<"OPTIONS">>]); [<<"HEAD">>, <<"GET">>, <<"OPTIONS">>]);
{stop, Req2, HandlerState} -> {stop, Req2, HandlerState} ->
terminate(Req2, State#state{handler_state=HandlerState}); terminate(Req2, State#state{handler_state=HandlerState});
{Switch, Req2, HandlerState} when element(1, Switch) =:= switch_handler ->
switch_handler(Switch, Req2, HandlerState);
{List, Req2, HandlerState} -> {List, Req2, HandlerState} ->
State2 = State#state{handler_state=HandlerState}, State2 = State#state{handler_state=HandlerState},
case lists:member(Method, List) of case lists:member(Method, List) of
@ -316,6 +348,8 @@ is_authorized(Req, State) ->
forbidden(Req, State); forbidden(Req, State);
{stop, Req2, HandlerState} -> {stop, Req2, HandlerState} ->
terminate(Req2, State#state{handler_state=HandlerState}); terminate(Req2, State#state{handler_state=HandlerState});
{Switch, Req2, HandlerState} when element(1, Switch) =:= switch_handler ->
switch_handler(Switch, Req2, HandlerState);
{true, Req2, HandlerState} -> {true, Req2, HandlerState} ->
forbidden(Req2, State#state{handler_state=HandlerState}); forbidden(Req2, State#state{handler_state=HandlerState});
{{false, AuthHead}, Req2, HandlerState} -> {{false, AuthHead}, Req2, HandlerState} ->
@ -346,6 +380,8 @@ options(Req, State=#state{allowed_methods=Methods, method= <<"OPTIONS">>}) ->
= << << ", ", M/binary >> || M <- Methods >>, = << << ", ", M/binary >> || M <- Methods >>,
Req2 = cowboy_req:set_resp_header(<<"allow">>, Allow, Req), Req2 = cowboy_req:set_resp_header(<<"allow">>, Allow, Req),
respond(Req2, State, 200); respond(Req2, State, 200);
{Switch, Req2, HandlerState} when element(1, Switch) =:= switch_handler ->
switch_handler(Switch, Req2, HandlerState);
{stop, Req2, HandlerState} -> {stop, Req2, HandlerState} ->
terminate(Req2, State#state{handler_state=HandlerState}); terminate(Req2, State#state{handler_state=HandlerState});
{ok, Req2, HandlerState} -> {ok, Req2, HandlerState} ->
@ -387,6 +423,8 @@ content_types_provided(Req, State) ->
end; end;
{stop, Req2, HandlerState} -> {stop, Req2, HandlerState} ->
terminate(Req2, State#state{handler_state=HandlerState}); terminate(Req2, State#state{handler_state=HandlerState});
{Switch, Req2, HandlerState} when element(1, Switch) =:= switch_handler ->
switch_handler(Switch, Req2, HandlerState);
{[], Req2, HandlerState} -> {[], Req2, HandlerState} ->
not_acceptable(Req2, State#state{handler_state=HandlerState}); not_acceptable(Req2, State#state{handler_state=HandlerState});
{CTP, Req2, HandlerState} -> {CTP, Req2, HandlerState} ->
@ -489,6 +527,8 @@ languages_provided(Req, State) ->
charsets_provided(Req, State); charsets_provided(Req, State);
{stop, Req2, HandlerState} -> {stop, Req2, HandlerState} ->
terminate(Req2, State#state{handler_state=HandlerState}); terminate(Req2, State#state{handler_state=HandlerState});
{Switch, Req2, HandlerState} when element(1, Switch) =:= switch_handler ->
switch_handler(Switch, Req2, HandlerState);
{[], Req2, HandlerState} -> {[], Req2, HandlerState} ->
not_acceptable(Req2, State#state{handler_state=HandlerState}); not_acceptable(Req2, State#state{handler_state=HandlerState});
{LP, Req2, HandlerState} -> {LP, Req2, HandlerState} ->
@ -549,6 +589,8 @@ charsets_provided(Req, State) ->
set_content_type(Req, State); set_content_type(Req, State);
{stop, Req2, HandlerState} -> {stop, Req2, HandlerState} ->
terminate(Req2, State#state{handler_state=HandlerState}); terminate(Req2, State#state{handler_state=HandlerState});
{Switch, Req2, HandlerState} when element(1, Switch) =:= switch_handler ->
switch_handler(Switch, Req2, HandlerState);
{[], Req2, HandlerState} -> {[], Req2, HandlerState} ->
not_acceptable(Req2, State#state{handler_state=HandlerState}); not_acceptable(Req2, State#state{handler_state=HandlerState});
{CP, Req2, HandlerState} -> {CP, Req2, HandlerState} ->
@ -832,6 +874,8 @@ moved_permanently(Req, State, OnFalse) ->
OnFalse(Req2, State#state{handler_state=HandlerState}); OnFalse(Req2, State#state{handler_state=HandlerState});
{stop, Req2, HandlerState} -> {stop, Req2, HandlerState} ->
terminate(Req2, State#state{handler_state=HandlerState}); terminate(Req2, State#state{handler_state=HandlerState});
{Switch, Req2, HandlerState} when element(1, Switch) =:= switch_handler ->
switch_handler(Switch, Req2, HandlerState);
no_call -> no_call ->
OnFalse(Req, State) OnFalse(Req, State)
end. end.
@ -853,6 +897,8 @@ moved_temporarily(Req, State) ->
is_post_to_missing_resource(Req2, State#state{handler_state=HandlerState}, 410); is_post_to_missing_resource(Req2, State#state{handler_state=HandlerState}, 410);
{stop, Req2, HandlerState} -> {stop, Req2, HandlerState} ->
terminate(Req2, State#state{handler_state=HandlerState}); terminate(Req2, State#state{handler_state=HandlerState});
{Switch, Req2, HandlerState} when element(1, Switch) =:= switch_handler ->
switch_handler(Switch, Req2, HandlerState);
no_call -> no_call ->
is_post_to_missing_resource(Req, State, 410) is_post_to_missing_resource(Req, State, 410)
end. end.
@ -903,6 +949,8 @@ accept_resource(Req, State) ->
respond(Req, State, 415); respond(Req, State, 415);
{stop, Req2, HandlerState} -> {stop, Req2, HandlerState} ->
terminate(Req2, State#state{handler_state=HandlerState}); terminate(Req2, State#state{handler_state=HandlerState});
{Switch, Req2, HandlerState} when element(1, Switch) =:= switch_handler ->
switch_handler(Switch, Req2, HandlerState);
{CTA, Req2, HandlerState} -> {CTA, Req2, HandlerState} ->
CTA2 = [normalize_content_types(P) || P <- CTA], CTA2 = [normalize_content_types(P) || P <- CTA],
State2 = State#state{handler_state=HandlerState}, State2 = State#state{handler_state=HandlerState},
@ -938,6 +986,8 @@ process_content_type(Req, State=#state{method=Method, exists=Exists}, Fun) ->
try case call(Req, State, Fun) of try case call(Req, State, Fun) of
{stop, Req2, HandlerState2} -> {stop, Req2, HandlerState2} ->
terminate(Req2, State#state{handler_state=HandlerState2}); terminate(Req2, State#state{handler_state=HandlerState2});
{Switch, Req2, HandlerState} when element(1, Switch) =:= switch_handler ->
switch_handler(Switch, Req2, HandlerState);
{true, Req2, HandlerState2} when Exists -> {true, Req2, HandlerState2} when Exists ->
State2 = State#state{handler_state=HandlerState2}, State2 = State#state{handler_state=HandlerState2},
next(Req2, State2, fun has_resp_body/2); next(Req2, State2, fun has_resp_body/2);
@ -1019,6 +1069,8 @@ set_resp_body(Req, State=#state{content_type_a={_, Callback}}) ->
try case call(Req, State, Callback) of try case call(Req, State, Callback) of
{stop, Req2, HandlerState2} -> {stop, Req2, HandlerState2} ->
terminate(Req2, State#state{handler_state=HandlerState2}); terminate(Req2, State#state{handler_state=HandlerState2});
{Switch, Req2, HandlerState} when element(1, Switch) =:= switch_handler ->
switch_handler(Switch, Req2, HandlerState);
{Body, Req2, HandlerState2} -> {Body, Req2, HandlerState2} ->
State2 = State#state{handler_state=HandlerState2}, State2 = State#state{handler_state=HandlerState2},
Req3 = cowboy_req:set_resp_body(Body, Req2), Req3 = cowboy_req:set_resp_body(Body, Req2),
@ -1114,6 +1166,8 @@ expect(Req, State, Callback, Expected, OnTrue, OnFalse) ->
next(Req, State, OnTrue); next(Req, State, OnTrue);
{stop, Req2, HandlerState} -> {stop, Req2, HandlerState} ->
terminate(Req2, State#state{handler_state=HandlerState}); terminate(Req2, State#state{handler_state=HandlerState});
{Switch, Req2, HandlerState} when element(1, Switch) =:= switch_handler ->
switch_handler(Switch, Req2, HandlerState);
{Expected, Req2, HandlerState} -> {Expected, Req2, HandlerState} ->
next(Req2, State#state{handler_state=HandlerState}, OnTrue); next(Req2, State#state{handler_state=HandlerState}, OnTrue);
{_Unexpected, Req2, HandlerState} -> {_Unexpected, Req2, HandlerState} ->
@ -1148,6 +1202,11 @@ next(Req, State, StatusCode) when is_integer(StatusCode) ->
respond(Req, State, StatusCode) -> respond(Req, State, StatusCode) ->
terminate(cowboy_req:reply(StatusCode, Req), State). terminate(cowboy_req:reply(StatusCode, Req), State).
switch_handler({switch_handler, Mod}, Req, HandlerState) ->
{Mod, Req, HandlerState};
switch_handler({switch_handler, Mod, Opts}, Req, HandlerState) ->
{Mod, Req, HandlerState, Opts}.
-spec error_terminate(cowboy_req:req(), #state{}, atom(), any()) -> no_return(). -spec error_terminate(cowboy_req:req(), #state{}, atom(), any()) -> no_return().
error_terminate(Req, #state{handler=Handler, handler_state=HandlerState}, Class, Reason) -> error_terminate(Req, #state{handler=Handler, handler_state=HandlerState}, Class, Reason) ->
cowboy_handler:terminate({crash, Class, Reason}, Req, HandlerState, Handler), cowboy_handler:terminate({crash, Class, Reason}, Req, HandlerState, Handler),

View file

@ -0,0 +1,36 @@
-module(switch_handler_h).
-export([init/2]).
-export([content_types_provided/2]).
-export([provide/2]).
-export([info/3]).
init(Req, State) ->
{cowboy_rest, Req, State}.
content_types_provided(Req, State) ->
{[{<<"text/plain">>, provide}], Req, State}.
provide(Req0, run) ->
Req = cowboy_req:stream_reply(200, Req0),
send_after(0),
{{switch_handler, cowboy_loop}, Req, 0};
provide(Req0, hibernate) ->
Req = cowboy_req:stream_reply(200, Req0),
send_after(0),
{{switch_handler, cowboy_loop, hibernate}, Req, 0}.
send_after(N) ->
erlang:send_after(100, self(), {stream, msg(N)}).
msg(0) -> <<"Hello\n">>;
msg(1) -> <<"streamed\n">>;
msg(2) -> <<"world!\n">>;
msg(3) -> stop.
info({stream, stop}, Req, State) ->
{stop, Req, State};
info({stream, What}, Req, State) ->
cowboy_req:stream_body(What, nofin, Req),
send_after(State + 1),
{ok, Req, State + 1}.

View file

@ -0,0 +1,70 @@
%% Copyright (c) 2017, Loïc Hoguin <essen@ninenines.eu>
%%
%% 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(rest_handler_SUITE).
-compile(export_all).
-import(ct_helper, [config/2]).
-import(ct_helper, [doc/1]).
-import(cowboy_test, [gun_open/1]).
%% ct.
all() ->
cowboy_test:common_all().
groups() ->
cowboy_test:common_groups(ct_helper: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([{'_', [
{"/switch_handler", switch_handler_h, run},
{"/switch_handler_opts", switch_handler_h, hibernate}
]}]).
%% Internal.
do_decode(Headers, Body) ->
case lists:keyfind(<<"content-encoding">>, 1, Headers) of
{_, <<"gzip">>} -> zlib:gunzip(Body);
_ -> Body
end.
%% Tests.
switch_handler(Config) ->
doc("Switch REST to loop handler for streaming the response body."),
ConnPid = gun_open(Config),
Ref = gun:get(ConnPid, "/switch_handler", [{<<"accept-encoding">>, <<"gzip">>}]),
{response, nofin, 200, Headers} = gun:await(ConnPid, Ref),
{ok, Body} = gun:await_body(ConnPid, Ref),
<<"Hello\nstreamed\nworld!\n">> = do_decode(Headers, Body),
ok.
switch_handler_opts(Config) ->
doc("Switch REST to loop handler for streaming the response body; with options."),
ConnPid = gun_open(Config),
Ref = gun:get(ConnPid, "/switch_handler_opts", [{<<"accept-encoding">>, <<"gzip">>}]),
{response, nofin, 200, Headers} = gun:await(ConnPid, Ref),
{ok, Body} = gun:await_body(ConnPid, Ref),
<<"Hello\nstreamed\nworld!\n">> = do_decode(Headers, Body),
ok.