aboutsummaryrefslogtreecommitdiff
path: root/src/web/ejabberd_websocket.erl
diff options
context:
space:
mode:
authorChristophe Romain <christophe.romain@process-one.net>2012-09-11 15:45:59 +0200
committerChristophe Romain <christophe.romain@process-one.net>2012-09-11 15:45:59 +0200
commit011535f0de1a14d6f5f411035bff9eeafec1c612 (patch)
treee60951904fbdc14dc126450c4d7515f51188d4b7 /src/web/ejabberd_websocket.erl
parentMerge branch '2.1.x' into 2.2.x (diff)
binary refactoring
Diffstat (limited to 'src/web/ejabberd_websocket.erl')
-rw-r--r--src/web/ejabberd_websocket.erl722
1 files changed, 384 insertions, 338 deletions
diff --git a/src/web/ejabberd_websocket.erl b/src/web/ejabberd_websocket.erl
index 8030842a0..fde8baff6 100644
--- a/src/web/ejabberd_websocket.erl
+++ b/src/web/ejabberd_websocket.erl
@@ -8,12 +8,12 @@
%%% (http://github.com/ostinelli/misultin/blob/master/src/misultin_websocket.erl)
%%% Copyright (C) 2010, Roberto Ostinelli <roberto@ostinelli.net>, Joe Armstrong.
%%% All rights reserved.
-%%%
+%%%
%%% Code portions from Joe Armstrong have been originally taken under MIT license at the address:
%%% <http://armstrongonsoftware.blogspot.com/2009/12/comet-is-dead-long-live-websockets.html>
%%%
%%% BSD License
-%%%
+%%%
%%% Redistribution and use in source and binary forms, with or without modification, are permitted provided
%%% that the following conditions are met:
%%%
@@ -36,400 +36,446 @@
%%% ejabberd, Copyright (C) 2002-2010 ProcessOne
%%%----------------------------------------------------------------------
--module (ejabberd_websocket).
+-module(ejabberd_websocket).
+
-author('ecestari@process-one.net').
+
-export([connect/2, check/2, is_acceptable/1]).
+
-include("ejabberd.hrl").
+
-include("jlib.hrl").
+
-include("ejabberd_http.hrl").
-check(_Path, Headers)->
- % set supported websocket protocols, order does matter
- VsnSupported = [{'draft-hybi', 8}, {'draft-hybi', 13}, {'draft-hixie', 0}, {'draft-hixie', 68}],
- % checks
- check_websockets(VsnSupported, Headers).
+check(_Path, Headers) ->
+ VsnSupported = [{'draft-hybi', 8}, {'draft-hybi', 13},
+ {'draft-hixie', 0}, {'draft-hixie', 68}],
+ check_websockets(VsnSupported, Headers).
-% Checks if websocket can be access by client
-% If origins are set in configuration, check if it belongs
-% If origins not set, access is open.
-is_acceptable(#ws{origin=Origin, protocol=Protocol,
- headers = Headers, acceptable_origins = Origins, auth_module=undefined})->
- ClientProtocol = lists:keyfind("Sec-Websocket-Protocol",1, Headers),
- case {(Origins == []) or lists:member(Origin, Origins), ClientProtocol, Protocol } of
- {false, _, _} ->
- ?INFO_MSG("client does not come from authorized origin", []),
- false;
- {_, false, _} ->
- true;
- {_, {_, P}, P} ->
- true;
- _ = E->
- ?INFO_MSG("Wrong protocol requested : ~p", [E]),
- false
- end;
-is_acceptable(#ws{local_path=LocalPath, origin=Origin, ip=IP, q=Q, protocol=Protocol, headers = Headers,auth_module=Module})->
- Module:is_acceptable(LocalPath, Q, Origin, Protocol, IP, Headers).
+is_acceptable(#ws{origin = Origin, protocol = Protocol,
+ headers = Headers, acceptable_origins = Origins,
+ auth_module = undefined}) ->
+ ClientProtocol =
+ lists:keyfind(<<"Sec-Websocket-Protocol">>, 1, Headers),
+ case {(Origins == []) or lists:member(Origin, Origins),
+ ClientProtocol, Protocol}
+ of
+ {false, _, _} ->
+ ?INFO_MSG("client does not come from authorized "
+ "origin",
+ []),
+ false;
+ {_, false, _} -> true;
+ {_, {_, P}, P} -> true;
+ _ = E ->
+ ?INFO_MSG("Wrong protocol requested : ~p", [E]), false
+ end;
+is_acceptable(#ws{local_path = LocalPath,
+ origin = Origin, ip = IP, q = Q, protocol = Protocol,
+ headers = Headers, auth_module = Module}) ->
+ Module:is_acceptable(LocalPath, Q, Origin, Protocol, IP,
+ Headers).
-% Connect and handshake with Websocket.
-connect(#ws{vsn = Vsn, socket = Socket, q=Q,origin=Origin, host=Host, port=Port, sockmod = SockMod, path = Path, headers = Headers, ws_autoexit = WsAutoExit} = Ws, WsLoop) ->
- % build handshake
- HandshakeServer = handshake(Vsn, Socket,SockMod, Headers, {Path, Q, Origin, Host, Port}),
- % send handshake back
- SockMod:send(Socket, HandshakeServer),
- ?DEBUG("Sent handshake response : ~p", [HandshakeServer]),
- Ws0 = ejabberd_ws:new(Ws#ws{origin = Origin, host = Host}, self()),
- %?DEBUG("Ws0 : ~p",[Ws0]),
- % add data to ws record and spawn controlling process
- {ok, WsHandleLoopPid} = WsLoop:start_link(Ws0),
- erlang:monitor(process, WsHandleLoopPid),
- % set opts
- case SockMod of
- gen_tcp ->
- inet:setopts(Socket, [{packet, 0}, {active, true}]);
- _ ->
- SockMod:setopts(Socket, [{packet, 0}, {active, true}])
- end,
- % start listening for incoming data
- ws_loop(Vsn, none, Socket, WsHandleLoopPid, SockMod, WsAutoExit).
+connect(#ws{vsn = Vsn, socket = Socket, q = Q,
+ origin = Origin, host = Host, port = Port,
+ sockmod = SockMod, path = Path, headers = Headers,
+ ws_autoexit = WsAutoExit} =
+ Ws,
+ WsLoop) ->
+ HandshakeServer = handshake(Vsn, Socket, SockMod,
+ Headers, {Path, Q, Origin, Host, Port}),
+ SockMod:send(Socket, HandshakeServer),
+ ?DEBUG("Sent handshake response : ~p",
+ [HandshakeServer]),
+ Ws0 = ejabberd_ws:new(Ws#ws{origin = Origin,
+ host = Host},
+ self()),
+ {ok, WsHandleLoopPid} = WsLoop:start_link(Ws0),
+ erlang:monitor(process, WsHandleLoopPid),
+ case SockMod of
+ gen_tcp ->
+ inet:setopts(Socket, [{packet, 0}, {active, true}]);
+ _ ->
+ SockMod:setopts(Socket, [{packet, 0}, {active, true}])
+ end,
+ ws_loop(Vsn, none, Socket, WsHandleLoopPid, SockMod,
+ WsAutoExit).
-
check_websockets([], _Headers) -> false;
-check_websockets([Vsn|T], Headers) ->
- case check_websocket(Vsn, Headers) of
- false -> check_websockets(T, Headers);
- Value -> Value
- end.
+check_websockets([Vsn | T], Headers) ->
+ case check_websocket(Vsn, Headers) of
+ false -> check_websockets(T, Headers);
+ Value -> Value
+ end.
-% Function: {true, Vsn} | false
-% Description: Check if the incoming request is a websocket request.
check_websocket({'draft-hixie', 0} = Vsn, Headers) ->
- %?DEBUG("testing for websocket protocol ~p", [Vsn]),
- % set required headers
- RequiredHeaders = [
- {'Upgrade', "WebSocket"}, {'Connection', "Upgrade"}, {'Host', ignore}, {"Origin", ignore},
- {"Sec-Websocket-Key1", ignore}, {"Sec-Websocket-Key2", ignore}
- ],
- % check for headers existance
- case check_headers(Headers, RequiredHeaders) of
- true ->
- % return
- {true, Vsn};
- _RemainingHeaders ->
- %?DEBUG("not protocol ~p, remaining headers: ~p", [Vsn, _RemainingHeaders]),
- false
- end;
+ RequiredHeaders = [{'Upgrade', <<"WebSocket">>},
+ {'Connection', <<"Upgrade">>}, {'Host', ignore},
+ {<<"Origin">>, ignore},
+ {<<"Sec-Websocket-Key1">>, ignore},
+ {<<"Sec-Websocket-Key2">>, ignore}],
+ case check_headers(Headers, RequiredHeaders) of
+ true -> {true, Vsn};
+ _RemainingHeaders ->
+ %?DEBUG("not protocol ~p, remaining headers: ~p", [Vsn, _RemainingHeaders]),
+ false
+ end;
check_websocket({'draft-hixie', 68} = Vsn, Headers) ->
- %?DEBUG("testing for websocket protocol ~p", [Vsn]),
- % set required headers
- RequiredHeaders = [
- {'Upgrade', "WebSocket"}, {'Connection', "Upgrade"}, {'Host', ignore}, {"Origin", ignore}
- ],
- % check for headers existance
- case check_headers(Headers, RequiredHeaders) of
- true -> {true, Vsn};
- _RemainingHeaders ->
- %?DEBUG("not protocol ~p, remaining headers: ~p", [Vsn, _RemainingHeaders]),
- false
- end;
+ RequiredHeaders = [{'Upgrade', <<"WebSocket">>},
+ {'Connection', <<"Upgrade">>}, {'Host', ignore},
+ {<<"Origin">>, ignore}],
+ case check_headers(Headers, RequiredHeaders) of
+ true -> {true, Vsn};
+ _RemainingHeaders ->
+ %?DEBUG("not protocol ~p, remaining headers: ~p", [Vsn, _RemainingHeaders]),
+ false
+ end;
check_websocket({'draft-hybi', 8} = Vsn, Headers) ->
- %?DEBUG("testing for websocket protocol ~p", [Vsn]),
- % set required headers
- RequiredHeaders = [
- {'Upgrade', "websocket"}, {'Connection', ignore}, {'Host', ignore},
- {"Sec-Websocket-Key", ignore}, {"Sec-Websocket-Version", "8"}
- ],
- % check for headers existance
- case check_headers(Headers, RequiredHeaders) of
- true -> {true, Vsn};
- RemainingHeaders ->
- %%?INFO_MSG("not protocol ~p, remaining headers: ~p", [Vsn, RemainingHeaders]),
- false
- end;
+ RequiredHeaders = [{'Upgrade', <<"websocket">>},
+ {'Connection', ignore}, {'Host', ignore},
+ {<<"Sec-Websocket-Key">>, ignore},
+ {<<"Sec-Websocket-Version">>, <<"8">>}],
+ case check_headers(Headers, RequiredHeaders) of
+ true -> {true, Vsn};
+ _RemainingHeaders ->
+ %%?INFO_MSG("not protocol ~p, remaining headers: ~p", [Vsn, RemainingHeaders]),
+ false
+ end;
check_websocket({'draft-hybi', 13} = Vsn, Headers) ->
- %?DEBUG("testing for websocket protocol ~p", [Vsn]),
- % set required headers
- RequiredHeaders = [
- {'Upgrade', "websocket"}, {'Connection', ignore}, {'Host', ignore},
- {"Sec-Websocket-Key", ignore}, {"Sec-Websocket-Version", "13"}
- ],
- % check for headers existance
- case check_headers(Headers, RequiredHeaders) of
- true -> {true, Vsn};
- RemainingHeaders ->
- %%?INFO_MSG("not protocol ~p, remaining headers: ~p", [Vsn, RemainingHeaders]),
- false
- end;
-check_websocket(_Vsn, _Headers) -> false. % not implemented
+ RequiredHeaders = [{'Upgrade', <<"websocket">>},
+ {'Connection', ignore}, {'Host', ignore},
+ {<<"Sec-Websocket-Key">>, ignore},
+ {<<"Sec-Websocket-Version">>, <<"13">>}],
+ case check_headers(Headers, RequiredHeaders) of
+ true -> {true, Vsn};
+ _RemainingHeaders ->
+ %%?INFO_MSG("not protocol ~p, remaining headers: ~p", [Vsn, RemainingHeaders]),
+ false
+ end.
-% Function: true | [{RequiredTag, RequiredVal}, ..]
-% Description: Check if headers correspond to headers requirements.
check_headers(Headers, RequiredHeaders) ->
- F = fun({Tag, Val}) ->
- % see if the required Tag is in the Headers
- case lists:keyfind(Tag, 1, Headers) of
- false -> true; % header not found, keep in list
- {_, HVal} ->
- %?DEBUG("check: ~p", [{Tag, HVal,Val }]),
- case Val of
- ignore -> false; % ignore value -> ok, remove from list
- HVal -> false; % expected val -> ok, remove from list
- _ -> true % val is different, keep in list
- end
+ F = fun ({Tag, Val}) ->
+ case lists:keyfind(Tag, 1, Headers) of
+ false -> true; % header not found, keep in list
+ {_, HVal} ->
+ case Val of
+ ignore -> false; % ignore value -> ok, remove from list
+ HVal -> false; % expected val -> ok, remove from list
+ _ ->
+ true % val is different, keep in list
+ end
end
end,
- case lists:filter(F, RequiredHeaders) of
- [] -> true;
- MissingHeaders -> MissingHeaders
- end.
+ case lists:filter(F, RequiredHeaders) of
+ [] -> true;
+ MissingHeaders -> MissingHeaders
+ end.
-% Function: List
-% Description: Builds the server handshake response.
-handshake({'draft-hixie', 0}, Sock,SocketMod, Headers, {Path, Q,Origin, Host, Port}) ->
- % build data
- {_, Key1} = lists:keyfind("Sec-Websocket-Key1",1, Headers),
- {_, Key2} = lists:keyfind("Sec-Websocket-Key2",1, Headers),
- HostPort = case lists:keyfind('Host', 1, Headers) of
- {_, Value} -> Value;
- _ -> string:join([Host, integer_to_list(Port)],":")
- end,
- % handshake needs body of the request, still need to read it [TODO: default recv timeout hard set, will be exported when WS protocol is final]
- case SocketMod of
- gen_tcp ->
- inet:setopts(Sock, [{packet, raw}, {active, false}]);
- _ ->
- SocketMod:setopts(Sock, [{packet, raw}, {active, false}])
- end,
- Body = case SocketMod:recv(Sock, 8, 30*1000) of
- {ok, Bin} -> Bin;
- {error, timeout} ->
- ?WARNING_MSG("timeout in reading websocket body", []),
- <<>>;
- _Other ->
- ?ERROR_MSG("tcp error treating data: ~p", [_Other]),
- <<>>
- end,
- QParams = lists:map(
- fun({nokey,[]})->
- none;
- ({K, V})->
- K ++ "=" ++ V
- end, Q),
- QString = case QParams of
- [none]-> "";
- QParams-> "?" ++ string:join(QParams, "&")
- end,
- %?DEBUG("got content in body of websocket request: ~p, ~p", [Body,string:join([Host, Path],"/")]),
- % prepare handhsake response
- ["HTTP/1.1 101 WebSocket Protocol Handshake\r\n",
- "Upgrade: WebSocket\r\n",
- "Connection: Upgrade\r\n",
- "Sec-WebSocket-Origin: ", Origin, "\r\n",
- "Sec-WebSocket-Location: ws://", HostPort, "/", string:join(Path,"/"),
- QString, "\r\n\r\n",
- build_challenge({'draft-hixie', 0}, {Key1, Key2, Body})
- ];
-handshake({'draft-hixie', 68}, _Sock,_SocketMod, Headers, {Path, Origin, Host, Port}) ->
- HostPort = case lists:keyfind('Host', 1, Headers) of
- {_, Value} -> Value;
- _ -> string:join([Host, integer_to_list(Port)],":")
- end,
- ["HTTP/1.1 101 Web Socket Protocol Handshake\r\n",
- "Upgrade: WebSocket\r\n",
- "Connection: Upgrade\r\n",
- "WebSocket-Origin: ", Origin , "\r\n",
- "WebSocket-Location: ws://", HostPort, "/", string:join(Path,"/"),"\r\n\r\n"
- ];
-handshake({'draft-hybi', _}, Sock,SocketMod, Headers, {Path, Q,Origin, Host, Port}) ->
- {_, Key} = lists:keyfind("Sec-Websocket-Key",1, Headers),
- Hash = jlib:encode_base64(binary_to_list(sha:sha1(Key++"258EAFA5-E914-47DA-95CA-C5AB0DC85B11"))),
- ["HTTP/1.1 101 Switching Protocols\r\n",
- "Upgrade: websocket\r\n",
- "Connection: Upgrade\r\n",
- "Sec-WebSocket-Accept: ", Hash, "\r\n\r\n"
- ].
+handshake({'draft-hixie', 0}, Sock, SocketMod, Headers,
+ {Path, Q, Origin, Host, Port}) ->
+ {_, Key1} = lists:keyfind(<<"Sec-Websocket-Key1">>, 1,
+ Headers),
+ {_, Key2} = lists:keyfind(<<"Sec-Websocket-Key2">>, 1,
+ Headers),
+ HostPort = case lists:keyfind('Host', 1, Headers) of
+ {_, Value} -> Value;
+ _ ->
+ str:join([Host,
+ jlib:integer_to_binary(Port)],
+ <<":">>)
+ end,
+ case SocketMod of
+ gen_tcp ->
+ inet:setopts(Sock, [{packet, raw}, {active, false}]);
+ _ ->
+ SocketMod:setopts(Sock,
+ [{packet, raw}, {active, false}])
+ end,
+ Body = case SocketMod:recv(Sock, 8, 30 * 1000) of
+ {ok, Bin} -> Bin;
+ {error, timeout} ->
+ ?WARNING_MSG("timeout in reading websocket body", []),
+ <<>>;
+ _Other ->
+ ?ERROR_MSG("tcp error treating data: ~p", [_Other]),
+ <<>>
+ end,
+ QParams = lists:map(fun ({nokey, <<>>}) -> none;
+ ({K, V}) -> <<K/binary, "=", V/binary>>
+ end,
+ Q),
+ QString = case QParams of
+ [none] -> <<"">>;
+ QParams -> <<"?", (str:join(QParams, <<"&">>))/binary>>
+ end,
+ [<<"HTTP/1.1 101 WebSocket Protocol Handshake\r\n">>,
+ <<"Upgrade: WebSocket\r\n">>,
+ <<"Connection: Upgrade\r\n">>,
+ <<"Sec-WebSocket-Origin: ">>, Origin, <<"\r\n">>,
+ <<"Sec-WebSocket-Location: ws://">>, HostPort, <<"/">>,
+ str:join(Path, <<"/">>), QString, <<"\r\n\r\n">>,
+ build_challenge({'draft-hixie', 0},
+ {Key1, Key2, Body})];
+handshake({'draft-hixie', 68}, _Sock, _SocketMod,
+ Headers, {Path, _Q, Origin, Host, Port}) ->
+ HostPort = case lists:keyfind('Host', 1, Headers) of
+ {_, Value} -> Value;
+ _ ->
+ str:join([Host,
+ iolist_to_binary(integer_to_list(Port))],
+ <<":">>)
+ end,
+ [<<"HTTP/1.1 101 Web Socket Protocol Handshake\r\n">>,
+ <<"Upgrade: WebSocket\r\n">>,
+ <<"Connection: Upgrade\r\n">>, <<"WebSocket-Origin: ">>,
+ Origin, <<"\r\n">>, <<"WebSocket-Location: ws://">>,
+ HostPort, <<"/">>, str:join(Path, <<"/">>),
+ <<"\r\n\r\n">>];
+handshake({'draft-hybi', _}, _Sock, _SocketMod, Headers,
+ {_Path, _Q, _Origin, _Host, _Port}) ->
+ {_, Key} = lists:keyfind(<<"Sec-Websocket-Key">>, 1,
+ Headers),
+ Hash = jlib:encode_base64(
+ sha:sha1(<<Key/binary, "258EAFA5-E914-47DA-95CA-C5AB0DC85B11">>)),
+ [<<"HTTP/1.1 101 Switching Protocols\r\n">>,
+ <<"Upgrade: websocket\r\n">>,
+ <<"Connection: Upgrade\r\n">>,
+ <<"Sec-WebSocket-Accept: ">>, Hash, <<"\r\n\r\n">>].
-% Function: List
-% Description: Builds the challenge for a handshake response.
-% Code portions from Sergio Veiga <http://sergioveiga.com/index.php/2010/06/17/websocket-handshake-76-in-erlang/>
-build_challenge({'draft-hixie', 0}, {Key1, Key2, Key3}) ->
- Ikey1 = [D || D <- Key1, $0 =< D, D =< $9],
- Ikey2 = [D || D <- Key2, $0 =< D, D =< $9],
- Blank1 = length([D || D <- Key1, D =:= 32]),
- Blank2 = length([D || D <- Key2, D =:= 32]),
- Part1 = list_to_integer(Ikey1) div Blank1,
- Part2 = list_to_integer(Ikey2) div Blank2,
- Ckey = <<Part1:4/big-unsigned-integer-unit:8, Part2:4/big-unsigned-integer-unit:8, Key3/binary>>,
- erlang:md5(Ckey).
-
-
-ws_loop(Vsn, HandlerState, Socket, WsHandleLoopPid, SocketMode, WsAutoExit) ->
- receive
- {tcp, Socket, Data} ->
- %?ERROR_MSG("[WS recv] ~p~n[Buffer state] ~p", [Data, Buffer]),
- {NewHandlerState, ToSend} = handle_data(Vsn, HandlerState, Data, Socket, WsHandleLoopPid, SocketMode, WsAutoExit),
- lists:foreach(fun(Pkt) ->
- SocketMode:send(Socket, Pkt)
- end, ToSend),
- ws_loop(Vsn, NewHandlerState, Socket, WsHandleLoopPid, SocketMode, WsAutoExit);
- {tcp_closed, Socket} ->
- ?DEBUG("tcp connection was closed, exit", []),
- % close websocket and custom controlling loop
- websocket_close(Socket, WsHandleLoopPid, SocketMode, WsAutoExit);
- {'DOWN', Ref, process, WsHandleLoopPid, Reason} ->
- case Reason of
- normal ->
- %?DEBUG("linked websocket controlling loop stopped.", []);
- ok;
- _ ->
- ?ERROR_MSG("linked websocket controlling loop crashed with reason: ~p", [Reason])
+build_challenge({'draft-hixie', 0},
+ {Key1, Key2, Key3}) ->
+ Ikey1 = << <<D>> || <<D>> <= Key1, $0 =< D, D =< $9>>,
+ Ikey2 = << <<D>> || <<D>> <= Key2, $0 =< D, D =< $9>>,
+ Blank1 = byte_size(<< <<D>> || <<D>> <= Key1, D =:= 32>>),
+ Blank2 = byte_size(<< <<D>> || <<D>> <= Key2, D =:= 32>>),
+ Part1 = jlib:binary_to_integer(Ikey1) div Blank1,
+ Part2 = jlib:binary_to_integer(Ikey2) div Blank2,
+ Ckey = <<Part1:4/big-unsigned-integer-unit:8,
+ Part2:4/big-unsigned-integer-unit:8, Key3/binary>>,
+ erlang:md5(Ckey).
+
+ws_loop(Vsn, HandlerState, Socket, WsHandleLoopPid,
+ SocketMode, WsAutoExit) ->
+ receive
+ {tcp, Socket, Data} ->
+ {NewHandlerState, ToSend} = handle_data(Vsn,
+ HandlerState, Data, Socket,
+ WsHandleLoopPid, SocketMode,
+ WsAutoExit),
+ lists:foreach(fun (Pkt) -> SocketMode:send(Socket, Pkt)
end,
- % demonitor
- erlang:demonitor(Ref),
- % close websocket and custom controlling loop
- websocket_close(Socket, WsHandleLoopPid, SocketMode, WsAutoExit);
- {send, Data} ->
- %?DEBUG("sending data to websocket: ~p", [Data]),
- SocketMode:send(Socket, encode_frame(Vsn, Data)),
- ws_loop(Vsn, HandlerState, Socket, WsHandleLoopPid, SocketMode, WsAutoExit);
- shutdown ->
- ?DEBUG("shutdown request received, closing websocket with pid ~p", [self()]),
- % close websocket and custom controlling loop
- websocket_close(Socket, WsHandleLoopPid, SocketMode, WsAutoExit);
- _Ignored ->
- ?WARNING_MSG("received unexpected message, ignoring: ~p", [_Ignored]),
- ws_loop(Vsn, HandlerState, Socket, WsHandleLoopPid, SocketMode, WsAutoExit)
- end.
+ ToSend),
+ ws_loop(Vsn, NewHandlerState, Socket, WsHandleLoopPid,
+ SocketMode, WsAutoExit);
+ {tcp_closed, Socket} ->
+ ?DEBUG("tcp connection was closed, exit", []),
+ websocket_close(Socket, WsHandleLoopPid, SocketMode,
+ WsAutoExit);
+ {'DOWN', Ref, process, WsHandleLoopPid, Reason} ->
+ case Reason of
+ normal ->
+ %?DEBUG("linked websocket controlling loop stopped.", []);
+ ok;
+ _ ->
+ ?ERROR_MSG("linked websocket controlling loop crashed "
+ "with reason: ~p",
+ [Reason])
+ end,
+ erlang:demonitor(Ref),
+ websocket_close(Socket, WsHandleLoopPid, SocketMode,
+ WsAutoExit);
+ {send, Data} ->
+ SocketMode:send(Socket, encode_frame(Vsn, Data)),
+ ws_loop(Vsn, HandlerState, Socket, WsHandleLoopPid,
+ SocketMode, WsAutoExit);
+ shutdown ->
+ ?DEBUG("shutdown request received, closing websocket "
+ "with pid ~p",
+ [self()]),
+ websocket_close(Socket, WsHandleLoopPid, SocketMode,
+ WsAutoExit);
+ _Ignored ->
+ ?WARNING_MSG("received unexpected message, ignoring: ~p",
+ [_Ignored]),
+ ws_loop(Vsn, HandlerState, Socket, WsHandleLoopPid,
+ SocketMode, WsAutoExit)
+ end.
encode_frame({'draft-hybi', _}, Data, Opcode) ->
case byte_size(Data) of
- S1 when S1 < 126 ->
- <<1:1, 0:3, Opcode:4, 0:1, S1:7, Data/binary>>;
- S2 when S2 < 65536 ->
- <<1:1, 0:3, Opcode:4, 0:1, 126:7, S2:16, Data/binary>>;
- S3 ->
- <<1:1, 0:3, Opcode:4, 0:1, 127:7, S3:64, Data/binary>>
+ S1 when S1 < 126 ->
+ <<1:1, 0:3, Opcode:4, 0:1, S1:7, Data/binary>>;
+ S2 when S2 < 65536 ->
+ <<1:1, 0:3, Opcode:4, 0:1, 126:7, S2:16, Data/binary>>;
+ S3 ->
+ <<1:1, 0:3, Opcode:4, 0:1, 127:7, S3:64, Data/binary>>
end.
-encode_frame({'draft-hybi', _}=Vsn, Data) ->
+encode_frame({'draft-hybi', _} = Vsn, Data) ->
encode_frame(Vsn, Data, 1);
-encode_frame(_, Data) ->
- <<0, Data/binary, 255>>.
+encode_frame(_, Data) -> <<0, Data/binary, 255>>.
process_hixie_68(none, Data) ->
process_hixie_68({false, <<>>}, Data);
-process_hixie_68({false, <<>>}, <<0,T/binary>>) ->
+process_hixie_68({false, <<>>}, <<0, T/binary>>) ->
process_hixie_68({true, <<>>}, T);
-process_hixie_68(L, <<>>) ->
- {L, [], []};
-process_hixie_68({_, L}, <<255,T/binary>>) ->
+process_hixie_68(L, <<>>) -> {L, [], []};
+process_hixie_68({_, L}, <<255, T/binary>>) ->
{L2, Recv, Send} = process_hixie_68({false, <<>>}, T),
- {L2, [L|Recv], Send};
+ {L2, [L | Recv], Send};
process_hixie_68({true, L}, <<H/utf8, T/binary>>) ->
process_hixie_68({true, <<L/binary, H>>}, T).
--record(hybi_8_state, {mask=none, offset=0, left, final_frame=true, opcode, unprocessed = <<>>, unmasked = <<>>, unmasked_msg = <<>>}).
+-record(hybi_8_state,
+ {mask = none, offset = 0, left, final_frame = true,
+ opcode, unprocessed = <<>>, unmasked = <<>>,
+ unmasked_msg = <<>>}).
-decode_hybi_8_header(<<Final:1, _:3, Opcode:4, 0:1, Len:7, Data/binary>>) when Len < 126 ->
+decode_hybi_8_header(<<Final:1, _:3, Opcode:4, 0:1,
+ Len:7, Data/binary>>)
+ when Len < 126 ->
{Len, Final, Opcode, none, Data};
-decode_hybi_8_header(<<Final:1, _:3, Opcode:4, 0:1, 126:7, Len:16/integer, Data/binary>>) ->
+decode_hybi_8_header(<<Final:1, _:3, Opcode:4, 0:1,
+ 126:7, Len:16/integer, Data/binary>>) ->
{Len, Final, Opcode, none, Data};
-decode_hybi_8_header(<<Final:1, _:3, Opcode:4, 0:1, 127:7, Len:64/integer, Data/binary>>) ->
+decode_hybi_8_header(<<Final:1, _:3, Opcode:4, 0:1,
+ 127:7, Len:64/integer, Data/binary>>) ->
{Len, Final, Opcode, none, Data};
-decode_hybi_8_header(<<Final:1, _:3, Opcode:4, 1:1, Len:7, Mask:4/binary, Data/binary>>) when Len < 126 ->
+decode_hybi_8_header(<<Final:1, _:3, Opcode:4, 1:1,
+ Len:7, Mask:4/binary, Data/binary>>)
+ when Len < 126 ->
{Len, Final, Opcode, Mask, Data};
-decode_hybi_8_header(<<Final:1, _:3, Opcode:4, 1:1, 126:7, Len:16/integer, Mask:4/binary, Data/binary>>) ->
+decode_hybi_8_header(<<Final:1, _:3, Opcode:4, 1:1,
+ 126:7, Len:16/integer, Mask:4/binary, Data/binary>>) ->
{Len, Final, Opcode, Mask, Data};
-decode_hybi_8_header(<<Final:1, _:3, Opcode:4, 1:1, 127:7, Len:64/integer, Mask:4/binary, Data/binary>>) ->
+decode_hybi_8_header(<<Final:1, _:3, Opcode:4, 1:1,
+ 127:7, Len:64/integer, Mask:4/binary, Data/binary>>) ->
{Len, Final, Opcode, Mask, Data};
-decode_hybi_8_header(_) ->
- none.
+decode_hybi_8_header(_) -> none.
unmask_hybi_8_int(Offset, _, <<>>, Acc) ->
{Acc, Offset};
-unmask_hybi_8_int(0, <<M:32>>=Mask, <<N:32, Rest/binary>>, Acc) ->
- unmask_hybi_8_int(0, Mask, Rest, <<Acc/binary, (M bxor N):32>>);
-unmask_hybi_8_int(0, <<M:8, _/binary>>=Mask, <<N:8, Rest/binary>>, Acc) ->
- unmask_hybi_8_int(1, Mask, Rest, <<Acc/binary, (M bxor N):8>>);
-unmask_hybi_8_int(1, <<_:8, M:8, _/binary>>=Mask, <<N:8, Rest/binary>>, Acc) ->
- unmask_hybi_8_int(2, Mask, Rest, <<Acc/binary, (M bxor N):8>>);
-unmask_hybi_8_int(2, <<_:16, M:8, _/binary>>=Mask, <<N:8, Rest/binary>>, Acc) ->
- unmask_hybi_8_int(3, Mask, Rest, <<Acc/binary, (M bxor N):8>>);
-unmask_hybi_8_int(3, <<_:24, M:8>>=Mask, <<N:8, Rest/binary>>, Acc) ->
- unmask_hybi_8_int(0, Mask, Rest, <<Acc/binary, (M bxor N):8>>).
+unmask_hybi_8_int(0, <<M:32>> = Mask,
+ <<N:32, Rest/binary>>, Acc) ->
+ unmask_hybi_8_int(0, Mask, Rest,
+ <<Acc/binary, (M bxor N):32>>);
+unmask_hybi_8_int(0, <<M:8, _/binary>> = Mask,
+ <<N:8, Rest/binary>>, Acc) ->
+ unmask_hybi_8_int(1, Mask, Rest,
+ <<Acc/binary, (M bxor N):8>>);
+unmask_hybi_8_int(1, <<_:8, M:8, _/binary>> = Mask,
+ <<N:8, Rest/binary>>, Acc) ->
+ unmask_hybi_8_int(2, Mask, Rest,
+ <<Acc/binary, (M bxor N):8>>);
+unmask_hybi_8_int(2, <<_:16, M:8, _/binary>> = Mask,
+ <<N:8, Rest/binary>>, Acc) ->
+ unmask_hybi_8_int(3, Mask, Rest,
+ <<Acc/binary, (M bxor N):8>>);
+unmask_hybi_8_int(3, <<_:24, M:8>> = Mask,
+ <<N:8, Rest/binary>>, Acc) ->
+ unmask_hybi_8_int(0, Mask, Rest,
+ <<Acc/binary, (M bxor N):8>>).
-unmask_hybi_8(#hybi_8_state{mask=none}=State, Data) ->
+unmask_hybi_8(#hybi_8_state{mask = none} = State,
+ Data) ->
{State, Data};
-unmask_hybi_8(#hybi_8_state{mask=Mask, offset=Offset}=State, Data) ->
- {Unmasked, NewOffset} = unmask_hybi_8_int(Offset, Mask, Data, <<>>),
- {State#hybi_8_state{offset=NewOffset}, Unmasked}.
-
+unmask_hybi_8(#hybi_8_state{mask = Mask,
+ offset = Offset} =
+ State,
+ Data) ->
+ {Unmasked, NewOffset} = unmask_hybi_8_int(Offset, Mask,
+ Data, <<>>),
+ {State#hybi_8_state{offset = NewOffset}, Unmasked}.
process_hybi_8(none, Data) ->
process_hybi_8(#hybi_8_state{}, Data);
-process_hybi_8(State, <<>>) ->
- {State, [], []};
-process_hybi_8(#hybi_8_state{unprocessed=none, unmasked=UnmaskedPre, left=Left}=State,
- Data) when byte_size(Data) < Left ->
+process_hybi_8(State, <<>>) -> {State, [], []};
+process_hybi_8(#hybi_8_state{unprocessed = none,
+ unmasked = UnmaskedPre, left = Left} =
+ State,
+ Data)
+ when byte_size(Data) < Left ->
{State2, Unmasked} = unmask_hybi_8(State, Data),
- {State2#hybi_8_state{left=Left-byte_size(Data), unmasked=[UnmaskedPre, Unmasked]}, [], []};
-process_hybi_8(#hybi_8_state{unprocessed=none, unmasked=UnmaskedPre, opcode=Opcode,
- final_frame=Final, left=Left, unmasked_msg=UnmaskedMsg}=State, Data) ->
- {_State, Unmasked} = unmask_hybi_8(State, binary_part(Data, 0, Left)),
- Unprocessed = binary_part(Data, Left, byte_size(Data)-Left),
+ {State2#hybi_8_state{left = Left - byte_size(Data),
+ unmasked = [UnmaskedPre, Unmasked]},
+ [], []};
+process_hybi_8(#hybi_8_state{unprocessed = none,
+ unmasked = UnmaskedPre, opcode = Opcode,
+ final_frame = Final, left = Left,
+ unmasked_msg = UnmaskedMsg} =
+ State,
+ Data) ->
+ <<ToProcess:(Left)/binary, Unprocessed/binary>> = Data,
+ {_State, Unmasked} = unmask_hybi_8(State, ToProcess),
case Final of
- true ->
- {State3, Recv, Send} = process_hybi_8(#hybi_8_state{}, Unprocessed),
- case Opcode of
- 9 ->
- Frame = encode_frame({'draft-hybi', 8}, Unprocessed, 10),
- {State3#hybi_8_state{unmasked_msg=UnmaskedMsg}, Recv, [Frame|Send]};
- X when X < 3 ->
- {State3, [iolist_to_binary([UnmaskedMsg, UnmaskedPre, Unmasked])|Recv], Send};
- _ ->
- {State3#hybi_8_state{unmasked_msg=UnmaskedMsg}, Recv, Send}
- end;
- _ ->
- process_hybi_8(#hybi_8_state{unmasked_msg=[UnmaskedMsg, UnmaskedPre, Unmasked]}, Unprocessed)
+ true ->
+ {State3, Recv, Send} = process_hybi_8(#hybi_8_state{},
+ Unprocessed),
+ case Opcode of
+ 9 ->
+ Frame = encode_frame({'draft-hybi', 8}, Unprocessed,
+ 10),
+ {State3#hybi_8_state{unmasked_msg = UnmaskedMsg}, Recv,
+ [Frame | Send]};
+ X when X < 3 ->
+ {State3,
+ [iolist_to_binary([UnmaskedMsg, UnmaskedPre, Unmasked])
+ | Recv],
+ Send};
+ _ ->
+ {State3#hybi_8_state{unmasked_msg = UnmaskedMsg}, Recv,
+ Send}
+ end;
+ _ ->
+ process_hybi_8(#hybi_8_state{unmasked_msg =
+ [UnmaskedMsg, UnmaskedPre,
+ Unmasked]},
+ Unprocessed)
end;
-process_hybi_8(#hybi_8_state{unprocessed= <<>>}=State, Data) ->
+process_hybi_8(#hybi_8_state{unprocessed = <<>>} =
+ State,
+ Data) ->
case decode_hybi_8_header(Data) of
- none ->
- {State#hybi_8_state{unprocessed=Data}, [], []};
- {Len, Final, Opcode, Mask, Rest} ->
- process_hybi_8(State#hybi_8_state{mask=Mask, final_frame=Final==1,
- left=Len, opcode=Opcode,
- unprocessed=none}, Rest)
+ none ->
+ {State#hybi_8_state{unprocessed = Data}, [], []};
+ {Len, Final, Opcode, Mask, Rest} ->
+ process_hybi_8(State#hybi_8_state{mask = Mask,
+ final_frame = Final == 1,
+ left = Len, opcode = Opcode,
+ unprocessed = none},
+ Rest)
end;
-process_hybi_8(#hybi_8_state{unprocessed=UnprocessedPre}=State, Data) ->
- process_hybi_8(State#hybi_8_state{unprocessed = <<>>}, <<UnprocessedPre/binary, Data/binary>>).
+process_hybi_8(#hybi_8_state{unprocessed =
+ UnprocessedPre} =
+ State,
+ Data) ->
+ process_hybi_8(State#hybi_8_state{unprocessed = <<>>},
+ <<UnprocessedPre/binary, Data/binary>>).
-
-% Buffering and data handling
-handle_data({'draft-hybi', _}, State, Data, _Socket, WsHandleLoopPid, _SocketMode, _WsAutoExit) ->
+handle_data({'draft-hybi', _}, State, Data, _Socket,
+ WsHandleLoopPid, _SocketMode, _WsAutoExit) ->
{NewState, Recv, Send} = process_hybi_8(State, Data),
- lists:foreach(fun(El) ->
- WsHandleLoopPid ! {browser, El}
- end, Recv),
+ lists:foreach(fun (El) ->
+ WsHandleLoopPid ! {browser, El}
+ end,
+ Recv),
{NewState, Send};
-handle_data(_Vsn, State, Data, _Socket, WsHandleLoopPid, _SocketMode, _WsAutoExit) ->
+handle_data(_Vsn, State, Data, _Socket, WsHandleLoopPid,
+ _SocketMode, _WsAutoExit) ->
{NewState, Recv, Send} = process_hixie_68(State, Data),
- lists:foreach(fun(El) ->
- WsHandleLoopPid ! {browser, El}
- end, Recv),
- {NewState, Send};
-%% Invalid input
-handle_data(_Vsn, _State, _Data, Socket, WsHandleLoopPid, SocketMode, WsAutoExit) ->
- websocket_close(Socket, WsHandleLoopPid, SocketMode, WsAutoExit).
+ lists:foreach(fun (El) ->
+ WsHandleLoopPid ! {browser, El}
+ end,
+ Recv),
+ {NewState, Send}.
-% Close socket and custom handling loop dependency
-websocket_close(Socket, WsHandleLoopPid, SocketMode, WsAutoExit) ->
- case WsAutoExit of
- true ->
- % kill custom handling loop process
- exit(WsHandleLoopPid, kill);
- false ->
- % the killing of the custom handling loop process is handled in the loop itself -> send event
- WsHandleLoopPid ! closed
- end,
- % close main socket
- SocketMode:close(Socket).
+websocket_close(Socket, WsHandleLoopPid, SocketMode,
+ WsAutoExit) ->
+ case WsAutoExit of
+ true ->
+ % kill custom handling loop process
+ exit(WsHandleLoopPid, kill);
+ false -> WsHandleLoopPid ! closed
+ end,
+ SocketMode:close(Socket).