a6-plugin-serverless
概览
Apache APISIX提供两个 serverless 插件,可在请求处理期间执行内联 Lua 函数:
serverless-pre-function— 在指定阶段的开始运行(优先级为 10000,较早执行)。serverless-post-function— 在指定阶段的结束运行(优先级为 −2000,较晚执行)。
两个插件共享相同的配置。函数以 Lua 字符串的形式通过 Admin API 定义,并在加载时编译。
适用场景
- 无需编写完整插件即可注入自定义请求/响应逻辑。
- 快速实现请求头注入、重定向或日志记录原型。
- 添加轻量级前置处理(重写)或后置处理(日志)。
- 根据请求属性动态决定路由。
插件配置参考
| 字段 | 类型 | 是否必填 | 默认值 | 有效值 | 说明 |
|---|---|---|---|---|---|
phase | string | 否 | "access" | rewrite, access, header_filter, body_filter, log, before_proxy | 函数执行的阶段 |
functions | array[string] | 是 | — | Lua 函数字符串 | 函数按顺序执行;每个字符串都必须返回一个函数 |
函数签名
从 Apache APISIX(基于 APISIX v2.6+)开始,函数接收两个参数:
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
规则:
- 字符串必须返回一个函数,直接使用原始语句会被拒绝。
- 函数会通过 LRU 缓存;更新路由以获取变更。
阶段执行顺序
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)
阶段限制
| 阶段 | 可读请求 | 可修改请求 | 可修改响应 | 可退出 |
|---|---|---|---|---|
| rewrite | ✅ | ✅ | ❌ | ✅ |
| access | ✅ | ✅ | ❌ | ✅ |
| before_proxy | ✅ | ✅ | ❌ | ✅ |
| header_filter | ✅ | ❌ | ✅ (请求头) | ❌ |
| body_filter | ✅ | ❌ | ✅(响应体分块) | ❌ |
| 日志 | ✅ | ❌ | ❌ | ❌ |
分步示例
1. 基本日志记录
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 到 HTTPS 重定向
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. 注入请求头
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. 修改上游 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. 修改响应头(header_filter 阶段)
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. 使用持久状态的闭包
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. 多个顺序执行的函数
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. 自定义身份认证保护
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
可用 Lua API
核心 ngx API
-- 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)
Apache APISIX上下文变量
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
可用库
local json = require("cjson")
local core = require("apisix.core")
local http = require("resty.http")
local lrucache = require("resty.lrucache")
配置同步示例
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
关键差异:Pre 与 Post
| 特性 | serverless-pre-function | serverless-post-function |
|---|---|---|
| 执行时机 | 阶段开始 | 阶段结束 |
| 优先级 | 10000(高——较早运行) | −2000(低——较晚运行) |
| 典型用途 | 前置处理、认证保护 | 后置处理、日志记录 |
故障排查
| 现象 | 原因 | 解决方法 |
|---|---|---|
only accept Lua function, input code type is nil | 函数字符串未返回函数 | 使用 return function() ... end 包装代码 |
failed to compile function | Lua 代码存在语法错误 | 先在 Lua REPL 中测试代码 |
| 函数变更未生效 | LRU 缓存保留了旧的已编译函数 | 更新路由以触发重新编译 |
ngx.say 在 header_filter 中无效 | 阶段限制:header_filter 不能写入响应体 | 在 header_filter 中仅使用 ngx.header 进行修改 |
| 日志阶段没有输出 | 日志阶段为只读 | 使用 ngx.log(),不要使用 ngx.say() |
| 阻塞 I/O 导致超时 | 请求路径中存在同步操作 | 使用 ngx.timer.at() 执行异步工作 |
本文根据 api7/a6 仓库中的 a6-plugin-serverless/SKILL.md 生成。可在 AI Agent Skills 页面浏览全部 Skill。