Permalink
Browse files

add close callback for websockets

WebSocket clients can close their end of the connection, and RFC 6455
requires servers to handle that message appropriately. This change
adds a new callback message for basic WebSocket callback modules. The
server now also replies to client "close" messages with a "close"
reply, as RFC 6455 requires. WebSockets documentation updated as well.

Also included a message ordering fix from Jan Bothma.
  • Loading branch information...
1 parent 0944464 commit 16834ce8818f70310034a7cbf00333a09d766226 @vinoski vinoski committed Feb 10, 2012
Showing with 62 additions and 16 deletions.
  1. +24 −8 doc/yaws.tex
  2. +4 −1 examples/src/basic_echo_callback.erl
  3. +34 −7 src/yaws_websockets.erl
View
@@ -2726,8 +2726,8 @@ \chapter{WebSocket Protocol Support}
allow the client to establish a WebSocket connection to the
server. These connections are handled by the code in
\verb+www/websockets_example_endpoint.yaws+, which when invoked simply
-establishes \\ \verb+src/basic_echo_callback.erl+ as the WebSocket
-callback module for the connection.
+establishes \\ \verb+examples/src/basic_echo_callback.erl+ as the
+WebSocket callback module for the connection.
\section{WebSocket Callback Modules}
@@ -2749,6 +2749,14 @@ \subsection{Basic Callback Modules}
\item \verb+{binary, Message}+ --- the callback receives an
unfragmented binary message.
+\item \verb+{close, Status, Reason}+ --- the callback receives a
+ notification that the client has closed the socket. \verb+Status+ is
+ the numerical status code sent by the client or the value 1000 (as
+ suggested by RFC 6455 section 7.4.1) if the client sent no status
+ code. \verb+Reason+ is a binary containing any text the client sent
+ to indicate the reason for closing the socket; this binary may be
+ empty.
+
\end{itemize}
The \verb+handle_message/1+ callback function supplies one of the
@@ -2765,7 +2773,11 @@ \subsection{Basic Callback Modules}
\item \verb+{close, Reason}+ --- close the connection and exit the
handling process with \verb+Reason+. For a regular non-error close,
- \verb+Reason+ should be the atom \verb+normal+.
+ \verb+Reason+ should be the atom \verb+normal+. \verb+Reason+ may
+ alternatively be any allowed numerical value specified for
+ \verb+close+ frames in section 7.4 of RFC 6455. Callback handlers
+ for \verb+close+ messages from the client must always return
+ \verb+{close, Reason}+.
\end{itemize}
@@ -2808,9 +2820,9 @@ \subsection{Advanced Callback Modules}
themselves---supply a \verb+handle_message/2+ callback function.
To indicate an advanced callback module, include
-\verb+{callback, {advanced, InitialState}}+ in the \verb+Options+ list
-when you return \verb+{websocket, CallbackModule, Options}+ from your
-\verb+out/1+ function, as described above.
+\verb+{callback, {advanced, InitialState}}+ in the \\ \verb+Options+
+list when you return \verb+{websocket, CallbackModule, Options}+ from
+your \verb+out/1+ function, as described above.
The arguments to the \verb+handle_message/2+ callback
function are as follows:
@@ -2839,13 +2851,17 @@ \subsection{Advanced Callback Modules}
callback module.
\item \verb+{reply, Reply, State}+ --- reply to the received message
- with \verb+Reply+, which is either \verb+{text, Data}+ or
+ with \verb+Reply+, which is either \\ \verb+{text, Data}+ or
\verb+{binary, Data}+. \verb+State+ is the (possibly updated) state
for the callback module.
\item \verb+{close, Reason}+ --- close the connection and exit the
handling process with \verb+Reason+. For a regular non-error close,
- \verb+Reason+ should be the atom \verb+normal+.
+ \verb+Reason+ should be the atom \verb+normal+. \verb+Reason+ may
+ alternatively be any allowed numerical value specified for
+ \verb+close+ frames in section 7.4 of RFC 6455. Callback handlers
+ for \verb+close+ frames from the client must always return
+ \verb+{close, Reason}+.
\end{itemize}
@@ -28,7 +28,10 @@ handle_message({text, Message}) ->
{reply, {text, <<Message/binary>>}};
handle_message({binary, Message}) ->
- {reply, {binary, Message}}.
+ {reply, {binary, Message}};
+
+handle_message({close, _Status, _Reason}) ->
+ {close, normal}.
say_hi(Pid) ->
@@ -177,8 +177,7 @@ handle_result_fun(WSState) ->
noreply ->
ok;
{close, Reason} ->
- exit(Reason)
-
+ do_close(WSState, Reason)
end
end.
@@ -191,10 +190,19 @@ do_callback_fun(WSState, CallbackMod) ->
{noreply, NewCallbackState} ->
NewCallbackState;
{close, Reason} ->
- exit(Reason)
+ %% the following does not return
+ do_close(WSState, Reason)
end
end.
+do_close(WSState, Reason) ->
+ Status = case Reason of
+ _ when is_integer(Reason) -> Reason;
+ _ -> 1000
+ end,
+ send(WSState, {close, <<Status:16/big>>}),
+ exit(Reason).
+
basic_messages(FrameInfos, {FragType, FragAcc}) ->
{Messages, NewFragType, NewFragAcc}
= lists:foldl(fun handle_message/2, {[], FragType, FragAcc}, FrameInfos),
@@ -221,13 +229,13 @@ handle_message(#ws_frame_info{fin=1,
{Messages, text, FragAcc}) ->
Unfragged = <<FragAcc/binary, Data/binary>>,
NewMessage = {text, Unfragged},
- {[NewMessage | Messages], none, <<>>};
+ {Messages ++ [NewMessage], none, <<>>};
%% unfragmented text message
handle_message(#ws_frame_info{opcode=text, data=Data},
{Messages, none, <<>>}) ->
NewMessage = {text, Data},
- {[NewMessage | Messages], none, <<>>};
+ {Messages ++ [NewMessage], none, <<>>};
%% end of binary fragmented message
handle_message(#ws_frame_info{fin=1,
@@ -236,13 +244,13 @@ handle_message(#ws_frame_info{fin=1,
{Messages, binary, FragAcc}) ->
Unfragged = <<FragAcc/binary, Data/binary>>,
NewMessage = {binary, Unfragged},
- {[NewMessage|Messages], none, <<>>};
+ {Messages ++ [NewMessage], none, <<>>};
handle_message(#ws_frame_info{opcode=binary,
data=Data},
{Messages, none, <<>>}) ->
NewMessage = {binary, Data},
- {[NewMessage|Messages], none, <<>>};
+ {Messages ++ [NewMessage], none, <<>>};
handle_message(#ws_frame_info{opcode=ping,
data=Data,
@@ -257,6 +265,25 @@ handle_message(#ws_frame_info{opcode=pong}, Acc) ->
%% draft-ietf-hybi-thewebsocketprotocol-08#section-4
Acc;
+%% According to RFC 6455 section 5.4, control messages like close
+%% MAY be injected in the middle of a fragmented message, which is
+%% why we pass FragType and FragAcc along below. Whether any clients
+%% actually do this in practice, I don't know.
+handle_message(#ws_frame_info{opcode=close,
+ length=Len,
+ data=Data},
+ {Messages, FragType, FragAcc}) ->
+ NewMessage = case Len of
+ 0 ->
+ %% RFC 6455 section 7.4.1:
+ %% status code 1000 means "normal"
+ {close, 1000, <<>>};
+ _ ->
+ <<Status:16/big, Msg/binary>> = Data,
+ {close, Status, Msg}
+ end,
+ {Messages ++ [NewMessage], FragType, FragAcc};
+
handle_message(#ws_frame_info{}, Acc) ->
Acc.

0 comments on commit 16834ce

Please sign in to comment.