Mosaic 🟢 Beta — full HTTPS stack shipped

Mosaic

Amalgame's web framework. A productive HTTPS server in the spirit of Next.js (filesystem routing), Caddy (automatic HTTPS via native ACME), Go net/http (thread pool, no event loop). 100% Amalgame stack on top of OpenSSL 3.x and nghttp2 — sessions, auth, security middleware, static files: all already shipped.

Install & run

Mosaic runs two ways. As a server: install the prebuilt binary, write a mosaic.toml, mosaic serve — no Amalgame code, no compiler (nginx/Caddy style). As a library: add the stack to your own project and write your own handlers. Full guide on docs.amalgame.me → Mosaic.

As a server — zero Amalgame code

# install once
curl -sSL https://raw.githubusercontent.com/amalgame-lang/mosaic/main/install.sh | bash

# run the whole stack from a config file
mosaic serve /etc/mosaic/mosaic.toml

One binary, N sites over HTTPS — static hosting, TLS + ACME, per-site middleware, and reverse-proxy / load-balanced hosts. A mosaic.toml mixes static sites and proxies:

[server]
port = 443
tls  = true

[tls]
acme  = true
email = "admin@example.com"

# a static site by Host
[[site]]
hosts = ["example.com", "www.example.com"]
root  = "/srv/example/public"

# a reverse proxy / load balancer by Host
[[proxy]]
hosts  = ["api.example.com"]
routes = [
  { prefix = "/",      upstream  = "http://127.0.0.1:8080" },
  { prefix = "/heavy", upstreams = ["http://127.0.0.1:9001", "http://127.0.0.1:9002"], strategy = "round_robin" },
]

As a library — write your own handlers

# add the stack to your project, then build with the mosaic tool
amc package add web net-http tls
mosaic build  # or: write server.am and `amc run`

Four ways to deploy, from a config-only server to a hand-rolled binary — see the installation guide.

Composable-package architecture

The stack follows the Amalgame convention: small, focused packages. Import just TLS for a custom protocol, just HTTP for a low-level client/server, or amalgame-web + the mosaic tool for the full stack.

v0.23.0 shipped

amalgame-web

The runtime library: WebApp, Router (:param + *splat), sessions (memory / signed cookie HMAC / Redis), security middleware, auth (Basic / JWT / OAuth2), static files, 7 Serve modes including in-process HTTPS, and MosaicServer — a multi-site, multi-domain HTTPS front (Host dispatch + SNI + :80→:443) that also mounts reverse proxies via AddHandler.

v0.13.6 shipped

amalgame-net-http

Pure-AM HTTP/1.1 parser + HTTPS client (chunked decode). HTTP/2 via nghttp2 (h2c + ALPN h2). HTTPS-over-HTTP/1.1 server with SNI (N domains, 1 cert each) and keep-alive. Fiber-driven async server (epoll). RFC 6455 WebSocket. Parser limits for L7 anti-DDoS.

v0.3.3 shipped

amalgame-tls

OpenSSL 3.x binding (LibreSSL too). TLS 1.2/1.3, ALPN, SNI, client-auth. Native RFC 8555 ACME — pure-AM state machine, no certbot subprocess. Embedded-renewal helpers.

v0.2.1 shipped

amalgame-net-proxy

HTTP/1.1 reverse proxy + load balancer — longest-prefix routing, X-Forwarded-For, hop-by-hop stripping, weighted round-robin / ip-hash / least-connections. Driven by [[proxy]] in mosaic serve, or mounted in code.

v0.7.0 shipped

mosaic (the tool)

The build tool / CLI — not a package. Scans app/ and generates _routes.am (Next.js convention), drives amc + gcc, serves public/ automatically. dev, build, new, hot-reload — and serve, the config-driven server (prebuilt binary, zero Amalgame code).

HTTPS Hello world — native ACME, the binary handles the rest

One AcmeNative.EnsureCert call provisions the certificate (http-01 challenge, RFC 8555, no certbot), then ServeHttpsMt terminates TLS in-process — no reverse proxy required.

namespace App
import Amalgame.Web
import Amalgame.Tls

public class Program {
    public static void Main(string[] args) {
        // Native RFC 8555 ACME — no subprocess, ES256 account key
        AcmeNative.EnsureCert("my-site.com", "admin@my-site.com",
                            "./certs", AcmeNative.LeStaging())

        let app: WebApp = WebApp.New()
            .WithSecurityHeaders(SecurityHeaders.StrictHtml())  // HSTS, CSP, XFO…
            .WithCsrf(Csrf.Default())                          // double-submit cookie
            .WithRateLimit(RateLimit.PerIp(100, 60))            // 100 req / 60 s / IP
            .WithStatic(Static.New("/assets", "./public").WithCacheMaxAge(3600))
        Routes.Bind(app)                                  // routes generated from app/

        app.ServeHttpsMt(443, "./certs/my-site.com/fullchain.pem",
                              "./certs/my-site.com/privkey.pem")
    }
}

Filesystem routing — Next.js convention

Every .am file under app/ becomes a route. Names with [id] capture a parameter (:id), [...slug] captures a catch-all (*slug). mosaic build scans the tree and generates _routes.am; Routes.Bind(app) wires it all up. Files in public/ are served automatically.

