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 <souls...@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 .