1 | #Contents
|
2 |
|
3 | - [QEWD-Up Life Cycle Events](#qewd-up-life-cycle-events)
|
4 | - [Life Cycle Points](#life-cycle-points)
|
5 | - [Monolithic QEWD-Up Application](#monolithic-qewd-up-application)
|
6 | - [Available Life-Cycle Hooks in Chronological Sequence](#available-life-cycle-hooks-in-chronological-sequence)
|
7 | - [MicroService-based QEWD-Up Application](#microService-based-qewd-up-application)
|
8 | - [Available MicroService Life-Cycle Hooks in Chronological Sequence](#available-microservice-life-cycle-hooks-in-chronological-sequence)
|
9 | - [Life-Cycle Hook Specifications](#life-cycle-hook-specifications)
|
10 | - [addMiddleware](#addmiddleware)
|
11 | - [onStarted](#onstarted)
|
12 | - [onWSRequest](#onwsrequest)
|
13 | - [onWorkerLoad](#onWorkerLoad)
|
14 | - [beforeHandler](#beforehandler)
|
15 | - [onMSResponse](#onmsresponse)
|
16 | - [onOrchResponse](#onorchresponse)
|
17 | - [onWSResponse](#onwsresponse)
|
18 |
|
19 | # QEWD-Up Life Cycle Events
|
20 |
|
21 | QEWD-Up provides hooks at various points in the life-cycle of an API, allowing you fine control over the behaviour of your API, whilst allowing you to document your API Life-Cycles in an easy-to-find and easy-to-understand way.
|
22 |
|
23 | # Life Cycle Points
|
24 |
|
25 | The points at which you can intercept the life-cycle of an API depends on whether your application is a monolithic one or implemented as a set of MicroServices.
|
26 |
|
27 | ## Monolithic QEWD-Up Application
|
28 |
|
29 | Understanding how and why to control the life-cycle of your monolithic application APIs requires an understanding of QEWD's Master Process / Worker Process architecture. **Note**: this applies to both Native and Docker versions of QEWD Monolithic applications.
|
30 |
|
31 | ### The QEWD Run-Time Environment
|
32 |
|
33 | This is described [diagrammatically here](https://www.slideshare.net/robtweed/qewdjs-json-web-tokens-microservices) in slides 4 - 40.
|
34 |
|
35 | In summary, all incoming requests are received by QEWD's Master Process. In the case of REST API requests, these are received by the embedded Express web server.
|
36 |
|
37 | The requests are then placed in a queue within the Master Process. By default, any request payloads are assumed to be JSON-formatted, and the request is passed unchanged from Express to QEWD's queue.
|
38 |
|
39 | When a new item is added to the queue, the Master Process tries to send it to an available Worker Process. By default, QEWD-Up configures QEWD with a pool of 2 Worker Processes, but you can modify this to whatever pool size you require in the *config.json* file.
|
40 |
|
41 | Your API handler module is invoked within the Worker process that receives the incoming request. Your API handler module has exclusive access to the Worker process, so, unusually for a Node.js environment, your handler module does not need to be concerned about concurrency. Additionally, the Worker process has synchronous access to the integrated persistent JSON database.
|
42 |
|
43 | Your API handler module can make use of any 3rd party Node.js modules you wish to use, and, subject to network availability, your API handler module can also send requests to external services and resources.
|
44 |
|
45 | Once your handler logic is completed, you must invoke QEWD's *finished* function which does two key things:
|
46 |
|
47 | - it tells QEWD that you have finished with the Worker process;
|
48 | - it allows you to define the JSON response object that you want to return to the REST client
|
49 |
|
50 | The *finished* function actually sends the JSON response object to the QEWD Master process, and, on receipt of this message, the Master process returns the Worker process back to the available pool, ready to handle the next queued request.
|
51 |
|
52 | Finally, the Master process sends the JSON response to the REST Client. By default, no changes are made to the JSON response by the Master process: QEWD assumes that the REST Client is expecting JSON-formatted.
|
53 |
|
54 | ### Available Life-Cycle Hooks in Chronological Sequence
|
55 |
|
56 | - **addMiddleware**: triggered during QEWD's Master process startup, just after its integrated Web Server (Express by default) is configured. This hook allows you to customise the Web Server middleware, eg to handle XML, set payload size limits etc. It can optionally be used together with the **config.json** file's *qewd.bodyParser* property to customise, replace or override the standard JSON body parsing.
|
57 |
|
58 | - **onStarted**: triggered after QEWD's Master process starts and before any APIs are allowed to be handled. It gives you access to QEWD's *this* object and can be used for complex situations, for example where you want to integrate QEWD with a third-party module that provides a turnkey server environment. You can often use this kook instead of the **addMiddleware** one, as it also gives you access to the Web Server's *app* object. This hook is invoked in QEWD's Master process.
|
59 |
|
60 | - **onWSRequest**: triggered as part of your Web Server's Middleware chain, before each incoming API request is routed to its appropriate handler method. Its usual purpose is to allow you to modify some aspect(s) of the incoming request, for example modifying and/or adding HTTP headers, and/or modifying the request payload. This hook is invoked in the QEWD Master process, before the incoming request is queued, and applies to **ALL** incoming requests.
|
61 |
|
62 | - **beforeHandler**: triggered in the QEWD Worker process that handles the incoming request **before** your handler method is invoked. This hook is applied to **all** incoming requests. Typically it is used to define logic that should be applied to all incoming requests, for example to test user authentication. The *beforeHandler* can be defined to accept or reject requests based on their headers and/or content, and return an appropriate error response for requests that are rejected. Rejected messages will **not** invoke your handler method. Because this hook is invoked in the QEWD Worker process, it has access to the integrated persistent JSON database and/or QEWD Session (if appropriate).
|
63 |
|
64 | - **onWSResponse**: triggered in the QEWD Master process on receipt of the response from the Worker process and before the JSON response is returned to the REST Client. Typically, this hook is used to modify the response content, eg adding or changing response headers and/or modifying the payload format (eg from JSON to XML). This hook is applied to **all** responses. **Note**: if you implement an *onWSResponse* hook, then you take on responsibility for sending all responses back to the REST Client, including error responses.
|
65 |
|
66 |
|
67 | ## MicroService-based QEWD-Up Application
|
68 |
|
69 | Understanding how and why to control the life-cycle of your MicroService application APIs requires an understanding of:
|
70 |
|
71 | - **each MicroService instance**: Each MicroService is an instance of QEWD, with its Master Process / Worker Process architecture;
|
72 | - **how QEWD's MicroServices interoperate**. In summary, all incoming requests from REST Clients are handled by a MicroService known as the *Orchestrator*. This communicates with all your other MicroService QEWD instances over persistent WebSocket connections. The *Orchestrator* MicroService routes incoming requests to the appropriate MicroService that is designated to handle that request. Responses are returned to the *Orchestrator* which then forwards them to the REST client.
|
73 |
|
74 | ### The QEWD Run-Time Environment
|
75 |
|
76 | Each MicroService (including the *Orchestrator*) is a complete QEWD instance, described [diagrammatically here](https://www.slideshare.net/robtweed/qewdjs-json-web-tokens-microservices) in slides 4 - 40.
|
77 |
|
78 | How the MicroServices communicate and interoperate is described [diagrammatically here](https://www.slideshare.net/robtweed/qewdjs-json-web-tokens-microservices) in slides 100 - 115.
|
79 |
|
80 | In summary, all incoming requests are received by each QEWD instance's Master Process.
|
81 |
|
82 | In the case of REST API requests sent from a REST Client and received by the Orchestrator, these are received by the embedded web server (Express by default).
|
83 |
|
84 | In the case of WebSocket messages received by your other MicroService QEWD instances, these are handled by the standard Node.js **socket.io** module which is included and automatically configured within QEWD.
|
85 |
|
86 | Requests received by the *Orchestrator* are repackaged as JSON messages and immediately routed within its Master Process via the appropriate WebSocket connection to the MicroService designated to handle the request.
|
87 |
|
88 | Requests received by your other MicroServices are placed in a queue within the Master Process. When a new item is added to this queue, the Master Process tries to send it to an available Worker Process. By default, QEWD-Up configures QEWD with a pool of 2 Worker Processes, but you can modify this to whatever pool size you require in the *config.json* file.
|
89 |
|
90 | Your API handler module is invoked within the Worker process that receives the incoming request. Your API handler module has exclusive access to the Worker process, so, unusually for a Node.js environment, your handler module does not need to be concerned about concurrency. Additionally, the Worker process has synchronous access to the integrated persistent JSON database.
|
91 |
|
92 | Your API handler module can make use of any 3rd party Node.js modules you wish to use, and, subject to network availability, your API handler module can also send requests to external services and resources.
|
93 |
|
94 | Once your handler logic is completed, you must invoke QEWD's *finished* function which does two key things:
|
95 |
|
96 | - it tells QEWD that you have finished with the Worker process;
|
97 | - it allows you to define the JSON response object that you want to return to the REST client
|
98 |
|
99 | The *finished* function actually sends the JSON response object to the QEWD Master process, and, on receipt of this message, the Master process returns the Worker process back to the available pool, ready to handle the next queued request.
|
100 |
|
101 | Your MicroService's Master process returns the JSON response to the *Orchestrator* MicroService via the WebSocket connection.
|
102 |
|
103 | On receipt of the response from another MicroService, the *Orchestrator* returns the response to the REST Client. By default, no changes are made to the JSON response by the Orchestrator
|
104 |
|
105 |
|
106 | ### Available MicroService Life-Cycle Hooks in Chronological Sequence
|
107 |
|
108 |
|
109 | #### Startup of Each MicroService
|
110 |
|
111 | - **addMiddleware**: this is appropriate only to the *Orchestrator*. It is triggered during QEWD's Master process startup, just after its integrated Web Server (Express by default) is configured. This hook allows you to customise the Web Server middleware, eg to handle XML, set payload size limits etc. It can optionally be used together with the **config.json** file's *qewd.bodyParser* property to customise, replace or override the standard JSON body parsing.
|
112 |
|
113 | - **onStarted**: this is triggered after QEWD's Master process starts and before any APIs are allowed to be handled. It gives you access to QEWD's *this* object and can be used for complex situations, for example where you want to integrate QEWD with a third-party module that provides a turnkey server environment. On the *Orchestrator* you can often use this kook instead of the **addMiddleware** one, as it also gives you access to the Web Server's *app* object. This hook is invoked in QEWD's Master process.
|
114 |
|
115 |
|
116 | #### Request Received by Orchestrator
|
117 |
|
118 | - **onWSRequest**: triggered as part of your Orchestrator's Middelware chain, and before each incoming API request is routed to its appropriate handling MicroService destination. Its usual purpose is to allow you to modify some aspect(s) of the incoming request, for example modifying and/or adding HTTP headers, and/or modifying the request payload. This hook is invoked in the QEWD Master process, before the incoming request is forwarded to its handling MicroService, and applies to **ALL** incoming requests.
|
119 |
|
120 |
|
121 | #### Request forwarded from Orchestrator Received by Handling MicroService
|
122 |
|
123 | - **onWorkerLoad**: triggered when a new QEWD Worker process starts and loads the generated Application Module that contains all your application's handler modules. A typical use for this event hook is to load one or more third-party Node.js modules that should be available to all your handler modules.
|
124 |
|
125 | - **beforeHandler**: triggered in the QEWD Worker process that handles the incoming request **before** your handler method is invoked. This hook is applied to **all** incoming requests. Typically it is used to define logic that should be applied to all incoming requests. A typical use for this event hook in a MicroService is to create and maintain a QEWD Session, linked to the JWT attached to the request. Rejected messages will **not** invoke your handler method. Because this hook is invoked in the QEWD Worker process, it has access to the integrated persistent JSON database.
|
126 |
|
127 | - **onMSResponse**: applied to a specific API and triggered on the Master process on receipt of the response from the Worker process that handled the API. This allows you to intercept the response from your API handler and optionally invoke one or more additional API requests that may be sent to be handled on other MicroServices. This allows you, for example, to build up a composite response from a series of chained APIs spread across multiple MicroServices
|
128 |
|
129 | #### Response received by Orchestrator from MicroService
|
130 |
|
131 | - **onOrchResponse**: applied to a specific API and triggered on the Orchestrator's Master process before the response is returned to the REST Client. This hook allows you to do several things, including:
|
132 |
|
133 | - repackaging/reformatting the response
|
134 | - intercepting the flow and sending out one or more new API requests to the MicroServices that will handle them, compiling a composite response that will ultimately be returned to the REST Client
|
135 |
|
136 | - **onWSResponse**: triggered in the QEWD Master process on receipt of **all** responses from a MicroService and before the JSON response is returned to the REST Client. Typically, this hook is used to globally modify the response content, eg adding or changing response headers and/or modifying the payload format (eg from JSON to XML). **Note**: if you implement an *onWSResponse* hook, then you take on responsibility for sending all responses back to the REST Client, including error responses.
|
137 |
|
138 |
|
139 | # Life-Cycle Hook Specifications
|
140 |
|
141 | ## addMiddleware
|
142 |
|
143 | ### Filename and Directory location
|
144 |
|
145 | The filename is **addMiddleware.js**. The name is case-sensitive.
|
146 |
|
147 | Its placement depends on what mode you are using and/or microservice you are specifying it for
|
148 |
|
149 | #### Monolith
|
150 |
|
151 | ~/dockerExample
|
152 | |
|
153 | |_ addMiddleware.js
|
154 | |
|
155 | |_ configuration
|
156 | |
|
157 | |_ apis
|
158 | |
|
159 |
|
160 | #### MicroService: Orchestrator
|
161 |
|
162 | ~/microserviceExample
|
163 | |
|
164 | |_ configuration
|
165 | |
|
166 | |_ orchestrator
|
167 | | |
|
168 | | |_ addMiddleware.js
|
169 |
|
170 |
|
171 | #### MicroService: Other Microservice
|
172 |
|
173 | *eg* for a MicroService named *login_service*:
|
174 |
|
175 | ~/microserviceExample
|
176 | |
|
177 | |_ configuration
|
178 | |
|
179 | |_ login_service
|
180 | | |
|
181 | | |_ addMiddleware.js
|
182 |
|
183 |
|
184 | ### Module structure
|
185 |
|
186 | Your *addModule.js* file should export a function of the structure shown below:
|
187 |
|
188 | module.exports = function(bodyParser, app, qewdRouter, config) {
|
189 | // add/ define / configure your WebServer middleware
|
190 | };
|
191 |
|
192 | ### Module Function Arguments
|
193 |
|
194 | #### bodyParser
|
195 |
|
196 | This is the bodyParser object that has been loaded and configured for the Web Server. By default, QEWD uses the *body-parser* module and configures it to handle and parse JSON content.
|
197 |
|
198 | if you want to use the *body-parser* module, but apply different/additional configuration settings to it, then, in your *config.json* file, use the property *qewd.bodyParser* to specify it.
|
199 |
|
200 | For example, in Monolith mode:
|
201 |
|
202 | {
|
203 | "qewd_up": true,
|
204 | "qewd": {
|
205 | "bodyParser": "body-parser"
|
206 | }
|
207 | }
|
208 |
|
209 | or in the Orchestrator MicroService:
|
210 |
|
211 | {
|
212 | "qewd_up": true,
|
213 | "orchestrator": {
|
214 | "qewd": {
|
215 | "bodyParser": "body-parser"
|
216 | }
|
217 | }
|
218 | }
|
219 |
|
220 | This will stop QEWD from configuring it automatically.
|
221 |
|
222 | If you want to use a different bodyParser module, then you must first specify it in your *config.json* file, eg:
|
223 |
|
224 | {
|
225 | "qewd_up": true,
|
226 | "qewd": {
|
227 | "bodyParser": "body-parse"
|
228 | }
|
229 | }
|
230 |
|
231 | and then configure it within your *addMiddleware** module.
|
232 |
|
233 |
|
234 | #### app
|
235 |
|
236 | This is the WebServer (eg Express) object. You can use this to, for example, specify your own custom *app.use* directives.
|
237 |
|
238 | For example, here is an *addMiddleware* module that configures payload size limits (assuming *qewd.bodyParser* was specified in the *config.json* file):
|
239 |
|
240 |
|
241 | module.exports = function(bodyParser, app) {
|
242 | app.use(bodyParser.json({limit: '1mb'}));
|
243 | app.use(bodyParser.urlencoded({limit: '1mb', extended: true}));
|
244 | };
|
245 |
|
246 | #### qewdRouter
|
247 |
|
248 | This is QEWD's REST API routing function. You can use this to insert a middleware chain prior to QEWD taking over the API routing. For example:
|
249 |
|
250 | var cors = require('cors');
|
251 | module.exports = function(bodyParser, app, qewdRouter) {
|
252 | app.use('/api/login', cors(), qewdRouter());
|
253 | };
|
254 |
|
255 |
|
256 | Note that for the above to work, you'd need to include *cors* in your *install_modules.json* file, eg:
|
257 |
|
258 | [
|
259 | "cors"
|
260 | ]
|
261 |
|
262 | QEWD-Up will ensure that this is loaded from NPM before starting QEWD.
|
263 |
|
264 | #### config
|
265 |
|
266 | This is the *config* object that contains your QEWD instance's configuration settings. It can be useful for conditional logic, for example that determines the configured database type, eg:
|
267 |
|
268 | module.exports = function(bodyParser, app, qewdRouter, config) {
|
269 | if (config.database.type === 'gtm') {
|
270 | // ... etc
|
271 | }
|
272 | };
|
273 |
|
274 | You can modify the values within the config object, but you should take care doing this. Note that by the time the *addMiddleware* hook is invoked, changing some *config* properties may not have any effect. You should try to set the values you require within the *qewd* property in the relevant part of your *config.json* file.
|
275 |
|
276 |
|
277 | ## onStarted
|
278 |
|
279 | ### Filename and Directory location
|
280 |
|
281 | The filename is **onStarted.js**. Its name is case-sensitive.
|
282 |
|
283 | Its placement depends on what mode you are using and/or microservice you are specifying it for
|
284 |
|
285 | #### Monolith
|
286 |
|
287 | ~/dockerExample
|
288 | |
|
289 | |_ onStarted.js
|
290 | |
|
291 | |_ configuration
|
292 | |
|
293 | |_ apis
|
294 | |
|
295 |
|
296 | #### MicroService: Orchestrator
|
297 |
|
298 | ~/microserviceExample
|
299 | |
|
300 | |_ configuration
|
301 | |
|
302 | |_ orchestrator
|
303 | | |
|
304 | | |_ onStarted.js
|
305 |
|
306 |
|
307 | #### MicroService: Other Microservice
|
308 |
|
309 | *eg* for a MicroService named *login_service*:
|
310 |
|
311 | ~/microserviceExample
|
312 | |
|
313 | |_ configuration
|
314 | |
|
315 | |_ login_service
|
316 | | |
|
317 | | |_ onStarted.js
|
318 |
|
319 |
|
320 | ### Module structure
|
321 |
|
322 | Your *onStarted.js* file should export a function of the structure shown below:
|
323 |
|
324 | module.exports = function(config, app, qewdRouter) {
|
325 | // perform startup tasks
|
326 | };
|
327 |
|
328 | ### Module Function Context
|
329 |
|
330 | The **this** object within your *onStarted* module is the QEWD Master Process object, so you have access to all its properties and method.
|
331 |
|
332 | ### Module Function Arguments
|
333 |
|
334 | #### config
|
335 |
|
336 | This is the *config* object that contains your QEWD instance's configuration settings. It can be useful for conditional logic, for example that determines the configured database type, eg:
|
337 |
|
338 | module.exports = function(config) {
|
339 | if (config.database.type === 'gtm') {
|
340 | // ... etc
|
341 | }
|
342 | };
|
343 |
|
344 | You can modify the values within the config object, but you should take care doing this. Note that by the time the *onStarted* hook is invoked, changing some *config* properties is unlikely to have any effect, as most of the startup actions that depend on them will have already taken place. You should try to set the values you require within the *qewd* property in the relevant part of your *config.json* file.
|
345 |
|
346 |
|
347 | #### app
|
348 |
|
349 | This is the WebServer (eg Express) object. You can use this to, for example, specify your own custom *app.use* directives.
|
350 |
|
351 | For example, here is an *addMiddleware* module that configures payload size limits (assuming *qewd.bodyParser* was specified in the *config.json* file):
|
352 |
|
353 |
|
354 | module.exports = function(config, app) {
|
355 | app.use(bodyParser.json({limit: '1mb'}));
|
356 | app.use(bodyParser.urlencoded({limit: '1mb', extended: true}));
|
357 | };
|
358 |
|
359 | #### qewdRouter
|
360 |
|
361 | This is QEWD's REST API routing function. You can use this to insert a middleware chain prior to QEWD taking over the API routing. For example:
|
362 |
|
363 | var cors = require('cors');
|
364 | module.exports = function(config, app, qewdRouter) {
|
365 | app.use('/api/login', cors(), qewdRouter());
|
366 | };
|
367 |
|
368 |
|
369 | Note that for the above to work, you'd need to include *cors* in your *install_modules.json* file, eg:
|
370 |
|
371 | [
|
372 | "cors"
|
373 | ]
|
374 |
|
375 | QEWD-Up will ensure that this is loaded from NPM before starting QEWD.
|
376 |
|
377 |
|
378 | ## onWSRequest
|
379 |
|
380 | ### Filename and Directory location
|
381 |
|
382 | The filename is **onWSRequest.js**. Its name is case-sensitive.
|
383 |
|
384 | The *onWSRequest* hook is only relevant to:
|
385 |
|
386 | - a QEWD-Up Monolithic application;
|
387 | - the Orchestrator MicroService in a QEWD-Up MicroService application
|
388 |
|
389 | Its placement depends on what mode you are using and/or microservice you are specifying it for
|
390 |
|
391 | #### Monolith
|
392 |
|
393 | ~/dockerExample
|
394 | |
|
395 | |_ onWSrequest.js
|
396 | |
|
397 | |_ configuration
|
398 | |
|
399 | |_ apis
|
400 | |
|
401 |
|
402 | #### MicroService: Orchestrator
|
403 |
|
404 | ~/microserviceExample
|
405 | |
|
406 | |_ configuration
|
407 | |
|
408 | |_ orchestrator
|
409 | | |
|
410 | | |_ onWSRequest.js
|
411 |
|
412 |
|
413 |
|
414 | ### Module structure
|
415 |
|
416 | Your *onWSRequest.js* file should export a function of the structure shown below:
|
417 |
|
418 | module.exports = function(req, res, next) {
|
419 | // perform onWSRequest processing
|
420 | next();
|
421 | };
|
422 |
|
423 | ### Module Function Arguments
|
424 |
|
425 | #### req
|
426 |
|
427 | The WebServer (eg Express by default) request object
|
428 |
|
429 | #### res
|
430 |
|
431 | The WebServer (eg Express by default) response object
|
432 |
|
433 | #### next
|
434 |
|
435 | The WebServer (eg Express by default) next() function, allowing control to be passed to the next function in the middleware chain.
|
436 |
|
437 | ### Example
|
438 |
|
439 | This *onWSRequest* example implements CSRF protection for all incoming requests:
|
440 |
|
441 | module.exports = function(req, res, next) {
|
442 |
|
443 | function sendError(message) {
|
444 | res.set('content-length', message.length);
|
445 | res.status(400).send(message);
|
446 | }
|
447 |
|
448 | if (!req.headers) {
|
449 | return sendError('Invalid request: headers missing');
|
450 | }
|
451 | if (!req.headers['x-requested-with']) {
|
452 | return sendError('Invalid request: x-requested-with header missing');
|
453 | }
|
454 | if (req.headers['x-requested-with'] !== 'XMLHttpRequest') {
|
455 | return sendError('Invalid request: x-requested-with header invalid');
|
456 | }
|
457 | next();
|
458 | };
|
459 |
|
460 |
|
461 |
|
462 | ## onWorkerLoad
|
463 |
|
464 |
|
465 | ### Filename and Directory location
|
466 |
|
467 | The filename is **onWorkerLoad.js**. Its name is case-sensitive.
|
468 |
|
469 | This event hook is useful if most or all of the handler modules for the MicroService require the use of the same third-party Node.js module. Rather than each handler module *require()*ing the module, you can do this once when the QEWD Worker loads the Application Module that QEWD-Up generates, and augment the *this* object with the loaded Node.js module(s).
|
470 |
|
471 | A more complex scenario is where you want to load a Node.js module and then instantiate an instance of a class or object from it, and make that class or object available to some or all of your handler modules.
|
472 |
|
473 | Placement of the *onWorkerLoad.js* file is within a MicroService's folder, eg:
|
474 |
|
475 | ~/microserviceExample
|
476 | |
|
477 | |_ configuration
|
478 | |
|
479 | |_ login_service
|
480 | | |
|
481 | | |_ onWorkerLoad.js
|
482 |
|
483 |
|
484 | ### Module structure
|
485 |
|
486 | Your *onWorkerLoad.js* file should export a function of the structure shown below:
|
487 |
|
488 | module.exports = function() {
|
489 | // onWorkerLoad logic here
|
490 | };
|
491 |
|
492 | ### Module Function Context
|
493 |
|
494 | The *onWorkerLoad* module's **this** object is the QEWD Worker Process object. This provides access to, for example:
|
495 |
|
496 | - **this.db.use**: Giving access to a document within the integrated persistent JSON database
|
497 | - **this.session**: The QEWD Session (if relevant)
|
498 | - **this.userDefined**: Any custom objects that were defined at QEWD Startup
|
499 |
|
500 | Typically you'll load one or more third-party modules and add them (or a derived instantiated class or object) to *this*.
|
501 |
|
502 |
|
503 |
|
504 | ## beforeHandler
|
505 |
|
506 |
|
507 | ### Filename and Directory location
|
508 |
|
509 | The filename is **beforeHandler.js**. Its name is case-sensitive.
|
510 |
|
511 | It is applied to **all** of the above API requests, and is invoked on the QEWD Worker process that handles the API
|
512 |
|
513 | Its most common uses are:
|
514 |
|
515 | - in a Monolithic application, for determining whether or not the request has been authenticated, and rejecting un-authenticated or otherwise invalid requests before the appropriate API handler method is invoked.
|
516 |
|
517 | - in a MicroService application, for establishing and sunsequently maintaining a QEWD Session that is linked to the incoming JWT that is included with the user's request, eg for cacheing user-specific data for the duration of the user's activity.
|
518 |
|
519 | **Note**: In a Monolithic application, although the *beforeHandler* hook is applied to all of the above API requests, it can be over-ridden on a per-API basis by setting a route's [*applyBeforeHandler* property to *false*](https://github.com/robtweed/qewd/blob/master/up/docs/Routes.md#the-beforehandler-hook-in-monolithic-applications).
|
520 |
|
521 | The placement of the *beforeHandler.js* file depends on what mode you are using:
|
522 |
|
523 | #### Monolith
|
524 |
|
525 | ~/dockerExample
|
526 | |
|
527 | |_ configuration
|
528 | |
|
529 | |_ apis
|
530 | | |
|
531 | | |_ beforeHandler.js
|
532 | |
|
533 |
|
534 | #### MicroService (Orchestrator)
|
535 |
|
536 | ~/microserviceExample
|
537 | |
|
538 | |_ configuration
|
539 | |
|
540 | |_ orchestrator
|
541 | | |
|
542 | | apis
|
543 | | |
|
544 | | |_ beforeHandler.js
|
545 |
|
546 |
|
547 | #### MicroService (MicroService)
|
548 |
|
549 | If you have defined your handler modules directly within your MicroService folder, then the *beforeHandler.js* module should also be placed there, eg:
|
550 |
|
551 |
|
552 | ~/microserviceExample
|
553 | |
|
554 | |_ configuration
|
555 | |
|
556 | |_ login_service
|
557 | | |
|
558 | | |
|
559 | | |_ beforeHandler.js
|
560 |
|
561 |
|
562 | Alternatively, if you have placed all your handlers within their own *apis* folder, you should also place the *beforeHandler.js* module within the *apis* folder, eg:
|
563 |
|
564 |
|
565 | ~/microserviceExample
|
566 | |
|
567 | |_ configuration
|
568 | |
|
569 | |_ login_service
|
570 | | |
|
571 | | apis
|
572 | | |
|
573 | | |_ beforeHandler.js
|
574 |
|
575 |
|
576 | ### Module structure
|
577 |
|
578 | Your *beforeHandler.js* file should export a function of the structure shown below:
|
579 |
|
580 | module.exports = function(req, finished) {
|
581 | // beforeHandler logic here
|
582 | };
|
583 |
|
584 | ### Module Function Context
|
585 |
|
586 | The *beforeHandler* module's **this** object is the QEWD Worker Process object. This provides access to, for example:
|
587 |
|
588 | - **this.db.use**: Giving access to a document within the integrated persistent JSON database
|
589 | - **this.session**: The QEWD Session (if relevant)
|
590 | - **this.userDefined**: Any custom objects that were defined at QEWD Startup
|
591 |
|
592 | ### Module Function Arguments
|
593 |
|
594 | #### req
|
595 |
|
596 | This is the version of the request object that is created by QEWD and transmitted to the Worker process. Its most relevant/useful properties are as follows:
|
597 |
|
598 | - **path**: The API route path, eg */api/info*
|
599 | - **method**: The HTTP request method
|
600 | - **headers**: The HTTP request headers object. You'll probably be most interested in the *headers.authorization* value which you can use to test
|
601 | - **query**: The URL query string (if any), parsed into an object
|
602 | - **body**: For POST/PUT requests, the request body payload (if any), parsed into an object
|
603 | - **ip**: The IP Address of the location that was determined to have sent the request (which may be a router or gateway IP address)
|
604 |
|
605 | #### finished
|
606 |
|
607 | The QEWD Worker function that you should use if you want to reject the request and return an error message, eg:
|
608 |
|
609 | module.exports = function(req, finished) {
|
610 | if (!req.headers.authorization) {
|
611 | finished({error: 'Missing Authorization Header'});
|
612 | return false; // stops QEWD invoking your request's normal handler method
|
613 | }
|
614 | //... etc
|
615 | };
|
616 |
|
617 | The *finished* function performs two tasks in QEWD:
|
618 |
|
619 | - it defines the object to be returned to the REST Client
|
620 | - it tells QEWD's Master process that you have finished with the Worker process, and it can be returned to the available pool
|
621 |
|
622 | **Note:** If you invoke the *finished()* function, you **MUST** *return false* from your *beforeHandler* module. Doing so instructs QEWD to bypass your normal handler method. See the example above
|
623 |
|
624 | ### Example
|
625 |
|
626 | In a MicroService application, you can establish and maintain a QEWD Session that is linked to the incoming request's JWT simply by defining the following *beforeHandler* module:
|
627 |
|
628 | module.exports = function(req, finished) {
|
629 | req.qewdSession = this.qewdSessionByJWT.call(this, req);
|
630 | return true;
|
631 | };
|
632 |
|
633 | Your handler modules can then access this QEWD Session via *args.req.qewdSession*.
|
634 |
|
635 |
|
636 | ## onMSResponse
|
637 |
|
638 |
|
639 | ### Filename and Directory location
|
640 |
|
641 | The filename is **onMSResponse.js**. Its name is case-sensitive.
|
642 |
|
643 | The *onMSResponse* hook is only relevant to MicroService applications
|
644 |
|
645 | It is applied to the specified API, and is invoked on the QEWD Master process of the MicroService that handled the API, on receipt of the handler response from the QEWD Worker process and before the MicroService returns its response to the Orchestrator (or other originating MicroService).
|
646 |
|
647 | The *onMSResponse* hook allows you to intercept the response from your API's handler, and send one or more further API requests, eg to other MicroServices, in order to build up a complex, composite response.
|
648 |
|
649 | Because the *onMSResponse* runs on the MicroService that handles the API, if you send out other API requests, those APIs must be available for use on your handling MicroService. This is specified in the route definitions in the *routes.json* file, via the *from_microservices* property. For example, if we wanted to use the */api/info/demographics* API in an *onHandler* hook for the */api/login* API, we'd need this defined in the *routes.json* file:
|
650 |
|
651 | {
|
652 | "uri": "/api/info/demographics",
|
653 | "method": "GET",
|
654 | "handler": "getDemographics",
|
655 | "on_microservice": "info_service",
|
656 | "from_microservices": [
|
657 | "login_service"
|
658 | ]
|
659 | }
|
660 |
|
661 |
|
662 | An *onMSResponse* module is placed is along-side the API handler module, eg: for the API defined in **routes.json**:
|
663 |
|
664 | {
|
665 | "uri": "/api/login",
|
666 | "method": "POST",
|
667 | "handler": "login",
|
668 | "on_microservice": "login_service",
|
669 | "authenticate": false
|
670 | }
|
671 |
|
672 |
|
673 | You would define an *onMSResponse* hook by adding the *onMSResponse.js* module file here:
|
674 |
|
675 |
|
676 | ~/microserviceExample
|
677 | |
|
678 | |_ configuration
|
679 | |
|
680 | |_ login_service
|
681 | | |
|
682 | | login
|
683 | | |
|
684 | | |_ index.js
|
685 | | |
|
686 | | |_ onMSResponse.js
|
687 |
|
688 |
|
689 | Alternatively, if you had chosen to group all your API handler modules in an *apis* folder, you would place the *onMSResponse* module within the *apis* folder, eg:
|
690 |
|
691 | ~/microserviceExample
|
692 | |
|
693 | |_ configuration
|
694 | |
|
695 | |_ login_service
|
696 | | |
|
697 | | apis
|
698 | | |
|
699 | | login
|
700 | | |
|
701 | | |_ index.js
|
702 | | |
|
703 | | |_ onMSResponse.js
|
704 |
|
705 |
|
706 |
|
707 | ### Module structure
|
708 |
|
709 | Your *onMSResponse.js* file should export a function of the structure shown below:
|
710 |
|
711 | module.exports = function(message, jwt, forward, sendBack) {
|
712 | // onRequest logic here
|
713 | };
|
714 |
|
715 |
|
716 | ### Module Function Context
|
717 |
|
718 | The *onMSResponse* module's **this** object is the QEWD Master Process object for the MicroService.
|
719 |
|
720 |
|
721 | ### Module Function Arguments
|
722 |
|
723 | #### message
|
724 |
|
725 | This is the response object you sent from your handler (ie the object you created as the *finished()* function's argument.
|
726 |
|
727 | It is also augmented by QEWD with some additional properties which are for internal use by QEWD and which should be left unchanged.
|
728 |
|
729 | #### jwt
|
730 |
|
731 | This is the updated JWT returned from your API handler.
|
732 |
|
733 | You can easily access values within the JWT using the built-in QEWD function:
|
734 |
|
735 | this.jwt.handlers.getProperty(propertyName, jwt)
|
736 |
|
737 | where *propertyName* is the name of a property (or claim) within the JWT. For example:
|
738 |
|
739 | var username = this.jwt.handlers.getProperty('username', jwt);
|
740 |
|
741 |
|
742 | #### forward
|
743 |
|
744 | This is the function that you should use for forwarding an API request. It is invoked like this:
|
745 |
|
746 | forward(apiRequest, jwt, callback)
|
747 |
|
748 | The arguments are:
|
749 |
|
750 | - **apiRequest**: an object that defines an API request, eg:
|
751 |
|
752 | *var apiRequest = {
|
753 | path: '/api/info/demographics',
|
754 | method: 'GET'
|
755 | };*
|
756 |
|
757 | - **jwt**: the *jwt* argument as described above
|
758 |
|
759 | - **callback**: Callback function with a single argument - *responseObj* - containing the response object from the API
|
760 |
|
761 |
|
762 | #### sendBack
|
763 |
|
764 | This is the function that you should use to return your final response back to the *Orchestrator* (or other MicroService if it sent the request).
|
765 |
|
766 | It has a single argument: the object you wish to return.
|
767 |
|
768 |
|
769 | ### Example onMSResponse Module
|
770 |
|
771 | This intercepts the response from the */api/login* handler and fetches demographics information before returning a combined response back to the *Orchestrator*
|
772 |
|
773 | module.exports = function(message, jwt, forward, sendBack) {
|
774 | var apiRequest = {
|
775 | path: '/api/info/demographics',
|
776 | method: 'GET'
|
777 | };
|
778 | forward(apiRequest, jwt, function(responseObj) {
|
779 | sendBack({
|
780 | login: message,
|
781 | demographics: responseObj
|
782 | });
|
783 | });
|
784 | };
|
785 |
|
786 |
|
787 |
|
788 |
|
789 | **Note**: If your *onMSResponse* function returns *false*, the original response object from your API handler will be returned to the *Orchestrator*. You can use this to conditionalise your *onMSResponse* logic, for example:
|
790 |
|
791 |
|
792 | module.exports = function(message, jwt, forward, sendBack) {
|
793 |
|
794 | if (message.item === 'bypass') {
|
795 | return false; // Just let QEWD return the original response from your handler
|
796 | }
|
797 |
|
798 | var apiRequest = {
|
799 | path: '/api/info/demographics',
|
800 | method: 'GET'
|
801 | };
|
802 | forward(apiRequest, jwt, function(responseObj) {
|
803 | sendBack({
|
804 | login: message,
|
805 | demographics: responseObj
|
806 | });
|
807 | });
|
808 | };
|
809 |
|
810 |
|
811 |
|
812 | ## onOrchResponse
|
813 |
|
814 |
|
815 | ### Filename and Directory location
|
816 |
|
817 | The filename is **onOrchResponse.js**. Its name is case-sensitive.
|
818 |
|
819 | The *onOrchResponse* hook is only relevant to MicroService applications
|
820 |
|
821 | It is applied to the specified API, and is invoked on the QEWD Master process of the *Orchestrator* MicroService, on receipt of the response from the MicroService that handled the API.
|
822 |
|
823 | Note: the *onOrchResponse* hook is invoked before the *onWSResponse* hook (if defined).
|
824 |
|
825 | The *onOrchResponse* hook allows you to intercept the response from your API's handling MicroService, and send one or more further API requests, eg to other MicroServices, in order to build up a complex, composite response.
|
826 |
|
827 | The *onOrchResponse* hook is therefore similar to the *onMSResponse* hook, but the *onOrchResponse* hook is invoked on the *Orchestrator*, whilst the *onMSResponse* hook is available on the other MicroServices that make up your application. Which you use will depend on how and where you want to construct the results of complex chains of APIs.
|
828 |
|
829 |
|
830 | An *onOrchResponse* module is placed is along-side the API handler module, eg: for the API defined in **routes.json**:
|
831 |
|
832 | {
|
833 | "uri": "/api/login",
|
834 | "method": "POST",
|
835 | "handler": "login",
|
836 | "on_microservice": "login_service",
|
837 | "authenticate": false
|
838 | }
|
839 |
|
840 |
|
841 | You would define an *onOrchResponse* hook by adding the *onOrchResponse.js* module file alongside the handler's *index.js* file, eg:
|
842 |
|
843 |
|
844 | ~/microserviceExample
|
845 | |
|
846 | |_ configuration
|
847 | |
|
848 | |_ login_service
|
849 | | |
|
850 | | login
|
851 | | |
|
852 | | |_ index.js
|
853 | | |
|
854 | | |_ onOrchResponse.js
|
855 |
|
856 |
|
857 | Or, if you had grouped all your handler modules within an *apis* subfolder:
|
858 |
|
859 |
|
860 | ~/microserviceExample
|
861 | |
|
862 | |_ configuration
|
863 | |
|
864 | |_ login_service
|
865 | | |
|
866 | | apis
|
867 | | |
|
868 | | login
|
869 | | |
|
870 | | |_ index.js
|
871 | | |
|
872 | | |_ onOrchResponse.js
|
873 |
|
874 |
|
875 |
|
876 | ### Module structure
|
877 |
|
878 | Your *onOrchResponse.js* file should export a function of the structure shown below:
|
879 |
|
880 | module.exports = function(responseObj, request, forwardToMS, sendResponse, getJWTproperty) {
|
881 | // onOrchRequest logic here
|
882 | };
|
883 |
|
884 |
|
885 | ### Module Function Context
|
886 |
|
887 | The *onOrchResponse* module's **this** object is the QEWD Master Process object for the MicroService.
|
888 |
|
889 |
|
890 | ### Module Function Arguments
|
891 |
|
892 | #### responseObj
|
893 |
|
894 | This is the object containing the response that has been received by the Orchestrator's Master process from another MicroService (or chain of MicroServices).
|
895 |
|
896 | **Note**: if an error has occurred, then *responseObj.message.error* will exist and contain a string value.
|
897 |
|
898 | #### request
|
899 |
|
900 | This is the object containing the original request that was received by the Orchestrator. It is made available because it may contain information that is useful for your hook's logic, eg *request.method* and *request.path* are the original incoming request's method and path respectively.
|
901 |
|
902 | #### forwardToMS
|
903 |
|
904 | This is a function that you can use to forward a new request to a MicroService, rather than, or before returning the response to the REST Client.
|
905 |
|
906 | It takes two arguments:
|
907 |
|
908 | - **routeObj**: an object that defines the API route that you want to invoke, in terms of its path and method. It can also include a *query* or *body* property (each of which is a sub-object);
|
909 | - **callback**: a function with a single argument - the response object from the MicroService that processed your API route information.
|
910 |
|
911 | For example:
|
912 |
|
913 | var msg = {
|
914 | path: '/api/info/info',
|
915 | method: 'GET'
|
916 | };
|
917 | forwardToMS(msg, function(responseObj) {
|
918 | // handle response
|
919 | });
|
920 |
|
921 |
|
922 | #### sendResponse
|
923 |
|
924 | This is a function that you should use to return your final response object back to the REST Client.
|
925 |
|
926 | It has a single argument:
|
927 |
|
928 | - **responseObj**: The object containing the response that you want to return to the REST Client
|
929 |
|
930 | For example:
|
931 |
|
932 | var msg = {
|
933 | path: '/api/info/info',
|
934 | method: 'GET'
|
935 | };
|
936 | forwardToMS(msg, function(responseObj) {
|
937 | sendResponse(responseObj);
|
938 | });
|
939 | return true;
|
940 |
|
941 |
|
942 | Alternatively, your *onWSResponse* hook module may simply be used to re-package / re-format the response before sending it to the REST Client, eg:
|
943 |
|
944 | if (!responseObj.message.error) {
|
945 | var respObj = {
|
946 | yousent: request.path,
|
947 | usingMethod: request.method,
|
948 | exp: getJWTProperty('exp')
|
949 | };
|
950 | sendResponse(respObj);
|
951 | return true;
|
952 | }
|
953 |
|
954 | **Note**: If you use the *sendResponse* function, your *onWSResponse* hook module **MUST** return *true*, as shown in the examples above. This tells QEWD that you are taking responsibility for returning the REST Client's response, and therefore QEWD doesn't try to also send a response.
|
955 |
|
956 |
|
957 | #### getJWTProperty
|
958 |
|
959 | This function allows you to extract a claim/property from the JWT.
|
960 |
|
961 | It has a single argument: the name of the JWT claim or property.
|
962 |
|
963 | See the example above, which gets the value of the *exp* property from the JWT.
|
964 |
|
965 |
|
966 | ### Example onWSResponse Module
|
967 |
|
968 |
|
969 | module.exports = function(responseObj, request, forwardToMS, sendResponse, getJWTProperty) {
|
970 | if (!responseObj.message.error) {
|
971 | var msg = {
|
972 | path: '/api/info/info',
|
973 | method: 'GET'
|
974 | };
|
975 | forwardToMS(msg, function(responseObj) {
|
976 | sendResponse(responseObj);
|
977 | });
|
978 | return true;
|
979 | }
|
980 | // otherwise the original error message will be sent to the REST Client
|
981 | };
|
982 |
|
983 |
|
984 |
|
985 | ## onWSResponse
|
986 |
|
987 | ### Filename and Directory location
|
988 |
|
989 | The filename is **onWSResponse.js**. Its name is case-sensitive.
|
990 |
|
991 | The *onWSResponse* hook is only relevant to:
|
992 |
|
993 | - a QEWD-Up Monolithic application;
|
994 | - the Orchestrator MicroService in a QEWD-Up MicroService application
|
995 |
|
996 | Its placement depends on what mode you are using and/or microservice you are specifying it for
|
997 |
|
998 | #### Monolith
|
999 |
|
1000 | ~/dockerExample
|
1001 | |
|
1002 | |_ onWSResponse.js
|
1003 | |
|
1004 | |_ configuration
|
1005 | |
|
1006 | |_ apis
|
1007 | |
|
1008 |
|
1009 | The *onWSResponse* hook is applied to *ALL* responses received by the Master process.
|
1010 |
|
1011 |
|
1012 | #### MicroService: Orchestrator
|
1013 |
|
1014 | ~/microserviceExample
|
1015 | |
|
1016 | |_ configuration
|
1017 | |
|
1018 | |_ orchestrator
|
1019 | | |
|
1020 | | |_ onWSResponse.js
|
1021 |
|
1022 |
|
1023 | The *onWSResponse* hook is applied to *ALL* responses received by the Orchestrator's Master process.
|
1024 |
|
1025 |
|
1026 | ### Module structure
|
1027 |
|
1028 | Your *onWSResponse.js* file should export a function of the structure shown below:
|
1029 |
|
1030 | module.exports = function(req, res, next) {
|
1031 | // perform onWSResponse processing
|
1032 | next();
|
1033 | };
|
1034 |
|
1035 | ### Module Function Arguments
|
1036 |
|
1037 | #### req
|
1038 |
|
1039 | The WebServer (eg Express by default) request object
|
1040 |
|
1041 | #### res
|
1042 |
|
1043 | The WebServer (eg Express by default) response object
|
1044 |
|
1045 | #### next
|
1046 |
|
1047 | The WebServer (eg Express by default) next() function, allowing control to be passed to the next function in the middleware chain.
|
1048 |
|
1049 | ### Example
|
1050 |
|
1051 | The following example does not, in fact, modify the response, but simply demonstrates how to implement a *pass-through* module that handles both success and error responses.
|
1052 |
|
1053 | **Notes**:
|
1054 |
|
1055 | - the response object can be found in *res.locals.message*;
|
1056 | - the original incoming API route path can be found in *req.originalUrl*: use this if you want the logic to be conditional for specific API routes.
|
1057 |
|
1058 |
|
1059 | module.exports = function(req, res, next) {
|
1060 | var messageObj = res.locals.message || {error: 'Not Found'};
|
1061 | if (messageObj.error) {
|
1062 | var code = 400;
|
1063 | var status = messageObj.status;
|
1064 | if (status && status.code) code = status.code;
|
1065 | delete messageObj.status;
|
1066 | delete messageObj.restMessage;
|
1067 | delete messageObj.ewd_application;
|
1068 | res.set('content-length', messageObj.length);
|
1069 | res.status(code).send(messageObj);
|
1070 | }
|
1071 | else {
|
1072 | res.send(messageObj);
|
1073 | }
|
1074 | next();
|
1075 | };
|
1076 |
|
1077 |
|
1078 |
|
1079 |
|