my-app/
├── amalgame.toml
├── mosaic.toml              # [security.*], [tls], [logging]…
├── server.am                # entry point: WebApp + Routes.Bind
├── app/
│   ├── index.am             → GET /
│   ├── about.am             → GET /about
│   ├── users/
│   │   ├── index.am         → GET /users
│   │   ├── [id].am          → GET /users/:id
│   │   └── [id]/posts.am    → GET /users/:id/posts
│   └── api/
│       ├── login.am         → POST /api/login
│       └── [...path].am     → catch-all route
├── lib/
├── public/                  # served at / (ETag, 304, binary-safe)
└── data/

Each file exports a Page class with methods named after the HTTP verb (GET / POST / PUT / PATCH / DELETE), each taking a WebContext:

// app/users/[id].am
namespace App.Users.Detail
import Amalgame.Web
import Amalgame.Net.Http

public class Page {
    public static HttpResponse GET(ctx: WebContext) {
        let id: string = ctx.Param("id")
        let user = Db.FindUser(id)
        if (user == null) {
            return HttpResponse.New().Status(404).Text("User not found")
        }
        return HttpResponse.New().Json(user)
    }
}

The mosaic tool — from scaffold to prod

Four commands cover the whole cycle. The tool installs once (curl … install.sh | bash) and runs from any project.

mosaic new

Scaffold (v0.4+)

mosaic new my-app drops a working project skeleton and auto-runs amc package addmosaic dev produces a serving binary on the very next command.

mosaic dev

DEV — watch + livereload (v0.2/0.3+)

Watches app/ and server.am, rebuilds + restarts the server on every save. WebSocket daemon on :35729 → the browser refreshes after each build.

mosaic dev --supervise

Hot-reload supervisor (v0.5+)

The freshly-built worker is swapped behind a TCP shim: in-flight WebSocket connections survive code edits. No disruption for connected clients.

mosaic build

PROD — one-shot build

Full pipeline: regenerate _routes.am, run amc then gcc, produce the binary. Reads amalgame.lock to resolve package archives. You ship a native binary.

mosaic serve

PROD — config-driven server (v0.7+)

mosaic serve mosaic.toml runs the whole stack from a config file — N static sites, TLS + ACME, per-site middleware, and reverse-proxy / load-balanced hosts. A prebuilt binary, no Amalgame code (nginx/Caddy style).

L7 security + auth built-in — already shipped

