Skip to content

Commit

Permalink
feat: support registering custom variable (#5941)
Browse files Browse the repository at this point in the history
  • Loading branch information
spacewander authored Dec 29, 2021
1 parent e9cea44 commit 0a8de20
Show file tree
Hide file tree
Showing 4 changed files with 136 additions and 10 deletions.
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)
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 @@ -447,6 +448,28 @@ curl -i -X GET "http://127.0.0.1:9090/v1/plugin/example-plugin/hello"

[Read more about control API introduction](./control-api.md)

## 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 @@ -494,7 +517,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 @@ -367,6 +368,28 @@ curl -i -X GET "http://127.0.0.1:9090/v1/plugin/example-plugin/hello"

[查看更多有关 control API 介绍](./control-api.md)

## 注册自定义变量

我们可以在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

0 comments on commit 0a8de20

Please sign in to comment.