More Related Content

Similar to EWD 3 Training Course Part 31: Using QEWD for Web and REST Services(20)

More from Rob Tweed(20)

EWD 3 Training Course Part 31: Using QEWD for Web and REST Services

  1. Copyright © 2016 M/Gateway Developments Ltd EWD 3 Training Course Part 31 Using QEWD for Web and REST Services Rob Tweed Director, M/Gateway Developments Ltd Twitter: @rtweed
  2. Copyright © 2016 M/Gateway Developments Ltd Using QEWD for Web & REST Services • This part of the course assumes that you've taken, at least: • Part 2: Overview of EWD 3 – https://www.slideshare.net/robtweed/ewd-3-overview • Part 4: Installing & Configuring QEWD – https://www.slideshare.net/robtweed/installing-configuring-ewdxpress
  3. Copyright © 2016 M/Gateway Developments Ltd QEWD for Web & REST Services • Provides a ready-made platform for developing high-performance, scalable, secure Web and REST Service APIs
  4. Copyright © 2016 M/Gateway Developments Ltd QEWD for Web & REST Services • Just as for interactive browser-based applications, the master QEWD process handles concurrency and HTTP(S) access • Your APIs run in the isolated environment of a QEWD worker process – A QEWD worker process invokes a single instance of an API function • So it can safely use synchronous APIs
  5. Copyright © 2016 M/Gateway Developments Ltd QEWD for Web & REST Services • An instance of QEWD can simultaneously support: – Multiple interactive browser-based applications; and/or – Multiple REST / Web Service APIs
  6. Copyright © 2016 M/Gateway Developments Ltd Typical QEWD Startup file for browser apps var config = { managementPassword: 'keepThisSecret!', serverName: 'My QEWD Server', port: 8080, poolSize: 2, database: { type: 'cache', params: { path: 'c:InterSystemsCache2015-2mgr’ } }}; var qewd = require('qewd').master; qewd.start(config); If using InterSystems Cache
  7. Copyright © 2016 M/Gateway Developments Ltd Typical QEWD Startup file for browser apps var config = { managementPassword: 'keepThisSecret!', serverName: 'My QEWD Server', port: 8080, poolSize: 2, database: { type: 'gtm' }; var qewd = require('qewd').master; qewd.start(config); If using GT.M
  8. Copyright © 2016 M/Gateway Developments Ltd Defining Routes • To support Web/REST APIs, you must add routes to the startup configuration: – Associate a URL prefix with a back-end QEWD module – QEWD uses this information and automatically looks after the Express middleware and routing needed for incoming matching URLs to invoke the associated back-end module in a worker process
  9. Copyright © 2016 M/Gateway Developments Ltd Defining Routes • The URL routes you want to handle are defined in your QEWD startup file: – An array of routing objects
  10. Copyright © 2016 M/Gateway Developments Ltd Defining Routes • The URL routes you want to handle are defined in your QEWD startup file: – An array of routing objects – Each routing object defines: • path: the URL path prefix • module: the associated back-end module name/path
  11. Copyright © 2016 M/Gateway Developments Ltd Add Routes to Startup File var config = { managementPassword: 'keepThisSecret!', serverName: 'My QEWD Server', port: 8080, poolSize: 2, database: { type: 'gtm' } }; var routes = [ { path: '/api', module: 'myRestService' } ]; var qewd = require('qewd').master; qewd.start(config, routes);
  12. Copyright © 2016 M/Gateway Developments Ltd Add Routes to Startup File var config = { managementPassword: 'keepThisSecret!', serverName: 'My QEWD Server', port: 8080, poolSize: 2, database: { type: 'gtm' } }; var routes = [ { path: '/api', module: 'myRestService' } ]; var qewd = require('qewd').master; qewd.start(config, routes); All incoming HTTP requests with a URL prefix of /api will be handled by a module named myRestService
  13. Copyright © 2016 M/Gateway Developments Ltd Add Routes to Startup File var config = { managementPassword: 'keepThisSecret!', serverName: 'My QEWD Server', port: 8080, poolSize: 2, database: { type: 'gtm' } }; var routes = [ { path: '/api', module: 'myRestService' } ]; var qewd = require('qewd').master; qewd.start(config, routes); Pass the routes array into the QEWD start() function as its 2nd argument
  14. Copyright © 2016 M/Gateway Developments Ltd Add Routes to Startup File var config = { managementPassword: 'keepThisSecret!', serverName: 'My QEWD Server', port: 8080, poolSize: 2, database: { type: 'gtm' } }; var routes = [ {path: '/api', module: 'myRestService'}, {path: '/testing', module: '/path/to/testService'} ]; var qewd = require('qewd').master; qewd.start(config, routes); You can define as many routes as you wish
  15. Copyright © 2016 M/Gateway Developments Ltd Add Routes to Startup File var config = { managementPassword: 'keepThisSecret!', serverName: 'My QEWD Server', port: 8080, poolSize: 2, database: { type: 'gtm' } }; var routes = [ {path: '/api', module: 'myRestService'}, {path: '/testing', module: '/path/to/testService'} ]; var qewd = require('qewd').master; qewd.start(config, routes); You can specify a module path if necessary
  16. Copyright © 2016 M/Gateway Developments Ltd Add Routes to Startup File var config = { managementPassword: 'keepThisSecret!', serverName: 'My QEWD Server', port: 8080, poolSize: 2, database: { type: 'gtm' } }; var routes = [ { path: '/api', module: 'myRestService' } ]; var qewd = require('qewd').master; qewd.start(config, routes); With this route definition, all incoming HTTP requests with a URL prefix of /api will be handled by a module named myRestService
  17. Copyright © 2016 M/Gateway Developments Ltd Add Routes to Startup File var config = { managementPassword: 'keepThisSecret!', serverName: 'My QEWD Server', port: 8080, poolSize: 2, database: { type: 'gtm' } }; var routes = [ { path: '/api', module: 'myRestService' } ]; var qewd = require('qewd').master; qewd.start(config, routes); Any incoming HTTP requests with a URL prefix that doesn't start /api will be rejected by QEWD
  18. Copyright © 2016 M/Gateway Developments Ltd How QEWD Routes Work • QEWD Master Process will detect any incoming HTTP(S) requests whose URL starts with a specified route
  19. Copyright © 2016 M/Gateway Developments Ltd How QEWD Routes Work • QEWD Master Process will reject any incoming HTTP(S) requests whose URL can't be matched in the routes array.
  20. Copyright © 2016 M/Gateway Developments Ltd How QEWD Routes Work • QEWD Master Process will detect any incoming HTTP(S) requests whose URL starts with a specified route • Matching requests are repackaged into a JSON message and placed on queue
  21. Copyright © 2016 M/Gateway Developments Ltd How QEWD Routes Work • QEWD Master Process will detect any incoming HTTP(S) requests whose URL starts with a specified route • Matching requests are repackaged into a JSON message and placed on queue • Queued message is dispatched to a worker process
  22. Copyright © 2016 M/Gateway Developments Ltd How QEWD Routes Work • QEWD Master Process will detect any incoming HTTP(S) requests whose URL starts with a specified route • Matching requests are repackaged into a JSON message and placed on queue • Queued message is dispatched to a worker process • Message is handled in the worker process by the module specified in the route definition
  23. Copyright © 2016 M/Gateway Developments Ltd How QEWD Routes Work • QEWD Master Process will detect any incoming HTTP(S) requests whose URL starts with a specified route – Note: matching of routes in the master process is by exact match of URL prefixes defined in startup file route definitions – URL templates are not used in the startup file route definitions
  24. Copyright © 2016 M/Gateway Developments Ltd How QEWD Routes Work • QEWD Master Process will detect any incoming HTTP(S) requests whose URL starts with a specified route – Startup file route definitions are simply a way to direct a set of incoming requests to a worker process where they are processed by a particular handler module
  25. Copyright © 2016 M/Gateway Developments Ltd How QEWD Routes Work • In your Worker Process handler module, you specify all the next-level routes you're interested in, and the function to handle each route • Worker Process routes can be templated, eg – /api/customer/:id
  26. Copyright © 2016 M/Gateway Developments Ltd How QEWD Routes Work • For example, if the QEWD startup file defines a route for: – /api • The worker process module specified for this route will handle or reject all lower-level URL paths, eg: – /api/search – /api/patient/allergy/detail – /api/user – /api/customer/123456
  27. Copyright © 2016 M/Gateway Developments Ltd Handler Module Structure module.exports = { restModule: true };
  28. Copyright © 2016 M/Gateway Developments Ltd Handler Module Structure module.exports = { restModule: true, }; You must tell QEWD that this Module is for handling Web/REST service APIs
  29. Copyright © 2016 M/Gateway Developments Ltd Handler Module Structure module.exports = { restModule: true, init: function() { // invoked just once when module is first loaded into worker process } };
  30. Copyright © 2016 M/Gateway Developments Ltd Handler Module Structure module.exports = { restModule: true, init: function() { // invoked just once when module is first loaded into worker process // this is where you define all the lower-level routes you want to handle // - path template // - HTTP method (eg POST, GET, etc) // - handler function to process all requests matching this path } };
  31. Copyright © 2016 M/Gateway Developments Ltd Handler Module Structure var router = require('qewd-router'); var routes; module.exports = { restModule: true, init: function() { // invoked just once when module is first loaded into worker process } };
  32. Copyright © 2016 M/Gateway Developments Ltd Handler Module Structure var router = require('qewd-router'); var routes; module.exports = { restModule: true, init: function() { routes = [ { url: '/api/search', method: 'GET', handler: search } ] } }; Define an array of routes
  33. Copyright © 2016 M/Gateway Developments Ltd Handler Module Structure var router = require('qewd-router'); var routes; module.exports = { restModule: true, init: function() { routes = [ { url: '/api/search', method: 'GET', handler: search } ] } }; Each route specifies: -url template -method (if not specified, all methods apply) -handler function
  34. Copyright © 2016 M/Gateway Developments Ltd Handler Module Structure var router = require('qewd-router'); var routes; function search(args, finished) { finished({test: 'finished ok'}); } module.exports = { restModule: true, init: function() { routes = [ { url: '/api/test', method: 'GET', handler: search } ] } }; Define handler function
  35. Copyright © 2016 M/Gateway Developments Ltd Handler Module Structure var router = require('qewd-router'); var routes; function search(args, finished) { finished({test: 'finished ok'}); } module.exports = { restModule: true, init: function() { routes = [ { url: '/api/search', method: 'GET', handler: search } ] } }; Define handler function Arguments: -args: object containing request data -finished: function you use to return response object and release worker process
  36. Copyright © 2016 M/Gateway Developments Ltd Handler Module Structure var router = require('qewd-router'); var routes; function search(args, finished) { finished({test: 'finished ok'}); } module.exports = { restModule: true, init: function() { routes = [ { url: '/api/search', method: 'GET', handler: search } ] } }; Define handler function. It: •should process the incoming message object •must create a response object •must end by invoking finished() function, which: - returns response object back to client - releases worker process to available pool
  37. Copyright © 2016 M/Gateway Developments Ltd Handler Module Structure var router = require('qewd-router'); var routes; function search(args, finished) { finished({test: 'finished ok'}); } module.exports = { restModule: true, init: function() { routes = [ { url: '/api/search', method: 'GET', handler: search } ] routes = router.initialise(routes, module.exports); } }; Finally, activate the routes
  38. Copyright © 2016 M/Gateway Developments Ltd Handler Function Structure var router = require('qewd-router'); var routes; function search(args, finished) { console.log('*** search args: ' + JSON.stringify(args, null, 2)); finished({test: 'finished ok'}); } module.exports = { restModule: true, init: function() { routes = [ { url: '/api/search', method: 'GET', handler: search } ] routes = router.initialise(routes, module.exports); } }; Simple Example:
  39. Copyright © 2016 M/Gateway Developments Ltd Let's try it out…
  40. Copyright © 2016 M/Gateway Developments Ltd Create a new QEWD startup file var config = { managementPassword: 'keepThisSecret!', serverName: 'QEWD REST Server', port: 8080, poolSize: 2, database: { type: 'gtm' } }; var routes = [ {path: '/api', module: 'myRestService'} ]; var qewd = require('qewd').master; qewd.start(config, routes); ~/qewd/rest.js
  41. Copyright © 2016 M/Gateway Developments Ltd Create the handler module var router = require('qewd-router'); var routes; function search(args, finished) { console.log('*** search args: ' + JSON.stringify(args, null, 2)); finished({test: 'finished ok'}); } module.exports = { restModule: true, init: function() { routes = [ { url: '/api/search', method: 'GET', handler: search } ] routes = router.initialise(routes, module.exports); } }; ~/qewd/node_modules/myRestService.js
  42. Copyright © 2016 M/Gateway Developments Ltd Start QEWD using your startup file cd ~/qewd node rest
  43. Copyright © 2016 M/Gateway Developments Ltd Try it It works!
  44. Copyright © 2016 M/Gateway Developments Ltd Check in the QEWD Node log: *** search args: { "req": { "type": "ewd-qoper8-express", "path": "/api/search", "method": "GET", "headers": { "host": "192.168.1.117:8080", "connection": "keep-alive", "cache-control": "max-age=0", "upgrade-insecure-requests": "1", "user-agent": "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36", "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8", "accept-encoding": "gzip, deflate", "accept-language": "en-US,en;q=0.8", "cookie": "io=5rWbmzRiCtkbJraIAAAA", "if-none-match": "W/"16-QmE+56DO76SiVG9thB82yN42w7U"" }, "params": { "type": "search" }, "query": {}, "body": {}, "ip": "::ffff:192.168.1.74", "ips": [], "application": "api", "expressType": "search" }, "session": {} } Our search() function displayed the entire args object to the console log:
  45. Copyright © 2016 M/Gateway Developments Ltd Check in the QEWD Node log: *** search args: { "req": { "type": "ewd-qoper8-express", "path": "/api/search", "method": "GET", "headers": { "host": "192.168.1.117:8080", "connection": "keep-alive", "cache-control": "max-age=0", "upgrade-insecure-requests": "1", "user-agent": "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36", "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8", "accept-encoding": "gzip, deflate", "accept-language": "en-US,en;q=0.8", "cookie": "io=5rWbmzRiCtkbJraIAAAA", "if-none-match": "W/"16-QmE+56DO76SiVG9thB82yN42w7U"" }, "params": { "type": "search" }, "query": {}, "body": {}, "ip": "::ffff:192.168.1.74", "ips": [], "application": "api", "expressType": "search" }, "session": {} } function search(args, finished) { console.log('*** search args: ' + JSON.stringify(args, null, 2)); finished({test: 'finished ok'}); } Our search() function displayed the entire messageObj object to the console log:
  46. Copyright © 2016 M/Gateway Developments Ltd Your API handler function • So what your QEWD Web/REST Service API handler functions must do is process the incoming message/request object: – To determine what to do – And use appropriate information in the incoming request object to: • Fetch data • Save data • Change data • …etc
  47. Copyright © 2016 M/Gateway Developments Ltd Your API handler function • It's therefore vital to understand what's in the args object that's made available by QEWD's router to your handler functions. • Let's take a detailed look…
  48. Copyright © 2016 M/Gateway Developments Ltd Check in the QEWD Node log: *** search args: { "req": { "type": "ewd-qoper8-express", "path": "/api/search", "method": "GET", "headers": { "host": "192.168.1.117:8080", "connection": "keep-alive", "cache-control": "max-age=0", "upgrade-insecure-requests": "1", "user-agent": "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36", "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8", "accept-encoding": "gzip, deflate", "accept-language": "en-US,en;q=0.8", "cookie": "io=5rWbmzRiCtkbJraIAAAA", "if-none-match": "W/"16-QmE+56DO76SiVG9thB82yN42w7U"" }, "params": { "type": "search" }, "query": {}, "body": {}, "ip": "::ffff:192.168.1.74", "ips": [], "application": "api", "expressType": "search" }, "session": {} } All the elements of the incoming HTTP request have been broken out and made available in the incoming args Object That was done automatically for us by the QEWD router
  49. Copyright © 2016 M/Gateway Developments Ltd QEWD args Object{ "req": { "type": "ewd-qoper8-express", "path": "/api/search", "method": "GET", "headers": { "host": "192.168.1.117:8080", "connection": "keep-alive", "cache-control": "max-age=0", "upgrade-insecure-requests": "1", "user-agent": "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36", "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8", "accept-encoding": "gzip, deflate", "accept-language": "en-US,en;q=0.8", "cookie": "io=5rWbmzRiCtkbJraIAAAA", "if-none-match": "W/"16-QmE+56DO76SiVG9thB82yN42w7U"" }, "params": { "type": "search" }, "query": {}, "body": {}, "ip": "::ffff:192.168.1.74", "ips": [], "application": "api", "expressType": "search" }, "session": {} } Request Type ie 2nd -level URL path: /api/search - args.req.params.type
  50. Copyright © 2016 M/Gateway Developments Ltd QEWD args Object{ "req": { "type": "ewd-qoper8-express", "path": "/api/search", "method": "GET", "headers": { "host": "192.168.1.117:8080", "connection": "keep-alive", "cache-control": "max-age=0", "upgrade-insecure-requests": "1", "user-agent": "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36", "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8", "accept-encoding": "gzip, deflate", "accept-language": "en-US,en;q=0.8", "cookie": "io=5rWbmzRiCtkbJraIAAAA", "if-none-match": "W/"16-QmE+56DO76SiVG9thB82yN42w7U"" }, "params": { "type": "search" }, "query": {}, "body": {}, "ip": "::ffff:192.168.1.74", "ips": [], "application": "api", "expressType": "search" }, "session": {} } The full URL path •args.req.path
  51. Copyright © 2016 M/Gateway Developments Ltd QEWD args Object{ "req": { "type": "ewd-qoper8-express", "path": "/api/search", "method": "GET", "headers": { "host": "192.168.1.117:8080", "connection": "keep-alive", "cache-control": "max-age=0", "upgrade-insecure-requests": "1", "user-agent": "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36", "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8", "accept-encoding": "gzip, deflate", "accept-language": "en-US,en;q=0.8", "cookie": "io=5rWbmzRiCtkbJraIAAAA", "if-none-match": "W/"16-QmE+56DO76SiVG9thB82yN42w7U"" }, "params": { "type": "search" }, "query": {}, "body": {}, "ip": "::ffff:192.168.1.74", "ips": [], "application": "api", "expressType": "search" }, "session": {} } The HTTP method used to submit the Request - args.req.method
  52. Copyright © 2016 M/Gateway Developments Ltd QEWD args Object{ "req": { "type": "ewd-qoper8-express", "path": "/api/search", "method": "GET", "headers": { "host": "192.168.1.117:8080", "connection": "keep-alive", "cache-control": "max-age=0", "upgrade-insecure-requests": "1", "user-agent": "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chro..", "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8", "accept-encoding": "gzip, deflate", "accept-language": "en-US,en;q=0.8", "cookie": "io=5rWbmzRiCtkbJraIAAAA", "if-none-match": "W/"16-QmE+56DO76SiVG9thB82yN42w7U"" }, "params": { "type": "search" }, "query": {}, "body": {}, "ip": "::ffff:192.168.1.74", "ips": [], "application": "api", "expressType": "search" }, "session": {} } The HTTP headers sent with the request - args.req.headers
  53. Copyright © 2016 M/Gateway Developments Ltd QEWD args Object{ "req": { "type": "ewd-qoper8-express", "path": "/api/search", "method": "GET", "headers": { "host": "192.168.1.117:8080", "connection": "keep-alive", "cache-control": "max-age=0", "upgrade-insecure-requests": "1", "user-agent": "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36", "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8", "accept-encoding": "gzip, deflate", "accept-language": "en-US,en;q=0.8", "cookie": "io=5rWbmzRiCtkbJraIAAAA", "if-none-match": "W/"16-QmE+56DO76SiVG9thB82yN42w7U"" }, "params": { "type": "search" }, "query": {}, "body": {}, "ip": "::ffff:192.168.1.74", "ips": [], "application": "api", "expressType": "search" }, "session": {} } The HTTP URL query string (if any) This is where you'll pick up any name/value pairs that were added to the URL
  54. Copyright © 2016 M/Gateway Developments Ltd QEWD args Object{ "req": { "type": "ewd-qoper8-express", "path": "/api/search?name=smith", "method": "GET", "headers": { "host": "192.168.1.117:8080", "connection": "keep-alive", "cache-control": "max-age=0", "upgrade-insecure-requests": "1", "user-agent": "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36", "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8", "accept-encoding": "gzip, deflate", "accept-language": "en-US,en;q=0.8", "cookie": "io=5rWbmzRiCtkbJraIAAAA", "if-none-match": "W/"16-QmE+56DO76SiVG9thB82yN42w7U"" }, "params": { "type": "search" }, "query": { name: 'smith' }, "body": {}, "ip": "::ffff:192.168.1.74", "ips": [], "application": "api", "expressType": "search" }, "session": {} } The HTTP URL query string (if any) This is where you'll pick up any name/value pairs that were added to the URL, eg /api/search?name=smith - args.req.query.name
  55. Copyright © 2016 M/Gateway Developments Ltd QEWD args Object{ "req": { "type": "ewd-qoper8-express", "path": "/api/search?name=smith", "method": "GET", "headers": { "host": "192.168.1.117:8080", "connection": "keep-alive", "cache-control": "max-age=0", "upgrade-insecure-requests": "1", "user-agent": "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36", "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8", "accept-encoding": "gzip, deflate", "accept-language": "en-US,en;q=0.8", "cookie": "io=5rWbmzRiCtkbJraIAAAA", "if-none-match": "W/"16-QmE+56DO76SiVG9thB82yN42w7U"" }, "params": { "type": "search" }, "query": { name: 'smith' }, "body": {}, "ip": "::ffff:192.168.1.74", "ips": [], "application": "api", "expressType": "search" }, "session": {} } Note args.req.path includes the query string too /api/search?name=smith - args.req.query.name
  56. Copyright © 2016 M/Gateway Developments Ltd QEWD args Object{ "req": { "type": "ewd-qoper8-express", "path": "/api/search", "method": "GET", "headers": { "host": "192.168.1.117:8080", "connection": "keep-alive", "cache-control": "max-age=0", "upgrade-insecure-requests": "1", "user-agent": "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36", "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8", "accept-encoding": "gzip, deflate", "accept-language": "en-US,en;q=0.8", "cookie": "io=5rWbmzRiCtkbJraIAAAA", "if-none-match": "W/"16-QmE+56DO76SiVG9thB82yN42w7U"" }, "params": { "type": "search" }, "query": {}, "body": {}, "ip": "::ffff:192.168.1.74", "ips": [], "application": "api", "expressType": "search" }, "session": {} } The HTTP request body payload (if any). If the HTTP request was sent as an application/json type, body will contain the pre-parsed JSON Only applies for POST / PUT methods - args.req.body
  57. Copyright © 2016 M/Gateway Developments Ltd QEWD args Object{ "req": { "type": "ewd-qoper8-express", "path": "/api/search", "method": "GET", "headers": { "host": "192.168.1.117:8080", "connection": "keep-alive", "cache-control": "max-age=0", "upgrade-insecure-requests": "1", "user-agent": "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36", "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8", "accept-encoding": "gzip, deflate", "accept-language": "en-US,en;q=0.8", "cookie": "io=5rWbmzRiCtkbJraIAAAA", "if-none-match": "W/"16-QmE+56DO76SiVG9thB82yN42w7U"" }, "params": { "type": "search" }, "query": {}, "body": {}, "ip": "::ffff:192.168.1.74", "ips": [], "application": "api", "expressType": "search" }, "session": {} } The IP address of the client endpoint that sent the request - args.req.ip
  58. Copyright © 2016 M/Gateway Developments Ltd Use a proper HTTP Client • A browser will only allow us to test GET requests – Need to test POST and other requests too (eg DELETE, PUT) • Numerous web service/REST clients available – Postman – Chrome's Advanced REST Client (ARC)
  59. Copyright © 2016 M/Gateway Developments Ltd Try a POST request
  60. Copyright © 2016 M/Gateway Developments Ltd Why did it error? var router = require('qewd-router'); var routes; function search(args, finished) { console.log('*** search args: ' + JSON.stringify(args, null, 2)); finished({test: 'finished ok'}); } module.exports = { restModule: true, init: function() { routes = [ { url: '/api/search', method: 'GET', handler: search } ] routes = router.initialise(routes, module.exports); } }; ~/qewd/node_modules/myRestService.js We're specifically only handling the GET method
  61. Copyright © 2016 M/Gateway Developments Ltd To handle all methods: var router = require('qewd-router'); var routes; function search(args, finished) { console.log('*** search args: ' + JSON.stringify(args, null, 2)); finished({test: 'finished ok'}); } module.exports = { restModule: true, init: function() { routes = [ { url: '/api/search', handler: search } ] routes = router.initialise(routes, module.exports); } }; ~/qewd/node_modules/myRestService.js Don't specify method at all
  62. Copyright © 2016 M/Gateway Developments Ltd Try Again • Stop and restart QEWD – To make sure the new version of the handler module is loaded into the worker process that handles the request • Alternatively, use the qewd-monitor application and stop all worker processes
  63. Copyright © 2016 M/Gateway Developments Ltd Try the POST request again
  64. Copyright © 2016 M/Gateway Developments Ltd QEWD args Object{ "req": { "type": "ewd-qoper8-express", "path": "/api/search", "method": "POST", "headers": { "host": "192.168.1.117:8080", "connection": "keep-alive", "cache-control": "max-age=0", "upgrade-insecure-requests": "1", "user-agent": "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36", "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8", "accept-encoding": "gzip, deflate", "accept-language": "en-US,en;q=0.8", "cookie": "io=5rWbmzRiCtkbJraIAAAA", "if-none-match": "W/"16-QmE+56DO76SiVG9thB82yN42w7U"" }, "params": { "type": "search" }, "query": {}, "body": {}, "ip": "::ffff:192.168.1.74", "ips": [], "application": "api", "expressType": "search" }, "session": {} }
  65. Copyright © 2016 M/Gateway Developments Ltd Try a POST with JSON payload
  66. Copyright © 2016 M/Gateway Developments Ltd QEWD args Object{ "req": { "type": "ewd-qoper8-express", "path": "/api/search", "method": "POST", "headers": { "host": "192.168.1.117:8080", "connection": "keep-alive", "cache-control": "max-age=0", "upgrade-insecure-requests": "1", "user-agent": "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36", "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8", "accept-encoding": "gzip, deflate", "accept-language": "en-US,en;q=0.8", "cookie": "io=5rWbmzRiCtkbJraIAAAA", "if-none-match": "W/"16-QmE+56DO76SiVG9thB82yN42w7U"" }, "params": { "type": "search" }, "query": {}, "body": { "name": "rob" }, "ip": "::ffff:192.168.1.74", "ips": [], "application": "api", "expressType": "search" }, "session": {} } Here's the body payload - args.req.body.name
  67. Copyright © 2016 M/Gateway Developments Ltd Using Path Templates • The routes that you specify in your worker process handler module can include templates – Variable pieces of the URL path are prefixed with a colon • /api/search/:text • This would match, eg: – /api/search/hello – /api/search/world
  68. Copyright © 2016 M/Gateway Developments Ltd Using Path Templates • Variable pieces of the URL path are prefixed with a colon • /api/search/:text • The variable name (eg text) is made available in the args object – eg: • /api/search/hello args.text = 'hello' • /api/search/world args.text = 'world'
  69. Copyright © 2016 M/Gateway Developments Ltd Using Path Templates • You can have as many variables as you wish in a URL path template, eg • /api/patient/:patientId/:heading/detail/:sourceId • In args we'd find: – args.patientId – args.heading – args.sourceId
  70. Copyright © 2016 M/Gateway Developments Ltd Example: add new route to: module.exports = { restModule: true, init: function() { routes = [ { url: '/api/search', method: 'GET', handler: search }, { url: '/api/patient/:patientId/:heading/summary', method: 'GET', handler: getSummary } ] routes = router.initialise(routes, module.exports); } }; ~/qewd/node_modules/myRestService.js
  71. Copyright © 2016 M/Gateway Developments Ltd Add the getSummary function to var headings = { allergies: true, medications: true }; function getSummary(args, finished) { if (headings[args.heading]) { finished({ patient: args.patientId, heading: args.heading, summary: 'details would be here' }); } else { finished({error: 'Invalid heading ' + args.heading}); } } ~/qewd/node_modules/myRestService.js
  72. Copyright © 2016 M/Gateway Developments Ltd Try Again • Stop and restart QEWD – To make sure the new version of the handler module is loaded into the worker process that handles the request • Alternatively, use the qewd-monitor application and stop all worker processes
  73. Copyright © 2016 M/Gateway Developments Ltd We'll try the new route • With this URL: – GET /api/patient/123456/allergies/summary
  74. Copyright © 2016 M/Gateway Developments Ltd Try the new route
  75. Copyright © 2016 M/Gateway Developments Ltd Try an invalid heading value
  76. Copyright © 2016 M/Gateway Developments Ltd Writing a QEWD Web/REST API • In your worker process handler module, simply define all the URL routes you want to allow, and write a corresponding handler function for each one • What the handler functions actually do is up to you • All the information your functions require will be in the args object • Each of your functions must create a response object and return it using finished() function
  77. Copyright © 2016 M/Gateway Developments Ltd Handling Errors • Return a response object that contains an error property – Its value should be the error string you want returned • {error: 'My error message'} – By default, the HTTP status for an error will be 400
  78. Copyright © 2016 M/Gateway Developments Ltd Remember our last error:
  79. Copyright © 2016 M/Gateway Developments Ltd Controlling the Error status function getSummary(args, finished) { if (headings[args.heading]) { finished({ patient: args.patientId, heading: args.heading, summary: 'details would be here' }); } else { finished({ error: 'Invalid heading ' + args.heading, status: { code: 401 } }); } } ~/qewd/node_modules/myRestService.js Define a custom status code value in the error response
  80. Copyright © 2016 M/Gateway Developments Ltd Restart QEWD and try again:
  81. Copyright © 2016 M/Gateway Developments Ltd Other Errors • Invalid routes – ie ones that aren't defined in the routes array
  82. Copyright © 2016 M/Gateway Developments Ltd Specify the error response: module.exports = { restModule: true, init: function() { routes = [ { url: '/api/search', method: 'GET', handler: search }, { url: '/api/patient/:patientId/:heading/summary', method: 'GET', handler: getSummary } ] routes = router.initialise(routes, module.exports); router.setErrorResponse(404, 'Not Found'); } }; ~/qewd/node_modules/myRestService.js
  83. Copyright © 2016 M/Gateway Developments Ltd Restart QEWD. Try an invalid path:
  84. Copyright © 2016 M/Gateway Developments Ltd Why doesn't this work?
  85. Copyright © 2016 M/Gateway Developments Ltd It's due to QEWD's handler logic • 2nd -level URL paths are defined behind the scenes with a handler function – So in our example there's one for /api/search – And one for /api/patient – router.setErrorResponse() handles "not found" errors within those paths – But /api/xxxxxx isn't defined as a route so router.setErrorResponse() can't handle it as an error
  86. Copyright © 2016 M/Gateway Developments Ltd Requires an additional step • this.setCustomErrorResponse()
  87. Copyright © 2016 M/Gateway Developments Ltd Specify the error response: module.exports = { restModule: true, init: function(application) { routes = [ { url: '/api/search', method: 'GET', handler: search }, { url: '/api/patient/:patientId/:heading/summary', method: 'GET', handler: getSummary } ] routes = router.initialise(routes, module.exports); router.setErrorResponse(404, 'Not Found'); } }; ~/qewd/node_modules/myRestService.js First, add application as an argument to the init() function
  88. Copyright © 2016 M/Gateway Developments Ltd Specify the error response: ] routes = router.initialise(routes, module.exports); router.setErrorResponse(404, 'Not Found'); this.setCustomErrorResponse.call(this, { application: application, errorType: 'noTypeHandler', text: 'Resource Not Found', statusCode: '404' }); } }; ~/qewd/node_modules/myRestService.js Then add the setCustomErrorResponse() Function within the init() function, exactly as shown here
  89. Copyright © 2016 M/Gateway Developments Ltd Now it should work
  90. Copyright © 2016 M/Gateway Developments Ltd Writing a QEWD Web/REST API • Within your handler functions, you have access to the embedded database via – this.db • eg for this.db.function() calls to legacy code – this.documentStore • This is what you'll usually use for database access – Also now abstracted via this.db.use()
  91. Copyright © 2016 M/Gateway Developments Ltd Background on QEWD's Embedded Database • See Training Course: – http://docs.qewdjs.com/qewd_training.html – Parts 17 – 26 for details on the embedded Document Store used by QEWD
  92. Copyright © 2016 M/Gateway Developments Ltd Example: /api/save handler routes = [ { url: '/api/search', //method: 'GET', handler: search }, { url: '/api/patient/:patientId/:heading/summary', method: 'GET', handler: getSummary }, { url: '/api/save', method: 'POST', handler: save } ] routes = router.initialise(routes, module.exports);
  93. Copyright © 2016 M/Gateway Developments Ltd Example: /api/save handler function save(args, finished) { var doc = new this.documentStore.DocumentNode('myData'); var ix = doc.increment(); doc.$(ix).setDocument(args.req.body); finished({saved: ix}); }
  94. Copyright © 2016 M/Gateway Developments Ltd Example: /api/save handler function save(args, finished) { var doc = this.db.use('myData'); var ix = doc.increment(); doc.$(ix).setDocument(args.req.body); finished({saved: ix}); } Can now also use this syntax
  95. Copyright © 2016 M/Gateway Developments Ltd Example: /api/save handler function save(args, finished) { var doc = this.db.use('myData'); var ix = doc.increment(); doc.$(ix).setDocument(args.req.body); finished({saved: ix}); } This will save the POSTed body JSON into a persistent document named myData
  96. Copyright © 2016 M/Gateway Developments Ltd Example: /api/save handler function save(args, finished) { var doc = this.db.use('myData'); var ix = doc.increment(); doc.$(ix).setDocument(args.req.body); finished({saved: ix}); } Each time you invoke it, it will increment the database index
  97. Copyright © 2016 M/Gateway Developments Ltd Restart QEWD and try it
  98. Copyright © 2016 M/Gateway Developments Ltd Check the database using qewd-monitor • qewd-monitor is a WebSocket-based browser application that is installed automatically if you use one of the QEWD installer scripts – See: – https://www.slideshare.net/robtweed/installing-configuring-ewdxpress – https://github.com/robtweed/qewd/tree/master/installers
  99. Copyright © 2016 M/Gateway Developments Ltd Check the database using qewd-monitor • start qewd-monitor in a browser using the URL – http://192.168.1.119:8080/qewd-monitor/index.html • Change the IP address / port to match your QEWD configuration
  100. Copyright © 2016 M/Gateway Developments Ltd Check the database using qewd-monitor • Log in to qewd-monitor with the managementPassword you specified in your QEWD startup file var config = { managementPassword: 'keepThisSecret!', serverName: 'My QEWD Server', port: 8080, poolSize: 2, database: { type: 'gtm' } }; var routes = [ { path: '/api', module: 'myRestService' } ]; var qewd = require('qewd').master; qewd.start(config, routes);
  101. Copyright © 2016 M/Gateway Developments Ltd Check the database using qewd-monitor • Log in to qewd-monitor with the managementPassword you specified in your QEWD startup file var config = { managementPassword: 'keepThisSecret!', serverName: 'My QEWD Server', port: 8080, poolSize: 2, database: { type: 'gtm' } }; var routes = [ { path: '/api', module: 'myRestService' } ]; var qewd = require('qewd').master; qewd.start(config, routes); It's a good idea to change this password from the default!
  102. Copyright © 2016 M/Gateway Developments Ltd Check the database using qewd-monitor Then click on the Document Store tab
  103. Copyright © 2016 M/Gateway Developments Ltd Check the database using qewd-monitor And sure enough, here's our POSTed data stored under index 1
  104. Copyright © 2016 M/Gateway Developments Ltd QEWD Multi-tasking • Note that your instance of QEWD.js is not only supporting your REST services • It's also simultaneously supporting a WebSocket-based browser application
  105. Copyright © 2016 M/Gateway Developments Ltd QEWD Multi-tasking • Note that your instance of QEWD.js is not only supporting your REST services • It's also simultaneously supporting a WebSocket-based browser application • A single instance of QEWD can simultaneously support as many browser applications and REST services as you wish
  106. Copyright © 2016 M/Gateway Developments Ltd No QEWD Session? • If you're familiar with QEWD browser- based applications, you'll know that they automatically have a QEWD Session • REST and Web Services are stateless • Not linked to an application • No implicit QEWD Session is appropriate
  107. Copyright © 2016 M/Gateway Developments Ltd Background on QEWD Sessions • See Training Course: – http://docs.qewdjs.com/qewd_training.html – Part 27 specifically explains how Sessions are implemented on top of QEWD's embedded Document Database – Part 12 for details on Session timeout control
  108. Copyright © 2016 M/Gateway Developments Ltd No QEWD Session? • REST and Web Services are stateless • Not linked to an application • No implicit QEWD Session is appropriate • However, you can create and maintain QEWD sessions in your own handlers – ewd-session module is available to you • this.sessions – You just need to know what to do!
  109. Copyright © 2016 M/Gateway Developments Ltd Using a Session with REST/Web Services • Create a login request handler – URL will be /api/login • For security, send as POST HTTP request • Username & password will be defined and sent in the Body payload, eg {"username": "rob", "password": "secret"}
  110. Copyright © 2016 M/Gateway Developments Ltd Using a Session with REST/Web Services routes = [ { url: '/api/search', //method: 'GET', handler: search }, { url: '/api/patient/:patientId/:heading/summary', method: 'GET', handler: getSummary }, { url: '/api/login', method: 'POST', handler: login } ] routes = router.initialise(routes, module.exports); Add new route into your REST handler module myRestService.js
  111. Copyright © 2016 M/Gateway Developments Ltd Using a Session with REST/Web Services function login(args, finished) { var username = args.req.body.username; if (!username || username === '') { return finished({error: 'You must provide a username'}); } var password = args.req.body.password; if (!password || password === '') { return finished({error: 'You must provide a password'}); } if (username !== 'rob') return finished({error: 'Invalid username'}); if (password !== 'secret') return finished({error: 'Invalid password'}); var session = this.sessions.create('testWebService', 3600); session.authenticated = true; finished({token: session.token}); } Add this function into your REST handler module myRestService.js
  112. Copyright © 2016 M/Gateway Developments Ltd Using a Session with REST/Web Services function login(args, finished) { var username = args.req.body.username; if (!username || username === '') { return finished({error: 'You must provide a username'}); } var password = args.req.body.password; if (!password || password === '') { return finished({error: 'You must provide a password'}); } if (username !== 'rob') return finished({error: 'Invalid username'}); if (password !== 'secret') return finished({error: 'Invalid password'}); var session = this.sessions.create('testWebService', 3600); session.authenticated = true; finished({token: session.token}); } Get username and password from POSTed body
  113. Copyright © 2016 M/Gateway Developments Ltd Using a Session with REST/Web Services function login(args, finished) { var username = args.req.body.username; if (!username || username === '') { return finished({error: 'You must provide a username'}); } var password = args.req.body.password; if (!password || password === '') { return finished({error: 'You must provide a password'}); } if (username !== 'rob') return finished({error: 'Invalid username'}); if (password !== 'secret') return finished({error: 'Invalid password'}); var session = this.sessions.create('testWebService', 3600); session.authenticated = true; finished({token: session.token}); } Validate username and password - in this example it's just hard-coded for simplicity
  114. Copyright © 2016 M/Gateway Developments Ltd Using a Session with REST/Web Services function login(args, finished) { var username = args.req.body.username; if (!username || username === '') { return finished({error: 'You must provide a username'}); } var password = args.req.body.password; if (!password || password === '') { return finished({error: 'You must provide a password'}); } if (username !== 'rob') return finished({error: 'Invalid username'}); if (password !== 'secret') return finished({error: 'Invalid password'}); var session = this.sessions.create('testWebService', 3600); session.authenticated = true; finished({token: session.token}); } If valid login credentials, start a session
  115. Copyright © 2016 M/Gateway Developments Ltd Using a Session with REST/Web Services function login(args, finished) { var username = args.req.body.username; if (!username || username === '') { return finished({error: 'You must provide a username'}); } var password = args.req.body.password; if (!password || password === '') { return finished({error: 'You must provide a password'}); } if (username !== 'rob') return finished({error: 'Invalid username'}); if (password !== 'secret') return finished({error: 'Invalid password'}); var session = this.sessions.create('testWebService', 3600); session.authenticated = true; finished({token: session.token}); } Return the session token
  116. Copyright © 2016 M/Gateway Developments Ltd Restart QEWD and Try it
  117. Copyright © 2016 M/Gateway Developments Ltd Restart QEWD and Try it It worked, and returned a QEWD Session Token
  118. Copyright © 2016 M/Gateway Developments Ltd Use QEWD Monitor to view the Session Then click on the Sessions tab
  119. Copyright © 2016 M/Gateway Developments Ltd Use QEWD Monitor to view the Session There's our testWebService Session and its token
  120. Copyright © 2016 M/Gateway Developments Ltd What next? • Subsequent requests should return the Session Token, usually in a header – eg Authorization header • Your back-end request handlers should authenticate each request – Check that the token is valid and not expired • Your handler can then use the QEWD Session for temporary data for client's ongoing dialogue
  121. Copyright © 2016 M/Gateway Developments Ltd Sounds repetitive! • It isn't – you can define this logic just once within the worker process handler module: – beforeHandler() function
  122. Copyright © 2016 M/Gateway Developments Ltd Using a Session with REST/Web Services module.exports = { restModule: true, beforeHandler: function(req, finished) { //invoked for every received message, before your route-specific handler // function is invoked // if this returns false, then your handler function is not invoked // if you do this, the beforeHandler function must invoke the finished function() // to return a response and release the worker // if it returns true or nothing, then your route-specific handler function will be invoked // in which case, the beforeHander() function must NOT invoke the finished function }, init: function(application) { // invoked once when loaded into worker process } };
  123. Copyright © 2016 M/Gateway Developments Ltd Using a Session with REST/Web Services module.exports = { restModule: true, beforeHandler: function(req, finished) { // the req argument contains the incoming repackaged request – the same // data that your handler function received in args.req }, init: function(application) { // invoked once when loaded into worker process } };
  124. Copyright © 2016 M/Gateway Developments Ltd Using a Session with REST/Web Services beforeHandler: function(req, finished) { if (req.path !== '/api/login') { var auth = req.headers.authorization; if (!auth) { finished({error: 'Authorization header missing'}); return false; } var token = auth.split('Bearer ')[1]; var status = this.sessions.authenticate(token); if (status.error) { finished(status); return false; } req.session = status.session; } }, init: function(application) { Authenticate token for all incoming requests apart from the login one
  125. Copyright © 2016 M/Gateway Developments Ltd Using a Session with REST/Web Services Ignore the login requests beforeHandler: function(req, finished) { if (req.path !== '/api/login') { var auth = req.headers.authorization; if (!auth) { finished({error: 'Authorization header missing'}); return false; } var token = auth.split('Bearer ')[1]; var status = this.sessions.authenticate(token); if (status.error) { finished(status); return false; } req.session = status.session; } }, init: function(application) {
  126. Copyright © 2016 M/Gateway Developments Ltd Using a Session with REST/Web Services Try to get the Authorization header beforeHandler: function(req, finished) { if (req.path !== '/api/login') { var auth = req.headers.authorization; if (!auth) { finished({error: 'Authorization header missing'}); return false; } var token = auth.split('Bearer ')[1]; var status = this.sessions.authenticate(token); if (status.error) { finished(status); return false; } req.session = status.session; } }, init: function(application) {
  127. Copyright © 2016 M/Gateway Developments Ltd Using a Session with REST/Web Services If there's no Authorization header, then return an error beforeHandler: function(req, finished) { if (req.path !== '/api/login') { var auth = req.headers.authorization; if (!auth) { finished({error: 'Authorization header missing'}); return false; } var token = auth.split('Bearer ')[1]; var status = this.sessions.authenticate(token); if (status.error) { finished(status); return false; } req.session = status.session; } }, init: function(application) {
  128. Copyright © 2016 M/Gateway Developments Ltd Using a Session with REST/Web Services ..and tell QEWD not to invoke the handler for this incoming message beforeHandler: function(req, finished) { if (req.path !== '/api/login') { var auth = req.headers.authorization; if (!auth) { finished({error: 'Authorization header missing'}); return false; } var token = auth.split('Bearer ')[1]; var status = this.sessions.authenticate(token); if (status.error) { finished(status); return false; } req.session = status.session; } }, init: function(application) {
  129. Copyright © 2016 M/Gateway Developments Ltd Using a Session with REST/Web Services beforeHandler: function(req, finished) { if (req.path !== '/api/login') { var auth = req.headers.authorization; if (!auth) { finished({error: 'Authorization header missing'}); return false; } var token = auth.split('Bearer ')[1]; var status = this.sessions.authenticate(token); if (status.error) { finished(status); return false; } req.session = status.session; } }, init: function(application) { Separate out the session token from the Authorization header. The usual convention is: Authorization: Bearer {{token}}
  130. Copyright © 2016 M/Gateway Developments Ltd Using a Session with REST/Web Services beforeHandler: function(req, finished) { if (req.path !== '/api/login') { var auth = req.headers.authorization; if (!auth) { finished({error: 'Authorization header missing'}); return false; } var token = auth.split('Bearer ')[1]; var status = this.sessions.authenticate(token); if (status.error) { finished(status); return false; } req.session = status.session; } }, init: function(application) { Authenticate the token using QEWD's Built-in session API
  131. Copyright © 2016 M/Gateway Developments Ltd Using a Session with REST/Web Services beforeHandler: function(req, finished) { if (req.path !== '/api/login') { var auth = req.headers.authorization; if (!auth) { finished({error: 'Authorization header missing'}); return false; } var token = auth.split('Bearer ')[1]; var status = this.sessions.authenticate(token); if (status.error) { finished(status); return false; } req.session = status.session; } }, init: function(application) { If the session was invalid, return the error reported by the session API
  132. Copyright © 2016 M/Gateway Developments Ltd Using a Session with REST/Web Services beforeHandler: function(req, finished) { if (req.path !== '/api/login') { var auth = req.headers.authorization; if (!auth) { finished({error: 'Authorization header missing'}); return false; } var token = auth.split('Bearer ')[1]; var status = this.sessions.authenticate(token); if (status.error) { finished(status); return false; } req.session = status.session; } }, init: function(application) { Instruct QEWD not to invoke the normal handler function
  133. Copyright © 2016 M/Gateway Developments Ltd Using a Session with REST/Web Services beforeHandler: function(req, finished) { if (req.path !== '/api/login') { var auth = req.headers.authorization; if (!auth) { finished({error: 'Authorization header missing'}); return false; } var token = auth.split('Bearer ')[1]; var status = this.sessions.authenticate(token); if (status.error) { finished(status); return false; } req.session = status.session; } }, init: function(application) { If token was valid, save the session DocumentNode Object into the incoming request object QEWD will automatically extract it and make it available to your handler function as args.session
  134. Copyright © 2016 M/Gateway Developments Ltd Using a Session with REST/Web Services beforeHandler: function(req, finished) { if (req.path !== '/api/login') { return this.sessions.authenticateRestRequest(req, finished); } }, init: function(application) { However, you can achieve the same thing by using this built-in API I'd recommend you use this in the beforeHandler() function
  135. Copyright © 2016 M/Gateway Developments Ltd Here's our routes init: function(application) { routes = [ { url: '/api/search', //method: 'GET', handler: search }, { url: '/api/patient/:patientId/:heading/summary', method: 'GET', handler: getSummary }, { url: '/api/save', method: 'POST', handler: save }, { url: '/api/login', method: 'POST', handler: login } ] routes = router.initialise(routes, module.exports); All but /api/login will now expect a token in the Authorization header
  136. Copyright © 2016 M/Gateway Developments Ltd Restart QEWD and Try it
  137. Copyright © 2016 M/Gateway Developments Ltd Add an invalid Authorization Header
  138. Copyright © 2016 M/Gateway Developments Ltd Try Logging In
  139. Copyright © 2016 M/Gateway Developments Ltd Put the token in the Authorization Header & Try Again
  140. Copyright © 2016 M/Gateway Developments Ltd Put the token in the Authorization Header & Try Again Now you can invoke the /api/search (and any other) API
  141. Copyright © 2016 M/Gateway Developments Ltd Now let's use the Session • Make use of it in the /api/search request – Display it in the response • First let's put the username into the Session if the user successfully logs in
  142. Copyright © 2016 M/Gateway Developments Ltd Add username to Session function login(args, finished) { var username = args.req.body.username; if (!username || username === '') { return finished({error: 'You must provide a username'}); } var password = args.req.body.password; if (!password || password === '') { return finished({error: 'You must provide a password'}); } if (username !== 'rob') return finished({error: 'Invalid username'}); if (password !== 'secret') return finished({error: 'Invalid password'}); var session = this.sessions.create('testWebService', 3600); session.authenticated = true; session.data.$('username').value = username; finished({token: session.token}); }
  143. Copyright © 2016 M/Gateway Developments Ltd Get the username in /api/search function search(args, finished) { finished({ test: 'finished ok', username: args.session.data.$('username').value }); }
  144. Copyright © 2016 M/Gateway Developments Ltd Restart QEWD and Try it • First run POST /api/login • Copy the returned token and paste it into the Authorization Header: – Bearer {{token}} • Then try GET /api/search
  145. Copyright © 2016 M/Gateway Developments Ltd Restart QEWD and Try it
  146. Copyright © 2016 M/Gateway Developments Ltd Restart QEWD and Try it Now it successfully retrieves and returns the username from the QEWD Session
  147. Copyright © 2016 M/Gateway Developments Ltd Session Logout? • No need for an explicit /logout request • Session will automatically expire • If you want, you could set the session as un-authenticated: – session.authenticated = false;
  148. Copyright © 2016 M/Gateway Developments Ltd Session Control • Remember, once you have authenticated the token and have the session, you can change the its timeout and update its expiry, just as in standard message handlers – session.timeout = 600; – session.updateExpiry();
  149. Copyright © 2016 M/Gateway Developments Ltd REST & Web Services • You now have everything you need to support REST and Web Services on your QEWD system – In addition to supporting interactive applications on the same QEWD server, if you wish • Add / define as many REST/Web Services as you want by adding them to the QEWD startup file
  150. Copyright © 2016 M/Gateway Developments Ltd Example Files • You'll find the example files in the QEWD Github Repo: • https://github.com/robtweed/qewd/tree/master/example/rest
  151. Copyright © 2016 M/Gateway Developments Ltd Advanced features • Although QEWD automates most of what you need, occasionally you may want to gain access to Express within the master process and handle routing etc yourself – To do this, you add an intercept to the QEWD startup file, which exposes Express
  152. Copyright © 2016 M/Gateway Developments Ltd Adding an Intercept to the Startup file var config = { managementPassword: 'keepThisSecret!', serverName: 'My QEWD Server', port: 8080, poolSize: 2, database: { type: 'cache', params: { path: 'c:InterSystemsCache2015-2mgr’ } }}; var master = require('qewd').master; var qewd = master.intercept(); master.start(config);
  153. Copyright © 2016 M/Gateway Developments Ltd Adding an Intercept to the Startup file var config = { managementPassword: 'keepThisSecret!', serverName: 'My QEWD Server', port: 8080, poolSize: 2, database: { type: 'cache', params: { path: 'c:InterSystemsCache2015-2mgr’ } }}; var master = require('qewd').master; var qewd = master.intercept(); master.start(config); now we have access to: - The qewd object itself: qewd.q - ewd-qoper8-express: qewd.qx - Express middleware: qewd.app
  154. Copyright © 2016 M/Gateway Developments Ltd Adding an Intercept to the Startup file var config = { managementPassword: 'keepThisSecret!', serverName: 'My QEWD Server', port: 8080, poolSize: 2, database: { type: 'cache', params: { path: 'c:InterSystemsCache2015-2mgr’ } }}; var master = require('qewd').master; var qewd = master.intercept(); master.start(config); eg: use some custom Express middleware qewd.app.use(….);