Octopus
1.x
Docs/Octopus/Scripting
Open

Reading6 min
Updated31 Jul 2026
Sourcev1/plugins/scripting.mdx

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.

Note

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 namespace and service produces 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.

Note

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:

FieldTypeDefaultDescription
languageenumrhaiScript language (see Languages).
code / pathstringInline code or a file path (flattened ScriptSource).
on_requestbooltrueRun the script on the request.
on_responseboolfalseRun the script on the response.
continue_on_errorboolfalseIf true, script errors are logged and the request continues.
timeout_msu64100Per-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";
        true

When 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.

rust
// Add a header, rewrite the method, then continue.
headers["X-Custom"] = "test";
method = "POST";
true   // return value: true = continue, false = short-circuit

The 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:

FunctionReturnsDescription
base64_encode(s)stringStandard base64 encode.
base64_decode(s)stringStandard base64 decode (empty string on failure).
unix_time()integerCurrent Unix time in seconds.
uuid()stringA 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)stringCurrently 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).

  • CacheStats exposes cached_scripts, hits, misses, and a hit_rate(); clear_cache() empties the cache.

Languages07

ScriptLanguage defines four variants, but only one is implemented:

LanguageStatus
RhaiImplemented.
LuaPlanned. Selecting it logs a warning and falls back to Rhai.
JavaScript (Deno)Planned. Falls back to Rhai.
WebAssemblyPlanned. Falls back to Rhai.
Warning

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.