Build Applications

Bounded HTTP Service

Build, run, and deploy a checked Topaz HTTP/1.1 service with explicit limits.

The http-service target turns one checked Topaz request handler into a managed native HTTP/1.1 service. The generated host owns the listener, framing, deadlines, overload response, logging, and shutdown. Your Topaz code receives an HttpRequest and returns an HttpResponse.

This is a deliberately bounded service host, not a general Web framework. It does not add outbound networking, sockets, TLS, HTTP/2, WebSocket, sessions, shared mutable application state, ambient environment access, or async/await syntax. Put an ordinary reverse proxy in front of the generated process when you need public TLS or Internet-facing policy.

Create and inspect the package

BASH
topaz init --target http-service --root hello-service
topaz fmt --root hello-service --check
topaz check --root hello-service
topaz test --root hello-service

The entry exports exactly one concrete handler with this checked shape:

TOPAZ
import std.http { HttpRequest, HttpResponse, text }

export function handle(req: HttpRequest) -> HttpResponse {
  if req.method == "GET" && req.url.path() == "/health" {
    return text(200, "ok")
  }
  text(404, "not found")
}

The listener does not start if handle is missing, generic, variadic, defaulted, or has another request or response type.

Develop through real loopback HTTP

BASH
topaz dev --root hello-service --port 8080
curl --fail-with-body http://127.0.0.1:8080/health

topaz dev always overrides the bind address to 127.0.0.1. It builds the same managed service executable used by deployment and transfers interruption to that process, so Ctrl-C exercises the real shutdown path.

Configure finite budgets

[service] rejects unknown keys and invalid ranges. These defaults are part of the generated product:

TOML
[service]
bind = "127.0.0.1"
port = 8080
workers = 1
max_connections = 64
queue_capacity = 32
max_target_bytes = 8192
max_header_bytes = 16384
max_headers = 64
max_body_bytes = 1048576
header_timeout_ms = 5000
body_timeout_ms = 5000
handler_timeout_ms = 1000
shutdown_grace_ms = 5000
log_format = "text"

workers is bounded concurrent handler capacity on a current-thread reactor. Every request gets a fresh runtime context and module graph; requests do not share a Topaz heap. A timed-out cooperative handler is dropped and its capacity becomes reusable. Potentially unbounded generated loops include cancellation checkpoints, while synchronous leaf work remains bounded by the admitted input sizes.

The executable accepts the same settings as kebab-case flags. A manifest can name only a loopback IP. A built service may bind another IP only through an explicit process argument:

BASH
./target/release/program --bind 0.0.0.0 --port 8080

Inspect the exact validated values after any overrides without opening a listener:

BASH
./target/release/program --port 9090 --workers 2 --print-config

The command prints one topaz.httpServiceConfig.v1 JSON object. The managed topaz-service-config.json uses the same schema for embedded defaults, while --print-config labels the values as the effective runtime configuration. Unknown, duplicate, malformed, or out-of-range options fail before bind.

Build and copy the product

BASH
topaz build --root hello-service --locked --release --out-dir hello-service-product
cp -R hello-service-product /srv/hello-service
cd /srv/hello-service
./target/release/program --help
./target/release/program

The managed directory contains the native executable, topaz-service-config.json, third-party notices, Topaz license and notices, and topaz-artifact.json. The binary does not need package source, a Topaz checkout, Cargo registry access, or the temporary build workspace at runtime. Copy the complete directory so integrity metadata and notices stay with the executable.

Failures and observations

  • Oversized bodies return 413; oversized targets return 414; oversized header sets return 431 where HTTP framing permits a response.
  • Handler queue overload returns 503 with Connection: close.
  • A handler deadline returns a generic 504 and releases its worker capacity.
  • Runtime faults and invalid responses return a generic 500 without exposing source paths, request bodies, header values, or Topaz fault messages.
  • Responses include x-topaz-request-id. Text or topaz.httpServiceLog.v1 JSON host logs contain the same request ID, a bounded code, and status or local diagnostic, but never targets, bodies, or header values.
  • Logs distinguish service-started, shutdown-requested, shutdown-complete, and shutdown-forced. log_format = "off" suppresses service logs; explicit --print-config output remains available.
  • SIGINT and, on Unix, SIGTERM stop accepting new connections and drain existing connections up to shutdown_grace_ms before reporting graceful or forced completion.

The host validates status codes and header syntax and owns Content-Length, transfer encoding, and hop-by-hop headers. Application responses cannot override those transport fields.

Deployment boundary

Run the executable under a process supervisor. Terminate TLS and newer HTTP protocols at a reverse proxy, forward plain HTTP/1.1 to the configured service address, keep the generated body/header/deadline limits enabled, and use /health only for the readiness behavior your Topaz handler actually implements. A copied previous public-minor artifact is the rollback unit; configuration changes do not rewrite its managed bytes.