kong插件官方文檔翻譯

kong插件官方文檔翻譯

目錄

  1. 介紹
  2. 文件結構
  3. 編寫自定義邏輯
  4. 存儲配置
  5. 訪問數據存儲
  6. 自定義實體
  7. 緩存自定義實體
  8. 擴展Admin API
  9. 編寫測試
  10. (卸載)安裝你的插件

插件開發 - 介紹

什麼是插件,他們如何與kong集成?

在進一步以前,有必要簡要解釋一下如何構建,特別是它如何與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-nginx-module API:容許與Nginx自己進行交互,例如檢索請求/響應或訪問Nginx的共享內存區域。
  • Kong的插件環境:容許與保存配置的數據存儲(API,消費者,插件...)和各類幫助器進行交互,從而容許插件之間的交互。這是本指南將要描述的環境。

注意:本指南假設您熟悉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
  • 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 在做爲重寫階段處理程序從客戶端接收時針對每一個請求執行。在這個階段,注意到apiconsumer都沒有被識別出來,所以,若是插件被配置爲全局插件,這個處理程序將被執行!
: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"

有兩個級別的緩存:

  1. Lua內存緩存(本地到nginx worker)這能夠保存任何類型的Lua值。
  2. 共享內存緩存 - 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_UPDATEDENTITY_DELETED事件,並經過調用適當的函數進行響應。 message_t表包含事件屬性:

屬性名稱 類型 描述
collection String 資料儲存庫中的集合受到操做的影響。
entity Table 最近更新的實體,或刪除或建立的實體。
old_entity Table 僅適用於更新事件,舊版本的實體。

entityold_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函數,它返回一個具備idconsumer_idapikey字段的對象。在咱們的鉤子中,咱們不須要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函數 啓動/關閉 busted
  • spec/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

徹底刪除插件有三個步驟。

  1. 從Kong api配置中刪除該插件。確保它再也不適用於全局或任何API或消費者。對於整個羣集,這隻能執行一次,不須要從新啓動/從新加載。這一步自己將使插件再也不使用。可是它仍然可用,而且仍然能夠從新應用插件。
  2. 經過custom_plugins指令(每一個Kong節點)刪除該插件。在這一步以前請確保步驟1已經執行。在這一步以後,任何人都不可能將插件從新應用於任何Kong API,消費者甚至全局。此步驟須要從新啓動/從新加載Kong節點才能生效。
  3. 要完全刪除插件,請從每一個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文件一塊兒存在。
相關文章
相關標籤/搜索