tcp_listener.pony

use net = ".."

actor TCPListener is net.TCPListenerActor
  """
  A TCP listener actor driven by a
  [`TCPListenNotify`](/net/notifier-TCPListenNotify/).

  Wraps `TCPListener` and `TCPListenerActor` into a single
  actor. When a client connects, the listener asks the notifier for a
  `ServerTCPConnectionNotify` and creates a `ServerTCPConnection` internally.
  """
  var _tcp_listener: net.TCPListener = net.TCPListener.none()
  var _notify: TCPListenNotify ref
  let _server_auth: net.TCPServerAuth
  let _ssl_ctx: (net.SSLContext val | None)
  let _servers: Array[ServerTCPConnection] = Array[ServerTCPConnection]

  new create(auth: net.TCPListenAuth,
    notify: TCPListenNotify iso,
    host: String,
    service: String,
    limit: (net.MaxSpawn | None) = net.DefaultMaxSpawn(),
    ip_version: net.IPVersion = net.DualStack)
  =>
    """
    Open a plaintext TCP listener on `host`:`service`.
    """
    _notify = consume notify
    _server_auth = net.TCPServerAuth(auth)
    _ssl_ctx = None
    _tcp_listener =
      net.TCPListener(
        auth, host, service, this, ip_version, limit)

  new ssl(auth: net.TCPListenAuth,
    notify: TCPListenNotify iso,
    ctx: net.SSLContext val,
    host: String,
    service: String,
    limit: (net.MaxSpawn | None) = net.DefaultMaxSpawn(),
    ip_version: net.IPVersion = net.DualStack)
  =>
    """
    Open an SSL TCP listener on `host`:`service`. Accepted connections use
    `ctx` for the TLS handshake.
    """
    _notify = consume notify
    _server_auth = net.TCPServerAuth(auth)
    _ssl_ctx = ctx
    _tcp_listener =
      net.TCPListener(
        auth, host, service, this, ip_version, limit)

  // --- TCPListenerActor ------------------------------------------------------
  fun ref _listener(): net.TCPListener =>
    _tcp_listener

  fun ref _on_accept(fd: U32): ServerTCPConnection ? =>
    let notify = _notify.on_connected(this)?
    let server = match _ssl_ctx
    | let ctx: net.SSLContext val =>
      ServerTCPConnection._ssl_create(_server_auth, ctx, fd, consume notify)
    else
      ServerTCPConnection._create(_server_auth, fd, consume notify)
    end
    _servers.push(server)
    server

  fun ref _on_listening() =>
    _notify.on_listening(this)

  fun ref _on_listen_failure() =>
    _notify.on_not_listening(this)

  fun ref _on_closed() =>
    for server in _servers.values() do
      server.dispose()
    end
    _servers.clear()
    _notify.on_closed(this)

  // --- Synchronous methods ---------------------------------------------------
  fun ref local_address(): net.NetAddress =>
    """
    Return the local IP address the listener is bound to.
    """
    _tcp_listener.local_address()

  fun ref close() =>
    """
    Close the listener. No further connections are accepted. `on_closed` fires
    on the notifier.
    """
    _tcp_listener.close()