skills/moonming/a6/a6-plugin-serverless

a6-plugin-serverless

SKILL.md

a6-plugin-serverless

Overview

APISIX provides two serverless plugins that execute inline Lua functions during request processing:

  • serverless-pre-function — runs at the beginning of the specified phase (priority 10000, executes early).
  • serverless-post-function — runs at the end of the specified phase (priority −2000, executes late).

Both share identical configuration. Functions are defined as Lua strings in the Admin API and compiled at load time.

When to Use

  • Inject custom request/response logic without writing a full plugin
  • Quick prototyping of header injection, redirects, or logging
  • Add lightweight pre-processing (rewrite) or post-processing (log)
  • Dynamic routing decisions based on request attributes

Plugin Configuration Reference

Field Type Required Default Valid Values Description
phase string No "access" rewrite, access, header_filter, body_filter, log, before_proxy Phase when functions execute
functions array[string] Yes Lua function strings Functions executed sequentially; each must return a function

Function Signature

Since APISIX v2.6+, functions receive two arguments:

return function(conf, ctx)
    -- conf: plugin configuration object
    -- ctx:  APISIX request context (shared across plugins)
    --
    -- Optional return:
    --   return code, body   -- exit immediately with HTTP status + body
    --   return              -- continue to next function / plugin
end

Rules:

  • The string MUST return a function. Raw statements are rejected.
  • Functions are cached via LRU cache; update the route to pick up changes.

Phase Execution Order

1. rewrite         → modify request before routing
2. access          → authorization / authentication checks
3. before_proxy    → last chance before upstream call
4. header_filter   → modify response headers
5. body_filter     → modify response body (chunked via ngx.arg)
6. log             → logging after response sent (read-only)

Phase Restrictions

Phase Can Read Request Can Modify Request Can Modify Response Can Exit
rewrite
access
before_proxy
header_filter ✅ (headers)
body_filter ✅ (body chunks)
log

Step-by-Step Examples

1. Basic Logging

a6 route create -f - <<'EOF'
{
  "id": "serverless-log",
  "uri": "/api/*",
  "plugins": {
    "serverless-pre-function": {
      "phase": "rewrite",
      "functions": [
        "return function() ngx.log(ngx.WARN, 'incoming request: ', ngx.var.uri) end"
      ]
    }
  },
  "upstream": {
    "type": "roundrobin",
    "nodes": {"backend:8080": 1}
  }
}
EOF

2. HTTP to HTTPS Redirect

a6 route create -f - <<'EOF'
{
  "id": "force-https",
  "uri": "/*",
  "plugins": {
    "serverless-pre-function": {
      "phase": "rewrite",
      "functions": [
        "return function() if ngx.var.scheme == 'http' then ngx.header['Location'] = 'https://' .. ngx.var.host .. ngx.var.request_uri; ngx.exit(301) end end"
      ]
    }
  },
  "upstream": {
    "type": "roundrobin",
    "nodes": {"backend:8080": 1}
  }
}
EOF

3. Request Header Injection

a6 route create -f - <<'EOF'
{
  "id": "inject-headers",
  "uri": "/api/*",
  "plugins": {
    "serverless-pre-function": {
      "phase": "rewrite",
      "functions": [
        "return function(conf, ctx) ngx.req.set_header('X-Request-ID', ngx.var.request_id); ngx.req.set_header('X-Real-IP', ngx.var.remote_addr) end"
      ]
    }
  },
  "upstream": {
    "type": "roundrobin",
    "nodes": {"backend:8080": 1}
  }
}
EOF

4. Modify Upstream URI

a6 route create -f - <<'EOF'
{
  "id": "rewrite-uri",
  "uri": "/legacy/*",
  "plugins": {
    "serverless-post-function": {
      "phase": "access",
      "functions": [
        "return function(conf, ctx) ctx.var.upstream_uri = '/v2' .. ngx.var.uri end"
      ]
    }
  },
  "upstream": {
    "type": "roundrobin",
    "nodes": {"backend:8080": 1}
  }
}
EOF

5. Response Header Modification (header_filter phase)

a6 route create -f - <<'EOF'
{
  "id": "resp-headers",
  "uri": "/api/*",
  "plugins": {
    "serverless-post-function": {
      "phase": "header_filter",
      "functions": [
        "return function() ngx.header['X-Processed-By'] = 'APISIX'; ngx.header['X-Response-Time'] = ngx.now() - ngx.req.start_time() end"
      ]
    }
  },
  "upstream": {
    "type": "roundrobin",
    "nodes": {"backend:8080": 1}
  }
}
EOF

