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:
parent
5e88a9b394
commit
836342abb8
5 changed files with 191 additions and 11 deletions
|
@ -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.
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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),
|
||||||
|
|
36
test/handlers/switch_handler_h.erl
Normal file
36
test/handlers/switch_handler_h.erl
Normal 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}.
|
70
test/rest_handler_SUITE.erl
Normal file
70
test/rest_handler_SUITE.erl
Normal 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.
|
Loading…
Add table
Add a link
Reference in a new issue