UNPKG

60.4 kBMarkdownView Raw
1# Contents
2
3- [QEWD Interactive Applications](#qewd-interactive-applications)
4- [The QEWD WebSocket Client](#the-qewd-websocket-client)
5 - [Loading *ewd-client* into the Browser](#loading-ewd-client-into-the-browser)
6 - [Application Registration](#application-registration)
7 - [The *start()* Method](#the-start-method)
8 - [Sending Messages from the Browser](#sending-messages-from-the-browser)
9 - [Handling Messages Sent from the QEWD Back-end](#handling-messages-sent-from-the-qewd-back-end)
10- [The QEWD Application Back-end](#the-qewd-application-back-end)
11 - [Defining A QEWD Interactive Application](#defining-a-qewd-interactive-application)
12 - [Locating Client-Side Code](#locating-client-side-code)
13 - [Locating Server-Side Code](#locating-server-side-code)
14 - [Message Handler Functions](#message-handler-functions)
15 - [Arguments](#arguments)
16 - [Message Handler Function Context](#message-handler-function-context)
17 - [The Incoming Message Object](#the-incoming-message-object)
18 - [Returning a Response from your Message Handler Function](#returning-a-response-from-your-message-handler-function)
19 - [Accessing the User's QEWD Session](#accessing-the-users-qewd-session)
20 - [Accessing the Integrated Persistent JSON/Document Database](#accessing-the-integrated-persistent-jsondocument-database)
21 - [Intermediate Messages](#intermediate-messages)
22- [Life-Cycle Event Hooks](#life-cycle-event-hooks)
23 - [The Life-Cycle of an Interactive QEWD Application](#the-life-cycle-of-an-interactive-qewd-application)
24 - [onLoad](#onload)
25 - [beforeHandler](#beforehandler)
26 - [onResponse](#onresponse)
27
28
29# QEWD Interactive Applications
30
31In addition to its REST support, QEWD provides in-build support for interactive, browser-based applications. By default, these rely on WebSockets, rather than HTTP, for the messaging between the browser and the QEWD back-end.
32
33WebSockets not only provide faster communication between browser and back-end, they also allow you to break free of the limitations of the request/response-based HTTP protocol. When you first start an interactive QEWD application, it automatically creates for you a persistent WebSocket connection between the browser and QEWD back-end. Once in place, rather than waiting for requests from the browser, the QEWD back-end can, at any time, send messages to the browser. These trigger events in the browser to handle those incoming messages.
34
35Conversely, if you send a message from the browser over the WebSocket connection, you won't necessarily expect and need to handle a response from the QEWD back-end. *Fire and forget* messaging from the browser to the QEWD back-end is very simple to achieve.
36
37That having been said, request/response messaging from the browser is also catered for and made very simple to use.
38
39If you've used the *qewd-monitor* application to monitor your QEWD instances, you've already seen one such interactive, WebSocket-based application in action. However, you can create your own applications which can run either as well as or instead of REST-based applications on a QEWD instance.
40
41# The QEWD WebSocket Client
42
43When you start QEWD-Up, you'll see that a new sub-folder - */www* is created within your QEWD-Up application folder, and within this you'll discover not only a symbolic link to the *qewd-monitor* front-end resources, but also a symbolic link to a file named *ewd-client.js* ([See here for details](https://github.com/robtweed/ewd-client)].
44
45This pre-built client module is designed to be loaded into the browser, where it handles all the security of your application and the WebSocket messaging with your application's QEWD back-end.
46
47The *ewd-client* module is designed to be compatible with any style of browser-based application, including:
48
49- plain manually-created HTML/JavaScript web applications that do not use a JavaScrip framework
50- web applications using a JavaScript framework (eg jQuery, React, Vue.js, Angular)
51- Mobile applications using, for example, React Native
52
53*ewd-client* requires a WebSocket module: the recommended on is [*socket.io*](https://github.com/socketio/socket.io). *ewd-client* uses the client module, while QEWD uses the Node.js-based server module. By default, QEWD uses *socket.io*.
54
55
56There are four key aspects of *ewd-client* that you need to understand:
57
58- loading *ewd-client* into the browser
59- application registration
60- sending messages from the browser and handling them in the QEWD back-end
61- handling messages sent from the QEWD back-end to the browser
62
63## Loading *ewd-client* into the Browser
64
65In plain manually-created HTML/JavaScript applications, you'll load *ewd-client* into the browser using a *<script>* tag, eg:
66
67 <script src="/socket.io/socket.io.js"></script>
68 <script src="/ewd-client.js"></script>
69
70**Note:** in the above example, both */socket.io/socket.io.js* and */ewd-client.js* are automatcally available to you on your QEWD-Up instance.
71
72
73If you're using a framework such as React or Angular, you'll have *bundled* all the JavaScript resources into a single JS file - *ewd-client* and [*socket.io-client*](https://github.com/socketio/socket.io-client) are resources that you must include in your project. For React applications, it's worth looking at the [*react-qewd*](https://github.com/wdbacker/react-qewd) module that has been created from *ewd-client*.
74
75
76## Application Registration
77
78Once loaded, the first step is to invoke the *ewd-client* module's *start()* method. This performs the following steps which are known as *application registration*:
79
80- *ewd-client* attempts to create a persistent WebSocket connection with the QEWD back-end
81- one a WebSocket connection is established, *ewd-client* sends a message to the QEWD back-end, specifying the name of the QEWD Application it wants to use
82- the QEWD back-end creates a new QEWD Session and returns a response message to the browser containing an opaque QEWD Session token
83- *ewd-client* retains the QEWD Session token within its closure, and creates a *send()* method that you will use for all your application messaging
84- *ewd-client* emits an *ewd-registered* event, denoting that it is now ready and safe for messages to be exhanged between the browser and QEWD back-end
85
86As part of its security management, on successful registration, *ewd-client* deletes the *socket.io* interface so that it cannot be used by the browser user for any other purpose. The *ewd-client send()* method, however, retains access to *socket.io*.
87
88Furthermore, because *ewd-client* automatically adds the registered QEWD Session Token to your messages from within its closure, the QEWD back-end will always recognise your messages as requiring handling **only**by the registered application - the browser user cannot manipulate the messages to attempt to access any other QEWD application.
89
90Additionally, as a result of the QEWD Session Token that the *send()* method attaches to all outgoing messages from the browser, those messages are locked to the QEWD Session that was created at registration.
91
92
93Registration is triggered by invoking *ewd-client's start()* method, so let's now examine how it is used.
94
95### The *start()* Method
96
97The arguments for the *start()* method are as follows:
98
99- **application:** The name of the QEWD Application that will handle messages from the browser
100- **$**: (Optional) If you are using jQuery, you should set this to the loaded/imported jQuery library/module
101- **io**: Set this to the loaded/imported WebSocket module (eg the instance of *socket.io*)
102- **customAjaxFn**: (Optional) Your own custom Ajax handler module if:
103 - you want to use Ajax messaging instead of WebSocket messaging; and
104 - you don't want to use *ewd-client*'s built-in jQuery-based Ajax messaging function
105- **url**: The URL of the back-end QEWD server, eg *http://qewd.example.com:8080*
106
107You can either specify these arguments separately or as properties of a single argument object, eg:
108
109 EWD.start('myQEWDApplication', $, io, null, 'http://qewd.example.com:8080')
110
111or:
112
113 EWD.start({
114 application: 'myQEWDApplication',
115 $: $,
116 io: io,
117 url: 'http://qewd.example.com:8080'
118 });
119
120The arguments you provide for the *start()* method will depend on your style of application - specifically whether:
121
122- you loaded *ewd-client* into the browser using a *<script>* tag and sourced it from the same origin as the HTML page; or
123- *ewd-client* was pre-bundled into the JavaScript (eg React, Angular, React Native)
124
125In the former instance, you do not need to specify the URL - it is implicitly the Origin server. For example:
126
127 <script src="//ajax.googleapis.com/ajax/libs/jquery/2.2.0/jquery.min.js"></script>
128 <script src="/socket.io/socket.io.js"></script>
129 <script src="/ewd-client.js"></script>
130
131
132 EWD.start('myQEWDApplication', $, io)
133
134
135Note that in the above example, *$*, *io* and *EWD* will have been implicitly created by the libraries loaded in the *<script>* tags.
136
137
138In the latter instance, you'll need to explicitly specify the *url*, eg:
139
140 import io from 'socket.io-client'
141 // import $ from 'jquery'
142 import EWD from 'ewd-client'
143
144 EWD.start({
145 application: 'myQEWDApplication',
146 io: io,
147 //$: $,
148 url: 'http://qewd.example.com:8080'
149 });
150
151
152**NOTE**: *ewd-client* does not have any explicit dependency on *jQuery*. Specifying the *$* argument is optional and **ONLY** necessary if:
153
154- you want to use Ajax instead of WebSocket messaging for communication between the browser and your QEWD instance; AND
155- you want to use *ewd-client's* built-in *jQuery*-based Ajax handler function.
156
157If you want to use WebSocket messaging (the recommended approach), you **DO NOT** need to specify the *$* argument.
158
159If you want to use Ajax messaging **BUT** you want to use your framework's own Ajax handler, or another one such as that provided by *axios*, then you don't specify the *$* argument but you do define your Ajax handler function using the *customAjaxFn* argument.
160
161The arguments for a *customAjaxFn* are:
162
163- params: an object, created by *ewd-client* containing the message and relevant Ajax properties
164- success: *ewd-client*'s success handler function
165- fail: *ewd-client*'s fail handler function
166
167For example:
168
169
170 import axios from 'axios'
171
172 EWD.start({
173 application: 'myQEWDApplication',
174 io: io,
175 url: 'http://qewd.example.com:8080'
176 customAjaxFn: function(params, success, fail) {
177 let data = JSON.stringify(params.data)
178 axios({
179 url: params.url,
180 method: 'post',
181 headers: {
182 'Content-Type': params.contentType
183 },
184 data,
185 timeout: params.timeout
186 })
187 .then(function (response) {
188 success(response.data)
189 })
190 .catch(function (error) {
191 if (error.response) {
192 success(error.response.data)
193 } else {
194 fail(error.message || 'unknown ajax error')
195 }
196 })
197 }
198 });
199
200
201### The *ewd-registered* Event
202
203Although usually a rapid process, *ewd-client* registration does take a finite amount of time, and until it has completed it is not safe for the browser to use the *send()* method. Indeed, the *send()* method does not exist until registration is complete.
204
205It is therefore important that the browser-side logic of your application listens for the *ewd-registered* event before commencing any activity that involves message exchange.
206
207*ewd-client* provides you with an Event Handler function: **EWD.on()** which you should use for this purpose.
208
209In a simple manually-created web application that uses jQuery, we could apply the following logic to controllably start an application:
210
211 $(document).ready(function() {
212
213 EWD.on('ewd-registered', function() {
214 // OK the app is now ready for use!
215 // commence the application's logic
216
217 // Use EWD.send() to send messages to QEWD back-end
218
219 });
220
221 EWD.start({
222 application: 'test-app',
223 io: io
224 });
225
226 });
227
228
229This logic and approach is fairly simple to adapt for use with Angular.js
230
231
232In applications built using the React framework, it becomes a bit more tricky to ensure that *ewd-client* registration has completed before letting the application rendering to properly take place. However, you can use the [*react-qewd*](https://github.com/wdbacker/react-qewd) module which does all the hard work for you.
233
234Here's an example:
235
236 import React from 'react';
237 import { render } from 'react-dom';
238 import io from 'socket.io-client';
239 import { QEWD, QEWDProvider } from 'react-qewd';
240 import App from 'myApp';
241
242 let qewd = QEWD({
243 application: 'test-app',
244 url: 'http://localhost:8080',
245 io: io
246 });
247
248 function AppContainer(props) {
249 return (
250 {
251 props.qewdProviderState.registered ?
252 <App qewd={qewd} />
253 :
254 <div>Please wait...</div>
255 }
256 )
257 }
258
259 render(
260 <QEWDProvider qewd={qewd}>
261 <AppContainer />
262 </QEWDProvider>,
263 document.getElementById('content')
264 );
265
266
267Note how the fully started-up instance of *ewd-client* is passed as a prop to your application:
268
269 <App qewd={qewd} />
270
271From within your application component(s), you can send your messages using *this.props.qewd.send()*
272
273
274Similarly, for applications that use the Vue.js and Nuxt.js frameworks, you should consider using [*vue.qewd*](https://github.com/wdbacker/vue-qewd)
275
276
277## Sending Messages from the Browser
278
279Once *ewd-client* has registered your application, you can send messages to the QEWD back-end using its *send()* method.
280
281This method has two arguments:
282
283- **messageObj**: (Mandatory) Object defining the message to send to the QEWD back-end. This object should be defined using three properties:
284 - **type**: the *type* of message you want to define the message as. Type names are up to you to define and can be any string value
285 - **params**: an object containing the parameters you want to speficy for your message. The content and structure of this object is up to you to define
286 - **ajax**: (Optional). If defined and set to *true*, then the message is sent using Ajax instead of via the QEWD WebSocket connection. By default, messages are sent via the WebSocket connection.
287- **callback**: (Optional) Callback function for handling the response. This function has a single argument:
288 - responseObj: Object containing the response message which has two key properties:
289 - type: the type of the original request message
290 - message: the response message object which is what you will create in your back-end message handler
291
292For example:
293
294 var msg = {
295 type: 'login',
296 params: {
297 username: 'rob',
298 password: 'secret'
299 }
300 };
301 EWD.send(msg, (responseObj) => {
302 console.log('Response was ' + JSON.stringify(responseObj.message));
303 });
304
305The example above would send the *login* message as a WebSocket message.
306
307To send it as an Ajax message, simply add *ajax: true* as a property:
308
309 var msg = {
310 type: 'login',
311 params: {
312 username: 'rob',
313 password: 'secret'
314 },
315 ajax: true
316 };
317 EWD.send(msg, (responseObj) => {
318 console.log('Response was ' + JSON.stringify(responseObj.message));
319 });
320
321
322See later for details on how to handle messages in the QEWD Back-end and return responses.
323
324
325## Handling Messages Sent from the QEWD Back-end
326
327If you send a message as an Ajax message (ie by setting the *ajax* message property to *true*), then you **must** expect a response **and** you will normally handle that response using the *send()* method's callback function as shown in the previous section.
328
329If, however, you're using WebSockets, it's possible for the QEWD Back-end to send messages to the browser at any time, without an initiating request arriving from the browser.
330
331The way to create messages in the QEWD back-end is described later.
332
333We've already seen in the previous section that if you are sending a WebSocket message from the client that results in a single response message being returned from QEWD, then you can handle the response by using *ewd-client's send()* method's callback function.
334
335However, there are other circumstances where you'll want to handle incoming WebSocket messages from the back-end independently, including:
336
337- QEWD back-end message handlers that return more than one response to an incoming message
338- messages independently sent from the QEWD back-end, ie without a triggering request from the browser
339- when using a framework such as React, you will often want to handle an incoming response in a Component that is higher up the Component hierarchy, so that handling the response triggers a re-rendering of that part of the sub-tree of Components.
340
341In all three such situations, you should use *ewd-client's on()* event handler method:
342
343 EWD.on(messageType, (messageObject) => {
344 // handle the incoming message from the QEWD back-end
345 });
346
347If you are using *react-qewd*, you'll probably access it using:
348
349 this.props.qewd.on(message, callback);
350
351
352The arguments are:
353
354- **messageType**: string value that identifies the type of message being sent from QEWD
355
356- **callback**: Callback function that is triggered on receipt of an incoming message of the specified type. The incoming message object is provided as its one and only argument.
357
358For example:
359
360 EWD.on('myTestMessage', (messageObj) => {
361 console.log('handle incoming message: ' + JSON.stringify(messageObj));
362 });
363
364All incoming messages will have a *type* property (which, of course, is used to trigger the *on()* Event Handler function. The rest of the message structure and content will depend on the QEWD back-end method that generated it.
365
366
367# The QEWD Application Back-end
368
369The back-end of an interactive QEWD application is defined in your QEWD-Up Application Directory. Interactive applications are supported in all three QEWD-Up Modes:
370
371- Native Monolith
372- Docker Monolith
373- Docker MicroServices
374
375In all three cases, a QEWD-Up instance can support as many interactive applications as you like, and you can run interactive applications together with REST applications, or run a QEWD-Up instance with just interactive applications and no REST APIs at all.
376
377In the case of the Docker Monolith mode, the Orchestrator and/or any of the MicroService QEWD Instances can run interactive QEWD applications. The key proviso is that each QEWD instance that runs an interactive application is exposed via a host port, so that the browser can make a WebSocket connections to that port.
378
379
380## Defining A QEWD Interactive Application
381
382There are two parts to defining an interactive QEWD Application in QEWD-Up:
383
384- creating a home for the browser/client-side code, including its HTML, CSS and JavaScript resources
385- defining the QEWD/server-side code, which consists of handler methods for each message type sent from the browser by the client side of the code.
386
387Each of your QEWD applications must be given a unique name - this can be any string value. This name will be used to identify the application on both the client and server side.
388
389## Locating Client-Side Code
390
391[The earlier section above](#the-qewd-websocket-client) described how to use the *ewd-client* module in your client side code. Having created that code, where you locate it will depend on whether:
392
393- your QEWD-Up instances are sitting behind a reverse-proxy such as NGINX
394- your QEWD Monolith instance or Orchstrator instance is directly exposed to the external users; or
395
396### Proxied Set=up
397
398If you are using a reverse-proxy such as NGINX, then your client-side resources should be placed in the NGINX Web Server root path, eg:
399
400 /usr/share/nginx/html/myQEWDApplication
401
402NGINX will then need to be configured to act as a proxy to your QEWD instance(s). [Documented separately](#link-here)
403
404### Directly-exposed Set-up
405
406If your QEWD instance is acting directly as the externally-facing web server, you should place the QEWD Application code in the appropriate */www* sub-folder within your QEWD-Up application folder.
407
408If you've previously started your QEWD-Up instance(s), you'll find a */www* subfolder already present. If not, just create it and add your application code. QEWD-Up will add its additional files to it when you next start it up.
409
410For example, for an interactive QEWD Application named *myQEWDApplication*:
411
412
413### Monolith
414
415 ~/dockerExample
416 |
417 |_ configuration
418 | |
419 | |_ config.json
420 |
421 |_ www
422 | |
423 | |_ myQEWDApplication
424 | |
425 | |_ index.html
426 | |
427 | |_ app.js etc....
428 |
429
430
431### MicroService: Orchestrator
432
433 ~/microserviceExample
434 |
435 |_ configuration
436 |
437 |_ orchestrator
438 | |
439 | |_ www
440 | |
441 | |_ myQEWDApplication
442 | |
443 | |_ index.html
444 | |
445 | |_ app.js etc....
446
447
448### MicroService: Other MicroService
449
450 ~/microserviceExample
451 |
452 |_ configuration
453 |
454 |_ login_service
455 | |
456 | |_ www
457 | |
458 | |_ myQEWDApplication
459 | |
460 | |_ index.html
461 | |
462 | |_ app.js etc....
463
464
465
466When you start up the QEWD-Up instance, you'll see that symbolic links to additional files and folders are automatically added to the */www* folders by QEWD-Up, eg:
467
468
469 ~/dockerExample
470 |
471 |_ configuration
472 | |
473 | |_ config.json
474 |
475 |_ www
476 | |
477 | |_ ewd-Client.js
478 | |
479 | |_ myQEWDApplication
480 | | |
481 | | |_ index.html
482 | | |
483 | | |_ app.js etc....
484 | |
485 | |_ qewd-monitor
486 | | |
487 | | |_ index.html
488 | | |
489 | | |_ bundle.js etc....
490
491
492Please leave these additional generated links/files untouched.
493
494
495## Locating Server-Side Code
496
497The way in which you define the server-side code of a QEWD Interactive application is very similar to how QEWD-Up REST APIs are defined.
498
499[You've seen earlier](#sending-messages-from-the-browser) how messages sent from the browser specify its *type*. The server-side of a QEWD Interactive application consists mainly of *message handler functions*: functions you write that specify how each of these message types is to be handled. What a *message handler function* does is completely up to you, provided:
500
501- the function signature, in terms of its arguments, meets the QEWD requirements
502- you use the methods provided to return any response messages and signal completion of your handler's logic
503
504The first step is to create a sub-folder named *qewd-apps* in your QEWD-Up directory for your QEWD Interactive applications. The location of this sub-folder depends on the mode of QEWD-Up application you're using.
505
506### Monolith
507
508 ~/dockerExample
509 |
510 |_ configuration
511 | |
512 | |_ config.json
513 |
514 |_ qewd-apps
515 |
516
517
518
519### MicroService: Orchestrator
520
521 ~/microserviceExample
522 |
523 |_ configuration
524 |
525 |_ orchestrator
526 | |
527 | |_ qewd-apps
528
529
530
531### MicroService: Other MicroService
532
533 ~/microserviceExample
534 |
535 |_ configuration
536 |
537 |_ login_service
538 | |
539 | |_ qewd-apps
540
541
542
543Within the *qewd-apps* folder, you create a sub-folder for each application you want to make available. The sub-folder name must match the name of the application. For example, if you wanted to define an application with a name of *myQEWDApplication*:
544
545### Monolith
546
547 ~/dockerExample
548 |
549 |_ configuration
550 | |
551 | |_ config.json
552 |
553 |_ qewd-apps
554 | |
555 | |_ myQEWDApplication
556
557
558
559### MicroService: Orchestrator
560
561 ~/microserviceExample
562 |
563 |_ configuration
564 |
565 |_ orchestrator
566 | |
567 | |_ qewd-apps
568 | |
569 | |_ myQEWDApplication
570
571
572
573### MicroService: Other MicroService
574
575 ~/microserviceExample
576 |
577 |_ configuration
578 |
579 |_ login_service
580 | |
581 | |_ qewd-apps
582 | |
583 | |_ myQEWDApplication
584
585
586You can now define the *message handler functions* that your application will require. Create a sub-folder for each one, using the message *type* as the sub-folder name, and then, within that sub-folder, create the function as a module file named *index.js*
587
588For example, for a message type of *login*:
589
590 ~/microserviceExample
591 |
592 |_ configuration
593 |
594 |_ login_service
595 | |
596 | |_ qewd-apps
597 | | |
598 | | |_ myQEWDApplication
599 | | | |
600 | | | |_ login
601 | | | | |
602 | | | | |- index.js
603
604
605
606## Message Handler Functions
607
608Each Message Handler Function *index.js* file must export a function with the following signature:
609
610 module.exports = function(messageObj, session, send, finished) {
611 };
612
613
614### Arguments
615
616The arguments of a *message handler function* are:
617
618- **messageObj**: The incoming message object, which will be identical to the object you sent from the browser using the [*ewd-client's send()*](#sending-messages-from-the-browser) method
619- **session**: The QEWD Session for the incoming message instance. QEWD uses the session token that was included in the message by the *ewd-client* module to automatically link your handler function to the user's QEWD Session
620- **send**: a function provided by QEWD that you can use to send *intermediate* messages to the browser (see later)
621- **finished**: a function provided by QEWD that you must use to return your handler's primary response (if any) and with which you signal to QEWD that you have finished using its Worker process (so that it can be returned to QEWD's available pool).
622
623### Message Handler Function Context
624
625The *this* object within your *message handler function* is the QEWD context which provides you access to, for example:
626
627- **this.db.use**: the function to use to instantiate a *document node object*, which is how you access the integrated persistent JSON / document database
628- **this.userDefined**: an object containing your QEWD configuration options *and* any custom properties that you defined at startup
629
630### The Incoming Message Object
631
632The first argument of a *message handler function* provides access to the incoming message object. For example, suppose you used the *ewd-client's send()* method to send the following message from the browser:
633
634 var msg = {
635 type: 'login',
636 params: {
637 username: 'rob',
638 password: 'secret'
639 }
640 };
641 EWD.send(msg, (responseObj) => {
642 // handle the reseponse returned by the QEWD message handler function
643 });
644
645You would handle this using a *message handler function* within a folder named *login*, and the *messageObj* argument would contain an exact copy of the message object you sent, ie:
646
647 {
648 type: 'login',
649 params: {
650 username: 'rob',
651 password: 'secret'
652 }
653 }
654
655So, your message handler logic for this example might look like this:
656
657 module.exports = function(messageObj, session, send, finished) {
658 var username = messageObj.params.username;
659 var password = messageObj.params.password;
660 // perform the appropriate logic to confirm the validity of the username and password
661 };
662
663
664### Returning a Response from your Message Handler Function
665
666You return a response from your *message handler function* using the *finished()* method which has a single argument: *responseObject*.
667
668The structure and content of the response object is up to you, but to return an error response, you should use the reserved response object structure:
669
670 {error: error_message_text}
671
672For example, extending the above example:
673
674 module.exports = function(messageObj, session, send, finished) {
675 var username = messageObj.params.username;
676 var password = messageObj.params.password;
677 // simple hard-coded validation by way of example:
678 if (username !== 'rob' && password !== 'secret') {
679 return finished({error: 'Invalid login attempt'});
680 }
681 finished({ok: true});
682 };
683
684The response object that you specify in your *finished()* method will be returned to the *ewd-client*, and will be contained in the *message* property of the response it receives. So, for example, taking the *ewd-client* example we used above:
685
686#### Successful login attempt
687
688 var msg = {
689 type: 'login',
690 params: {
691 username: 'rob',
692 password: 'secret'
693 }
694 };
695 EWD.send(msg, (responseObj) => {
696 console.log('Response was ' + JSON.stringify(responseObj.message));
697 // responseObj.message.ok = true
698 });
699
700#### Unsuccessful login attempt
701
702 var msg = {
703 type: 'login',
704 params: {
705 username: 'xxx',
706 password: 'yyyyyy'
707 }
708 };
709 EWD.send(msg, (responseObj) => {
710 console.log('Response was ' + JSON.stringify(responseObj.message));
711 // responseObj.message.error = 'Invalid login attempt'
712 });
713
714**IMPORTANT**: You must **ALWAYS** terminate your *message handler function*'s logic by invoking the *finished()* function. Failure to do so will mean that the QEWD Worker process that invokes your *message handler function* will never be released back to QEWD's worker pool. If a number of such message types are handled, you'll quickly run out of available Worker processes and QEWD will queue up subsequent messages until you manually force down the Worker processes using the *qewd-monitor* application or you restart QEWD (which will result in the loss of queued messages).
715
716If your *message handler function* includes asynchronous logic, then you must make sure you invoke the *finished()* method from within the asynchronous logic's callback. For example:
717
718 module.exports = function(messageObj, session, send, finished) {
719 setTimeout(() => {
720 finished({ok: true});
721 }, 5000);
722 };
723
724In the example above, the QEWD Worker process will not be released until after 5 seconds, when the *setTimeout* has triggered.
725
726
727**NOTE**: If you are using WebSockets for your application message transport, you do not have to return a response. You still **MUST** use the *finished()* function to signal that you have completed your *message handler function's* logic, but simply don't provide an argument, eg:
728
729 module.exports = function(messageObj, session, send, finished) {
730 //.. process the incoming message
731 finished();
732 };
733
734No response will be returned to the browser in this situation.
735
736
737### Accessing the User's QEWD Session
738
739You can use the QEWD Session to save and retrieve user-specific information that you want to exist for the duration of the user's session.
740
741A user's session starts when they load your applications's client-side resources into their browser and your code invoked the *ewd-client's start()* method.
742
743In most situations, a user's session stops when it times out, through lack of activity. By default a user session will expire after 5 minutes: this initial timeout value is set when *ewd-client* first registers the application.
744
745You can reset the session timeout value from within any of your *message handler functions*. For most applications that require a user authentication/login step, your *login message handler function* will be the normal place to do this.
746
747The QEWD Session object is a *Document Node Object* (ie it is implemented using the integrated persistent JSON / document database), and is made available you via the 2nd argument of your *message handler function*. It has a number of reserved properties and methods that you may use, but you can create and maintain your own custom information within its *data* property.
748
749[See here for detailed documentation about the QEWD Session Object](#not-yet-documented).
750
751Here's an example demonstrating typical use of the QEWD Session.
752
753 module.exports = function(messageObj, session, send, finished) {
754 var username = messageObj.params.username;
755 var password = messageObj.params.password;
756 // simple hard-coded validation by way of example:
757 if (username !== 'rob' && password !== 'secret') {
758 return finished({error: 'Invalid login attempt'});
759 }
760
761 // valid login, so flag the user's session as authenticated
762 // and reset and update the session timeout
763
764 session.authenticated = true;
765 session.timeout = 3600; // 1 hour inactivity timeout
766 session.updateExpiry(); // apply the new timeout immediately
767
768 session.data.$('username').value = username; // add username to session
769
770 finished({ok: true});
771 };
772
773
774Your other *message handler functions* can check the *session.authenticated* property to confirm that the user has logged in - you'll want to prevent unauthorised access by users who have not logged in! They can also make use of or update the user's session information. For example:
775
776
777 module.exports = function(messageObj, session, send, finished) {
778 if (!session.authenticated) {
779 return finished({error: 'You have not logged in'});
780 }
781 // get the user's username with which they logged in:
782
783 var username = session.data.$('username').value;
784
785 // save some information that you sent from the browser into the user's session
786
787 session.data.$('myNewInfo').setDocument(messageObj.params.newInfo);
788
789 // return the username back to the browser
790
791 finished({username: username});
792 };
793
794
795### Accessing the Integrated Persistent JSON/Document Database
796
797You can access QEWD's integrated Persistent JSON Database from within your *message handler functions* and make use of it for whatever purposes you require. The key first step is to use the *this.db.use()* function to instantiate what is known as a *Document Node Object*. For example:
798
799 // create a Document Node Object that references the topmost node - a physical Global
800 var userDoc = this.db.use('Users');
801
802 // then create a Document Node Object that references the former's 'administrator' child node
803
804 var adminDoc = userDoc.$('admininstrators');
805
806
807The latter Document Node Object could alternatively be created in one step:
808
809 var adminDoc = this.db.use('Users', 'administrators');
810
811From this point on, you can use and apply all the methods and techniques described in the training presentation slide decks listed below:
812
813#### Introduction to Global Storage Databases
814
815- [Modelling NoSQL Databases using Global Storage](https://www.slideshare.net/robtweed/ewd-3-training-course-part-18-modelling-nosql-databases-using-global-storage)
816- [Basic Access to a Global Storage Database from JavaScript: the cache.node APIs](https://www.slideshare.net/robtweed/ewd-3-training-course-part-19-the-cachenode-apis)
817
818#### JavaScript Abstraction of Global Storage
819
820- [The DocumentNode Object](https://www.slideshare.net/robtweed/ewd-3-training-course-part-20-the-documentnode-object)
821- [Persistent JavaScript Objects](https://www.slideshare.net/robtweed/ewd-3-training-course-part-21-persistent-javascript-objects)
822- [Traversing Documents](https://www.slideshare.net/robtweed/ewd-3-training-course-part-22-traversing-documents-using-documentnode-objects)
823- [Traversing a Range of Nodes](https://www.slideshare.net/robtweed/ewd-3-training-course-part-23-traversing-a-range-using-documentnode-objects)
824- [Traversing a Document's Leaf Nodes](https://www.slideshare.net/robtweed/ewd-3-training-course-part-24-traversing-a-documents-leaf-nodes)
825- [Global Storage as a Document Database](https://www.slideshare.net/robtweed/ewd-3-training-course-part-25-document-database-capabilities)
826- [Event-Driven Indexing](https://www.slideshare.net/robtweed/ewd-3-training-course-part-26-eventdriven-indexing)
827
828#### QEWD's Session Storage
829
830In the previous section you saw examples of how to use the QEWD Session which makes use of this same JSON Database. The user-defined custom storage part of the QEWD session is exposed as a *Document Node Object*, allowing, once again, all the above techniques and methods to be applied to your custom Session storage.
831
832[The QEWD Session is described in more detail here](https://www.slideshare.net/robtweed/ewd-3-training-course-part-27-the-ewd-3-session)
833
834
835### Intermediate Messages
836
837If you are using WebSocket messaging for your QEWD Interactive application, you are not limited to a single response message being returned from your *message handler function*. QEWD provides your *message handler functions* with a function - *send()* - that allows you to send additional messages, known as *intermediate messages* from your handler function before you signal its completion with the *finished()* method.
838
839Unlike the *finished()* method that can only be invoked once from within your *message handler function*, you can invoke the *send()* method as many times as you like.
840
841The *send()* method takes a single argument: *messageObject*. The content and structure of this object is up to you, but it **MUST** contain one reserved property: *type*. The value of *type* is up to you to define and is a string value. It's a good idea to use a different *type* than the one for the message your handler function is dealing with - this ensures that your client/browser-side *ewd-client* response handler doesn't get confused.
842
843Here's an example of a *message handler function*, let's say for a mesage of type *intermediateTest*, that generates an intermediate and final response message provided the user is logged in:
844
845
846 module.exports = function(messageObj, session, send, finished) {
847 if (!session.authenticated) {
848 return finished({error: 'You have not logged in'});
849 }
850
851 send({
852 type: 'info',
853 foo: 'bar'
854 });
855
856 //... etc
857
858 finished({ok: true});
859 };
860
861
862The associated browser/client-side logic might look like this:
863
864
865 EWD.on('info', (responseObj) => {
866 // this will handle the intermediate message
867 // responseObj will contain {"type": "info", "foo": "bar"}
868 });
869
870 var msg = {
871 type: 'intermediateTest'
872 };
873 EWD.send(msg, (responseObj) => {
874 // this will handle the response (or error) from the message handler function's finished() function
875 }
876
877
878In theory, you could create a *message handler function* that sent a series of intermediate messages using a timed event (eg using *setInterval*), but it would be a bad idea to do so - it would mean that the QEWD Worker process handling the *message handler function* would be tied up and not released back to the QEWD Available Worker Pool for the entire duration of the timed events. See later for alternative techniques for this kind of scenario that avoid tying up a Worker process for long periods of time.
879
880
881# Life-Cycle Event Hooks
882
883For many interactive QEWD Applications, the functionality provided by the *ewd-client* front-end module and your back end *message handler functions* will be sufficient for your needs. However, QEWD-Up provides additional advanced techniques for customising, automating and/or simplifying your applications. These take the form of *Life-Cycle Event Hooks* that allow you to define logic that gets invoked at various stages of the server-side processing of your applications' messages.
884
885In order to make best use of these Life-Cycle Event Hooks, you need to fully understand the complete life-cycle of a QEWD Application and its handling of messages. [This slide-deck](https://www.slideshare.net/robtweed/ewd-3-training-course-part-8-anatomy-of-the-ewdxpress-messaging-cycle) describes the life-cycle diagrammatically, but it is also described below in detail.
886
887## The Life-Cycle of an Interactive QEWD Application
888
8891. The Life-Cycle starts when an instance of the *ewd-client* module registers an application. This has been [explained in detail earlier](#application-registration). In summary:
890 - the *ewd-client's start()* method sends to the QEWD back-end an *ewd-register* message that identifies the application name;
891 - on receipt of this message, the QEWD back-end starts a user QEWD Session, generates a random Uid-formatted token as a pointer to that Session, saves the application name in the Session, and returns the token to in the response that is returned to the browser;
892 - on receipt of this response, *ewd-client* saves the token and creates the *send()* method that will provide the means by which subsequent user/application messages are sent to the QEWD back-end
893
8942. A message is sent from a browser using the *ewd-client's send()* method. The *send()* method always automatically adds the QEWD Session token to the message behind the scenes.
895
8963. The message is received by the QEWD back-end, queued and dispatched to an available Worker process
897
8984. The QEWD Worker process performs a number of initial tests:
899 - does the message include a token? If not, an error message is returned immediately to the browser, the message is discarded without any further processing and the Worker process returned to QEWD's Available Worker Pool
900 - does the token exist as a pointer to a QEWD Session, and, if so, has that QEWD Session not yet expired? If it fails either of these tests, an error message is returned immediately to the browser, the message is discarded without any further processing and the Worker process returned to QEWD's Available Worker Pool
901
9025. The token is used to access the user's Session, from which it can identify the application to which this message applies. Note that *ewd-client* deliberately does not send a property that explicitly identifies the application: this prevents malicious attempts by a user to access a different application from the one they have been registered to use. The application is implicitly defined via the QEWD Session token.
903
9046. The Worker process checks to see whether the *application handler module* that handles messages for this application has been loaded. When the QEWD-Up instance was started, it created this *application handler module* automatically from the set of *message handler function* modules that you had defined for the application. If the *application handler module* hasn't yet been loaded into the QEWD Worker process, this is now done. However, if the QEWD Worker process to which the message has been dispatched had previously handled a message for this application (for **any** browser user), the *application handler module* will already be loaded and ready for re-use. Step 6 therefore only occurs once per named application in any one QEWD Worker Process.
905
9067. The Worker process checks to see whether the incoming message object includes a *type* property. If not, an error message is returned immediately to the browser, the message is discarded without any further processing and the Worker process returned to QEWD's Available Worker Pool
907
9088. QEWD checks to see whether a *message handler function* has been defined within the *application handler module* for the specified message *type* in the incoming message. If not, an error message is returned immediately to the browser, the message is discarded without any further processing and the Worker process returned to QEWD's Available Worker Pool
909
9109. QEWD can now invoke your *message handler function*. As described earlier, your function will use the *finished()* method to signal completion of your handler logic and, optionally, to define a response message object.
911
91210. The expiry time of the user's QEWD session is updated, by adding the current time to the session timeout value.
913
91411. If your *message handler function* defined a response message object as an argument of the *finished() method*, it is passed from the QEWD Worker Process to QEWD's Master process. The Worker Process is returned to the QEWD Available Pool.
915
91612. If you had created a response object in your *message handler function*, QEWD's Master Process sends it to the browser.
917
91813. *ewd-client* handles the response object within the browser, either via the *send()* method's callback function, or via a *type*-specific *EWD.on()* event handler.
919
920
921Within this Life-Cycle, there are three points at which you can intercept the process and customise/augment the processing. This is done via the three Life-Cycle Event Hooks that you have available:
922
923- **onLoad**: triggered during step 6 above, when a QEWD Worker process first loads the *application handler module* for the application to which an incoming message belongs. This is a fairly specialised event hook, but can be useful for augmenting the context environment for all of an application's *message handler functions*.
924
925- **beforeHandler**: triggered at step 9 above, within the QEWD Worker process. This hoow is applied to *all* of the messages for a specific QEWD application, **before** they are handled by their appropriate *message handler function*. A typical use for this hook is to carry out some processing that should apply to all, if not most, of an application's message *types*: for example, ensuring that the user has been authenticated before allowing the *type*-specific *message handler function* to be invoked, and returning an error response if not.
926
927- **onResponse**: triggered between steps 11 and 12 above, and invoked on the QEWD Master process when it receives the response object returned by the *message handler function's finished()* method, but before it sends it to the browser. This event hook is message *type*-specific. A typical use of this hook is to trigger one or more further messages to be generated on the QEWD back-end that perform other tasks in the background, whilst the primary response is returned to the browser. This hook can therefore be used to prevent QEWD Worker Processes being unnecessarily tied up.
928
929Each of these Life-Cycle Event Hooks are described in more detail below:
930
931## onLoad
932
933### Location
934
935Create a file named *onLoad.js* within your Interactive QEWD Application sub-folder. Note: the file name is case-sensitive.
936
937For example:
938
939#### Monolith
940
941 ~/dockerExample
942 |
943 |_ configuration
944 | |
945 | |_ config.json
946 |
947 |_ qewd-apps
948 | |
949 | |_ myQEWDApplication
950 | | |
951 | | |_ onLoad.js
952
953
954#### MicroService: Other MicroService
955
956 ~/microserviceExample
957 |
958 |_ configuration
959 |
960 |_ login_service
961 | |
962 | |_ qewd-apps
963 | | |
964 | | |_ myQEWDApplication
965 | | | |
966 | | | |_ onLoad.js
967
968
969### Signature
970
971The *onLoad.js* file must export a function with the following signature:
972
973 module.exports = function(application) {
974 // perform your onLoad logic
975 };
976
977
978### Arguments
979
980- **application**: the name of the application to which the incoming message belongs. This can be usefully used to distinguish application-specific *this* context augmentation.
981
982### Context
983
984The *this* object within your *onLoad* event hook method is the QEWD object. You therefore have access to the integrated persistent JSON database and all the QEWD configuration information.
985
986### Example Use
987
988Each of your *message handler functions* can *require()* any additional Node.js modules that you want to make use of in your processing logic. However, if all, or most, of your *message handler functions* are going to use the same module(s), you can use the *onLoad* event hook to load the module(s) and augment *this* with them. For example:
989
990 module.exports = function(application) {
991 if (!this.myModules) {
992 this.myModules = {};
993 }
994 this.myModules[application] = {
995 moment: require('moment')
996 };
997 };
998
999Any/all of your *message handler functions* could then make use of the *moment* module without having to explicity *require()* it, since they also have the same *this* context, for example:
1000
1001 module.exports = function(messageObj, session, send, finished) {
1002
1003 var moment = this.myModules[session.application].moment;
1004
1005 //...etc
1006 };
1007
1008
1009## beforeHandler
1010
1011### Location
1012
1013Create a file named *beforeHandler.js* within your Interactive QEWD Application sub-folder. Note: the file name is case-sensitive.
1014
1015For example:
1016
1017#### Monolith
1018
1019 ~/dockerExample
1020 |
1021 |_ configuration
1022 | |
1023 | |_ config.json
1024 |
1025 |_ qewd-apps
1026 | |
1027 | |_ myQEWDApplication
1028 | | |
1029 | | |_ beforeHandler.js
1030
1031
1032#### MicroService: Other MicroService
1033
1034 ~/microserviceExample
1035 |
1036 |_ configuration
1037 |
1038 |_ login_service
1039 | |
1040 | |_ qewd-apps
1041 | | |
1042 | | |_ myQEWDApplication
1043 | | | |
1044 | | | |_ beforeHandler.js
1045
1046
1047### Signature
1048
1049The *beforeHandler.js* file must export a function with the following signature:
1050
1051 module.exports = function(messageObj, session, send, finished) {
1052 // perform your beforeHandler logic
1053 };
1054
1055
1056### Arguments
1057
1058The arguments of the *beforeHandler* hook function are the same as your *message handler function*:
1059
1060- **messageObj**: The incoming message object, which will be identical to the object you sent from the browser using the [*ewd-client's send()*](#sending-messages-from-the-browser) method
1061- **session**: The QEWD Session for the incoming message instance. QEWD uses the session token that was included in the message by the *ewd-client* module to automatically link your handler function to the user's QEWD Session
1062- **send**: a function provided by QEWD that you can use to send *intermediate* messages to the browser
1063- **finished**: a function provided by QEWD that you must use to return a response from your *beforeHandler*(if any) and with which you signal to QEWD that you have finished using the Worker process (so that it can be returned to QEWD's available pool).
1064
1065### returnValue
1066
1067If the *beforeHandler* function simply returns (ie returns a *null* returnValue), then your *message handler function* will be triggered. In this circumstance you **must not** invoke the *finished()* function within the *beforeHandler*.
1068
1069If the *beforeHandler* function returns *false*, then your *message handler function* will **not** be invoked. In this circumstance you **must** invoke the *finished()* function within the *beforeHandler* before *return*ing.
1070
1071See the example below.
1072
1073
1074### Context
1075
1076The *this* object within your *beforeHandler* event hook method is the QEWD object. You therefore have access to the integrated persistent JSON database and all the QEWD configuration information.
1077
1078### Example Use
1079
1080You will typically use the *beforeHandler* to check whether or not the user that sent the incoming message was autheticated or not, and therefore whether or not they can invoke your *message handler function*.
1081
1082You'll want certain incoming message types, eg a *login* message with which a user is authenticated, to bypass the *beforeHandler* tests.
1083
1084For example:
1085
1086 module.exports = function(messageObj, session, send, finished) {
1087
1088 // bypass authentication test for login messages:
1089
1090 if (messageObj.type === 'login') return;
1091
1092 // for all other messages, check if user is already authenticated:
1093
1094 if (!session.authenticated) {
1095
1096 // if not return an error message and release the Worker process:
1097
1098 finished({error: 'User MUST be authenticated'});
1099
1100 // bypass the handler function for this message type:
1101
1102 return false;
1103 }
1104 };
1105
1106
1107
1108## onResponse
1109
1110### Location
1111
1112Create a file named *onResponse.js* within the sub-folder for the message type too which this hook will apply, ie alongside the *index.js* file containing the *message handler function*. Note: the file name is case-sensitive.
1113
1114For example:
1115
1116#### Monolith
1117
1118 ~/dockerExample
1119 |
1120 |_ configuration
1121 | |
1122 | |_ config.json
1123 |
1124 |_ qewd-apps
1125 | |
1126 | |_ myQEWDApplication
1127 | | |
1128 | | |_ getStats
1129 | | | |
1130 | | | |_index.js
1131 | | | |
1132 | | | |_onResponse.js
1133
1134
1135
1136#### MicroService: Other MicroService
1137
1138 ~/microserviceExample
1139 |
1140 |_ configuration
1141 |
1142 |_ login_service
1143 | |
1144 | |_ qewd-apps
1145 | | |
1146 | | |_ myQEWDApplication
1147 | | | |
1148 | | | |_ getStats
1149 | | | | |
1150 | | | | |_index.js
1151 | | | | |
1152 | | | | |_onResponse.js
1153
1154
1155### Signature
1156
1157The *onResponse.js* file must export a function with the following signature:
1158
1159 module.exports = function(messageObj, send) {
1160 // perform your onResponse logic
1161 };
1162
1163
1164### Arguments
1165
1166The arguments of the *onResponse* hook function are:
1167
1168- **messageObj**: The message object returned from the *finished()* function within your *message handler function* (or *beforeHandler* function)
1169- **send**: a function provided by QEWD that you can use to return a message object to the browser.
1170
1171
1172### Context
1173
1174The *this* object within your *onResponse* event hook method is the QEWD object, as instantiated on the Master process.
1175
1176**NOTE*: The *onResponse* function is invoked on the QEWD Master Process which **does not** have access to either the user's QEWD Session or the integrated JSON database.
1177
1178### Best Practice
1179
1180Because the *onResponse* function is invoked on the QEWD Master Process, you should avoid performing any CPU-intensive or long-running processing. Otherwise you risk causing performance problems for all other users.
1181
1182Such activity, if necessary, should be conducted, instead, in a QEWD Worker Process. To do this, use the *this.handleMessage()* function. This will place a new message object onto the QEWD queue. QEWD will then dispatch it to the first available Worker process for handling. You will need to define a *message handler function* for the *type* you assign to your new message. Its response will be handled within the callback function of *this.handleMessage()*. See the example below.
1183
1184If you want to perform this kind of action, you'll need to have access to the token that was sent with the original incoming message and put it into the new message that you dispatch to a Worker Process via *this.handleMessage()*. The simplest way to do this is to return *session.token* as one of the response properties of your *message handler function*.
1185
1186
1187### Example Use
1188
1189Typically you'll use the *onResponse* hook to trigger some background activity whilst returning a message back to the browser.
1190
1191For example, here's the *message handler function* for the original incoming message of type *getInfo*:
1192
1193
1194 module.exports = function(messageObj, session, send, finished) {
1195 finished({
1196 username: session.data.$('username').value,
1197 token: session.token
1198 });
1199 };
1200
1201
1202This response will be intercepted on the QEWD Master Process by the *onResponse* hook for the *getInfo* message type:
1203
1204
1205 module.exports = function(message, send) {
1206
1207 // get hold of the QEWD Session token and remove it from the message object
1208
1209 var token = message.token;
1210 delete message.token;
1211
1212 // return the message to the browser:
1213
1214 send(message);
1215
1216 // construct a new message of type getStats, and add the Session token:
1217
1218 var msgObj = {
1219 type: 'getStats',
1220 token: token
1221 };
1222
1223 // place it on the QEWD queue for processing
1224 // it will be sent to a Worker process and the 'getStats' handler function
1225 // will be used to process it
1226
1227 this.handleMessage(msgObj, function(responseObj) {
1228
1229 // responseObj will be the response from the 'getStats' handler function
1230
1231 // send the 'getStats' response to the browser
1232 send(responseObj);
1233 });
1234 };
1235
1236
1237We'll need to define a *message handler function* for this new *getStats* message *type*. For example:
1238
1239 module.exports = function(messageObj, session, send, finished) {
1240
1241 // because we sent back the token, we can access the user's session again
1242
1243 finished({
1244 stats: session.data.$('stats').getDocument()
1245 });
1246 };
1247
1248Note that the response from the *finished()* function of this *getStats* handler will not be automatically returned to the browser, but instead it will be picked up by the callback function of the *this.handleMessage()* function in the *onResponse* hook above.
1249
1250From this relatively trivial example, you can probably see that you can potentially chain together complex sequences of events and messages. By using WebSockets, QEWD allows you to send the results from the server as separate messages as they are obtained, rather than waiting until a composite set of values is collated, which is what would have to be done if you were using HTTP-based messaging (eg REST or Ajax).
1251