skip to content
$registry / workers

http

v0.22.2

Expose functions as HTTP endpoints — registers the `http` trigger type and serves matched routes.

worker
iiiverified
5,750 installs1,217 in 7d145 today
install
$iii trigger compose::add worker=http@0.22.2
binarylicense: Apache-2.0apiendpointhttprest
  • macOS: arm64 · x64
  • Linux: arm64 · armv7 · x64
  • Windows: arm64 · x64

exact versions are immutable; binary and bundle artifacts are digest-pinned.

agent-ready brief for v0.22.2
install + config + dependencies + readme + skills + api reference, all in one place. fetch as agent-context.md for an llm to consume.
the same content rendered as discrete blocks below is exposed as a single markdown document at /workers/http.md?version=0.22.2. paste it into an llm prompt or pipe it through curl from a worker.

install

install
$iii trigger compose::add worker=http@0.22.2

configuration

iii-config.yaml
- concurrency_request_limit: 1024
  cors:
    allowed_methods:
      - GET
      - POST
      - PUT
      - DELETE
      - OPTIONS
    allowed_origins:
      - *
  default_timeout: 30000
  host: 127.0.0.1
  port: 3111
  webhook_listener: null

dependencies

dependencies1

readme

README.md

http

Exposes registered functions as HTTP endpoints. Any function bound to an http trigger becomes a route — external clients call it with a plain HTTP request instead of going through the iii SDK, which is what you want for webhooks, browser clients, or any caller outside the engine's WebSocket protocol. Routing, path params, per-route/global middleware, conditional execution, CORS, and chunked streaming responses are all handled by this worker; the function just receives an HttpRequest and returns a value.

Install

iii trigger compose::add worker=http

iii trigger compose::add resolves the worker and its dependencies, writes exact declarations to worker-compose.yaml, and reconciles the Compose project.

Quickstart

Register a function and bind it to this worker's trigger type (http — see Trigger type) with api_path and http_method:

use iii_sdk::protocol::RegisterTriggerInput;
use iii_sdk::{InitOptions, RegisterFunction, errors::Error, register_worker};
use iii_http::types::HttpRequest;
use serde_json::{json, Value};

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let iii = register_worker("ws://localhost:49134", InitOptions::default());

    iii.register_function(
        "orders::get",
        RegisterFunction::new_async(|req: HttpRequest| async move {
            Ok::<Value, Error>(json!({
                "status_code": 200,
                "body": { "order_id": req.path_params.get("id") },
            }))
        }),
    );

    iii.register_trigger(RegisterTriggerInput {
        trigger_type: "http".to_string(),
        function_id: "orders::get".into(),
        config: json!({ "api_path": "/orders/:id", "http_method": "GET" }),
        metadata: None,
    })?;

    tokio::signal::ctrl_c().await?;
    Ok(())
}
curl http://localhost:3111/orders/42
# {"order_id":"42"}

The function's return value becomes the response: status_code (default 200), headers, and body. Path segments prefixed with : (e.g. :id) land in req.path_params; query string and headers arrive as req.query_params / req.headers.

Configuration

Field Default Description
port 3111 TCP port the HTTP server binds to. 0 binds an OS-assigned ephemeral port.
host 0.0.0.0 Host/interface to bind.
webhook_listener null (disabled) Optional restricted listener; only explicitly opted-in routes.
webhook_listener.host 127.0.0.1 Restricted listener interface; does not inherit host.
webhook_listener.port 3112 Restricted listener port; separate from the normal/admin listener on 3111.
default_timeout 30000 (ms) Per-request timeout; on expiry the server returns 504.
cors.allowed_origins [] (permissive) Allowed CORS origins. An empty list allows any origin.
cors.allowed_methods [] (permissive) Allowed CORS methods. An empty list allows any method.
concurrency_request_limit 1024 Maximum in-flight requests; requests over the limit wait for a slot.
middleware[] [] Global middleware, each { function_id, phase, priority }, run on every route in ascending priority order before the handler.

Configuration is owned by the configuration worker — edit it from the console (Configuration → Workers → http) or seed it once via --config .yaml on first boot. middleware and default_timeout hot-reload without a restart, as do cors and concurrency_request_limit. Changing host/port or webhook_listener binds the new addresses before stopping the old listeners. All required binds must succeed; otherwise both old listeners and their live configuration remain unchanged. Set webhook_listener: null to disable it live. Shutdown drains both listeners and serializes with reload; a late reload cannot restart a stopped server.

Trigger type

This worker always registers the http trigger type. Bind a function to it with:

Field Required Default Description
api_path yes — Route path, e.g. /orders/:id. Segments prefixed with : are extracted into path_params.
http_method no GET HTTP method to match.
public_webhook no false Also expose this route on the restricted listener. Omitted/false routes are normal-listener only.
condition_function_id no — Function invoked first; if it returns a falsy value the request is rejected with 422.
middleware_function_ids no [] Per-route middleware, invoked before the handler in list order, in addition to any global middleware.

Functions can stream their response: write to req.response (a StreamChannelRef) with a ChannelWriter to send set_status / set_headers control frames and body chunks for a chunked HTTP response. Returning a non-null value instead yields a regular buffered response built from { status_code, headers, body }.

Restricted webhook listener

Enable the optional listener in the existing http configuration:

host: 127.0.0.1
port: 3111
webhook_listener:
  host: 127.0.0.1
  port: 3112

The internal http::webhook-listener::status function distinguishes saved configuration from a listener that this process actually opened. Its applied field contains the host from the applied configuration and the actual port from the bound socket (including an OS-assigned port when configured with 0), or null when the listener is off. The last_reload_error field reports the most recent failed reload and is cleared by the next successful one. The function waits for an in-progress reload before responding and exposes no other HTTP configuration.

