您目前查看的是 Apigee Edge 說明文件。
前往 Apigee X 說明文件。 info
Edge Microgateway 3.0.x 版
目標對象
本主題適用於想編寫自訂外掛程式來擴充 Edge Microgateway 功能的開發人員。如要編寫新外掛程式,您必須具備 JavaScript 和 Node.js 經驗。
什麼是自訂 Edge Microgateway 外掛程式?
外掛程式是 Node.js 模組,可為 Edge Microgateway 新增功能。外掛程式模組遵循一致的模式,並儲存在 Edge Microgateway 已知的位置,因此系統可以自動探索及執行這些模組。安裝 Edge Microgateway 時,系統會提供數個預先定義的外掛程式。包括驗證、尖峰流量抑制、配額和分析的外掛程式。如要瞭解現有外掛程式,請參閱「使用外掛程式」。
您可以編寫自訂外掛程式,為微型閘道新增功能。根據預設,Edge Microgateway 基本上是安全的直通 Proxy,可將要求和回應原封不動地傳送至目標服務,以及從目標服務傳送至 Edge Microgateway。透過自訂外掛程式,您可以透過程式輔助方式,與流經微型閘道的請求和回應互動。
自訂外掛程式程式碼的放置位置
自訂外掛程式的資料夾會隨 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 專案:
多次按下 Return 鍵,接受預設值。npm init
- 使用文字編輯器建立名為
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是 Edge 機構和環境名稱。 - 將
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 的要求事件中呼叫。
onrequestondata_requestonend_requestonclose_requestonerror_request
onrequest function
在用戶端要求開始時呼叫。當 Edge Microgateway 收到要求的第一個位元組時,就會觸發這個函式。這個函式可讓您存取要求標頭、網址、查詢參數和 HTTP 方法。如果使用第一個引數 (例如 Error 的執行個體) 呼叫 next,要求處理作業就會停止,且不會啟動目標要求。
範例:
onrequest: function(req, res, next) { debug('plugin onrequest'); req.headers['x-foo-request-start'] = Date.now(); next(); }
ondata_request function
當從用戶端收到資料區塊時,系統會呼叫此方法。將要求資料傳遞至外掛程式序列中的下一個外掛程式。序列中最後一個外掛程式傳回的值會傳送至目標。如下所示,常見的用途是在將要求資料傳送至目標之前,先轉換資料。
範例:
ondata_request: function(req, res, data, next) { debug('plugin ondata_request ' + data.length); var transformed = data.toString().toUpperCase(); next(null, transformed); }
onend_request function
當系統從用戶端收到所有要求資料時,會呼叫此方法。
範例:
onend_request: function(req, res, data, next) { debug('plugin onend_request'); next(null, data); }
onclose_request 函式
表示用戶端連線已關閉。如果用戶端連線不穩定,您可能會使用這項功能。當與用戶端的 Socket 連線關閉時,系統會呼叫這個函式。
範例:
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 的回應事件中呼叫。
onresponseondata_responseonend_responseonclose_responseonerror_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 function
從目標接收到資料區塊時,系統會呼叫此方法。
範例:
ondata_response: function(req, res, data, next) { debug('plugin ondata_response ' + data.length); var transformed = data.toString().toUpperCase(); next(null, transformed); }
onend_response function
當目標傳送的所有回應資料都已接收完畢時,系統會呼叫此方法。
範例:
onend_response: function(req, res, data, next) { debug('plugin onend_response'); next(null, data); }
onclose_response 函式
表示目標連線已關閉。如果目標連線不穩定,您可能會使用這項功能。當與目標的 Socket 連線關閉時,系統會呼叫這個函式。
範例:
onclose_response: function(req, res, next) { debug('plugin onclose_response'); next(); }
onerror_response function
如果接收目標回應時發生錯誤,系統會呼叫這個函式。
範例:
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.<plugin-name>。
如要將名為 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
記錄器
系統記錄器。目前使用的記錄器會匯出這些函式,其中物件可以是字串、HTTP 要求、HTTP 回應或 Error 執行個體。
info(object, message)warn(object, message)error(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
}
- 要求:要求總數。
- 回應 - 回應總數。
- 連線:有效目標連線的數量。
關於 next() 函式
所有外掛程式方法都必須呼叫 next(),才能繼續處理序列中的下一個方法 (否則外掛程式程序會停止運作)。在要求生命週期中,第一個呼叫的方法是 onrequest()。下一個呼叫的方法是 ondata_request() 方法;不過,ondata_request 只有在要求包含資料時才會呼叫,例如 POST 要求。下一個呼叫的方法是 onend_request(),會在要求處理完成時呼叫。onerror_* 函式只會在發生錯誤時呼叫,讓您視需要使用自訂程式碼處理錯誤。
假設資料是在要求中傳送,且呼叫了 ondata_request()。請注意,函式會使用兩個參數呼叫 next():
next(null, data);
按照慣例,第一個參數會用於傳達錯誤資訊,您隨後可以在鏈結中的函式處理這些資訊。將其設為 null (虛假引數) 表示沒有錯誤,要求處理程序應正常進行。如果這個引數為真值 (例如 Error 物件),要求處理作業就會停止,並將要求傳送至目標。
第二個參數會將要求資料傳遞至鏈結中的下一個函式。如果您沒有其他處理作業,要求資料會原封不動地傳遞至 API 的目標。不過,您有機會在這個方法中修改要求資料,並將修改過的要求傳遞至目標。舉例來說,如果要求資料是 XML,但目標預期是 JSON,您可以在 ondata_request() 方法中新增程式碼,將要求標頭的 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 設定檔中指定外掛程式序列時,要求處理常式會以遞增順序執行,回應處理常式則會以遞減順序執行。
以下範例有助於瞭解這個執行順序。
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 這兩個變數,動態覆寫外掛程式中的預設目標網址。
新增版本:2.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 安裝位置」一節所述。
accumulate-request
這個外掛程式會將用戶端的資料區塊累積到附加至要求物件的陣列屬性中。收到所有要求資料後,陣列會串連到緩衝區,然後傳遞至序列中的下一個外掛程式。這個外掛程式應是序列中的第一個外掛程式,後續外掛程式才能接收累積的要求資料。
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); } }; }
accumulate-response
這個外掛程式會將目標中的資料區塊累積到附加至回應物件的陣列屬性中。收到所有回應資料後,陣列會串連到緩衝區,然後傳遞至序列中的下一個外掛程式。由於這個外掛程式會對回應進行操作,而回應的處理順序是反向,因此您應將這個外掛程式放在序列中的最後一個位置。
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(); } }; }
transform-uppercase
這是通用的轉換外掛程式,您可以修改這個外掛程式,執行任何類型的轉換作業。這個範例只是將回應和要求資料轉換為大寫。
*/ 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); } }; }