Scripting
Octopus embeds Rhai, a small Rust-native scripting language, in the
octopus-scripting crate. Scripts can run from inline source or from a file, with compiled scripts
cached as ASTs for fast repeat execution.
How scripting attaches today. Two paths run your scripts on live traffic: (1) a script plugin
in config.plugins with plugin_type: script, which adds a ScriptMiddleware to
the request chain (see below); and (2) convention
host-resolution (subdomain → backend mapping). Lua/JavaScript/WebAssembly fall back to Rhai today.
The wired path: convention host-resolution01
Octopus's subdomain routing can derive the backend for a request from its Host header using a
"convention". A convention may carry an inline Rhai script that maps the host to a target. This
is evaluated per host (cached) by the request handler — the one production scripting integration.
The script is given the request host as a string variable named host, and must return a Rhai map
#{ namespace: "...", service: "...", port: 1234 } to map the host to a backend, or unit () to
decline (the handler then falls back to label-based resolution). port is optional; when
omitted the convention's default port is used.
// `host` is provided by the gateway, e.g. "orders.acme.platform.com".
// Return a backend mapping, or `()` to decline and use label parsing.
if host == "special.example.com" {
#{ namespace: "vip", service: "web" } // port omitted → convention default
} else {
let parts = host.split(".");
#{ namespace: parts[1], service: parts[0], port: 8080 }
}Behaviour:
Returning a map with both
namespaceandserviceproduces the backend target.Returning a map missing either field, or returning any non-map value, is treated as decline.
If the script errors, the handler logs a warning and falls back to the convention's label layout.
Each distinct script body gets its own AST cache slot (keyed by a content hash), so the script
compiles once and is reused across requests. A single process-wide RhaiEngine backs this path so
the AST cache persists for the lifetime of the gateway.
The convention (and its script field) is part of the router's convention model used by the
Kubernetes-first subdomain routing. It is configured programmatically / via the operator rather than
through the main gateway YAML's routes: section. See
Concepts: Routing and the Kubernetes docs.
Script plugins (plugin_type: script)02
A plugin entry in config.plugins with plugin_type: script adds a ScriptMiddleware (from
octopus-scripting, implementing octopus_core::Middleware) to the request chain. It runs a script
on the request (before routing) and/or on the response (after the upstream), applies the script's
mutations back to the HTTP request/response, and short-circuits when the request script returns
false. Enabled script plugins are ordered by descending priority.
The plugin's config is deserialized into ScriptMiddlewareConfig:
| Field | Type | Default | Description |
language | enum | rhai | Script language (see Languages). |
code / path | string | — | Inline code or a file path (flattened ScriptSource). |
on_request | bool | true | Run the script on the request. |
on_response | bool | false | Run the script on the response. |
continue_on_error | bool | false | If true, script errors are logged and the request continues. |
timeout_ms | u64 | 100 | Per-execution timeout in milliseconds. |
Configure one as a script plugin:
plugins:
- name: add-headers
plugin_type: script
enabled: true
priority: 50
config:
language: rhai
on_request: true
code: |
headers["X-Gateway"] = "Octopus";
trueWhen the request script returns false, ScriptMiddleware short-circuits and returns an empty
200 OK response. On a script error, it returns a 500 unless continue_on_error is set.
The context API exposed to scripts03
When a script runs against a request or response, the engine pushes the HTTP fields into the Rhai scope as variables. Scripts read and write those variables directly; the engine extracts the modified values back out afterwards.
// Add a header, rewrite the method, then continue.
headers["X-Custom"] = "test";
method = "POST";
true // return value: true = continue, false = short-circuitstatus = 200;
headers["X-Processed"] = "true";
trueThe script's return value controls flow: a boolean false means short-circuit (request) / stop
(response); any other value (or no boolean) defaults to continue.
Built-in helper functions
The Rhai engine registers a handful of host functions usable from any script:
| Function | Returns | Description |
base64_encode(s) | string | Standard base64 encode. |
base64_decode(s) | string | Standard base64 decode (empty string on failure). |
unix_time() | integer | Current Unix time in seconds. |
uuid() | string | A new random UUID v4. |
log_debug(msg) | — | Emit a debug-level trace log. |
log_info(msg) | — | Emit an info-level trace log. |
log_warn(msg) | — | Emit a warn-level trace log. |
parse_json(s) / to_json(v) | string | Currently pass-through helpers (return the string as-is); scripts manipulate JSON as strings. |
Sandboxing and limits04
The default RhaiEngine is configured with guardrails:
Max operations per run:
10_000(prevents infinite loops).Max expression depths:
25/10.Max string size:
1 MiB; max array size:10_000; max map size:10_000.
The engine forbids unsafe code and runs without filesystem or network access beyond the host
functions listed above.
Timeouts05
ScriptMiddleware wraps each execution in a tokio::time::timeout using timeout_ms (default
100 ms); exceeding it yields a timeout error. The convention host-resolution path does not apply a
wall-clock timeout — it instead relies on the engine's operation/size limits to bound execution.
AST caching06
Compiled scripts are cached as Rhai ASTs:
The cache is keyed by script name (inline scripts default to the name
"inline"; the convention path hashes the script body so distinct scripts get distinct slots).First execution compiles and stores the AST (a cache miss); subsequent executions reuse it (a cache hit).
CacheStatsexposescached_scripts,hits,misses, and ahit_rate();clear_cache()empties the cache.
Languages07
ScriptLanguage defines four variants, but only one is implemented:
| Language | Status |
| Rhai | Implemented. |
| Lua | Planned. Selecting it logs a warning and falls back to Rhai. |
| JavaScript (Deno) | Planned. Falls back to Rhai. |
| WebAssembly | Planned. Falls back to Rhai. |
Lua, JavaScript, and WebAssembly are not implemented. The enum variants exist, but the script middleware silently substitutes the Rhai engine for any of them. Treat Rhai as the only usable language today.
Related08
Plugins overview — how scripting relates to the plugin system.
Concepts: Routing — where convention host-resolution scripts fit.
Kubernetes — subdomain routing and conventions in a cluster.
Configuration — the gateway configuration schema.