Current section
Files
Jump to
Current section
Files
src/mug.erl
-module(mug).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch]).
-define(FILEPATH, "src/mug.gleam").
-export([new/2, ip_version_preference/2, timeout/2, connect/1, send_builder/2, send/2, 'receive'/2, receive_exact/3, shutdown/1, receive_next_packet_as_message/1, select_tcp_messages/2]).
-export_type([socket/0, do_not_leak/0, connect_error/0, error/0, connection_options/0, ip_version_preference/0, mode_value/0, active_value/0, gen_tcp_option/0, tcp_message/0]).
-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 socket() :: any().
-type do_not_leak() :: any().
-type connect_error() :: {connect_failed_ipv4, error()} |
{connect_failed_ipv6, error()} |
{connect_failed_both, error(), error()}.
-type error() :: closed |
timeout |
eaddrinuse |
eaddrnotavail |
eafnosupport |
ealready |
econnaborted |
econnrefused |
econnreset |
edestaddrreq |
ehostdown |
ehostunreach |
einprogress |
eisconn |
emsgsize |
enetdown |
enetunreach |
enopkg |
enoprotoopt |
enotconn |
enotty |
enotsock |
eproto |
eprotonosupport |
eprototype |
esocktnosupport |
etimedout |
ewouldblock |
exbadport |
exbadseq |
nxdomain |
eacces |
eagain |
ebadf |
ebadmsg |
ebusy |
edeadlk |
edeadlock |
edquot |
eexist |
efault |
efbig |
eftype |
eintr |
einval |
eio |
eisdir |
eloop |
emfile |
emlink |
emultihop |
enametoolong |
enfile |
enobufs |
enodev |
enolck |
enolink |
enoent |
enomem |
enospc |
enosr |
enostr |
enosys |
enotblk |
enotdir |
enotsup |
enxio |
eopnotsupp |
eoverflow |
eperm |
epipe |
erange |
erofs |
espipe |
esrch |
estale |
etxtbsy |
exdev.
-type connection_options() :: {connection_options,
binary(),
integer(),
integer(),
ip_version_preference()}.
-type ip_version_preference() :: ipv4_only |
ipv4_preferred |
ipv6_only |
ipv6_preferred.
-type mode_value() :: binary.
-type active_value() :: any().
-type gen_tcp_option() :: inet |
inet6 |
{active, active_value()} |
{mode, mode_value()}.
-type tcp_message() :: {packet, socket(), bitstring()} |
{socket_closed, socket()} |
{tcp_error, socket(), error()}.
-file("src/mug.gleam", 139).
?DOC(" Create a new set of connection options.\n").
-spec new(binary(), integer()) -> connection_options().
new(Host, Port) ->
{connection_options, Host, Port, 1000, ipv6_preferred}.
-file("src/mug.gleam", 153).
?DOC(
" What approach to take selecting between IPv4 and IPv6.\n"
" See the `IpVersionPreference` type for documentation.\n"
"\n"
" The default is `Ipv6Preferred`.\n"
).
-spec ip_version_preference(connection_options(), ip_version_preference()) -> connection_options().
ip_version_preference(Options, Preference) ->
{connection_options,
erlang:element(2, Options),
erlang:element(3, Options),
erlang:element(4, Options),
Preference}.
-file("src/mug.gleam", 164).
?DOC(
" Specify a timeout for the connection to be established, in milliseconds.\n"
"\n"
" The default is 1000ms.\n"
).
-spec timeout(connection_options(), integer()) -> connection_options().
timeout(Options, Timeout) ->
{connection_options,
erlang:element(2, Options),
erlang:element(3, Options),
Timeout,
erlang:element(5, Options)}.
-file("src/mug.gleam", 203).
?DOC(
" Establish a TCP connection to the server specified in the connection\n"
" options.\n"
"\n"
" Returns an error if the connection could not be established.\n"
"\n"
" The socket is created in passive mode, meaning the the `receive` function is\n"
" to be called to receive packets from the client. The\n"
" `receive_next_packet_as_message` function can be used to switch the socket\n"
" to active mode and receive the next packet as an Erlang message.\n"
).
-spec connect(connection_options()) -> {ok, socket()} | {error, connect_error()}.
connect(Options) ->
Host = unicode:characters_to_list(erlang:element(2, Options)),
Connect = fun(Inet) ->
Gen_options = [Inet, {active, mug_ffi:passive()}, {mode, binary}],
gen_tcp:connect(
Host,
erlang:element(3, Options),
Gen_options,
erlang:element(4, Options)
)
end,
case erlang:element(5, Options) of
ipv4_only ->
_pipe = Connect(inet),
gleam@result:map_error(
_pipe,
fun(Field@0) -> {connect_failed_ipv4, Field@0} end
);
ipv6_only ->
_pipe@1 = Connect(inet6),
gleam@result:map_error(
_pipe@1,
fun(Field@0) -> {connect_failed_ipv6, Field@0} end
);
ipv4_preferred ->
case Connect(inet) of
{ok, Conn} ->
{ok, Conn};
{error, Ipv4} ->
case Connect(inet6) of
{ok, Conn@1} ->
{ok, Conn@1};
{error, Ipv6} ->
{error, {connect_failed_both, Ipv4, Ipv6}}
end
end;
ipv6_preferred ->
case Connect(inet6) of
{ok, Conn@2} ->
{ok, Conn@2};
{error, Ipv6@1} ->
case Connect(inet) of
{ok, Conn@3} ->
{ok, Conn@3};
{error, Ipv4@1} ->
{error, {connect_failed_both, Ipv4@1, Ipv6@1}}
end
end
end.
-file("src/mug.gleam", 282).
?DOC(
" Send a message to the client, the data in `BytesBuilder`. Using this function\n"
" is more efficient turning an `BytesBuilder` or a `StringBuilder` into a\n"
" `BitArray` to use with the `send` function.\n"
).
-spec send_builder(socket(), gleam@bytes_tree:bytes_tree()) -> {ok, nil} |
{error, error()}.
send_builder(Socket, Packet) ->
mug_ffi:send(Socket, Packet).
-file("src/mug.gleam", 273).
?DOC(" Send a message to the client.\n").
-spec send(socket(), bitstring()) -> {ok, nil} | {error, error()}.
send(Socket, Message) ->
mug_ffi:send(Socket, gleam@bytes_tree:from_bit_array(Message)).
-file("src/mug.gleam", 289).
?DOC(
" Receive a message from the client.\n"
"\n"
" Errors if the socket is closed, if the timeout is reached, or if there is\n"
" some other problem receiving the packet.\n"
).
-spec 'receive'(socket(), integer()) -> {ok, bitstring()} | {error, error()}.
'receive'(Socket, Timeout) ->
gen_tcp:recv(Socket, 0, Timeout).
-file("src/mug.gleam", 306).
?DOC(
" Receive the specified number of bytes from the client, unless the socket\n"
" was closed, from the other side. In that case, the last read may return\n"
" less bytes.\n"
" If the specified number of bytes is not available to read from the socket\n"
" then the function will block until the bytes are available, or until the\n"
" timeout is reached.\n"
" This directly calls the underlying Erlang function `gen_tcp:recv/3`.\n"
"\n"
" Errors if the socket is closed, if the timeout is reached, or if there is\n"
" some other problem receiving the packet.\n"
).
-spec receive_exact(socket(), integer(), integer()) -> {ok, bitstring()} |
{error, error()}.
receive_exact(Socket, Size, Timeout) ->
gen_tcp:recv(Socket, Size, Timeout).
-file("src/mug.gleam", 325).
?DOC(
" Close the socket, ensuring that any data buffered in the socket is flushed\n"
" to the operating system kernel socket first.\n"
).
-spec shutdown(socket()) -> {ok, nil} | {error, error()}.
shutdown(Socket) ->
mug_ffi:shutdown(Socket).
-file("src/mug.gleam", 338).
?DOC(
" Switch the socket to active mode, meaning that the next packet received on\n"
" the socket will be sent as an Erlang message to the socket owner's inbox.\n"
"\n"
" This is useful for when you wish to have an OTP actor handle incoming\n"
" messages as using the `receive` function would result in the actor being\n"
" blocked and unable to handle other messages while waiting for the next\n"
" packet.\n"
"\n"
" Messages will be send to the process that controls the socket, which is the\n"
" process that established the socket with the `connect` function.\n"
).
-spec receive_next_packet_as_message(socket()) -> nil.
receive_next_packet_as_message(Socket) ->
inet:setopts(Socket, [{active, mug_ffi:active_once()}]),
nil.
-file("src/mug.gleam", 377).
-spec map_tcp_message(fun((tcp_message()) -> HKN)) -> fun((gleam@dynamic:dynamic_()) -> HKN).
map_tcp_message(Mapper) ->
fun(Message) -> Mapper(mug_ffi:coerce_tcp_message(Message)) end.
-file("src/mug.gleam", 363).
?DOC(
" Configure a selector to receive messages from TCP sockets.\n"
"\n"
" Note this will receive messages from all TCP sockets that the process\n"
" controls, rather than any specific one. If you wish to only handle messages\n"
" from one socket then use one process per socket.\n"
).
-spec select_tcp_messages(
gleam@erlang@process:selector(HKK),
fun((tcp_message()) -> HKK)
) -> gleam@erlang@process:selector(HKK).
select_tcp_messages(Selector, Mapper) ->
Tcp = erlang:binary_to_atom(<<"tcp"/utf8>>),
Closed = erlang:binary_to_atom(<<"tcp_closed"/utf8>>),
Error = erlang:binary_to_atom(<<"tcp_error"/utf8>>),
_pipe = Selector,
_pipe@1 = gleam@erlang@process:select_record(
_pipe,
Tcp,
2,
map_tcp_message(Mapper)
),
_pipe@2 = gleam@erlang@process:select_record(
_pipe@1,
Closed,
1,
map_tcp_message(Mapper)
),
gleam@erlang@process:select_record(
_pipe@2,
Error,
2,
map_tcp_message(Mapper)
).