1 | # apiextender
|
2 | This module allows to extend an API backend, by adding endpoints or modifying existing endpoints' behaviour.
|
3 | This is accomplished in a plug-in fashion.
|
4 | With apiextender you can :
|
5 | + enable third-party developers to create functionalities extending an application
|
6 | + support the addition of new features in a simple way
|
7 |
|
8 | [![NPM](https://nodei.co/npm/apiextender.png?downloads=true&downloadRank=true&stars=true)![NPM](https://nodei.co/npm-dl/apiextender.png?months=6&height=3)](https://nodei.co/npm/apiextender/)
|
9 |
|
10 | * [Installation](#installation)
|
11 | * [Using apiextender](#using)
|
12 | * [make your environment capable to accept plugins functions](#folder)
|
13 | * [How to write function that extends the "API"](#howto)
|
14 | * [Reference](#reference)
|
15 | * [extend(app)](#extend)
|
16 | * [install(app,extender,save)](#install)
|
17 | * [Examples](#examples)
|
18 | * [Examples:How to set an API extensible by plugins](#examplesapi)
|
19 | * [Examples:How to write extensible plugin functions](#examplesplugin)
|
20 | * [Examples:override mode](#exampleoverride)
|
21 | * [Examples:before mode](#examplebefore)
|
22 | * [Examples:after mode](#exampleafter)
|
23 | * [Examples:before_after mode](#examplebefore_after)
|
24 | * [Examples:extend throwing error](#exampleerror)
|
25 | * [Examples:A Complete Example of plugin extension](#examplecomplete)
|
26 | * [Example: Extend API on the fly at runtime](#exampleonFly)
|
27 |
|
28 |
|
29 | ## <a name="installation"></a>Installation
|
30 | Install **apiextender** in your project by typing:
|
31 |
|
32 | ```shell
|
33 | $ npm install apiextender
|
34 | ```
|
35 |
|
36 |
|
37 | ## <a name="using"></a>Usage
|
38 | This is an Express module for nodeJs. This section explains how to use it in your code to make your API extensible by plugin.
|
39 |
|
40 | ### Include apiextender
|
41 | Just require it:
|
42 | ```javascript
|
43 | var apiextender = require('apiextender');
|
44 | ```
|
45 |
|
46 | ### Using apiextender
|
47 |
|
48 | ```javascript
|
49 | var express=require('express');
|
50 | var apiextender = require('apiextender');
|
51 | var app=express();
|
52 |
|
53 | // make your API extensible by plugin techniques in a simple and fast
|
54 | // and with one line of code.
|
55 | apiextender.extend(app); // now your API is exensible
|
56 | ```
|
57 |
|
58 | ## <a name="folder"></a>Make your environment compliant to plugins
|
59 | Plugin functions that extends your API must be defined in a file called ```extend.js```,
|
60 | located in a folder named ```plugin``` in the home directory of your application.
|
61 |
|
62 | ### Create plugin folder
|
63 | To create folder type:
|
64 | ```shell
|
65 | $ mkdir plugin
|
66 | ```
|
67 | ### Create plugin file container
|
68 | To create ```extend.js``` type:
|
69 | ```shell
|
70 | $ cd plugin
|
71 | $ vi extend.js
|
72 | ```
|
73 |
|
74 | ```extend.js``` contains an array of plugin functions. Now we initialize it as an empty plugin function container:
|
75 | ```javascript
|
76 | var plugins=[];
|
77 | module.exports = plugins;
|
78 | ```
|
79 |
|
80 |
|
81 | ## <a name="howto"></a>Write your functions that extend the API
|
82 | To extend an API endpoint, you must populate the ```plugins``` array containing all functions extending the API.
|
83 |
|
84 | ### extend.js structure
|
85 | ```javascript
|
86 | var plugins=[];
|
87 | module.exports = plugins;
|
88 | ```
|
89 | **`plugins=[]`** is an array of objects , which are functions that constitutes a plugin. Each function is defined as:
|
90 | ```javascript
|
91 | {
|
92 | "resource":"/resourceToExtend",
|
93 | "method":"GET",
|
94 | "mode":"override",
|
95 | "params":[query],
|
96 | "extender": function(req,content,cType,callback){
|
97 | callback(null,{"Content extension"});
|
98 | }
|
99 | }
|
100 | ```
|
101 |
|
102 | * URI: URI of the resource to protect
|
103 | * method: HTTP method used to call the resource to protect
|
104 | * authToken : An array of Strings containing the token types authorized by this role
|
105 |
|
106 | where:
|
107 | * **resource**: URI of the resource to extend
|
108 | * **method** : HTTP method of the resource to extend
|
109 | * **mode** : Mode defining how the resource must be extended. There are four
|
110 | different possible modes: ```override```, ```before```, ```after```, ```before_after```. In detail:
|
111 | * **override** : overrides the original endpoint definition
|
112 | * **before** : the plugin function is executed before the original endpoint
|
113 | * **after** : the plugin function is executed after the original endpoint
|
114 | * **before_after**: the plugin function is executed both before and after the original endpoint
|
115 |
|
116 | * **params** : Array of the fields of Express ```req``` object that must be passed to the plugin function in ```reqParams``` param
|
117 | * **enabled** : Optional boolean parameter indicating if the plugin is enabled or not. Default is disabled
|
118 | * **extender**: Plugin function definition. This function is invoked end executed by apiextender, defined as:
|
119 | ```function(reqParams,content,contentType,callback)```
|
120 | * **reqParams** : Object containing the Express request params defined in ```params``` field.
|
121 | E.g. if ```params=["query"]```, ```reqParams``` contains the object ```{query:req.query}```
|
122 | * **content** : Response of the original endpoint when ```mode``` param is set to ```after``` or ```before_after```.
|
123 | Null when mode is set to ```override``` or ```before```
|
124 | * **contentType** : Content Type of ```content```, e.g. ```application/json``` , ```text/html```...
|
125 | Null when mode is set to ```override``` or ```before```
|
126 | * **callback** : callback function to apiextender. It must be invoked with params ```error``` and ```newContent```, defined as:
|
127 | ```callback(error,newContent)```
|
128 | * **error** : If an error occurs, apiextender stops the request execution and sends a response to the client
|
129 | with this object, that must have two fields:
|
130 | * **error_code** : HTTP status code
|
131 | * **error_message** : error message content
|
132 | * **newContent** : response sent to the client if mode is ```override```, ```after``` or ```before_after```
|
133 | (for the latter, only for what is executed in the ```after``` part). <br>
|
134 | If mode is set to ```before``` or ```before_after```
|
135 | (for the latter, only for what is executed in the ```before``` part), it is an
|
136 | object whose keys are appended to Express' ```req``` .
|
137 |
|
138 | ####<a name="nb"></a> Warning:
|
139 | If `mode` is set to `before_after`, it is mandatory to declare in `extender` param two functions,
|
140 | both for the `before` and `after` actions.
|
141 | So the `extender` parameter is not a function but an object containing two keys:
|
142 | * **before**: the function definition for `before` actions
|
143 | * **after** : the function definition for `after` actions
|
144 |
|
145 | ######Example
|
146 | ```javascript
|
147 | {
|
148 | //....
|
149 | //....
|
150 | "mode":"before_after"
|
151 | "extender":{
|
152 | "before": function(req,content,cType,callback){
|
153 | // before logic
|
154 | callback(null,{"Content extension before"});
|
155 | },
|
156 | "after": function(req,content,cType,callback){
|
157 | // after logic
|
158 | callback(null,{"Content extension after"});
|
159 | }
|
160 | }
|
161 | }
|
162 | ```
|
163 |
|
164 | ######Example of extend.js plugin definition
|
165 | ```javascript
|
166 | var plugins=[
|
167 | {
|
168 | "resource":"/resourceToExtend",
|
169 | "method":"GET",
|
170 | "mode":"override",
|
171 | "params":[query],
|
172 | "extender": function(req,content,cType,callback){
|
173 | callback(null,{"Content extension"});
|
174 | }
|
175 | },
|
176 | {
|
177 | "resource":"/OtherResourceToExtend",
|
178 | "method":"GET",
|
179 | "mode":"before",
|
180 | "params":[query],
|
181 | "extender": function(req,content,cType,callback){
|
182 | callback(null,{"Content extension"});
|
183 | }
|
184 | }
|
185 | ]
|
186 | ```
|
187 |
|
188 |
|
189 | ## <a name="reference"></a>Reference
|
190 | ### <a name="extend"></a>`extend(app)`
|
191 | Function that extends the API. The param `app` is the application that you want extend.
|
192 |
|
193 | ### <a name="install"></a>`install(app,extender,save)`
|
194 | Function that extends an API at runtime, without stopping and restarting your application.
|
195 | * `app` is the application that you want extend
|
196 | * `extender` is the plugin function described in section [plugin extender structure](#functionextension)
|
197 | * `save` if true, the plugin is saved in the `extend.js` file, being appended in the `plugin` array and becoming permanent.
|
198 |
|
199 | ######Example:
|
200 | ```javascript
|
201 | var express=require('express');
|
202 | var apiextender = require('apiextender');
|
203 | var app=express();
|
204 |
|
205 | apiextender.extend(app); // now your API is extensible
|
206 |
|
207 | // Define an endpoint wrapping apiextender install function that lets you to extend API on the fly
|
208 | // The access to this endpoint should be protectd with token privileges
|
209 | app.post("/installPlugin",function(req,res,next){
|
210 | // check for tokens
|
211 | //.....
|
212 |
|
213 | // install and run the plugin
|
214 | apiextender.install(app,req.body.pluginExtender,req.body.save || false);
|
215 | res.send({"status":"installed"});
|
216 | });
|
217 | ```
|
218 |
|
219 |
|
220 | ## <a name="examples"></a>`Examples`
|
221 | ### <a name="examplesapi"></a>`Examples: How to set an API extensible by plugins`
|
222 | From your application home directory type:
|
223 | ```shell
|
224 | $ cd /Your_App_Home
|
225 | $ npm install apiextender // install apiextender
|
226 | $ mkdir plugin // Create plugin container folder
|
227 | $ cd plugin // go into plugin folder
|
228 | $ vi extend.js // Create plugin container file
|
229 | ```
|
230 |
|
231 |
|
232 | Insert this content in `extend.js`.
|
233 | ```javascript
|
234 | var plugins=[];
|
235 | module.exports = plugins;
|
236 | ```
|
237 |
|
238 | Include the apiextender module in `app.js`
|
239 | ```javascript
|
240 | var express=require('express');
|
241 | var apiextender = require('apiextender');
|
242 | var app=express();
|
243 |
|
244 | apiextender.extend(app);
|
245 | ```
|
246 |
|
247 | ### <a name="examplesplugin"></a>`Examples: How to extend an API`
|
248 |
|
249 | Suppose we want to extend this API:
|
250 | ```javascript
|
251 | var express=require('express');
|
252 | var apiextender = require('apiextender');
|
253 | var app=express();
|
254 |
|
255 | apiextender.extend(app); // now your API is exensible
|
256 |
|
257 | app.get('/overrideOriginal', function(req, res){
|
258 | res.send({"response":"override"});
|
259 | });
|
260 | app.get('/beforeOriginal', function(req, res){
|
261 | var params=req.optionalParams || null;
|
262 | var response = params!=null ? {"response":"before", "response_before":params} : {"response":"before"}
|
263 | res.send(response);
|
264 | });
|
265 | app.get('/afterOriginal', function(req, res){
|
266 | res.send({"response":"after"});
|
267 | });
|
268 | app.get('/before_afterOriginal', function(req, res){
|
269 | var params=req.optionalParams || null;
|
270 | var response = params!=null ? {"response":"before_after", "response_before":params} : {"response":"before_after"}
|
271 | res.send(response);
|
272 | });
|
273 | ```
|
274 |
|
275 | #### <a name="exampleoverride"></a>`Override mode`
|
276 | With no plugin function defined, the endpoint response is:
|
277 | ```shell
|
278 | $ curl -i -H "Accept: application/json" -H "Content-Type: application/json" -X GET http://hostname/overrideOriginal
|
279 | X-Powered-By: Express
|
280 | Content-Type: application/json; charset=utf-8
|
281 | Content-Length: 23
|
282 | ETag: "35-6BXjKyRXlm+rSEU9a23z/g"
|
283 | Date: Fri, 11 Nov 2016 13:16:44 GMT
|
284 | Connection: keep-alive
|
285 |
|
286 | {"response":"override"} // original response because no extension function
|
287 | $
|
288 | ```
|
289 |
|
290 | Now we extend "/overrideOriginal" endpoint with a plugin, writing a plugin function in `extend.js`
|
291 | ```javascript
|
292 | var plugins=[
|
293 | {
|
294 | "resource":"/overrideOriginal", // extend overrideOriginal endpoint
|
295 | "method":"GET", // extend overrideOriginal endpoint in get method
|
296 | "mode":"override", // endpoint overrideOriginal must be overrided
|
297 | "params":[query], // express request "req.query" should be throw to extender function
|
298 | "extender": function(requestParams,content,cType,callback){ // this is the extender Function definition
|
299 | // the mode is set to "override" so "content,cType" are both null
|
300 | var username=requestParams.username || ""; // if username exist is set otherwise use a void string
|
301 | callback(null,{"response":"Hello " + username + " this is a plugin function that override"});
|
302 | // ^
|
303 | // |
|
304 | // No Error
|
305 | }
|
306 | }
|
307 | ];
|
308 | module.exports = plugins;
|
309 | ```
|
310 |
|
311 | Restart your application (or invoke `apiextender.install`) to apply the plugin function.
|
312 | If you curl the endpoint, you can notice that the original endpoint is not executed, being overridden by the plugin function:
|
313 | ```shell
|
314 | $ curl -i -H "Accept: application/json" -H "Content-Type: application/json" -X GET http://hostname/overrideOriginal
|
315 | X-Powered-By: Express
|
316 | Content-Type: application/json; charset=utf-8
|
317 | Content-Length: 60
|
318 | ETag: "35-6BXjKyRXlm+rSEU9a23z/g"
|
319 | Date: Fri, 11 Nov 2016 13:16:44 GMT
|
320 | Connection: keep-alive
|
321 |
|
322 | {"response":"Hello this is a plugin function that ovveride"} // original response "{"response":"before"}"
|
323 | // overrided by extension function
|
324 | $
|
325 | ```
|
326 |
|
327 |
|
328 | #### <a name="examplebefore"></a>`Before mode`
|
329 | With no plugin function defined, the endpoint response is:
|
330 | ```shell
|
331 | $ curl -i -H "Accept: application/json" -H "Content-Type: application/json" -X GET http://hostname/beforeOriginal
|
332 | X-Powered-By: Express
|
333 | Content-Type: application/json; charset=utf-8
|
334 | Content-Length: 51
|
335 | ETag: "35-6BXjKyRXlm+rSEU9a23z/g"
|
336 | Date: Fri, 11 Nov 2016 13:16:44 GMT
|
337 | Connection: keep-alive
|
338 |
|
339 | {"response":"before"} // original response because no extension function
|
340 | ```
|
341 |
|
342 | Now we extend "/beforeOriginal" endpoint with a plugin, writing a plugin function in `extend.js`
|
343 | ```javascript
|
344 | var plugins=[
|
345 | {
|
346 | "resource":"/beforeOriginal", // extend beforeOriginal endpoint
|
347 | "method":"GET", // extend beforeOriginal endpoint in get method
|
348 | "mode":"before", // plugin function must be executed before original endpoint beforeOriginal
|
349 | "params":[query], // express request "req.query" should be throw to extender function
|
350 | "extender": function(requestParams,content,cType,callback){ // this is the extender Function definition
|
351 | // the mode is set to "before" so "content,cType" are both null
|
352 | var username=requestParams.username || ""; // if username exist is set otherwise use a void string
|
353 | callback(null,{"optionalParams":"Hello " + username + " this is a plugin function that extend"});
|
354 | // ^
|
355 | // |
|
356 | // No Error
|
357 | }
|
358 | }
|
359 | ];
|
360 | module.exports = plugins;
|
361 | ```
|
362 |
|
363 | Restart your application (or invoke `apiextender.install`) to apply the plugin function.
|
364 | If you curl the endpoint, you can notice that the original endpoint is extended by the plugin function:
|
365 |
|
366 | ```shell
|
367 | $ curl -i -H "Accept: application/json" -H "Content-Type: application/json" -X GET http://hostname/beforeOriginal?username=Alex
|
368 | X-Powered-By: Express
|
369 | Content-Type: application/json; charset=utf-8
|
370 | Content-Length: 90
|
371 | ETag: "35-6BXjKyRXlm+rSEU9a23z/g"
|
372 | Date: Fri, 11 Nov 2016 13:16:44 GMT
|
373 | Connection: keep-alive
|
374 |
|
375 | {"response":"before","response_before":"Hello Alex this is a plugin function that extend"} // original and extended response
|
376 | ```
|
377 |
|
378 |
|
379 | #### <a name="exampleafter"></a>`After mode`
|
380 | With no plugin function defined, the endpoint response is:
|
381 | ```shell
|
382 | $ curl -i -H "Accept: application/json" -H "Content-Type: application/json" -X GET http://hostname/afterOriginal
|
383 | X-Powered-By: Express
|
384 | Content-Type: application/json; charset=utf-8
|
385 | Content-Length: 20
|
386 | ETag: "35-6BXjKyRXlm+rSEU9a23z/g"
|
387 | Date: Fri, 11 Nov 2016 13:16:44 GMT
|
388 | Connection: keep-alive
|
389 |
|
390 | {"response":"after"} // original response because no extension function
|
391 | $ curl -i -H "Accept: application/json" -H "Content-Type: application/json" -X GET http://hostname/afterOriginal?username=Alex
|
392 | X-Powered-By: Express
|
393 | Content-Type: application/json; charset=utf-8
|
394 | Content-Length: 20
|
395 | ETag: "35-6BXjKyRXlm+rSEU9a23z/g"
|
396 | Date: Fri, 11 Nov 2016 13:16:44 GMT
|
397 | Connection: keep-alive
|
398 |
|
399 | {"response":"after"} // original response because no extension function
|
400 | ```
|
401 |
|
402 | Now we extend "/afterOriginal" endpoint with a plugin, writing a plugin function in `extend.js`
|
403 | ```javascript
|
404 | var plugins=[
|
405 | {
|
406 | "resource":"/afterOriginal", // extend afterOriginal endpoint
|
407 | "method":"GET", // extend afterOriginal endpoint in get method
|
408 | "mode":"after", // plugin function must be executed after original endpoint afterOriginal
|
409 | "params":[query], // express request "req.query" should be throw to extender function
|
410 | "extender": function(requestParams,content,cType,callback){ // this is the extender Function definition
|
411 | // the mode is set to "after" so "content,cType" are both not null
|
412 | if(cType==="application/json"){ // if content is a Json
|
413 | var username=requestParams.username || ""; // if username exist is set otherwise use a void string
|
414 | content.after_response="Hello " + username + " this is a plugin function that extend";
|
415 | }
|
416 | callback(null,content);
|
417 | // ^
|
418 | // |
|
419 | // No Error
|
420 | }
|
421 | }
|
422 | ];
|
423 | module.exports = plugins;
|
424 | ```
|
425 |
|
426 | Restart your application (or invoke `apiextender.install`) to apply the plugin function.
|
427 | If you curl the endpoint, you can notice that the original endpoint is extended by the plugin function:
|
428 |
|
429 | ```shell
|
430 | $ curl -i -H "Accept: application/json" -H "Content-Type: application/json" -X GET http://hostname/afterOriginal
|
431 | X-Powered-By: Express
|
432 | Content-Type: application/json; charset=utf-8
|
433 | Content-Length: 82
|
434 | ETag: "35-6BXjKyRXlm+rSEU9a23z/g"
|
435 | Date: Fri, 11 Nov 2016 13:16:44 GMT
|
436 | Connection: keep-alive
|
437 |
|
438 | {"response":"after","after_response":Hello this is a plugin function that extend"} // original response extended
|
439 | $ curl -i -H "Accept: application/json" -H "Content-Type: application/json" -X GET http://hostname/afterOriginal?username=Alex
|
440 | X-Powered-By: Express
|
441 | Content-Type: application/json; charset=utf-8
|
442 | Content-Length: 87
|
443 | ETag: "35-6BXjKyRXlm+rSEU9a23z/g"
|
444 | Date: Fri, 11 Nov 2016 13:16:44 GMT
|
445 | Connection: keep-alive
|
446 |
|
447 | {"response":"after","after_response":Hello Alex this is a plugin function that extend"} // original response extended
|
448 | ```
|
449 |
|
450 |
|
451 | #### <a name="examplebefore_after"></a>`Examples: before_after mode`
|
452 | With no plugin function defined, the endpoint response is:
|
453 | ```shell
|
454 | $ curl -i -H "Accept: application/json" -H "Content-Type: application/json" -X GET http://hostname/before_afterOriginal
|
455 | X-Powered-By: Express
|
456 | Content-Type: application/json; charset=utf-8
|
457 | Content-Length: 27
|
458 | ETag: "35-6BXjKyRXlm+rSEU9a23z/g"
|
459 | Date: Fri, 11 Nov 2016 13:16:44 GMT
|
460 | Connection: keep-alive
|
461 |
|
462 | {"response":"before_after"} // original response because no extension function
|
463 | $ curl -i -H "Accept: application/json" -H "Content-Type: application/json" -X GET http://hostname/before_afterOriginal?username=Alex
|
464 | X-Powered-By: Express
|
465 | Content-Type: application/json; charset=utf-8
|
466 | Content-Length: 27
|
467 | ETag: "35-6BXjKyRXlm+rSEU9a23z/g"
|
468 | Date: Fri, 11 Nov 2016 13:16:44 GMT
|
469 | Connection: keep-alive
|
470 |
|
471 | {"response":"before_after"} // original response because no extension function
|
472 | ```
|
473 |
|
474 | Now we extend "/before_afterOriginal" endpoint with a plugin, writing a plugin function in `extend.js`
|
475 | ```javascript
|
476 | var plugins=[
|
477 | {
|
478 | "resource":"/before_afterOriginal", // extend before_afterOriginal endpoint
|
479 | "method":"GET", // extend before_afterOriginal endpoint in get method
|
480 | "mode":"before_after", // plugin function must be executed before and after original endpoint before_afterOriginal
|
481 | "params":[query], // express request "req.query" should be throw to extender function
|
482 | "extender":{
|
483 | "before": function(requestParams,content,cType,callback){ // this is the extender Function definition
|
484 | // the mode is set to "before_after" so in before function "content,cType" are both null
|
485 | var username=requestParams.username || ""; // if username exist is set otherwise use a void string
|
486 | callback(null,{"optionalParams":"Hello " + username + " this is a plugin function that extend"});
|
487 | // ^
|
488 | // |
|
489 | // No Error
|
490 | },
|
491 | "after": function(requestParams,content,cType,callback){ // this is the extender Function definition
|
492 | // the mode is set to "before _after" so in after function "content,cType" are both not null
|
493 | if(cType==="application/json"){ // if content is a Json
|
494 | var username=requestParams.username || ""; // if username exist is set otherwise use a void string
|
495 | content.after_response="Hello " + username + " this is a plugin function that extend";
|
496 | }
|
497 | callback(null,content);
|
498 | // ^
|
499 | // |
|
500 | // No Error
|
501 | }
|
502 | }
|
503 | ];
|
504 | module.exports = plugins;
|
505 | ```
|
506 |
|
507 | Restart your application (or invoke `apiextender.install`) to apply the plugin function.
|
508 | If you curl the endpoint, you can notice that the original endpoint is extended by the plugin function:
|
509 | ```shell
|
510 | $ curl -i -H "Accept: application/json" -H "Content-Type: application/json" -X GET http://hostname/before_afterOriginal
|
511 | X-Powered-By: Express
|
512 | Content-Type: application/json; charset=utf-8
|
513 | Content-Length: 154
|
514 | ETag: "35-6BXjKyRXlm+rSEU9a23z/g"
|
515 | Date: Fri, 11 Nov 2016 13:16:44 GMT
|
516 | Connection: keep-alive
|
517 |
|
518 | {
|
519 | "response":"before_after",
|
520 | "response_before":"Hello this is a plugin function that extend", // original and extended response
|
521 | "response_after":"Hello this is a plugin function that extend"
|
522 | }
|
523 |
|
524 | $ curl -i -H "Accept: application/json" -H "Content-Type: application/json" -X GET http://hostname/before_afterOriginal?username=Alex
|
525 | X-Powered-By: Express
|
526 | Content-Type: application/json; charset=utf-8
|
527 | Content-Length: 164
|
528 | ETag: "35-6BXjKyRXlm+rSEU9a23z/g"
|
529 | Date: Fri, 11 Nov 2016 13:16:44 GMT
|
530 | Connection: keep-alive
|
531 |
|
532 | {
|
533 | "response":"before_after",
|
534 | "response_before":"Hello Alex this is a plugin function that extend", // original and extended response
|
535 | "response_after":"Hello Alex this is a plugin function that extend"
|
536 | }
|
537 | ```
|
538 |
|
539 |
|
540 | #### <a name="exampleerror"></a>`Error throwing`
|
541 | With no plugin function defined, the endpoint response is:
|
542 | ```shell
|
543 | $ curl -i -H "Accept: application/json" -H "Content-Type: application/json" -X GET http://hostname/beforeOriginal
|
544 | X-Powered-By: Express
|
545 | Content-Type: application/json; charset=utf-8
|
546 | Content-Length: 51
|
547 | ETag: "35-6BXjKyRXlm+rSEU9a23z/g"
|
548 | Date: Fri, 11 Nov 2016 13:16:44 GMT
|
549 | Connection: keep-alive
|
550 |
|
551 | {"response":"before"} // original response because no extension function
|
552 | $ // now with username param (no response changes)
|
553 | $ curl -i -H "Accept: application/json" -H "Content-Type: application/json" -X GET http://hostname/beforeOriginal?username=Alex
|
554 | X-Powered-By: Express
|
555 | Content-Type: application/json; charset=utf-8
|
556 | Content-Length: 51
|
557 | ETag: "35-6BXjKyRXlm+rSEU9a23z/g"
|
558 | Date: Fri, 11 Nov 2016 13:16:44 GMT
|
559 | Connection: keep-alive
|
560 |
|
561 | {"response":"before"} // original response because no extension function
|
562 | ```
|
563 | Now we extend "/beforeOriginal" endpoint with a plugin, writing a plugin function in `extend.js`. Starting from the
|
564 | example [`Examples:before mode`](#examplebefore), we edit the plugin function to throw an error if no `username` field is sent.
|
565 | ```javascript
|
566 | var plugins=[
|
567 | {
|
568 | "resource":"/beforeOriginal", // extend beforeOriginal endpoint
|
569 | "method":"GET", // extend beforeOriginal endpoint in get method
|
570 | "mode":"before", // endpoint beforeOriginal must be overrided
|
571 | "params":[query], // express request "req.query" should be throw to extender function
|
572 | "extender": function(requestParams,content,cType,callback){ // this is the extender Function definition
|
573 | // the mode is set to "override" so "content,cType" are both null
|
574 | var username=requestParams.username || null; // if username exist is set otherwise use a void string
|
575 | if(!username)
|
576 | callback({"error_code":"400", "error_message":"no username field"},null);
|
577 | else
|
578 | callback(null,{"response":"Hello " + username + " this is a plugin function that override"});
|
579 | // ^
|
580 | // |
|
581 | // No Error
|
582 | }
|
583 | }
|
584 | ];
|
585 | module.exports = plugins;
|
586 | ```
|
587 |
|
588 | Restart your application (or invoke `apiextender.install`) to apply the plugin function.
|
589 | If you curl the endpoint, you can notice that the original endpoint is not executed after the plugin function,
|
590 | because no username field is sent. This is due to the fact that `before extension` function stops the execution
|
591 | with an error message to the apiextender callback:
|
592 |
|
593 | ```shell
|
594 | $ curl -i -H "Accept: application/json" -H "Content-Type: application/json" -X GET http://hostname/beforeOriginal
|
595 | X-Powered-By: Express
|
596 | Content-Type: application/json; charset=utf-8
|
597 | Content-Length: 37
|
598 | ETag: "35-6BXjKyRXlm+rSEU9a23z/g"
|
599 | Date: Fri, 11 Nov 2016 13:16:44 GMT
|
600 | Connection: keep-alive
|
601 |
|
602 | {"error_message":"no username field"} // only error_message from extended function response
|
603 | $
|
604 | $ curl -i -H "Accept: application/json" -H "Content-Type: application/json" -X GET http://hostname/beforeOriginal?username=Alex
|
605 | X-Powered-By: Express
|
606 | Content-Type: application/json; charset=utf-8
|
607 | Content-Length: 90
|
608 | ETag: "35-6BXjKyRXlm+rSEU9a23z/g"
|
609 | Date: Fri, 11 Nov 2016 13:16:44 GMT
|
610 | Connection: keep-alive
|
611 |
|
612 | {"response":"before","response_before":"Hello Alex this is a plugin function that extend"} // original and extended response
|
613 | ```
|
614 |
|
615 |
|
616 | #### <a name="examplecomplete"></a>`Examples: A Complete Eexample of plugin extension`
|
617 | With no plugin function defined, the endpoint response is:
|
618 | ```shell
|
619 | $ curl -i -H "Accept: application/json" -H "Content-Type: application/json" -X GET http://hostname/overrideOriginal?username=Alex
|
620 | X-Powered-By: Express
|
621 | Content-Type: application/json; charset=utf-8
|
622 | Content-Length: 23
|
623 | ETag: "35-6BXjKyRXlm+rSEU9a23z/g"
|
624 | Date: Fri, 11 Nov 2016 13:16:44 GMT
|
625 | Connection: keep-alive
|
626 |
|
627 | {"response":"override"} // original response because no extension function
|
628 | $
|
629 | $ curl -i -H "Accept: application/json" -H "Content-Type: application/json" -X GET http://hostname/beforeOriginal?username=Alex
|
630 | X-Powered-By: Express
|
631 | Content-Type: application/json; charset=utf-8
|
632 | Content-Length: 21
|
633 | ETag: "35-6BXjKyRXlm+rSEU9a23z/g"
|
634 | Date: Fri, 11 Nov 2016 13:16:44 GMT
|
635 | Connection: keep-alive
|
636 |
|
637 | {"response":"before"} // original response because no extension function
|
638 | $
|
639 | $ curl -i -H "Accept: application/json" -H "Content-Type: application/json" -X GET http://hostname/afterOriginal?username=Alex
|
640 | X-Powered-By: Express
|
641 | Content-Type: application/json; charset=utf-8
|
642 | Content-Length: 20
|
643 | ETag: "35-6BXjKyRXlm+rSEU9a23z/g"
|
644 | Date: Fri, 11 Nov 2016 13:16:44 GMT
|
645 | Connection: keep-alive
|
646 |
|
647 | {"response":"after"} // original response because no extension function
|
648 | $
|
649 | $ curl -i -H "Accept: application/json" -H "Content-Type: application/json" -X GET http://hostname/before_afterOriginal?username=Alex
|
650 | X-Powered-By: Express
|
651 | Content-Type: application/json; charset=utf-8
|
652 | Content-Length: 27
|
653 | ETag: "35-6BXjKyRXlm+rSEU9a23z/g"
|
654 | Date: Fri, 11 Nov 2016 13:16:44 GMT
|
655 | Connection: keep-alive
|
656 |
|
657 | {"response":"before_after"} // original response because no extension function
|
658 | ```
|
659 |
|
660 | Now we extend "/....Original" endpoint with a plugin, writing a plugin function in `extend.js`.
|
661 |
|
662 | ```javascript
|
663 | var plugins=[
|
664 | {
|
665 | "resource":"/overrideOriginal", // extend overrideOriginal endpoint
|
666 | "method":"GET", // extend overrideOriginal endpoint in get method
|
667 | "mode":"override", // endpoint overrideOriginal must be overrided
|
668 | "params":[query], // express request "req.query" should be throw to extender function
|
669 | "extender": function(requestParams,content,cType,callback){ // this is the extender Function definition
|
670 | // the mode is set to "override" so "content,cType" are both null
|
671 | var username=requestParams.username || ""; // if username exist is set otherwise use a void string
|
672 | callback(null,{"response":"Hello " + username + " this is a plugin function that override"});
|
673 | // ^
|
674 | // |
|
675 | // No Error
|
676 | }
|
677 | },
|
678 | {
|
679 | "resource":"/overrideOriginal", // extend overrideOriginal endpoint
|
680 | "method":"GET", // extend overrideOriginal endpoint in get method
|
681 | "mode":"before", // endpoint overrideOriginal must be overrided
|
682 | "params":[query], // express request "req.query" should be throw to extender function
|
683 | "extender": function(requestParams,content,cType,callback){ // this is the extender Function definition
|
684 | // the mode is set to "override" so "content,cType" are both null
|
685 | var username=requestParams.username || null; // if username exist is set otherwise use a void string
|
686 | if(!username)
|
687 | callback({"error_code":"400", "error_message":"no username field"},null);
|
688 | else
|
689 | callback(null,{"response":"Hello " + username + " this is a plugin function that override"});
|
690 | // ^
|
691 | // |
|
692 | // No Error
|
693 | }
|
694 | },
|
695 | {
|
696 | "resource":"/afterOriginal", // extend afterOriginal endpoint
|
697 | "method":"GET", // extend afterOriginal endpoint in get method
|
698 | "mode":"after", // plugin function must be executed after original endpoint afterOriginal
|
699 | "params":[query], // express request "req.query" should be throw to extender function
|
700 | "extender": function(requestParams,content,cType,callback){ // this is the extender Function definition
|
701 | // the mode is set to "after" so "content,cType" are both not null
|
702 | if(cType==="application/json"){ // if content is a Json
|
703 | var username=requestParams.username || ""; // if username exist is set otherwise use a void string
|
704 | content.after_response="Hello " + username + " this is a plugin function that extend";
|
705 | }
|
706 | callback(null,content);
|
707 | // ^
|
708 | // |
|
709 | // No Error
|
710 | }
|
711 | },
|
712 | {
|
713 | "resource":"/before_afterOriginal", // extend before_afterOriginal endpoint
|
714 | "method":"GET", // extend before_afterOriginal endpoint in get method
|
715 | "mode":"before_after", // plugin function must be executed before and after original endpoint before_afterOriginal
|
716 | "params":[query], // express request "req.query" should be throw to extender function
|
717 | "extender":{
|
718 | "before": function(requestParams,content,cType,callback){ // this is the extender Function definition
|
719 | // the mode is set to "before_after" so in before function "content,cType" are both null
|
720 | var username=requestParams.username || ""; // if username exist is set otherwise use a void string
|
721 | callback(null,{"optionalParams":"Hello " + username + " this is a plugin function that extend"});
|
722 | // ^
|
723 | // |
|
724 | // No Error
|
725 | },
|
726 | "after": function(requestParams,content,cType,callback){ // this is the extender Function definition
|
727 | // the mode is set to "before _after" so in after function "content,cType" are both not null
|
728 | if(cType==="application/json"){ // if content is a Json
|
729 | var username=requestParams.username || ""; // if username exist is set otherwise use a void string
|
730 | content.after_response="Hello " + username + " this is a plugin function that extend";
|
731 | }
|
732 | callback(null,content);
|
733 | // ^
|
734 | // |
|
735 | // No Error
|
736 | }
|
737 | }
|
738 | ];
|
739 | module.exports = plugins;
|
740 | ```
|
741 |
|
742 | Restart your application (or invoke `apiextender.install`) to apply the plugin function.
|
743 | If you curl the endpoint, you can notice that the original endpoint is extended by the plugin function:
|
744 | ```shell
|
745 | $ curl -i -H "Accept: application/json" -H "Content-Type: application/json" -X GET http://hostname/overrideOriginal?username=Alex
|
746 | X-Powered-By: Express
|
747 | Content-Type: application/json; charset=utf-8
|
748 | Content-Length: 65
|
749 | ETag: "35-6BXjKyRXlm+rSEU9a23z/g"
|
750 | Date: Fri, 11 Nov 2016 13:16:44 GMT
|
751 | Connection: keep-alive
|
752 |
|
753 | {"response":"Hello Alex this is a plugin function that ovveride"} // original response "{"response":"before"}"
|
754 | // overrided by extension function
|
755 | $
|
756 | $ curl -i -H "Accept: application/json" -H "Content-Type: application/json" -X GET http://hostname/beforeOriginal?username=Alex
|
757 | X-Powered-By: Express
|
758 | Content-Type: application/json; charset=utf-8
|
759 | Content-Length: 90
|
760 | ETag: "35-6BXjKyRXlm+rSEU9a23z/g"
|
761 | Date: Fri, 11 Nov 2016 13:16:44 GMT
|
762 | Connection: keep-alive
|
763 |
|
764 | {"response":"before","response_before":"Hello Alex this is a plugin function that extend"} // original and extended response
|
765 | $
|
766 | $ curl -i -H "Accept: application/json" -H "Content-Type: application/json" -X GET http://hostname/afterOriginal?username=Alex
|
767 | X-Powered-By: Express
|
768 | Content-Type: application/json; charset=utf-8
|
769 | Content-Length: 88
|
770 | ETag: "35-6BXjKyRXlm+rSEU9a23z/g"
|
771 | Date: Fri, 11 Nov 2016 13:16:44 GMT
|
772 | Connection: keep-alive
|
773 |
|
774 | {"response":"after","after_response":Hello Alex this is a plugin function that extend"} // original response extended
|
775 | $
|
776 | $ curl -i -H "Accept: application/json" -H "Content-Type: application/json" -X GET http://hostname/before_afterOriginal?username=Alex
|
777 | X-Powered-By: Express
|
778 | Content-Type: application/json; charset=utf-8
|
779 | Content-Length: 165
|
780 | ETag: "35-6BXjKyRXlm+rSEU9a23z/g"
|
781 | Date: Fri, 11 Nov 2016 13:16:44 GMT
|
782 | Connection: keep-alive
|
783 |
|
784 | {
|
785 | "response":"before_after",
|
786 | "response_before":"Hello Alex this is a plugin function that extend", // original and extended response
|
787 | "response_after":"Hello Alex this is a plugin function that extend"
|
788 | }
|
789 | ```
|
790 |
|
791 |
|
792 |
|
793 | #### <a name="exampleonFly"></a>`Extend API on the fly at runtime`
|
794 | To extend an API on the fly as described in reference section [install(app,extender,save)](#install), we need to define an
|
795 | endpoint that wraps apiextender install function. Add these code lines to you app.js:
|
796 | ```javascript
|
797 | var express=require('express');
|
798 | var apiextender = require('apiextender');
|
799 | var app=express();
|
800 |
|
801 | // .....
|
802 | // Old app.js logic
|
803 | // .....
|
804 |
|
805 | // Define an endpoint that wrap apiextender install function that lets you to extend API on the fly
|
806 | // The access to this endpoint should be protectd with token privileges
|
807 | app.post("/installPlugin",function(req,res,next){
|
808 | // check for tokens
|
809 | //.....
|
810 |
|
811 | apiextender.install(app,req.body.pluginExtender,req.body.save || false);
|
812 | res.send({"status":"installed"});
|
813 | });
|
814 | ```
|
815 |
|
816 | With no plugin function defined, the endpoint response is:
|
817 | ```shell
|
818 | $ curl -i -H "Accept: application/json" -H "Content-Type: application/json" -X GET http://hostname/overrideOriginal?username=Alex
|
819 | X-Powered-By: Express
|
820 | Content-Type: application/json; charset=utf-8
|
821 | Content-Length: 23
|
822 | ETag: "35-6BXjKyRXlm+rSEU9a23z/g"
|
823 | Date: Fri, 11 Nov 2016 13:16:44 GMT
|
824 | Connection: keep-alive
|
825 |
|
826 | {"response":"override"} // original response because no extension function
|
827 | ```
|
828 |
|
829 | Now we extend "/overrideOriginal" endpoint with a plugin, installed on the fly without defining it in `extend.js`.
|
830 | To install, just call `/installPlugin` in POST:
|
831 | ```shell
|
832 | EXTENDER='{
|
833 | "pluginExtender":{
|
834 | "resource":"/overrideOriginal", // extend overrideOriginal endpoint
|
835 | "method":"GET", // extend overrideOriginal endpoint in get method
|
836 | "mode":"override", // endpoint overrideOriginal must be overrided
|
837 | "params":[query], // express request "req.query" should be throw to extender function
|
838 | "extender": function(requestParams,content,cType,callback){ // this is the extender Function definition
|
839 | // the mode is set to "override" so "content,cType" are both null
|
840 | var username=requestParams.username || ""; // if username exist is set otherwise use a void string
|
841 | callback(null,{"response":"Hello " + username + " this is a plugin function that override"});
|
842 | // ^
|
843 | // |
|
844 | // No Error
|
845 | }
|
846 | },
|
847 | "save":false; // not save extenson plugin
|
848 | }'
|
849 | $
|
850 | $ curl -i -H "Accept: application/json" -H "Content-Type: application/json" -X POST http://hostname/overrideOriginal?username=Alex
|
851 | -d $EXTENDER
|
852 |
|
853 | X-Powered-By: Express
|
854 | Content-Type: application/json; charset=utf-8
|
855 | Content-Length: 22
|
856 | ETag: "35-6BXjKyRXlm+rSEU9a23z/g"
|
857 | Date: Fri, 11 Nov 2016 13:16:44 GMT
|
858 | Connection: keep-alive
|
859 |
|
860 | {"status":"installed"} // plugin extension installed
|
861 | ```
|
862 |
|
863 |
|
864 | Restart your application (or invoke `apiextender.install`) to apply the plugin function.
|
865 |
|
866 |
|
867 |
|
868 | **Without restarting** your application, if you curl the endpoint, you can notice that the original endpoint is extended by the plugin function:
|
869 | ```shell
|
870 | $ curl -i -H "Accept: application/json" -H "Content-Type: application/json" -X GET http://hostname/overrideOriginal?username=Alex
|
871 | X-Powered-By: Express
|
872 | Content-Type: application/json; charset=utf-8
|
873 | Content-Length: 65
|
874 | ETag: "35-6BXjKyRXlm+rSEU9a23z/g"
|
875 | Date: Fri, 11 Nov 2016 13:16:44 GMT
|
876 | Connection: keep-alive
|
877 |
|
878 | {"response":"Hello Alex this is a plugin function that ovveride"} // original response "{"response":"before"}"
|
879 | // overrided by extension function
|
880 | $
|
881 | ```
|
882 |
|
883 | License - "MIT License"
|
884 | -----------------------
|
885 |
|
886 | MIT License
|
887 |
|
888 | Copyright (c) 2016 aromanino
|
889 |
|
890 | Permission is hereby granted, free of charge, to any person obtaining a copy
|
891 | of this software and associated documentation files (the "Software"), to deal
|
892 | in the Software without restriction, including without limitation the rights
|
893 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
894 | copies of the Software, and to permit persons to whom the Software is
|
895 | furnished to do so, subject to the following conditions:
|
896 |
|
897 | The above copyright notice and this permission notice shall be included in all
|
898 | copies or substantial portions of the Software.
|
899 |
|
900 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
901 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
902 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
903 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
904 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
905 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
906 | SOFTWARE.
|
907 |
|
908 | Author
|
909 | ------
|
910 | Alessandro Romanino ([a.romanino@gmail.com](mailto:a.romanino@gmail.com))<br>
|
911 | Guido Porruvecchio ([guido.porruvecchio@gmail.com](mailto:guido.porruvecchio@gmail.com))
|
912 |
|
913 | Contributors
|
914 | ------
|
915 | CRS4 Microservice Core Team ([cmc.smartenv@crs4.it](mailto:cmc.smartenv@crs4.it))
|