0
Fork 0
mirror of https://github.com/ninenines/cowboy.git synced 2025-07-16 05:00:24 +00:00

Add a commands-based interface to Websocket handlers

This feature is currently experimental. It will become the
preferred way to use Websocket handlers once it becomes
documented.

A commands-based interface enables adding commands without
having to change the interface much. It mirrors the interface
of stream handlers or gen_statem. It will enable adding
commands that have been needed for some time but were not
implemented for fear of making the interface too complex.
This commit is contained in:
Loïc Hoguin 2018-09-11 14:33:58 +02:00
parent 4b385749f2
commit 8404b1c908
No known key found for this signature in database
GPG key ID: 8A9DF795F6FED764
5 changed files with 342 additions and 4 deletions

View file

@ -31,7 +31,12 @@
-export([system_terminate/4]).
-export([system_code_change/4]).
-type call_result(State) :: {ok, State}
-type commands() :: [cow_ws:frame()].
-export_type([commands/0]).
-type call_result(State) :: {commands(), State} | {commands(), State, hibernate}.
-type deprecated_call_result(State) :: {ok, State}
| {ok, State, hibernate}
| {reply, cow_ws:frame() | [cow_ws:frame()], State}
| {reply, cow_ws:frame() | [cow_ws:frame()], State, hibernate}
@ -48,13 +53,13 @@
when Req::cowboy_req:req().
-callback websocket_init(State)
-> call_result(State) when State::any().
-> call_result(State) | deprecated_call_result(State) when State::any().
-optional_callbacks([websocket_init/1]).
-callback websocket_handle(ping | pong | {text | binary | ping | pong, binary()}, State)
-> call_result(State) when State::any().
-> call_result(State) | deprecated_call_result(State) when State::any().
-callback websocket_info(any(), State)
-> call_result(State) when State::any().
-> call_result(State) | deprecated_call_result(State) when State::any().
-callback terminate(any(), cowboy_req:req(), any()) -> ok.
-optional_callbacks([terminate/3]).
@ -457,6 +462,13 @@ handler_call(State=#state{handler=Handler}, HandlerState,
websocket_init -> Handler:websocket_init(HandlerState);
_ -> Handler:Callback(Message, HandlerState)
end of
{Commands, HandlerState2} when is_list(Commands) ->
handler_call_result(State,
HandlerState2, ParseState, NextState, Commands);
{Commands, HandlerState2, hibernate} when is_list(Commands) ->
handler_call_result(State#state{hibernate=true},
HandlerState2, ParseState, NextState, Commands);
%% The following call results are deprecated.
{ok, HandlerState2} ->
NextState(State, HandlerState2, ParseState);
{ok, HandlerState2, hibernate} ->
@ -488,6 +500,32 @@ handler_call(State=#state{handler=Handler}, HandlerState,
erlang:raise(Class, Reason, erlang:get_stacktrace())
end.
-spec handler_call_result(#state{}, any(), parse_state(), fun(), commands()) -> no_return().
handler_call_result(State0, HandlerState, ParseState, NextState, Commands) ->
case commands(Commands, State0, []) of
{ok, State} ->
NextState(State, HandlerState, ParseState);
{stop, State} ->
terminate(State, HandlerState, stop);
{Error = {error, _}, State} ->
terminate(State, HandlerState, Error)
end.
commands([], State, []) ->
{ok, State};
commands([], State, Data) ->
Result = transport_send(State, nofin, lists:reverse(Data)),
{Result, State};
commands([Frame|Tail], State=#state{extensions=Extensions}, Data0) ->
Data = [cow_ws:frame(Frame, Extensions)|Data0],
case is_close_frame(Frame) of
true ->
_ = transport_send(State, fin, lists:reverse(Data)),
{stop, State};
false ->
commands(Tail, State, Data)
end.
transport_send(#state{socket=Stream={Pid, _}, transport=undefined}, IsFin, Data) ->
Pid ! {Stream, {data, IsFin, Data}},
ok;