A minimal, synchronous ZooKeeper client for OpenResty / plain Lua with robust CONNECT handshake parsing and basic operations.
This repository provides:
lua/resty/zookeeper.lua— core client implementation (handshake, signed error handling, create/get_children, exists, get_data, add_auth, close).test/test_zk.lua— extended test script (connect, exists, get_data, create, get_children, delete, watch).
Notable updates
- Robust CONNECT handshake parsing with heuristics to handle different server response layouts.
- Converts server 32-bit error codes to signed int32 for correct interpretation (e.g. ZNONODE = -101, ZNODEEXISTS = -110).
- Added API:
client:create(path, data, mode, sequential)— create nodes (supports persistent / ephemeral and sequential flags).client:get_children(path)— list children (returns a Lua table).client:delete(path, version)— delete a node (supports specifying version; default -1 means any version).client:watch(path, kind)— register a watcher and block until the next watcher event for this session (synchronous/blocking).
-
Optional dependency for JSON support:
Using luarocks:
luarocks install lua-cjson -
Ensure the project
lua/directory is onpackage.path. -
Run the test script:
-
Linux / macOS (bash):
export LUA_PATH="./lua/?.lua;./lua/?/init.lua;;" lua test/test_zk.lua 127.0.0.1:2181 /your_parent_path -
Windows (PowerShell):
$env:LUA_PATH = ".\lua\?.lua;.\lua\?\init.lua;;" lua test\test_zk.lua 127.0.0.1:2181 \your_parent_path
-
-
Add the
lua/directory tolua_package_pathinnginx.conf:lua_package_path "/path/to/project/lua/?.lua;/path/to/project/lua/?/init.lua;;";
-
Use in OpenResty Lua code:
local zk = require("resty.zookeeper") local client, err = zk.new{ connect_string = "127.0.0.1:2181", timeout = 3000, session_timeout = 30000, debug = false, } local ok, err = client:connect()
- Create client
local zk = require("zk_connection") -- or require("resty.zookeeper") or require("zookeeper")
local client, err = zk.new{
connect_string = "127.0.0.1:2181",
timeout = 5000, -- ms for ngx, best-effort otherwise
session_timeout = 30000,
debug = true, -- print handshake payload hex when true
}- Connect
local ok, err = client:connect()
if not ok then error(err) end- Basic operations
-- exists
local exists, err = client:exists("/myapp")
-- get_data
local data, err = client:get_data("/myapp")
-- add auth (instance method)
local ok, err = client:add_auth("digest", "user:pass")- create
-- create(path, data, mode, sequential)
-- mode: "persistent" (default) or "ephemeral"
-- sequential: boolean; if true the server appends a monotonically increasing sequence number
local created_path, err = client:create("/myapp/node", "payload", "persistent", true)- get_children
local children, err = client:get_children("/myapp")
-- returns a Lua table, e.g. { "node0000000001", "node0000000002", ... }- delete
-- delete(path, version)
-- version: signed int32; default -1 means match any version
local ok, err = client:delete("/myapp/node", -1)
if not ok then
print("delete failed:", err)
end- watch
-- watch(path, kind)
-- kind: "exists" (default), "get_data", or "get_children"
-- Blocks until a watcher event is delivered for this session.
local evt, err = client:watch("/myapp", "get_children")
if not evt then
print("watch failed:", err)
else
-- evt = { type = <int>, state = <int>, path = "<string>" }
print("watch event:", evt.type, evt.state, evt.path)
end- Server error codes are 32-bit signed integers. This implementation converts received error codes to signed int32 values and interprets common constants:
- -101 (ZNONODE) : node does not exist
- -110 (ZNODEEXISTS) : node already exists
- -103 (ZBADVERSION) : version conflict on update/delete
- Client methods return friendly errors for these conditions (for example,
existsreturnsfalse, nilwhen the node does not exist).
-
The test script
test/test_zk.luademonstrates:- connecting to a server
- checking for and creating a parent node if necessary
- creating a sequential child node
- listing children via
get_children - deleting a created node
- registering a watch and waiting for an event
-
Run the test with debug enabled to print handshake payload hex:
export LUA_PATH="./lua/?.lua;./lua/?/init.lua;;" lua test/test_zk.lua 127.0.0.1:2181 /daaa
- Watchers in ZooKeeper are session-scoped and the server sends watcher events to the session that registered them.
- This library implements
watchas a synchronous blocking call that waits for the next watcher event delivered to the session. Because it blocks, use a separate connection or coroutine for watchers if you need to perform other requests concurrently. - For reliable testing of watchers, use two clients/processes:
- Client A registers the watch (calls
client:watch(...)). - Client B performs an operation (create/delete/set) that triggers the watch.
- Client A should then receive the watcher event.
- Client A registers the watch (calls
deleteencodes the provided version as a 32-bit integer.version = -1is encoded as0xFFFFFFFF, matching ZooKeeper's signed int32 representation for "any version".watchsends the corresponding request (exists/get_data/get_children) with the watch flag set to 1, then reads packets until a watcher event (xid == -1, unsigned 4294967295) arrives. Non-watcher packets received while waiting are ignored.- This client is synchronous and designed for single-request-at-a-time usage per connection. If you require concurrent requests and watcher/event dispatching on a single connection, consider adding:
- an internal packet dispatcher that reads incoming packets continuously,
- routing responses by xid to waiting coroutines,
- delivering watcher events to registered callbacks.
-
Current limitations:
- synchronous (blocking) I/O model,
- single-request-per-connection assumption,
- simple watcher support (blocking wait for a single event),
- limited operation coverage and stat parsing,
- no automatic reconnection/session recovery logic,
- limited tests and no CI.
-
Potential enhancements:
- add an event-driven dispatcher and non-blocking watcher callbacks,
- implement reconnection and session recovery,
- expand the supported ZooKeeper protocol operations (ACLs, multi, get_acl, set_acl, stat parsing),
- add unit tests and CI workflow,
- publish as a LuaRock.
- BSD