在進一步以前,有必要簡要解釋一下如何構建,特別是它如何與Nginx集成,以及Lua與它有關。html
lua-nginx-module模塊能夠在Nginx中啓用Lua腳本功能。 Kong並無使用這個模塊編譯Nginx,而是與OpenResty一塊兒發行,OpenResty已經包含了lua-nginx-module。OpenResty不是Nginx的分支,而是一系列擴展其功能的模塊。nginx
所以,Kong是一個Lua應用程序,旨在加載和執行Lua模塊(咱們更常稱之爲「插件」),併爲它們提供了一個完整的開發環境,包括數據庫抽象,遷移,幫助等等...git
您的插件將由Lua模塊組成,將由Kong加載和執行,它將受益於兩個API:github
注意:本指南假設您熟悉Lua和lua-nginx-module API,而且只會描述Kong的插件環境。
正則表達式
注意:本章假設您熟悉Lua。
shell
將您的插件視爲一組Lua模塊。本章中描述的每一個文件都將被視爲單獨的模塊。若是他們的名字符合這個約定,Kong會檢測並加載您的插件的模塊:數據庫
"kong.plugins.<plugin_name>.<module_name>"
您的模塊固然須要經過您的package.path變量來訪問,能夠經過您的Nginx配置中的lua-package-path指令來調整您的需求。然而,安裝插件的首選方法是經過Luarocks。在本指南中的更多內容。api
爲了讓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
一些插件可能必須更深刻地與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
文件必須返回一個實現要執行的函數的表。爲了簡潔起見,這裏是一個註釋的示例模塊,實現全部可用的方法:
注意: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
和相應的錯誤消息。
此模塊將返回一個具備屬性的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 | 對屬性執行任何自定義驗證的功能。有關其參數和返回值,請參閱後面的示例。 |
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+。
Kong的全部實體都如下列方式表示:
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
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工廠」。本章將介紹如何爲您的實體提供抽象。
一旦定義了您的模型,您必須建立遷移模塊,這些模塊將由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; ]] } }
雖然Postgres,Cassandra不支持「NOT NULL」,「UNIQUE」或「FOREIGN KEY」等約束,可是在定義模型的架構時,Kong能夠提供這些功能。請記住,對於PostgreSQL和Cassandra,此模式將是相同的,所以,您可能會爲與Cassandra一塊兒使用的純SQL模式進行權衡。
重要信息:若是您的架構使用惟一的約束,那麼對於Cassandra,Kong將強制執行,但對於Postgres,您必須在migrations文件中設置此約束。
要使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以標識用戶。這將在每一個請求上發生,這將是很是低效的:
爲了不每次查詢數據存儲區,咱們均可以在節點上緩存自定義實體內存,以便頻繁的實體查找不會每次(僅第一次)觸發數據存儲查詢,可是在內存中發生,從數據存儲區(特別是在重負載狀況下)查詢更快,更可靠。
注意:當緩存內存中的自定義實體時,您還須要提供一個無效機制,在「hooks.lua」文件中實現。
一旦您定義了自定義實體,就能夠經過要求database_cache
依賴關係在代碼中緩存內存:
local cache = require "kong.tools.database_cache"
有兩個級別的緩存:
當從數據庫中獲取數據時,它將被存儲在兩個緩存中。如今若是同一個工做進程再次請求數據,它將從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
函數返回,您能夠選擇實現。
此函數將自定義實體序列化到最小版本,僅包含稍後須要在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是Kong用戶與Kong進行溝通以設置其API和插件的地方。它們可能還須要與您爲插件實現的自定義實體進行交互(例如,建立和刪除API密鑰)。您將要作的事情是擴展Admin API,咱們將在下一章中詳細介紹:擴展Admin API。
"kong.plugins.<plugin_name>.api"
Admin API是用戶配置Kong的接口。若是您的插件具備自定義實體或管理要求,則須要擴展Admin API。這容許您公開您本身的端點並實現您本身的管理邏輯。其中一個典型的例子是API密鑰的建立,檢索和刪除(一般稱爲「CRUD操做」)。
Admin API是一個Lapis應用程序,Kong的抽象級別使您能夠輕鬆添加端點。
注意:本章假定您具備Lapis的相關知識。
若是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動詞以外,路由表還能夠包含兩個其餘密鑰:
在Admin API處理請求時,有時您但願發回響應並處理錯誤,以幫助您這樣作,第三個參數helpers
是具備如下屬性的表:
responses
: 具備幫助函數的模塊發送HTTP響應。yield_error
: 來自Lapis的yield_error函數。當你的處理程序遇到錯誤(例如從DAO)時調用。因爲全部Kong錯誤都是具備上下文的表,所以能夠根據錯誤(內部服務器錯誤,錯誤請求等)發送適當的響應代碼。因爲您將在您的端點執行的大多數操做將是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集羣中的每一個節點,以確保自定義插件在每一個節點上均可用。
您可使用常規打包策略(例如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
要使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羣集中的每一個節點執行此操做。
您如今必須將自定義插件的名稱添加到Kong配置(每一個Kong節點)的custom_plugins列表中:
custom_plugins = <plugin-name>
若是您使用兩個或多個自定義插件,請在其間插入逗號,以下所示:
custom_plugins = plugin1,plugin2
注意:您還能夠經過其環境變量等效設置此屬性:KONG_CUSTOM_PLUGINS
。 提醒:不要忘記更新您的Kong羣集中每一個節點的custom_plugins
指令。
你如今應該能夠沒有任何問題的開始了。請參閱您的自定義插件的說明,瞭解如何在API或Consumer對象上啓用/配置插件。
要確保您的插件由Kong加載,您可使用調試日誌級別啓動Kong:
log_level = debug
或者:
KONG_LOG_LEVEL=debug
而後,您將看到正在加載的每一個插件的如下日誌:
[debug] Loading plugin <plugin-name>
徹底刪除插件有三個步驟。
custom_plugins
指令(每一個Kong節點)刪除該插件。在這一步以前請確保步驟1已經執行。在這一步以後,任何人都不可能將插件從新應用於任何Kong API,消費者甚至全局。此步驟須要從新啓動/從新加載Kong節點才能生效。luarocks remove <plugin-name>
將其刪除。這樣作的首選方法是使用Luarocks,一個Lua模塊的軟件包管理器。它稱之爲「rocks」模塊。您的模塊沒必要住在Kong存儲庫中,可是若是您想保持您的Kong設置,它將能夠存在kong 存儲庫中。
經過在rockspec文件中定義模塊(及其最終依賴關係),您能夠經過Luarocks在平臺上安裝這些模塊。您還能夠將您的模塊上傳到Luarocks,並將其提供給全部人!
這裏是一個使用「內置」構建類型定義Lua符號及其相應文件中的模塊的rockspec示例:
有關示例,請參閱Kong插件模板,有關格式的更多信息,請參閱Rockspecs上的LuaRocks文檔。
因爲幾個緣由,因爲配置錯誤的自定義插件,Kong可能沒法啓動:
custom_plugins
指令中沒有。要解決,請將插件的名稱添加到節點的custom_plugins
指令中。custom_plugins
指令中,可是Kong沒法從文件系統加載handler.lua
源文件。要解決,請確保lua_package_path
指令正確設置爲加載此插件的Lua源。custom_plugins
中啓用,但Kong沒法從文件系統加載schema.lua
源文件。要解決,請確保schema.lua
文件與插件的handler.lua
文件一塊兒存在。