L3/L4 defense stays with the operator (kernel sysctl, firewall, CDN). Everything application-layer is in the framework, opt-in via .With*() and orchestrated by WebApp in the right pipeline order:

  • WithSecurityHeaders (v0.4) — StrictHtml() / StrictApi(): HSTS, CSP, X-Frame-Options, nosniff, Referrer-Policy, Permissions-Policy, COOP/COEP
  • WithCsrf (v0.7) — double-submit cookie, 256-bit token, Secure + SameSite=Lax
  • WithCors (v0.5) — deny by default, AllowAll / Strict presets, OPTIONS preflight handling
  • WithRateLimit (v0.6) — per-IP fixed window, 429 + Retry-After on overflow
  • WithStatic (v0.13 · caching v0.27) — file serving, strong ETag + Last-Modified, 304 (If-None-Match / If-Modified-Since), Range 206 / 416, pre-compressed .gz selection, path-traversal guard, ~35 MIME types
  • WithCompress (v0.26) — on-the-fly gzip, negotiated via Accept-Encoding (text/*, JSON, JS, SVG, XML), sets Vary; via amalgame-compress
  • Multipart uploads (v0.25) — ctx.Multipart()UploadedFile (binary-safe, SaveTo / Bytes / Text) + text fields, capped by max_body_bytes
  • Template engine (v0.24) — Mustache-like Template, HTML auto-escape by default (closes the .Html() XSS hole), ctx.Render(path, data); {{{x}}} for raw
  • BasicAuth (v0.15, RFC 7617) · JwtAuth (v0.16, HS256) · OAuth2Client (v0.17, GitHub/Google presets, authorization-code flow)
  • SessionsMemorySessionStore, SignedCookieSessionStore (HMAC-SHA-256, stateless), RedisSessionStore
  • WithLogging (v0.8.2) — structured access logs via amalgame-logging, driven by [logging]
  • Anti-slowloris timeouts via HttpServerConfig — header/body/idle timeouts, max_body_bytes, max_header_bytes

Automatic HTTPS via native RFC 8555 ACME

One AcmeNative.EnsureCert(domain, email, dir, server) call and Mosaic provisions the certificate via the http-01 challenge, on a pure-Amalgame ACME state machine (ES256 account key) — no more certbot subprocess. Certs are written to <dir>/<domain>/{fullchain.pem, privkey.pem}. For embedded renewal: AcmeNative.NeedsRenewal(certPath, days) + CertDaysRemaining(certPath) in a thread. Let's Encrypt staging via AcmeNative.LeStaging(). (The legacy certbot wrapper Acme.EnsureCertMulti stays available for multi-SAN until native multi-SAN lands.)

RFC 6455 WebSocket — built-in server

The WebSocket server lives in amalgame-net-http: Ws.Serve in cleartext, Wss.Serve over TLS. The handler receives a connection and loops on WsConn. The *With variants apply the same socket timeouts as the HTTP server.

import Amalgame.Net.Http

let handler = conn => {
    while (!WsConn.IsClosed(conn)) {
        let msg: string = WsConn.ReceiveText(conn)
        if (String_Length(msg) == 0) { return 0 }   // disconnected
        WsConn.SendText(conn, "echo @ " + msg)
    }
    return 0
}

Ws.Serve(8080, handler)                          // ws://  — Wss.Serve(port, cert, key, handler) for wss://

Subprotocol negotiation (v0.17) — Ws.ServeWithProtocols(port, "v2.json,v1", handler) echoes back the first Sec-WebSocket-Protocol the server supports; WsConn.Subprotocol(conn) reads it. Phoenix-style multi-client pub/sub (channels, topics, broadcast) is not yet a framework primitive: build it on top of WsConn, or wait for a dedicated version (see roadmap).

Server-Sent Events — one-way push

For live push (progress, notifications, tickers) without the weight of a WebSocket: app.Sse("/events", sc => { ... }) (v0.28). The handler holds the connection and pushes frames via SseConnSend(data) (false when the client leaves), SendEvent(event, data, id), Comment (heartbeat), Retry. Browser side: new EventSource("/events").

Concurrency: thread pool or fibers, your choice

Go net/http model — blocking I/O, native multi-core, no async/await contaminating your code. WebApp exposes 7 Serve modes; pick based on load.

  • Serve / ServeWith — serial HTTP/1.1 loop, simple, for dev or low load
  • ServeMt / ServeMtWith — one thread per connection (~8 MB lazy stack), prod default for CPU-bound handlers
  • ServeAsync (v0.12) — 1 thread, N fibers (~64 KB/conn), Linux epoll — ideal for I/O-bound handlers, thousands of connections
  • ServeHttps / ServeHttpsMt (v0.14) — in-process TLS termination, no reverse proxy required
  • HTTP/1.1 keep-alive — enabled whenever idle_timeout_sec > 0
  • Graceful shutdown — SIGTERM/SIGINT handler, drains in-flight requests, clean exit

Roadmap — where we are

The stack started from scratch in mid-May 2026 and shipped, in a few weeks, an end-to-end usable web stack. Here's the real state.

Shipped

The core

Filesystem routing, in-process HTTPS, native RFC 8555 ACME, full security pack (CSRF/CORS/headers/rate-limit), auth (Basic/JWT/OAuth2), sessions (memory/signed cookie/Redis), static files, fiber async, hot-reload supervisor, HTTPS client.

Shipped

Rendering, uploads & real-time

Auto-escaping template engine (anti-XSS), binary-safe multipart uploads, negotiated gzip compression, static Last-Modified + Range 206 + .gz selection, Server-Sent Events (WebApp.Sse), WebSocket subprotocol negotiation. (June 2026.)

Close

HTTP & TLS polish

sendfile(2) zero-copy for static. Native multi-SAN ACME, multi-cert SNI, OCSP stapling, dns-01 challenge + wildcards. PKCE + OIDC/id_token + RS256 JWT. Async I/O for H2/HTTPS/WS (gated on TLS-side fibers).

Later

WebSocket & real-time

Binary + continuation frames, wss:// client-side, per-message-deflate. Phoenix-style pub/sub channels primitive (topics + broadcast), multi-node via Redis Pub/Sub.

Later

Ops & breadth

Prometheus /metrics, OpenTelemetry tracing, /healthz + /readyz, systemd Type=notify. Typed validation, template list iteration. amalgame-email SMTP, amalgame-queue, amalgame-cron, amalgame-openapi.

Beyond HTTP — nginx "front door" ambition

Beyond HTTPS listening, Mosaic aims at the nginx/apache front-door role: reverse proxy, load balancing, raw TCP/UDP proxying. Inventory in the beyond-http.md proposal. Reverse proxy and load balancing have shipped; the raw-stream and relay bricks remain roadmap.

shipped

HTTP/HTTPS reverse proxy

amalgame-net-proxy — proxy in front of N upstreams: longest-prefix routing, X-Forwarded-For, hop-by-hop stripping. Configure it in one [[proxy]] block of mosaic serve. HTTP/2 + transparent WebSocket forwarding still roadmap.

shipped

Load balancing

Weighted round-robin, least-connections, IP-hash — a pool per route via upstreams = [...] + strategy. Active health checks, outlier detection and sticky sessions are still roadmap.

prio MED

TCP/UDP raw proxy

amalgame-net-stream — nginx stream {} or HAProxy TCP mode equivalent. Fronting Postgres, Redis Sentinel, MQTT, UDP servers.

prio LOW

SFTP / SMTP relay

amalgame-net-ssh (libssh2 binding, SFTP automation) and amalgame-net-smtp (receive + forward, basic v0.1 relay).

Follow the progress

Mosaic is public and usable. The spec is the source of truth — phases advance through PRs tracked on the repos.