1 | # qewd-up: A new fast-track route to Building APIs Using QEWD
|
2 |
|
3 | Rob Tweed <rtweed@mgateway.com>
|
4 | 11 May 2020, M/Gateway Developments Ltd [http://www.mgateway.com](http://www.mgateway.com)
|
5 |
|
6 | Twitter: @rtweed
|
7 |
|
8 | Google Group for discussions, support, advice etc: [http://groups.google.co.uk/group/enterprise-web-developer-community](http://groups.google.co.uk/group/enterprise-web-developer-community)
|
9 |
|
10 | # What is QEWD-Up?
|
11 |
|
12 | Previously, in order to use QEWD, you needed to create a mixture of configuration settings and application code, and in doing so you needed to have a relatively detailed understanding of how QEWD worked under the covers. This meant:
|
13 |
|
14 | - a learning curve which took time and effort
|
15 | - it could be difficult and time-consuming to construct the necessary moving parts that make up an API
|
16 | - the scalability of this approach could become more problematic with increasing numbers of APIs to support
|
17 | - the maintainability of APIs could be tricky and difficult to trace through the moving parts of QEWD itself. Someone else picking up your APIs wouldn�t necessarily find it intuitively clear what they were doing and how they worked.
|
18 |
|
19 | QEWD-Up is a new layer of abstraction on top of QEWD that aims to hide all these issues away, leaving you with just a simple description of your API routes and the associated code that they invoke at the QEWD back-end. You now just describe the What, and QEWD-up looks after the How. Under the covers, it�s all standard QEWD as before - it�s just all been automated and abstracted out of the way for you!
|
20 |
|
21 | # QEWD-Up Modes
|
22 |
|
23 | QEWD-Up can be used in three key architectural modes:
|
24 |
|
25 | - [**Dockerised QEWD Monolith**](#dockerised-qewd-monolith): QEWD running as a Dockerised Container, and as a monolithic application defined as a set of REST APIs
|
26 |
|
27 | - [**Dockerised QEWD MicroServices**](#dockerised-qewd-microservices): A set of Dockerised QEWD Containers, implementing your REST APIs across a set of MicroServices
|
28 |
|
29 | - [**Native QEWD Monolith**](#native-qewd-monolith): If you prefer a native, un-Dockerised platform, this option has QEWD installed and running natively on your system (along with Node.js and YottaDB), allowing you to create a monolithic application defined as a set of REST APIs
|
30 |
|
31 | In the first two modes, *all* you need to install on your hardware is Docker. Everything else that is needed by QEWD is encapsulated within the QEWD Docker Container(s). These two Docker-based modes allow you to be up and running with the minimum of fuss and and within a few minutes, rapidly building APIs that are as complex as you require, but which are easy to maintain and understand.
|
32 |
|
33 | Getting started with each of the QEWD-Up modes is described below. They all follow a similar pattern, but there are some mode-specific differences.
|
34 |
|
35 | For advanced use of QEWD-Up, see the [detailed documentation here.](https://github.com/robtweed/qewd/tree/master/up/docs)
|
36 |
|
37 |
|
38 |
|
39 | # Dockerised QEWD Monolith
|
40 |
|
41 | ## Pre-requisites
|
42 |
|
43 | It's very quick and simple to get started in this mode, because all you need to install on your machine is Docker. Everything else is handled by the QEWD Docker Container (which is available from the Docker Hub as rtweed/docker-server (or, if you use a Raspberry Pi: rtweed/docker-server-rpi )
|
44 |
|
45 | There are lots of documents on the Internet that describe how to install Docker. If you use Ubuntu 18.04, I�ve found [this to be an excellent set of instructions](https://www.digitalocean.com/community/tutorials/how-to-install-and-use-docker-on-ubuntu-18-04).
|
46 |
|
47 | However, if you�re using a different flavour of Linux, a simple Google search will quickly find what you need.
|
48 |
|
49 | if you want to try out QEWD-Up on a Raspberry Pi, you can install Docker by simply typing:
|
50 |
|
51 | curl -sSL https://get.docker.com | sh
|
52 |
|
53 |
|
54 | ## Defining Your APIs
|
55 |
|
56 | Create a folder for your application / suite of APIs. I'm going to name my example *dockerExample* (eg using the file path *~/dockerExample*). Also create sub-folders beneath it as shown below (use the sub-folder names exactly as shown):
|
57 |
|
58 |
|
59 | ~/dockerExample
|
60 | |
|
61 | |_ configuration
|
62 | |
|
63 | |_ apis
|
64 | |
|
65 |
|
66 | Create two text files in the configuration folder:
|
67 |
|
68 |
|
69 | ~/dockerExample
|
70 | |
|
71 | |_ configuration
|
72 | | |
|
73 | | |_ routes.json
|
74 | | |
|
75 | | |_ config.json
|
76 | |
|
77 | |_ apis
|
78 | |
|
79 |
|
80 |
|
81 | ### The *config.json* File
|
82 |
|
83 | The *config.json* file provides QEWD-Up with instructions on how to configure QEWD.
|
84 |
|
85 | As its file extension implies, its contents must be correctly-structured JSON.
|
86 |
|
87 | QEWD-Up will apply sensible default values unless overridden by information in your *config.json* file.
|
88 |
|
89 | At the very least, your *config.json* file should contain the following:
|
90 |
|
91 | {
|
92 | "qewd_up": true
|
93 | }
|
94 |
|
95 | See later in this document for more details on the *config.json* file and how to configure QEWD differently from the QEWD-Up defaults.
|
96 |
|
97 |
|
98 | ### The *routes.json* File
|
99 |
|
100 | You MUST create a file named *routes.json*. This file contains the definitions of your REST routes, expressed as a JSON array of route objects. Each route is described in terms of:
|
101 |
|
102 | - the URI path
|
103 | - the HTTP method
|
104 | - the name of the handler module that will perform the work of this API
|
105 |
|
106 | *eg* here�s the definition of a single API:
|
107 |
|
108 | [
|
109 | {
|
110 | "uri": "/api/info",
|
111 | "method": "GET",
|
112 | "handler": "getInfo"
|
113 | }
|
114 | ]
|
115 |
|
116 | You can describe as many routes as you wish in this file. The syntax MUST be strict JSON - ie all property names and text-string values must be double-quoted.
|
117 |
|
118 |
|
119 | ### Defining Your API Handlers
|
120 |
|
121 | You define your API Handlers within the *apis* folder. First create a sub-folder for each *handler* property name that you defined in the *routes.json* file (see above). For example, using the *routes.json* example above, you'd create:
|
122 |
|
123 | ~/dockerExample
|
124 | |
|
125 | |� configuration
|
126 | | |
|
127 | | |� routes.json
|
128 | | |
|
129 | | |� config.json
|
130 | |
|
131 | |� apis
|
132 | | |
|
133 | | |� getInfo
|
134 | |
|
135 |
|
136 |
|
137 | Within each handler sub-folder, you now create a module file that defines the code that will be invoked when the API route is requested. This module file can be named either *handler.js* or *index.js". For example:
|
138 |
|
139 | ~/dockerExample
|
140 | |
|
141 | |� configuration
|
142 | | |
|
143 | | |� routes.json
|
144 | | |
|
145 | | |� config.json
|
146 | |
|
147 | |� apis
|
148 | | |
|
149 | | |� getInfo
|
150 | | |
|
151 | | |� handler.js
|
152 |
|
153 |
|
154 |
|
155 | Here's an example *handler.js* file for the *getInfo* API:
|
156 |
|
157 | module.exports = function(args, finished) {
|
158 | finished({
|
159 | info: {
|
160 | server: 'Qewd-Up Container',
|
161 | arch: process.arch,
|
162 | platform: process.platform,
|
163 | versions: process.versions,
|
164 | memory: process.memoryUsage(),
|
165 | }
|
166 | });
|
167 | };
|
168 |
|
169 | If you�ve written QEWD REST handlers, you�ll recognise this format.
|
170 |
|
171 | In summary, simply export a function with 2 arguments:
|
172 |
|
173 | - **args**: an object that contains the content of your REST request, including its path, headers, HTTP method, any querystring values and any body payload
|
174 |
|
175 | - **finished**: the QEWD function that you use to end your handler. This function releases the QEWD worker process that handled your module back to QEWD's available worker pool, and tells QEWD to return the object you provide as its argument as a JSON response to the REST client that sent the original request.
|
176 |
|
177 |
|
178 | ## Starting your QEWD Container
|
179 |
|
180 | Fire up the QEWD Docker Container, eg:
|
181 |
|
182 | docker run -it --name qewdup --rm -p 8080:8080 -v ~/dockerExample:/opt/qewd/mapped rtweed/qewd-server
|
183 |
|
184 | Note: you may need to add sudo to the start of this command, depending on how you configured your Docker environment.
|
185 |
|
186 | If you�re using a Raspberry Pi:
|
187 |
|
188 | docker run -it --name qewdup --rm -p 8080:8080 -v ~/dockerExample:/opt/qewd/mapped rtweed/qewd-server-rpi
|
189 |
|
190 | By default, QEWD listens within the Docker Container on port 8080. The command examples above are mapping this to port 8080 on your host machine. If you want to use a different host port, just change the -p directive, eg to listen on port 3000:
|
191 |
|
192 | docker run -it --name qewdup --rm -p 3000:8080 -v ~/dockerExample:/opt/qewd/mapped rtweed/qewd-server
|
193 |
|
194 |
|
195 | Your application's configuration and API files are mapped into the Docker Container using this parameter:
|
196 |
|
197 | -v ~/dockerExample:/opt/qewd/mapped
|
198 |
|
199 | **Always** map your QEWD-Up Application Folder to */opt/qewd/mapped*
|
200 |
|
201 |
|
202 | ## Running Your APIs
|
203 |
|
204 | Now you can try out your API(s). By default, if you specified port 8080 as your host listener port, point a browser at:
|
205 |
|
206 | http://192.168.1.100:8080/api/info
|
207 |
|
208 | (change the IP address as appropriate)
|
209 |
|
210 | and back should come the results from your API handler module, eg:
|
211 |
|
212 | {
|
213 | "info": {
|
214 | "server": "Qewd-Up Container",
|
215 | "arch": "x64",
|
216 | "platform": "linux",
|
217 | "versions": {
|
218 | "http_parser": "2.8.0",
|
219 | "node": "10.12.0",
|
220 | "v8": "6.8.275.32-node.35",
|
221 | "uv": "1.23.2",
|
222 | "zlib": "1.2.11",
|
223 | "ares": "1.14.0",
|
224 | "modules": "64",
|
225 | "nghttp2": "1.34.0",
|
226 | "napi": "3",
|
227 | "openssl": "1.1.0i",
|
228 | "icu": "62.1",
|
229 | "unicode": "11.0",
|
230 | "cldr": "33.1",
|
231 | "tz": "2018e"
|
232 | },
|
233 | "memory": {
|
234 | "rss": 52396032,
|
235 | "heapTotal": 11780096,
|
236 | "heapUsed": 7756656,
|
237 | "external": 28323
|
238 | }
|
239 | }
|
240 | }
|
241 |
|
242 | That�s it - your API is up and working!
|
243 |
|
244 | You can now add more routes and their associated handler methods. If you do, you must restart QEWD-Up.
|
245 |
|
246 | To stop your running QEWD instance, just type CTRL & C.
|
247 |
|
248 | To restart QEWD-Up, just re-use the *docker run* command (as shown above) again.
|
249 |
|
250 |
|
251 | ## Installing Additional Node.js Modules
|
252 |
|
253 | if you need to use additional Node.js modules (ie ones not automatically included with QEWD itself), you can do this by adding the file *install_modules.json*, eg:
|
254 |
|
255 | ~/dockerExample
|
256 | |
|
257 | |� install_modules.json
|
258 | |
|
259 | |� configuration
|
260 | | |
|
261 | | |� routes.json
|
262 | | |
|
263 | | |� config.json
|
264 | |
|
265 |
|
266 | This file should contain a JSON array, listing the names of the modules you want loaded from NPM. For example:
|
267 |
|
268 | [
|
269 | "a-find",
|
270 | "body-parser-xml",
|
271 | "multer"
|
272 | ]
|
273 |
|
274 | The modules in this file are loaded from NPM when the *qewd-server* Docker Container is next started. Modules are saved into a *node_modules* folder in your mapped host file volume. As a result, the next time(s) you start the Docker Container, your additional modules are already available and don't need to be reloaded.
|
275 |
|
276 | So, after starting the Container, you'll see the *node_modules* folder that will have been created, eg:
|
277 |
|
278 | ~/dockerExample
|
279 | |
|
280 | |� install_modules.json
|
281 | |
|
282 | |� node_modules
|
283 | | |
|
284 | | a_find
|
285 | | ...etc
|
286 | |
|
287 | |� configuration
|
288 | | |
|
289 | | |� routes.json
|
290 | | |
|
291 | | |� config.json
|
292 |
|
293 |
|
294 | If you later modify your *install_modules.json* file and add new modules, they will be loaded from NPM into the *node_modules* folder when the Container is next restarted.
|
295 |
|
296 | # Dockerised QEWD MicroServices
|
297 |
|
298 | This mode allows you to split out your APIs and run them as separate, discrete MicroServices. For example, you might want to separate out your APIs by overall business function, so, for example, all the APIs that are related to user authentication could be in one single MicroService.
|
299 |
|
300 | Your MicroServices can run on separate physical machines, or they can all run on one host machine, or any combination between - it�s a very scalable and powerful approach.
|
301 |
|
302 | ## Pre-requisites
|
303 |
|
304 | It's very quick and simple to get started in this mode, because all you need to install on each machine that will host a MicroService is Docker. Everything else is handled by each instance of the QEWD Docker Container (which is available from the Docker Hub as rtweed/docker-server (or, if you use a Raspberry Pi: rtweed/docker-server-rpi )
|
305 |
|
306 | There are lots of documents on the Internet that describe how to install Docker. If you use Ubuntu 18.04, I�ve found [this to be an excellent set of instructions](https://www.digitalocean.com/community/tutorials/how-to-install-and-use-docker-on-ubuntu-18-04).
|
307 |
|
308 | However, if you�re using a different flavour of Linux, a simple Google search will quickly find what you need.
|
309 |
|
310 | if you want to try out QEWD-Up on a Raspberry Pi, you can install Docker by simply typing:
|
311 |
|
312 | curl -sSL https://get.docker.com | sh
|
313 |
|
314 | Finally, create a bridged Docker network that will be used exclusively by your QEWD Orchestrator and MicroService instances:
|
315 |
|
316 | sudo docker network create qewd-net
|
317 |
|
318 |
|
319 | ## Key Concepts
|
320 |
|
321 | In a QEWD-Up MicroService architecture, all external-facing communication is via a MicroService that is known as the Orchestrator (*aka* Orchestrator MicroService). REST clients will send their requests to the Orchestrator MicroService.
|
322 |
|
323 | All the other MicroServices are named according to how you want to name them, and each request sent by a REST client to the Orchestrator service is forwarded by it to the MicroService that has been specified to handle the API.
|
324 |
|
325 | Usually, the Orchestrator does not handle any APIs directly itself, but it can if you wish.
|
326 |
|
327 | In our example we�ll have 3 MicroServices:
|
328 |
|
329 | - **orchestrator**
|
330 | - **login_service** - this will handle the login API
|
331 | - **info_service** - once logged in, you�ll be able to send a query to this MicroService
|
332 |
|
333 |
|
334 | ## JSON Web Tokens
|
335 |
|
336 | QEWD automatically uses JSON Web Tokens (JWTs) to maintain security between the MicroServices, and returns its JWTs to the REST client as part of the response. Your REST client can make use of the JWT for its own purposes, but the JWT **must** be returned as a Bearer Token with all REST requests, except for APIs that you specifically tell QEWD-Up to ignore the JWT.
|
337 |
|
338 | The QEWD-generated JWT is added to your requests using the HTTP Authorization Header:
|
339 |
|
340 | Authorization: Bearer {{JWT}}
|
341 |
|
342 | For example:
|
343 |
|
344 | Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE1NDI2MzMwO��etc
|
345 |
|
346 | See later how this works.
|
347 |
|
348 | ## Defining Your APIs
|
349 |
|
350 | Create a folder for your application / suite of APIs. I'm going to name my example *microserviceExample* (eg using the file path *~/microserviceExample*). Also create sub-folders beneath it as shown below (use the sub-folder names exactly as shown):
|
351 |
|
352 |
|
353 | ~/microserviceExample
|
354 | |
|
355 | |� configuration
|
356 | |
|
357 | |� login_service
|
358 | |
|
359 | |� info_service
|
360 | |
|
361 |
|
362 | You **always** have a *configuration* folder. The other folder names are up to you - create one for each of your MicroServices.
|
363 |
|
364 |
|
365 | Create two text files in the configuration folder:
|
366 |
|
367 |
|
368 | ~/microserviceExample
|
369 | |
|
370 | |� configuration
|
371 | | |
|
372 | | |� routes.json
|
373 | | |
|
374 | | |� config.json
|
375 | |
|
376 | |� login_service
|
377 | |
|
378 | |� info_service
|
379 | |
|
380 |
|
381 |
|
382 | ### The *config.json* File
|
383 |
|
384 | You **MUST** create a file named *config.json*. This file tells QEWD to use the QEWD-Up mechanism and also defines the names of your MicroServices, for example we�ll use:
|
385 |
|
386 | {
|
387 | "qewd_up": true,
|
388 | "microservices": [
|
389 | {
|
390 | "name": "login_service",
|
391 | "qewd": {
|
392 | "serverName": "Login MicroService"
|
393 | }
|
394 | },
|
395 | {
|
396 | "name": "info_service",
|
397 | "qewd": {
|
398 | "serverName": "Info MicroService"
|
399 | }
|
400 | }
|
401 | ]
|
402 | }
|
403 |
|
404 | You can see that the MicroServices are defined in the *microservices* array which contains one or more MicroService objects, each defining the MicroService name (which must match the MicroService folder names that we previously created)
|
405 |
|
406 | Provided we use the *qewd-net* bridged Docker network that we created earlier, and provided we use these names when we start up each Docker instance, the *Orchestrator* can use Docker's automatic service discovery to find and connect to the MicroServices.
|
407 |
|
408 | Optionally, if you want to use the *qewd-monitor* application with the MicroService (it is enabled by default), then you can specify the name that will appear in the *qewd-monitor* main page. In the example above you can see we�re specifying this using the **qewd.serverName** property for each MicroService.
|
409 |
|
410 | We�re going to run the Orchestrator and the two MicroServices on the same physical host machine, but they�ll each listen on a different port.
|
411 |
|
412 | QEWD-Up will automatically generate a unique Uid-formatted Secret string that will be used to sign and authenticate the QEWD-generated JWTs. You'll see this appearing in your *config.json* file after you run up the Orchestrator MicroService for the first time, eg:
|
413 |
|
414 | "jwt": {
|
415 | "secret": "b3de07f4-d2be-4337-b77c-4caf977f5199"
|
416 | }
|
417 |
|
418 | This ensures that all the MicroServices share this same secret.
|
419 |
|
420 | Optionally, the *config.json* file also allows you to specify how you want to configure QEWD itself on each MicroService. QEWD-Up will apply sensible default settings that will be good enough for now. See later in this document for more details on the *config.json* file and how to configure QEWD differently from the QEWD-Up defaults.
|
421 |
|
422 |
|
423 | ### The *routes.json* File
|
424 |
|
425 | You **MUST** create a file named *routes.json*. This will contain the definition of your REST routes. As its file extension implies, its contents must be correctly-structured JSON, describing the list of REST routes that your system will support. Each route is described in terms of:
|
426 |
|
427 | - **uri**: the URI path
|
428 | - **method**: the HTTP method
|
429 | - **handler**: the name of the handler module that will perform the work of this API
|
430 | - **on_microservice**: the name of the MicroService in which the handler module is defined and on which it will run
|
431 | - **authenticate**: optionally, set this property to *false* in order to allow the API to be invoked without a JWT being present in the incoming request.
|
432 |
|
433 | The routes are described as an array of route objects, eg let�s use this example
|
434 |
|
435 | [
|
436 | {
|
437 | "uri": "/api/info/info",
|
438 | "method": "GET",
|
439 | "handler": "getInfo",
|
440 | "on_microservice": "info_service"
|
441 | },
|
442 | {
|
443 | "uri": "/api/login",
|
444 | "method": "POST",
|
445 | "handler": "login",
|
446 | "on_microservice": "login_service",
|
447 | "authenticate": false
|
448 | }
|
449 | ]
|
450 |
|
451 | You can describe as many routes as you wish in this file. The syntax MUST be strict JSON - ie all property names and text-string values must be double-quoted.
|
452 |
|
453 | In the example above:
|
454 |
|
455 | - the first route defines an API whose handler method will be invoked on the Orchestrator MicroService.
|
456 |
|
457 | - the second and third routes include the **on_microservice** property which tells QEWD-Up the MicroService on which to invoke the specified **handler** module.
|
458 |
|
459 | - the third route includes the property **authenticate: false** which tells QEWD-Up that it will be invoked without first checking for a valid JWT
|
460 |
|
461 |
|
462 | ### Defining Your MicroService API Handlers
|
463 |
|
464 | #### Location
|
465 |
|
466 | You define your API Handlers within the MicroService folder in which it will execute. You have two options:
|
467 |
|
468 | - if your handler modules are simple and you don't want or need to break them into smaller sub-modules, you can define each one using sub-folder names that correspond to the handler names, placing each sub-folder under the MicroService folder.
|
469 |
|
470 | - in more complex situations, you may find it neater to add an intermediate sub-folder named *apis*, below which you define your handler modules.
|
471 |
|
472 | So, first create a sub-folder for each *handler* property name that you defined in the *routes.json* file (see above). Then, within each of these handler sub-folders, create the module file that defines what the handler actually does. You can name this file either *handler.js* or *index.js*.
|
473 |
|
474 | For example, using the *routes.json* example above, you'd create:
|
475 |
|
476 | ~/microserviceExample
|
477 | |
|
478 | |� configuration
|
479 | | |
|
480 | | |� routes.json
|
481 | | |
|
482 | | |� config.json
|
483 | |
|
484 | |� login_service
|
485 | | |
|
486 | | |� login
|
487 | | |
|
488 | | |� handler.js
|
489 | |
|
490 | |� info_service
|
491 | | |
|
492 | | |� getInfo
|
493 | | |
|
494 | | |� handler.js
|
495 | |
|
496 |
|
497 | Alternatively, you could use an intermediate *apis* sub-folder:
|
498 |
|
499 | ~/microserviceExample
|
500 | |
|
501 | |� configuration
|
502 | | |
|
503 | | |� routes.json
|
504 | | |
|
505 | | |� config.json
|
506 | |
|
507 | |� login_service
|
508 | | |
|
509 | | |� apis
|
510 | | |
|
511 | | |� login
|
512 | | |
|
513 | | |� index.js
|
514 | |
|
515 | |� info_service
|
516 | | |
|
517 | | |� apis
|
518 | | |
|
519 | | |� getInfo
|
520 | | |
|
521 | | |� index.js
|
522 | |
|
523 |
|
524 | This latter structure allows you to add other MicroService folders in which to place your own modules on which your handler modules depend, eg:
|
525 |
|
526 | ~/microserviceExample
|
527 | |
|
528 | |
|
529 | |� login_service
|
530 | | |
|
531 | | |� apis
|
532 | | | |
|
533 | | | |� login
|
534 | | | |
|
535 | | | |� index.js
|
536 | | |� utils
|
537 | | | |
|
538 | | | |� encrypt.js
|
539 | | | |
|
540 | | | |� decrypt.js
|
541 |
|
542 |
|
543 | So in the above example, your login handler and other API handler methods may need to use encryption and decryption logic. We can put the modules for these in a *utils* folder (apart from *apis*, any other folder names are up to you), keeping them neatly separated from the main handler modules.
|
544 |
|
545 | #### API Handler Module Arguments
|
546 |
|
547 | QEWD REST handler modules export a function with 2 arguments:
|
548 |
|
549 | - **args**: an object that contains the content of your REST request, including its path, headers, HTTP method, any querystring values and any body payload. It also contains the QEWD MicroService JWT.
|
550 |
|
551 | - **finished**: the QEWD function that you use to end your handler. This function releases the QEWD worker process that handled your module back to QEWD's available worker pool, and tells QEWD to return the object you provide as its argument as a JSON response to the REST client that sent the original request.
|
552 |
|
553 |
|
554 | #### Examples
|
555 |
|
556 | Here's the example handler modules:
|
557 |
|
558 | ##### login/handler.js
|
559 |
|
560 | module.exports = function(args, finished) {
|
561 | var username = args.req.body.username;
|
562 | var password = args.req.body.password;
|
563 | var jwt = args.session;
|
564 | if (username === 'rob' && password === 'secret') {
|
565 | jwt.userText = 'Welcome Rob';
|
566 | jwt.username = username;
|
567 | jwt.authenticated = true;
|
568 | jwt.timeout = 1200;
|
569 | finished({ok: true});
|
570 | }
|
571 | else {
|
572 | finished({error: 'Invalid login'});
|
573 | }
|
574 | };
|
575 |
|
576 |
|
577 | In this example I'm simply hard-coding the authentication logic, allowing only a username of *rob* and a password of *secret*. Any other username/password will return an error JSON object.
|
578 |
|
579 | If login is successful, then I'm adding information to the JWT. The *authenticated* and *timeout* properties (or *claim* in JWT parlance) are special, QEWD-reserved ones that are used by QEWD's JWT authentication:
|
580 |
|
581 | - **authenticated**: determines that the user has been correctly authenticated each time the JWT is tested within the QEWD MicroServices
|
582 | - **timeout**: sets the JWT timeout which, in turn, updates the JWT *exp* claim each time a handler module is invoked
|
583 |
|
584 |
|
585 | ##### getInfo/handler.js
|
586 |
|
587 | module.exports = function(args, finished) {
|
588 | finished({
|
589 | info: {
|
590 | microService: 'info_service',
|
591 | arch: process.arch,
|
592 | platform: process.platform,
|
593 | versions: process.versions,
|
594 | memory: process.memoryUsage(),
|
595 | }
|
596 | });
|
597 | };
|
598 |
|
599 | In this simple example, I'm simply returning some information about the info_service MicroService run-time environment.
|
600 |
|
601 | ## Starting the MicroServices
|
602 |
|
603 | First fire up the Orchestrator QEWD Docker Container, eg:
|
604 |
|
605 | docker run -it --name orchestrator --rm --net qewd-net -p 8080:8080 -v ~/microserviceExample:/opt/qewd/mapped rtweed/qewd-server
|
606 |
|
607 | Note: you may need to add sudo to the start of this command, depending on how you configured your Docker environment.
|
608 |
|
609 | If you�re using a Raspberry Pi, simply substitute the Docker container name *rtweed/qewd-server-rpi*, eg:
|
610 |
|
611 | docker run -it --name orchestrator --rm --net qewd-net -p 8080:8080 -v ~/microserviceExample:/opt/qewd/mapped rtweed/qewd-server-rpi
|
612 |
|
613 |
|
614 | By default, QEWD listens within the Docker Container on port 8080. The command examples above are mapping this to port 8080 on your host machine. If you want to use a different host port, just change the -p directive, eg to listen on port 3000:
|
615 |
|
616 | docker run -it --name orchestrator --rm --net qewd-net -p 3000:8080 -v ~/microserviceExample:/opt/qewd/mapped rtweed/qewd-server
|
617 |
|
618 | Your application's configuration and API files are mapped into the Docker Container using this parameter:
|
619 |
|
620 | -v ~/microServiceExample:/opt/qewd/mapped
|
621 |
|
622 | **Always** map your QEWD-Up Application Folder to */opt/qewd/mapped*
|
623 |
|
624 |
|
625 | Secondly, in a separate terminal window, start up the *login_service* MicroService:
|
626 |
|
627 | docker run -it --name login_service --rm --net qewd-net -v ~/microserviceExample:/opt/qewd/mapped -e microservice="login_service" rtweed/qewd-server
|
628 |
|
629 | Note how we�ve specified an environment variable to tell QEWD-Up which MicroService this is:
|
630 |
|
631 | -e microservice="login_service"
|
632 |
|
633 |
|
634 | And finally, in a third terminal window, start up the *info_service* MicroService:
|
635 |
|
636 | docker run -it --name info_service --rm --net qewd-net -v ~/microserviceExample:/opt/qewd/mapped -e microservice=�info_service" rtweed/qewd-server
|
637 |
|
638 | **IMPORTANT**: Make sure the MicroService name in the *--name** and *-e* parameters match exactly with each other and also with the MicroService name used in the *routes.json* file.
|
639 |
|
640 | [See here](https://github.com/robtweed/qewd/blob/master/up/docs/MicroServices.md) for more details on setting up QEWD-Up MicroServices.
|
641 |
|
642 | ## Running Your APIs
|
643 |
|
644 | With everything running, you can now try out the APIs.
|
645 |
|
646 | First we must use the Login API. Using a REST client:
|
647 |
|
648 | POST http://192.168.1.100:8080/api/login
|
649 | Content-type: application/json
|
650 |
|
651 | with a body payload containing
|
652 |
|
653 | {
|
654 | "username": "rob",
|
655 | "password": "secret"
|
656 | }
|
657 |
|
658 | **Note**: Change the IP address to match that of the host machine that is running the Orchestrator Service.
|
659 |
|
660 | You should see activity in the console logs for both the Orchestrator and Login QEWD instances, and back should come a response looking something like this:
|
661 |
|
662 | {
|
663 | "ok": true,
|
664 | "token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE1NDI2Mz��etc�
|
665 | }
|
666 |
|
667 | You're now ready to try the other API, but in order for it to work, you need to include the JWT that was returned to us as the token property in the */api/login* response. As described earlier, you need to add it as a *Bearer Token* in the HTTP *Authorization* request header: *ie* the *Authorization* header value must be the word *Bearer * followed by the JWT value.
|
668 |
|
669 | So try this:
|
670 |
|
671 |
|
672 | GET http://192.168.1.100:8080/api/info/info
|
673 | Content-type: application/json
|
674 | Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE1NDI2Mz��etc
|
675 |
|
676 |
|
677 | and you should get the response:
|
678 |
|
679 | {
|
680 | "info": {
|
681 | "microService": "info_service",
|
682 | "arch": "x64",
|
683 | "platform": "linux",
|
684 | "versions": {
|
685 | "http_parser": "2.8.0",
|
686 | "node": "10.12.0",
|
687 | "v8": "6.8.275.32-node.35",
|
688 | "uv": "1.23.2",
|
689 | "zlib": "1.2.11",
|
690 | "ares": "1.14.0",
|
691 | "modules": "64",
|
692 | "nghttp2": "1.34.0",
|
693 | "napi": "3",
|
694 | "openssl": "1.1.0i",
|
695 | "icu": "62.1",
|
696 | "unicode": "11.0",
|
697 | "cldr": "33.1",
|
698 | "tz": "2018e"
|
699 | },
|
700 | "memory": {
|
701 | "rss": 52363264,
|
702 | "heapTotal": 19120128,
|
703 | "heapUsed": 9825976,
|
704 | "external": 177816
|
705 | }
|
706 | },
|
707 | "token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE1NDI3MjA4MzcsI....etc�
|
708 | }
|
709 |
|
710 | Notice how the response includes a new, updated JWT, returned as a property named *token*.
|
711 |
|
712 |
|
713 | ## Inspecting the JWT
|
714 |
|
715 | One of the features of JWTs is that you can decode them and see what they contain. Try copying the JWT value returned in the *token* property of the */api/login* response. Then paste it into the JWT Debugging window at *https://jwt.io*. Look in the *Decoded* panel at the *PAYLOAD* section and you'll see something like:
|
716 |
|
717 | {
|
718 | "exp": 1544607641,
|
719 | "iat": 1544606441,
|
720 | "iss": "qewd.jwt",
|
721 | "application": "login_service",
|
722 | "timeout": 1200,
|
723 | "qewd": "9585569d6b0acef7b320ca5f200a47ec99b55a29f7808f3d84a937d138497a123d67174a7e56fca9b1626ed040c27f2feef094b25e522ec47cfb3152784fe292edc5d410bfe5f661bb7b2b31779c86e1e747b98741f7175b8671a1953fae0542",
|
724 | "userText": "Welcome Rob",
|
725 | "username": "rob"
|
726 | }
|
727 |
|
728 | Some of these JWT claims (or properties) are created and maintains internally within QEWD, but you should recognise three that we created in our *login/handler.js* module's logic:
|
729 |
|
730 | jwt.userText = 'Welcome Rob';
|
731 | jwt.username = username;
|
732 | jwt.timeout = 1200;
|
733 |
|
734 | **Note**: although you can read the contents of a JWT, you can't modify its contents without knowing the secret that is used to digitally sign the JWT.
|
735 |
|
736 |
|
737 | ## How JWTs Protect Your APIs
|
738 |
|
739 | Without you needing to write anything other than the login credential checking, QEWD protects access to your APIs automatically via the JWT it creates and maintains for you across the MicroServices. The following examples will demonstrate how this works:
|
740 |
|
741 | Try sending the */api/login* request with invalid username/password credentials. You should get back an error response with an HTTP Status Code of 400 and a response payload of:
|
742 |
|
743 | {
|
744 | "error": "Invalid login"
|
745 | }
|
746 |
|
747 | Notice that no JWT is returned with error responses, and without a JWT, you can't access the other APIs.
|
748 |
|
749 | For example, try sending the */api/info/info* request without an *Authorization* header. You should get back an error response with an HTTP Status Code of 400 and a response payload of:
|
750 |
|
751 | {
|
752 | "error": "Authorization Header missing or JWT not found in header (expected format: Bearer {{JWT}}"
|
753 | }
|
754 |
|
755 | Because JWTs are digitally signed using a secret that your REST client doesn't have access to, you can't tamper with the JWT or make one up yourself. For example, try sending the */api/info/info* request with a valid *Authorization* header, but make a change to one or two of the characters within the JWT. You should get back an error response with an HTTP Status Code of 400 and a response payload of:
|
756 |
|
757 | {
|
758 | "error": "Invalid JWT: Error: Signature verification failed"
|
759 | }
|
760 |
|
761 | Finally, see what happens if you try to use a valid but expired JWT in the Authorization header (ie wait for 20 minutes or more after */api/login*). You should get back an error response with an HTTP Status Code of 400 and a response payload of:
|
762 |
|
763 | {
|
764 | "error": "Invalid JWT: Error: Token expired"
|
765 | }
|
766 |
|
767 |
|
768 | ## Installing Additional Node.js Modules
|
769 |
|
770 | if you need to use additional Node.js modules (ie ones not automatically included with QEWD itself) in any of your application's MicroServices, you can do this by adding the file *install_modules.json*, eg:
|
771 |
|
772 | ~/microserviceExample
|
773 | |
|
774 | |� install_modules.json
|
775 | |
|
776 | |� configuration
|
777 | | |
|
778 | | |� routes.json
|
779 | | |
|
780 | | |� config.json
|
781 | |
|
782 |
|
783 | This file should contain a JSON array, listing the names of the modules you want loaded from NPM. For example:
|
784 |
|
785 | [
|
786 | "a-find",
|
787 | "body-parser-xml",
|
788 | "multer"
|
789 | ]
|
790 |
|
791 | The modules in this file are loaded from NPM when one of your MicroService Containers is next started. Modules are saved into a *node_modules* folder in your mapped host file volume. As a result, the next time(s) you start/restart any of your MicroService Containers, your additional modules are already available for use and don't need to be reloaded.
|
792 |
|
793 | If you later modify your *install_modules.json* file and add new modules, they will be loaded from NPM when a MicroService Container is next restarted.
|
794 |
|
795 | So, after starting one of your MicroServices, you'll see the *node_modules* folder that will have been created, eg:
|
796 |
|
797 | ~/microserviceExample
|
798 | |
|
799 | |� install_modules.json
|
800 | |
|
801 | |� node_modules
|
802 | | |
|
803 | | a_find
|
804 | | ...etc
|
805 | |
|
806 | |� configuration
|
807 | | |
|
808 | | |� routes.json
|
809 | | |
|
810 | | |� config.json
|
811 |
|
812 |
|
813 | # Native QEWD Monolith
|
814 |
|
815 | ## Pre-requisites
|
816 |
|
817 | To use this mode, you must first install Node.js (version 8 or later is recommended) and the [YottaDB](https://yottadb.com/) database. QEWD-Up's Native mode is designed to run on Linux or Unix platforms (including Virtual Machines).
|
818 |
|
819 | Don't worry if you've never installed Node.js or YottaDB before: you can simply run this automated script on a new Linux machine or VM:
|
820 |
|
821 | cd ~
|
822 | wget https://raw.githubusercontent.com/robtweed/qewd/master/installers/install_ydb_and_node.sh
|
823 | source install_ydb_and_node.sh
|
824 |
|
825 | This script file will first install something called NVM and then use that to install the latest version of Node.js 10.x. It also installs the latest version of YottaDB and configures it ready for use with QEWD.
|
826 |
|
827 |
|
828 | ## Setting Up QEWD-Up
|
829 |
|
830 | Create a folder for your application/suite of APIs. I'm going to name my example *nativeExample* (eg using the file path *~/nativeExample*). Also create sub-folders beneath it as shown below (use the sub-folder names exactly as shown):
|
831 |
|
832 |
|
833 | ~/nativeExample
|
834 | |
|
835 | |� configuration
|
836 | |
|
837 | |� apis
|
838 | |
|
839 |
|
840 | In the configuration folder, create a text file named *routes.json*:
|
841 |
|
842 |
|
843 | ~/nativeExample
|
844 | |
|
845 | |� configuration
|
846 | | |
|
847 | | |� routes.json
|
848 | |
|
849 | |� apis
|
850 | |
|
851 |
|
852 |
|
853 | ### The *routes.json* File
|
854 |
|
855 | This file contains the definitions of your REST routes, expressed as a JSON array of route objects. Each route is described in terms of:
|
856 |
|
857 | - the URI path
|
858 | - the HTTP method
|
859 | - the name of the handler module that will perform the work of this API
|
860 |
|
861 | *eg* here�s the definition of a single API:
|
862 |
|
863 | [
|
864 | {
|
865 | "uri": "/api/info",
|
866 | "method": "GET",
|
867 | "handler": "getInfo"
|
868 | }
|
869 | ]
|
870 |
|
871 | You can describe as many routes as you wish in this file. The syntax MUST be strict JSON - ie all property names and text-string values must be double-quoted.
|
872 |
|
873 |
|
874 | ### The *config.json* File
|
875 |
|
876 | By Default, QEWD-Up applies a set of sensible default settings to the QEWD environment that will run your APIs. If the defaults are satisfactory, then you don't need to create a *config.json* file. If you want different settings for QEWD (eg a larger Worker pool size), then here's what to do:
|
877 |
|
878 | Create a text file named *config.json* within your configuration sub-folder, ie:
|
879 |
|
880 | ~/nativeExample
|
881 | |
|
882 | |� configuration
|
883 | | |
|
884 | | |� routes.json
|
885 | | |
|
886 | | |� config.json
|
887 | |
|
888 | |� apis
|
889 | |
|
890 |
|
891 |
|
892 | The *config.json* file provides QEWD-Up with instructions on how to configure QEWD. As its file extension implies, its contents must be correctly-structured JSON. Here's a simple example that increases the QEWD Worker pool size from the default of 2 to 4, and tells QEWD's integrated Express Web Server to listen on port 3000:
|
893 |
|
894 | {
|
895 | qewd: {
|
896 | poolSize: 4,
|
897 | port: 3000
|
898 | }
|
899 | }
|
900 |
|
901 |
|
902 | ## Defining Your API Handlers
|
903 |
|
904 | You define your API Handlers within the *apis* folder. First create a sub-folder for each *handler* property name that you defined in the *routes.json* file (see above). For example, using the *routes.json* example above, you'd create:
|
905 |
|
906 | ~/nativeExample
|
907 | |
|
908 | |� configuration
|
909 | | |
|
910 | | |� routes.json
|
911 | |� apis
|
912 | | |
|
913 | | |� getInfo
|
914 | |
|
915 |
|
916 |
|
917 | Within each handler sub-folder, you now create a module file that defines the code that will be invoked when the API route is requested. This module file can be named either *handler.js* or *index.js". For example:
|
918 |
|
919 | ~/nativeExample
|
920 | |
|
921 | |� configuration
|
922 | | |
|
923 | | |� routes.json
|
924 | |� apis
|
925 | | |
|
926 | | |� getInfo
|
927 | | |
|
928 | | |� handler.js
|
929 |
|
930 |
|
931 |
|
932 | Here's an example *handler.js* file for the *getInfo* API:
|
933 |
|
934 | module.exports = function(args, finished) {
|
935 | finished({
|
936 | info: {
|
937 | server: 'Qewd-Up Native',
|
938 | arch: process.arch,
|
939 | platform: process.platform,
|
940 | versions: process.versions,
|
941 | memory: process.memoryUsage(),
|
942 | }
|
943 | });
|
944 | };
|
945 |
|
946 | If you�ve written QEWD REST handlers, you�ll recognise this format.
|
947 |
|
948 | In summary, simply export a function with 2 arguments:
|
949 |
|
950 | - **args**: an object that contains the content of your REST request, including its path, headers, HTTP method, any querystring values and any body payload
|
951 |
|
952 | - **finished**: the QEWD function that you use to end your handler. This function releases the QEWD worker process that handled your module back to QEWD's available worker pool, and tells QEWD to return the object you provide as its argument as a JSON response to the REST client that sent the original request.
|
953 |
|
954 |
|
955 | ## Create a *package.json* File
|
956 |
|
957 | Create a file named package.json in your top-level application directory, eg in our case:
|
958 |
|
959 | ~/nativeExample
|
960 | |
|
961 | |� package.json
|
962 | |
|
963 | |� configuration
|
964 | | |
|
965 | | |� routes.json
|
966 | |� apis
|
967 | | |
|
968 | | |� getInfo
|
969 | | |
|
970 | | |� handler.js
|
971 |
|
972 | The easiest thing is to copy the example below and use it as a template for your package.json:
|
973 |
|
974 | {
|
975 | "name": "qewd-up",
|
976 | "version": "1.0.0",
|
977 | "description": "Automated QEWD Builder",
|
978 | "author": "Rob Tweed <rtweed@mgateway.com>",
|
979 | "scripts": {
|
980 | "start": "node node_modules/qewd/up/run_native"
|
981 | },
|
982 | "dependencies": {
|
983 | "qewd": "",
|
984 | "qewd-transform-json": "",
|
985 | "git-clone": "^0.1.0"
|
986 | }
|
987 | }
|
988 |
|
989 | If your handler modules have additional *dependencies*, add them to the package.json, otherwise just use the template shown above and leave it unchanged.
|
990 |
|
991 |
|
992 | ## Starting QEWD-Up
|
993 |
|
994 | You're now ready to start up QEWD-Up.
|
995 |
|
996 | First you need to use NPM to install everything that�s needed by QEWD-Up and QEWD itself. When you installed Node.js, NPM will have also been installed automatically.
|
997 |
|
998 | This is a one-off step that you don�t need to repeat (unless you add more dependencies to your handlers later):
|
999 |
|
1000 | cd ~/nativeExample
|
1001 | npm install
|
1002 |
|
1003 | Once that�s finished, you�re ready to go! Just type:
|
1004 |
|
1005 | npm start
|
1006 |
|
1007 |
|
1008 | QEWD is now ready and you can try out your APIs.
|
1009 |
|
1010 |
|
1011 | ## Running Your APIs
|
1012 |
|
1013 | Now you can try out your API(s). If you used the default QEWD-Up configuration, QEWD will be listening on port 8080. So, point a browser at:
|
1014 |
|
1015 | http://192.168.1.100:8080/api/info
|
1016 |
|
1017 | (change the IP address as appropriate)
|
1018 |
|
1019 | and back should come the results from your API handler module, eg:
|
1020 |
|
1021 | {
|
1022 | "info": {
|
1023 | "server": "Qewd-Up Native",
|
1024 | "arch": "x64",
|
1025 | "platform": "linux",
|
1026 | "versions": {
|
1027 | "http_parser": "2.8.0",
|
1028 | "node": "10.12.0",
|
1029 | "v8": "6.8.275.32-node.35",
|
1030 | "uv": "1.23.2",
|
1031 | "zlib": "1.2.11",
|
1032 | "ares": "1.14.0",
|
1033 | "modules": "64",
|
1034 | "nghttp2": "1.34.0",
|
1035 | "napi": "3",
|
1036 | "openssl": "1.1.0i",
|
1037 | "icu": "62.1",
|
1038 | "unicode": "11.0",
|
1039 | "cldr": "33.1",
|
1040 | "tz": "2018e"
|
1041 | },
|
1042 | "memory": {
|
1043 | "rss": 52396032,
|
1044 | "heapTotal": 11780096,
|
1045 | "heapUsed": 7756656,
|
1046 | "external": 28323
|
1047 | }
|
1048 | }
|
1049 | }
|
1050 |
|
1051 | That�s it - your API is up and working!
|
1052 |
|
1053 | You can now add more routes and their associated handler methods. If you do, you must restart QEWD-Up.
|
1054 |
|
1055 | To stop your running QEWD instance, just type CTRL & C.
|
1056 |
|
1057 | To restart QEWD-Up, just type *npm start* again.
|
1058 |
|
1059 |
|
1060 | ## Installing Additional Node.js Modules
|
1061 |
|
1062 | if you need to use additional Node.js modules (ie ones not automatically included with QEWD itself), you can do this by adding them to the *package.json* **dependencies**. For example:
|
1063 |
|
1064 | {
|
1065 | "name": "qewd-up",
|
1066 | "version": "1.0.0",
|
1067 | "description": "Automated QEWD Builder",
|
1068 | "author": "Rob Tweed <rtweed@mgateway.com>",
|
1069 | "scripts": {
|
1070 | "start": "node node_modules/qewd/up/run_native"
|
1071 | },
|
1072 | "dependencies": {
|
1073 | "qewd": "",
|
1074 | "qewd-transform-json": "",
|
1075 | "git-clone": "^0.1.0",
|
1076 | "a-find": "",
|
1077 | "multer": ""
|
1078 | }
|
1079 | }
|
1080 |
|
1081 |
|
1082 |
|
1083 |
|