25. HttpServer & HTML UI Generation
This chapter covers MALDA's route-first web server, HTML page handlers, AI-generated page helpers, and HTML caching utilities. Use it when you want to serve pages with HttpServer and traditional request/response routes.
HttpServer, @PAGE, @AIPAGE, and HTML generation helpers. For server components and ui.*, see Web UI Server Components. For API-first routes, see REST API Server.
25.1 Chapter Map
- 25.2
HttpServerlifecycle and route model - 25.3 Request and response objects
- 25.4 Dynamic pages with
@PAGE - 25.5 AI-generated pages with
@AIPAGE - 25.6
@GET/@POSTin page-hosting flows - 25.7 Redirects and multi-step page flows
- 25.8 HTML generation helpers and caching
- 25.9 Request-to-render lifecycle and production notes
- 25.10 Production hardening
- 25.11 Testing and troubleshooting
25.2 HttpServer Class
The HttpServer class provides a built-in HTTP server for static assets, server-generated HTML, AI-generated pages, form handling, and route handlers. It works well for route-first applications where each request returns a full document or a targeted response.
25.2.1 Constructor
var server = new HttpServer(port);
// OR
var server = new HttpServer(port, webDirectory); // Custom web directory
// OR
var server = new HttpServer(port, webDirectory, pathBase, host);
port must be an integer in 1-65535. Privileged ports (1-1023, e.g. 80/443) are allowed; binding may still require elevated permissions or OS URL reservations.
Bind host defaults to localhost (loopback only). Pass host as the optional fourth argument, call setHost(host) before start(), or set the MALDA_HTTP_HOST environment variable. Use 0.0.0.0 or * to listen on all interfaces (same prefix rules as RestServer). The current bind host is available as the host property.
HTTPS is optional and cross-platform. Call enableHttps(certPath, password?) before start() with a .pfx/.p12 file, or a PEM .pem/.crt (private key beside it as .key). Environment alternatives: MALDA_HTTP_HTTPS=1, MALDA_HTTP_CERT, and optional MALDA_HTTP_CERT_PASSWORD. Properties: https, certPath. TLS is terminated by Kestrel; the existing request pipeline still runs on a loopback HttpListener.
25.2.2 Core methods
start(): Start the server and scan for decorated routes.stop(): Stop the server gracefully.setHost(host): Set the bind host beforestart()(localhost, IP/hostname, or0.0.0.0/*for all interfaces).enableHttps(certPath, password?)/disableHttps(): Enable or disable TLS beforestart().getRoutes(): Return the registered route table.clearCache(): Clear the static file cache.setHTML(html): Set HTML content for the root path.use(middleware): Register global middleware withreq,res, andnext.enableCsrf(secret, cookieName?, headerName?)/disableCsrf(): Toggle built-in CSRF protection.enableSession(secret, options?)/disableSession(): Signed session-id cookie with in-memory or SQLite store; exposereq.sessionand flash helpers.mount(restServer): Serve aRestServeron the same listener/port (API routes first, then pages/components/static).setRateLimit(limit, windowSeconds, keyStrategy?)/disableRateLimit(): Toggle endpoint rate limiting.
25.2.3 Minimal host example
var server = new HttpServer(8080);
server.start();
print("Server running at http://localhost:8080");
while (server.isRunning) {
sleep(1000);
}
// HTTPS (optional):
// var tls = new HttpServer(8443);
// tls.setHost("0.0.0.0");
// tls.enableHttps("ask.pfx", "secret");
// tls.start();
// print("Server running at https://localhost:8443");
25.3 Request and Response Objects
Page and route handlers can receive request context and return either raw HTML or a structured response object.
- Request:
method,path,query,params,headers,cookies,body,auth,session,correlationId,ip/remoteIp. - Session:
req.session.get/set/delete/clear,flash/getFlash/getFlashes(one-request notices after redirect). - Response helpers:
status,json,text,html,redirect,header,cookie, andsend. - Form helpers:
csrfField(secret),bindForm(body, fields),formErrors(errors),pageLayout(title, bodyHtml, options?). Preferui.layoutfor rich templates. - Route metadata:
@RouteGroup/@Group/@Prefix,@Version/@ApiVersion,@Use/@Middleware, and@Validate.
When a route is API-style, framework failures return a standardized JSON payload with status, error, message, and correlationId, with optional details for validation failures.
25.4 Dynamic Pages with @PAGE
Use @PAGE for request-driven HTML pages. A page handler can return a full HTML document directly, and path parameters are bound from the URL.
@PAGE("/")
function handleHome() {
return "<html><body><h1>Home</h1></body></html>";
}
@PAGE("/user/{id}")
function handleUser(id) {
return "<html><body><h1>User: " + id + "</h1></body></html>";
}
Use @PAGE when the request should produce a complete page. If the page needs interactive fragments, live updates, or server-managed component state, move reusable UI into a component flow and keep the route as the entry point.
25.5 AI-Generated Pages with @AIPAGE
Use @AIPAGE when you want MALDA to generate the initial HTML based on a prompt-like description.
@AIPAGE("/", "Contact form with name, email, and message fields")
function homePage() {
// AI generates the HTML automatically on first access
return "";
}
- AI generates HTML from the description.
- Generated HTML is cached for performance.
- The AJAX helper script is injected automatically for modern form submission.
- If no model is configured, the UI generation agent uses the default local LLM (Qwen/Qwen2.5-0.5B-Instruct, downloaded as a GGUF build from Hugging Face); you can also provide an
OpenRouterClientandAgent.
25.5.1 Complete AI form example
var server = new HttpServer(8080);
@AIPAGE("/", "Contact form with name, email, and message fields")
function homePage() {
return "";
}
@POST("/submit")
function handleSubmit(body) {
print("Form submitted!");
print("Name: " + body.name);
print("Email: " + body.email);
print("Message: " + body.message);
return {
"status": 200,
"body": "<html><body><h1>Thank You!</h1><p>Your message has been received.</p><p><a href='/'>Back to form</a></p></body></html>"
};
}
server.start();
25.6 GET/POST Routes in Page-Hosting Flows
HttpServer can host @GET and @POST routes alongside pages. That is useful for AJAX handlers, small JSON endpoints, or Server-Sent Events used by a page. For full API design guidance, advanced validation, and endpoint organization, use REST API Server as the primary reference.
25.6.1 GET handlers
@GET("/api/users")
function getUsers() {
return {"status": 200, "users": ["Alice", "Bob", "Charlie"]};
}
@GET("/api/events")
function getEvents() {
return {"sse": true};
}
- Return JSON objects for normal API-style responses.
- Return
{"sse": true}to enable SSE. - Use path parameters such as
@GET("/api/users/{id}")when needed.
25.6.2 POST handlers
@POST("/submit")
function handleSubmit(body) {
print("Name: " + body.name);
print("Email: " + body.email);
return {
"status": 200,
"body": "<html><body><h1>Thank You!</h1></body></html>"
};
}
- The request body is parsed automatically as JSON or form-urlencoded data.
- The parsed payload is available through a
bodyparameter. - POST handlers work well for form submissions from
@PAGEor@AIPAGEpages.
25.7 Redirects with RedirectTo
Use redirect(location, status?) for explicit redirect responses, or the legacy alias RedirectTo(location) when migrating existing code.
@POST("/login")
function handleLogin(body) {
if (body.username == "admin" && body.password == "secret") {
return redirect("/dashboard");
}
return redirect("/login?error=invalid");
}
@PAGE("/old-page")
function oldPage() {
return redirect("/new-page", 302);
}
For production HTML apps, prefer password hashing + JWT in a signed cookie instead of plaintext checks. See Examples/Web/auth_cookie_login.malda and the shared req.auth helpers (same surface as RestServer).
function requireAuth(req, res, next) {
req.auth.authenticateCookieJwt("session", jwtSecret, cookieSecret);
next();
}
server.use(requireAuth, { "except": ["/", "/login"] });
redirect(location)returns a response object with status303 See Otherand aLocationheader, which is the safest default after a POST action.- Pass an explicit 3xx status when you need a different redirect semantic, for example
redirect("/new-page", 302). RedirectTo(location)remains available as a compatibility alias and follows the same default303behavior.- It works with both regular browser requests and AJAX-driven form submissions.
25.8 HTML Generation Helpers
MALDA includes helper utilities for generated HTML and server-rendered UI prototypes.
25.8.1 HTMLCache Class
The HTMLCache class stores generated HTML to reduce repeated LLM work and improve response times.
var cache = new HTMLCache(cacheDirectory?, maxSize?, expirationHours?);
get(prompt): Get cached HTML ornull.set(prompt, html, metadata?): Store generated HTML.has(prompt): Check whether a prompt has a cached entry.clear(): Clear all cached entries.
25.8.2 extractHTML Function
extractHTML(markdown) extracts HTML from markdown code fences or returns HTML as-is.
var html = extractHTML(markdown);
25.8.3 markdownToHtml Function
markdownToHtml(markdown) converts Markdown to an HTML fragment (headings, emphasis, lists, tables, fenced code, and similar). Raw HTML tags in the input are disabled so untrusted model output is safer to embed in a page. Returns a string; wrap it in your own document chrome.
var body = markdownToHtml("**Hello** and a table:\n\n| A | B |\n|---|---|\n| 1 | 2 |");
return "<html><body class='md'>" + body + "</body></html>";
25.8.4 generateUI Function
generateUI is a convenience helper for prompt-driven HTML generation with optional caching and a custom agent.
var html = generateUI(description, cache?, agent?);
25.8.5 ui.generate Function
Use ui.generate when AI should return a structured server UI tree instead of an HTML string.
var tree = ui.generate("Dashboard with title, filter row, and data grid", uiAgent, cache);
var envelope = ui.mountEnvelope(tree, "dashboard-session");
return envelope;
- The result is a root node object with
type,props,children, and an optionalkey. - The UI runtime validates the generated tree before it is returned.
- Use this when you want AI assistance but still want the server-components protocol described in Web UI Server Components.
25.9 Request-to-Render Lifecycle
- The browser requests a page or route handled by
@PAGE,@AIPAGE,@GET, or@POST. HttpServerresolves the route and loads any required request context.- The handler returns HTML, a redirect, JSON, SSE, or a generated UI artifact.
- The browser either renders the returned document, follows the redirect, or processes the API response.
25.10 Production Hardening
- Enable CSRF protection on mutating routes with
enableCsrf. - Apply rate limiting with
setRateLimit(runs after auth middleware soverifiedSubOrIpcan key by JWT subject). - Protect routes with
req.auth.authenticateBearerJwt/authenticateCookieJwtand skip public paths viause(fn, { "except": [...] }). - Validate request input before persistence or side effects.
- Use correlation IDs in logs for route-level debugging.
- Clear generated HTML caches when the prompt contract or page template changes.
25.11 Testing and Troubleshooting
- Use
getRoutes()to verify that page and handler routes were registered. - If a page does not update after prompt changes, clear both the server cache and the
HTMLCacheentry. - For form issues, confirm the request body shape your handler expects.
- For SSE routes, verify the client connects to the correct URL and the handler returns
{"sse": true}.
See Also
- 24. Web UI Server Components - Component-oriented server UI, fragments, live updates, and
ui.* - REST API Server - API-focused route design, middleware, validation, and policies
- Full-Stack Development with MALDA - Architecture guidance for mixing pages, APIs, and browser UI