diff --git a/.gitignore b/.gitignore index ed7672a..3a5472e 100644 --- a/.gitignore +++ b/.gitignore @@ -7,4 +7,5 @@ node_modules .psci .spago output +.direnv/ .psc-ide-port diff --git a/README.md b/README.md index 26760c4..34631ec 100644 --- a/README.md +++ b/README.md @@ -4,3 +4,57 @@ Purescript Lua bindings for the openresty/lua-nginx-module * [PureScript Compiler Backend for Lua](https://github.com/purescript-lua/purescript-lua) * [NGINX API for Lua](https://github.com/openresty/lua-nginx-module#nginx-api-for-lua) + +## Building + +```sh +nix develop -c ./scripts/build +``` + +Spago compiles the PureScript to CoreFn (a no-op `backend` keeps codegen on +corefn rather than JavaScript), then pslua links each module to a flat Lua file +under `dist/`, for example `dist/Lua.Ngx.lua`. + +## Running + +The bindings wrap the `ngx.*` API that the lua-nginx-module injects, so they +only work inside an OpenResty worker. Plain `lua` cannot run them (`ngx` is +nil) and neither can vanilla nginx (no Lua). The dev shell ships OpenResty, and +`scripts/run` serves the compiled modules so you can curl them: + +```sh +nix develop -c ./scripts/build # produce dist/*.lua +nix develop -c ./scripts/run # serve on http://127.0.0.1:8099 +``` + +Then, in another terminal: + +```sh +curl http://127.0.0.1:8099/say +# hello from purescript-lua-ngx + +curl -i http://127.0.0.1:8099/status +# HTTP/1.1 404 Not Found +# ... +# HTTP_OK constant = 200 +# ngx.status now = 404 +``` + +`PORT=9000 nix develop -c ./scripts/run` picks another port. + +### Using the bindings from your own config + +A linked module is a flat file whose name contains literal dots, such as +`Lua.Ngx.lua`, so load it with `dofile` by absolute path. `require("Lua.Ngx")` +would rewrite the dot to a directory separator and miss the file. Effectful +bindings are curried, so a `String -> Effect Unit` like `say` is called as +`say(msg)()`: + +```nginx +location /hello { + content_by_lua_block { + local Ngx = dofile("/path/to/dist/Lua.Ngx.lua") + Ngx.say("hello")() + } +} +``` diff --git a/flake.nix b/flake.nix index ca90632..9d18826 100644 --- a/flake.nix +++ b/flake.nix @@ -42,6 +42,7 @@ lua51Packages.luacheck luaformatter nixfmt-rfc-style + openresty # nginx + lua-nginx-module, for ./scripts/run pslua.packages.${system}.default purs-bin.purs-0_15_16 spago-bin.spago-1_0_4 diff --git a/scripts/run b/scripts/run new file mode 100755 index 0000000..cdf875e --- /dev/null +++ b/scripts/run @@ -0,0 +1,98 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Demo runner: serves the compiled Lua.Ngx bindings under OpenResty so you can +# curl them. The bindings wrap the `ngx.*` API the lua-nginx-module injects, so +# they only run inside an OpenResty worker (plain `lua` or vanilla nginx will +# not work). Build first with ./scripts/build, then: +# +# nix develop -c ./scripts/run # serve on :8099 +# PORT=9000 nix develop -c ./scripts/run # pick another port +# +# In another terminal: +# curl http://127.0.0.1:8099/say +# curl -i http://127.0.0.1:8099/status + +here="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +dist="$here/dist" +port="${PORT:-8099}" + +# PORT is interpolated into `listen $port;`, so reject anything that is not a +# plain TCP port number before it reaches the config. +if ! [[ $port =~ ^[0-9]+$ ]] || [ "$port" -lt 1 ] || [ "$port" -gt 65535 ]; then + echo "PORT must be an integer in 1..65535 (got: '$port')." >&2 + exit 1 +fi + +if ! command -v openresty >/dev/null 2>&1; then + echo "openresty is not on PATH." >&2 + echo "Run inside the dev shell: nix develop -c ./scripts/run" >&2 + echo "(With direnv, reload it after pulling: direnv reload)" >&2 + exit 1 +fi + +# Both modules are served below (/say and /status), and both come from the same +# ./scripts/build run, so fail fast if either is missing rather than only at the +# first request that needs it. +for f in Lua.Ngx.lua Lua.Ngx.Http.Status.lua; do + if [ ! -f "$dist/$f" ]; then + echo "dist/$f is missing. Run ./scripts/build first." >&2 + exit 1 + fi +done + +work="$(mktemp -d)" +trap 'rm -rf "$work"' EXIT +mkdir -p "$work/logs" + +# A linked dist module is a flat file named e.g. `Lua.Ngx.lua` (a literal dot in +# the filename, not a path), so consumers load it with dofile by absolute path +# rather than require, which would rewrite the dot to a directory separator. +cat > "$work/nginx.conf" < Effect Unit), so the generated value + # is curried: say(msg) returns a thunk that performs the write when called. + location /say { + content_by_lua_block { + local Ngx = dofile("$dist/Lua.Ngx.lua") + Ngx.say("hello from purescript-lua-ngx")() + } + } + + # Lua.Ngx.Http.Status exposes the HTTP_* constants plus get/set for + # ngx.status (set is likewise an effectful thunk). + location /status { + content_by_lua_block { + local Ngx = dofile("$dist/Lua.Ngx.lua") + local Status = dofile("$dist/Lua.Ngx.Http.Status.lua") + Status.set(Status.notFound)() + Ngx.say("HTTP_OK constant = " .. tostring(Status.ok))() + Ngx.say("ngx.status now = " .. tostring(Status.get()))() + } + } + } +} +EOF + +echo "Serving compiled Lua.Ngx on http://127.0.0.1:$port" +echo " curl http://127.0.0.1:$port/say" +echo " curl -i http://127.0.0.1:$port/status" +echo "Ctrl-C to stop." +# -e stderr sets the error log before the config is parsed, so nginx does not +# try (and noisily fail) to open its compiled-in default /var/log/nginx path. +openresty -p "$work" -e stderr -c "$work/nginx.conf"