Skip to content

Commit 699569e

Browse files
Updated terminate typespec, cleanup, exit websocket process with non-normal code on error
1 parent 0cfa285 commit 699569e

File tree

2 files changed

+76
-116
lines changed

2 files changed

+76
-116
lines changed

src/websocket_client.erl

Lines changed: 55 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,7 @@
22
%% @doc Erlang websocket client
33
-module(websocket_client).
44

5-
-export([
6-
start_link/3,
5+
-export([start_link/3,
76
start_link/4,
87
cast/2,
98
send/2
@@ -182,7 +181,7 @@ handle_websocket_message(WSReq, HandlerState, Buffer, Message) ->
182181
ok = send(Frame, WSReq),
183182
websocket_loop(WSReq, HandlerState, Buffer);
184183
{_Closed, Socket} ->
185-
websocket_close(WSReq, HandlerState, {remote, closed});
184+
websocket_close(WSReq, HandlerState, remote);
186185
{_TransportType, Socket, Data} ->
187186
case Remaining of
188187
undefined ->
@@ -194,18 +193,11 @@ handle_websocket_message(WSReq, HandlerState, Buffer, Message) ->
194193
end;
195194
Msg ->
196195
try Handler:websocket_info(Msg, WSReq, HandlerState) of
197-
HandlerResponse ->
198-
handle_response(WSReq, HandlerResponse, Buffer)
199-
catch Class:Reason ->
200-
error_logger:error_msg(
201-
"** Websocket client ~p terminating in ~p/~p~n"
202-
" for the reason ~p:~p~n"
203-
"** Last message was ~p~n"
204-
"** Handler state was ~p~n"
205-
"** Stacktrace: ~p~n~n",
206-
[Handler, websocket_info, 3, Class, Reason, Msg, HandlerState,
207-
erlang:get_stacktrace()]),
208-
websocket_close(WSReq, HandlerState, Reason)
196+
HandlerResponse ->
197+
handle_response(WSReq, HandlerResponse, Buffer)
198+
catch
199+
_:Reason ->
200+
websocket_close(WSReq, HandlerState, {handler, Reason})
209201
end
210202
end.
211203

@@ -224,16 +216,26 @@ cancel_keepalive_timer(WSReq) ->
224216
Reason :: tuple()) -> ok.
225217
websocket_close(WSReq, HandlerState, Reason) ->
226218
Handler = websocket_req:handler(WSReq),
227-
try Handler:websocket_terminate(Reason, WSReq, HandlerState)
228-
catch Class:Reason2 ->
229-
error_logger:error_msg(
230-
"** Websocket handler ~p terminating in ~p/~p~n"
231-
" for the reason ~p:~p~n"
219+
try Handler:websocket_terminate(Reason, WSReq, HandlerState) of
220+
_ ->
221+
case Reason of
222+
normal -> ok;
223+
_ -> error_info(Handler, Reason, HandlerState)
224+
end,
225+
exit(Reason)
226+
catch
227+
_:Reason2 ->
228+
error_info(Handler, Reason2, HandlerState),
229+
exit(Reason2)
230+
end.
231+
232+
error_info(Handler, Reason, State) ->
233+
error_logger:error_msg(
234+
"** Websocket handler ~p terminating~n"
235+
"** for the reason ~p~n"
232236
"** Handler state was ~p~n"
233237
"** Stacktrace: ~p~n~n",
234-
[Handler, websocket_terminate, 3, Class, Reason2, HandlerState,
235-
erlang:get_stacktrace()])
236-
end.
238+
[Handler, Reason, State, erlang:get_stacktrace()]).
237239

238240
%% @doc Key sent in initial handshake
239241
-spec generate_ws_key() ->
@@ -339,18 +341,24 @@ retrieve_frame(WSReq, HandlerState, Opcode, Len, Data, Buffer) ->
339341
end,
340342
case OpcodeName of
341343
close when byte_size(FullPayload) >= 2 ->
342-
<< CodeBin:2/binary, ClosePayload/binary >> = FullPayload,
344+
<< CodeBin:2/binary, _ClosePayload/binary >> = FullPayload,
343345
Code = binary:decode_unsigned(CodeBin),
344346
Reason = case Code of
345-
1000 -> {normal, ClosePayload};
346-
1002 -> {error, badframe, ClosePayload};
347-
1007 -> {error, badencoding, ClosePayload};
348-
1011 -> {error, handler, ClosePayload};
349-
_ -> {remote, Code, ClosePayload}
347+
% 1000 indicates a normal closure, meaning that the purpose for
348+
% which the connection was established has been fulfilled.
349+
1000 -> normal;
350+
351+
% 1001 indicates that an endpoint is "going away", such as a server
352+
% going down or a browser having navigated away from a page.
353+
1001 -> normal;
354+
355+
% See https://tools.ietf.org/html/rfc6455#section-7.4.1
356+
% for error code descriptions.
357+
_ -> {remote, Code}
350358
end,
351359
websocket_close(WSReq, HandlerState, Reason);
352360
close ->
353-
websocket_close(WSReq, HandlerState, {remote, <<>>});
361+
websocket_close(WSReq, HandlerState, remote);
354362
%% Non-control continuation frame
355363
_ when Opcode < 8, Continuation =/= undefined, Fin == 0 ->
356364
%% Append to previously existing continuation payloads and continue
@@ -366,36 +374,21 @@ retrieve_frame(WSReq, HandlerState, Opcode, Len, Data, Buffer) ->
366374
try Handler:websocket_handle(
367375
{ContinuationOpcodeName, DefragPayload},
368376
WSReq2, HandlerState) of
369-
HandlerResponse ->
370-
handle_response(websocket_req:remaining(undefined, WSReq1),
371-
HandlerResponse, Rest)
372-
catch Class:Reason ->
373-
error_logger:error_msg(
374-
"** Websocket client ~p terminating in ~p/~p~n"
375-
" for the reason ~p:~p~n"
376-
"** Websocket message was ~p~n"
377-
"** Handler state was ~p~n"
378-
"** Stacktrace: ~p~n~n",
379-
[Handler, websocket_handle, 3, Class, Reason, {ContinuationOpcodeName, DefragPayload}, HandlerState,
380-
erlang:get_stacktrace()]),
381-
websocket_close(WSReq, HandlerState, Reason)
377+
HandlerResponse ->
378+
handle_response(websocket_req:remaining(undefined, WSReq1),
379+
HandlerResponse, Rest)
380+
catch _:Reason ->
381+
websocket_close(WSReq, HandlerState, {handler, Reason})
382382
end;
383383
_ ->
384384
try Handler:websocket_handle(
385385
{OpcodeName, FullPayload},
386386
WSReq, HandlerState) of
387-
HandlerResponse ->
388-
handle_response(websocket_req:remaining(undefined, WSReq),
389-
HandlerResponse, Rest)
390-
catch Class:Reason ->
391-
error_logger:error_msg(
392-
"** Websocket client ~p terminating in ~p/~p~n"
393-
" for the reason ~p:~p~n"
394-
"** Handler state was ~p~n"
395-
"** Stacktrace: ~p~n~n",
396-
[Handler, websocket_handle, 3, Class, Reason, HandlerState,
397-
erlang:get_stacktrace()]),
398-
websocket_close(WSReq, HandlerState, Reason)
387+
HandlerResponse ->
388+
handle_response(websocket_req:remaining(undefined, WSReq),
389+
HandlerResponse, Rest)
390+
catch _:Reason ->
391+
websocket_close(WSReq, HandlerState, {handler, Reason})
399392
end
400393
end.
401394

@@ -407,11 +400,14 @@ handle_response(WSReq, {reply, Frame, HandlerState}, Buffer) ->
407400
%% we can still have more messages in buffer
408401
case websocket_req:remaining(WSReq) of
409402
%% buffer should not contain uncomplete messages
410-
undefined -> retrieve_frame(WSReq, HandlerState, Buffer);
403+
undefined ->
404+
retrieve_frame(WSReq, HandlerState, Buffer);
411405
%% buffer contain uncomplete message that shouldnt be parsed
412-
_ -> websocket_loop(WSReq, HandlerState, Buffer)
406+
_ ->
407+
websocket_loop(WSReq, HandlerState, Buffer)
413408
end;
414-
Reason -> websocket_close(WSReq, HandlerState, Reason)
409+
{error, Reason} ->
410+
websocket_close(WSReq, HandlerState, {local, Reason})
415411
end;
416412
handle_response(WSReq, {ok, HandlerState}, Buffer) ->
417413
%% we can still have more messages in buffer
@@ -424,7 +420,7 @@ handle_response(WSReq, {ok, HandlerState}, Buffer) ->
424420

425421
handle_response(WSReq, {close, Payload, HandlerState}, _) ->
426422
send({close, Payload}, WSReq),
427-
websocket_close(WSReq, HandlerState, {normal, Payload}).
423+
websocket_close(WSReq, HandlerState, normal).
428424

429425
%% @doc Encodes the data with a header (including a masking key) and
430426
%% masks the data

src/websocket_client_handler.erl

Lines changed: 21 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -4,62 +4,26 @@
44
-type keepalive() :: integer().
55

66
-type close_reason() ::
7-
% Either the local end was closed via a `{closed, Reason, State}` tuple
8-
% returned from websocket_handle/3 or websocket_info/3, or the remote end
9-
% was closed during graceful teardown of the connection via a close frame
10-
% (see https://tools.ietf.org/html/rfc6455#section-5.5.1).
11-
{normal, binary()} |
12-
% The transport failed to send (see http://erlang.org/doc/man/gen_tcp.html#send-2
13-
% or http://erlang.org/doc/man/ssl.html#send-2)
14-
{error, term()} |
15-
% The remote end was closed due to a protocol error
16-
% (see https://tools.ietf.org/html/rfc6455#section-7.4.1, status code 1002).
17-
{error, badframe, binary()} |
18-
% The remote end was closed due to an encoding error
19-
% (see https://tools.ietf.org/html/rfc6455#section-7.4.1, status code 1007).
20-
{error, badencoding, binary()} |
21-
% The remote end was closed unexpectedly
22-
% (see https://tools.ietf.org/html/rfc6455#section-7.4.1, status code 1011).
23-
{error, handler, binary()} |
24-
% The remote host closed the socket.
25-
{remote, closed} |
26-
% The remote end was closed for some other reason
27-
% (see https://tools.ietf.org/html/rfc6455#section-7.4.1).
28-
{remote, integer(), binary()} |
29-
% The remote host closed the socket.
30-
% TODO: How is this different than `{remote, closed}`?
31-
{remote, binary()} |
32-
% The handler terminated unexpectedly in websocket_handle/3 or websocket_info/3.
33-
term().
34-
35-
-type close_reason() ::
36-
% Either the local end was closed via a `{closed, Reason, State}` tuple
37-
% returned from websocket_handle/3 or websocket_info/3, or the remote end
38-
% was closed during graceful teardown of the connection via a close frame
39-
% (see https://tools.ietf.org/html/rfc6455#section-5.5.1).
40-
{normal, binary()} |
41-
% The transport failed to send (see http://erlang.org/doc/man/gen_tcp.html#send-2
42-
% or http://erlang.org/doc/man/ssl.html#send-2)
43-
{error, term()} |
44-
% The remote end was closed due to a protocol error
45-
% (see https://tools.ietf.org/html/rfc6455#section-7.4.1, status code 1002).
46-
{error, badframe, binary()} |
47-
% The remote end was closed due to an encoding error
48-
% (see https://tools.ietf.org/html/rfc6455#section-7.4.1, status code 1007).
49-
{error, badencoding, binary()} |
50-
% The remote end was closed unexpectedly
51-
% (see https://tools.ietf.org/html/rfc6455#section-7.4.1, status code 1011).
52-
{error, handler, binary()} |
53-
% The remote host closed the socket.
54-
{remote, closed} |
55-
% The remote end was closed for some other reason
56-
% (see https://tools.ietf.org/html/rfc6455#section-7.4.1).
57-
{remote, integer(), binary()} |
58-
% The remote host closed the socket.
59-
% TODO: How is this different than `{remote, closed}`?
60-
{remote, binary()} |
61-
% The handler terminated unexpectedly in websocket_handle/3 or websocket_info/3.
62-
term().
7+
% Either:
8+
% - The websocket was closed by a handler via a `{closed, Reason, State}` tuple
9+
% returned from websocket_handle/3 or websocket_info/3.
10+
% - A 'close' frame was received with code 1000 or 1001.
11+
normal |
12+
% The local end failed to send (see http://erlang.org/doc/man/gen_tcp.html#send-2
13+
% or http://erlang.org/doc/man/ssl.html#send-2). The second element in the
14+
% tuple is the same term that was wrapped in an `{error, Reason}` tuple by
15+
% `send/2`, i.e. `{error, closed}` will become `{local, closed}`, and not
16+
% `{local, {error, closed}}`.
17+
{local, term()} |
18+
% The remote end either closed abruptly, or closed after sending a 'close' frame
19+
% without a status code.
20+
remote |
21+
% The remote end closed with a status code (see https://tools.ietf.org/html/rfc6455#section-7.4.1).
22+
{remote, integer()} |
23+
% An asynchronous exception was raised during message handling, either in
24+
% websocket_handle/3 or websocket_info/3. The term raised is passed as the
25+
% second element in this tuple.
26+
{handler, term()}.
6327

6428
-callback init(list(), websocket_req:req()) ->
6529
{ok, state()}
@@ -73,6 +37,6 @@
7337
-callback websocket_info(any(), websocket_req:req(), state()) ->
7438
{ok, state()}
7539
| {reply, websocket_req:frame(), state()}
76-
| {close, binary(), state()}.
40+
| {close, binary(), state()}.
7741

7842
-callback websocket_terminate(close_reason(), websocket_req:req(), state()) -> ok.

0 commit comments

Comments
 (0)