Mosaic
Le framework web d'Amalgame. Serveur HTTPS productif dans la veine de Next.js (filesystem routing), Caddy (HTTPS automatique via ACME natif), Go net/http (thread pool, pas d'event loop). Stack 100 % Amalgame au-dessus d'OpenSSL 3.x et nghttp2 — sessions, auth, middleware de sécurité, statique : tout est déjà livré.
Installer & lancer
Mosaic se lance de deux façons. Comme serveur : on installe le binaire pré-compilé, on écrit un mosaic.toml, mosaic serve — aucun code Amalgame, aucun compilateur (façon nginx/Caddy). Comme bibliothèque : on ajoute la stack à son propre projet et on écrit ses handlers. Guide complet sur docs.amalgame.me → Mosaic.
Comme serveur — zéro code Amalgame
# installer une fois curl -sSL https://raw.githubusercontent.com/amalgame-lang/mosaic/main/install.sh | bash # lancer toute la pile depuis un fichier de config mosaic serve /etc/mosaic/mosaic.toml
Un binaire, N sites en HTTPS — hébergement statique, TLS + ACME, middleware par site, et des hosts en reverse proxy / load balancing. Un mosaic.toml mélange sites statiques et proxies :
[server] port = 443 tls = true [tls] acme = true email = "admin@example.com" # un site statique par Host [[site]] hosts = ["example.com", "www.example.com"] root = "/srv/example/public" # un reverse proxy / load balancer par 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" }, ]
Comme bibliothèque — écris tes propres handlers
# ajoute la stack à ton projet, puis build avec l'outil mosaic amc package add web net-http tls mosaic build # ou : écris server.am et `amc run`
Quatre façons de déployer, du serveur tout-en-config au binaire fait main — voir le guide d'installation.
Architecture en packages composables
La pile suit la convention Amalgame : des packages petits et focalisés. Tu importes juste TLS pour un protocole custom, juste HTTP pour un client/serveur bas niveau, ou amalgame-web + l'outil mosaic pour la pile complète.
amalgame-web
La bibliothèque runtime : WebApp, Router (:param + *splat), sessions (mémoire / cookie signé HMAC / Redis), middleware sécurité, auth (Basic / JWT / OAuth2), statique, 7 modes de Serve dont l'HTTPS in-process, et MosaicServer — un front HTTPS multi-sites multi-domaines (dispatch par Host + SNI + :80→:443) qui monte aussi des reverse proxies via AddHandler.
amalgame-net-http
Parser HTTP/1.1 pur AM + client HTTPS (décodage chunked). HTTP/2 via nghttp2 (h2c + ALPN h2). Serveur HTTPS-sur-HTTP/1.1 avec SNI (N domaines, 1 cert chacun) et keep-alive. Serveur async fibres (epoll). WebSocket RFC 6455. Limites parser anti-DDoS.
amalgame-tls
Binding OpenSSL 3.x (LibreSSL aussi). TLS 1.2/1.3, ALPN, SNI, client-auth. ACME natif RFC 8555 — state machine pure-AM, plus de sous-process certbot. Helpers de renouvellement embarqué.
amalgame-net-proxy
Reverse proxy + load balancer HTTP/1.1 — routage par plus long préfixe, X-Forwarded-For, strip hop-by-hop, round-robin pondéré / ip-hash / least-connections. Piloté par [[proxy]] dans mosaic serve, ou monté en code.
mosaic (l'outil)
L'outil de build/CLI — pas un package. Scanne app/ et génère _routes.am (convention Next.js), pilote amc + gcc, sert public/ automatiquement. dev, build, new, hot-reload — et serve, le serveur tout-en-config (binaire pré-compilé, zéro code Amalgame).
Hello world HTTPS — ACME natif, le binaire gère le reste
Un appel à AcmeNative.EnsureCert provisionne le certificat (challenge http-01, RFC 8555, sans certbot), puis ServeHttpsMt termine le TLS in-process — pas de reverse proxy requis.
namespace App import Amalgame.Web import Amalgame.Tls public class Program { public static void Main(string[] args) { // ACME RFC 8555 natif — pas de sous-process, clé compte ES256 AcmeNative.EnsureCert("mon-site.com", "admin@mon-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 générées depuis app/ app.ServeHttpsMt(443, "./certs/mon-site.com/fullchain.pem", "./certs/mon-site.com/privkey.pem") } }
Filesystem routing — convention Next.js
Chaque fichier .am sous app/ devient une route. Les noms en [id] capturent un paramètre (:id), [...slug] capture un catch-all (*slug). mosaic build scanne l'arbo et génère _routes.am ; Routes.Bind(app) branche le tout. Les fichiers de public/ sont servis automatiquement.
mon-app/ ├── amalgame.toml ├── mosaic.toml # [security.*], [tls], [logging]… ├── server.am # point d'entrée : 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/ # servi à / (ETag, 304, binary-safe) └── data/
Chaque fichier exporte une classe Page avec des méthodes nommées d'après le verbe HTTP (GET / POST / PUT / PATCH / DELETE), chacune recevant un 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) } }
L'outil mosaic — du scaffold à la prod
Quatre commandes couvrent tout le cycle. L'outil s'installe une fois (curl … install.sh | bash) et se lance depuis n'importe quel projet.
Scaffold (v0.4+)
mosaic new mon-app dépose un squelette de projet qui sert, et lance amc package add automatiquement — mosaic dev produit un binaire qui répond dès la commande suivante.
DEV — watch + livereload (v0.2/0.3+)
Surveille app/ et server.am, recompile + redémarre le serveur à chaque sauvegarde. Daemon WebSocket sur :35729 → le navigateur se rafraîchit après chaque build.
Hot-reload superviseur (v0.5+)
Le worker fraîchement buildé est swappé derrière un shim TCP : les connexions WebSocket en cours survivent aux éditions de code. Pas de coupure pour les clients connectés.
PROD — build one-shot
Pipeline complet : régénère _routes.am, lance amc puis gcc, produit le binaire. Lit amalgame.lock pour résoudre les archives de packages. Tu ships un binaire natif.
PROD — serveur tout-en-config (v0.7+)
mosaic serve mosaic.toml lance toute la pile depuis un fichier — N sites statiques, TLS + ACME, middleware par site, et des hosts en reverse proxy / load balancing. Un binaire pré-compilé, zéro code Amalgame (façon nginx/Caddy).
Sécurité L7 + auth built-in — déjà livré
La défense L3/L4 reste à l'opérateur (kernel sysctl, firewall, CDN). Tout l'applicatif est dans le framework, opt-in via .With*() et orchestré par WebApp dans le bon ordre du pipeline :
- ✓WithSecurityHeaders (v0.4) —
StrictHtml()/StrictApi(): HSTS, CSP, X-Frame-Options, nosniff, Referrer-Policy, Permissions-Policy, COOP/COEP - ✓WithCsrf (v0.7) — double-submit cookie, token 256 bits,
Secure+SameSite=Lax - ✓WithCors (v0.5) — deny par défaut,
AllowAll/Strictpresets, gestion preflight OPTIONS - ✓WithRateLimit (v0.6) — fenêtre fixe par IP, 429 +
Retry-Afterau dépassement - ✓WithStatic (v0.13 · caching v0.27) — service de fichiers, ETag fort +
Last-Modified,304(If-None-Match / If-Modified-Since), Range 206 / 416, sélection.gzpré-compressé, garde anti-traversée, ~35 types MIME - ✓WithCompress (v0.26) — compression gzip à la volée, négociée par
Accept-Encoding(text/*, JSON, JS, SVG, XML),Varyposé ; viaamalgame-compress - ✓Uploads multipart (v0.25) —
ctx.Multipart()→UploadedFile(binaire-safe,SaveTo/Bytes/Text) + champs texte, plafonné parmax_body_bytes - ✓Moteur de templates (v0.24) —
TemplateMustache-like, auto-échappement HTML par défaut (ferme le trou XSS de.Html()),ctx.Render(path, data);{{{x}}}pour le brut - ✓BasicAuth (v0.15, RFC 7617) · JwtAuth (v0.16, HS256) · OAuth2Client (v0.17, presets GitHub/Google, flow authorization code)
- ✓Sessions —
MemorySessionStore,SignedCookieSessionStore(HMAC-SHA-256, stateless),RedisSessionStore - ✓WithLogging (v0.8.2) — access logs structurés via
amalgame-logging, pilotés par[logging] - ✓Timeouts anti-slowloris via
HttpServerConfig— header/body/idle timeouts,max_body_bytes,max_header_bytes
HTTPS automatique via ACME natif RFC 8555
Un appel à AcmeNative.EnsureCert(domain, email, dir, server) et Mosaic provisionne le certificat via le challenge http-01, sur une state machine ACME pure-Amalgame (clé de compte ES256) — fini le sous-process certbot. Les certs sont écrits dans <dir>/<domain>/{fullchain.pem, privkey.pem}. Pour le renouvellement embarqué : AcmeNative.NeedsRenewal(certPath, days) + CertDaysRemaining(certPath) dans un thread. Staging Let's Encrypt via AcmeNative.LeStaging(). (Le wrapper certbot historique Acme.EnsureCertMulti reste dispo pour le multi-SAN en attendant le multi-SAN natif.)
WebSocket RFC 6455 — serveur intégré
Le serveur WebSocket vit dans amalgame-net-http : Ws.Serve en clair, Wss.Serve sur TLS. Le handler reçoit une connexion et boucle sur WsConn. Les variantes *With appliquent les mêmes timeouts socket que le serveur HTTP.
import Amalgame.Net.Http let handler = conn => { while (!WsConn.IsClosed(conn)) { let msg: string = WsConn.ReceiveText(conn) if (String_Length(msg) == 0) { return 0 } // déconnexion WsConn.SendText(conn, "echo @ " + msg) } return 0 } Ws.Serve(8080, handler) // ws:// — Wss.Serve(port, cert, key, handler) pour wss://
Négociation de sous-protocole (v0.17) — Ws.ServeWithProtocols(port, "v2.json,v1", handler) renvoie au client le premier Sec-WebSocket-Protocol supporté ; WsConn.Subprotocol(conn) le relit. Le pub/sub multi-clients façon Phoenix (channels, topics, broadcast) n'est pas encore une primitive du framework : à construire au-dessus de WsConn, ou en attendre une version dédiée (voir roadmap).
Server-Sent Events — push unidirectionnel
Pour du push live (progression, notifications, tickers) sans le poids d'un WebSocket : app.Sse("/events", sc => { ... }) (v0.28). Le handler tient la connexion et pousse des frames via SseConn — Send(data) (false quand le client part), SendEvent(event, data, id), Comment (heartbeat), Retry. Côté navigateur : new EventSource("/events").
Concurrence : thread pool ou fibres, au choix
Modèle Go net/http — blocking I/O, multi-core natif, pas d'async/await qui contamine le code. WebApp expose 7 modes de Serve ; tu choisis selon la charge.
- ✓Serve / ServeWith — boucle série HTTP/1.1, simple, pour dev ou charge faible
- ✓ServeMt / ServeMtWith — un thread par connexion (~8 MB de stack lazy), défaut prod pour handlers CPU-bound
- ✓ServeAsync (v0.12) — 1 thread, N fibres (~64 KB/conn), epoll Linux — idéal handlers I/O-bound, milliers de connexions
- ✓ServeHttps / ServeHttpsMt (v0.14) — terminaison TLS in-process, pas de reverse proxy requis
- ✓HTTP/1.1 keep-alive — actif dès que
idle_timeout_sec > 0 - ✓Graceful shutdown — handler SIGTERM/SIGINT, drain des requêtes en vol, exit propre
Roadmap — où on en est
La pile a démarré de zéro mi-mai 2026 et a livré, en quelques semaines, une pile web utilisable de bout en bout. Voici l'état réel.
Le cœur
Filesystem routing, HTTPS in-process, ACME natif RFC 8555, pile sécurité complète (CSRF/CORS/headers/rate-limit), auth (Basic/JWT/OAuth2), sessions (mémoire/cookie signé/Redis), statique, async fibres, hot-reload superviseur, client HTTPS.
Rendu, uploads & temps réel
Moteur de templates auto-échappé (anti-XSS), uploads multipart binaire-safe, compression gzip négociée, statique Last-Modified + Range 206 + sélection .gz, Server-Sent Events (WebApp.Sse), négociation de sous-protocole WebSocket. (Juin 2026.)
Affinage HTTP & TLS
sendfile(2) zero-copy pour le statique. ACME natif multi-SAN, SNI multi-certificats, OCSP stapling, challenge dns-01 + wildcards. PKCE + OIDC/id_token + JWT RS256. I/O async pour H2/HTTPS/WS (dépend des fibres côté TLS).
WebSocket & temps réel
Frames binaires + continuation, wss:// côté client, per-message-deflate. Primitive pub/sub channels façon Phoenix (topics + broadcast), multi-nœud via Redis Pub/Sub.
Ops & breadth
/metrics Prometheus, tracing OpenTelemetry, /healthz + /readyz, systemd Type=notify. Validation typée, itération de liste dans les templates. amalgame-email SMTP, amalgame-queue, amalgame-cron, amalgame-openapi.
Beyond HTTP — ambition « front door » nginx
Au-delà de l'écoute HTTPS, Mosaic vise le rôle de front door nginx/apache : reverse proxy, load balancing, proxy TCP/UDP brut. Inventaire dans la proposal beyond-http.md. Le reverse proxy et le load balancing sont livrés ; les briques raw-stream et relais restent de la roadmap.
Reverse proxy HTTP/HTTPS
amalgame-net-proxy — proxy en front de N upstreams : routage par plus long préfixe, X-Forwarded-For, strip hop-by-hop. Se configure en un bloc [[proxy]] de mosaic serve. HTTP/2 + WebSocket forwarding transparent encore en roadmap.
Load balancing
Round-robin pondéré, least-connections, IP-hash — un pool par route via upstreams = [...] + strategy. Active health checks, outlier detection et sticky sessions encore en roadmap.
TCP/UDP raw proxy
amalgame-net-stream — équivalent stream {} nginx ou HAProxy TCP mode. Front de Postgres, Redis Sentinel, MQTT, serveurs UDP.
SFTP / SMTP relay
amalgame-net-ssh (binding libssh2, SFTP automation) et amalgame-net-smtp (receive + forward, relay basique v0.1).
Suivre l'avancement
Mosaic est public et utilisable. La spec est la source de vérité — les phases avancent par PRs trackées sur les repos.