aboutsummaryrefslogtreecommitdiffstats
path: root/src/cowboy_websocket.erl
diff options
context:
space:
mode:
Diffstat (limited to 'src/cowboy_websocket.erl')
-rw-r--r--src/cowboy_websocket.erl159
1 files changed, 115 insertions, 44 deletions
diff --git a/src/cowboy_websocket.erl b/src/cowboy_websocket.erl
index 1c6d20c..8c02ac7 100644
--- a/src/cowboy_websocket.erl
+++ b/src/cowboy_websocket.erl
@@ -21,6 +21,11 @@
%% Internal.
-export([handler_loop/4]).
+-type frame() :: close | ping | pong
+ | {text | binary | close | ping | pong, binary()}
+ | {close, 1000..4999, binary()}.
+-export_type([frame/0]).
+
-type opcode() :: 0 | 1 | 2 | 8 | 9 | 10.
-type mask_key() :: 0..16#ffffffff.
@@ -125,12 +130,12 @@ handler_init(State=#state{transport=Transport, handler=Handler, opts=Opts},
closed
catch Class:Reason ->
upgrade_error(Req),
- PLReq = cowboy_req:to_list(Req),
error_logger:error_msg(
- "** Handler ~p terminating in websocket_init/3~n"
+ "** 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, Class, Reason, Opts, PLReq, erlang:get_stacktrace()])
+ [Handler, websocket_init, 3, Class, Reason, Opts,
+ cowboy_req:to_list(Req),erlang:get_stacktrace()])
end.
-spec upgrade_error(cowboy_req:req()) -> closed.
@@ -149,9 +154,9 @@ websocket_handshake(State=#state{socket=Socket, transport=Transport,
{<< "http", Location/binary >>, Req1} = cowboy_req:url(Req),
{ok, Req2} = cowboy_req:upgrade_reply(
<<"101 WebSocket Protocol Handshake">>,
- [{<<"Upgrade">>, <<"WebSocket">>},
- {<<"Sec-Websocket-Location">>, << "ws", Location/binary >>},
- {<<"Sec-Websocket-Origin">>, Origin}],
+ [{<<"upgrade">>, <<"WebSocket">>},
+ {<<"sec-websocket-location">>, << "ws", Location/binary >>},
+ {<<"sec-websocket-origin">>, Origin}],
Req1),
%% Flush the resp_sent message before moving on.
receive {cowboy_req, resp_sent} -> ok after 0 -> ok end,
@@ -171,18 +176,20 @@ websocket_handshake(State=#state{socket=Socket, transport=Transport,
handler_before_loop(State#state{messages=Transport:messages()},
Req4, HandlerState, <<>>);
_Any ->
- closed %% If an error happened reading the body, stop there.
+ %% If an error happened reading the body, stop there.
+ handler_terminate(State, Req3, HandlerState, {error, closed})
end;
websocket_handshake(State=#state{transport=Transport, challenge=Challenge},
Req, HandlerState) ->
{ok, Req2} = cowboy_req:upgrade_reply(
101,
- [{<<"Upgrade">>, <<"websocket">>},
- {<<"Sec-Websocket-Accept">>, Challenge}],
+ [{<<"upgrade">>, <<"websocket">>},
+ {<<"sec-websocket-accept">>, Challenge}],
Req),
%% Flush the resp_sent message before moving on.
receive {cowboy_req, resp_sent} -> ok after 0 -> ok end,
- handler_before_loop(State#state{messages=Transport:messages()},
+ State2 = handler_loop_timeout(State),
+ handler_before_loop(State2#state{messages=Transport:messages()},
Req2, HandlerState, <<>>).
-spec handler_before_loop(#state{}, cowboy_req:req(), any(), binary()) -> closed.
@@ -190,15 +197,13 @@ handler_before_loop(State=#state{
socket=Socket, transport=Transport, hibernate=true},
Req, HandlerState, SoFar) ->
Transport:setopts(Socket, [{active, once}]),
- State2 = handler_loop_timeout(State),
catch erlang:hibernate(?MODULE, handler_loop,
- [State2#state{hibernate=false}, Req, HandlerState, SoFar]),
+ [State#state{hibernate=false}, Req, HandlerState, SoFar]),
closed;
handler_before_loop(State=#state{socket=Socket, transport=Transport},
Req, HandlerState, SoFar) ->
Transport:setopts(Socket, [{active, once}]),
- State2 = handler_loop_timeout(State),
- handler_loop(State2, Req, HandlerState, SoFar).
+ handler_loop(State, Req, HandlerState, SoFar).
-spec handler_loop_timeout(#state{}) -> #state{}.
handler_loop_timeout(State=#state{timeout=infinity}) ->
@@ -216,7 +221,8 @@ handler_loop(State=#state{
Req, HandlerState, SoFar) ->
receive
{OK, Socket, Data} ->
- websocket_data(State, Req, HandlerState,
+ State2 = handler_loop_timeout(State),
+ websocket_data(State2, Req, HandlerState,
<< SoFar/binary, Data/binary >>);
{Closed, Socket} ->
handler_terminate(State, Req, HandlerState, {error, closed});
@@ -452,38 +458,76 @@ handler_call(State=#state{handler=Handler, opts=Opts}, Req, HandlerState,
Req2, HandlerState2, RemainingData);
{reply, Payload, Req2, HandlerState2}
when is_tuple(Payload) ->
- ok = websocket_send(Payload, State),
- NextState(State, Req2, HandlerState2, RemainingData);
+ case websocket_send(Payload, State) of
+ ok ->
+ State2 = handler_loop_timeout(State),
+ NextState(State2, Req2, HandlerState2, RemainingData);
+ shutdown ->
+ handler_terminate(State, Req2, HandlerState,
+ {normal, shutdown});
+ {error, _} = Error ->
+ handler_terminate(State, Req2, HandlerState2, Error)
+ end;
{reply, Payload, Req2, HandlerState2, hibernate}
when is_tuple(Payload) ->
- ok = websocket_send(Payload, State),
- NextState(State#state{hibernate=true},
- Req2, HandlerState2, RemainingData);
+ case websocket_send(Payload, State) of
+ ok ->
+ State2 = handler_loop_timeout(State),
+ NextState(State2#state{hibernate=true},
+ Req2, HandlerState2, RemainingData);
+ shutdown ->
+ handler_terminate(State, Req2, HandlerState,
+ {normal, shutdown});
+ {error, _} = Error ->
+ handler_terminate(State, Req2, HandlerState2, Error)
+ end;
{reply, Payload, Req2, HandlerState2}
when is_list(Payload) ->
- ok = websocket_send_many(Payload, State),
- NextState(State, Req2, HandlerState2, RemainingData);
+ case websocket_send_many(Payload, State) of
+ ok ->
+ State2 = handler_loop_timeout(State),
+ NextState(State2, Req2, HandlerState2, RemainingData);
+ shutdown ->
+ handler_terminate(State, Req2, HandlerState,
+ {normal, shutdown});
+ {error, _} = Error ->
+ handler_terminate(State, Req2, HandlerState2, Error)
+ end;
{reply, Payload, Req2, HandlerState2, hibernate}
when is_list(Payload) ->
- ok = websocket_send_many(Payload, State),
- NextState(State#state{hibernate=true},
- Req2, HandlerState2, RemainingData);
+ case websocket_send_many(Payload, State) of
+ ok ->
+ State2 = handler_loop_timeout(State),
+ NextState(State2#state{hibernate=true},
+ Req2, HandlerState2, RemainingData);
+ shutdown ->
+ handler_terminate(State, Req2, HandlerState,
+ {normal, shutdown});
+ {error, _} = Error ->
+ handler_terminate(State, Req2, HandlerState2, Error)
+ end;
{shutdown, Req2, HandlerState2} ->
websocket_close(State, Req2, HandlerState2, {normal, shutdown})
catch Class:Reason ->
PLReq = cowboy_req:to_list(Req),
error_logger:error_msg(
- "** Handler ~p terminating in ~p/3~n"
+ "** Cowboy handler ~p terminating in ~p/~p~n"
" 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, Class, Reason, Message, Opts,
+ [Handler, Callback, 3, Class, Reason, Message, Opts,
HandlerState, PLReq, erlang:get_stacktrace()]),
websocket_close(State, Req, HandlerState, {error, handler})
end.
--spec websocket_send({text | binary | ping | pong, binary()}, #state{})
- -> ok | {error, atom()}.
+websocket_opcode(text) -> 1;
+websocket_opcode(binary) -> 2;
+websocket_opcode(close) -> 8;
+websocket_opcode(ping) -> 9;
+websocket_opcode(pong) -> 10.
+
+-spec websocket_send(frame(), #state{})
+ -> ok | shutdown | {error, atom()}.
%% hixie-76 text frame.
websocket_send({text, Payload}, #state{
socket=Socket, transport=Transport, version=0}) ->
@@ -491,24 +535,52 @@ websocket_send({text, Payload}, #state{
%% Ignore all unknown frame types for compatibility with hixie 76.
websocket_send(_Any, #state{version=0}) ->
ok;
+websocket_send(Type, #state{socket=Socket, transport=Transport})
+ when Type =:= close ->
+ Opcode = websocket_opcode(Type),
+ case Transport:send(Socket, << 1:1, 0:3, Opcode:4, 0:8 >>) of
+ ok -> shutdown;
+ Error -> Error
+ end;
+websocket_send(Type, #state{socket=Socket, transport=Transport})
+ when Type =:= ping; Type =:= pong ->
+ Opcode = websocket_opcode(Type),
+ Transport:send(Socket, << 1:1, 0:3, Opcode:4, 0:8 >>);
+websocket_send({close, Payload}, State) ->
+ websocket_send({close, 1000, Payload}, State);
+websocket_send({Type = close, StatusCode, Payload}, #state{
+ socket=Socket, transport=Transport}) ->
+ Opcode = websocket_opcode(Type),
+ Len = 2 + iolist_size(Payload),
+ %% Control packets must not be > 125 in length.
+ true = Len =< 125,
+ BinLen = hybi_payload_length(Len),
+ Transport:send(Socket,
+ [<< 1:1, 0:3, Opcode:4, 0:1, BinLen/bits, StatusCode:16 >>, Payload]),
+ shutdown;
websocket_send({Type, Payload}, #state{socket=Socket, transport=Transport}) ->
- Opcode = case Type of
- text -> 1;
- binary -> 2;
- ping -> 9;
- pong -> 10
+ Opcode = websocket_opcode(Type),
+ Len = iolist_size(Payload),
+ %% Control packets must not be > 125 in length.
+ true = if Type =:= ping; Type =:= pong ->
+ Len =< 125;
+ true ->
+ true
end,
- Len = hybi_payload_length(iolist_size(Payload)),
- Transport:send(Socket, [<< 1:1, 0:3, Opcode:4, 0:1, Len/bits >>,
- Payload]).
+ BinLen = hybi_payload_length(Len),
+ Transport:send(Socket,
+ [<< 1:1, 0:3, Opcode:4, 0:1, BinLen/bits >>, Payload]).
--spec websocket_send_many([{text | binary | ping | pong, binary()}], #state{})
- -> ok | {error, atom()}.
+-spec websocket_send_many([frame()], #state{})
+ -> ok | shutdown | {error, atom()}.
websocket_send_many([], _) ->
ok;
websocket_send_many([Frame|Tail], State) ->
- ok = websocket_send(Frame, State),
- websocket_send_many(Tail, State).
+ case websocket_send(Frame, State) of
+ ok -> websocket_send_many(Tail, State);
+ shutdown -> shutdown;
+ Error -> Error
+ end.
-spec websocket_close(#state{}, cowboy_req:req(), any(), {atom(), atom()})
-> closed.
@@ -516,7 +588,6 @@ websocket_close(State=#state{socket=Socket, transport=Transport, version=0},
Req, HandlerState, Reason) ->
Transport:send(Socket, << 255, 0 >>),
handler_terminate(State, Req, HandlerState, Reason);
-%% @todo Send a Payload? Using Reason is usually good but we're quite careless.
websocket_close(State=#state{socket=Socket, transport=Transport},
Req, HandlerState, Reason) ->
Transport:send(Socket, << 1:1, 0:3, 8:4, 0:8 >>),
@@ -531,11 +602,11 @@ handler_terminate(#state{handler=Handler, opts=Opts},
catch Class:Reason ->
PLReq = cowboy_req:to_list(Req),
error_logger:error_msg(
- "** Handler ~p terminating in websocket_terminate/3~n"
+ "** Cowboy handler ~p terminating in ~p/~p~n"
" 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, Class, Reason, TerminateReason, Opts,
+ [Handler, websocket_terminate, 3, Class, Reason, TerminateReason, Opts,
HandlerState, PLReq, erlang:get_stacktrace()])
end,
closed.