Lua network utilities for DAW projects -- IP/CIDR matching, HTTP client, URL parsing
  • Lua 99.3%
  • Makefile 0.7%
Find a file
2026-10-17 11:01:44 +02:00
.github feat(errors): migrate to daw_error/v1 error contract 2026-08-10 13:19:22 +02:00
docs add release notes 2.2.0 2026-10-17 11:01:44 +02:00
i18n fix(i18n): add the schema field required by daw_i18n/v1 2026-10-17 08:21:49 +02:00
test fix(net): fall back to sentinel when error.new() returns nothing 2026-09-21 08:03:39 +02:00
.fragjan chore(config): add .fragjan project config 2026-05-22 17:56:05 +02:00
.gitignore chore: fragjan error resolves 2026-06-01 16:07:33 +02:00
.merkwerk chore(config): add .merkwerk project config 2026-05-22 17:57:00 +02:00
.version_history chore: merkwerk auto-update 2026-10-17 08:22:48 +02:00
http_client.lua fix(net): fall back to sentinel when error.new() returns nothing 2026-09-21 08:03:39 +02:00
init.lua fix(net): fall back to sentinel when error.new() returns nothing 2026-09-21 08:03:39 +02:00
ip_utils.lua fix(net): fall back to sentinel when error.new() returns nothing 2026-09-21 08:03:39 +02:00
LICENSE feat(http_client)!: require explicit opts table with mandatory timeout 2026-07-25 17:56:56 +02:00
Makefile chore(make): remove obsolete install/uninstall targets 2026-09-20 20:10:52 +02:00
README.md fix(net): fall back to sentinel when error.new() returns nothing 2026-09-21 08:03:39 +02:00
VERSION chore: bump version to 2.2.0 2026-10-17 08:22:48 +02:00

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/v1 error 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.