Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support registering custom variable #5941

Merged
merged 3 commits into from
Dec 29, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 24 additions & 4 deletions apisix/core/ctx.lua
Original file line number Diff line number Diff line change
Expand Up @@ -215,11 +215,19 @@ do
key = sub_str(key, 9)
val = get_parsed_graphql()[key]

elseif apisix_var_names[key] then
val = ngx.ctx.api_ctx and ngx.ctx.api_ctx[key]

else
val = get_var(key, t._request)
local getter = apisix_var_names[key]
if getter then
if getter == true then
val = ngx.ctx.api_ctx and ngx.ctx.api_ctx[key]
else
-- the getter is registered by ctx.register_var
val = getter(ngx.ctx.api_ctx)
shuaijinchao marked this conversation as resolved.
Show resolved Hide resolved
end

else
val = get_var(key, t._request)
end
end

if val ~= nil and not no_cacheable_var_names[key] then
Expand All @@ -239,6 +247,18 @@ do
end,
}

function _M.register_var(name, getter)
if type(getter) ~= "function" then
error("the getter of registered var should be a function")
end

if apisix_var_names[name] then
error(name .. " is registered")
end

apisix_var_names[name] = getter
end

function _M.set_vars_meta(ctx)
local var = tablepool.fetch("ctx_var", 0, 32)
if not var._cache then
Expand Down
35 changes: 29 additions & 6 deletions docs/en/latest/plugin-develop.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,11 @@ title: Plugin Develop
- [implement the logic](#implement-the-logic)
- [conf parameter](#conf-parameter)
- [ctx parameter](#ctx-parameter)
- [Register public API](#register-public-api)
- [Register control API](#register-control-api)
- [register public API](#register-public-api)
- [register control API](#register-control-api)
- [register custom variable](#register-custom-variable)
- [write test case](#write-test-case)
- [Attach the test-nginx execution process:](#attach-the-test-nginx-execution-process)
- [attach the test-nginx execution process:](#attach-the-test-nginx-execution-process)

This documentation is about developing plugin in Lua. For other languages,
see [external plugin](./external-plugin.md).
Expand Down Expand Up @@ -388,7 +389,7 @@ function _M.access(conf, ctx)
end
```

## Register public API
## register public API

A plugin can register API which exposes to the public. Take jwt-auth plugin as an example, this plugin registers `GET /apisix/plugin/jwt/sign` to allow client to sign its key:

Expand All @@ -411,7 +412,7 @@ end
Note that the public API is exposed to the public.
You may need to use [interceptors](plugin-interceptors.md) to protect it.

## Register control API
## register control API

If you only want to expose the API to the localhost or intranet, you can expose it via [Control API](./control-api.md).

Expand Down Expand Up @@ -441,6 +442,28 @@ end

If you don't change the default control API configuration, the plugin will be expose `GET /v1/plugin/example-plugin/hello` which can only be accessed via `127.0.0.1`.

## register custom variable

We can use variables in many places of APISIX. For example, customizing log format in http-logger, using it as the key of `limit-*` plugins. In some situations, the builtin variables are not enough. Therefore, APISIX allows developers to register their variables globally, and use them as normal builtin variables.

For instance, let's register a variable called `a6_labels_zone` to fetch the value of the `zone` label in a route:

```
local core = require "apisix.core"

core.ctx.register_var("a6_labels_zone", function(ctx)
local route = ctx.matched_route and ctx.matched_route.value
if route and route.labels then
return route.labels.zone
end
return nil
end)
```

After that, any get operation to `$a6_labels_zone` will call the registered getter to fetch the value.

Note that the custom variables can't be used in features that depend on the Nginx directive, like `access_log_format`.

## write test case

For functions, write and improve the test cases of various dimensions, do a comprehensive test for your plugin! The
Expand Down Expand Up @@ -488,7 +511,7 @@ Additionally, there are some convenience testing endpoints which can be found [h

Refer the following [document](how-to-build.md#Step-4-Run-Test-Cases) to setup the testing framework.

### Attach the test-nginx execution process:
### attach the test-nginx execution process:

According to the path we configured in the makefile and some configuration items at the front of each __.t__ file, the
framework will assemble into a complete nginx.conf file. "__t/servroot__" is the working directory of Nginx and start the
Expand Down
23 changes: 23 additions & 0 deletions docs/zh/latest/plugin-develop.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ title: 插件开发
- [ctx 参数](#ctx-参数)
- [注册公共接口](#注册公共接口)
- [注册控制接口](#注册控制接口)
- [注册自定义变量](#注册自定义变量)
- [编写测试用例](#编写测试用例)
- [附上 test-nginx 执行流程](#附上-test-nginx-执行流程)

Expand Down Expand Up @@ -361,6 +362,28 @@ end

如果你没有改过默认的 control API 配置,这个插件暴露的 `GET /v1/plugin/example-plugin/hello` API 只有通过 `127.0.0.1` 才能访问它。

## 注册自定义变量

我们可以在APISIX的许多地方使用变量。例如,在 http-logger 中自定义日志格式,用它作为 `limit-*` 插件的键。在某些情况下,内置的变量是不够的。因此,APISIX允许开发者在全局范围内注册他们的变量,并将它们作为普通的内置变量使用。

例如,让我们注册一个叫做 `a6_labels_zone` 的变量来获取路由中 `zone` 标签的值。

```
local core = require "apisix.core"

core.ctx.register_var("a6_labels_zone", function(ctx)
local route = ctx.matched_route and ctx.matched_route.value
if route and route.labels then
return route.labels.zone
end
return nil
end)
```

此后,任何对 `$a6_labels_zone` 的获取操作都会调用注册的获取器来获取数值。

注意,自定义变量不能用于依赖 Nginx 指令的功能,如 `access_log_format`。

## 编写测试用例

针对功能,完善各种维度的测试用例,对插件做个全方位的测试吧!插件的测试用例,都在 __t/plugin__ 目录下,可以前去了解。
Expand Down
60 changes: 60 additions & 0 deletions t/core/ctx2.t
Original file line number Diff line number Diff line change
Expand Up @@ -316,3 +316,63 @@ Content-Type: application/x-www-form-urlencoded
--- error_code: 404
--- response_body
{"error_msg":"404 Route Not Found"}



=== TEST 15: register custom variable
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[=[{
"upstream": {
"nodes": {
"127.0.0.1:1980": 1
},
"type": "roundrobin"
},
"plugins": {
"serverless-pre-function": {
"phase": "rewrite",
"functions" : ["return function(conf, ctx) ngx.say('find ctx.var.a6_labels_zone: ', ctx.var.a6_labels_zone) end"]
}
},
"uri": "/hello",
"labels": {
"zone": "Singapore"
}
}]=]
)

if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}



=== TEST 16: hit
--- config
location /t {
content_by_lua_block {
local http = require "resty.http"
local core = require "apisix.core"
core.ctx.register_var("a6_labels_zone", function(ctx)
local route = ctx.matched_route and ctx.matched_route.value
if route and route.labels then
return route.labels.zone
end
return nil
end)
local uri = "http://127.0.0.1:" .. ngx.var.server_port .. "/hello"
local httpc = http.new()
local res = assert(httpc:request_uri(uri))
ngx.print(res.body)
}
}
--- response_body
find ctx.var.a6_labels_zone: Singapore