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 |
|
31 | In 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 |
|
33 | WebSockets 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 |
|
35 | Conversely, 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 |
|
37 | That having been said, request/response messaging from the browser is also catered for and made very simple to use.
|
38 |
|
39 | If 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 |
|
43 | When 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 |
|
45 | This 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 |
|
47 | The *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 |
|
56 | There 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 |
|
65 | In 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 |
|
73 | If 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 |
|
78 | Once 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 |
|
86 | As 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 |
|
88 | Furthermore, 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 |
|
90 | Additionally, 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 |
|
93 | Registration is triggered by invoking *ewd-client's start()* method, so let's now examine how it is used.
|
94 |
|
95 | ### The *start()* Method
|
96 |
|
97 | The 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 |
|
107 | You 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 |
|
111 | or:
|
112 |
|
113 | EWD.start({
|
114 | application: 'myQEWDApplication',
|
115 | $: $,
|
116 | io: io,
|
117 | url: 'http://qewd.example.com:8080'
|
118 | });
|
119 |
|
120 | The 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 |
|
125 | In 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 |
|
135 | Note that in the above example, *$*, *io* and *EWD* will have been implicitly created by the libraries loaded in the *<script>* tags.
|
136 |
|
137 |
|
138 | In 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 |
|
157 | If you want to use WebSocket messaging (the recommended approach), you **DO NOT** need to specify the *$* argument.
|
158 |
|
159 | If 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 |
|
161 | The 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 |
|
167 | For 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 |
|
203 | Although 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 |
|
205 | It 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 |
|
209 | In 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 |
|
229 | This logic and approach is fairly simple to adapt for use with Angular.js
|
230 |
|
231 |
|
232 | In 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 |
|
234 | Here'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 |
|
267 | Note how the fully started-up instance of *ewd-client* is passed as a prop to your application:
|
268 |
|
269 | <App qewd={qewd} />
|
270 |
|
271 | From within your application component(s), you can send your messages using *this.props.qewd.send()*
|
272 |
|
273 |
|
274 | Similarly, 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 |
|
279 | Once *ewd-client* has registered your application, you can send messages to the QEWD back-end using its *send()* method.
|
280 |
|
281 | This 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 |
|
292 | For 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 |
|
305 | The example above would send the *login* message as a WebSocket message.
|
306 |
|
307 | To 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 |
|
322 | See 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 |
|
327 | If 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 |
|
329 | If, 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 |
|
331 | The way to create messages in the QEWD back-end is described later.
|
332 |
|
333 | We'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 |
|
335 | However, 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 |
|
341 | In 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 |
|
347 | If you are using *react-qewd*, you'll probably access it using:
|
348 |
|
349 | this.props.qewd.on(message, callback);
|
350 |
|
351 |
|
352 | The 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 |
|
358 | For example:
|
359 |
|
360 | EWD.on('myTestMessage', (messageObj) => {
|
361 | console.log('handle incoming message: ' + JSON.stringify(messageObj));
|
362 | });
|
363 |
|
364 | All 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 |
|
369 | The 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 |
|
375 | In 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 |
|
377 | In 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 |
|
382 | There 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 |
|
387 | Each 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 |
|
398 | If 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 |
|
402 | NGINX 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 |
|
406 | If 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 |
|
408 | If 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 |
|
410 | For 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 |
|
466 | When 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 |
|
492 | Please leave these additional generated links/files untouched.
|
493 |
|
494 |
|
495 | ## Locating Server-Side Code
|
496 |
|
497 | The 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 |
|
504 | The 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 |
|
543 | Within 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 |
|
586 | You 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 |
|
588 | For 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 |
|
608 | Each 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 |
|
616 | The 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 |
|
625 | The *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 |
|
632 | The 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 |
|
645 | You 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 |
|
655 | So, 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 |
|
666 | You return a response from your *message handler function* using the *finished()* method which has a single argument: *responseObject*.
|
667 |
|
668 | The 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 |
|
672 | For 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 |
|
684 | The 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 |
|
716 | If 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 |
|
724 | In 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 |
|
734 | No response will be returned to the browser in this situation.
|
735 |
|
736 |
|
737 | ### Accessing the User's QEWD Session
|
738 |
|
739 | You 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 |
|
741 | A 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 |
|
743 | In 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 |
|
745 | You 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 |
|
747 | The 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 |
|
751 | Here'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 |
|
774 | Your 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 |
|
797 | You 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 |
|
807 | The latter Document Node Object could alternatively be created in one step:
|
808 |
|
809 | var adminDoc = this.db.use('Users', 'administrators');
|
810 |
|
811 | From 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 |
|
830 | In 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 |
|
837 | If 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 |
|
839 | Unlike 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 |
|
841 | The *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 |
|
843 | Here'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 |
|
862 | The 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 |
|
878 | In 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 |
|
883 | For 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 |
|
885 | In 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 |
|
889 | 1. 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 |
|
894 | 2. 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 |
|
896 | 3. The message is received by the QEWD back-end, queued and dispatched to an available Worker process
|
897 |
|
898 | 4. 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 |
|
902 | 5. 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 |
|
904 | 6. 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 |
|
906 | 7. 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 |
|
908 | 8. 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 |
|
910 | 9. 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 |
|
912 | 10. The expiry time of the user's QEWD session is updated, by adding the current time to the session timeout value.
|
913 |
|
914 | 11. 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 |
|
916 | 12. If you had created a response object in your *message handler function*, QEWD's Master Process sends it to the browser.
|
917 |
|
918 | 13. *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 |
|
921 | Within 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 |
|
929 | Each of these Life-Cycle Event Hooks are described in more detail below:
|
930 |
|
931 | ## onLoad
|
932 |
|
933 | ### Location
|
934 |
|
935 | Create a file named *onLoad.js* within your Interactive QEWD Application sub-folder. Note: the file name is case-sensitive.
|
936 |
|
937 | For 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 |
|
971 | The *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 |
|
984 | The *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 |
|
988 | Each 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 |
|
999 | Any/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 |
|
1013 | Create a file named *beforeHandler.js* within your Interactive QEWD Application sub-folder. Note: the file name is case-sensitive.
|
1014 |
|
1015 | For 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 |
|
1049 | The *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 |
|
1058 | The 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 |
|
1067 | If 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 |
|
1069 | If 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 |
|
1071 | See the example below.
|
1072 |
|
1073 |
|
1074 | ### Context
|
1075 |
|
1076 | The *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 |
|
1080 | You 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 |
|
1082 | You'll want certain incoming message types, eg a *login* message with which a user is authenticated, to bypass the *beforeHandler* tests.
|
1083 |
|
1084 | For 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 |
|
1112 | Create 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 |
|
1114 | For 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 |
|
1157 | The *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 |
|
1166 | The 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 |
|
1174 | The *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 |
|
1180 | Because 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 |
|
1182 | Such 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 |
|
1184 | If 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 |
|
1189 | Typically you'll use the *onResponse* hook to trigger some background activity whilst returning a message back to the browser.
|
1190 |
|
1191 | For 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 |
|
1202 | This 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 |
|
1237 | We'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 |
|
1248 | Note 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 |
|
1250 | From 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 |
|