diff options
Diffstat (limited to 'src/cowboy_websocket.erl')
-rw-r--r-- | src/cowboy_websocket.erl | 137 |
1 files changed, 87 insertions, 50 deletions
diff --git a/src/cowboy_websocket.erl b/src/cowboy_websocket.erl index 8c02ac7..abcc015 100644 --- a/src/cowboy_websocket.erl +++ b/src/cowboy_websocket.erl @@ -1,4 +1,4 @@ -%% Copyright (c) 2011-2012, Loïc Hoguin <[email protected]> +%% Copyright (c) 2011-2013, Loïc Hoguin <[email protected]> %% %% Permission to use, copy, modify, and/or distribute this software for any %% purpose with or without fee is hereby granted, provided that the above @@ -38,11 +38,12 @@ {fin, opcode(), binary()}. %% last fragment has been seen. -record(state, { + env :: cowboy_middleware:env(), socket = undefined :: inet:socket(), transport = undefined :: module(), version :: 0 | 7 | 8 | 13, handler :: module(), - opts :: any(), + handler_opts :: any(), challenge = undefined :: undefined | binary() | {binary(), binary()}, timeout = infinity :: timeout(), timeout_ref = undefined :: undefined | reference(), @@ -58,15 +59,19 @@ %% You do not need to call this function manually. To upgrade to the WebSocket %% protocol, you simply need to return <em>{upgrade, protocol, {@module}}</em> %% in your <em>cowboy_http_handler:init/3</em> handler function. --spec upgrade(pid(), module(), any(), cowboy_req:req()) -> closed. -upgrade(ListenerPid, Handler, Opts, Req) -> +-spec upgrade(Req, Env, module(), any()) + -> {ok, Req, Env} | {error, 400, Req} + | {suspend, module(), atom(), [any()]} + when Req::cowboy_req:req(), Env::cowboy_middleware:env(). +upgrade(Req, Env, Handler, HandlerOpts) -> + {_, ListenerPid} = lists:keyfind(listener, 1, Env), ranch_listener:remove_connection(ListenerPid), {ok, Transport, Socket} = cowboy_req:transport(Req), - State = #state{socket=Socket, transport=Transport, - handler=Handler, opts=Opts}, + State = #state{env=Env, socket=Socket, transport=Transport, + handler=Handler, handler_opts=HandlerOpts}, case catch websocket_upgrade(State, Req) of {ok, State2, Req2} -> handler_init(State2, Req2); - {'EXIT', _Reason} -> upgrade_error(Req) + {'EXIT', _Reason} -> upgrade_error(Req, Env) end. -spec websocket_upgrade(#state{}, Req) @@ -110,10 +115,13 @@ websocket_upgrade(Version, State, Req) {ok, State#state{version=IntVersion, challenge=Challenge}, cowboy_req:set_meta(websocket_version, IntVersion, Req2)}. --spec handler_init(#state{}, cowboy_req:req()) -> closed. -handler_init(State=#state{transport=Transport, handler=Handler, opts=Opts}, - Req) -> - try Handler:websocket_init(Transport:name(), Req, Opts) of +-spec handler_init(#state{}, Req) + -> {ok, Req, cowboy_middleware:env()} | {error, 400, Req} + | {suspend, module(), atom(), [any()]} + when Req::cowboy_req:req(). +handler_init(State=#state{env=Env, transport=Transport, + handler=Handler, handler_opts=HandlerOpts}, Req) -> + try Handler:websocket_init(Transport:name(), Req, HandlerOpts) of {ok, Req2, HandlerState} -> websocket_handshake(State, Req2, HandlerState); {ok, Req2, HandlerState, hibernate} -> @@ -127,27 +135,31 @@ handler_init(State=#state{transport=Transport, handler=Handler, opts=Opts}, hibernate=true}, Req2, HandlerState); {shutdown, Req2} -> cowboy_req:ensure_response(Req2, 400), - closed + {ok, Req2, [{result, closed}|Env]} catch Class:Reason -> - upgrade_error(Req), error_logger:error_msg( "** Cowboy handler ~p terminating in ~p/~p~n" " for the reason ~p:~p~n** Options were ~p~n" "** Request was ~p~n** Stacktrace: ~p~n~n", - [Handler, websocket_init, 3, Class, Reason, Opts, - cowboy_req:to_list(Req),erlang:get_stacktrace()]) + [Handler, websocket_init, 3, Class, Reason, HandlerOpts, + cowboy_req:to_list(Req),erlang:get_stacktrace()]), + upgrade_error(Req, Env) end. --spec upgrade_error(cowboy_req:req()) -> closed. -upgrade_error(Req) -> +-spec upgrade_error(Req, Env) -> {ok, Req, Env} | {error, 400, Req} + when Req::cowboy_req:req(), Env::cowboy_middleware:env(). +upgrade_error(Req, Env) -> receive - {cowboy_req, resp_sent} -> closed + {cowboy_req, resp_sent} -> + {ok, Req, [{result, closed}|Env]} after 0 -> - _ = cowboy_req:reply(400, [], [], Req), - closed + {error, 400, Req} end. --spec websocket_handshake(#state{}, cowboy_req:req(), any()) -> closed. +-spec websocket_handshake(#state{}, Req, any()) + -> {ok, Req, cowboy_middleware:env()} + | {suspend, module(), atom(), [any()]} + when Req::cowboy_req:req(). websocket_handshake(State=#state{socket=Socket, transport=Transport, version=0, origin=Origin, challenge={Key1, Key2}}, Req, HandlerState) -> @@ -192,14 +204,16 @@ websocket_handshake(State=#state{transport=Transport, challenge=Challenge}, handler_before_loop(State2#state{messages=Transport:messages()}, Req2, HandlerState, <<>>). --spec handler_before_loop(#state{}, cowboy_req:req(), any(), binary()) -> closed. +-spec handler_before_loop(#state{}, Req, any(), binary()) + -> {ok, Req, cowboy_middleware:env()} + | {suspend, module(), atom(), [any()]} + when Req::cowboy_req:req(). handler_before_loop(State=#state{ socket=Socket, transport=Transport, hibernate=true}, Req, HandlerState, SoFar) -> Transport:setopts(Socket, [{active, once}]), - catch erlang:hibernate(?MODULE, handler_loop, - [State#state{hibernate=false}, Req, HandlerState, SoFar]), - closed; + {suspend, ?MODULE, handler_loop, + [State#state{hibernate=false}, Req, HandlerState, SoFar]}; handler_before_loop(State=#state{socket=Socket, transport=Transport}, Req, HandlerState, SoFar) -> Transport:setopts(Socket, [{active, once}]), @@ -215,7 +229,10 @@ handler_loop_timeout(State=#state{timeout=Timeout, timeout_ref=PrevRef}) -> State#state{timeout_ref=TRef}. %% @private --spec handler_loop(#state{}, cowboy_req:req(), any(), binary()) -> closed. +-spec handler_loop(#state{}, Req, any(), binary()) + -> {ok, Req, cowboy_middleware:env()} + | {suspend, module(), atom(), [any()]} + when Req::cowboy_req:req(). handler_loop(State=#state{ socket=Socket, messages={OK, Closed, Error}, timeout_ref=TRef}, Req, HandlerState, SoFar) -> @@ -237,7 +254,10 @@ handler_loop(State=#state{ SoFar, websocket_info, Message, fun handler_before_loop/4) end. --spec websocket_data(#state{}, cowboy_req:req(), any(), binary()) -> closed. +-spec websocket_data(#state{}, Req, any(), binary()) + -> {ok, Req, cowboy_middleware:env()} + | {suspend, module(), atom(), [any()]} + when Req::cowboy_req:req(). %% No more data. websocket_data(State, Req, HandlerState, <<>>) -> handler_before_loop(State, Req, HandlerState, <<>>); @@ -294,9 +314,11 @@ websocket_data(State, Req, HandlerState, websocket_data(State, Req, HandlerState, _Data) -> websocket_close(State, Req, HandlerState, {error, badframe}). --spec websocket_data(#state{}, cowboy_req:req(), any(), non_neg_integer(), +-spec websocket_data(#state{}, Req, any(), non_neg_integer(), non_neg_integer(), non_neg_integer(), non_neg_integer(), - non_neg_integer(), binary(), binary()) -> closed. + non_neg_integer(), binary(), binary()) + -> {ok, Req, cowboy_middleware:env()} + when Req::cowboy_req:req(). %% A fragmented message MUST start a non-zero opcode. websocket_data(State=#state{frag_state=undefined}, Req, HandlerState, _Fin=0, _Rsv=0, _Opcode=0, _Mask, _PayloadLen, _Rest, _Buffer) -> @@ -349,8 +371,11 @@ websocket_data(State, Req, HandlerState, _Fin, _Rsv, _Opcode, _Mask, websocket_close(State, Req, HandlerState, {error, badframe}). %% hybi routing depending on whether unmasking is needed. --spec websocket_before_unmask(#state{}, cowboy_req:req(), any(), binary(), - binary(), opcode(), 0 | 1, non_neg_integer() | undefined) -> closed. +-spec websocket_before_unmask(#state{}, Req, any(), binary(), + binary(), opcode(), 0 | 1, non_neg_integer() | undefined) + -> {ok, Req, cowboy_middleware:env()} + | {suspend, module(), atom(), [any()]} + when Req::cowboy_req:req(). websocket_before_unmask(State, Req, HandlerState, Data, Rest, Opcode, Mask, PayloadLen) -> case {Mask, PayloadLen} of @@ -366,15 +391,21 @@ websocket_before_unmask(State, Req, HandlerState, Data, end. %% hybi unmasking. --spec websocket_unmask(#state{}, cowboy_req:req(), any(), binary(), - opcode(), binary(), mask_key()) -> closed. +-spec websocket_unmask(#state{}, Req, any(), binary(), + opcode(), binary(), mask_key()) + -> {ok, Req, cowboy_middleware:env()} + | {suspend, module(), atom(), [any()]} + when Req::cowboy_req:req(). websocket_unmask(State, Req, HandlerState, RemainingData, Opcode, Payload, MaskKey) -> websocket_unmask(State, Req, HandlerState, RemainingData, Opcode, Payload, MaskKey, <<>>). --spec websocket_unmask(#state{}, cowboy_req:req(), any(), binary(), - opcode(), binary(), mask_key(), binary()) -> closed. +-spec websocket_unmask(#state{}, Req, any(), binary(), + opcode(), binary(), mask_key(), binary()) + -> {ok, Req, cowboy_middleware:env()} + | {suspend, module(), atom(), [any()]} + when Req::cowboy_req:req(). websocket_unmask(State, Req, HandlerState, RemainingData, Opcode, << O:32, Rest/bits >>, MaskKey, Acc) -> T = O bxor MaskKey, @@ -404,8 +435,10 @@ websocket_unmask(State, Req, HandlerState, RemainingData, Opcode, Acc). %% hybi dispatching. --spec websocket_dispatch(#state{}, cowboy_req:req(), any(), binary(), - opcode(), binary()) -> closed. +-spec websocket_dispatch(#state{}, Req, any(), binary(), opcode(), binary()) + -> {ok, Req, cowboy_middleware:env()} + | {suspend, module(), atom(), [any()]} + when Req::cowboy_req:req(). %% First frame of a fragmented message unmasked. Expect intermediate or last. websocket_dispatch(State=#state{frag_state={nofin, Opcode}}, Req, HandlerState, RemainingData, 0, Payload) -> @@ -446,10 +479,12 @@ websocket_dispatch(State, Req, HandlerState, RemainingData, 10, Payload) -> handler_call(State, Req, HandlerState, RemainingData, websocket_handle, {pong, Payload}, fun websocket_data/4). --spec handler_call(#state{}, cowboy_req:req(), any(), binary(), - atom(), any(), fun()) -> closed. -handler_call(State=#state{handler=Handler, opts=Opts}, Req, HandlerState, - RemainingData, Callback, Message, NextState) -> +-spec handler_call(#state{}, Req, any(), binary(), atom(), any(), fun()) + -> {ok, Req, cowboy_middleware:env()} + | {suspend, module(), atom(), [any()]} + when Req::cowboy_req:req(). +handler_call(State=#state{handler=Handler, handler_opts=HandlerOpts}, Req, + HandlerState, RemainingData, Callback, Message, NextState) -> try Handler:Callback(Message, Req, HandlerState) of {ok, Req2, HandlerState2} -> NextState(State, Req2, HandlerState2, RemainingData); @@ -515,7 +550,7 @@ handler_call(State=#state{handler=Handler, opts=Opts}, Req, HandlerState, " for the reason ~p:~p~n** Message was ~p~n" "** Options were ~p~n** Handler state was ~p~n" "** Request was ~p~n** Stacktrace: ~p~n~n", - [Handler, Callback, 3, Class, Reason, Message, Opts, + [Handler, Callback, 3, Class, Reason, Message, HandlerOpts, HandlerState, PLReq, erlang:get_stacktrace()]), websocket_close(State, Req, HandlerState, {error, handler}) end. @@ -582,8 +617,9 @@ websocket_send_many([Frame|Tail], State) -> Error -> Error end. --spec websocket_close(#state{}, cowboy_req:req(), any(), {atom(), atom()}) - -> closed. +-spec websocket_close(#state{}, Req, any(), {atom(), atom()}) + -> {ok, Req, cowboy_middleware:env()} + when Req::cowboy_req:req(). websocket_close(State=#state{socket=Socket, transport=Transport, version=0}, Req, HandlerState, Reason) -> Transport:send(Socket, << 255, 0 >>), @@ -593,9 +629,10 @@ websocket_close(State=#state{socket=Socket, transport=Transport}, Transport:send(Socket, << 1:1, 0:3, 8:4, 0:8 >>), handler_terminate(State, Req, HandlerState, Reason). --spec handler_terminate(#state{}, cowboy_req:req(), - any(), atom() | {atom(), atom()}) -> closed. -handler_terminate(#state{handler=Handler, opts=Opts}, +-spec handler_terminate(#state{}, Req, any(), atom() | {atom(), atom()}) + -> {ok, Req, cowboy_middleware:env()} + when Req::cowboy_req:req(). +handler_terminate(#state{env=Env, handler=Handler, handler_opts=HandlerOpts}, Req, HandlerState, TerminateReason) -> try Handler:websocket_terminate(TerminateReason, Req, HandlerState) @@ -606,10 +643,10 @@ handler_terminate(#state{handler=Handler, opts=Opts}, " for the reason ~p:~p~n** Initial reason was ~p~n" "** Options were ~p~n** Handler state was ~p~n" "** Request was ~p~n** Stacktrace: ~p~n~n", - [Handler, websocket_terminate, 3, Class, Reason, TerminateReason, Opts, - HandlerState, PLReq, erlang:get_stacktrace()]) + [Handler, websocket_terminate, 3, Class, Reason, TerminateReason, + HandlerOpts, HandlerState, PLReq, erlang:get_stacktrace()]) end, - closed. + {ok, Req, [{result, closed}|Env]}. %% hixie-76 specific. |