-module(glisten). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/glisten.gleam"). -export([convert_ip_address/1, get_server_info/2, ip_address_to_string/1, get_connection_info/1, send/2, continue/1, with_selector/2, stop/0, stop_abnormal/1, convert_next/1, map_selector/2, new/2, with_close/2, with_pool_size/2, with_http2/1, with_ipv6/1, with_tls/3, with_active_state/2, with_listener_name/2, with_connection_factory_name/2, start/2, bind/2, supervised/2]). -export_type([message/1, ip_address/0, connection_info/0, connection/1, next/2, builder/2]). -if(?OTP_RELEASE >= 27). -define(MODULEDOC(Str), -moduledoc(Str)). -define(DOC(Str), -doc(Str)). -else. -define(MODULEDOC(Str), -compile([])). -define(DOC(Str), -compile([])). -endif. -type message(GOO) :: {packet, bitstring()} | {user, GOO}. -type ip_address() :: {ip_v4, integer(), integer(), integer(), integer()} | {ip_v6, integer(), integer(), integer(), integer(), integer(), integer(), integer(), integer()}. -type connection_info() :: {connection_info, integer(), ip_address()}. -type connection(GOP) :: {connection, glisten@socket:socket(), glisten@transport:transport(), gleam@erlang@process:subject(glisten@internal@handler:message(GOP))}. -opaque next(GOQ, GOR) :: {continue, GOQ, gleam@option:option(gleam@erlang@process:selector(GOR))} | normal_stop | {abnormal_stop, binary()}. -opaque builder(GOS, GOT) :: {builder, glisten@socket@options:interface(), fun((connection(GOT)) -> {GOS, gleam@option:option(gleam@erlang@process:selector(GOT))}), fun((GOS, message(GOT), connection(GOT)) -> next(GOS, message(GOT))), gleam@option:option(fun((GOS) -> nil)), integer(), boolean(), boolean(), gleam@option:option(glisten@socket@options:tls_certs()), gleam@option:option(gleam@erlang@process:name(glisten@internal@listener:message())), gleam@option:option(gleam@erlang@process:name(gleam@otp@factory_supervisor:message(glisten@socket:socket(), gleam@erlang@process:subject(glisten@internal@handler:message(GOT))))), glisten@socket@options:active_state()}. -file("src/glisten.gleam", 68). ?DOC(false). -spec convert_ip_address(glisten@socket@options:ip_address()) -> ip_address(). convert_ip_address(Ip) -> case Ip of {ip_v4, A, B, C, D} -> {ip_v4, A, B, C, D}; {ip_v6, A@1, B@1, C@1, D@1, E, F, G, H} -> {ip_v6, A@1, B@1, C@1, D@1, E, F, G, H} end. -file("src/glisten.gleam", 48). ?DOC(" Returns the user-provided port or the OS-assigned value if 0 was provided.\n"). -spec get_server_info( gleam@erlang@process:name(glisten@internal@listener:message()), integer() ) -> connection_info(). get_server_info(Listener, Timeout) -> Listener@1 = gleam@erlang@process:named_subject(Listener), State = gleam@erlang@process:call( Listener@1, Timeout, fun(Field@0) -> {info, Field@0} end ), {connection_info, erlang:element(3, State), convert_ip_address(erlang:element(4, State))}. -file("src/glisten.gleam", 97). -spec join_ipv6_fields(list(integer())) -> binary(). join_ipv6_fields(Fields) -> _pipe = gleam@list:map(Fields, fun gleam@int:to_base16/1), gleam@string:join(_pipe, <<":"/utf8>>). -file("src/glisten.gleam", 106). ?DOC( " Finds the longest sequence of consecutive all-zero fields in an IPv6.\n" " If the address contains multiple runs of all-zero fields of the same size,\n" " it is the leftmost that is compressed.\n" "\n" " This returns the start & end indices of the compressed zeros.\n" ). -spec ipv6_zeros(list(integer()), integer(), integer(), integer(), integer()) -> {ok, {integer(), integer()}} | {error, nil}. ipv6_zeros(Fields, Pos, Len, Max_start, Max_len) -> case Fields of [] when Max_len > 1 -> {ok, {Max_start, Max_start + Max_len}}; [] -> {error, nil}; [X | Xs] when X =:= 0 -> Len@1 = Len + 1, case Len@1 > Max_len of true -> ipv6_zeros(Xs, Pos + 1, Len@1, (Pos + 1) - Len@1, Len@1); false -> ipv6_zeros(Xs, Pos + 1, Len@1, Max_start, Max_len) end; [_ | Xs@1] -> ipv6_zeros(Xs@1, Pos + 1, 0, Max_start, Max_len) end. -file("src/glisten.gleam", 77). ?DOC( " Convenience function for convert an `IpAddress` type into a string. It will\n" " convert IPv6 addresses to the canonical short-hand (ie. loopback is ::1).\n" ). -spec ip_address_to_string(ip_address()) -> binary(). ip_address_to_string(Address) -> case Address of {ip_v4, A, B, C, D} -> _pipe = [A, B, C, D], _pipe@1 = gleam@list:map(_pipe, fun erlang:integer_to_binary/1), gleam@string:join(_pipe@1, <<"."/utf8>>); {ip_v6, A@1, B@1, C@1, D@1, E, F, G, H} -> Fields = [A@1, B@1, C@1, D@1, E, F, G, H], _pipe@2 = case ipv6_zeros(Fields, 0, 0, 0, 0) of {error, _} -> join_ipv6_fields(Fields); {ok, {Start, End}} -> <<<<(join_ipv6_fields(gleam@list:take(Fields, Start)))/binary, "::"/utf8>>/binary, (join_ipv6_fields(gleam@list:drop(Fields, End)))/binary>> end, string:lowercase(_pipe@2) end. -file("src/glisten.gleam", 127). ?DOC( " Tries to read the IP address and port of a connected client. It will\n" " return valid IPv4 or IPv6 addresses, attempting to return the most relevant\n" " one for the client.\n" ). -spec get_connection_info(connection(any())) -> {ok, connection_info()} | {error, nil}. get_connection_info(Conn) -> _pipe = glisten@transport:peername( erlang:element(3, Conn), erlang:element(2, Conn) ), gleam@result:map( _pipe, fun(Pair) -> {connection_info, erlang:element(2, Pair), convert_ip_address(erlang:element(1, Pair))} end ). -file("src/glisten.gleam", 135). ?DOC(" Sends a BytesTree message over the socket using the active transport\n"). -spec send(connection(any()), gleam@bytes_tree:bytes_tree()) -> {ok, nil} | {error, glisten@socket:socket_reason()}. send(Conn, Msg) -> glisten@transport:send( erlang:element(3, Conn), erlang:element(2, Conn), Msg ). -file("src/glisten.gleam", 148). -spec continue(GPT) -> next(GPT, any()). continue(State) -> {continue, State, none}. -file("src/glisten.gleam", 152). -spec with_selector(next(GPX, GPY), gleam@erlang@process:selector(GPY)) -> next(GPX, GPY). with_selector(Next, Selector) -> case Next of {continue, State, _} -> {continue, State, {some, Selector}}; Stop -> Stop end. -file("src/glisten.gleam", 162). -spec stop() -> next(any(), any()). stop() -> normal_stop. -file("src/glisten.gleam", 166). -spec stop_abnormal(binary()) -> next(any(), any()). stop_abnormal(Reason) -> {abnormal_stop, Reason}. -file("src/glisten.gleam", 171). ?DOC(false). -spec convert_next(next(GQM, GQN)) -> glisten@internal@handler:next(GQM, GQN). convert_next(Next) -> case Next of {continue, State, Selector} -> {continue, State, Selector}; normal_stop -> normal_stop; {abnormal_stop, Reason} -> {abnormal_stop, Reason} end. -file("src/glisten.gleam", 182). ?DOC(false). -spec map_selector(next(GQS, GQT), fun((GQT) -> GQW)) -> next(GQS, GQW). map_selector(Next, Mapper) -> case Next of {continue, State, {some, Selector}} -> {continue, State, {some, gleam_erlang_ffi:map_selector(Selector, Mapper)}}; {continue, State@1, none} -> {continue, State@1, none}; {abnormal_stop, Reason} -> {abnormal_stop, Reason}; normal_stop -> normal_stop end. -file("src/glisten.gleam", 222). -spec map_user_selector(gleam@erlang@process:selector(message(GQZ))) -> gleam@erlang@process:selector(glisten@internal@handler:loop_message(GQZ)). map_user_selector(Selector) -> gleam_erlang_ffi:map_selector(Selector, fun(Value) -> case Value of {packet, Msg} -> {packet, Msg}; {user, Msg@1} -> {custom, Msg@1} end end). -file("src/glisten.gleam", 233). -spec convert_loop( fun((GRE, message(GRF), connection(GRF)) -> next(GRE, message(GRF))) ) -> fun((GRE, glisten@internal@handler:loop_message(GRF), glisten@internal@handler:connection(GRF)) -> glisten@internal@handler:next(GRE, glisten@internal@handler:loop_message(GRF))). convert_loop(Loop) -> fun(Data, Msg, Conn) -> Conn@1 = {connection, erlang:element(3, Conn), erlang:element(4, Conn), erlang:element(5, Conn)}, Message = case Msg of {packet, Msg@1} -> {packet, Msg@1}; {custom, Msg@2} -> {user, Msg@2} end, case Loop(Data, Message, Conn@1) of {continue, Data@1, Selector} -> case Selector of {some, Selector@1} -> _pipe = glisten@internal@handler:continue(Data@1), glisten@internal@handler:with_selector( _pipe, map_user_selector(Selector@1) ); _ -> glisten@internal@handler:continue(Data@1) end; normal_stop -> glisten@internal@handler:stop(); {abnormal_stop, Reason} -> glisten@internal@handler:stop_abnormal(Reason) end end. -file("src/glisten.gleam", 257). -spec convert_on_init( fun((connection(GRK)) -> {GRM, gleam@option:option(gleam@erlang@process:selector(GRK))}) ) -> fun((glisten@internal@handler:connection(GRK)) -> {GRM, gleam@option:option(gleam@erlang@process:selector(GRK))}). convert_on_init(On_init) -> fun(Conn) -> Connection = {connection, erlang:element(3, Conn), erlang:element(4, Conn), erlang:element(5, Conn)}, On_init(Connection) end. -file("src/glisten.gleam", 276). ?DOC( " Create a new handler for each connection. The required arguments mirror the\n" " `actor.start` API from `gleam_otp`. The default pool is 10 accceptor\n" " processes.\n" ). -spec new( fun((connection(GRS)) -> {GRU, gleam@option:option(gleam@erlang@process:selector(GRS))}), fun((GRU, message(GRS), connection(GRS)) -> next(GRU, message(GRS))) ) -> builder(GRU, GRS). new(On_init, Loop) -> {builder, loopback, On_init, Loop, none, 10, false, false, none, none, none, once}. -file("src/glisten.gleam", 297). ?DOC(" Adds a function to the handler to be called when the connection is closed.\n"). -spec with_close(builder(GSB, GSC), fun((GSB) -> nil)) -> builder(GSB, GSC). with_close(Builder, On_close) -> {builder, erlang:element(2, Builder), erlang:element(3, Builder), erlang:element(4, Builder), {some, On_close}, erlang:element(6, Builder), erlang:element(7, Builder), erlang:element(8, Builder), erlang:element(9, Builder), erlang:element(10, Builder), erlang:element(11, Builder), erlang:element(12, Builder)}. -file("src/glisten.gleam", 305). ?DOC(" Modify the size of the acceptor pool\n"). -spec with_pool_size(builder(GSH, GSI), integer()) -> builder(GSH, GSI). with_pool_size(Builder, Size) -> {builder, erlang:element(2, Builder), erlang:element(3, Builder), erlang:element(4, Builder), erlang:element(5, Builder), Size, erlang:element(7, Builder), erlang:element(8, Builder), erlang:element(9, Builder), erlang:element(10, Builder), erlang:element(11, Builder), erlang:element(12, Builder)}. -file("src/glisten.gleam", 316). ?DOC(false). -spec with_http2(builder(GSN, GSO)) -> builder(GSN, GSO). with_http2(Builder) -> {builder, erlang:element(2, Builder), erlang:element(3, Builder), erlang:element(4, Builder), erlang:element(5, Builder), erlang:element(6, Builder), true, erlang:element(8, Builder), erlang:element(9, Builder), erlang:element(10, Builder), erlang:element(11, Builder), erlang:element(12, Builder)}. -file("src/glisten.gleam", 344). ?DOC( " By default, `glisten` listens on `localhost` only over IPv4. With an IPv4\n" " address, you can call this builder method to also serve over IPv6 on that\n" " interface. If it is not supported, your application will crash. If you\n" " call this with an IPv6 interface specified, it will have no effect.\n" ). -spec with_ipv6(builder(GSZ, GTA)) -> builder(GSZ, GTA). with_ipv6(Builder) -> {builder, erlang:element(2, Builder), erlang:element(3, Builder), erlang:element(4, Builder), erlang:element(5, Builder), erlang:element(6, Builder), erlang:element(7, Builder), true, erlang:element(9, Builder), erlang:element(10, Builder), erlang:element(11, Builder), erlang:element(12, Builder)}. -file("src/glisten.gleam", 351). ?DOC(" To use TLS, provide a path to a certficate and key file.\n"). -spec with_tls(builder(GTF, GTG), binary(), binary()) -> builder(GTF, GTG). with_tls(Builder, Cert, Key) -> {builder, erlang:element(2, Builder), erlang:element(3, Builder), erlang:element(4, Builder), erlang:element(5, Builder), erlang:element(6, Builder), erlang:element(7, Builder), erlang:element(8, Builder), {some, {cert_key_files, Cert, Key}}, erlang:element(10, Builder), erlang:element(11, Builder), erlang:element(12, Builder)}. -file("src/glisten.gleam", 361). ?DOC( " Set the server's `ActiveState` for flow control of received packets.\n" " Default is `Once`. Allowed are `Once`, `Active` and `Count(n)` where n > 1.\n" ). -spec with_active_state( builder(GTL, GTM), glisten@socket@options:active_state() ) -> builder(GTL, GTM). with_active_state(Builder, Active_state) -> case Active_state of once -> {builder, erlang:element(2, Builder), erlang:element(3, Builder), erlang:element(4, Builder), erlang:element(5, Builder), erlang:element(6, Builder), erlang:element(7, Builder), erlang:element(8, Builder), erlang:element(9, Builder), erlang:element(10, Builder), erlang:element(11, Builder), Active_state}; active -> {builder, erlang:element(2, Builder), erlang:element(3, Builder), erlang:element(4, Builder), erlang:element(5, Builder), erlang:element(6, Builder), erlang:element(7, Builder), erlang:element(8, Builder), erlang:element(9, Builder), erlang:element(10, Builder), erlang:element(11, Builder), Active_state}; {count, N} when N > 1 -> {builder, erlang:element(2, Builder), erlang:element(3, Builder), erlang:element(4, Builder), erlang:element(5, Builder), erlang:element(6, Builder), erlang:element(7, Builder), erlang:element(8, Builder), erlang:element(9, Builder), erlang:element(10, Builder), erlang:element(11, Builder), Active_state}; {count, _} -> erlang:error(#{gleam_error => panic, message => <<"Count shall be greater than 1"/utf8>>, file => <>, module => <<"glisten"/utf8>>, function => <<"with_active_state"/utf8>>, line => 369}); passive -> erlang:error(#{gleam_error => panic, message => <<"You cannot set the server's `ActiveState` to `Passive`"/utf8>>, file => <>, module => <<"glisten"/utf8>>, function => <<"with_active_state"/utf8>>, line => 371}) end. -file("src/glisten.gleam", 376). ?DOC(false). -spec with_listener_name( builder(GTR, GTS), gleam@erlang@process:name(glisten@internal@listener:message()) ) -> builder(GTR, GTS). with_listener_name(Builder, Listener_name) -> {builder, erlang:element(2, Builder), erlang:element(3, Builder), erlang:element(4, Builder), erlang:element(5, Builder), erlang:element(6, Builder), erlang:element(7, Builder), erlang:element(8, Builder), erlang:element(9, Builder), {some, Listener_name}, erlang:element(11, Builder), erlang:element(12, Builder)}. -file("src/glisten.gleam", 384). ?DOC(false). -spec with_connection_factory_name( builder(GTY, GTZ), gleam@erlang@process:name(gleam@otp@factory_supervisor:message(glisten@socket:socket(), gleam@erlang@process:subject(glisten@internal@handler:message(GTZ)))) ) -> builder(GTY, GTZ). with_connection_factory_name(Builder, Connection_factory_name) -> {builder, erlang:element(2, Builder), erlang:element(3, Builder), erlang:element(4, Builder), erlang:element(5, Builder), erlang:element(6, Builder), erlang:element(7, Builder), erlang:element(8, Builder), erlang:element(9, Builder), erlang:element(10, Builder), {some, Connection_factory_name}, erlang:element(12, Builder)}. -file("src/glisten.gleam", 394). ?DOC(" Start the TCP server with the given handler on the provided port\n"). -spec start(builder(any(), any()), integer()) -> {ok, gleam@otp@actor:started(gleam@otp@static_supervisor:supervisor())} | {error, gleam@otp@actor:start_error()}. start(Builder, Port) -> Listener_name = gleam@option:unwrap( erlang:element(10, Builder), gleam_erlang_ffi:new_name(<<"glisten_listener"/utf8>>) ), Connection_supervisor = gleam@option:unwrap( erlang:element(11, Builder), gleam_erlang_ffi:new_name(<<"glisten_connection_supervisor"/utf8>>) ), Options = begin _pipe = [{ip, erlang:element(2, Builder)}], _pipe@1 = lists:append(_pipe, case erlang:element(8, Builder) of true -> [ipv6]; false -> [] end), _pipe@2 = lists:append(_pipe@1, case erlang:element(9, Builder) of {some, Opts} -> [{cert_key_config, Opts}]; _ -> [] end), lists:append( _pipe@2, case {erlang:element(9, Builder), erlang:element(7, Builder)} of {{some, _}, true} -> [{alpn_preferred_protocols, [<<"h2"/utf8>>, <<"http/1.1"/utf8>>]}]; {{some, _}, false} -> [{alpn_preferred_protocols, [<<"http/1.1"/utf8>>]}]; {none, _} -> [] end ) end, Transport = case erlang:element(9, Builder) of {some, _} -> ssl; _ -> tcp end, _pipe@3 = {pool, convert_loop(erlang:element(4, Builder)), erlang:element(6, Builder), Connection_supervisor, convert_on_init(erlang:element(3, Builder)), erlang:element(5, Builder), Transport, erlang:element(12, Builder)}, glisten@internal@acceptor:start_pool( _pipe@3, Transport, Port, Options, Listener_name ). -file("src/glisten.gleam", 326). ?DOC( " This sets the interface for `glisten` to listen on. It accepts the following\n" " strings: \"localhost\", valid IPv4 addresses (i.e. \"127.0.0.1\"), and valid\n" " IPv6 addresses (i.e. \"::1\"). If an invalid value is provided, this will\n" " panic.\n" ). -spec bind(builder(GST, GSU), binary()) -> builder(GST, GSU). bind(Builder, Interface) -> Address@1 = case {Interface, glisten_ffi:parse_address(unicode:characters_to_list(Interface))} of {<<"0.0.0.0"/utf8>>, _} -> any; {<<"localhost"/utf8>>, _} -> loopback; {<<"127.0.0.1"/utf8>>, _} -> loopback; {_, {ok, Address}} -> {address, Address}; {_, {error, _}} -> erlang:error(#{gleam_error => panic, message => <<"Invalid interface provided: must be a valid IPv4/IPv6 address, or \"localhost\""/utf8>>, file => <>, module => <<"glisten"/utf8>>, function => <<"bind"/utf8>>, line => 335}) end, {builder, Address@1, erlang:element(3, Builder), erlang:element(4, Builder), erlang:element(5, Builder), erlang:element(6, Builder), erlang:element(7, Builder), erlang:element(8, Builder), erlang:element(9, Builder), erlang:element(10, Builder), erlang:element(11, Builder), erlang:element(12, Builder)}. -file("src/glisten.gleam", 443). ?DOC( " Helper method for building a child specification for use in a supervision\n" " tree.\n" ). -spec supervised(builder(any(), any()), integer()) -> gleam@otp@supervision:child_specification(gleam@otp@static_supervisor:supervisor()). supervised(Handler, Port) -> gleam@otp@supervision:supervisor(fun() -> start(Handler, Port) end).