More Related Content
Similar to EWD 3 Training Course Part 31: Using QEWD for Web and REST Services (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:
- 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
- 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)
- 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
- 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": {}
}
- 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
- 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
- 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
- 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:
- 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
- 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
- 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
- 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
- 137. Copyright © 2016 M/Gateway Developments Ltd
Add an invalid Authorization Header
- 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
- 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(….);