Skip to content

Commit 2bb396c

Browse files
hyperpolymathclaude
andcommitted
feat(zig-ffi): HTTP server primitives for OikosBot webhook receiver
Adds a synchronous, single-threaded HTTP/1.1 server FFI surface for sitting behind a TLS reverse proxy (Caddy / nginx). Intended use: the OikosBot links libgateway.so alongside libhpm_crypto.so and composes them — no router, no TLS, no concurrency primitives here. Why this repo: the user-elected home for HTTP server primitives, matching the repo's HTTP-domain identity (verb governance is the Elixir layer; this is the protocol-primitive layer alongside the existing gRPC / GraphQL parsers). New Zig exports (libgateway.so): - hpm_http_server_listen / _port / _free / _accept - hpm_http_request_method / _path / _header / _body / _respond / _free Implementation notes: - Heap-allocated RequestCtx holds the connection + IO buffers + the std.http.Server + parsed std.http.Server.Request, so inner Reader / Writer pointer addresses stay stable for the lifetime of the request. - Headers parsed via std.http.Server's HeaderIterator with case-insensitive name match. - Method enum ordinal matches std.http.Method directly (GET=0..PATCH=8). - Body capped at 1 MiB; content-length-driven read; idempotent. - Connection always closes after respond (keep_alive=false) — keeps the surface narrow for v1. Idris2 ABI: - New src/abi/HttpServer.idr with %foreign declarations and safe wrappers mirroring the Zig exports. gateway.ipkg updated. Tests: - 15/15 main module tests pass (zig test src/main.zig -lc): GET / POST + body / header lookup / header-absent / path size-query / extra response headers / arbitrary status / method ordinals / null req safety / listen failure paths / port introspection. Pre-existing build hygiene fixes (latent under Zig 0.15.2): - Handle: opaque → struct (Zig 0.15 forbids fields on opaque {}). - Callback callconv: .C → .c (rename in Zig 0.15). Unrelated red test: ffi/zig/grpc/parser.zig::"parse gRPC frame header" fails because the test fixture isn't a valid HTTP/2 HEADERS frame. Pre-existing on main; not addressed here. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 385ccf7 commit 2bb396c

5 files changed

Lines changed: 755 additions & 8 deletions

File tree

ffi/zig/README.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,31 @@ zig build test
2929

3030
All exported functions use C calling convention for Idris2 FFI compatibility:
3131

32+
### HTTP server (`hpm_http_*`)
33+
34+
Synchronous, single-threaded HTTP/1.1 server primitives intended to sit
35+
behind a TLS reverse proxy. Used by the OikosBot for webhook reception.
36+
37+
- `hpm_http_server_listen(host, host_len, port) -> server*` — bind TCP
38+
- `hpm_http_server_port(server) -> port` — query bound port (e.g. when
39+
`port=0` was passed)
40+
- `hpm_http_server_accept(server) -> request*` — block until next request,
41+
return parsed head
42+
- `hpm_http_request_method(request) -> method_ordinal` — matches
43+
`std.http.Method` (GET=0 HEAD=1 POST=2 PUT=3 DELETE=4 CONNECT=5
44+
OPTIONS=6 TRACE=7 PATCH=8)
45+
- `hpm_http_request_path(request, out, cap) -> bytes` — copy URI target
46+
- `hpm_http_request_header(request, name, name_len, out, cap) -> bytes`
47+
case-insensitive lookup; returns 0 if absent
48+
- `hpm_http_request_body(request, out, cap) -> bytes` — read body (max 1
49+
MiB, content-length-driven); idempotent
50+
- `hpm_http_request_respond(request, status, headers, headers_len, body,
51+
body_len) -> 0/-1` — send full response; connection closes after
52+
- `hpm_http_request_free(request)` — close + free
53+
- `hpm_http_server_free(server)` — close listener + free
54+
55+
Idris2 wrappers live in `../../src/abi/HttpServer.idr`.
56+
3257
### gRPC
3358
- `parse_grpc_request` - Parse HTTP/2 gRPC frame
3459
- Validates frame headers and extracts service/method

ffi/zig/build.zig

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,14 @@ pub fn build(b: *std.Build) void {
4545
const grpc_tests = b.addTest(.{
4646
.root_module = grpc_module,
4747
});
48+
const run_grpc_tests = b.addRunArtifact(grpc_tests);
49+
50+
const main_tests = b.addTest(.{
51+
.root_module = lib_module,
52+
});
53+
const run_main_tests = b.addRunArtifact(main_tests);
4854

49-
const run_tests = b.addRunArtifact(grpc_tests);
5055
const test_step = b.step("test", "Run all tests");
51-
test_step.dependOn(&run_tests.step);
56+
test_step.dependOn(&run_grpc_tests.step);
57+
test_step.dependOn(&run_main_tests.step);
5258
}

0 commit comments

Comments
 (0)