Gaoxy
Gaoxy runs supervised TCP, OTP TLS and UDP services inside an Elixir application.
Each instance owns its listeners and connection handlers. HTTP/Plug/Phoenix and
HTTP/3 are available through the separate gaoxy_http and gaoxy_http3 packages.
Install
Add to mix.exs, then run mix deps.get:
defp deps do
[
{:gaoxy, "~> 0.5.3"}
]
end
Use Elixir 1.18 or newer with Erlang/OTP. Core uses Hex abyss == 0.9.0, which
has no bundled DHCP NIF or Rustler build dependency. Neither Rust/Cargo nor
abyss_raw is required for ordinary Gaoxy services.
Run a TCP service
Implement the stream handler in your application:
defmodule MyApp.Echo do
@behaviour Gaoxy.StreamHandler
@impl true
def init(_context, _options), do: {:ok, nil}
@impl true
def handle_data(bytes, state), do: {:send, bytes, state}
end
Add a Gaoxy instance to your application's supervision tree:
children = [
{Gaoxy,
name: MyApp.Transports,
services: [
%{
id: :echo,
transport: :tcp,
ip: {127, 0, 0, 1},
port: 4001,
handler: MyApp.Echo
}
]}
]
Supervisor.start_link(children, strategy: :one_for_one)
Start your application and connect with nc 127.0.0.1 4001. Bytes written to
the connection are echoed back. Input callbacks receive an ordered byte stream;
your handler must buffer and parse any application message framing.
handler_options: term supplies the second argument to init/2.
Optional terminate/2 handles connection cleanup.
Inspect the instance from IEx:
instance = Process.whereis(MyApp.Transports)
Gaoxy.services(instance)
Gaoxy.info(instance, :echo)
Gaoxy.pause(instance, :echo)
Gaoxy.resume(instance, :echo)
Service IDs belong to an instance, so two instances can each have an :echo
service. Gaoxy.add/2 and Gaoxy.remove/3 manage services dynamically;
Gaoxy.stop/2 stops the instance. A cleanup deadline error can mean cleanup is
still in progress.
TLS and UDP
For TLS, use transport: :tls and supply
tls: %{certfile: "/path/to/cert.pem", keyfile: "/path/to/key.pem"} on the service.
TCP/TLS stream services use the same handler behaviour. TLS uses OTP :ssl;
TLS 1.3 is the default.
For UDP, implement Gaoxy.DatagramHandler:
defmodule MyApp.DatagramEcho do
@behaviour Gaoxy.DatagramHandler
@impl true
def handle_datagram(%{data: bytes}, _options), do: {:reply, bytes}
end
Use %{id: :udp_echo, transport: :udp, ip: {127, 0, 0, 1}, port: 4002, handler: MyApp.DatagramEcho} in services. Each admitted packet gets one
isolated callback; {:reply, bytes} sends to that packet's sender.
Configuration
The instance accepts :name and :services. Services are maps; unknown keys are
rejected. Bind addresses are IP tuples, and port 0 requests an ephemeral port.
Defaults bind loopback and limit each service to 64 active operations and
1,048,576 retained bytes. Override finite positive limits with a map such as
limits: %{max_active: 32, max_bytes: 4_194_304, idle_timeout: 60_000}.
These budgets describe admitted resources, not total VM or kernel memory.
For HTTP and Phoenix, see the HTTP package and Phoenix guide. See capabilities for transport support and architecture for lifecycle and resource ownership.