Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Avoid github dependency + add elixir syntax sweet

  • Loading branch information...
commit c0f969e786a1b2ba909dac82858a0f8893cd0944 1 parent 05a50b5
Eugen authored
18 tetris-servers/erlang/Makefile
View
@@ -1,14 +1,16 @@
.PHONY: all clean
all:
- mkdir -p lib
- cd lib && git clone https://github.com/jeremyong/websocket_client
- cd ..
mkdir -p ebin
- erlc -o ebin -pa ebin lib/websocket_client/src/*.erl
- erlc -o ebin -pa ebin utilities.erl
- erlc -o ebin -pa ebin player.erl
- erlc -o ebin -pa ebin ws_handler.erl
+ erlc -o ebin -pa ebin erlang_lib/*.erl
+ erlc -o ebin -pa ebin websocket_client/src/*.erl
+ erlc -o ebin -pa ebin erlang_lib/ws_handler.erl
+
+elixir:
+ mkdir -p ebin
+ elixirc -o ebin -pa ebin elixir_lib/*.ex
+ erlc -o ebin -pa ebin websocket_client/src/*.erl
+ erlc -o ebin -pa ebin elixir_lib/ws_handler.erl
clean:
- rm -rf ebin lib
+ rm -rf ebin
BIN  tetris-servers/erlang/elixir_lib/Elixir.Glass.beam
View
Binary file not shown
28 tetris-servers/erlang/elixir_lib/glass.ex
View
@@ -0,0 +1,28 @@
+defmodule Glass do
+ defp parse_glass(binary) do
+ Enum.map Enum.chunk(String.to_char_list!(binary), 10), fn(chunk) ->
+ Enum.map String.codepoints(iolist_to_binary(chunk)), fn(token) ->
+ case binary_to_atom(token) do
+ :' ' -> :blank
+ :'*' -> :box
+ end
+ end
+ end
+ end
+
+ def parse(binary) do
+ Enum.map String.split(binary, "&"), fn(pair) ->
+ [name, value_raw] = String.split(pair, "=")
+ name_atom = binary_to_atom(name)
+ value = case name_atom do
+ :figure -> binary_to_atom(value_raw) # :O, :I, :J, :L, :S, :Z, :T
+ :x -> binary_to_integer(value_raw)
+ :y -> binary_to_integer(value_raw)
+ :glass -> parse_glass(value_raw)
+ :next -> Enum.map String.codepoints(value_raw), &binary_to_atom/1
+ _ -> nil
+ end
+ {name_atom, value}
+ end
+ end
+end
17 tetris-servers/erlang/elixir_lib/player.ex
View
@@ -0,0 +1,17 @@
+defmodule Player do
+ import Keyword, only: [get: 2]
+
+ def username do 'netoctone' end
+ def hostname do '192.168.0.100:8080' end
+
+ def step(msg) do
+ keys = Glass.parse(msg)
+ IO.puts """
+ x = #{get(keys, :x)}
+ y = #{get(keys, :y)}
+ figure = #{get(keys, :figure)}
+ """
+ get(keys, :next)
+ 'left=1'
+ end
+end
33 tetris-servers/erlang/elixir_lib/ws_handler.erl
View
@@ -0,0 +1,33 @@
+-module(ws_handler).
+
+-behaviour(websocket_client_handler).
+-export([
+ start/0,
+ init/2,
+ websocket_info/3,
+ websocket_handle/3,
+ websocket_terminate/3
+ ]).
+
+-define(Host,'Elixir.Player':hostname()).
+-define(User,'Elixir.Player':username()).
+
+start() ->
+ websocket_client:start_link("ws://"++ ?Host ++ "/tetris-contest/ws?user=" ++ ?User, ?MODULE, []).
+
+init([], _ConnState) ->
+ websocket_client:cast(self(), {text, <<"message 1">>}),
+ {ok, 2}.
+
+websocket_handle({text, Msg}, _ConnState, State) ->
+ io:format("Received msg ~p~n", [Msg]),
+ Step = list_to_binary('Elixir.Player':step(Msg)),
+ {reply, {text, Step}, State}.
+
+websocket_info(start, _ConnState, State) ->
+ {reply, {text, <<"erlang message received">>}, State}.
+
+websocket_terminate(Reason, _ConnState, State) ->
+ io:format("Websocket closed in state ~p wih reason ~p~n",
+ [State, Reason]),
+ ok.
2  tetris-servers/erlang/player.erl → tetris-servers/erlang/erlang_lib/player.erl
View
@@ -8,7 +8,7 @@
username() ->
"anatoli".
hostname() ->
- "localhost:8080".
+ "192.168.0.100:8080".
step(Msg) ->
Arr = utilities:process_msg(Msg),
0  tetris-servers/erlang/utilities.erl → tetris-servers/erlang/erlang_lib/utilities.erl
View
File renamed without changes
0  tetris-servers/erlang/ws_handler.erl → tetris-servers/erlang/erlang_lib/ws_handler.erl
View
File renamed without changes
22 tetris-servers/erlang/websocket_client/LICENSE.md
View
@@ -0,0 +1,22 @@
+# The MIT License
+
+Copyright (C) 2012-2013 Jeremy Ong
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
12 tetris-servers/erlang/websocket_client/src/websocket_client.app.src
View
@@ -0,0 +1,12 @@
+{application, websocket_client,
+ [
+ {description, "Erlang websocket client"},
+ {vsn, "0.5.4"},
+ {registered, []},
+ {applications, [
+ ssl,
+ crypto
+ ]},
+ {env, []}
+ ]
+}.
459 tetris-servers/erlang/websocket_client/src/websocket_client.erl
View
@@ -0,0 +1,459 @@
+%% @author Jeremy Ong
+%% @doc Erlang websocket client
+-module(websocket_client).
+
+-export([
+ start_link/3,
+ cast/2,
+ send/2
+ ]).
+
+-export([ws_client_init/6]).
+
+%% @doc Start the websocket client
+-spec start_link(URL :: string(), Handler :: module(), Args :: list()) ->
+ {ok, pid()} | {error, term()}.
+start_link(URL, Handler, Args) ->
+ case http_uri:parse(URL, [{scheme_defaults, [{ws,80},{wss,443}]}]) of
+ {ok, {Protocol, _, Host, Port, Path, Query}} ->
+ proc_lib:start_link(?MODULE, ws_client_init,
+ [Handler, Protocol, Host, Port, Path ++ Query, Args]);
+ {error, _} = Error ->
+ Error
+ end.
+
+%% Send a frame asynchronously
+-spec cast(Client :: pid(), Frame :: websocket_req:frame()) ->
+ ok.
+cast(Client, Frame) ->
+ Client ! {cast, Frame},
+ ok.
+
+%% @doc Create socket, execute handshake, and enter loop
+-spec ws_client_init(Handler :: module(), Protocol :: websocket_req:protocol(),
+ Host :: string(), Port :: inet:port_number(), Path :: string(),
+ Args :: list()) ->
+ no_return().
+ws_client_init(Handler, Protocol, Host, Port, Path, Args) ->
+ Transport = case Protocol of
+ wss ->
+ ssl;
+ ws ->
+ gen_tcp
+ end,
+ SockReply = case Transport of
+ ssl ->
+ ssl:connect(Host, Port,
+ [{mode, binary},
+ {verify, verify_none},
+ {active, false},
+ {packet, 0}
+ ], 6000);
+ gen_tcp ->
+ gen_tcp:connect(Host, Port,
+ [binary,
+ {active, false},
+ {packet, 0}
+ ], 6000)
+ end,
+ {ok, Socket} = case SockReply of
+ {ok, Sock} -> {ok, Sock};
+ {error, _} = ConnectError ->
+ proc_lib:init_ack(ConnectError),
+ exit(normal)
+ end,
+ proc_lib:init_ack({ok, self()}),
+ WSReq = websocket_req:new(
+ Protocol,
+ Host,
+ Port,
+ Path,
+ Socket,
+ Transport,
+ Handler,
+ generate_ws_key()
+ ),
+ {ok, Buffer} = websocket_handshake(WSReq),
+ {ok, HandlerState, KeepAlive} = case Handler:init(Args, WSReq) of
+ {ok, HS} ->
+ {ok, HS, infinity};
+ {ok, HS, KA} ->
+ {ok, HS, KA}
+ end,
+ case Socket of
+ {sslsocket, _, _} ->
+ ssl:setopts(Socket, [{active, true}]);
+ _ ->
+ inet:setopts(Socket, [{active, true}])
+ end,
+ %% Since we could have already received some data already, we simulate a Socket message.
+ case Buffer of
+ <<>> -> ok;
+ _ -> self() ! {Transport, Socket, Buffer}
+ end,
+ KATimer = case KeepAlive of
+ infinity ->
+ undefined;
+ _ ->
+ erlang:send_after(KeepAlive, self(), keepalive)
+ end,
+ websocket_loop(websocket_req:set([{keepalive,KeepAlive},{keepalive_timer,KATimer}], WSReq), HandlerState, <<>>).
+
+%% @doc Send http upgrade request and validate handshake response challenge
+-spec websocket_handshake(WSReq :: websocket_req:req()) -> {ok, binary()}.
+websocket_handshake(WSReq) ->
+ [Protocol, Path, Host, Key, Transport, Socket] =
+ websocket_req:get([protocol, path, host, key, transport, socket], WSReq),
+ Handshake = [<<"GET ">>, Path,
+ <<" HTTP/1.1"
+ "\r\nHost: ">>, Host,
+ <<"\r\nUpgrade: WebSocket"
+ "\r\nConnection: Upgrade"
+ "\r\nSec-WebSocket-Key: ">>, Key,
+ <<"\r\nOrigin: ">>, atom_to_binary(Protocol, utf8), <<"://">>, Host,
+ <<"\r\nSec-WebSocket-Protocol: "
+ "\r\nSec-WebSocket-Version: 13"
+ "\r\n\r\n">>],
+ Transport = websocket_req:transport(WSReq),
+ Socket = websocket_req:socket(WSReq),
+ Transport:send(Socket, Handshake),
+ {ok, HandshakeResponse} = receive_handshake(<<>>, Transport, Socket),
+ {ok, Buffer} = validate_handshake(HandshakeResponse, Key),
+ {ok, Buffer}.
+
+%% @doc Blocks and waits until handshake response data is received
+-spec receive_handshake(Buffer :: binary(),
+ Transport :: module(),
+ Socket :: term()) ->
+ {ok, binary()}.
+receive_handshake(Buffer, Transport, Socket) ->
+ case re:run(Buffer, "\\r\\n\\r\\n") of
+ {match, _} ->
+ {ok, Buffer};
+ _ ->
+ {ok, Data} = Transport:recv(Socket, 0, 6000),
+ receive_handshake(<< Buffer/binary, Data/binary >>,
+ Transport, Socket)
+ end.
+
+%% @doc Send frame to server
+send(Frame, WSReq) ->
+ Socket = websocket_req:socket(WSReq),
+ Transport = websocket_req:transport(WSReq),
+ Transport:send(Socket, encode_frame(Frame)).
+
+
+%% @doc Main loop
+-spec websocket_loop(WSReq :: websocket_req:req(), HandlerState :: any(),
+ Buffer :: binary()) ->
+ ok.
+websocket_loop(WSReq, HandlerState, Buffer) ->
+ receive
+ Message -> handle_websocket_message(WSReq, HandlerState, Buffer, Message)
+ end.
+
+handle_websocket_message(WSReq, HandlerState, Buffer, Message) ->
+ [Handler, Remaining, Socket] =
+ websocket_req:get([handler, remaining, socket], WSReq),
+ case Message of
+ keepalive ->
+ case websocket_req:get([keepalive_timer], WSReq) of
+ [undefined] -> ok;
+ [OldTimer] -> erlang:cancel_timer(OldTimer)
+ end,
+ ok = send({ping, <<>>}, WSReq),
+ KATimer = erlang:send_after(websocket_req:keepalive(WSReq), self(), keepalive),
+ websocket_loop(websocket_req:set([{keepalive_timer,KATimer}], WSReq), HandlerState, Buffer);
+ {cast, Frame} ->
+ ok = send(Frame, WSReq),
+ websocket_loop(WSReq, HandlerState, Buffer);
+ {_Closed, Socket} ->
+ websocket_close(WSReq, HandlerState, {remote, closed});
+ {_TransportType, Socket, Data} ->
+ case Remaining of
+ undefined ->
+ retrieve_frame(WSReq, HandlerState,
+ << Buffer/binary, Data/binary >>);
+ _ ->
+ retrieve_frame(WSReq, HandlerState,
+ websocket_req:opcode(WSReq), Remaining, Data, Buffer)
+ end;
+ Msg ->
+ Handler = websocket_req:handler(WSReq),
+ try Handler:websocket_info(Msg, WSReq, HandlerState) of
+ HandlerResponse ->
+ handle_response(WSReq, HandlerResponse, Buffer)
+ catch Class:Reason ->
+ error_logger:error_msg(
+ "** Websocket client ~p terminating in ~p/~p~n"
+ " for the reason ~p:~p~n"
+ "** Last message was ~p~n"
+ "** Handler state was ~p~n"
+ "** Stacktrace: ~p~n~n",
+ [Handler, websocket_info, 3, Class, Reason, Msg, HandlerState,
+ erlang:get_stacktrace()]),
+ websocket_close(WSReq, HandlerState, Reason)
+ end
+ end.
+
+-spec websocket_close(WSReq :: websocket_req:req(),
+ HandlerState :: any(),
+ Reason :: tuple()) -> ok.
+websocket_close(WSReq, HandlerState, Reason) ->
+ Handler = websocket_req:handler(WSReq),
+ try Handler:websocket_terminate(Reason, WSReq, HandlerState)
+ catch Class:Reason2 ->
+ error_logger:error_msg(
+ "** Websocket handler ~p terminating in ~p/~p~n"
+ " for the reason ~p:~p~n"
+ "** Handler state was ~p~n"
+ "** Stacktrace: ~p~n~n",
+ [Handler, websocket_terminate, 3, Class, Reason2, HandlerState,
+ erlang:get_stacktrace()])
+ end.
+
+%% @doc Key sent in initial handshake
+-spec generate_ws_key() ->
+ binary().
+generate_ws_key() ->
+ base64:encode(crypto:rand_bytes(16)).
+
+%% @doc Validate handshake response challenge
+-spec validate_handshake(HandshakeResponse :: binary(), Key :: binary()) -> {ok, binary()}.
+validate_handshake(HandshakeResponse, Key) ->
+ Challenge = base64:encode(
+ crypto:hash(sha, << Key/binary, "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" >>)),
+ %% Consume the response...
+ {ok, _Status, Header, Buffer} = consume_response(HandshakeResponse),
+ %% ...and make sure the challenge is valid.
+ Challenge = proplists:get_value(<<"Sec-Websocket-Accept">>, Header),
+ {ok, Buffer}.
+
+%% @doc Consumes the HTTP response and extracts status, header and the body.
+consume_response(Response) ->
+ {ok, {http_response, Version, Code, Message}, Header} = erlang:decode_packet(http_bin, Response, []),
+ consume_response({Version, Code, Message}, Header, []).
+
+consume_response(Status, Response, HeaderAcc) ->
+ case erlang:decode_packet(httph_bin, Response, []) of
+ {ok, {http_header, _Length, Field, _Reserved, Value}, Rest} ->
+ consume_response(Status, Rest, [{Field, Value} | HeaderAcc]);
+
+ {ok, http_eoh, Body} ->
+ {ok, Status, HeaderAcc, Body}
+ end.
+
+%% @doc Start or continue continuation payload with length less than 126 bytes
+retrieve_frame(WSReq, HandlerWSReq,
+ << 0:4, Opcode:4, 0:1, Len:7, Rest/bits >>)
+ when Len < 126 ->
+ WSReq1 = set_continuation_if_empty(WSReq, Opcode),
+ WSReq2 = websocket_req:fin(0, WSReq1),
+ retrieve_frame(WSReq2, HandlerWSReq, Opcode, Len, Rest, <<>>);
+%% @doc Start or continue continuation payload with length a 2 byte int
+retrieve_frame(WSReq, HandlerWSReq,
+ << 0:4, Opcode:4, 0:1, 126:7, Len:16, Rest/bits >>)
+ when Len > 125, Opcode < 8 ->
+ WSReq1 = set_continuation_if_empty(WSReq, Opcode),
+ WSReq2 = websocket_req:fin(0, WSReq1),
+ retrieve_frame(WSReq2, HandlerWSReq, Opcode, Len, Rest, <<>>);
+%% @doc Start or continue continuation payload with length a 64 bit int
+retrieve_frame(WSReq, HandlerWSReq,
+ << 0:4, Opcode:4, 0:1, 127:7, 0:1, Len:63, Rest/bits >>)
+ when Len > 16#ffff, Opcode < 8 ->
+ WSReq1 = set_continuation_if_empty(WSReq, Opcode),
+ WSReq2 = websocket_req:fin(0, WSReq1),
+ retrieve_frame(WSReq2, HandlerWSReq, Opcode, Len, Rest, <<>>);
+%% @doc Length is less 126 bytes
+retrieve_frame(WSReq, HandlerWSReq,
+ << 1:1, 0:3, Opcode:4, 0:1, Len:7, Rest/bits >>)
+ when Len < 126 ->
+ WSReq1 = websocket_req:fin(1, WSReq),
+ retrieve_frame(WSReq1, HandlerWSReq, Opcode, Len, Rest, <<>>);
+%% @doc Length is a 2 byte integer
+retrieve_frame(WSReq, HandlerWSReq,
+ << 1:1, 0:3, Opcode:4, 0:1, 126:7, Len:16, Rest/bits >>)
+ when Len > 125, Opcode < 8 ->
+ WSReq1 = websocket_req:fin(1, WSReq),
+ retrieve_frame(WSReq1, HandlerWSReq, Opcode, Len, Rest, <<>>);
+%% @doc Length is a 64 bit integer
+retrieve_frame(WSReq, HandlerWSReq,
+ << 1:1, 0:3, Opcode:4, 0:1, 127:7, 0:1, Len:63, Rest/bits >>)
+ when Len > 16#ffff, Opcode < 8 ->
+ WSReq1 = websocket_req:fin(1, WSReq),
+ retrieve_frame(WSReq1, HandlerWSReq, Opcode, Len, Rest, <<>>);
+%% @doc Need more data to read length properly
+retrieve_frame(WSReq, HandlerWSReq, Data) ->
+ websocket_loop(WSReq, HandlerWSReq, Data).
+
+%% @doc Length known and still missing data
+retrieve_frame(WSReq, HandlerWSReq, Opcode, Len, Data, Buffer)
+ when byte_size(Data) < Len ->
+ Remaining = Len - byte_size(Data),
+ WSReq1 = websocket_req:remaining(Remaining, WSReq),
+ WSReq2 = websocket_req:opcode(Opcode, WSReq1),
+ websocket_loop(WSReq2, HandlerWSReq, << Buffer/bits, Data/bits >>);
+%% @doc Length known and remaining data is appended to the buffer
+retrieve_frame(WSReq, HandlerState, Opcode, Len, Data, Buffer) ->
+ [Handler, Continuation, ContinuationOpcode] =
+ websocket_req:get([handler, continuation, continuation_opcode], WSReq),
+ Fin = websocket_req:fin(WSReq),
+ << Payload:Len/binary, Rest/bits >> = Data,
+ FullPayload = << Buffer/binary, Payload/binary >>,
+ OpcodeName = websocket_req:opcode_to_name(Opcode),
+ case OpcodeName of
+ ping ->
+ %% If a ping is received, send a pong automatically
+ ok = send({pong, FullPayload}, WSReq);
+ _ ->
+ ok
+ end,
+ case OpcodeName of
+ close when byte_size(FullPayload) >= 2 ->
+ << CodeBin:2/binary, ClosePayload/binary >> = FullPayload,
+ Code = binary:decode_unsigned(CodeBin),
+ Reason = case Code of
+ 1000 -> {normal, ClosePayload};
+ 1002 -> {error, badframe, ClosePayload};
+ 1007 -> {error, badencoding, ClosePayload};
+ 1011 -> {error, handler, ClosePayload};
+ _ -> {remote, Code, ClosePayload}
+ end,
+ websocket_close(WSReq, HandlerState, Reason);
+ close ->
+ websocket_close(WSReq, HandlerState, {remote, <<>>});
+ %% Non-control continuation frame
+ _ when Opcode < 8, Continuation =/= undefined, Fin == 0 ->
+ %% Append to previously existing continuation payloads and continue
+ Continuation1 = << Continuation/binary, FullPayload/binary >>,
+ WSReq1 = websocket_req:continuation(Continuation1, WSReq),
+ retrieve_frame(WSReq1, HandlerState, Rest);
+ %% Terminate continuation frame sequence with non-control frame
+ _ when Opcode < 8, Continuation =/= undefined, Fin == 1 ->
+ DefragPayload = << Continuation/binary, FullPayload/binary >>,
+ WSReq1 = websocket_req:continuation(undefined, WSReq),
+ WSReq2 = websocket_req:continuation_opcode(undefined, WSReq1),
+ ContinuationOpcodeName = websocket_req:opcode_to_name(ContinuationOpcode),
+ try Handler:websocket_handle(
+ {ContinuationOpcodeName, DefragPayload},
+ WSReq2, HandlerState) of
+ HandlerResponse ->
+ handle_response(websocket_req:remaining(undefined, WSReq1),
+ HandlerResponse, Rest)
+ catch Class:Reason ->
+ error_logger:error_msg(
+ "** Websocket client ~p terminating in ~p/~p~n"
+ " for the reason ~p:~p~n"
+ "** Websocket message was ~p~n"
+ "** Handler state was ~p~n"
+ "** Stacktrace: ~p~n~n",
+ [Handler, websocket_handle, 3, Class, Reason, {ContinuationOpcodeName, DefragPayload}, HandlerState,
+ erlang:get_stacktrace()]),
+ websocket_close(WSReq, HandlerState, Reason)
+ end;
+ _ ->
+ try Handler:websocket_handle(
+ {OpcodeName, FullPayload},
+ WSReq, HandlerState) of
+ HandlerResponse ->
+ handle_response(websocket_req:remaining(undefined, WSReq),
+ HandlerResponse, Rest)
+ catch Class:Reason ->
+ error_logger:error_msg(
+ "** Websocket client ~p terminating in ~p/~p~n"
+ " for the reason ~p:~p~n"
+ "** Handler state was ~p~n"
+ "** Stacktrace: ~p~n~n",
+ [Handler, websocket_handle, 3, Class, Reason, HandlerState,
+ erlang:get_stacktrace()]),
+ websocket_close(WSReq, HandlerState, Reason)
+ end
+ end.
+
+%% @doc Handles return values from the callback module
+handle_response(WSReq, {reply, Frame, HandlerState}, Buffer) ->
+ [Socket, Transport] = websocket_req:get([socket, transport], WSReq),
+ case Transport:send(Socket, encode_frame(Frame)) of
+ ok ->
+ %% we can still have more messages in buffer
+ case websocket_req:remaining(WSReq) of
+ %% buffer should not contain uncomplete messages
+ undefined -> retrieve_frame(WSReq, HandlerState, Buffer);
+ %% buffer contain uncomplete message that shouldnt be parsed
+ _ -> websocket_loop(WSReq, HandlerState, Buffer)
+ end;
+ Reason -> websocket_close(WSReq, HandlerState, Reason)
+ end;
+handle_response(WSReq, {ok, HandlerState}, Buffer) ->
+ %% we can still have more messages in buffer
+ case websocket_req:remaining(WSReq) of
+ %% buffer should not contain uncomplete messages
+ undefined -> retrieve_frame(WSReq, HandlerState, Buffer);
+ %% buffer contain uncomplete message that shouldnt be parsed
+ _ -> websocket_loop(WSReq, HandlerState, Buffer)
+ end;
+
+handle_response(WSReq, {close, Payload, HandlerState}, _) ->
+ send({close, Payload}, WSReq),
+ websocket_close(WSReq, HandlerState, {normal, Payload}).
+
+%% @doc Encodes the data with a header (including a masking key) and
+%% masks the data
+-spec encode_frame(websocket_req:frame()) ->
+ binary().
+encode_frame({Type, Payload}) ->
+ Opcode = websocket_req:name_to_opcode(Type),
+ Len = iolist_size(Payload),
+ BinLen = payload_length_to_binary(Len),
+ MaskingKeyBin = crypto:rand_bytes(4),
+ << MaskingKey:32 >> = MaskingKeyBin,
+ Header = << 1:1, 0:3, Opcode:4, 1:1, BinLen/bits, MaskingKeyBin/bits >>,
+ MaskedPayload = mask_payload(MaskingKey, Payload),
+ << Header/binary, MaskedPayload/binary >>;
+encode_frame(Type) when is_atom(Type) ->
+ encode_frame({Type, <<>>}).
+
+%% @doc The payload is masked using a masking key byte by byte.
+%% Can do it in 4 byte chunks to save time until there is left than 4 bytes left
+mask_payload(MaskingKey, Payload) ->
+ mask_payload(MaskingKey, Payload, <<>>).
+mask_payload(_, <<>>, Acc) ->
+ Acc;
+mask_payload(MaskingKey, << D:32, Rest/bits >>, Acc) ->
+ T = D bxor MaskingKey,
+ mask_payload(MaskingKey, Rest, << Acc/binary, T:32 >>);
+mask_payload(MaskingKey, << D:24 >>, Acc) ->
+ << MaskingKeyPart:24, _:8 >> = << MaskingKey:32 >>,
+ T = D bxor MaskingKeyPart,
+ << Acc/binary, T:24 >>;
+mask_payload(MaskingKey, << D:16 >>, Acc) ->
+ << MaskingKeyPart:16, _:16 >> = << MaskingKey:32 >>,
+ T = D bxor MaskingKeyPart,
+ << Acc/binary, T:16 >>;
+mask_payload(MaskingKey, << D:8 >>, Acc) ->
+ << MaskingKeyPart:8, _:24 >> = << MaskingKey:32 >>,
+ T = D bxor MaskingKeyPart,
+ << Acc/binary, T:8 >>.
+
+%% @doc Encode the payload length as binary in a variable number of bits.
+%% See RFC Doc for more details
+payload_length_to_binary(Len) when Len =<125 ->
+ << Len:7 >>;
+payload_length_to_binary(Len) when Len =< 16#ffff ->
+ << 126:7, Len:16 >>;
+payload_length_to_binary(Len) when Len =< 16#7fffffffffffffff ->
+ << 127:7, Len:64 >>.
+
+%% @doc If this is the first continuation frame, set the opcode and initialize
+%% continuation to an empty binary. Otherwise, return the request object untouched.
+-spec set_continuation_if_empty(WSReq :: websocket_req:req(),
+ Opcode :: websocket_req:opcode()) ->
+ websocket_req:req().
+set_continuation_if_empty(WSReq, Opcode) ->
+ case websocket_req:continuation(WSReq) of
+ undefined ->
+ WSReq1 = websocket_req:continuation_opcode(Opcode, WSReq),
+ websocket_req:continuation(<<>>, WSReq1);
+ _ ->
+ WSReq
+ end.
23 tetris-servers/erlang/websocket_client/src/websocket_client_handler.erl
View
@@ -0,0 +1,23 @@
+-module(websocket_client_handler).
+
+-type state() :: any().
+-type keepalive() :: integer().
+-type close_type() :: normal | error | remote.
+
+-callback init(list(), websocket_req:req()) ->
+ {ok, state()}
+ | {ok, state(), keepalive()}.
+
+-callback websocket_handle({text | binary | ping | pong, binary()}, websocket_req:req(), state()) ->
+ {ok, state()}
+ | {reply, websocket_req:frame(), state()}
+ | {close, binary(), state()}.
+
+-callback websocket_info(any(), websocket_req:req(), state()) ->
+ {ok, state()}
+ | {reply, websocket_req:frame(), state()}
+ | {close, binary(), state()}.
+
+-callback websocket_terminate({close_type(), term()} | {close_type(), integer(), binary()},
+ websocket_req:req(), state()) ->
+ ok.
244 tetris-servers/erlang/websocket_client/src/websocket_req.erl
View
@@ -0,0 +1,244 @@
+%% @doc Accessor module for the #websocket_req{} record.
+-module(websocket_req).
+
+-record(websocket_req, {
+ protocol :: protocol(),
+ host :: string(),
+ port :: inet:port_number(),
+ path :: string(),
+ keepalive = infinity :: infinity | integer(),
+ keepalive_timer = undefined :: undefined | reference(),
+ socket :: inet:socket() | ssl:sslsocket(),
+ transport :: module(),
+ handler :: module(),
+ key :: binary(),
+ remaining = undefined :: undefined | integer(),
+ fin = undefined :: undefined | fin(),
+ opcode = undefined :: undefined | opcode(),
+ continuation = undefined :: undefined | binary(),
+ continuation_opcode = undefined :: undefined | opcode()
+ }).
+
+-opaque req() :: #websocket_req{}.
+-export_type([req/0]).
+
+-type protocol() :: ws | wss.
+
+-type frame() :: close | ping | pong
+ | {text | binary | close | ping | pong, binary()}
+ | {close, 1000..4999, binary()}.
+
+-type opcode() :: 0 | 1 | 2 | 8 | 9 | 10.
+-export_type([protocol/0, opcode/0, frame/0]).
+
+-type fin() :: 0 | 1.
+-export_type([fin/0]).
+
+-export([new/8,
+ protocol/2, protocol/1,
+ host/2, host/1,
+ port/2, port/1,
+ path/2, path/1,
+ keepalive/2, keepalive/1,
+ socket/2, socket/1,
+ transport/2, transport/1,
+ handler/2, handler/1,
+ key/2, key/1,
+ remaining/2, remaining/1,
+ fin/2, fin/1,
+ opcode/2, opcode/1,
+ continuation/2, continuation/1,
+ continuation_opcode/2, continuation_opcode/1,
+ get/2, set/2
+ ]).
+
+-export([
+ opcode_to_name/1,
+ name_to_opcode/1
+ ]).
+
+-spec new(protocol(), string(), inet:port_number(),
+ string(), inet:socket() | ssl:sslsocket(),
+ module(), module(), binary()) -> req().
+new(Protocol, Host, Port, Path, Socket, Transport, Handler, Key) ->
+ #websocket_req{
+ protocol = Protocol,
+ host = Host,
+ port = Port,
+ path = Path,
+ socket = Socket,
+ transport = Transport,
+ handler = Handler,
+ key = Key
+ }.
+
+
+%% @doc Mapping from opcode to opcode name
+-spec opcode_to_name(opcode()) ->
+ atom().
+opcode_to_name(0) -> continuation;
+opcode_to_name(1) -> text;
+opcode_to_name(2) -> binary;
+opcode_to_name(8) -> close;
+opcode_to_name(9) -> ping;
+opcode_to_name(10) -> pong.
+
+%% @doc Mapping from opcode to opcode name
+-spec name_to_opcode(atom()) ->
+ opcode().
+name_to_opcode(continuation) -> 0;
+name_to_opcode(text) -> 1;
+name_to_opcode(binary) -> 2;
+name_to_opcode(close) -> 8;
+name_to_opcode(ping) -> 9;
+name_to_opcode(pong) -> 10.
+
+
+-spec protocol(req()) -> protocol().
+protocol(#websocket_req{protocol = P}) -> P.
+
+-spec protocol(protocol(), req()) -> req().
+protocol(P, Req) ->
+ Req#websocket_req{protocol = P}.
+
+
+-spec host(req()) -> string().
+host(#websocket_req{host = H}) -> H.
+
+-spec host(string(), req()) -> req().
+host(H, Req) ->
+ Req#websocket_req{host = H}.
+
+
+-spec port(req()) -> inet:port_number().
+port(#websocket_req{port = P}) -> P.
+
+-spec port(inet:port_number(), req()) -> req().
+port(P, Req) ->
+ Req#websocket_req{port = P}.
+
+
+-spec path(req()) -> string().
+path(#websocket_req{path = P}) -> P.
+
+-spec path(string(), req()) -> req().
+path(P, Req) ->
+ Req#websocket_req{path = P}.
+
+
+-spec keepalive(req()) -> integer().
+keepalive(#websocket_req{keepalive = K}) -> K.
+
+-spec keepalive(integer(), req()) -> req().
+keepalive(K, Req) ->
+ Req#websocket_req{keepalive = K}.
+
+
+-spec socket(req()) -> inet:socket() | ssl:sslsocket().
+socket(#websocket_req{socket = S}) -> S.
+
+-spec socket(inet:socket() | ssl:sslsocket(), req()) -> req().
+socket(S, Req) ->
+ Req#websocket_req{socket = S}.
+
+
+-spec transport(req()) -> module().
+transport(#websocket_req{transport = T}) -> T.
+
+-spec transport(module(), req()) -> req().
+transport(T, Req) ->
+ Req#websocket_req{transport = T}.
+
+
+-spec handler(req()) -> module().
+handler(#websocket_req{handler = H}) -> H.
+
+-spec handler(module(), req()) -> req().
+handler(H, Req) ->
+ Req#websocket_req{handler = H}.
+
+
+-spec key(req()) -> binary().
+key(#websocket_req{key = K}) -> K.
+
+-spec key(binary(), req()) -> req().
+key(K, Req) ->
+ Req#websocket_req{key = K}.
+
+
+-spec remaining(req()) -> undefined | integer().
+remaining(#websocket_req{remaining = R}) -> R.
+
+-spec remaining(undefined | integer(), req()) -> req().
+remaining(R, Req) ->
+ Req#websocket_req{remaining = R}.
+
+-spec fin(req()) -> fin().
+fin(#websocket_req{fin = F}) -> F.
+
+-spec fin(fin(), req()) -> req().
+fin(F, Req) ->
+ Req#websocket_req{fin = F}.
+
+-spec opcode(req()) -> opcode().
+opcode(#websocket_req{opcode = O}) -> O.
+
+-spec opcode(opcode(), req()) -> req().
+opcode(O, Req) ->
+ Req#websocket_req{opcode = O}.
+
+-spec continuation(req()) -> undefined | binary().
+continuation(#websocket_req{continuation = C}) -> C.
+
+-spec continuation(undefined | binary(), req()) -> req().
+continuation(C, Req) ->
+ Req#websocket_req{continuation = C}.
+
+-spec continuation_opcode(req()) -> undefined | opcode().
+continuation_opcode(#websocket_req{continuation_opcode = C}) -> C.
+
+-spec continuation_opcode(undefined | opcode(), req()) -> req().
+continuation_opcode(C, Req) ->
+ Req#websocket_req{continuation_opcode = C}.
+
+
+-spec get(atom(), req()) -> any(); ([atom()], req()) -> [any()].
+get(List, Req) when is_list(List) ->
+ [g(Atom, Req) || Atom <- List];
+get(Atom, Req) when is_atom(Atom) ->
+ g(Atom, Req).
+
+g(protocol, #websocket_req{protocol = Ret}) -> Ret;
+g(host, #websocket_req{host = Ret}) -> Ret;
+g(port, #websocket_req{port = Ret}) -> Ret;
+g(path, #websocket_req{path = Ret}) -> Ret;
+g(keepalive, #websocket_req{keepalive = Ret}) -> Ret;
+g(keepalive_timer, #websocket_req{keepalive_timer = Ret}) -> Ret;
+g(socket, #websocket_req{socket = Ret}) -> Ret;
+g(transport, #websocket_req{transport = Ret}) -> Ret;
+g(handler, #websocket_req{handler = Ret}) -> Ret;
+g(key, #websocket_req{key = Ret}) -> Ret;
+g(remaining, #websocket_req{remaining = Ret}) -> Ret;
+g(fin, #websocket_req{fin = Ret}) -> Ret;
+g(opcode, #websocket_req{opcode = Ret}) -> Ret;
+g(continuation, #websocket_req{continuation = Ret}) -> Ret;
+g(continuation_opcode, #websocket_req{continuation_opcode = Ret}) -> Ret.
+
+
+-spec set([{atom(), any()}], Req) -> Req when Req::req().
+set([{protocol, Val} | Tail], Req) -> set(Tail, Req#websocket_req{protocol = Val});
+set([{host, Val} | Tail], Req) -> set(Tail, Req#websocket_req{host = Val});
+set([{port, Val} | Tail], Req) -> set(Tail, Req#websocket_req{port = Val});
+set([{path, Val} | Tail], Req) -> set(Tail, Req#websocket_req{path = Val});
+set([{keepalive, Val} | Tail], Req) -> set(Tail, Req#websocket_req{keepalive = Val});
+set([{keepalive_timer, Val} | Tail], Req) -> set(Tail, Req#websocket_req{keepalive_timer = Val});
+set([{socket, Val} | Tail], Req) -> set(Tail, Req#websocket_req{socket = Val});
+set([{transport, Val} | Tail], Req) -> set(Tail, Req#websocket_req{transport = Val});
+set([{handler, Val} | Tail], Req) -> set(Tail, Req#websocket_req{handler = Val});
+set([{key, Val} | Tail], Req) -> set(Tail, Req#websocket_req{key = Val});
+set([{remaining, Val} | Tail], Req) -> set(Tail, Req#websocket_req{remaining = Val});
+set([{fin, Val} | Tail], Req) -> set(Tail, Req#websocket_req{fin = Val});
+set([{opcode, Val} | Tail], Req) -> set(Tail, Req#websocket_req{opcode = Val});
+set([{continuation, Val} | Tail], Req) -> set(Tail, Req#websocket_req{continuation = Val});
+set([{continuation_opcode, Val} | Tail], Req) -> set(Tail, Req#websocket_req{continuation_opcode = Val});
+set([], Req) -> Req.
Please sign in to comment.
Something went wrong with that request. Please try again.