mirror of
https://github.com/ninenines/cowboy.git
synced 2025-07-14 20:30:23 +00:00
Add hibernate support for websockets.
Return {ok, Req, State, hibernate} or {reply, Data, Req, State, hibernate} to hibernate the websocket process and save up memory and CPU. You should hibernate processes that will receive few messages. So probably most of them.
This commit is contained in:
parent
b00dd6fba2
commit
648921b8cf
1 changed files with 27 additions and 8 deletions
|
@ -13,7 +13,8 @@
|
||||||
%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
-module(cowboy_http_websocket).
|
-module(cowboy_http_websocket).
|
||||||
-export([upgrade/3]).
|
-export([upgrade/3]). %% API.
|
||||||
|
-export([handler_loop/4]). %% Internal.
|
||||||
|
|
||||||
-include("include/http.hrl").
|
-include("include/http.hrl").
|
||||||
|
|
||||||
|
@ -24,7 +25,8 @@
|
||||||
challenge = undefined :: undefined | binary(),
|
challenge = undefined :: undefined | binary(),
|
||||||
timeout = infinity :: timeout(),
|
timeout = infinity :: timeout(),
|
||||||
messages = undefined :: undefined | {atom(), atom(), atom()},
|
messages = undefined :: undefined | {atom(), atom(), atom()},
|
||||||
eop :: tuple()
|
eop :: tuple(),
|
||||||
|
hibernate = false :: boolean()
|
||||||
}).
|
}).
|
||||||
|
|
||||||
-spec upgrade(module(), any(), #http_req{}) -> ok.
|
-spec upgrade(module(), any(), #http_req{}) -> ok.
|
||||||
|
@ -95,7 +97,7 @@ websocket_handshake(State=#state{origin=Origin, challenge=Challenge},
|
||||||
{<<"Sec-WebSocket-Location">>, Location},
|
{<<"Sec-WebSocket-Location">>, Location},
|
||||||
{<<"Sec-WebSocket-Origin">>, Origin}],
|
{<<"Sec-WebSocket-Origin">>, Origin}],
|
||||||
Challenge, Req#http_req{resp_state=waiting}),
|
Challenge, Req#http_req{resp_state=waiting}),
|
||||||
handler_loop(State#state{messages=Transport:messages()},
|
handler_before_loop(State#state{messages=Transport:messages()},
|
||||||
Req2, HandlerState, <<>>).
|
Req2, HandlerState, <<>>).
|
||||||
|
|
||||||
-spec websocket_location(atom(), binary(), inet:ip_port(), binary())
|
-spec websocket_location(atom(), binary(), inet:ip_port(), binary())
|
||||||
|
@ -107,11 +109,21 @@ websocket_location(_Any, Host, Port, Path) ->
|
||||||
<< "ws://", Host/binary, ":",
|
<< "ws://", Host/binary, ":",
|
||||||
(list_to_binary(integer_to_list(Port)))/binary, Path/binary >>.
|
(list_to_binary(integer_to_list(Port)))/binary, Path/binary >>.
|
||||||
|
|
||||||
-spec handler_loop(#state{}, #http_req{}, any(), binary()) -> ok.
|
-spec handler_before_loop(#state{}, #http_req{}, any(), binary()) -> ok.
|
||||||
handler_loop(State=#state{messages={OK, Closed, Error}, timeout=Timeout},
|
handler_before_loop(State=#state{hibernate=true},
|
||||||
Req=#http_req{socket=Socket, transport=Transport},
|
Req=#http_req{socket=Socket, transport=Transport},
|
||||||
HandlerState, SoFar) ->
|
HandlerState, SoFar) ->
|
||||||
Transport:setopts(Socket, [{active, once}]),
|
Transport:setopts(Socket, [{active, once}]),
|
||||||
|
erlang:hibernate(?MODULE, handler_loop, [State#state{hibernate=false},
|
||||||
|
Req, HandlerState, SoFar]);
|
||||||
|
handler_before_loop(State, Req=#http_req{socket=Socket, transport=Transport},
|
||||||
|
HandlerState, SoFar) ->
|
||||||
|
Transport:setopts(Socket, [{active, once}]),
|
||||||
|
handler_loop(State, Req, HandlerState, SoFar).
|
||||||
|
|
||||||
|
-spec handler_loop(#state{}, #http_req{}, any(), binary()) -> ok.
|
||||||
|
handler_loop(State=#state{messages={OK, Closed, Error}, timeout=Timeout},
|
||||||
|
Req=#http_req{socket=Socket}, HandlerState, SoFar) ->
|
||||||
receive
|
receive
|
||||||
{OK, Socket, Data} ->
|
{OK, Socket, Data} ->
|
||||||
websocket_data(State, Req, HandlerState,
|
websocket_data(State, Req, HandlerState,
|
||||||
|
@ -122,7 +134,7 @@ handler_loop(State=#state{messages={OK, Closed, Error}, timeout=Timeout},
|
||||||
handler_terminate(State, Req, HandlerState, {error, Reason});
|
handler_terminate(State, Req, HandlerState, {error, Reason});
|
||||||
Message ->
|
Message ->
|
||||||
handler_call(State, Req, HandlerState,
|
handler_call(State, Req, HandlerState,
|
||||||
SoFar, Message, fun handler_loop/4)
|
SoFar, Message, fun handler_before_loop/4)
|
||||||
after Timeout ->
|
after Timeout ->
|
||||||
websocket_close(State, Req, HandlerState, {normal, timeout})
|
websocket_close(State, Req, HandlerState, {normal, timeout})
|
||||||
end.
|
end.
|
||||||
|
@ -131,7 +143,7 @@ handler_loop(State=#state{messages={OK, Closed, Error}, timeout=Timeout},
|
||||||
websocket_data(State, Req, HandlerState, << 255, 0, _Rest/bits >>) ->
|
websocket_data(State, Req, HandlerState, << 255, 0, _Rest/bits >>) ->
|
||||||
websocket_close(State, Req, HandlerState, {normal, closed});
|
websocket_close(State, Req, HandlerState, {normal, closed});
|
||||||
websocket_data(State, Req, HandlerState, <<>>) ->
|
websocket_data(State, Req, HandlerState, <<>>) ->
|
||||||
handler_loop(State, Req, HandlerState, <<>>);
|
handler_before_loop(State, Req, HandlerState, <<>>);
|
||||||
websocket_data(State, Req, HandlerState, Data) ->
|
websocket_data(State, Req, HandlerState, Data) ->
|
||||||
websocket_frame(State, Req, HandlerState, Data, binary:first(Data)).
|
websocket_frame(State, Req, HandlerState, Data, binary:first(Data)).
|
||||||
|
|
||||||
|
@ -146,7 +158,7 @@ websocket_frame(State=#state{eop=EOP}, Req, HandlerState, Data, 0) ->
|
||||||
Rest, {websocket, Frame}, fun websocket_data/4);
|
Rest, {websocket, Frame}, fun websocket_data/4);
|
||||||
nomatch ->
|
nomatch ->
|
||||||
%% @todo We probably should allow limiting frame length.
|
%% @todo We probably should allow limiting frame length.
|
||||||
handler_loop(State, Req, HandlerState, Data)
|
handler_before_loop(State, Req, HandlerState, Data)
|
||||||
end;
|
end;
|
||||||
websocket_frame(State, Req, HandlerState, _Data, _FrameType) ->
|
websocket_frame(State, Req, HandlerState, _Data, _FrameType) ->
|
||||||
websocket_close(State, Req, HandlerState, {error, badframe}).
|
websocket_close(State, Req, HandlerState, {error, badframe}).
|
||||||
|
@ -157,9 +169,16 @@ handler_call(State=#state{handler=Handler, opts=Opts}, Req, HandlerState,
|
||||||
try Handler:websocket_handle(Message, Req, HandlerState) of
|
try Handler:websocket_handle(Message, Req, HandlerState) of
|
||||||
{ok, Req2, HandlerState2} ->
|
{ok, Req2, HandlerState2} ->
|
||||||
NextState(State, Req2, HandlerState2, RemainingData);
|
NextState(State, Req2, HandlerState2, RemainingData);
|
||||||
|
{ok, Req2, HandlerState2, hibernate} ->
|
||||||
|
NextState(State#state{hibernate=true},
|
||||||
|
Req2, HandlerState2, RemainingData);
|
||||||
{reply, Data, Req2, HandlerState2} ->
|
{reply, Data, Req2, HandlerState2} ->
|
||||||
websocket_send(Data, Req2),
|
websocket_send(Data, Req2),
|
||||||
NextState(State, Req2, HandlerState2, RemainingData);
|
NextState(State, Req2, HandlerState2, RemainingData);
|
||||||
|
{reply, Data, Req2, HandlerState2, hibernate} ->
|
||||||
|
websocket_send(Data, Req2),
|
||||||
|
NextState(State#state{hibernate=true},
|
||||||
|
Req2, HandlerState2, RemainingData);
|
||||||
{shutdown, Req2, HandlerState2} ->
|
{shutdown, Req2, HandlerState2} ->
|
||||||
websocket_close(State, Req2, HandlerState2, {normal, shutdown})
|
websocket_close(State, Req2, HandlerState2, {normal, shutdown})
|
||||||
catch Class:Reason ->
|
catch Class:Reason ->
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue