You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@apisix.apache.org by we...@apache.org on 2019/11/27 05:56:55 UTC

[incubator-apisix] branch master updated: doc: add doc for how to write plugin.(#909)

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

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


The following commit(s) were added to refs/heads/master by this push:
     new f76fcec  doc: add doc for how to write plugin.(#909)
f76fcec is described below

commit f76fcec57f2c9370e7b7ee6e56dd23dfd4070ba7
Author: coolsoul <so...@users.noreply.github.com>
AuthorDate: Wed Nov 27 13:56:49 2019 +0800

    doc: add doc for how to write plugin.(#909)
---
 FAQ.md                           |   2 +
 FAQ_CN.md                        |   2 +
 doc/plugins/plugin-develop-cn.md | 183 +++++++++++++++++++++++++++++++++++++
 doc/plugins/plugin-develop.md    | 191 +++++++++++++++++++++++++++++++++++++++
 4 files changed, 378 insertions(+)

diff --git a/FAQ.md b/FAQ.md
index a0e6b82..1f1ae17 100644
--- a/FAQ.md
+++ b/FAQ.md
@@ -45,6 +45,8 @@ Yes, in version 0.6 we have dashboard built in, you can operate APISIX through t
 
 Of course, APISIX provides flexible custom plugins for developers and businesses to write their own logic.
 
+[How to write plugin](doc/plugins/plugin-develop.md)
+
 ## Why we choose etcd as the configuration center?
 
 For the configuration center, configuration storage is only the most basic function, and APISIX also needs the following features:
diff --git a/FAQ_CN.md b/FAQ_CN.md
index e1cf4d0..6832c2c 100644
--- a/FAQ_CN.md
+++ b/FAQ_CN.md
@@ -45,6 +45,8 @@ APISIX 是当前性能最好的 API 网关,单核 QPS 达到 2.3 万,平均
 
 当然可以,APISIX 提供了灵活的自定义插件,方便开发者和企业编写自己的逻辑。
 
+[如何开发插件](doc/plugins/plugin-develop-cn.md)
+
 ## 我们为什么选择 etcd 作为配置中心?
 
 对于配置中心,配置存储只是最基本功能,APISIX 还需要下面几个特性:
diff --git a/doc/plugins/plugin-develop-cn.md b/doc/plugins/plugin-develop-cn.md
new file mode 100644
index 0000000..6bf6a9d
--- /dev/null
+++ b/doc/plugins/plugin-develop-cn.md
@@ -0,0 +1,183 @@
+<!--
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+-->
+[English](plugin-develop.md)
+
+# 目录
+- [**检查外部依赖**](#检查外部依赖)
+- [**插件命名与配置**](#插件命名与配置)
+- [**配置描述与校验**](#配置描述与校验)
+- [**确定执行阶段**](#确定执行阶段)
+- [**编写执行逻辑**](#编写执行逻辑)
+- [**编写测试用例**](#编写测试用例)
+
+
+## 检查外部依赖
+
+如果你的插件,涉及到一些外部的依赖和三方库,请首先检查一下依赖项的内容。 如果插件需要用到共享内存,需要在 __bin/apisix__ 文
+件里面进行申明,例如:
+
+```nginx
+    lua_shared_dict plugin-limit-req     10m;
+    lua_shared_dict plugin-limit-count   10m;
+    lua_shared_dict prometheus-metrics   10m;
+    lua_shared_dict plugin-limit-conn    10m;
+    lua_shared_dict upstream-healthcheck 10m;
+    lua_shared_dict worker-events        10m;
+
+    # for openid-connect plugin
+    lua_shared_dict discovery             1m; # cache for discovery metadata documents
+    lua_shared_dict jwks                  1m; # cache for JWKs
+    lua_shared_dict introspection        10m; # cache for JWT verification results
+```
+
+插件本身提供了 init 方法。方便插件加载后做初始化动作。
+
+注:如果部分插件的功能实现,需要在 Nginx 初始化启动,则可能需要在 __lua/apisix.lua__ 文件的初始化方法 http_init 中添加逻辑,并且
+    可能需要在 __bin/apisix__ 文件中,对 Nginx 配置文件生成的部分,添加一些你需要的处理。但是这样容易对全局产生影响,根据现有的
+    插件机制,我们不建议这样做,除非你已经对代码完全掌握。
+
+## 插件命名与配置
+
+给插件取一个很棒的名字,确定插件的加载优先级,然后在 __conf/config.yaml__ 文件中添加上你的插件名。例如 key-auth 这个插件,
+需要在代码里指定插件名称(名称是插件的唯一标识,不可重名),在 __lua/apisix/plugins/key-auth.lua__ 文件中可以看到:
+
+```lua
+   local plugin_name = "key-auth"
+
+   local _M = {
+       version = 0.1,
+       priority = 2500,
+       type = 'auth',
+       name = plugin_name,
+       schema = schema,
+   }
+```
+
+注:新插件的优先级( priority 属性 )不能与现有插件的优先级相同。
+
+在 __conf/config.yaml__ 配置文件中,列出了启用的插件(都是以插件名指定的):
+
+```yaml
+plugins:                          # plugin list
+  - example-plugin
+  - limit-req
+  - limit-count
+  - limit-conn
+  - key-auth
+  - prometheus
+  - node-status
+  - jwt-auth
+  - zipkin
+  - ip-restriction
+  - grpc-transcode
+  - serverless-pre-function
+  - serverless-post-function
+  - openid-connect
+  - proxy-rewrite
+  - redirect
+```
+
+注:先后顺序与执行顺序无关。
+
+## 配置描述与校验
+
+定义插件的配置项,以及对应的 [Json Schema](https://json-schema.org) 描述,并完成对 json 的校验,这样方便对配置的数据规
+格进行验证,以确保数据的完整性以及程序的健壮性。同样,我们以 key-auth 插件为例,看看他的配置数据:
+
+```json
+ "key-auth" : {
+       "key" : "auth-one"
+  }
+```
+
+插件的配置数据比较简单,只支持一个命名为 key 的属性,那么我们看下他的 Schema 描述:
+
+```lua
+   local schema = {
+       type = "object",
+       properties = {
+           key = {type = "string"},
+       }
+   }
+```
+
+同时,需要实现 __check_schema(conf)__ 方法,完成配置参数的合法性校验。
+
+```lua
+   function _M.check_schema(conf)
+       return core.schema.check(schema, conf)
+   end
+```
+
+注:项目已经提供了 __core.schema.check__ 公共方法,直接使用即可完成配置参数校验。
+
+## 确定执行阶段
+
+根据业务功能,确定你的插件需要在哪个阶段执行。 key-auth 是一个认证插件,只要在请求进来之后业务响应之前完成认证即可。
+该插件在 rewrite 、access 阶段执行都可以,项目中是用 rewrite 阶段执行认证逻辑,一般 IP 准入、接口权限是在 access 阶段
+完成的。
+
+## 编写执行逻辑
+
+在对应的阶段方法里编写功能的逻辑代码。
+
+## 编写测试用例
+
+针对功能,完善各种维度的测试用例,对插件做个全方位的测试吧!插件的测试用例,都在 __t/plugin__ 目录下,可以前去了解。
+项目测试框架采用的 [****test-nginx****](https://github.com/openresty/test-nginx)  。
+一个测试用例 __.t__ 文件,通常用 \__DATA\__ 分割成 序言部分 和 数据部分。这里我们简单介绍下数据部分,
+也就是真正测试用例的部分,仍然以 key-auth 插件为例:
+
+```perl
+=== TEST 1: sanity
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.key-auth")
+            local ok, err = plugin.check_schema({key = 'test-key'})
+            if not ok then
+                ngx.say(err)
+            end
+
+            ngx.say("done")
+        }
+    }
+--- request
+GET /t
+--- response_body
+done
+--- no_error_log
+[error]
+```
+
+一个测试用例主要有三部分内容:
+- 程序代码: Nginx  location 的配置内容
+- 输入: http 的 request 信息
+- 输出检查: status ,header ,body ,error_log 检查
+
+这里请求 __/t__ ,经过配置文件 __location__ ,调用 __content_by_lua_block__ 指令完成 lua 的脚本,最终返回。
+用例的断言是 response_body 返回 "done",__no_error_log__ 表示会对 Nginx 的 error.log 检查,
+必须没有 ERROR 级别的记录。
+
+### 附上test-nginx 执行流程:
+
+根据我们在 Makefile 里配置的 PATH,和每一个 __.t__ 文件最前面的一些配置项,框架会组装成一个完整的 nginx.conf 文件,
+__t/servroot__ 会被当成 Nginx 的工作目录,启动 Nginx 实例。根据测试用例提供的信息,发起 http 请求并检查 http 的返回项,
+包括 http status,http response header, http response body 等。
+
diff --git a/doc/plugins/plugin-develop.md b/doc/plugins/plugin-develop.md
new file mode 100644
index 0000000..72f7816
--- /dev/null
+++ b/doc/plugins/plugin-develop.md
@@ -0,0 +1,191 @@
+<!--
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+-->
+[中文](plugin-develop-cn.md)
+
+# table of contents
+- [**check dependencies**](#check-dependencies)
+- [**name and config**](#name-and-config)
+- [**schema and check**](#schema-and-check)
+- [**choose phase to run**](#choose-phase-to-run)
+- [**implement the logic**](#implement-the-logic)
+- [**write test case**](#write-test-case)
+
+
+## check dependencies
+
+if you have dependencies on external libraries , check the dependent items . if your plugin needs to use shared memory , it
+ needs to declare in __bin/apisix__ , for example :
+
+```nginx
+    lua_shared_dict plugin-limit-req     10m;
+    lua_shared_dict plugin-limit-count   10m;
+    lua_shared_dict prometheus-metrics   10m;
+    lua_shared_dict plugin-limit-conn    10m;
+    lua_shared_dict upstream-healthcheck 10m;
+    lua_shared_dict worker-events        10m;
+
+    # for openid-connect plugin
+    lua_shared_dict discovery             1m; # cache for discovery metadata documents
+    lua_shared_dict jwks                  1m; # cache for JWKs
+    lua_shared_dict introspection        10m; # cache for JWT verification results
+```
+
+The plugin itself provides the init method . It is convenient for plugins to perform some initialization after
+ the plugin is loaded .
+
+Note : if the dependency of some plugin needs to be initialized when Nginx start , you may need to add logic to the initialization
+       method "http_init" in the file __Lua/apifix.lua__ , And you may need to add some processing on generated part of Nginx
+       configuration file in __bin/apisix__ file . but it is easy to have an impact on the overall situation according to the
+       existing plugin mechanism, we do not recommend this unless you have a complete grasp of the code .
+
+## name and config
+
+determine the name and priority of the plugin , and add to conf/config.yaml . For example , for the key-auth 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 "__lua/apisix/plugins/key-auth.lua__" :
+
+```lua
+   local plugin_name = "key-auth"
+
+   local _M = {
+      version = 0.1,
+      priority = 2500,
+      type = 'auth',
+      name = plugin_name,
+      schema = schema,
+   }
+```
+
+Note : The priority of the new plugin cannot be the same as the priority of any existing plugin.
+
+in the "__conf/config.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
+  - key-auth
+  - prometheus
+  - node-status
+  - jwt-auth
+  - zipkin
+  - ip-restriction
+  - grpc-transcode
+  - serverless-pre-function
+  - serverless-post-function
+  - openid-connect
+  - proxy-rewrite
+  - redirect
+```
+
+Note : the order of the plugins is not related to the order of execution .
+
+## 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
+ configuration data :
+
+```json
+ "key-auth" : {
+       "key" : "auth-one"
+  }
+```
+
+The configuration data of the plugin is relatively simple . Only one attribute named key is supported . Let's look
+at its schema description :
+
+```lua
+   local schema = {
+       type = "object",
+       properties = {
+           key = {type = "string"},
+       }
+   }
+```
+
+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
+```
+
+Note: the project has provided the public method "__core.schema.check__" , which can be used directly to complete JSON
+verification .
+
+## choose phase to run
+
+determine which phase to run , generally access or rewrite . if you don't know the Openresty life cycle , it's
+recommended to know it in advance . key-auth is an authentication plugin , as long as the authentication is completed
+before the business response after the request comes in . The plugin can be executed in the rewrite and access phases ,
+in the project, the authentication logic is implemented in the rewrite phase . Generally, IP access and interface
+permission are completed in the access phase .
+
+## implement the logic
+
+Write the logic of the plugin in the corresponding phase .
+
+## write test case
+
+for functions , write and improve the test cases of various dimensions , do a comprehensive test for your plugin ! The
+test cases of plugins are all in the "__t/plugin__" directory. You can go ahead to find out . the test framework
+[****test-nginx****](https://github.com/openresty/test-nginx)  adopted by the project. a test case, .t file is usually
+divided into prologue and data parts by \__data\__ . Here we will briefly introduce the data part, that is, the part
+of the real test case . For example, the key-auth plugin :
+
+```perl
+=== TEST 1: sanity
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.key-auth")
+            local ok, err = plugin.check_schema({key = 'test-key'})
+            if not ok then
+                ngx.say(err)
+            end
+
+            ngx.say("done")
+        }
+    }
+--- request
+GET /t
+--- response_body
+done
+--- no_error_log
+[error]
+```
+
+a test case consists of three parts :
+- __Program code__ : configuration content of Nginx location
+- __Input__ : http request information
+- __Output check__ : status, header, body, error log check
+
+when we request __/t__ , which config in the configuration file , the Nginx will call "__content_by_lua_block__" instruction to
+ complete the Lua script, and finally return. The assertion of the use case is response_body return "done",
+"__no_error_log__" means to check the "__error.log__" of Nginx. There must be no ERROR level record .
+
+### 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
+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 .