use lori = ".."
use net = "net"
use "ssl/net"
actor ClientTCPConnection is
(lori.TCPConnectionActor & lori.ClientLifecycleEventReceiver)
"""
A client TCP connection actor driven by a
[`ClientTCPConnectionNotify`](/lori/notifier-ClientTCPConnectionNotify/).
Wraps lori's `TCPConnection` class and `ClientLifecycleEventReceiver` trait
into a single actor, forwarding lifecycle callbacks to the notifier you
provide. This is the notifier-based alternative to implementing
`TCPConnectionActor` and `ClientLifecycleEventReceiver` yourself.
## Sending data
`write` and `writev` are fire-and-forget behaviors: they call `send()` on
the underlying connection and discard the result. A send that cannot be
accepted (connection not open, backpressure) is silently dropped — no
callback fires for it. An accepted send produces `on_send_accepted`, then
exactly one of `on_sent` or `on_send_failed` on the notifier.
## Synchronous methods
Callbacks receive `conn: ClientTCPConnection ref`. Synchronous methods on
the actor — `buffer_until`, socket options, `close`, `set_timer`, etc. —
are callable from any callback and take effect immediately.
"""
var _tcp_connection: lori.TCPConnection = lori.TCPConnection.none()
var _notify: ClientTCPConnectionNotify ref
new create(auth: lori.TCPConnectAuth,
notify: ClientTCPConnectionNotify iso,
host: String,
service: String,
from: String = "",
read_buffer_size: lori.ReadBufferSize = lori.DefaultReadBufferSize(),
ip_version: lori.IPVersion = lori.DualStack,
connection_timeout: (lori.ConnectionTimeout | None) = None)
=>
"""
Open a plaintext client connection to `host`:`service`.
"""
_notify = consume notify
_tcp_connection =
lori.TCPConnection.client(
auth,
host,
service,
from,
this,
this,
read_buffer_size,
ip_version,
connection_timeout)
new ssl(auth: lori.TCPConnectAuth,
notify: ClientTCPConnectionNotify iso,
ctx: SSLContext val,
host: String,
service: String,
from: String = "",
read_buffer_size: lori.ReadBufferSize = lori.DefaultReadBufferSize(),
ip_version: lori.IPVersion = lori.DualStack,
connection_timeout: (lori.ConnectionTimeout | None) = None)
=>
"""
Open an SSL client connection to `host`:`service`. The SSL session is
created from `ctx`. `on_connected` fires after the TLS handshake completes.
"""
_notify = consume notify
_tcp_connection =
lori.TCPConnection.ssl_client(
auth,
ctx,
host,
service,
from,
this,
this,
read_buffer_size,
ip_version,
connection_timeout)
// --- TCPConnectionActor ---------------------------------------------------
fun ref _connection(): lori.TCPConnection =>
_tcp_connection
// --- Behaviors -------------------------------------------------------------
be write(data: ByteSeq) =>
"""
Send data on this connection. Fire-and-forget: the send is silently
dropped if the connection is not open or is under backpressure. An
accepted send produces `on_send_accepted`, then `on_sent` or
`on_send_failed` on the notifier.
"""
_tcp_connection.send(data)
be writev(data: ByteSeqIter) =>
"""
Send multiple buffers in a single syscall. Same fire-and-forget semantics
as `write`.
"""
_tcp_connection.send(data)
be mute() =>
"""
Stop reading from the socket until `unmute` is called. Takes effect on
the next turn, not immediately — use `YieldReading` from `on_received`
for an immediate one-shot pause.
"""
_tcp_connection.mute()
be unmute() =>
"""
Resume reading after a `mute`.
"""
_tcp_connection.unmute()
// --- Synchronous methods ---------------------------------------------------
fun ref buffer_until(qty: (lori.BufferSize | lori.Streaming)):
lori.BufferUntilResult
=>
"""
Set the number of bytes to buffer before delivering data via `on_received`.
Pass `Streaming` to deliver all available data as it arrives.
"""
_tcp_connection.buffer_until(qty)
fun ref close() =>
"""
Gracefully close the connection. Sends already accepted by the underlying
connection are delivered before close completes. Because `close` is
synchronous and `write` is a behavior, a `write` call in the same
callback runs after `close` — use `write` before `close`, not after.
"""
_tcp_connection.close()
fun ref hard_close() =>
"""
Close the connection immediately, dropping any queued data.
"""
_tcp_connection.hard_close()
fun ref start_tls(ssl_ctx: SSLContext val, host: String = ""):
(None | lori.StartTLSError)
=>
"""
Initiate a TLS handshake on an established plaintext connection. Returns
`None` when the handshake starts, or a `StartTLSError` if the upgrade
cannot proceed. On success, `on_tls_ready` fires. On failure,
`on_tls_failure` fires followed by `on_closed`.
"""
_tcp_connection.start_tls(ssl_ctx, host)
fun set_nodelay(state: Bool): U32 =>
"""
Turn Nagle on/off. Returns 0 on success, or a non-zero errno on failure.
"""
_tcp_connection.set_nodelay(state)
fun ref keepalive(secs: U32) =>
"""
Set the TCP keepalive timeout. Pass 0 to disable.
"""
_tcp_connection.keepalive(secs)
fun ref local_address(): net.NetAddress =>
"""
Return the local IP address.
"""
_tcp_connection.local_address()
fun ref remote_address(): net.NetAddress =>
"""
Return the remote IP address.
"""
_tcp_connection.remote_address()
fun ref idle_timeout(duration: (lori.IdleTimeout | None)) =>
"""
Set or disable the idle timeout. The timer fires when no data is sent or
received for the configured duration. Pass `None` to disable.
"""
_tcp_connection.idle_timeout(duration)
fun ref set_timer(duration: lori.TimerDuration):
(lori.TimerToken | lori.SetTimerError)
=>
"""
Create a one-shot timer. Returns a `TimerToken` on success. Only one
timer can be active at a time; cancel the existing one first.
"""
_tcp_connection.set_timer(duration)
fun ref cancel_timer(token: lori.TimerToken) =>
"""
Cancel an active timer. Safe to call with stale tokens.
"""
_tcp_connection.cancel_timer(token)
fun ref set_read_buffer_minimum(new_min: lori.ReadBufferSize):
(lori.ReadBufferResized | lori.ReadBufferResizeBelowBufferSize)
=>
"""
Set the shrink-back floor for the read buffer.
"""
_tcp_connection.set_read_buffer_minimum(new_min)
fun ref resize_read_buffer(size': lori.ReadBufferSize):
lori.ReadBufferResizeResult
=>
"""
Force the read buffer to a specific size.
"""
_tcp_connection.resize_read_buffer(size')
fun get_so_rcvbuf(): (U32, U32) =>
"""
Get the OS receive buffer size. Returns (errno, value).
"""
_tcp_connection.get_so_rcvbuf()
fun set_so_rcvbuf(bufsize: U32): U32 =>
"""
Set the OS receive buffer size. Returns 0 on success, or errno.
"""
_tcp_connection.set_so_rcvbuf(bufsize)
fun get_so_sndbuf(): (U32, U32) =>
"""
Get the OS send buffer size. Returns (errno, value).
"""
_tcp_connection.get_so_sndbuf()
fun set_so_sndbuf(bufsize: U32): U32 =>
"""
Set the OS send buffer size. Returns 0 on success, or errno.
"""
_tcp_connection.set_so_sndbuf(bufsize)
fun getsockopt_u32(level: I32, option_name: I32): (U32, U32) =>
"""
Get a socket option as a U32. Returns (errno, value).
"""
_tcp_connection.getsockopt_u32(level, option_name)
fun setsockopt_u32(level: I32, option_name: I32, option: U32): U32 =>
"""
Set a socket option as a U32. Returns 0 on success, or errno.
"""
_tcp_connection.setsockopt_u32(level, option_name, option)
fun is_closed(): Bool =>
"""
True when the connection is closed or closing.
"""
_tcp_connection.is_closed()
fun is_writeable(): Bool =>
"""
True when the socket can currently send.
"""
_tcp_connection.is_writeable()
// --- ClientLifecycleEventReceiver ------------------------------------------
fun ref _on_connecting(inflight_connections: U32) =>
_notify.on_connecting(this, inflight_connections)
fun ref _on_connected() =>
_notify.on_connected(this)
fun ref _on_connection_failure(reason: lori.ConnectionFailureReason) =>
_notify.on_connect_failed(this, reason)
fun ref _on_received(data: Array[U8] iso): lori.ReadAction =>
_notify.on_received(this, consume data)
fun ref _on_send_accepted(token: lori.SendToken,
data: (ByteSeq | ByteSeqIter))
=>
_notify.on_send_accepted(this, token, data)
fun ref _on_sent(token: lori.SendToken) =>
_notify.on_sent(this, token)
fun ref _on_send_failed(token: lori.SendToken) =>
_notify.on_send_failed(this, token)
fun ref _on_throttled() =>
_notify.on_throttled(this)
fun ref _on_unthrottled() =>
_notify.on_unthrottled(this)
fun ref _on_closed() =>
_notify.on_closed(this)
fun ref _on_tls_ready() =>
_notify.on_tls_ready(this)
fun ref _on_tls_failure(reason: lori.TLSFailureReason) =>
_notify.on_tls_failure(this, reason)
fun ref _on_idle_timeout() =>
_notify.on_idle_timeout(this)
fun ref _on_idle_timer_failure() =>
_notify.on_idle_timer_failure(this)
fun ref _on_timer(token: lori.TimerToken) =>
_notify.on_timer(this, token)
fun ref _on_timer_failure() =>
_notify.on_timer_failure(this)