您正在查看 Apigee Edge 說明文件。
查看 Apigee X 說明文件。 資訊
Edge Microgateway 3.3.x 版
適用對象
本主題的適用對象為希望透過編寫自訂外掛程式擴充 Edge Microgateway 功能的開發人員。如要編寫新的外掛程式,您必須具備 JavaScript 和 Node.js 的經驗。
什麼是自訂 Edge Microgateway 外掛程式?
外掛程式是一種 Node.js 模組,可為 Edge Microgateway 新增功能。外掛程式模組遵循一致的模式,並儲存在 Edge Microgateway 已知的位置,以便自動探索及執行。安裝 Edge Microgateway 時,系統會提供數種預先定義的外掛程式。包括驗證、尖峰流量防範、配額和數據分析的外掛程式。在「使用外掛程式」一文中,認識這些現有的外掛程式。
您可以編寫自訂外掛程式,為微閘道新增功能。根據預設,Edge Microgateway 基本上是安全的直通 Proxy,會在與目標服務之間照常傳送要求和回應。透過自訂外掛程式,您可以透過程式與通過微閘道的要求和回應互動。
自訂外掛程式程式碼的位置
這裡的 Edge Microgateway 安裝程序中會包含自訂外掛程式的資料夾:
[prefix]/lib/node_modules/edgemicro/node_modules/microgateway-plugins
其中 [prefix]
是 npm
前置字串目錄,如「安裝 Edge Microgateway」中的「安裝 Edge Microgateway 在哪裡」一節所述。
您可以變更這個預設外掛程式目錄。請參閱「哪裡可以找到外掛程式」。
查看預先定義的外掛程式
在嘗試開發自己的外掛程式前,建議您確認所有預先定義的外掛程式皆不符合您的需求。這些外掛程式位於:
[prefix]/lib/node_modules/edgemicro/node_modules/microgateway-plugins
其中 [prefix]
是 npm
前置字串目錄。另請參閱「安裝 Edge Microgateway」中的「Edge Microgateway 安裝位置」。
詳情請參閱「透過 Edge Microgateway 提供的預先定義外掛程式」。
編寫簡單的外掛程式
本節將逐一說明建立簡易外掛程式的必要步驟。這個外掛程式能用「Hello, World!」字串覆寫回應資料 (不論其內容),並將其輸出至終端機。
- 如果 Edge Microgateway 正在執行,請立即停止:
edgemicro stop
-
cd
至自訂外掛程式目錄:cd [prefix]/lib/node_modules/edgemicro/plugins
其中
[prefix]
是npm
前置字串目錄,如「安裝 Edge Microgateway」中的「安裝 Edge Microgateway 在哪裡」所述。 - 建立名為 response-override 和
cd
的外掛程式專案:
mkdir response-override && cd response-override
- 建立新的 Node.js 專案:
npm init
Hit Returnt 多次傳回預設值。 - 使用文字編輯器建立名為
index.js
的新檔案。 - 將下列程式碼複製到
index.js
中,然後儲存檔案。
'use strict'; var debug = require('debug') module.exports.init = function(config, logger, stats) { return { ondata_response: function(req, res, data, next) { debug('***** plugin ondata_response'); next(null, null); }, onend_response: function(req, res, data, next) { debug('***** plugin onend_response'); next(null, "Hello, World!\n\n"); } }; }
- 您已建立外掛程式,現在需要將外掛程式新增至 Edge Microgateway 設定。開啟
$HOME/.edgemicro/[org]-[env]-config.yaml
檔案,其中org
和env
是邊緣機構和環境名稱。 - 將
response-override
外掛程式新增至plugins:sequence
元素,如下所示。
... plugins: dir: ../plugins sequence: - oauth - response-override ...
- 重新啟動 Edge Microgateway。
- 透過 Edge Microgateway 呼叫 API。(這個 API 呼叫假設您已設定與 API 金鑰安全性教學課程相同的設定,詳情請參閱設定及設定 Edge Microgateway:
curl -H 'x-api-key: uAM4gBSb6YoMvTHfx5lXJizYIpr5Jd' http://localhost:8000/hello/echo Hello, World!
外掛程式剖析
下列 Edge Microgateway 範例外掛程式說明自行開發外掛程式時應遵循的模式。本節所討論範例外掛程式的原始碼位於 plugins/header-uppercase/index.js.
- 外掛程式是標準 NPM 模組,根資料夾中有
package.json
和index.js
。 - 外掛程式必須匯出 init() 函式。
- init() 函式使用三個引數:config、logger 和 stats。如要瞭解這些引數,請參閱外掛程式 init() 函式引數。
- init() 會傳回含有已命名函式處理常式的物件,系統會在要求生命週期發生特定事件時呼叫此常式函式。
事件處理常式函式
外掛程式必須實作部分或所有的事件處理常式函式。您可以自行決定實作這些函式。任何指定函式為選用項目,一般外掛程式至少會實作部分函式。
要求流程事件處理常式
系統會在 Edge Microgateway 中的要求事件呼叫這些函式。
onrequest
ondata_request
onend_request
onclose_request
onerror_request
onrequest
函式
在用戶端要求開始時呼叫。這個函式會在 Edge Microgateway 收到要求的第一個位元組時觸發。這個函式可讓您存取要求標頭、網址、查詢參數和 HTTP 方法。如果後續呼叫的第一個引數 (例如「錯誤」例項),系統會停止要求處理作業,且不會啟動目標要求。
示例:
onrequest: function(req, res, next) { debug('plugin onrequest'); req.headers['x-foo-request-start'] = Date.now(); next(); }
ondata_request
函式
從用戶端收到資料區塊時呼叫。將要求資料傳送至外掛程式序列中的下一個外掛程式。系統會將序列中最後一個外掛程式傳回的值傳送至目標。如下所示,典型的用途是先轉換要求資料,然後再傳送至目標。
示例:
ondata_request: function(req, res, data, next) { debug('plugin ondata_request ' + data.length); var transformed = data.toString().toUpperCase(); next(null, transformed); }
onend_request
函式
收到用戶端的所有要求資料時,會呼叫此方法。
示例:
onend_request: function(req, res, data, next) { debug('plugin onend_request'); next(null, data); }
onclose_request
函式
表示用戶端連線已關閉。在用戶端連線不穩定的情況下,您可以使用此函式。當與用戶端的通訊端連線關閉時,就會呼叫此方法。
示例:
onclose_request: function(req, res, next) { debug('plugin onclose_request'); next(); }
onerror_request
函式
如果接收用戶端要求時發生錯誤,就會呼叫此方法。
示例:
onerror_request: function(req, res, err, next) { debug('plugin onerror_request ' + err); next(); }
回應流程事件處理常式
系統會在 Edge Microgateway 中的回應事件呼叫這些函式。
onresponse
ondata_response
onend_response
onclose_response
onerror_response
onresponse
函式
在目標回應開始時呼叫。這個函式會在 Edge Microgateway 收到回應的第一個位元組時觸發。這個函式可讓您存取回應標頭和狀態碼。
示例:
onresponse: function(req, res, next) { debug('plugin onresponse'); res.setHeader('x-foo-response-time', Date.now() - req.headers['x-foo-request-start']) next(); }
ondata_response
函式
從目標收到資料區塊時呼叫。
示例:
ondata_response: function(req, res, data, next) { debug('plugin ondata_response ' + data.length); var transformed = data.toString().toUpperCase(); next(null, transformed); }
onend_response
函式
從目標收到所有回應資料時,會呼叫此方法。
示例:
onend_response: function(req, res, data, next) { debug('plugin onend_response'); next(null, data); }
onclose_response
函式
表示目標連線已關閉。在目標連線不穩定的情況下,您可以使用這個函式。當與目標的通訊端連線關閉時,就會呼叫此方法。
示例:
onclose_response: function(req, res, next) { debug('plugin onclose_response'); next(); }
onerror_response
函式
如果接收到目標回應時發生錯誤,會呼叫此方法。
示例:
onerror_response: function(req, res, err, next) { debug('plugin onerror_response ' + err); next(); }
外掛程式事件處理常式函式須知
系統會呼叫外掛程式事件處理常式函式,以回應在 Edge Microgateway 處理特定 API 要求時發生的特定事件。
- 每個 init() 函式處理常式 (ondata_request、ondata_response 等) 都必須在處理完成後呼叫 next() 回呼。如果您沒有呼叫 next(),處理程序就會停止,要求也會停止運作。
- next() 的第一個引數可能為錯誤,這會導致要求處理作業終止。
- ondata_ 和 onend_ 處理常式必須透過第二個引數呼叫 next(),其中含有要傳遞至目標或用戶端的資料。如果外掛程式正在緩衝處理,而且目前資料不足以轉換,這個引數可以是空值。
- 請注意,這個外掛程式的執行個體會用來處理所有要求與回應。如果外掛程式想在呼叫之間保留每項要求的狀態,可將狀態儲存在新增至提供的 request 物件 (req) 的屬性中,該物件的生命週期為 API 呼叫的時間長度。
- 請務必找出所有錯誤,並呼叫該錯誤的 next()。如未呼叫 next(),將導致 API 呼叫停止運作。
- 請注意,不要導入記憶體流失情形,因為這可能會影響 Edge Microgateway 的整體效能,並在記憶體不足時造成當機。
- 請留意 Node.js 模型,請勿在主執行緒中執行會耗用大量運算資源的工作,以免對 Edge Microgateway 的效能造成負面影響。
關於外掛程式 init() 函式
本節說明傳送至 init() 函式的引數:config、logger 和 stats。
config
將 Edge Microgateway 設定檔與從 Apigee Edge 下載的資料合併後,產生的設定資料會放在名為 config
的物件內。
如要將名為 param 的設定參數 (值為 foo) 新增至名為 response-override 的外掛程式,請在 default.yaml
檔案中放置以下程式碼:
response-override: param: foo
接著,您可以在外掛程式程式碼中存取該參數,如下所示:
// Called when response data is received ondata_response: function(req, res, data, next) { debug('***** plugin ondata_response'); debug('***** plugin ondata_response: config.param: ' + config.param); next(null, data); },
在本範例中,您將在外掛程式偵錯輸出中看到 foo 列印:
Sun, 13 Dec 2015 21:25:08 GMT plugin:response-override ***** plugin ondata_response: config.param: foo
您可以存取已合併的微閘道設定,並在子物件「config.emgConfigs
」中下載 Apigee Edge 資料。例如,您可以在 init
函式中存取這項設定資料,如下所示:
module.exports.init = function(config, logger, stats) { let emgconfigs = config.emgConfigs;
以下是 emgConfigs
包含的資料範例:
{ edgemicro: { port: 8000, max_connections: 1000, config_change_poll_interval: 600, logging: { level: 'error', dir: '/var/tmp', stats_log_interval: 60, rotate_interval: 24, stack_trace: false }, plugins: { sequence: [Array] }, global: { org: 'Your Org', env: 'test' } }, headers: { 'x-forwarded-for': true, 'x-forwarded-host': true, 'x-request-id': true, 'x-response-time': true, via: true }, proxies: [ { max_connections: 1000, name: 'edgemicro_delayed', revision: '1', proxy_name: 'default', base_path: '/edgemicro_delayed', target_name: 'default', url: 'https://httpbin.org/delay/10', timeout: 0 } ], product_to_proxy: { EdgeMicroTestProduct: [ 'edgemicro-auth','edgemicro_delayed',] }, product_to_scopes: {prod4: [ 'Admin', 'Guest', 'Student' ] }, product_to_api_resource: { EdgeMicroTestProduct: [ '/*' ] }, _hash: 0, keys: { key: 'Your key', secret: 'Your key ' }, uid: 'Internally generated uuid', targets: [] }
logger
系統記錄器。目前採用的記錄器會匯出這些函式,其中物件可以是字串、HTTP 要求、HTTP 回應或錯誤執行個體。
info(object, message)
warn(object, message)
error(object, message)
trace(object, message)
debug(object, message)
stats
這個物件會保留與透過微閘道執行個體流動的要求與回應相關的要求數、回應、錯誤和其他匯總統計資料。
- treqErrors:含有錯誤的目標要求數量。
- treqErrors:含有錯誤的目標回應數量。
- statusCodes - 包含回應代碼數量的物件:
{ 1: number of target responses with 1xx response codes 2: number of target responses with 2xx response codes 3: number of target responses with 3xx response codes 4: number of target responses with 4xx response codes 5: number of target responses with 5xx response codes }
- requests:要求總數。
- Responses - 回應總數。
- connections:有效目標連線的數量。
關於 next() 函式
所有外掛程式方法都必須呼叫 next()
,才能繼續處理系列中的下一個方法 (否則外掛程式程序會停止運作)。在要求生命週期中,第一個呼叫的方法是 onrequest()。下一個呼叫的方法是 ondata_request()
方法;但是,只有在要求包含資料時才會呼叫 ondata_request
,如上述範例所示:POST 要求。下一個呼叫的方法是 onend_request()
,系統會在要求處理完成後呼叫此方法。只有在發生錯誤時才會呼叫 onerror_*
函式,您可以視需要使用自訂程式碼處理錯誤。
假設資料是在要求中傳送,且系統呼叫了 ondata_request()
。請注意,函式會使用兩個參數呼叫 next()
:
next(null, data);
依照慣例,第一個參數是用來傳達錯誤資訊,然後您就可以在鏈結中的後續函式中處理。把這個引數設為 null
(falsy 引數),我們就會指出沒有任何錯誤,所以要求處理程序應該會正常地繼續進行。如果這個引數是無害的 (例如 Error 物件),系統就會停止要求處理作業,並將要求傳送至目標。
第二個參數會將要求資料傳送至鏈結中的下一個函式。如果您未額外處理,系統會將要求資料傳送至 API 的目標。不過,您有機會修改此方法中的要求資料,並將修改後的要求傳送至目標。舉例來說,如果要求資料是 XML,且目標預期採用 JSON,則您可以在 ondata_request()
方法中新增程式碼,以便 (a) 將要求標頭的 Content-Type 變更為 application/json
,並使用您想要的任何方式將要求資料轉換為 JSON (例如,您可以使用從 NPM 取得的 Node.js xml2json 轉換工具)。
可能如下:
ondata_request: function(req, res, data, next) { debug('****** plugin ondata_request'); var translated_data = parser.toJson(data); next(null, translated_data); },
在這種情況下,要求資料 (假設為 XML) 會轉換為 JSON,而轉換後的資料會透過 next()
傳送至要求鏈結中的下一個函式,然後再傳送至後端目標。
請注意,您可以新增另一個偵錯陳述式,顯示轉換後的資料以供偵錯。例如:
ondata_request: function(req, res, data, next) { debug('****** plugin ondata_request'); var translated_data = parser.toJson(data); debug('****** plugin ondata_response: translated_json: ' + translated_json); next(null, translated_data); },
關於外掛程式處理常式執行順序
如果您要編寫 Edge Microgateway 外掛程式,就必須瞭解外掛程式事件處理常式的執行順序。
請特別注意,在 Edge Microgateway 設定檔中指定外掛程式序列時,要求處理常式會以「遞增」順序執行,而回應處理常式會以「descending」順序執行。
以下範例旨在協助您瞭解這個執行順序。
1. 建立三個簡單的外掛程式
請考慮使用下列外掛程式。系統會在呼叫事件處理常式時,輸出主控台輸出內容:
plugins/plugin-1/index.js
module.exports.init = function(config, logger, stats) { return { onrequest: function(req, res, next) { console.log('plugin-1: onrequest'); next(); }, onend_request: function(req, res, data, next) { console.log('plugin-1: onend_request'); next(null, data); }, ondata_response: function(req, res, data, next) { console.log('plugin-1: ondata_response ' + data.length); next(null, data); }, onend_response: function(req, res, data, next) { console.log('plugin-1: onend_response'); next(null, data); } }; }
現在,請考慮使用相同程式碼另外建立 plugin-2
和 plugin-3
兩個外掛程式 (不過,請將 console.log()
陳述式分別變更為 plugin-2
和 plugin-3
)。
2. 查看外掛程式程式碼
<microgateway-root-dir>/plugins/plugin-1/index.js
中匯出的外掛程式函式是一種事件處理常式,會在要求和回應處理期間的特定時間執行。舉例來說,onrequest
會執行接收要求標頭的第一個位元組。雖然 onend_response
會在收到回應資料的最後位元組後執行。
查看 ondata_response 處理常式;每當收到回應資料區塊時,便會呼叫此處理常式。值得注意的是,回應資料不一定會一次收到。而是可能會以任意長度的區塊接收資料。
3. 將外掛程式新增至外掛程式序列
延續這個範例,我們會在 Edge Microgateway 設定檔 (~./edgemicro/config.yaml
) 中的外掛程式序列中新增外掛程式,如下所示。序列很重要,它會定義外掛程式處理常式的執行順序。
plugins: dir: ../plugins sequence: - plugin-1 - plugin-2 - plugin-3
4. 檢查偵錯輸出內容
現在,我們來看看呼叫這些外掛程式時會產生的輸出內容。請注意以下幾個重點:
- 外掛程式會序列 Edge Microgateway 設定檔 (
~./edgemicro/config.yaml
) 指定事件處理常式的呼叫順序。 - 要求處理常式會以「遞增」順序呼叫 (這類處理常式在外掛程式序列中的顯示順序:1、2、3)。
- 回應處理常式會依「遞減」順序依序呼叫 3、2、1。
- 系統會針對傳送的每個資料區塊呼叫
ondata_response
處理常式。在此範例中 (輸出內容如下所示),系統會接收兩個區塊。
以下是這三個外掛程式在使用中,並透過 Edge Microgateway 傳送要求時產生的偵錯輸出範例。只要注意處理常式呼叫的順序即可:
plugin-1: onrequest plugin-2: onrequest plugin-3: onrequest plugin-1: onend_request plugin-2: onend_request plugin-3: onend_request plugin-3: ondata_response 931 plugin-2: ondata_response 931 plugin-1: ondata_response 931 plugin-3: ondata_response 1808 plugin-3: onend_response plugin-2: ondata_response 1808 plugin-2: onend_response plugin-1: ondata_response 1808 plugin-1: onend_response
摘要
在嘗試實作自訂外掛程式功能 (例如累積及轉換要求或回應資料) 時,請務必瞭解呼叫外掛程式處理常式的順序。
請記住,要求處理常式會按照在 Edge Microgateway 設定檔中指定的順序執行,而回應處理常式會以相反順序執行。
關於在外掛程式中使用全域變數
每個傳送至 Edge Microgateway 的要求,都會傳送至相同的外掛程式執行個體;因此,來自其他用戶端的第二個要求的狀態會覆寫第一個要求的狀態。儲存外掛程式狀態的唯一安全位置,就是將狀態儲存在要求或回應物件的屬性中 (生命週期僅限於要求的生命週期)。
外掛程式中的目標網址
已新增至 2.3.3 版
您可以在外掛程式程式碼中修改下列變數,藉此動態覆寫外掛程式中的預設目標網址:req.targetHostname 和 req.targetPath。
已新增至 v2.4.x
您也可以覆寫目標端點通訊埠,並選擇 HTTP 或 HTTPS。在外掛程式程式碼中修改以下變數:req.targetPort 和 req.targetSecure。如要選擇 HTTPS,請將 req.targetSecure 設為 true;如果是 HTTP,請設為 false。如果將 req.targetSecure 設為 true,請參閱這個討論串瞭解詳情。
外掛程式範例
這些外掛程式會在安裝 Edge Microgateway 時一併提供。您可以在下列位置找到 Edge Microgateway 安裝項目:
[prefix]/lib/node_modules/edgemicro/plugins
其中 [prefix]
是 npm
前置字串目錄,如「安裝 Edge Microgateway」中的「安裝 Edge Microgateway 在哪裡」一節所述。
累積要求
這個外掛程式會將用戶端的資料區塊累積到附加在要求物件的陣列屬性中。收到所有要求資料時,陣列會串連成緩衝區,接著傳遞到序列中的下一個外掛程式。此外掛程式應是序列中的第一個外掛程式,以便後續的外掛程式接收累積的要求資料。
module.exports.init = function(config, logger, stats) { function accumulate(req, data) { if (!req._chunks) req._chunks = []; req._chunks.push(data); } return { ondata_request: function(req, res, data, next) { if (data && data.length > 0) accumulate(req, data); next(null, null); }, onend_request: function(req, res, data, next) { if (data && data.length > 0) accumulate(req, data); var content = null; if (req._chunks && req._chunks.length) { content = Buffer.concat(req._chunks); } delete req._chunks; next(null, content); } }; }
累積回應
這個外掛程式會將目標的資料區塊累積至附加至回應物件的陣列屬性。收到所有回應資料時,陣列會串連成緩衝區,接著傳遞到序列中的下一個外掛程式。由於這個外掛程式是以反向順序處理的回應 (以反向順序處理),因此您應將其放在序列中最後一個外掛程式的位置。
module.exports.init = function(config, logger, stats) { function accumulate(res, data) { if (!res._chunks) res._chunks = []; res._chunks.push(data); } return { ondata_response: function(req, res, data, next) { if (data && data.length > 0) accumulate(res, data); next(null, null); }, onend_response: function(req, res, data, next) { if (data && data.length > 0) accumulate(res, data); var content = Buffer.concat(res._chunks); delete res._chunks; next(null, content); } }; }
header-uppercase 外掛程式
Edge Microgateway 發行版內含名為 <microgateway-root-dir>/plugins/header-uppercase
的範例外掛程式。這個範例內含說明每個函式處理常式的註解。這個範例會對目標回應執行一些簡單的資料轉換作業,並在用戶端要求和目標回應中新增自訂標頭。
以下是 <microgateway-root-dir>/plugins/header-uppercase/index.js
的原始碼:
'use strict'; var debug = require('debug')('plugin:header-uppercase'); // required module.exports.init = function(config, logger, stats) { var counter = 0; return { // indicates start of client request // request headers, url, query params, method should be available at this time // request processing stops (and a target request is not initiated) if // next is called with a truthy first argument (an instance of Error, for example) onrequest: function(req, res, next) { debug('plugin onrequest'); req.headers['x-foo-request-id'] = counter++; req.headers['x-foo-request-start'] = Date.now(); next(); }, // indicates start of target response // response headers and status code should be available at this time onresponse: function(req, res, next) { debug('plugin onresponse'); res.setHeader('x-foo-response-id', req.headers['x-foo-request-id']); res.setHeader('x-foo-response-time', Date.now() - req.headers['x-foo-request-start']); next(); }, // chunk of request body data received from client // should return (potentially) transformed data for next plugin in chain // the returned value from the last plugin in the chain is written to the target ondata_request: function(req, res, data, next) { debug('plugin ondata_request ' + data.length); var transformed = data.toString().toUpperCase(); next(null, transformed); }, // chunk of response body data received from target // should return (potentially) transformed data for next plugin in chain // the returned value from the last plugin in the chain is written to the client ondata_response: function(req, res, data, next) { debug('plugin ondata_response ' + data.length); var transformed = data.toString().toUpperCase(); next(null, transformed); }, // indicates end of client request onend_request: function(req, res, data, next) { debug('plugin onend_request'); next(null, data); }, // indicates end of target response onend_response: function(req, res, data, next) { debug('plugin onend_response'); next(null, data); }, // error receiving client request onerror_request: function(req, res, err, next) { debug('plugin onerror_request ' + err); next(); }, // error receiving target response onerror_response: function(req, res, err, next) { debug('plugin onerror_response ' + err); next(); }, // indicates client connection closed onclose_request: function(req, res, next) { debug('plugin onclose_request'); next(); }, // indicates target connection closed onclose_response: function(req, res, next) { debug('plugin onclose_response'); next(); } }; }
轉換大寫
這是一般轉換外掛程式,您可以修改這類外掛程式,以執行您想要的任何轉換。這個範例只會將回應和要求資料轉換為大寫。
*/ module.exports.init = function(config, logger, stats) { // perform content transformation here // the result of the transformation must be another Buffer function transform(data) { return new Buffer(data.toString().toUpperCase()); } return { ondata_response: function(req, res, data, next) { // transform each chunk as it is received next(null, data ? transform(data) : null); }, onend_response: function(req, res, data, next) { // transform accumulated data, if any next(null, data ? transform(data) : null); }, ondata_request: function(req, res, data, next) { // transform each chunk as it is received next(null, data ? transform(data) : null); }, onend_request: function(req, res, data, next) { // transform accumulated data, if any next(null, data ? transform(data) : null); } }; }