0
Fork 0
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:
Loïc Hoguin 2011-06-06 18:15:36 +02:00
parent b00dd6fba2
commit 648921b8cf

View file

@ -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 ->