Packages
mist
0.14.0
6.0.3
6.0.2
6.0.1
6.0.0
5.0.4
5.0.3
5.0.2
5.0.1
5.0.0
5.0.0-rc1
4.0.7
4.0.6
4.0.5
4.0.4
4.0.3
4.0.2
4.0.1
4.0.0
3.0.0
2.0.0
1.2.0
1.1.0
1.0.0
1.0.0-rc3
1.0.0-rc2
1.0.0-rc1
0.17.0
0.15.0
0.14.3
0.14.2
0.14.1
0.14.0
0.13.2
0.13.1
0.13.0
0.12.0
0.11.0
0.10.0
0.9.4
0.9.3
0.9.2
0.9.1
0.9.0
0.8.3
0.8.2
0.8.1
0.8.0
0.7.1
0.7.0
0.6.1
0.6.0
0.5.2
0.5.1
0.5.0
0.4.5
0.4.4
0.4.3
0.4.2
0.4.1
0.4.0
0.3.3
0.3.2
0.3.1
0.3.0
0.2.1
0.2.0
0.1.3
a misty Gleam web server
Current section
Files
Jump to
Current section
Files
src/mist@internal@websocket.erl
-module(mist@internal@websocket).
-compile([no_auto_import, nowarn_unused_vars]).
-export([frame_from_message/3, frame_to_bit_builder/1, to_text_frame/1, to_binary_frame/1, initialize_connection/5]).
-export_type([data_frame/0, control_frame/0, frame/0, valid_message/1, websocket_message/1, websocket_connection/0]).
-type data_frame() :: {text_frame, integer(), bitstring()} |
{binary_frame, integer(), bitstring()}.
-type control_frame() :: {close_frame, integer(), bitstring()} |
{ping_frame, integer(), bitstring()} |
{pong_frame, integer(), bitstring()}.
-type frame() :: {data, data_frame()} | {control, control_frame()}.
-type valid_message(KEN) :: {internal, frame()} | socket_closed | {user, KEN}.
-type websocket_message(KEO) :: {valid, valid_message(KEO)} | invalid.
-type websocket_connection() :: {websocket_connection,
glisten@socket:socket(),
glisten@socket@transport:transport()}.
-spec unmask_data(bitstring(), list(bitstring()), integer(), bitstring()) -> bitstring().
unmask_data(Data, Masks, Index, Resp) ->
case Data of
<<>> ->
Resp;
<<Masked:8/bitstring, Rest/bitstring>> ->
_assert_subject = gleam@list:at(Masks, Index rem 4),
{ok, Mask_value} = case _assert_subject of
{ok, _} -> _assert_subject;
_assert_fail ->
erlang:error(#{gleam_error => let_assert,
message => <<"Assertion pattern match failed"/utf8>>,
value => _assert_fail,
module => <<"mist/internal/websocket"/utf8>>,
function => <<"unmask_data"/utf8>>,
line => 48})
end,
Unmasked = crypto:exor(Mask_value, Masked),
unmask_data(
Rest,
Masks,
Index + 1,
<<Resp/bitstring, Unmasked/bitstring>>
)
end.
-spec frame_from_message(
glisten@socket:socket(),
glisten@socket@transport:transport(),
bitstring()
) -> {ok, frame()} | {error, nil}.
frame_from_message(Socket, Transport, Message) ->
<<_:1, Rest/bitstring>> = case Message of
<<_:1, _/bitstring>> -> Message;
_assert_fail ->
erlang:error(#{gleam_error => let_assert,
message => <<"Assertion pattern match failed"/utf8>>,
value => _assert_fail,
module => <<"mist/internal/websocket"/utf8>>,
function => <<"frame_from_message"/utf8>>,
line => 65})
end,
<<_:3, Rest@1/bitstring>> = case Rest of
<<_:3, _/bitstring>> -> Rest;
_assert_fail@1 ->
erlang:error(#{gleam_error => let_assert,
message => <<"Assertion pattern match failed"/utf8>>,
value => _assert_fail@1,
module => <<"mist/internal/websocket"/utf8>>,
function => <<"frame_from_message"/utf8>>,
line => 66})
end,
<<Opcode:4/integer, Rest@2/bitstring>> = case Rest@1 of
<<_:4/integer, _/bitstring>> -> Rest@1;
_assert_fail@2 ->
erlang:error(#{gleam_error => let_assert,
message => <<"Assertion pattern match failed"/utf8>>,
value => _assert_fail@2,
module => <<"mist/internal/websocket"/utf8>>,
function => <<"frame_from_message"/utf8>>,
line => 67})
end,
<<1:1, Rest@3/bitstring>> = case Rest@2 of
<<1:1, _/bitstring>> -> Rest@2;
_assert_fail@3 ->
erlang:error(#{gleam_error => let_assert,
message => <<"Assertion pattern match failed"/utf8>>,
value => _assert_fail@3,
module => <<"mist/internal/websocket"/utf8>>,
function => <<"frame_from_message"/utf8>>,
line => 69})
end,
<<Payload_length:7/integer, Rest@4/bitstring>> = case Rest@3 of
<<_:7/integer, _/bitstring>> -> Rest@3;
_assert_fail@4 ->
erlang:error(#{gleam_error => let_assert,
message => <<"Assertion pattern match failed"/utf8>>,
value => _assert_fail@4,
module => <<"mist/internal/websocket"/utf8>>,
function => <<"frame_from_message"/utf8>>,
line => 70})
end,
{Payload_length@1, Rest@7} = case Payload_length of
126 ->
<<Length:16/integer, Rest@5/bitstring>> = case Rest@4 of
<<_:16/integer, _/bitstring>> -> Rest@4;
_assert_fail@5 ->
erlang:error(#{gleam_error => let_assert,
message => <<"Assertion pattern match failed"/utf8>>,
value => _assert_fail@5,
module => <<"mist/internal/websocket"/utf8>>,
function => <<"frame_from_message"/utf8>>,
line => 73})
end,
{Length, Rest@5};
127 ->
<<Length@1:64/integer, Rest@6/bitstring>> = case Rest@4 of
<<_:64/integer, _/bitstring>> -> Rest@4;
_assert_fail@6 ->
erlang:error(#{gleam_error => let_assert,
message => <<"Assertion pattern match failed"/utf8>>,
value => _assert_fail@6,
module => <<"mist/internal/websocket"/utf8>>,
function => <<"frame_from_message"/utf8>>,
line => 77})
end,
{Length@1, Rest@6};
_ ->
{Payload_length, Rest@4}
end,
<<Mask1:8/bitstring,
Mask2:8/bitstring,
Mask3:8/bitstring,
Mask4:8/bitstring,
Rest@8/bitstring>> = case Rest@7 of
<<_:8/bitstring,
_:8/bitstring,
_:8/bitstring,
_:8/bitstring,
_/bitstring>> -> Rest@7;
_assert_fail@7 ->
erlang:error(#{gleam_error => let_assert,
message => <<"Assertion pattern match failed"/utf8>>,
value => _assert_fail@7,
module => <<"mist/internal/websocket"/utf8>>,
function => <<"frame_from_message"/utf8>>,
line => 82})
end,
_pipe@5 = case Payload_length@1 - gleam@bit_string:byte_size(Rest@8) of
0 ->
{ok, unmask_data(Rest@8, [Mask1, Mask2, Mask3, Mask4], 0, <<>>)};
Need ->
_pipe = Need,
_pipe@1 = (erlang:element(10, Transport))(Socket, _pipe),
_pipe@2 = gleam@result:replace_error(_pipe@1, nil),
gleam@result:map(_pipe@2, fun(Needed) -> _pipe@3 = Rest@8,
_pipe@4 = gleam@bit_string:append(_pipe@3, Needed),
unmask_data(_pipe@4, [Mask1, Mask2, Mask3, Mask4], 0, <<>>) end)
end,
gleam@result:map(_pipe@5, fun(Data) -> case Opcode of
1 ->
{data, {text_frame, Payload_length@1, Data}};
2 ->
{data, {binary_frame, Payload_length@1, Data}};
8 ->
{control, {close_frame, Payload_length@1, Data}};
9 ->
{control, {ping_frame, Payload_length@1, Data}};
10 ->
{control, {pong_frame, Payload_length@1, Data}}
end end).
-spec make_frame(integer(), integer(), bitstring()) -> gleam@bit_builder:bit_builder().
make_frame(Opcode, Length, Payload) ->
Length_section = case Length of
Length@1 when Length@1 > 65535 ->
<<127:7, Length@1:64/integer>>;
Length@2 when Length@2 >= 126 ->
<<126:7, Length@2:16/integer>>;
_ ->
<<Length:7>>
end,
_pipe = <<1:1,
0:3,
Opcode:4,
0:1,
Length_section/bitstring,
Payload/bitstring>>,
gleam@bit_builder:from_bit_string(_pipe).
-spec frame_to_bit_builder(frame()) -> gleam@bit_builder:bit_builder().
frame_to_bit_builder(Frame) ->
case Frame of
{data, {text_frame, Payload_length, Payload}} ->
make_frame(1, Payload_length, Payload);
{control, {close_frame, Payload_length@1, Payload@1}} ->
make_frame(8, Payload_length@1, Payload@1);
{data, {binary_frame, Payload_length@2, Payload@2}} ->
make_frame(2, Payload_length@2, Payload@2);
{control, {pong_frame, Payload_length@3, Payload@3}} ->
make_frame(10, Payload_length@3, Payload@3);
{control, {ping_frame, Payload_length@4, Payload@4}} ->
make_frame(9, Payload_length@4, Payload@4)
end.
-spec to_text_frame(bitstring()) -> gleam@bit_builder:bit_builder().
to_text_frame(Data) ->
Size = gleam@bit_string:byte_size(Data),
frame_to_bit_builder({data, {text_frame, Size, Data}}).
-spec to_binary_frame(bitstring()) -> gleam@bit_builder:bit_builder().
to_binary_frame(Data) ->
Size = gleam@bit_string:byte_size(Data),
frame_to_bit_builder({data, {binary_frame, Size, Data}}).
-spec initialize_connection(
fun(() -> {KEX, gleam@option:option(gleam@erlang@process:selector(KEY))}),
fun(() -> nil),
fun((KEX, websocket_connection(), valid_message(KEY)) -> gleam@otp@actor:next(KEY, KEX)),
glisten@socket:socket(),
glisten@socket@transport:transport()
) -> {ok, gleam@erlang@process:subject(websocket_message(KEY))} | {error, nil}.
initialize_connection(On_init, On_close, Handler, Socket, Transport) ->
Connection = {websocket_connection, Socket, Transport},
_pipe@26 = gleam@otp@actor:start_spec(
{spec,
fun() ->
{Initial_state, User_selector} = On_init(),
Selector@1 = begin
_pipe = gleam_erlang_ffi:new_selector(),
_pipe@7 = gleam@erlang@process:selecting_record3(
_pipe,
erlang:binary_to_atom(<<"tcp"/utf8>>),
fun(_, Data) -> _pipe@1 = Data,
_pipe@2 = gleam@dynamic:bit_string(_pipe@1),
_pipe@3 = gleam@result:replace_error(_pipe@2, nil),
_pipe@4 = gleam@result:then(
_pipe@3,
fun(_capture) ->
frame_from_message(
Socket,
Transport,
_capture
)
end
),
_pipe@5 = gleam@result:map(
_pipe@4,
fun(Field@0) -> {internal, Field@0} end
),
_pipe@6 = gleam@result:map(
_pipe@5,
fun(Field@0) -> {valid, Field@0} end
),
gleam@result:unwrap(_pipe@6, invalid) end
),
_pipe@14 = gleam@erlang@process:selecting_record3(
_pipe@7,
erlang:binary_to_atom(<<"ssl"/utf8>>),
fun(_, Data@1) -> _pipe@8 = Data@1,
_pipe@9 = gleam@dynamic:bit_string(_pipe@8),
_pipe@10 = gleam@result:replace_error(_pipe@9, nil),
_pipe@11 = gleam@result:then(
_pipe@10,
fun(_capture@1) ->
frame_from_message(
Socket,
Transport,
_capture@1
)
end
),
_pipe@12 = gleam@result:map(
_pipe@11,
fun(Field@0) -> {internal, Field@0} end
),
_pipe@13 = gleam@result:map(
_pipe@12,
fun(Field@0) -> {valid, Field@0} end
),
gleam@result:unwrap(_pipe@13, invalid) end
),
_pipe@15 = gleam@erlang@process:selecting_record2(
_pipe@14,
erlang:binary_to_atom(<<"ssl_closed"/utf8>>),
fun(_) -> {valid, socket_closed} end
),
_pipe@16 = gleam@erlang@process:selecting_record2(
_pipe@15,
erlang:binary_to_atom(<<"tcp_closed"/utf8>>),
fun(_) -> {valid, socket_closed} end
),
(fun(Selector) -> case User_selector of
{some, User_selector@1} ->
_pipe@17 = User_selector@1,
_pipe@18 = gleam_erlang_ffi:map_selector(
_pipe@17,
fun(Field@0) -> {user, Field@0} end
),
_pipe@19 = gleam_erlang_ffi:map_selector(
_pipe@18,
fun(Field@0) -> {valid, Field@0} end
),
gleam_erlang_ffi:merge_selector(
_pipe@19,
Selector
);
_ ->
Selector
end end)(_pipe@16)
end,
{ready, Initial_state, Selector@1}
end,
500,
fun(Msg, State) -> case Msg of
{valid, {internal, {control, {close_frame, _, _}} = Frame}} ->
_ = (erlang:element(12, erlang:element(3, Connection)))(
erlang:element(2, Connection),
frame_to_bit_builder(Frame)
),
On_close(),
{stop, normal};
{valid,
{internal, {control, {ping_frame, Length, Payload}}}} ->
_pipe@20 = (erlang:element(
12,
erlang:element(3, Connection)
))(
erlang:element(2, Connection),
frame_to_bit_builder(
{control, {pong_frame, Length, Payload}}
)
),
_pipe@21 = gleam@result:map(
_pipe@20,
fun(_) -> gleam@otp@actor:continue(State) end
),
gleam@result:lazy_unwrap(
_pipe@21,
fun() ->
On_close(),
{stop,
{abnormal,
<<"Failed to send pong frame"/utf8>>}}
end
);
invalid ->
mist@internal@logger:error(
{<<"Received a malformed Websocket frame"/utf8>>}
),
gleam@otp@actor:continue(State);
{valid, Msg@1} ->
_pipe@22 = gleam_erlang_ffi:rescue(
fun() -> Handler(State, Connection, Msg@1) end
),
_pipe@25 = gleam@result:map(
_pipe@22,
fun(Cont) -> case Cont of
{continue, State@1, Selector@2} ->
_pipe@23 = Selector@2,
_pipe@24 = gleam@option:map(
_pipe@23,
fun(_capture@2) ->
gleam_erlang_ffi:map_selector(
_capture@2,
fun(Msg@2) ->
{valid, {user, Msg@2}}
end
)
end
),
{continue, State@1, _pipe@24};
{stop, Reason} ->
On_close(),
{stop, Reason}
end end
),
gleam@result:lazy_unwrap(
_pipe@25,
fun() ->
mist@internal@logger:error(
<<"Caught error in websocket handler"/utf8>>
),
On_close(),
{stop,
{abnormal, <<"Websocket terminated"/utf8>>}}
end
)
end end}
),
_pipe@27 = gleam@result:replace_error(_pipe@26, nil),
_pipe@28 = gleam@result:map(
_pipe@27,
fun(Subj) ->
Websocket_pid = gleam@erlang@process:subject_owner(Subj),
_assert_subject = (erlang:element(5, erlang:element(3, Connection)))(
erlang:element(2, Connection),
Websocket_pid
),
{ok, _} = case _assert_subject of
{ok, _} -> _assert_subject;
_assert_fail ->
erlang:error(#{gleam_error => let_assert,
message => <<"Assertion pattern match failed"/utf8>>,
value => _assert_fail,
module => <<"mist/internal/websocket"/utf8>>,
function => <<"initialize_connection"/utf8>>,
line => 285})
end,
_assert_subject@1 = (erlang:element(
13,
erlang:element(3, Connection)
))(erlang:element(2, Connection), [{active_mode, active}]),
{ok, _} = case _assert_subject@1 of
{ok, _} -> _assert_subject@1;
_assert_fail@1 ->
erlang:error(#{gleam_error => let_assert,
message => <<"Assertion pattern match failed"/utf8>>,
value => _assert_fail@1,
module => <<"mist/internal/websocket"/utf8>>,
function => <<"initialize_connection"/utf8>>,
line => 287})
end,
Subj
end
),
gleam@result:replace_error(_pipe@28, nil).