{"name":"static_server.nu","source":"// examples/static_server.nu — production-style static file server.\n//\n// One-file demo that exercises the full HTTP server stack:\n//\n//     tcp_listen / server_new / server_run\n//     router_get + named/wildcard captures\n//     serve_static + mime_for_ext\n//     with_cors_default\n//     with_metrics + metrics_handler\n//     with_access_log\n//     signal_install_shutdown (Ctrl+C / SIGTERM)\n//\n// Build & run:\n//\n//     ./build.sh                # or .\\build.bat on Windows\n//     ./nurl.sh examples/static_server.nu\n//\n// Probe from another terminal:\n//\n//     curl -i http://127.0.0.1:18080/                 # public/index.html\n//     curl -i http://127.0.0.1:18080/api/health       # JSON\n//     curl -i http://127.0.0.1:18080/metrics          # Prometheus text\n//     curl -i http://127.0.0.1:18080/../etc/passwd    # 403 (path traversal)\n//     curl -i http://127.0.0.1:18080/no-such-file     # 404\n//\n//     # Then Ctrl+C in the server's terminal — server prints\n//     # \"static server: clean shutdown\" and exits with status 0.\n//\n// On first run the server creates `./public/index.html` so a fresh\n// checkout has something to serve. Drop your own files into `./public/`\n// (CSS, images, etc.) — `serve_static` figures the Content-Type out\n// from the extension and reads the file off disk per request.\n\n// One-line batteries-included pull-in. `http_full.nu` aggregates the\n// whole HTTP stack — request parser, response builder, server,\n// router, static, auth, cookies, middleware, multipart, signals,\n// HTTP client. Per-module includes like `$ \\`stdlib/ext/http_server.nu\\``\n// still work for callers who want a leaner include surface.\n$ `stdlib/ext/http_full.nu`\n$ `stdlib/std/fs.nu`\n\n// ── Boot-time setup ──────────────────────────────────────────────────\n//\n// Plant a tiny index.html under ./public on first run so the example\n// has something to demonstrate without checking files into the repo.\n// Errors are tolerated — if the directory already exists or can't be\n// written, we proceed and let serve_static's 404 path handle it.\n\n@ setup_public_dir → v {\n    ? ! ( file_exists `public/index.html` ) {\n        : !v IoErr dr ( dir_create `public` )\n        ?? dr { T → {} F _ → {} }\n        : s body `<!doctype html>\\n<html><head><meta charset=\"utf-8\"><title>NURL static server</title></head>\\n<body>\\n<h1>NURL static server is running.</h1>\\n<p>Try <a href=\"/api/health\">/api/health</a> or <a href=\"/metrics\">/metrics</a>.</p>\\n<p>Drop more files into <code>./public/</code> and refresh.</p>\\n</body></html>\\n`\n        : !v IoErr wr ( write_file `public/index.html` body )\n        ?? wr {\n            T → { ( nurl_eprint `[boot] wrote public/index.html\\n` ) }\n            F _ → { ( nurl_eprint `[boot] could not write public/index.html (continuing)\\n` ) }\n        }\n    } {}\n}\n\n// ── Route handlers ───────────────────────────────────────────────────\n\n@ h_health HttpRequest req Params params → HttpResponse {\n    // Start from the text-response shape, then override Content-Type.\n    // `response_set_header` deduplicates by name (case-insensitive), so\n    // the second call REPLACES the `text/plain` default rather than\n    // appending a duplicate header.\n    : HttpResponse r ( response_text 200 `{\"ok\":true,\"server\":\"nurl-static-demo\"}\\n` )\n    ( response_set_header r `Content-Type` `application/json; charset=utf-8` )\n    ^ r\n}\n\n// `/` and `/*path` both fall through to serve_static. The router only\n// passes the request along — serve_static reads `req.path` itself,\n// strips the leading '/', joins under `public/`, rejects `..`\n// segments, picks Content-Type from the extension, returns 404 if the\n// file is missing.\n@ h_static HttpRequest req Params params → HttpResponse {\n    ^ ( serve_static `public` req )\n}\n\n// ── main ─────────────────────────────────────────────────────────────\n\n@ main → i {\n    ( setup_public_dir )\n\n    : !TcpListener NetErr lr ( tcp_listen `127.0.0.1` 18080 )\n    ?? lr {\n        T listener → {\n            // Counter bag — captured by `with_metrics` AND by the\n            // `/metrics` route handler. Both keep the same handle alive.\n            : Metrics m ( metrics_new )\n\n            // Router build-up.\n            : Router r ( router_new )\n\n            ( router_get r `/metrics`\n            \\ HttpRequest req Params params → HttpResponse { ^ ( metrics_handler m req ) } )\n\n            ( router_get r `/api/health`\n            \\ HttpRequest req Params params → HttpResponse { ^ ( h_health req params ) } )\n\n            // Static fallback — `/` first so it picks up index.html, then\n            // `/*path` for any deeper file under public/.\n            ( router_get r `/`\n            \\ HttpRequest req Params params → HttpResponse { ^ ( h_static req params ) } )\n            ( router_get r `/*path`\n            \\ HttpRequest req Params params → HttpResponse { ^ ( h_static req params ) } )\n\n            // Middleware compose, innermost-first:\n            //     base    →  router_handle (dispatch)\n            //     cors    →  CORS headers + OPTIONS preflight\n            //     metered →  Prometheus counters update\n            //     logged  →  one stderr line per request\n            // The OUTERMOST wrapper (`logged`) is what the server sees.\n            : ( @ HttpResponse HttpRequest ) base\n            \\ HttpRequest req → HttpResponse { ^ ( router_handle r req ) }\n            : ( @ HttpResponse HttpRequest ) cors ( with_cors_default base )\n            : ( @ HttpResponse HttpRequest ) metered ( with_metrics m cors )\n            : ( @ HttpResponse HttpRequest ) logged ( with_access_log metered )\n\n            // Wire Ctrl+C / SIGTERM to a clean listener shutdown. Must come\n            // AFTER tcp_listen so the runtime sees a valid handle.\n            ( signal_install_shutdown listener )\n\n            ( nurl_print `static server listening on http://127.0.0.1:18080/\\n` )\n            ( nurl_print `  • /                  → public/index.html\\n` )\n            ( nurl_print `  • /<path>            → public/<path>\\n` )\n            ( nurl_print `  • /api/health        → JSON\\n` )\n            ( nurl_print `  • /metrics           → Prometheus text-exposition\\n` )\n            ( nurl_print `Ctrl+C to shut down cleanly.\\n` )\n\n            : HttpServer srv ( server_new listener logged )\n            : !v NetErr rr ( server_run srv )\n\n            // Cleanup. Order matters — clear the signal slot first so a\n            // late signal doesn't dereference a closed listener handle;\n            // the router and metrics release themselves.\n            ( signal_clear_shutdown )\n            ( server_stop srv )\n\n            ?? rr {\n                T _ → {\n                    ( nurl_print `static server: clean shutdown\\n` )\n                    ^ 0\n                }\n                F e → {\n                    ( nurl_eprint `[srv] runtime error: ` )\n                    ( nurl_eprint ( net_err_name e ) )\n                    ( nurl_eprint `\\n` )\n                    ^ 1\n                }\n            }\n        }\n        F e → {\n            ( nurl_eprint `[boot] could not bind 127.0.0.1:18080: ` )\n            ( nurl_eprint ( net_err_name e ) )\n            ( nurl_eprint `\\n` )\n            ^ 1\n        }\n    }\n}\n","bytes":7600}