You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@apisix.apache.org by sp...@apache.org on 2020/12/27 01:30:10 UTC

[apisix] branch master updated: docs: improve the plugin-develop doc (#3125)

This is an automated email from the ASF dual-hosted git repository.

spacewander pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/apisix.git


The following commit(s) were added to refs/heads/master by this push:
     new 5a67fc3  docs: improve the plugin-develop doc (#3125)
5a67fc3 is described below

commit 5a67fc382e8670382ac48345acf127cb49116eb8
Author: 罗泽轩 <sp...@gmail.com>
AuthorDate: Sun Dec 27 09:30:01 2020 +0800

    docs: improve the plugin-develop doc (#3125)
    
    Co-authored-by: John Bampton <jb...@users.noreply.github.com>
---
 apisix/plugins/example-plugin.lua |  10 +--
 doc/plugin-develop.md             | 176 +++++++++++++++++++++++++++++++-------
 doc/zh-cn/plugin-develop.md       | 172 ++++++++++++++++++++++++++++++-------
 3 files changed, 288 insertions(+), 70 deletions(-)

diff --git a/apisix/plugins/example-plugin.lua b/apisix/plugins/example-plugin.lua
index 089b018..e270a5c 100644
--- a/apisix/plugins/example-plugin.lua
+++ b/apisix/plugins/example-plugin.lua
@@ -52,14 +52,8 @@ local _M = {
 }
 
 
-function _M.check_schema(conf)
-    local ok, err = core.schema.check(schema, conf)
-
-    if not ok then
-        return false, err
-    end
-
-    return true
+function _M.check_schema(conf, schema_type)
+    return core.schema.check(schema, conf)
 end
 
 
diff --git a/doc/plugin-develop.md b/doc/plugin-develop.md
index 147fffd..d3c3f67 100644
--- a/doc/plugin-develop.md
+++ b/doc/plugin-develop.md
@@ -26,6 +26,8 @@
 - [**choose phase to run**](#choose-phase-to-run)
 - [**implement the logic**](#implement-the-logic)
 - [**write test case**](#write-test-case)
+- [**register public API**](#register-public-api)
+- [**register control API**](#register-control-api)
 
 ## check dependencies
 
@@ -56,29 +58,28 @@ Note : if the dependency of some plugin needs to be initialized when Nginx start
 
 ## name and config
 
-determine the name and priority of the plugin, and add to conf/config-default.yaml. For example, for the key-auth plugin,
+Determine the name and priority of the plugin, and add to conf/config-default.yaml. For example, for the example-plugin plugin,
  you need to specify the plugin name in the code (the name is the unique identifier of the plugin and cannot be
- duplicate), you can see the code in file "__apisix/plugins/key-auth.lua__" :
+ duplicate), you can see the code in file "__apisix/plugins/example-plugin.lua__" :
 
 ```lua
-   local plugin_name = "key-auth"
+local plugin_name = "example-plugin"
 
-   local _M = {
-      version = 0.1,
-      priority = 2500,
-      type = 'auth',
-      name = plugin_name,
-      schema = schema,
-   }
+local _M = {
+    version = 0.1,
+    priority = 0,
+    name = plugin_name,
+    schema = schema,
+    metadata_schema = metadata_schema,
+}
 ```
 
-Note : The priority of the new plugin cannot be the same as the priority of any existing plugin. In addition, plugins with a high priority value will be executed first. For example, the priority of basic-auth is 2520 and the priority of ip-restriction is 3000. Therefore, the ip-restriction plugin will be executed first, then the basic-auth plugin.
+Note : The priority of the new plugin cannot be the same as the priority of any existing plugin. In addition, plugins with a high priority value will be executed first in a given phase (see the definition of `phase` in [choose-phase-to-run](#choose-phase-to-run)). For example, the priority of example-plugin is 0 and the priority of ip-restriction is 3000. Therefore, the ip-restriction plugin will be executed first, then the example-plugin plugin.
 
 in the "__conf/config-default.yaml__" configuration file, the enabled plugins (all specified by plugin name) are listed.
 
 ```yaml
 plugins:                          # plugin list
-  - example-plugin
   - limit-req
   - limit-count
   - limit-conn
@@ -94,6 +95,7 @@ plugins:                          # plugin list
   - openid-connect
   - proxy-rewrite
   - redirect
+  ...
 ```
 
 Note : the order of the plugins is not related to the order of execution.
@@ -106,33 +108,41 @@ $(INSTALL) apisix/plugins/skywalking/*.lua $(INST_LUADIR)/apisix/plugins/skywalk
 
 ## schema and check
 
-Write [Json Schema](https://json-schema.org) descriptions and check functions. similarly, take the key-auth plugin as an example to see its
+Write [Json Schema](https://json-schema.org) descriptions and check functions. Similarly, take the example-plugin plugin as an example to see its
  configuration data :
 
 ```json
- "key-auth" : {
-       "key" : "auth-one"
-  }
+"example-plugin" : {
+    "i": 1,
+    "s": "s",
+    "t": [1]
+}
 ```
 
-The configuration data of the plugin is relatively simple. Only one attribute named key is supported. Let's look
-at its schema description :
+Let's look at its schema description :
 
 ```lua
-   local schema = {
-       type = "object",
-       properties = {
-           key = {type = "string"},
-       }
-   }
+local schema = {
+    type = "object",
+    properties = {
+        i = {type = "number", minimum = 0},
+        s = {type = "string"},
+        t = {type = "array", minItems = 1},
+        ip = {type = "string"},
+        port = {type = "integer"},
+    },
+    required = {"i"},
+}
 ```
 
+The schema defines a non-negative number `i`, a string `s`, a non-empty array of `t`, and `ip` / `port`. Only `i` is required.
+
 At the same time, we need to implement the __check_schema(conf)__ method to complete the specification verification.
 
 ```lua
-   function _M.check_schema(conf)
-       return core.schema.check(schema, conf)
-   end
+function _M.check_schema(conf, schema_type)
+    return core.schema.check(schema, conf)
+end
 ```
 
 Note: the project has provided the public method "__core.schema.check__", which can be used directly to complete JSON
@@ -162,15 +172,65 @@ local _M = {
 }
 ```
 
+You might have noticed the key-auth plugin has `type = 'auth'` in its definition.
+When we set the type of plugin to `auth`, it means that this plugin is an authentication plugin.
+
+An authentication plugin needs to choose a consumer after execution. For example, in key-auth plugin, it calls the `consumer.attach_consumer` to attach a consumer, which is chosen via the `apikey` header.
+
+To interact with the `consumer` resource, this type of plugin needs to provide a `consumer_schema` to check the `plugins` configuration in the `consumer`.
+
+Here is the consumer configuration for key-auth plugin:
+```json
+{
+    "username": "Joe",
+    "plugins": {
+        "key-auth": {
+            "key": "Joe's key"
+        }
+    }
+}
+```
+It will be used when you try to create a [Consumer](https://github.com/apache/apisix/blob/master/doc/admin-api.md#consumer)
+
+To validate the configuration, the plugin uses a schema like this:
+```json
+local consumer_schema = {
+    type = "object",
+    additionalProperties = false,
+    properties = {
+        key = {type = "string"},
+    },
+    required = {"key"},
+}
+```
+
+Note the difference between key-auth's __check_schema(conf)__ method to example-plugin's:
+```lua
+-- key-auth
+function _M.check_schema(conf, schema_type)
+    if schema_type == core.schema.TYPE_CONSUMER then
+        return core.schema.check(consumer_schema, conf)
+    else
+        return core.schema.check(schema, conf)
+    end
+end
+```
+
+```lua
+-- example-plugin
+function _M.check_schema(conf, schema_type)
+    return core.schema.check(schema, conf)
+end
+```
+
 ## choose phase to run
 
 Determine which phase to run, generally access or rewrite. If you don't know the [Openresty life cycle](https://openresty-reference.readthedocs.io/en/latest/Directives/), it's
 recommended to know it in advance. For example key-auth is an authentication plugin, thus the authentication should be completed
-before forwarding the request to any upstream service. Therefore, the plugin can be executed in the rewrite and access phases.
-In APISIX, the authentication logic is implemented in the rewrite phase. Generally, IP access and interface
-permission are completed in the access phase.
+before forwarding the request to any upstream service. Therefore, the plugin must be executed in the rewrite phases.
+In APISIX, only the authentication logic can be run in the rewrite phase. Other logic needs to run before proxy should be in access phase.
 
-The following code snippet shows how to implement any logic relevant to the plugin in the Openresty log phase.
+The following code snippet shows how to implement any logic relevant to the plugin in the OpenResty log phase.
 
 ```lua
 function _M.log(conf)
@@ -198,7 +258,7 @@ of the real test case. For example, the key-auth plugin :
     location /t {
         content_by_lua_block {
             local plugin = require("apisix.plugins.key-auth")
-            local ok, err = plugin.check_schema({key = 'test-key'})
+            local ok, err = plugin.check_schema({key = 'test-key'}, core.schema.TYPE_CONSUMER)
             if not ok then
                 ngx.say(err)
             end
@@ -233,3 +293,55 @@ According to the path we configured in the makefile and some configuration items
 framework will assemble into a complete nginx.conf file. "__t/servroot__" is the working directory of Nginx and start the
 Nginx instance. according to the information provided by the test case, initiate the http request and check that the
 return items of HTTP include HTTP status, HTTP response header, HTTP response body and so on.
+
+### 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:
+
+```lua
+local function gen_token()
+    ...
+end
+
+function _M.api()
+    return {
+        {
+            methods = {"GET"},
+            uri = "/apisix/plugin/jwt/sign",
+            handler = gen_token,
+        }
+    }
+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
+
+If you only want to expose the API to the localhost or intranet, you can expose it via [Control API](./control-api.md).
+
+Take a look at example-plugin plugin:
+```lua
+local function hello()
+    local args = ngx.req.get_uri_args()
+    if args["json"] then
+        return 200, {msg = "world"}
+    else
+        return 200, "world\n"
+    end
+end
+
+
+function _M.control_api()
+    return {
+        {
+            methods = {"GET"},
+            uris = {"/v1/plugin/example-plugin/hello"},
+            handler = hello,
+        }
+    }
+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`.
diff --git a/doc/zh-cn/plugin-develop.md b/doc/zh-cn/plugin-develop.md
index 8d86be1..c93fa44 100644
--- a/doc/zh-cn/plugin-develop.md
+++ b/doc/zh-cn/plugin-develop.md
@@ -26,6 +26,8 @@
 - [**确定执行阶段**](#确定执行阶段)
 - [**编写执行逻辑**](#编写执行逻辑)
 - [**编写测试用例**](#编写测试用例)
+- [**注册公共接口**](#注册公共接口)
+- [**注册控制接口**](#注册控制接口)
 
 ## 检查外部依赖
 
@@ -54,28 +56,27 @@
 
 ## 插件命名与配置
 
-给插件取一个很棒的名字,确定插件的加载优先级,然后在 __conf/config-default.yaml__ 文件中添加上你的插件名。例如 key-auth 这个插件,
-需要在代码里指定插件名称(名称是插件的唯一标识,不可重名),在 __apisix/plugins/key-auth.lua__ 文件中可以看到:
+给插件取一个很棒的名字,确定插件的加载优先级,然后在 __conf/config-default.yaml__ 文件中添加上你的插件名。例如 example-plugin 这个插件,
+需要在代码里指定插件名称(名称是插件的唯一标识,不可重名),在 __apisix/plugins/example-plugin.lua__ 文件中可以看到:
 
 ```lua
-   local plugin_name = "key-auth"
+local plugin_name = "example-plugin"
 
-   local _M = {
-       version = 0.1,
-       priority = 2500,
-       type = 'auth',
-       name = plugin_name,
-       schema = schema,
-   }
+local _M = {
+    version = 0.1,
+    priority = 0,
+    name = plugin_name,
+    schema = schema,
+    metadata_schema = metadata_schema,
+}
 ```
 
-注:新插件的优先级( priority 属性 )不能与现有插件的优先级相同。另外,优先级( priority )值大的插件,会优先执行,比如 `basic-auth` 的优先级是 2520 ,`ip-restriction` 的优先级是 3000 ,所以在每个阶段,会先执行 `ip-restriction` 插件,再去执行 `basic-auth` 插件。
+注:新插件的优先级( priority 属性 )不能与现有插件的优先级相同。另外,同一个阶段里面,优先级( priority )值大的插件,会优先执行,比如 `example-plugin` 的优先级是 0 ,`ip-restriction` 的优先级是 3000 ,所以在每个阶段,会先执行 `ip-restriction` 插件,再去执行 `example-plugin` 插件。这里的“阶段”的定义,参见后续的[确定执行阶段](#确定执行阶段)这一节。
 
 在 __conf/config-default.yaml__ 配置文件中,列出了启用的插件(都是以插件名指定的):
 
 ```yaml
 plugins:                          # plugin list
-  - example-plugin
   - limit-req
   - limit-count
   - limit-conn
@@ -91,6 +92,7 @@ plugins:                          # plugin list
   - openid-connect
   - proxy-rewrite
   - redirect
+  ...
 ```
 
 注:先后顺序与执行顺序无关。
@@ -104,31 +106,40 @@ $(INSTALL) apisix/plugins/skywalking/*.lua $(INST_LUADIR)/apisix/plugins/skywalk
 ## 配置描述与校验
 
 定义插件的配置项,以及对应的 [Json Schema](https://json-schema.org) 描述,并完成对 json 的校验,这样方便对配置的数据规
-格进行验证,以确保数据的完整性以及程序的健壮性。同样,我们以 key-auth 插件为例,看看他的配置数据:
+格进行验证,以确保数据的完整性以及程序的健壮性。同样,我们以 example-plugin 插件为例,看看他的配置数据:
 
 ```json
- "key-auth" : {
-       "key" : "auth-one"
-  }
+"example-plugin" : {
+    "i": 1,
+    "s": "s",
+    "t": [1]
+}
 ```
 
-插件的配置数据比较简单,只支持一个命名为 key 的属性,那么我们看下他的 Schema 描述:
+我们看下他的 Schema 描述:
 
 ```lua
-   local schema = {
-       type = "object",
-       properties = {
-           key = {type = "string"},
-       }
-   }
+local schema = {
+    type = "object",
+    properties = {
+        i = {type = "number", minimum = 0},
+        s = {type = "string"},
+        t = {type = "array", minItems = 1},
+        ip = {type = "string"},
+        port = {type = "integer"},
+    },
+    required = {"i"},
+}
 ```
 
+这个 schema 定义了一个非负数 `i`,字符串 `s`,非空数组 `t`,和 `ip` 跟 `port`。只有 `i` 是必需的。
+
 同时,需要实现 __check_schema(conf)__ 方法,完成配置参数的合法性校验。
 
 ```lua
-   function _M.check_schema(conf)
-       return core.schema.check(schema, conf)
-   end
+function _M.check_schema(conf)
+    return core.schema.check(schema, conf)
+end
 ```
 
 注:项目已经提供了 __core.schema.check__ 公共方法,直接使用即可完成配置参数校验。
@@ -157,11 +168,60 @@ local _M = {
 }
 ```
 
+你可能之前见过 key-auth 这个插件在它的模块定义时设置了 `type = 'auth'`。
+当一个插件设置 `type = 'auth'`,说明它是个认证插件。
+
+认证插件需要在执行后选择对应的consumer。举个例子,在 key-auth 插件中,它通过 `apikey` 请求头获取对应的 consumer,然后通过 `consumer.attach_consumer` 设置它。
+
+为了跟 `consumer` 资源一起使用,认证插件需要提供一个 `consumer_schema` 来检验 `consumer` 资源的 `plugins` 属性里面的配置。
+
+下面是 key-auth 插件的 consumer 配置:
+```json
+{
+    "username": "Joe",
+    "plugins": {
+        "key-auth": {
+            "key": "Joe's key"
+        }
+    }
+}
+```
+你在创建 [Consumer](https://github.com/apache/apisix/blob/master/doc/admin-api.md#consumer) 时会用到它。
+
+为了检验这个配置,这个插件使用了如下的schema:
+```json
+local consumer_schema = {
+    type = "object",
+    additionalProperties = false,
+    properties = {
+        key = {type = "string"},
+    },
+    required = {"key"},
+}
+```
+
+注意 key-auth 的 __check_schema(conf)__ 方法和 example-plugin 的同名方法的区别:
+```lua
+-- key-auth
+function _M.check_schema(conf, schema_type)
+    if schema_type == core.schema.TYPE_CONSUMER then
+        return core.schema.check(consumer_schema, conf)
+    else
+        return core.schema.check(schema, conf)
+    end
+end
+```
+
+```lua
+-- example-plugin
+function _M.check_schema(conf, schema_type)
+    return core.schema.check(schema, conf)
+end
+```
+
 ## 确定执行阶段
 
-根据业务功能,确定你的插件需要在哪个阶段执行。 key-auth 是一个认证插件,只要在请求进来之后业务响应之前完成认证即可。
-该插件在 rewrite 、access 阶段执行都可以,项目中是用 rewrite 阶段执行认证逻辑,一般 IP 准入、接口权限是在 access 阶段
-完成的。
+根据业务功能,确定你的插件需要在哪个阶段执行。 key-auth 是一个认证插件,所以需要在 rewrite 阶段执行。在 APISIX,只有认证逻辑可以在 rewrite 阶段里面完成,其他需要在代理到上游之前执行的逻辑都是在 access 阶段完成的。
 
 **注意:我们不能在 rewrite 和 access 阶段调用 `ngx.exit` 或者 `core.respond.exit`。如果确实需要退出,只需要 return 状态码和正文,插件引擎将使用返回的状态码和正文进行退出。[例子](https://github.com/apache/apisix/blob/35269581e21473e1a27b11cceca6f773cad0192a/apisix/plugins/limit-count.lua#L177)**
 
@@ -182,7 +242,7 @@ local _M = {
     location /t {
         content_by_lua_block {
             local plugin = require("apisix.plugins.key-auth")
-            local ok, err = plugin.check_schema({key = 'test-key'})
+            local ok, err = plugin.check_schema({key = 'test-key'}, core.schema.TYPE_CONSUMER)
             if not ok then
                 ngx.say(err)
             end
@@ -213,3 +273,55 @@ done
 根据我们在 Makefile 里配置的 PATH,和每一个 __.t__ 文件最前面的一些配置项,框架会组装成一个完整的 nginx.conf 文件,
 __t/servroot__ 会被当成 Nginx 的工作目录,启动 Nginx 实例。根据测试用例提供的信息,发起 http 请求并检查 http 的返回项,
 包括 http status,http response header, http response body 等。
+
+### 注册公共接口
+
+插件可以注册暴露给公网的接口。以 jwt-auth 插件为例,这个插件为了让客户端能够签名,注册了 `GET /apisix/plugin/jwt/sign` 这个接口:
+
+```lua
+local function gen_token()
+    ...
+end
+
+function _M.api()
+    return {
+        {
+            methods = {"GET"},
+            uri = "/apisix/plugin/jwt/sign",
+            handler = gen_token,
+        }
+    }
+end
+```
+
+注意注册的接口会暴露到外网。
+你可能需要使用 [interceptors](plugin-interceptors.md) 来保护它。
+
+### 注册控制接口
+
+如果你只想暴露 API 到 localhost 或内网,你可以通过 [Control API](./control-api.md) 来暴露它。
+
+Take a look at example-plugin plugin:
+```lua
+local function hello()
+    local args = ngx.req.get_uri_args()
+    if args["json"] then
+        return 200, {msg = "world"}
+    else
+        return 200, "world\n"
+    end
+end
+
+
+function _M.control_api()
+    return {
+        {
+            methods = {"GET"},
+            uris = {"/v1/plugin/example-plugin/hello"},
+            handler = hello,
+        }
+    }
+end
+```
+
+如果你没有改过默认的 control API 配置,这个插件暴露的 `GET /v1/plugin/example-plugin/hello` API 只有通过 `127.0.0.1` 才能访问它。