# `Ankusa.Listener`
[🔗](https://github.com/jamescarr/ankusa/blob/ankusa-v0.7.0/packages/ankusa/lib/ankusa/listener.ex#L1)

What every HTTP listener the instance runs (the ingest edge, the admin API,
the route-management API, the claim-check gateway, the metrics listener) has
in common: the connection limits and timeouts of `Ankusa.Config`'s
`listener` section, and the optional TLS block each listener carries.

Bandit's own defaults are tuned for a browser-facing app (a 60 s header read
timeout, unlimited keep-alive, 16 384 connections for each of 100 acceptors,
HTTP/2 on), so the instance passes every limit explicitly: see
`bandit_options/2`.

## TLS

A `tls` block is `nil` (plain HTTP) or a map:

| Key | Default | Meaning |
| --- | --- | --- |
| `certfile`, `keyfile` | required | PEM files of the server certificate and its key |
| `cacertfile` | `nil` | PEM bundle of the CAs that sign client certificates; required by `verify: :verify_peer` |
| `verify` | `:verify_none` | `:verify_peer` makes the listener demand a client certificate that chains to `cacertfile` (mTLS): a client without one is refused at the handshake, and the accepted certificate is available to the request as `Plug.Conn.get_peer_data/1`'s `:ssl_cert` |
| `versions` | `[:"tlsv1.2", :"tlsv1.3"]` | protocol versions offered |
| `ciphers` | `nil` | a list of OpenSSL-style names (`"ECDHE-RSA-AES256-GCM-SHA384"`) or TLS 1.3 names (`"TLS_AES_256_GCM_SHA384"`); `nil` leaves the Erlang/OTP defaults |

# `tls`

```elixir
@type tls() :: %{
  certfile: Path.t(),
  keyfile: Path.t(),
  cacertfile: Path.t() | nil,
  verify: :verify_none | :verify_peer,
  versions: [:&quot;tlsv1.2&quot; | :&quot;tlsv1.3&quot;],
  ciphers: [String.t()] | nil
}
```

A normalised `tls` block.

# `bandit_options`

```elixir
@spec bandit_options(Ankusa.Config.t(), tls() | nil, keyword()) :: keyword()
```

The Bandit options that apply `config.listener`'s limits and the listener's
own `tls` block. The caller adds `:plug`, `:port` and `:ip`.

`listener.max_connections` is spread over `listener.num_acceptors`
(Thousand Island counts connections per acceptor). `thousand_island_extra`
is appended to the Thousand Island options (the ingress listener passes its
`:shutdown_timeout`).

# `default_acceptors`

```elixir
@spec default_acceptors() :: pos_integer()
```

The default number of acceptors: two per scheduler.

# `ingress_shutdown_timeout_ms`

```elixir
@spec ingress_shutdown_timeout_ms(Ankusa.Config.t()) :: pos_integer()
```

How long the ingress listener waits, on a stop, for the requests it is
serving before it closes their connections (Thousand Island's
`:shutdown_timeout`, which defaults to 15 s: the batcher's own record
deadline, so a commit that starts late would be killed mid-fsync and the
provider would see a reset for a committed hook).

A request may wait its whole record deadline
(`Ankusa.Edge.Batcher.record_deadline_ms/0`) before its batch starts
committing, then needs the store commit (2000 ms of
budget; under `wal: :dual` also one intake publish,
`intake.publish_timeout_ms`), and the listener keeps 5000
ms in hand. Under `wal: :none` the request waits for its sinks instead:
`direct_publish_timeout_ms` plus the margin.

# `normalize_tls!`

```elixir
@spec normalize_tls!(term(), String.t()) :: tls() | nil
```

Validate one `tls` block (`key` is its dotted config name, for messages) and
return it as a map with every key present and every path absolute, or `nil`.
Raises `ArgumentError` naming the key.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
