From 3601f923be9012a50a5d53f2a3ae5cfcde58ca7b Mon Sep 17 00:00:00 2001 From: unwox Date: Fri, 27 Dec 2024 15:45:34 +0600 Subject: improve README.md --- README.md | 268 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 267 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index afe2fd2..5a40fa9 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,271 @@ +# Luna + +Luna is a simple web server programmable via opinionated Lua API. It has the +following key features: + +* support for both PUC Lua and luajit +* sqlite3 for data storage +* asynchronous I/O operations +* small and maintainable codebase +* handles relatively high number of requests per second + + +## Build + +You'll need `lua` or `luajit` libraries installed on your system: + +```sh +git clone https://git.sr.ht/~unwox/luna +cd luna +go build -tags=puc . # for lua +go build -tags=jit . # for luajit +``` + +To install the server to the $GOBIN ($GOPATH/bin by default) directory: + +```sh +go install -tags=puc . # for lua +go install -tags=jit . # for luajit +``` + + +## Usage + +``` +luna [options] LUAFILE + -D Debug/development mode + -l string + Address HTTP-server will listen to (default "127.0.0.1:3000") + -n int + Number of HTTP-workers to start (default $(nproc)) +``` + +LUAFILE must specify a Lua file that may or may not register routes to handle +(via calling `luna.route.static` or `luna.route.route`). Whatever the LUAFILE +returns is ignored. If LUAFILE defines no routes luna will execute the file and +then exit. This behavior is useful when you want to have a script file with an +access to the luna API. + +If luna is started with -D flag it will accept user input into its stdio. The +input is executed in Lua state as is or (if `luna.evalfn` was called in +LUAFILE) is passed to a custom eval handler. + +## Lua API + +All API functions return 2 values: the first one is a boolean indicating if the +call was successful (true for successful and false if there was an error) and +the second one is the value (or an error text if the call was unsuccessful). + +API names are hyphenated since I am mostly calling the functions from +[fennel language](https://fennel-lang.org/) and for lisps hyphenation is the +norm. + +Every argument in every API function is required: luna does not support optinal +arguments yet. Pass an empty value for corresponding type instead if you do not +want to specify an argument: {} for tables, "" for strings, 0 for numbers. + + +__luna.router.route(pattern, handler)__: +registers a new route for Go to proxy to Lua. For pattern specification see +[Go documentation](https://pkg.go.dev/net/http#hdr-Patterns-ServeMux). Handler +is a Lua function that accepts a table representing request and returns 3 +values: response status code, headers and body. + +```lua +luna.router.route("GET /test", function (request) + return 200, {["Content-Type"] = "application/json"}, "{\"foo\": \"world\"}" +end) +``` + + +__luna.router.static(pattern, directory)__: +registers a new route for Go to serve static files. + +```lua +luna.router.static("GET /static/", "./whatever/path/static") +``` + + +__luna.http.request(method, url, headers, body)__: +sends an HTTP request returning a response object: +`{status = ..., headers = ..., body = ...}`. + +```lua +luna.http.request("GET", "https://git.sr.ht", {Accept = "text/html"}, "") +``` + + +__luna.http\["encode-url"\](string)__: +encodes a STRING for a safe usage in URLs. + + +__luna.db.open(file)__: +opens a connection to an sqlite database. +For FILE specification see +[mattn/go-sqlite3 library documentation](https://github.com/mattn/go-sqlite3?tab=readme-ov-file#connection-string). + +```lua +local ok, db = luna.db.open("file:var/db.sqlite?_journal=WAL&_sync=NORMAL") +``` + + +__luna.db.begin(db)__: +starts a transaction for a DB. + +```lua +local ok, tx = luna.db.begin(db) +``` + + +__luna.db.commit(tx)__: +commits a transaction TX. + +```lua +local ok, tx = luna.db.begin(db) +local ok, err = luna.db["exec-tx"](tx, "DELETE FROM foobar;") +local ok, err = luna.db.commit(tx) +``` + + +__luna.db.rollback(tx)__: +rolls back a transaction TX. + +```lua +local ok, tx = luna.db.begin(db) +local ok, err = luna.db["exec-tx"](tx, "DELETE FROM foobar;", {}) +local ok, err = luna.db.rollback(tx) +-- the tx was rolled back so "DELETE FROM foobar;" isn't executed +``` + + +__luna.db\["exec-tx"\](tx, query, args)__: +executes a QUERY with ARGS in the context of a given transaction TX. + +```lua +local ok, tx = luna.db.begin(db) +local ok, err = luna.db["exec-tx"]( + tx, + "INSERT INTO foobar VALUES (?, ?, ?);" + {1, "hello!", "2024-12-26 12:00:00"} +) +``` + + +__luna.db.exec(db, query, args)__: +executes a QUERY with ARGS in a given DB: + +```lua +local ok, db = luna.db.open("file:var/db.sqlite?_journal=WAL&_sync=NORMAL") +local ok, err = luna.db.exec( + db, + "INSERT INTO foobar VALUES (?, ?, ?);" + {1, "hello!", "2024-12-26 12:00:00"} +) +``` + + +__luna.db.query(db, query, args)__: +executes a QUERY with ARGS in a given DB and returns result as an array of +arrays: + +```lua +local ok, db = luna.db.open("file:var/db.sqlite?_journal=WAL&_sync=NORMAL") +local ok, res = luna.db.query( + db, + "SELECT * FROM foobar WHERE name = '?'", + {"hello!"} +) +-- if the table foobar has 3 columns (id, name, creation_time) the RES variable +-- would contain something like this: +-- {{1, "hello!", "2024-12-26 12:00:00"}} +``` + + +__luna.db.\["query\*"\](db, query, args)__: +executes a QUERY with ARGS in a given DB and returns result as an array of +tables where keys are column names and values are associated value: + +```lua +local ok, db = luna.db.open("file:var/db.sqlite?_journal=WAL&_sync=NORMAL") +local ok, res = luna.db["query*"]( + db, + "SELECT * FROM foobar WHERE name = '?'", + {"hello!"} +) +-- if the table foobar has 3 columns (id, name, creation_time) the RES variable +-- would contain something like this: +-- {{id = 1, name = "hello!", creation_time = "2024-12-26 12:00:00"}} +``` + + +__luna.db.close(db)__: +closes a connection to DB. + +```lua +local ok, db = luna.db.open("file:var/db.sqlite?_journal=WAL&_sync=NORMAL") +local ok, err = luna.db.close(db) +``` + + +__luna.utf8.len(string)__: +returns a number of UTF-8 symbols in a STRING. + + +__luna.utf8.lower(string)__: +returns a copy of a STRING with all letters mapped to their lower case. + + +__luna.utf8.upper(string)__: +returns a copy of a STRING with all letters mapped to their upper case. + + +__luna.utf8.sub(string, start, length)__: +returns a substring of a STRING starting at index START (1-indexed position) +with a LENGTH. + + +__luna.crypto.sha1(string)__: +returns hex-encoded SHA1 hash of a STRING. + + +__luna.evalfn(handler)__: +sets an eval handler for server commands. Handler accepts one argument TEXT. If +the server is started with -D flag it starts listening for input from stdio. In +this case if eval handler is set the handler will receive the user input and +should handle it appropriately. If eval handler is not set REPL input is +executed in Lua state as is. + +``` +local fennel = require("fennel") +luna.evalfn(function (text) fennel.eval(text, {env = _G}) end) +``` + + +__luna.debug__: +a variable that indicates whether the server was started with -D (debug) flag. + + +## Contribution + +Send patches to me@unwox.com. Possible ideas for patches are + +* adding more *useful* API functions. It's easy to do, see `main.go` for that. +* replacing sqlite with something else since its sqlite3_step function is + blocking and it's the major perfomance bottleneck right now. +* supporting redis for synchronization, maybe? +* supporting websocket connections, maybe? +* compiling Lua code and luna into one binary, maybe? + + +I will not accept patches that are + +* relying on too many Go (or external) dependencies. +* adding too much complexity into the existing codebase. +* adding a new major functionality that should be implemented outside + (for example service management, use systemd, shepher or whatever). + + ## Useful links * https://lucasklassmann.com/blog/2019-02-02-embedding-lua-in-c/ * https://pgl.yoyo.org/luai/i/3.7+Functions+and+Types - -- cgit v1.2.3