Register an explicit opt-in using the same http trigger provider:

{"api_path":"/webhooks/github","http_method":"POST","public_webhook":true}

Only that opted-in route is eligible on 3112; it remains available on 3111. Do not expose 3111 through a public proxy/tunnel. The restricted listener mounts no Console, engine, or admin endpoints. Its private paths return 404 (including OPTIONS); methods not opted in for an eligible path return 405 with only public methods in Allow. Admission runs before CORS. It performs no URL decoding, repeated/trailing-slash normalization, HEAD-to-GET aliasing, or method-override handling. Parameter routes are still supported: opting one in deliberately exposes every value matched by its parameter segments, never a more specific private handler. Existing normal-listener behavior is unchanged. Route removal or opt-in revocation applies to subsequent requests.

Both listeners share the route registry, configuration and layered router; global/per-route middleware, conditions, timeouts, concurrency and the 16 MiB body limit still apply. A bind collision, including using the normal address for the restricted listener, fails safely rather than combining them.

Signature verification: read HttpRequest.request_body using the SDK ChannelReader; it contains the exact received bytes (UTF-8, JSON whitespace, newlines and binary data included). Never compute HMAC from body, which is only a parsed JSON convenience projection. Header names follow HTTP's case-insensitive/lowercase representation; ASCII values such as x-hub-signature-256 and x-github-delivery pass through unchanged. The existing header map is not a raw HTTP-header archive: duplicate headers and non-text values are not represented byte-for-byte. This listener does not validate signatures itself; the opted-in handler must authenticate deliveries.

Local tests

tests/webhook_listener.rs uses the shared harness's loopback fake engine and real SDK binary channels, without any external service or skipped tests:

CARGO_TARGET_DIR=target-http-local cargo test --locked --test webhook_listener
cargo fmt --all -- --check
CARGO_TARGET_DIR=target-http-local cargo clippy --all-targets --all-features --locked -- -D warnings

Other e2e_* suites retain their existing connect-or-skip engine requirement.

Requires removing the legacy built-in HTTP service

The legacy built-in HTTP service also owns the http trigger type. Two owners of the same trigger type on one engine collide — whichever registers last wins — so this worker requires it to be absent: omit it from the engine's config.yaml (a config that doesn't list a worker won't run it).

On boot, this worker queries the engine for connected workers and refuses to start with a clear error if the legacy built-in is still active, so a stale config fails loudly instead of silently racing the built-in worker for ownership of http.

skills

api reference (json)

agent-api-reference.json
{
  "functions": [
    {
      "description": "Returns this HTTP instance's configuration entry ID, without its value.",
      "metadata": {
        "internal": true
      },
      "name": "http::configuration-id",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "title": "ConfigurationIdentityRequest",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "id": {
            "type": "string"
          }
        },
        "required": [
          "id"
        ],
        "title": "ConfigurationIdentityResponse",
        "type": "object"
      }
    }
  ],
  "triggers": [
    {
      "description": "HTTP API trigger",
      "invocation_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "description": "Configuration carried by an `http` trigger instance, as read from the trigger's `config` value.",
        "properties": {
          "api_path": {
            "type": "string"
          },
          "condition_function_id": {
            "default": null,
            "type": [
              "string",
              "null"
            ]
          },
          "http_method": {
            "default": "GET",
            "type": "string"
          },
          "middleware_function_ids": {
            "default": [],
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "public_webhook": {
            "default": false,
            "description": "Also expose this route on the restricted webhook listener. Explicit opt-in only; the default keeps existing routes on the normal listener.",
            "type": "boolean"
          }
        },
        "required": [
          "api_path"
        ],
        "title": "HttpTriggerConfig",
        "type": "object"
      },
      "metadata": {},
      "name": "http",
      "return_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "ChannelDirection": {
            "enum": [
              "read",
              "write"
            ],
            "type": "string"
          },
          "StreamChannelRef": {
            "properties": {
              "access_key": {
                "type": "string"
              },
              "channel_id": {
                "type": "string"
              },
              "direction": {
                "$ref": "#/definitions/ChannelDirection"
              }
            },
            "required": [
              "access_key",
              "channel_id",
              "direction"
            ],
            "type": "object"
          },
          "TriggerMetadata": {
            "description": "Metadata describing the trigger that produced an `HttpRequest`.",
            "properties": {
              "method": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "path": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "type": {
                "type": "string"
              }
            },
            "required": [
              "type"
            ],
            "type": "object"
          }
        },
        "description": "Request payload sent to the invoked function over the wire.",
        "properties": {
          "body": true,
          "headers": {
            "additionalProperties": {
              "type": "string"
            },
            "type": "object"
          },
          "method": {
            "type": "string"
          },
          "path": {
            "type": "string"
          },
          "path_params": {
            "additionalProperties": {
              "type": "string"
            },
            "type": "object"
          },
          "query_params": {
            "additionalProperties": {
              "type": "string"
            },
            "type": "object"
          },
          "request_body": {
            "$ref": "#/definitions/StreamChannelRef"
          },
          "response": {
            "$ref": "#/definitions/StreamChannelRef"
          },
          "trigger": {
            "anyOf": [
              {
                "$ref": "#/definitions/TriggerMetadata"
              },
              {
                "type": "null"
              }
            ]
          }
        },
        "required": [
          "body",
          "headers",
          "method",
          "path",
          "path_params",
          "query_params",
          "request_body",
          "response"
        ],
        "title": "HttpRequest",
        "type": "object"
      }
    }
  ]
}