6. Closure with Persistent State

a6 route create -f - <<'EOF'
{
  "id": "closure-counter",
  "uri": "/count",
  "plugins": {
    "serverless-pre-function": {
      "phase": "log",
      "functions": [
        "local count = 0; return function() count = count + 1; ngx.log(ngx.WARN, 'request count: ', count) end"
      ]
    }
  },
  "upstream": {
    "type": "roundrobin",
    "nodes": {"backend:8080": 1}
  }
}
EOF

7. Multiple Sequential Functions

a6 route create -f - <<'EOF'
{
  "id": "multi-fn",
  "uri": "/api/*",
  "plugins": {
    "serverless-pre-function": {
      "phase": "rewrite",
      "functions": [
        "return function() ngx.log(ngx.WARN, 'step one') end",
        "return function() ngx.log(ngx.WARN, 'step two') end"
      ]
    }
  },
  "upstream": {
    "type": "roundrobin",
    "nodes": {"backend:8080": 1}
  }
}
EOF

8. Custom Authentication Guard

a6 route create -f - <<'EOF'
{
  "id": "custom-auth",
  "uri": "/admin/*",
  "plugins": {
    "serverless-pre-function": {
      "phase": "access",
      "functions": [
        "return function() local token = ngx.var.http_authorization; if not token or token ~= 'Bearer secret123' then return 401, '{\"error\":\"unauthorized\"}' end end"
      ]
    }
  },
  "upstream": {
    "type": "roundrobin",
    "nodes": {"backend:8080": 1}
  }
}
EOF

Available Lua APIs

Core ngx APIs

-- Request
ngx.var.uri, ngx.var.request_uri, ngx.var.scheme, ngx.var.host
ngx.var.remote_addr, ngx.var.request_method, ngx.var.request_id
ngx.req.get_headers(), ngx.req.get_uri_args(), ngx.req.get_method()
ngx.req.set_header(name, value), ngx.req.read_body(), ngx.req.get_body_data()

-- Response
ngx.header["Name"] = "value"
ngx.status = 200
ngx.say(data), ngx.print(data)
ngx.exit(status), ngx.redirect(uri, status)

-- Logging
ngx.log(ngx.ERR, msg), ngx.log(ngx.WARN, msg), ngx.log(ngx.INFO, msg)

-- Utilities
ngx.time(), ngx.now(), ngx.encode_base64(str), ngx.decode_base64(str)

APISIX Context Variables

ctx.var.upstream_uri = "/new/path"   -- modify upstream request URI
ctx.curr_req_matched._path           -- matched route path
ctx.consumer_name                    -- authenticated consumer name
ctx.route_id                         -- current route ID
ctx.service_id                       -- current service ID

Available Libraries

local json = require("cjson")
local core = require("apisix.core")
local http = require("resty.http")
local lrucache = require("resty.lrucache")

Config Sync Example

version: "1"
routes:
  - id: serverless-demo
    uri: /api/*
    plugins:
      serverless-pre-function:
        phase: rewrite
        functions:
          - "return function() ngx.req.set_header('X-Gateway', 'apisix') end"
      serverless-post-function:
        phase: log
        functions:
          - "return function() ngx.log(ngx.WARN, 'request completed') end"
    upstream_id: my-upstream

Key Differences: Pre vs Post

Feature serverless-pre-function serverless-post-function
Execution Beginning of phase End of phase
Priority 10000 (high — runs early) −2000 (low — runs late)
Typical Use Pre-processing, auth guards Post-processing, logging

Troubleshooting

Symptom Cause Fix
only accept Lua function, the input code type is nil Function string doesn't return a function Wrap code in return function() ... end
failed to compile function Syntax error in Lua code Test code in a Lua REPL first
Function changes not taking effect LRU cache holds old compiled function Update route to trigger recompilation
ngx.say not working in header_filter Phase restriction — cannot write body in header_filter Use header_filter only for ngx.header modifications
No output in log phase Log phase is read-only Use ngx.log() instead of ngx.say()
Blocking I/O causes timeout Synchronous operations in request path Use ngx.timer.at() for async work
Weekly Installs
1
Repository
moonming/a6
First Seen
7 days ago
Installed on
amp1
cline1
opencode1
cursor1
kimi-cli1
codex1