kong插件官方文檔翻譯
目錄
- 介紹
- 文件結構
- 編寫自定義邏輯
- 存儲配置
- 訪問數據存儲
- 自定義實體
- 緩存自定義實體
- 擴展Admin API
- 編寫測試
- (卸載)安裝你的插件
插件開發 - 介紹
什么是插件,他們如何與kong集成?
在進一步之前,有必要簡要解釋一下如何構建,特別是它如何與Nginx集成,以及Lua與它有關。
lua-nginx-module模塊可以在Nginx中啟用Lua腳本功能。 Kong並沒有使用這個模塊編譯Nginx,而是與OpenResty一起發行,OpenResty已經包含了lua-nginx-module。OpenResty不是Nginx的分支,而是一系列擴展其功能的模塊。
因此,Kong是一個Lua應用程序,旨在加載和執行Lua模塊(我們更常稱之為“插件”),並為它們提供了一個完整的開發環境,包括數據庫抽象,遷移,幫助等等...
您的插件將由Lua模塊組成,將由Kong加載和執行,它將受益於兩個API:
- lua-nginx-module API:允許與Nginx本身進行交互,例如檢索請求/響應或訪問Nginx的共享內存區域。
- Kong的插件環境:允許與保存配置的數據存儲(API,消費者,插件...)和各種幫助器進行交互,從而允許插件之間的交互。這是本指南將要描述的環境。
注意:本指南假設您熟悉Lua和lua-nginx-module API,並且只會描述Kong的插件環境。
插件開發 - 文件結構
注意:本章假設您熟悉Lua。
將您的插件視為一組Lua模塊。本章中描述的每個文件都將被視為單獨的模塊。如果他們的名字符合這個約定,Kong會檢測並加載您的插件的模塊:
"kong.plugins.<plugin_name>.<module_name>"
您的模塊當然需要通過您的package.path變量來訪問,可以通過您的Nginx配置中的lua-package-path指令來調整您的需求。然而,安裝插件的首選方法是通過Luarocks。在本指南中的更多內容。
為了讓Kong知道它必須找到你的插件的模塊,你必須將它添加到你的配置文件中的custom_plugins
屬性中。例如:
custom_plugins:
- my-custom-plugin # your plugin name here
現在,Kong將嘗試加載本章所述的模塊。其中有些是強制性的,但不會被忽略,而Kong會認為你不會使用它。例如,Kong將加載“kong.plugins.my-custom-plugin.handler”
來檢索和執行插件的邏輯。
現在讓我們來描述你可以實現什么模塊以及它們的目的。
基本插件模塊
在最基本的形式中,一個插件由兩個必需的模塊組成:
simple-plugin
├── handler.lua
└── schema.lua
- handler.lua:你的插件的核心。它是一個實現的接口,其中每個功能將在請求的生命周期中的所需時刻運行。
- schema.lua:您的插件可能必須保留用戶輸入的一些配置。該模塊保存該配置的模式並定義其上的規則,使用戶只能輸入有效的配置值。
高級插件模塊
一些插件可能必須更深入地與Kong集成:在數據庫中擁有自己的表,在Admin API中公開端點等...每個都可以通過在你的插件中添加一個新的模塊來完成。這是一個插件的結構,如果它正在實現所有可選的模塊:
complete-plugin
├── api.lua
├── daos.lua
├── handler.lua
├── hooks.lua
├── migrations
│ ├── cassandra.lua
│ └── postgres.lua
└── schema.lua
以下是實現可能的模塊的完整列表,並簡要說明其目的。本指南將詳細介紹,讓您掌握他們每一個。
模塊名稱 | 需要 | 描述 |
---|---|---|
api.lua | No | 定義在Admin API中可用的端點列表,以便與您的插件處理的實體自定義實體進行交互。 |
daos.lua | No | 定義DAO(數據庫訪問對象)的列表,它是插件所需的存儲在數據存儲中的自定義實體的抽象。 |
handler.lua | Yes | 一個需要被實現的接口。每個功能將由Kong在請求的生命周期中的所需時刻運行。 |
migrations/*.lua | No | 給定數據存儲區的相應遷移。只有當您的插件必須將自定義實體存儲在數據庫中並通過daos.lua定義的DAO之一與之進行交互時,才需要進行遷移。 |
hooks.lua | No | 對daos.lua中定義的數據存儲實體實現無效事件處理程序。如果要將實體存儲在內存中的緩存中,以便在數據存儲上進行更新/刪除時使其無效,則為必需。 |
schema.lua | Yes | 保存插件配置的架構,以便用戶只能輸入有效的配置值。 |
插件開發 - 編寫自定義邏輯
模塊
"kong.plugins.<plugin_name>.handler"
注意:本章假設您熟悉Lua和lua-nginx-module API。
Kong允許您在請求的生命周期中的不同時間執行自定義代碼。為此,您必須實現base_plugin.lua
接口的一個或多個方法。這些方法將在一個模塊中實現:“kong.plugins”<plugin_name> .handler“
可用請求上下文
Kong允許您在所有lua-nginx模塊上下文中編寫代碼。當您的請求達到上下文時,將在執行的handler.lua
文件中執行每個函數:
函數名 | LUA-NGINX-MODULE 背景 | 描述 |
---|---|---|
:init_worker() |
init_worker_by_lua | 每個Nginx worker 進程啟動時執行。 |
:certificate() |
ssl_certificate_by_lua_block | 在SSL握手的SSL證書服務階段執行。 |
:rewrite() |
rewrite_by_lua_block | 在作為重寫階段處理程序從客戶端接收時針對每個請求執行。在這個階段,注意到api 和consumer 都沒有被識別出來,因此,如果插件被配置為全局插件,這個處理程序將被執行! |
:access() |
access_by_lua | 針對客戶端的每個請求執行,並在代理上游服務之前執行。 |
:header_filter() |
header_filter_by_lua | 從上游服務接收到所有響應頭字節時執行。 |
:body_filter() |
body_filter_by_lua | 從上游服務接收到的響應體的每個塊執行。由於響應被流回到客戶端,所以它可以超過緩沖區大小,並且通過塊被流傳輸塊。因此如果響應大,可以多次調用該方法。有關更多詳細信息,請參閱lua-nginx-module 文檔。 |
:log() |
log_by_lua | 最后一個響應字節發送到客戶端時被執行。 |
所有這些功能都采用Kong給出的一個參數:插件的配置。此參數是一個簡單的Lua表,並將包含您的用戶定義的值,根據您選擇的模式。更多的在下一章。
handler.lua規范
handler.lua
文件必須返回一個實現要執行的函數的表。為了簡潔起見,這里是一個注釋的示例模塊,實現所有可用的方法:
注意:Kong使用rxi / classic模塊來模擬Lua中的類,並簡化了繼承模式。
-- Extending the Base Plugin handler is optional, as there is no real
-- concept of interface in Lua, but the Base Plugin handler's methods
-- can be called from your child implementation and will print logs
-- in your `error.log` file (where all logs are printed).
local BasePlugin = require "kong.plugins.base_plugin"
local CustomHandler = BasePlugin:extend()
-- Your plugin handler's constructor. If you are extending the
-- Base Plugin handler, it's only role is to instanciate itself
-- with a name. The name is your plugin name as it will be printed in the logs.
function CustomHandler:new()
CustomHandler.super.new(self, "my-custom-plugin")
end
function CustomHandler:init_worker(config)
-- Eventually, execute the parent implementation
-- (will log that your plugin is entering this context)
CustomHandler.super.init_worker(self)
-- Implement any custom logic here
end
function CustomHandler:certificate(config)
-- Eventually, execute the parent implementation
-- (will log that your plugin is entering this context)
CustomHandler.super.certificate(self)
-- Implement any custom logic here
end
function CustomHandler:rewrite(config)
-- Eventually, execute the parent implementation
-- (will log that your plugin is entering this context)
CustomHandler.super.rewrite(self)
-- Implement any custom logic here
end
function CustomHandler:access(config)
-- Eventually, execute the parent implementation
-- (will log that your plugin is entering this context)
CustomHandler.super.access(self)
-- Implement any custom logic here
end
function CustomHandler:header_filter(config)
-- Eventually, execute the parent implementation
-- (will log that your plugin is entering this context)
CustomHandler.super.header_filter(self)
-- Implement any custom logic here
end
function CustomHandler:body_filter(config)
-- Eventually, execute the parent implementation
-- (will log that your plugin is entering this context)
CustomHandler.super.body_filter(self)
-- Implement any custom logic here
end
function CustomHandler:log(config)
-- Eventually, execute the parent implementation
-- (will log that your plugin is entering this context)
CustomHandler.super.log(self)
-- Implement any custom logic here
end
-- This module needs to return the created table, so that Kong
-- can execute those functions.
return CustomHandler
當然,您的插件本身的邏輯可以在另一個模塊中抽象出來,並從您的handler
模塊中調用。許多現有的插件在邏輯冗余時已經選擇了這種模式,但它完全是可選的:
local BasePlugin = require "kong.plugins.base_plugin"
-- The actual logic is implemented in those modules
local access = require "kong.plugins.my-custom-plugin.access"
local body_filter = require "kong.plugins.my-custom-plugin.body_filter"
local CustomHandler = BasePlugin:extend()
function CustomHandler:new()
CustomHandler.super.new(self, "my-custom-plugin")
end
function CustomHandler:access(config)
CustomHandler.super.access(self)
-- Execute any function from the module loaded in `access`,
-- for example, `execute()` and passing it the plugin's configuration.
access.execute(config)
end
function CustomHandler:body_filter(config)
CustomHandler.super.body_filter(self)
-- Execute any function from the module loaded in `body_filter`,
-- for example, `execute()` and passing it the plugin's configuration.
body_filter.execute(config)
end
return CustomHandler
插件執行順序
注意:這仍然是一個正在進行中的API。關於未來可以配置插件執行順序的想法,請參閱Mashape / kong#267。
一些插件可能取決於其他人執行某些操作的執行。例如,依賴於消費者身份的插件必須在驗證插件之后運行。考慮到這一點,Kong定義了插件執行之間的優先級,以確保執行順序能夠得到遵守。
您的插件的優先級可以通過在返回的處理程序表中接受一個數字的屬性進行配置:
CustomHandler.PRIORITY = 10
優先級越高,您的插件相對於其他插件的階段(例如:access()
,:log()
等)被執行的時間越早。當前的身份驗證插件的優先級為1000。
插件開發 - 存儲配置
模塊
"kong.plugins.<plugin_name>.schema"
大多數情況下,您的插件可以配置為滿足用戶的所有需求。您的插件的配置存儲在Kong的數據存儲區中,以檢索它,並在插件執行時將其傳遞給您的handler.lua方法。
配置由Kong中的Lua表組成,我們稱之為schema
。它包含通過Admin API啟用插件時用戶將設置的鍵/值屬性。Kong為您提供驗證插件的用戶配置的方法。
當用戶向Admin API發出請求以在給定的API and/or Consumer上啟用或更新插件時,您的插件的配置正在針對您的模式進行驗證。
例如,用戶執行以下請求:
$ curl -X POST http://kong:8001/apis/<api name>/plugins \
-d "name=my-custom-plugin" \
-d "config.foo=bar"
如果配置對象的所有屬性根據您的架構有效,那么API將返回201 Created
,並且該插件將與其配置({foo =“bar”}
)一起存儲在數據庫中。如果配置無效,Admin API將返回400 Bad Request
和相應的錯誤消息。
schema.lua規范
此模塊將返回一個具有屬性的Lua表,該屬性將定義用戶以后可以配置插件。可用屬性有:
屬性名稱 | LUA類型 | 默認值 | 描述 |
---|---|---|---|
no_consumer |
Boolean | false |
如果為真,則無法將此插件應用於特定的消費者。此插件必須僅適用於API范圍。例如:認證插件。 |
fields |
Table | {} |
你的插件的架構。一個可用屬性及其規則的鍵/值表 |
self_check |
Function | nil |
如果要在接受插件的配置之前執行任何自定義驗證,則實現該功能。 |
self_check
功能必須如下實現:
-- @param `schema` A table describing the schema (rules) of your plugin configuration.
-- @param `config` A key/value table of the current plugin's configuration.
-- @param `dao` An instance of the DAO (see DAO chapter).
-- @param `is_updating` A boolean indicating wether or not this check is performed in the context of an update.
-- @return `valid` A boolean indicating if the plugin's configuration is valid or not.
-- @return `error` A DAO error (see DAO chapter)
以下是一個潛在的schema.lua
文件示例:
return {
no_consumer = true, -- this plugin will only be API-wide,
fields = {
-- Describe your plugin's configuration's schema here.
},
self_check = function(schema, plugin_t, dao, is_updating)
-- perform any custom verification
return true
end
}
描述你的配置概要
schema.lua
文件的fields
屬性描述了插件配置的模式。它是一個靈活的鍵/值表,其中每個鍵將是您的插件的有效配置屬性,並且每個值都是描述該屬性的規則的表。例如:
fields = {
some_string = {type = "string", required = true},
some_boolean = {type = "boolean", default = false},
some_array = {type = "array", enum = {"GET", "POST", "PUT", "DELETE"}}
}
以下是屬性的接受規則列表:
規則 | LUA類型 | 允許的值 | 描述 |
---|---|---|---|
type |
string | "id", "number", "boolean", "string", "table", "array", "url", "timestamp" | 驗證屬性的類型。 |
required |
boolean | 默認值:false。如果為true,則屬性必須存在於配置中。 | |
unique |
boolean | 默認值:false。如果為true,則該值必須是唯一的(見下面的注釋)。 | |
default |
any | 如果配置中未指定該屬性,則將該屬性設置為給定值。 | |
immutable |
boolean | 默認值:false。如果為true,則在創建插件配置后,不允許更新該屬性。 | |
enum |
table | 整數索引表 | 屬性的接受值列表。此列表中未包含的任何值將不被接受。 |
regex |
string | 有效的PCRE正則表達式 | 一個用於驗證該屬性值的正則表達式。 |
schema |
table | 嵌套模式定義 | 如果屬性的類型是表,則定義要驗證這些子屬性的模式。 |
func |
function | 對屬性執行任何自定義驗證的功能。有關其參數和返回值,請參閱后面的示例。 |
- type: 將轉換從請求參數中檢索的值。如果該類型不是本地Lua類型之一,那么會對其執行自定義驗證:
- id: 必須是一個字符串
- timestamp: 必須是數字
- url: 必須是有效的網址
- array: 必須是一個整數索引表(相當於Lua中的數組)。在Admin API中,這樣的數組可以通過在請求的正文中具有不同值的屬性的鍵多次發送,或者通過單個主體參數以逗號分隔。
- unique: 該屬性對於插件配置沒有意義,但是當插件需要在數據存儲中存儲自定義實體時使用該屬性。
- schema: 如果需要對嵌套屬性進行深化驗證,則此字段允許您創建嵌套模式。模式驗證是遞歸的。任何級別的嵌套都是有效的,但請注意,這將影響插件的可用性。
- 附加到配置對象但不存在於schema中的任何屬性也將使所述配置無效。
舉例:
key-auth
插件的schema.lua
文件定義了API密鑰的接受參數名稱的默認列表,以及默認設置為false的布爾值:
-- schema.lua
return {
no_consumer = true,
fields = {
key_names = {type = "array", required = true, default = {"apikey"}},
hide_credentials = {type = "boolean", default = false}
}
}
因此,當在handler.lua
中實現插件的access()
函數時,並且給予用戶啟用了默認值的插件,您可以訪問:
-- handler.lua
local BasePlugin = require "kong.plugins.base_plugin"
local CustomHandler = BasePlugin:extend()
function CustomHandler:new()
CustomHandler.super.new(self, "my-custom-plugin")
end
function CustomHandler:access(config)
CustomHandler.super.access(self)
print(config.key_names) -- {"apikey"}
print(config.hide_credentials) -- false
end
return CustomHandler
一個更復雜的例子,可用於最終的日志插件:
-- schema.lua
local function server_port(given_value, given_config)
-- Custom validation
if given_value > 65534 then
return false, "port value too high"
end
-- If environment is "development", 8080 will be the default port
if given_config.environment == "development" then
return true, nil, {port = 8080}
end
end
return {
fields = {
environment = {type = "string", required = true, enum = {"production", "development"}}
server = {
type = "table",
schema = {
host = {type = "url", default = "http://example.com"},
port = {type = "number", func = server_port, default = 80}
}
}
}
}
這樣的配置將允許用戶將配置發布到您的插件,如下所示:
$ curl -X POST http://kong:8001/apis/<api name>/plugins \
-d "name=<my-custom-plugin>" \
-d "config.environment=development" \
-d "config.server.host=http://localhost"
以下將在handler.lua中可用:
-- handler.lua
local BasePlugin = require "kong.plugins.base_plugin"
local CustomHandler = BasePlugin:extend()
function CustomHandler:new()
CustomHandler.super.new(self, "my-custom-plugin")
end
function CustomHandler:access(config)
CustomHandler.super.access(self)
print(config.environment) -- "development"
print(config.server.host) -- "http://localhost"
print(config.server.port) -- 8080
end
return CustomHandler
插件開發 - 訪問數據存儲
Kong通過我們稱之為“DAO”的類與模型層交互。本章將詳細介紹與數據存儲區進行交互的可用API。
從0.8.0開始,Kong支持兩個主要數據存儲:Cassandra 3.x.x和PostgreSQL 9.4+。
DAO工廠
Kong的所有實體都以下列方式表示:
- 描述實體在數據存儲中涉及哪個表的模式,其字段上的約束,如外鍵,非空約束等...此模式是插件配置一章中描述的表。
- 映射到當前使用的數據庫(Cassandra或PostgreSQL)的
DAO
類的實例。該類的方法使用模式並公開方法來插入,更新,查找和刪除該類型的實體。
Kong的核心實體是:Apis,Consumers and Plugins。這些實體中的每一個都可以通過其相應的DAO實例進行交互,通過DAO Factory實例可以實現。DAO工廠負責加載這些核心實體的DAO以及任何其他實體,例如通過插件提供。
DAO工廠是Kong的singleton instance(單例接口),因此可以通過singletons
模塊訪問:
local singletons = require "kong.singletons"
-- Core DAOs
local apis_dao = singletons.dao.apis
local consumers_dao = singletons.dao.consumers
local plugins_dao = singletons.dao.plugins
The DAO Lua API
DAO類負責在數據存儲區中的給定表上執行的操作,通常映射到Kong中的實體。所有底層支持的數據庫(目前為Cassandra和PostgreSQL)都遵循相同的接口,從而使DAO與所有數據庫兼容。
例如,插入一個API就像:
local singletons = require "kong.singletons"
local dao = singletons.dao
local inserted_api, err = dao.apis:insert({
name = "mockbin",
hosts = { "mockbin.com" },
upstream_url = "http://mockbin.com"
})
插件開發 - 自定義實體
模塊
"kong.plugins.<plugin_name>.schema.migrations"
"kong.plugins.<plugin_name>.daos"
您的插件可能需要存儲比在數據庫中存儲的配置更多的內容。在這種情況下,Kong可以在主數據存儲之上提供抽象,可以讓您存儲自定義實體。
如前一章所述,Kong通過我們稱之為“DAO”的classes與模型層相互作用,通常被稱為“DAO工廠”。本章將介紹如何為您的實體提供抽象。
創建一個migration文件
一旦定義了您的模型,您必須創建遷移模塊,這些模塊將由Kong執行,以創建您的實體的記錄將被存儲在其中的表。遷移文件簡單地保存遷移數組,並返回它們。
由於Kong 0.8.0
,Cassandra和PostgreSQL都支持,這要求您的插件實現其兩個數據庫的遷移。
每個遷移必須具有唯一的名稱,以及up
,down
字段。這樣的字段可以是用於簡單遷移的SQL / CQL查詢的字符串,也可以是復雜的Lua代碼。當Kong向前移動時,up
字段將被執行。它必須使您的數據庫的架構達到插件所需的最新狀態。 down
字段必須執行必要的操作才能將模式恢復到之前的狀態,在運行up
之前。
這種方法的主要優點之一是,如果您需要發布修改模型的新版本的插件,則可以在發布插件之前,將新的遷移添加到數組中。另一個好處是還可以恢復這種遷移。
如文件結構章節所述,遷移模塊必須命名為:
"kong.plugins.<plugin_name>.migrations.cassandra"
"kong.plugins.<plugin_name>.migrations.postgres"
以下是如何定義遷移文件以存儲API密鑰的示例:
-- cassandra.lua
return {
{
name = "2015-07-31-172400_init_keyauth",
up = [[
CREATE TABLE IF NOT EXISTS keyauth_credentials(
id uuid,
consumer_id uuid,
key text,
created_at timestamp,
PRIMARY KEY (id)
);
CREATE INDEX IF NOT EXISTS ON keyauth_credentials(key);
CREATE INDEX IF NOT EXISTS keyauth_consumer_id ON keyauth_credentials(consumer_id);
]],
down = [[
DROP TABLE keyauth_credentials;
]]
}
}
-- postgres.lua
return {
{
name = "2015-07-31-172400_init_keyauth",
up = [[
CREATE TABLE IF NOT EXISTS keyauth_credentials(
id uuid,
consumer_id uuid REFERENCES consumers (id) ON DELETE CASCADE,
key text UNIQUE,
created_at timestamp without time zone default (CURRENT_TIMESTAMP(0) at time zone 'utc'),
PRIMARY KEY (id)
);
DO $$
BEGIN
IF (SELECT to_regclass('public.keyauth_key_idx')) IS NULL THEN
CREATE INDEX keyauth_key_idx ON keyauth_credentials(key);
END IF;
IF (SELECT to_regclass('public.keyauth_consumer_idx')) IS NULL THEN
CREATE INDEX keyauth_consumer_idx ON keyauth_credentials(consumer_id);
END IF;
END$$;
]],
down = [[
DROP TABLE keyauth_credentials;
]]
}
}
- name: 必須是唯一的字符串。格式並不重要,但可以幫助您在開發插件時調試問題,因此請務必以相關方式命名。
- up: 當kong遷移時執行。
- down: 當kong回滾時執行。
雖然Postgres,Cassandra不支持“NOT NULL”,“UNIQUE”或“FOREIGN KEY”等約束,但是在定義模型的架構時,Kong可以提供這些功能。請記住,對於PostgreSQL和Cassandra,此模式將是相同的,因此,您可能會為與Cassandra一起使用的純SQL模式進行權衡。
重要信息:如果您的架構使用唯一的約束,那么對於Cassandra,Kong將強制執行,但對於Postgres,您必須在migrations文件中設置此約束。
從DAO工廠檢索您的定制DAO
要使DAO工廠加載您的自定義DAO,您只需要定義實體的模式(就像描述插件配置的模式)。此模式包含更多值,因為它必須描述實體在數據存儲中涉及的表,其字段上的約束,如外鍵,非空約束等。
該schema將在名為:
"kong.plugins.<plugin_name>.daos"
一旦該模塊返回您的實體模式,並假設您的插件由Kong加載(請參閱kong.yml
中的custom_plugins
屬性),DAO工廠將使用它來實現DAO對象。
以下是一個示例,說明如何定義模式以將API密鑰存儲在他或她的數據庫中:
-- daos.lua
local SCHEMA = {
primary_key = {"id"},
table = "keyauth_credentials", -- the actual table in the database
fields = {
id = {type = "id", dao_insert_value = true}, -- a value to be inserted by the DAO itself (think of serial ID and the uniqueness of such required here)
created_at = {type = "timestamp", immutable = true, dao_insert_value = true}, -- also interted by the DAO itself
consumer_id = {type = "id", required = true, foreign = "consumers:id"}, -- a foreign key to a Consumer's id
key = {type = "string", required = false, unique = true} -- a unique API key
}
}
return {keyauth_credentials = SCHEMA} -- this plugin only results in one custom DAO, named `keyauth_credentials`
由於您的插件可能需要處理多個自定義DAO(在要存儲多個實體的情況下),該模塊必須返回一個鍵/值表,其中鍵是DAO工廠中自定義DAO可用的名稱。
您將在模式定義中注意到一些新屬性(與schema.lua文件相比):
屬性名稱 | LUA類型 | 描述 |
---|---|---|
primary_key |
整數索引表 | 您列系列主鍵的每個部分的一個數組。它還支持復合密鑰,即使所有Kong實體當前使用簡單的id 來管理API的可用性。如果你的主鍵是復合的,那么只包括您的分區鍵。 |
fields.*.dao_insert_value |
Boolean | 如果為true,則指定此字段將由DAO自動填充(在base_dao實現中),具體取決於其類型。類型id 的屬性將是生成的uuid,並且timestamp 具有第二精度的時間戳。 |
fields.*.queryable |
Boolean | 如果為true,則指定Cassandra在指定列上維護索引。這允許查詢此列過濾的列集合字段。 |
fields.*.foreign |
String | 指定此列是另一個實體的列的外鍵。格式為:dao_name:column_name 。這使得Cassandra不支持外鍵。當父行將被刪除時,Kong還將刪除包含父列的列值的行。 |
您的DAO現在將由DAO工廠加載,並作為其屬性之一提供:
local singletons = require "kong.singletons"
local dao_factory = singletons.dao
local keys_dao = dao_factory.keyauth_credentials
local key_credential, err = keys_dao:insert({
consumer_id = consumer.id,
key = "abcd"
}
可以從DAO工廠訪問的DAO名稱(keyauth_credentials
)取決於在daos.lua
的返回表中導出DAO的鍵。
緩存自定義實體
有時每個請求/響應都需要自定義實體,這反過來又會觸發每次數據存儲上的查詢。這是非常低效的,因為查詢數據存儲會增加延遲並降低請求/響應速度,並導致數據存儲區上的負載增加可能會影響數據存儲的性能本身,也可能影響其他Kong節點。
當每個請求/響應都需要一個自定義實體時,通過利用Kong提供的內存中緩存API來緩存內存是個好習慣。
下一章將專注於緩存自定義實體,並在數據存儲區中更改時使其無效:緩存自定義實體。
插件開發 - 緩存自定義實體
模塊
"kong.plugins.<plugin_name>.daos"
"kong.plugins.<plugin_name>.hooks"
您的插件可能需要經常訪問每個請求和/或響應的自定義實體(在上一章中介紹)。通常加載它們一次,並將它們緩存在內存中,可以顯着提高性能,同時確保數據存儲不受負載增加的壓力。
想想一個需要在每個請求上驗證api密鑰的api密鑰驗證插件,從而在每個請求上從數據存儲區加載自定義憑證對象。當客戶端與請求一起提供api密鑰時,通常會查詢數據存儲區以檢查該密鑰是否存在,然后阻止請求或檢索用戶ID以標識用戶。這將在每個請求上發生,這將是非常低效的:
- 查詢數據存儲區會增加每個請求的延遲,從而使請求處理速度更慢。
- 數據存儲還將受到負載增加的影響,可能會導致數據存儲崩潰或減慢,這又會影響每個Kong節點。
為了避免每次查詢數據存儲區,我們都可以在節點上緩存自定義實體內存,以便頻繁的實體查找不會每次(僅第一次)觸發數據存儲查詢,但是在內存中發生,從數據存儲區(特別是在重負載情況下)查詢更快,更可靠。
注意:當緩存內存中的自定義實體時,您還需要提供一個無效機制,在“hooks.lua”文件中實現。
緩存自定義實體
一旦您定義了自定義實體,就可以通過要求database_cache
依賴關系在代碼中緩存內存:
local cache = require "kong.tools.database_cache"
有兩個級別的緩存:
- Lua內存緩存(本地到nginx worker)這可以保存任何類型的Lua值。
- 共享內存緩存 - SHM(本地到nginx節點,但在所有workers之間共享)這只能保存標量值,因此需要(反)序列化。
當從數據庫中獲取數據時,它將被存儲在兩個緩存中。現在如果同一個工作進程再次請求數據,它將從Lua內存緩存中檢索先前反序列化的數據。如果同一個nginx節點內的一個不同的worker請求該數據,它會在SHM中找到數據,並將其反序列化(並將其存儲在自己的Lua內存緩存中),然后返回。
該模塊公開了以下功能:
函數名 | 描述 |
---|---|
ok, err = cache.set(key, value, ttl) |
使用指定的鍵將Lua對象存儲到內存中緩存(可選ttl以秒為單位)。該值可以是任何Lua類型,包括表。返回true或false,如果操作失敗,則返回err。 |
value = cache.get(key) |
檢索存儲在特定鍵中的Lua對象。 |
cache.delete(key) |
刪除存儲在指定鍵的緩存對象。 |
ok, err = cache.sh_add(key, value, ttl) |
將新值添加到SHM緩存中,可選ttl(以秒為單位)(如果nil不會過期) |
ok, err = cache.sh_set(key, value, ttl) |
在SHM中的指定鍵下設置一個新值,可選ttl(以秒為單位)(如果nil不會過期) |
value = cache.sh_get(key) |
返回存儲在SHM下的值,如果沒有找到,則返回nil |
cache.sh_delete(key) |
從SHMs刪除指定key的值 |
newvalue, err = cache.sh_incr(key, amount) |
在指定的鍵下增加存儲在SHM中的數量,以指定的單位數量。該數字需要已經存在於緩存中,否則將返回錯誤。如果成功,則返回新增值,否則返回錯誤。 |
value, ... = cache.get_or_set(key, ttl, function, ...) |
這是一個使用指定鍵檢索對象的實用方法,但如果對象為nil,則將執行傳遞的函數,而返回值將用於將對象存儲在指定的鍵。這有效地確保該對象僅從數據存儲區一次加載,因為每次其他調用將從內存中緩存加載對象。 |
返回我們的認證插件示例,要使用特定的api密鑰查找憑據,我們將會寫下如下:
-- access.lua
local function load_entity_key(api_key)
-- IMPORTANT: the callback is executed inside a lock, hence we cannot terminate
-- a request here, we MUST always return.
local apikeys, err = dao.apikeys:find_by_keys({key = api_key}) -- Lookup in the datastore
if err then
return nil, err -- errors must be returned, not dealt with here
end
if not apikeys then
return nil -- nothing was found
end
-- assuming the key was unique, we always only have 1 value...
return apikeys[1] -- Return the credential (this will be also stored in-memory)
end
local credential
-- Retrieve the apikey from the request querystring
local apikey = request.get_uri_args().apikey
if apikey then -- If the apikey has been passed, we can check if it exists
-- We are using cache.get_or_set to first check if the apikey has been already stored
-- into the in-memory cache at the key: "apikeys."..apikey
-- If it's not, then we lookup the datastore and return the credential object. Internally
-- cache.get_or_set will save the value in-memory, and then return the credential.
credential, err = cache.get_or_set("apikeys."..apikey, nil, load_entity_key, apikey)
if err then
-- here we can deal with the error returned by the callback
return response.HTTP_INTERNAL_SERVER_ERROR(err)
end
end
if not credential then -- If the credential couldn't be found, show an error message
return responses.send_HTTP_FORBIDDEN("Invalid authentication credentials")
end
通過這樣做,不用擔心客戶端使用該特定api密鑰發送多少請求,在第一個請求之后,每次查找將在內存中完成,而不查詢數據存儲區。
更新或刪除自定義實體
每次在數據存儲上更新或刪除緩存的自定義實體時,例如使用Admin API,它會在數據存儲區中的數據與緩存在內存中的數據存儲區之間產生矛盾。為了避免這種不一致,我們需要從內存存儲器中刪除緩存的實體,並強制要求從數據存儲區再次請求。為了這樣做,我們必須實現一個無效的鈎子。
使自定義實體無效
每當在數據存儲區中創建/更新/刪除實體時,Kong會通知所有節點上的數據存儲區操作,告知執行了哪個命令以及哪個實體受到影響。這發生在APIs, Plugins 和 Consumers,也適用於自定義實體。
由於這種行為,我們可以通過適當的操作來監聽這些事件和響應,以便在數據存儲區中修改緩存的實體時,我們可以將其從緩存中顯式刪除,以避免數據存儲和緩存本身之間的狀態不一致。從內存的緩存中刪除它將觸發系統再次查詢數據存儲區,並重新緩存實體。
kong傳播的事件是:
事件名稱 | 描述 |
---|---|
ENTITY_CREATED |
當任何實體被創建時。 |
ENTITY_UPDATED |
任何實體正在更新時。 |
ENTITY_DELETED |
任何實體被刪除時。 |
為了偵聽這些事件,我們需要實現hooks.lua
文件並使用我們的插件進行分發,例如:
-- hooks.lua
local events = require "kong.core.events"
local cache = require "kong.tools.database_cache"
local function invalidate_on_update(message_t)
if message_t.collection == "apikeys" then
cache.delete("apikeys."..message_t.old_entity.apikey)
end
end
local function invalidate_on_create(message_t)
if message_t.collection == "apikeys" then
cache.delete("apikeys."..message_t.entity.apikey)
end
end
return {
[events.TYPES.ENTITY_UPDATED] = function(message_t)
invalidate_on_update(message_t)
end,
[events.TYPES.ENTITY_DELETED] = function(message_t)
invalidate_on_create(message_t)
end
}
在上面的示例中,插件正在偵聽ENTITY_UPDATED
和ENTITY_DELETED
事件,並通過調用適當的函數進行響應。 message_t
表包含事件屬性:
屬性名稱 | 類型 | 描述 |
---|---|---|
collection |
String | 資料儲存庫中的集合受到操作的影響。 |
entity |
Table | 最近更新的實體,或刪除或創建的實體。 |
old_entity |
Table | 僅適用於更新事件,舊版本的實體。 |
在entity
和old_entity
屬性中傳輸的實體不具有在模式中定義的所有字段,而只包含一個子集。這是必需的,因為每個事件都是以一個有效載荷大小限制為512字節的UDP數據包發送的。該子集由模式中的marshall_event
函數返回,您可以選擇實現。
marshall_event
此函數將自定義實體序列化到最小版本,僅包含稍后需要在hooks.lua
中使用的字段。如果marshall_event
未被實現,默認情況下,Kong不發送任何實體字段值以及事件。
例如:
-- daos.lua
local SCHEMA = {
primary_key = {"id"},
-- clustering_key = {}, -- none for this entity
fields = {
id = {type = "id", dao_insert_value = true},
created_at = {type = "timestamp", dao_insert_value = true},
consumer_id = {type = "id", required = true, queryable = true, foreign = "consumers:id"},
apikey = {type = "string", required = false, unique = true, queryable = true}
},
marshall_event = function(self, t) -- This is related to the invalidation hook
return { id = t.id, consumer_id = t.consumer_id, apikey = t.apikey }
end
}
在上面的示例中,自定義實體提供了一個marshall_event
函數,它返回一個具有id
,consumer_id
和apikey
字段的對象。在我們的鈎子中,我們不需要creation_date
來使實體無效,所以我們不在乎在事件中傳播它。參數中的t
表是其所有字段的原始對象。
注意:正在返回的Lua表的JSON序列化不能超過512個字節,以便將整個事件放於一個UDP數據包。不符合這種約束將會阻止無效宣傳事件的傳播,從而造成節點間的數據不一致。
擴展Admin API
您可能知道,Admin API是Kong用戶與Kong進行溝通以設置其API和插件的地方。它們可能還需要與您為插件實現的自定義實體進行交互(例如,創建和刪除API密鑰)。您將要做的事情是擴展Admin API,我們將在下一章中詳細介紹:擴展Admin API。
插件開發 - 擴展Admin API
模塊
"kong.plugins.<plugin_name>.api"
Admin API是用戶配置Kong的接口。如果您的插件具有自定義實體或管理要求,則需要擴展Admin API。這允許您公開您自己的端點並實現您自己的管理邏輯。其中一個典型的例子是API密鑰的創建,檢索和刪除(通常稱為“CRUD操作”)。
Admin API是一個Lapis應用程序,Kong的抽象級別使您可以輕松添加端點。
注意:本章假定您具有Lapis的相關知識。
將端點添加到Admin API
如果endpoinds按照如下的模塊中定義,Kong將檢測並加載endpoints。
"kong.plugins.<plugin_name>.api"
該模塊必須返回一個包含描述路由的字符串的表(請參閱Lapis路由和URL模式)和它們支持的HTTP動詞。然后路由被分配一個簡單的處理函數。
然后將此表提供給Lapis(請參閱Lapis處理HTTP動詞文檔)。例:
return {
["/my-plugin/new/get/endpoint"] = {
GET = function(self, dao_factory, helpers)
-- ...
end
}
}
處理函數有三個參數,它們按順序排列:
self
: 請求對象。請參閱Lapis請求對象dao_factory
: DAO工廠。請參閱本指南的數據存儲區一章。helpers
: 一個包含幾個助手的表,如下所述。
除了支持的HTTPS動詞之外,路由表還可以包含兩個其他密鑰:
- before: 在Lapis中,在執行的動詞動作之前運行before_filter。
- on_error: 一個自定義的錯誤處理函數,它覆蓋了由Kong提供的函數。請參閱Lapis的捕獲可恢復錯誤文檔。
Helpers
在Admin API處理請求時,有時您希望發回響應並處理錯誤,以幫助您這樣做,第三個參數helpers
是具有以下屬性的表:
responses
: 具有幫助函數的模塊發送HTTP響應。yield_error
: 來自Lapis的yield_error函數。當你的處理程序遇到錯誤(例如從DAO)時調用。由於所有Kong錯誤都是具有上下文的表,因此可以根據錯誤(內部服務器錯誤,錯誤請求等)發送適當的響應代碼。
crud_helpers
由於您將在您的端點執行的大多數操作將是CRUD操作,您還可以使用kong.api.crud_helpers
模塊。此模塊為您提供任何插入,搜索,更新或刪除操作的幫助程序,並執行必要的DAO操作並使用適當的HTTP狀態代碼進行回復。它還為您提供了從路徑中檢索參數的功能,例如API的名稱或ID,或Consumer的用戶名或ID。
舉例:
local crud = require "kong.api.crud_helpers"
return {
["/consumers/:username_or_id/key-auth/"] = {
before = function(self, dao_factory, helpers)
crud.find_consumer_by_username_or_id(self, dao_factory, helpers)
self.params.consumer_id = self.consumer.id
end,
GET = function(self, dao_factory, helpers)
crud.paginated_set(self, dao_factory.keyauth_credentials)
end,
PUT = function(self, dao_factory)
crud.put(self.params, dao_factory.keyauth_credentials)
end,
POST = function(self, dao_factory)
crud.post(self.params, dao_factory.keyauth_credentials)
end
}
}
插件開發 - 編寫測試
如果你對你的插件負責,你可能想為它編寫測試。單元測試Lua很簡單,並且有許多測試框架可用。但是,您也可能需要編寫集成測試。再次,kong能夠給你提供支援。
編寫集成測試
Kong的首選測試框架busted通過resty-cli翻譯運行,盡管如果願意,您可以自由使用另一個。在Kong存儲庫中,可以在bin / busted
找到busted
的可執行文件。
Kong在您的測試套件中為您提供了一個幫助程序來啟動和停止Lua:spec.helpers
。此助手還提供了在運行測試之前在數據存儲區中插入fixtures的方法,以及刪除,以及各種其他helper。
如果您在自己的存儲庫中編寫插件,則需要復制以下文件,直到Kong測試框架被釋放:
bin/busted
: busted 的可執行文件與resty-cli解釋器一起運行spec/helpers.lua
: Kong的helper函數 啟動/關閉 bustedspec/kong_tests.conf
: 一個用於使用helpers模塊運行test Kong實例的配置文件
假設spec.helpers
模塊在您的LUA_PATH
中可用,您可以使用以下busted
的Lua代碼來啟動和停止Kong:
local helpers = require "spec.helpers"
describe("my plugin", function()
local proxy_client
local admin_client
setup(function()
assert(helpers.dao.apis:insert {
name = "test-api",
hosts = "test.com",
upstream_url = "http://httpbin.org"
})
-- start Kong with your testing Kong configuration (defined in "spec.helpers")
assert(helpers.start_kong())
admin_client = helpers.admin_client(timeout?)
end)
teardown(function()
if admin_client then
admin_client:close()
end
helpers.stop_kong()
end)
before_each(function()
proxy_client = helpers.proxy_client(timeout?)
end)
after_each(function()
if proxy_client then
proxy_client:close()
end
end)
describe("thing", function()
it("should do thing", function()
-- send requests through Kong
local res = assert(proxy_client:send {
method = "GET",
path = "/get",
headers = {
["Host"] = "test.com"
}
})
local body = assert.res_status(200, res)
-- body is a string containing the response
end)
end)
end)
提醒:通過test Kong配置文件,Kong運行在代理監聽端口8100和端口8101上的Admin API。
插件開發 - (卸載)安裝你的插件
Kong的自定義插件由Lua源文件組成,需要位於每個Kong節點的文件系統中。本指南將為您提供有助於使Kong節點了解您的自定義插件的分步說明。
這些步驟應該應用到您的Kong集群中的每個節點,以確保自定義插件在每個節點上都可用。
目錄
- Packaging sources
- Installing the plugin
- Load the plugin
- Verify loading the plugin
- Removing a plugin
- Distribute your plugin
- Troubleshooting
Packaging sources
您可以使用常規打包策略(例如tar
),也可以使用LuaRocks包管理器為您執行。我們建議您使用LuaRocks,因為它與Kong一起使用官方發行包之一。
使用LuaRocks時,您必須創建一個指定軟件包內容的rockspec
文件。有關示例,請參閱Kong插件模板,有關格式的更多信息,請參閱Rockspecs上的LuaRocks文檔。
使用以下命令(從插件repo)打包你的項目:
# install it locally (based on the `.rockspec` in the current directory)
$ luarocks make
# pack the installed rock
$ luarocks pack <plugin-name> <version>
假設你的插件rockspec被稱為kong-plugin-myPlugin-0.1.0-1.rockspec
,以上將成為如下;
$ luarocks pack kong-plugin-myPlugin 0.1.0-1
LuaRocks pack
命令現在已經創建了一個.rock
文件(這只是一個zip文件,其中包含安裝包所需的一切)。
如果您不使用或不能使用LuaRocks,則使用tar
將您的插件所在的.lua
文件打包到.tar.gz
存檔中。如果目標系統上有LuaRocks,還可以包括.rockspec
文件。
此存檔的內容應接近於以下內容:
$ tree <plugin-name>
<plugin-name>
├── INSTALL.txt
├── README.md
├── kong
│ └── plugins
│ └── <plugin-name>
│ ├── handler.lua
│ └── schema.lua
└── <plugin-name>-<version>.rockspec
Installing the plugin
要使Kong節點能夠使用自定義插件,必須在主機的文件系統上安裝自定義插件的Lua源。有多種方法:通過LuaRocks,或手動。選擇一個,然后跳轉到第3節。
1.通過LuaRocks從創建的“rock”安裝
.rock
文件是一個自包含的軟件包,可以在本地安裝或從遠程服務器安裝。
如果您的系統中安裝了luarocks
實用程序(如果您使用其中一個官方安裝包,則可能會出現此情況),則可以在LuaRocks樹(LuaRocks安裝Lua模塊的目錄)中安裝“rock”。
可以通過以下方式進行安裝:
$ luarocks install <rock-filename>
文件名可以是本地名稱,也可以是任何支持的方法,例如。
http://myrepository.lan/rocks/myplugin-0.1.0-1.all.rock
2.通過LuaRocks從源安裝
如果您的系統中安裝了luarocks
實用程序(如果您使用其中一個官方安裝軟件包,則可能是這種情況),則可以在LuaRocks樹(LuaRocks安裝Lua模塊的目錄)中安裝Lua源。
您可以通過將當前目錄更改為提取的存檔,其中rockspec文件位於:
$ cd <plugin-name>
然后運行以下命令:
luarocks make
這將在系統的LuaRocks樹中的kong / plugins / <plugin-name>
中安裝Lua源,其中所有Kong源都已存在。
3.手動安裝
安裝插件源代碼的更加保守的方法是避免“污染”LuaRocks樹,而是將Kong指向包含它們的目錄。
這通過調整您的Kong配置的lua_package_path
屬性來完成。在引擎蓋下,如果您熟悉該屬性,則該屬性是Lua VM的LUA_PATH
變量的別名。
這些屬性包含用於搜索Lua源的目錄的分號分隔列表。您的Kong配置文件應該如此設置:
lua_package_path = /<path-to-plugin-location>/?.lua;;
where:
* `/<path-to-plugin-location>` is the path to the directory containing the
extracted archive. It should be the location of the `kong` directory
from the archive.
* `?` is a placeholder that will be replaced by
`kong.plugins.<plugin-name>` when Kong will try to load your plugin. Do
not change it.
* `;;` a placeholder for the "the default Lua path". Do not change it.
Example:
The plugin `something` being located on the file system such that the
handler file is:
/usr/local/custom/kong/plugins/<something>/handler.lua
The location of the `kong` directory is: `/usr/local/custom`, hence the
proper path setup would be:
lua_package_path = /usr/local/custom/?.lua;;
Multiple plugins:
If you wish to install two or more custom plugins this way, you can set
the variable to something like:
lua_package_path = /path/to/plugin1/?.lua;/path/to/plugin2/?.lua;;
* `;` is the separator between directories.
* `;;` still means "the default Lua path".
Note: you can also set this property via its environment variable
equivalent: `KONG_LUA_PACKAGE_PATH`.
提醒:無論您使用哪種方法來安裝插件的源,您仍然必須對Kong群集中的每個節點執行此操作。
Load the plugin
您現在必須將自定義插件的名稱添加到Kong配置(每個Kong節點)的custom_plugins列表中:
custom_plugins = <plugin-name>
如果您使用兩個或多個自定義插件,請在其間插入逗號,如下所示:
custom_plugins = plugin1,plugin2
注意:您還可以通過其環境變量等效設置此屬性:KONG_CUSTOM_PLUGINS
。 提醒:不要忘記更新您的Kong群集中每個節點的custom_plugins
指令。
Verify loading the plugin
你現在應該可以沒有任何問題的開始了。請參閱您的自定義插件的說明,了解如何在API或Consumer對象上啟用/配置插件。
要確保您的插件由Kong加載,您可以使用調試日志級別啟動Kong:
log_level = debug
或者:
KONG_LOG_LEVEL=debug
然后,您將看到正在加載的每個插件的以下日志:
[debug] Loading plugin <plugin-name>
Removing a plugin
完全刪除插件有三個步驟。
- 從Kong api配置中刪除該插件。確保它不再適用於全局或任何API或消費者。對於整個群集,這只能執行一次,不需要重新啟動/重新加載。這一步本身將使插件不再使用。但是它仍然可用,並且仍然可以重新應用插件。
- 通過
custom_plugins
指令(每個Kong節點)刪除該插件。在這一步之前請確保步驟1已經執行。在這一步之后,任何人都不可能將插件重新應用於任何Kong API,消費者甚至全局。此步驟需要重新啟動/重新加載Kong節點才能生效。 - 要徹底刪除插件,請從每個Kong節點刪除與插件相關的文件。在刪除文件之前,請務必完成第2步,包括重新啟動/重新加載Kong。如果使用LuaRocks安裝插件,您可以使用
luarocks remove <plugin-name>
將其刪除。
Distribute your plugin
這樣做的首選方法是使用Luarocks,一個Lua模塊的軟件包管理器。它稱之為“rocks”模塊。您的模塊不必住在Kong存儲庫中,但是如果您想保持您的Kong設置,它將可以存在kong 存儲庫中。
通過在rockspec文件中定義模塊(及其最終依賴關系),您可以通過Luarocks在平台上安裝這些模塊。您還可以將您的模塊上傳到Luarocks,並將其提供給所有人!
這里是一個使用“內置”構建類型定義Lua符號及其相應文件中的模塊的rockspec示例:
有關示例,請參閱Kong插件模板,有關格式的更多信息,請參閱Rockspecs上的LuaRocks文檔。
Troubleshooting
由於幾個原因,由於配置錯誤的自定義插件,Kong可能無法啟動:
- “plugin is in use but not enabled” -->您從另一個節點配置了一個自定義插件,並且該插件配置位於數據庫中,但您嘗試啟動的當前節點在其
custom_plugins
指令中沒有。要解決,請將插件的名稱添加到節點的custom_plugins
指令中。 - "plugin is enabled but not installed" -->該插件的名稱存在於
custom_plugins
指令中,但是Kong無法從文件系統加載handler.lua
源文件。要解決,請確保lua_package_path
指令正確設置為加載此插件的Lua源。 - "no configuration schema found for plugin" -->該插件已安裝,已在
custom_plugins
中啟用,但Kong無法從文件系統加載schema.lua
源文件。要解決,請確保schema.lua
文件與插件的handler.lua
文件一起存在。