- Lua 99.3%
- Makefile 0.7%
| .github | ||
| docs | ||
| i18n | ||
| test | ||
| .fragjan | ||
| .gitignore | ||
| .merkwerk | ||
| .version_history | ||
| http_client.lua | ||
| init.lua | ||
| ip_utils.lua | ||
| LICENSE | ||
| Makefile | ||
| README.md | ||
| VERSION | ||
daw-lua-net
Network utilities for Dragons@Work projects.
daw-lua-net provides small, dependency-conscious building blocks for outgoing HTTP and IP/CIDR handling in Lua 5.1 and Lua 5.4 applications.
Status: Stable · Lua: 5.1 / 5.4 · License: ISC
❤️ Contribute and support our work
What is daw-lua-net?
daw-lua-net is a small, focused library for two recurring needs in DAW
programs that talk to the network: making outgoing HTTP requests
(http_client) and reasoning about IP addresses and CIDR ranges
(ip_utils, e.g. for trusted-proxy and access-list checks). It is not
an installable module -- no module.conf, no .stpkg distribution --
but a core submodule, cloned directly into consuming projects
alongside daw-lua-common.
What problem does it solve?
Every DAW program that talks HTTP or needs to reason about client IPs (behind a proxy or not) ends up rebuilding the same small pieces -- JSON request/response handling, timeout enforcement, safe CIDR matching, spoofing-resistant client IP resolution. daw-lua-net bundles these once, with the same error contract and architectural rules as the rest of the DAW Lua ecosystem, so no consuming project has to get proxy-header trust or CIDR edge cases right on its own.
Status
- Stable. Lua 5.1 and 5.4 are supported equally and tested against each other continuously.
- Fully migrated to the
daw_error/v1error contract (see below, including the bootstrap sentinel) -- no module boundary in this library returns a plain error string. - Currently 3 test files, 67 tests,
fragjan-clean. - Known limitation: IPv4 only -- IPv6 is explicitly not supported (see
ip_matches_cidr,get_client_ip: both return an error rather than silently mishandling non-IPv4 values).
Error contract
Every public function in daw-lua-net returns nil, err on failure --
never an uncaught Lua error. err is not a string, but a structured
error object following the daw_error/v1 contract: schema, id,
source, code, message, optionally cause (a cause chain) and
data (structured additional information).
Bootstrap case: if daw.common.error itself is unreachable
(e.g. daw-lua-common missing or badly broken), every module returns a
minimal literal daw_error/v1 object with the contract-reserved id
daw-error-facility-unavailable (DAW/daw-contracts#11) -- the same
sentinel daw-lua-common itself uses. It is still a daw_error/v1
object, never a plain string. Under any normal setup this path is never
taken; daw.common.error is a required, always-present dependency.
local http = require("daw.net.http_client")
local result, err = http.get("https://api.example.com/things")
if not result then
print(err.code) -- e.g. "invalid_opts"
print(err.message) -- human-readable description
end
The full contract (field meaning, invariants, JSON representation) lives in the contracts repository: daw-contracts: daw_error/v1
Installation
Clone as git submodule into lib/daw/net/ (gitignored), alongside
daw-lua-common in lib/daw/common/:
git submodule add \
ssh://forgejo@smida.dragons-at-work.de:4022/DAW/daw-lua-net.git \
lib/daw/net
Usage
local net = require("daw.net")
local ip_utils = require("daw.net.ip_utils")
local http = require("daw.net.http_client")
local matches, err = ip_utils.ip_matches_cidr("192.168.1.5", "192.168.1.0/24")
-- fail-closed: aborts with err on the first malformed entry, rather
-- than silently skipping it.
local allowed, err = ip_utils.is_ip_allowed(client_ip, config.allowed_ips)
-- request.remote_addr is required (the real TCP peer address, not
-- spoofable). Forwarding headers (x-forwarded-for, x-real-ip) are only
-- honored when trusted_proxies is given and remote_addr matches it --
-- otherwise headers are ignored and remote_addr is returned directly.
-- IPv4 only.
local client_ip, err = ip_utils.get_client_ip(request, trusted_proxies)
-- opts.timeout is required -- no default (module takes everything it
-- needs from the caller, nothing is assumed or remembered).
local result, err = http.get("https://api.example.com/things", { timeout = 5 })
local result, err = http.post("https://api.example.com/things", { name = "x" }, { timeout = 5 })
-- result: { body, status, headers }, nil | nil, err
-- get_raw -- never JSON-decodes, result.body is always the raw response
-- string. Use for binary responses (PDFs, file downloads).
local result, err = http.get_raw("https://example.com/file.pdf", { timeout = 5 })
Modules
http_client.lua
Outgoing HTTP client (GET/POST/PUT, JSON body handling). Uses
luasocket (socket.http + ltn12) -- no additional dependencies.
http.get(url, opts) -- { body, status, headers }, nil | nil, daw_error/v1
http.post(url, body, opts) -- { body, status, headers }, nil | nil, daw_error/v1
http.put(url, body, opts) -- { body, status, headers }, nil | nil, daw_error/v1
http.get_raw(url, opts) -- { body, status, headers }, nil | nil, daw_error/v1
opts.timeout (seconds) is required, no default -- the module is
"dumb": everything it needs comes from the caller explicitly, nothing
is assumed or remembered. get_raw() never JSON-decodes; result.body
is always the raw response string, for binary endpoints (PDFs, file
downloads).
Requires:
# OpenBSD: pkg_add lua54-socket
# Debian: apt install lua-socket
# Arch: luarocks --lua-version 5.4 install luasocket
ip_utils.lua
IP address and CIDR utilities. IPv4 only.
ip_utils.ip_matches_cidr(ip, cidr) -- bool | nil, daw_error/v1
ip_utils.is_ip_allowed(client_ip, allowed_ips) -- bool | nil, daw_error/v1
ip_utils.get_client_ip(request, trusted_proxies) -- string | nil, daw_error/v1
is_ip_allowed fails closed: the first malformed entry in
allowed_ips aborts the whole check with an error, rather than being
silently skipped, even if a later entry would have matched.
get_client_ip requires request.remote_addr (the real, non-spoofable
TCP peer address). Forwarding headers (x-forwarded-for, x-real-ip)
are only honored when trusted_proxies is given and
request.remote_addr itself matches an entry in it; otherwise headers
are ignored entirely and remote_addr is returned directly.
DAW integration
When running inside the Dragons@Work runtime, daw.net and its
submodules automatically register themselves via the optional global
hook _G.daw_register(name, module) -- part of the Biocodie
self-registration pattern (modules announce themselves rather than
being wired up centrally). Outside DAW this hook is simply absent
(if _G.daw_register then ...), so it's a no-op and can be ignored.
Development and Testing
make test
To pin a Lua version, create config/local.mk (gitignored):
DAW_LUA_VERSION = 5.1
Documentation
Contributing
Bug reports, tests, feedback, and contributions are welcome.
The primary development repository is on Forgejo: daw-lua-net
The GitHub repository is a public mirror for better discoverability. Bug reports, questions, suggestions, and other feedback are welcome through GitHub Issues and are handled there. There is no need to report them again in the primary Forgejo repository.
Development planning and project coordination stay in the primary Forgejo repository. Contributions and pull requests are still submitted there.
Open Knowledge Needs People
We develop ideas, share knowledge, and build open source software and tools. Projects like this one don't grow on their own -- they live because people ask questions, experiment, share knowledge, find bugs, and help make things better.
You can contribute in different ways:
- reporting bugs
- reviewing code
- testing on other platforms
- improving documentation
- translating content
- or, if you like, supporting us financially
Every contribution helps us keep developing open knowledge and open source software.
Contribute and support our work →
License
Released under the ISC License as part of the Dragons@Work open source ecosystem. See LICENSE.