M/Gateway Developments Ltd ewd-federator Reference Guide M/Gateway Developments Ltd http://www.mgateway.com Copyright ©2015, M/Gateway Developments Ltd. All Rights Reserved Table of Contents Introduction 1 Background 1 Background and Architecture 2 Overview 2 ewd-federator Architecture 2 Choice of Database for ewd-federator 5 Installing, Configuring & Running 6 Initial Installation Steps 6 Installing ewd-federator 6 Configuring ewd-federator 6 Caché: 7 GT.M: 7 Starting ewd-federator 8 Testing ewd-federator 8 Stopping ewd-federator 9 ewd-federator Configuration 10 Configuration Basics 10 The server Object 12 Re-routing to EWD.js Servers 12 The service Object ewd-federator Reference Guide (Build 0.13) 10 i Copyright ©2015, M/Gateway Developments Ltd. All Rights Reserved EWD.js URL Mapping 12 Signing the EWD.js Request 15 Grouping Destinations 16 Defining a Destination Group 16 Using a Group Destination 17 Customising ewd-federator 19 Why Customise ewd-federator? 19 Creating an Extension Module 19 Defining and Handling Events in your Extension Module 20 Handling the request Event 20 Manually Sending a Request to a Destination 23 Defining Custom Response Events 24 The ScratchPad Object 26 Response Event Handlers for Destination Groups 26 ewd-federator Reference Guide (Build 0.13) ii Copyright ©2013-15, M/Gateway Developments Ltd. All Rights Reserved Introduction Background ewd-federator is an Apache 2-licensed Open Source Node.js-based customisable platform that allows you to create a federation of servers, each of which provide access via HTTP-based or RESTbased Web Services. Key features are as follows: • ewd-federator is accessed as a REST-ful service. ewd-federator makes use of the Node.js Restify module. • incoming REST requests can be re-routed to one or more of the federated Web Service servers. Routing to more than one servers simultaneously is achieved by defining groups of servers. You may define as many groups as you like, and groups can overlap. Groups can also be defined dynamically. • responses from a group of servers are automatically aggregated by ewd-federator • if one or more of the federated servers is running EWD.js, ewd-federator will correctly sign the requests (provided it is registered for access by the EWD.js server). ewd-federator can be used with EWD.js to provide a fully REST-ful interface to your EWD.js Web Services. • you can optionally customise ewd-federator by adding your own Node.js module that allows you to intercept and modify/manipulate not only incoming REST requests to ewd-federator, but also incoming responses from the back-end federated servers. • ewd-federator uses the same master/child-process architecture as EWD.js, and similarly makes use of a persistent JSON database (Caché, GT.M, GlobalsDB). The purpose of this integrated database is to allow you to cache or store custom information, such as Single Sign On (SSO) authentication tokens. How you make use of this database is entirely up to you. It is not used or required by ewdfederator itself. ewd-federator is therefore a lightweight, but very flexible and powerful federation platform. This document explains how to install, configure, run and customise ewd-federator. ewd-federator Reference Guide (Build 0.13) 1 Copyright ©2013-15, M/Gateway Developments Ltd. All Rights Reserved Background and Architecture Overview ewd-federator acts as an intermediate tier between REST clients and a federation of back-end Web Service and REST servers (which may include EWD.js servers), as summarised below: You can federate as many back-end servers as you wish, and they can be any combination of HTTP-based Web Service servers, REST service servers and EWD.js servers. ewd-federator would normally be installed on its own server. It runs as a set of Node.js processes (see below), and requires the use of a database that you may use for local cacheing and data persistence within your customisation logic. Currently 3 databases are supported: Caché, GlobalsDB and GT.M, all of which behave as persistent JSON storage engines. ewd-federator Architecture ewd-federator’s architecture is similar to that used by EWD.js, which best exploits the behaviour and characteristics of the Node.js interface to the databases it uses. All incoming REST requests are handled by a fully asynchronous master Node.js process. The main work, however, is carried out by a pre-forked, constantly-running pool of child processes, each of which has an in-process, synchronous connection to the selected database. This is summarised in the diagram below: ewd-federator Reference Guide (Build 0.13) 2 Copyright ©2013-15, M/Gateway Developments Ltd. All Rights Reserved You can configure the size of the child process pool in the ewd-federator start-up file, and you can modify and control the size of the pool when ewd-federator is running. Communication with the back-end Web Service and REST servers occurs within the ewd-federator module that runs in each child process (as summarised below): The master process uses the Restify module to provide the REST server interface through which REST clients communicate. Whenever an incoming REST request is received, it is placed in a queue which is processed immediately. If a child process is available, the request is passed to that child process which is removed from the available pool: a child process therefore only handles a single request at a time. The ewd-federator child process module re-routes and re-writes the REST request and forwards it to one or more back-end servers. If the back-end server is an EWD.js server, ewd-federator uses the ewdliteclient module to digitally sign the request: ewd-federator Reference Guide (Build 0.13) 3 Copyright ©2013-15, M/Gateway Developments Ltd. All Rights Reserved Responses from the back-end server(s) are returned to the child process and returned to the master process which forwards it to the awaiting REST client. As soon as the child process has returned the response to the master process, it is returned to the available pool. Re-routing of incoming REST requests is performed by default by a JSON object that maps the destination (as defined in the incoming REST request) to a physical back-end server’s host address and port. Re-writing of the REST request to meet the characteristics of the remote server’s web service interface is carried out, as far as is possible, with a JSON object map. For more complex re-routing and re-writing, and in order to create complex “dances” between the remote servers (eg for Single Sign On logic), ewd-federator allows you to define your own extension Node.js module in which you can intercept the incoming requests and returned responses and modify them as required. For example, on receipt of a response from a remote server, your extension module can send out one or more requests to back-end servers instead of allowing the response to be returned to the original REST client. Once your extension module’s logic determines that the information has been assembled to construct the required federated response, you can then return it to the awaiting REST client. Along the way, your extension module logic can save information to the database and/or retrieve information from the database: this is useful as a means of cacheing information to avoid the need for repeated “dances” between the back-end servers, and also allows authentication tokens to be stored on a permanent or temporary basis. The diagram below summarises the customised flow: ewd-federator Reference Guide (Build 0.13) 4 Copyright ©2013-15, M/Gateway Developments Ltd. All Rights Reserved Choice of Database for ewd-federator The databases currently supported by ewd-federator are Caché, GlobalsDB and GT.M. ewd-federator uses the globals.js module to abstract all three databases to behave as persistent JSON stores. Due to the currently available versions of the interfaces between these databases and Node.js, ewd-federator is limited to running with Node.js version 0.10.x. This will change in due course. ewd-federator itself does not require the use of a database for its own default behaviour, but you will almost certainly want to customise ewd-federator, The included database allows you to easily persist and cache information to support your federation logic as JSON documents. ewd-federator can be installed on Windows, Linux or Mac OS X. Caché or GlobalsDB can be used as the database on any of these operating systems. GT.M can only be used on Linux systems. If you need to choose which one to use, bear the following in mind: • GlobalsDB is a free, but closed source product. However, it has no limitations in terms of its use or re-distribution. It is essentially the core database engine from Caché. Versions are available for Windows, Mac OS X and Linux. No technical support or maintenance is available for GlobalsDB from InterSystems: it is provided for use on an “as-is” basis. GlobalsDB is the quickest and simplest of the databases to install, and is probably the best option to use if you are new to ewdfederator. • GT.M is a free, Open Source, industrial-strength product. It is limited to Linux systems. • Caché is a proprietary and commercially-licensed industrial-strength product, with versions available for Windows, Mac OS X and Linux. ewd-federator only requires its core database engine, but if you’re already a Caché user, you may prefer to use it. Caché also provides excellent built-in solutions for scaling out ewd-federator instances where high scalability and availability is essential. For more details about these databases, see: • GlobalsDB: http://www.globalsdb.org/ • GT.M: http://www.fisglobal.com/products-technologyplatforms-gtm • Caché: http://www.intersystems.com/cache/index.html All three databases are extremely fast, and both Caché and GT.M can scale to extremely large enterprise levels. The Node.js interface for GlobalsDB and Caché currently has about twice the speed of performance as the NodeM interface for GT.M. Note that when you use ewd-federator, all three Mumps databases appear to be identical in terms of how you access and manipulate data from within your customisation logic. The only difference you’ll need to be aware of is a small difference in the configuration settings within the startup file you’ll use for the ewd-federator module (see later). Otherwise, ewd-federator customisation logic that you develop for, say, Caché, should normally work without any coding changes with a GT.M database. ewd-federator Reference Guide (Build 0.13) 5 Copyright ©2013-15, M/Gateway Developments Ltd. All Rights Reserved Installing, Configuring & Running Initial Installation Steps • Install Node.js: See http://nodejs.org/ ewd-federator can currently be used with version 0.10.x of Node.js. Don’t install the latest 0.12.x version. • Install your chosen database: see the relevant web-site for details (the relevant URLs are listed in the previous chapter) A very quick and simple way to get started with ewd-federator, particularly if you’re not familiar with these databases is to use the EWD.js installers that are documented in Appendix I and II in the EWD.js Reference Guide. These will install Node.js, GT.M or GlobalsDB and EWD.js on a Ubuntu Linux platform (physical machine, VM, Amazon EC2 instance). You can simply ignore EWD.js when the installation is complete. The EWD.js Reference Guide also provides further information about the Node.js interfaces for the databases used by ewdfederator. Installing ewd-federator Simply navigate to the correct directory where you want ewd-federator to be installed and use NPM, eg: cd ~/ewdjs npm install ewd-federator You may see some errors reported, but you can usually ignore these as they relate to some of the Restify dependencies that aren’t actually used by ewd-federator. Configuring ewd-federator Next you need to configure ewd-federator. This is done in the JavaScript file that you’ll use to start ewd-federator. Initially we’ll just create a simple “hello world” setup that will make use of a test REST server. Make sure you’re in the same directory as when you installed ewd-federator, eg in the example above ~/ewdjs. Create and save your startup file as shown below. You can name your startup file anything you like, but it should have a file extension of .js. For example, save it as startFederator.js: ewd-federator Reference Guide (Build 0.13) 6 Copyright ©2013-15, M/Gateway Developments Ltd. All Rights Reserved var federator = require('ewd-federator'); var params = { restPort: 8081, poolSize: 2, traceLevel: 3, }; database: { type: 'globals', path: '/home/ubuntu/globalsdb/mgr', }, server: { placeholder: { host: 'jsonplaceholder.typicode.com', port: 80, ssl: false, ewdjs: false } } federator.start(params); This example above assumes we’re using GlobalsDB as the database, and that GlobalsDB Manager directory path is: /home/ubuntu/globalsdb/mgr You should modify this path to match where you’ve installed GlobalsDB. If you’re using Caché or GT.M you’ll need to modify the database section of the params object, for example: Caché: database: { type: 'cache', path: '/usr/local/cachesys/mgr', username: 'demoUser', password: 'xyz1234', namespace: 'USER' }, Modify the path to match the one where Caché’s mgr directory is installed. Note that the cache.node interface needs to log into Caché using a valid username and password, and note that you also must specify the namespace into which it will connect. GT.M: database: { type: 'gtm' }, This assumes that the Node.js interface for GT.M (NodeM) has been installed using NPM - see https://github.com/dlwicksell/ nodem The startup file has configured a single back-end server that we have given a destination of placeholder, and which points to a host name jsonplaceholder.typicode.com. This is a useful, publicly-available demonstration REST Server. ewd-federator Reference Guide (Build 0.13) 7 Copyright ©2013-15, M/Gateway Developments Ltd. All Rights Reserved Starting ewd-federator Once we have a startup file defined, we can fire it up. Once again, do this from the directory in which you installed ewdfederator (which is also the directory where your start-up file should reside): cd ~/ewdjs node startFederator Provided that everything is correctly installed and that the configuration paths etc are correct, ewd-federator should start, and will then be sitting waiting to receive incoming REST requests. For example, at the end of the startup trace report, it should say something like this: requireAndWatch: /home/ubuntu/ewd-test/node_modules/globalIndexer.js loaded by process 2758 ** Global Indexer loaded: /home/ubuntu/ewd-test/node_modules/globalIndexer.js child process returned response {"pid":2758,"type":"initialise","release":true,"empty":true,"processNo":1} process 2758 returned to available pool; type=initialise ****************************************************** ******* ewd-federator is Ready *********** ****************************************************** Started: Tue, 17 Mar 2015 15:18:55 GMT Listening on port 8081 *** checking child process pool at Tue, 17 Mar 2015 15:18:57 GMT pid: 2754 available for 1000 pid: 2758 available for 924 Testing ewd-federator Although you can send simple GET requests from any browser, you probably want to use a proper REST client. For example, if you use the Chrome browser, try installing the Advanced REST Client extension. Try the following GET request, substituting the IP address or domain name of the server on which you’re running ewdfederator: http://xx.xx.xxx.xxx:8081/placeholder/posts/1 You should get back a response similar to this: ewd-federator Reference Guide (Build 0.13) 8 Copyright ©2013-15, M/Gateway Developments Ltd. All Rights Reserved { userId: 1 id: 1 title: "sunt aut facere repellat provident occaecati excepturi optio reprehenderit" body: "quia et suscipit suscipit recusandae consequuntur expedita et cum reprehenderit molestiae ut ut quas totam nostrum rerum est autem sunt rem eveniet architecto" } What’s happened is, of course, that ewd-federator has relayed the REST request to the placeholder server. Try sending the equivalent request directly to this server: http://jsonplaceholder.typicode.com/posts/1 If this example worked for you, you now have ewd-federator up and working. Stopping ewd-federator If you’re running ewd-federator as a foreground process, you can stop it simply by typing CTRL & C within the process window. If you’re running it as a background service, send a SIGTERM message to it (eg using kill {process id}) ewd-federator Reference Guide (Build 0.13) 9 Copyright ©2013-15, M/Gateway Developments Ltd. All Rights Reserved ewd-federator Configuration Configuration Basics ewd-federator is configured by a JSON object that is used as the argument for the ewd-federator start() function. A simple example was shown in the previous chapter. The configuration object has the following mandatory properties: • restPort: the port on which ewd-federator listens for incoming REST requests • poolSize: the number of child processes that are pre-forked when ewd-federator is started • traceLevel: the level of verbosity of logging information shown in the ewd-federator console.log (0 = none, 3 = maximum) • timeout: the number of milliseconds ewd-federator will wait for a response from a remote server, before giving up and returning a 408 error (default 120,000ms) • database: an object that defines the type of database and its configuration parameters (see previous chapter) • server: an object defining the destination alias names and associated connection details of the back-end servers accessible via ewd-federator. You can define as many servers as you like There are a number of additional, optional properties: • service: an object defining mappings from REST paths to corresponding EWD.js module and function (service) names • destination: an object defining group alias names that represent aggregations of back-end servers, eg _florida. • extensionModule: the name of an optional extension module in which you can customise the behaviour of ewd-federator • password: the management password for system-level access to ewd-federator when it is running (see later). The server Object The server object allows you to define the connection details and other key characteristics of each back-end server within your federation. Each server is given an alias (otherwise known as a destination name). Against each alias is an object that defines the specific server, ie: ewd-federator Reference Guide (Build 0.13) 10 Copyright ©2013-15, M/Gateway Developments Ltd. All Rights Reserved var params = { ... server: { <alias1>: { host: <host name or IP address>, port: <port>, ssl: <true | false>, ewdjs: <true | false> }, <alias2>: { host: <host name or IP address>, port: <port>, ssl: <true | false>, ewdjs: <true | false> }, ...etc } }; The alias name can be any valid JSON property name. This is the name that will be used as the destination to which REST requests are routed. The way this works is very simple: when sending a REST request to ewd-federator, the first part of the URL path must correspond to a configured server alias name. So in the example from the previous chapter, a server named placeholder was defined: server: { placeholder: { host: 'jsonplaceholder.typicode.com', port: 80, ssl: false, ewdjs: false } } We were then able to route REST requests to it via ewd-federator using a URL that contained /placeholder as the first part of its path, eg: http://localhost:8081/placeholder/posts/1 Each individual server is defined by a sub-object with the following properties: • host: the IP address or domain name of the remote server • port: the port on which the remote server listens for REST / Web Service requests • ssl: true | false - defines whether or not the remote server requests must be sent in the clear or over SSL/TLS • ewdjs: true | false (default if not specified = true) - if true, the remote server is an EWD.js server, and ewd-federator must digitally sign the requests that are forwarded to it. In order to do this, the following two properties must be defined: • accessId: the accessId assigned by the EWD.js server for use by this instance of the ewd-federator. • secretKey: the corresponding secret key, assigned by the EWD.js server. This is used to digitally sign all request to the EWD.js server, using the HMAC-SHA256 cipher. Note: accessId and secretKey can be omitted if the ewdjs property is set to false. So in the example above, the REST request took the first part of the URL path (ie placeholder) and looked that up in the server object. It then had the information needed to route the request to the physical host (jsonplaceholder.typicode.com). The first part of the path was removed from the URL sent to the remote site (ie the URL path sent was posts/1). ewd-federator Reference Guide (Build 0.13) 11 Copyright ©2013-15, M/Gateway Developments Ltd. All Rights Reserved Re-routing to EWD.js Servers When a REST request is forwarded to an EWD.js server, ewd-federator takes a couple of extra steps: • the incoming REST URL is mapped to a corresponding EWD.js HTTP Web Service URL using the service configuration object. • the HTTP request that is forwarded to the EWD.js is digitally signed using the accessId and secretKey that was allocated to the ewd-federator instance by that particular EWD.js server. Note also that the ewd-federator instance can only send requests that access the modules that the EWD.js server has permitted it to use. The accessId, secretKey and application permissions are granted on the EWD.js server by using a security option within the ewdMonitor application. Consult the EWD.js Reference Guide for details. The service Object The service configuration sub-object defines the mappings between incoming REST URLs and remote EWD.js web service modules, eg: var params = { ... service: { <module alias 1>: { module: <EWD.js module name>, service: <function name within module>, contentType: 'application/json' }, <module alias 2>: { module: <EWD.js module name>, service: <function name within module>, contentType: 'application/json' }, ...etc } }; EWD.js URL Mapping The structure of EWD.js-mapped REST requests is as follows: http://localhost:8081/<destination>/<module alias name>/<operation name> .... Any further sub-paths or querystring name/value pairs that follow the operation name are specific to the operation name. The destination mapping has been described in the previous section. The second part of the URL path (ie the module alias name) is looked up in the service object. If it can be found, then the corresponding EWD.js Web Service URL is constructed. For example, if the server and service objects contained the following: ewd-federator Reference Guide (Build 0.13) 12 Copyright ©2013-15, M/Gateway Developments Ltd. All Rights Reserved var params = { ... service: { demo: { module: myDemo, service: parse, contentType: 'application/json' } }, server: { london: { host: '99.9.9.9', port: 8080, ssl: false, ewdjs: true, secretKey: 'rob123', accessId: 'rob' } } }; then the following incoming REST request: http://localhost:8081/london/demo/helloworld?id=1234 would be re-written as: http://99.9.9.9:8080/json/myDemo/parse?id=1234&rest_path=demo/helloworld&...etc Notice that the operation name in the REST request (helloworld) is not directly mapped to the EWD.js URL, but is conveyed within the value of the rest_path name/value pair. Provided this request is correctly digitally signed (see later), the remote EWD.js server will invoke the myDemo module and execute the parse() function within it. The parse() function should parse/inspect the rest_path name/value pair to determine which function within the myDemo module is actually required. In fact, all the relevant information about the originating REST request is forwarded to the EWD.js server as name value pairs attached to the URL query string. The full list is as follows: • rest_url: the full original REST URL, eg http://localhost:8081/london/demo/helloworld?id=123 • rest_path: the path part of the original REST URL, with the destination removed, eg /demo/helloworld • rest_auth: if an Authorization HTTP Header was included with the request, its value is included here • rest_method: the original REST HTTP method (eg GET, POST, PUT etc) The parse() method (or whatever function you specify for processing all REST requests for a particular operation within an EWD.js module) may need to use several or all of these name/value pairs in order to determine what to do. For example, suppose on the EWD.js server you have three operations defined for a module (aka application) named Scheduling: • getAppointments • setAppointment ewd-federator Reference Guide (Build 0.13) 13 Copyright ©2013-15, M/Gateway Developments Ltd. All Rights Reserved • deleteAppointment Expressed via REST requests sent to ewd-federator, these would probably be expressed using a single resource name Appointments - and the appropriate HTTP verb, ie: • GET /Scheduling/Appointments • PUT /Scheduling/Appointments • DELETE /Scheduling/Appointments So, for example, the REST GET /Scheduling/Appointments URL might look like this: GET http://localhost:8081/london/Scheduling/Appointments?id=1234&date=today As a result of the service object mapping, this might get re-written to the following EWD.js URL that is sent to the london server: http://99.9.9.9:8080/json/scheduling/parse?id=1234&date=today& rest_path=Scheduling/Appointments&rest_method=GET&...etc The scheduling.js module on the EWD.js server might then look something like this: var operations = { Appointments: { GET: function(ewd) { // code to get appointments var id = ewd.query.id; var date = ewd.query.date; var appointments = {}; //...etc return appointments; }, PUT: function(ewd) { // code to set an appointment }, DELETE: function(ewd) { // code to delete an appointment } } } module.exports: { parse: function(ewd) { var resource = ewd.query.rest_path.split('/')[1]; var method = ewd.query.rest_method; if (operations[resource] && operations[resource][method]) { return operations[resource][method](ewd); } else { return {error: 'Invalid request'}; } } }; ewd-federator Reference Guide (Build 0.13) 14 Copyright ©2013-15, M/Gateway Developments Ltd. All Rights Reserved Signing the EWD.js Request The final step performed by ewd-federator is to digitally sign the re-written EWD.js request using the accessId and SecretKey values that are held in the server object for the specified destination EWD.js server. ewd-federator uses the ewdliteclient module to perform this task. The request is then dispatched to the remote EWD.js server. Provided the accessId and secretKey are correct and provided the EWD.js server allows the ewd-federator instance to invoke the specified module (myDemo.js in the example above), then the request will be accepted and invoked on the EWD.js server. For full details on how to define and write Web Services on EWD.js systems, see the chapter “Web Service Interface” in the EWD.js Reference Manual. ewd-federator Reference Guide (Build 0.13) 15 Copyright ©2013-15, M/Gateway Developments Ltd. All Rights Reserved Grouping Destinations Defining a Destination Group So far, you’ve seen how individual destinations can be defined and mapped to the connection details of a specific back-end server. However, ewd-federator gives you much more flexibility by allowing you to define groups of destinations. For example, suppose you had the following destinations defined: • crawley • reigate • guildford • leeds • york Suppose you want to get all of a particular set of results from the servers in Surrey (ie crawley, reigate and guildford), you can define a Group Destination named surrey, by using the destinations configuration object. Provided you’ve already defined the three servers above in the servers object, you simply add the following to the configuration object: var params = { ... destination: { Surrey: [ 'crawley', 'reigate', 'guildford' ], Yorkshire: [ 'leeds', 'york' ], ...etc } }; You can define as many group destinations as you want, and individual servers can appear in as many group destinations as you want. Note that each member of a group must be an alias defined in the server object. You can’t use a group destination name as a member of another group definition array. For example, to define a group named England that included all the sites in England, you’d have to define it as follows: ewd-federator Reference Guide (Build 0.13) 16 Copyright ©2013-15, M/Gateway Developments Ltd. All Rights Reserved var params = { ... destination: { Surrey: [ 'crawley', 'reigate', 'guildford' ], Yorkshire: [ 'leeds', 'york' ], England: [ 'crawley', 'reigate', 'guildford', 'leeds', 'york' ], } }; You cannot do this, however: var params = { ... destination: { Surrey: [ 'crawley', 'reigate', 'guildford' ], Yorkshire: [ 'leeds', 'york' ], England: [ 'Surrey', 'Yorkshire' ], } }; Using a Group Destination if you specify the destination as /Surrey in the REST request that you send to ewd-federator, it will send identical copies of the mapped request to the servers with aliases of crawley, reigate and guildford. These requests are sent out in parallel, at the same time and asynchronously. For example, your REST request might look like this: GET http://localhost:8081/Surrey/Warehouse/StockReport?item=widgets ewd-federator will automatically aggregate the responses that are returned from the three servers in Surrey. When it has received all the expected responses, it returns the aggregated JSON response to the waiting REST client. So, for example, the aggregated response might look like this: ewd-federator Reference Guide (Build 0.13) 17 Copyright ©2013-15, M/Gateway Developments Ltd. All Rights Reserved { } "crawley": { "stockLevel": 23, "orderNo": "AB13" }, "reigate": { "stockLevel": 0, "orderNo": "RBV531A" }, {"guildford": { "stockLevel": 143, "orderNo": "GU7FGY5" } Note that if an error occurs on one of the servers within the group, eg if it times out, the error response is added as a JSON object for that server to the aggregated response. ewd-federator Reference Guide (Build 0.13) 18 Copyright ©2013-15, M/Gateway Developments Ltd. All Rights Reserved Customising ewd-federator Why Customise ewd-federator? ewd-federator’s default behaviour and functionality that has been described so far may meet all your requirements - in which case that is great: read no further! However, you will probably find situations where you’d like to take control over the mapping between the incoming REST requests and the outgoing requests sent to some or all of the back-end servers. Perhaps you want to keep an audit trail on ewd-federator of the activity, or logging information. Perhaps you want to implement a Single Sign On mechanism where one back-end server is the the main authentication server which returns a token that is then used for signing onto other back-end servers. Perhaps you want to intercept responses from the back-end servers and, dependent on the results, fire off some more requests to another set of back-end servers before creating your own customised aggregated response. ewd-federator lets you take complete control and perform any level of complexity of “dance” that you want to create between sending in a REST request to ewd-federator and ultimately getting back a response. The key to customising ewd-federator is for you to define an extension module, and instruct ewd-federator to use it via the startup configuration object. Creating an Extension Module An ewd-federator extension module is simply a standard Node.js module file, written in JavaScript. It has the following signature / pattern: var events = require('events'); var federator = new events.EventEmitter(); module.exports = { event: federator, events: [ // array of names of events to be handled ] }; // Event Handler functions .... // Event Workflow .... You typically save this file in the node_modules directory that exists below the directory into which you originally installed ewd-federator. Give it any name you like, eg: ~/ewdjs/node_modules/myFederator.js Next, add it to the configuration object in your ewd-federator startup file. Use the filename you’ve used, but remove the .js file extension, eg: ewd-federator Reference Guide (Build 0.13) 19 Copyright ©2013-15, M/Gateway Developments Ltd. All Rights Reserved var params = { ... extensionModule: 'myFederator', ... }; Stop and restart ewd-federator using your start-up file. Provided you don’t have any syntax errors in your extension module file, ewd-federator should start successfully. Defining and Handling Events in your Extension Module Essentially an ewd-federator extension module defines a list of named events that are exposed to the main ewd-federator child process module so that it can trigger them when required for you. There are two types of events that you can define: • request: this fires whenever ewd-federator receives an incoming REST request, allowing you to handle and, if you wish, modify ewd-federator’s behaviour when receiving that request • your own named custom response events. You assign a name of your choice to each request that your extension module sends to a back-end server or group destination, and when the response is returned, your corresponding custom event handler fires. Handling the request Event The request event is a built-in event that ewd-federator triggers every time it receives an incoming REST request. Below is a simple example of how to define your own handler for this event within your extension module: var events = require('events'); var federator = new events.EventEmitter(); module.exports = { event: federator, events: [ 'request' ] }; // Event Handler functions function onRequest(request, ewd) { console.log('onRequest: request = ' + JSON.stringify(request, null, 2)); ewd.util.sendRequest(request.destination, request); } // Event Workflow federator.on('request', onRequest); Stop and restart ewd-federator to ensure that this module is loaded (subsequently, ewd-federator should automatically reload your extension module every time you edit it) If you send a REST request into the federator, you should see something like this appear in amongst the console.log traces generated within the ewd-federator terminal window: ewd-federator Reference Guide (Build 0.13) 20 Copyright ©2013-15, M/Gateway Developments Ltd. All Rights Reserved onRequest: request = { "type": "posts/1", "path": "posts/1", "method": "GET", "contentType": "application/json", "nvps": { "rest_url": "http://99.9.9.9:8081/placeholder/posts/1", "rest_path": "posts/1", "rest_method": "GET" }, "post_data": {}, "destination": "placeholder" } Otherwise, you should see the normal response coming back from the remote site. What happened was that the ewd-federator child process triggered your event handler via your workflow function: federator.on('request', onRequest); It knew to invoke this because you’d registered this event in your extension module’s module.exports: module.exports = { event: federator, events: [ 'request' ] }; As a result, your handler function - onRequest() - fired. Your handler function should define two arguments: • request • ewd As you can see from the console.log trace created by your handler function, the request object contains all the information that you need in order to manipulate and/or deal with the incoming request. The ewd object provides you with a number of key objects used by ewd-federator in addition to a number of functions that you’ll want to use within your customisation logic. Notice the second line in your handler function: function onRequest(request, ewd) { console.log('onRequest: request = ' + JSON.stringify(request, null, 2)); ewd.util.sendRequest(request.destination, request); } If you intercept the request event, it becomes your responsibility to handle what happens next, otherwise ewd-federator will just sit waiting. In this case we’re going to forward the request to the remote destination. As we haven’t defined any other event handlers, ewd-federator will return the response from the remote site automatically. To forward a request to a remote site, you use the ewd.util.sendRequest() function. This takes two arguments: ewd-federator Reference Guide (Build 0.13) 21 Copyright ©2013-15, M/Gateway Developments Ltd. All Rights Reserved • the destination name of the site to which you want the request to be sent. The destination name can be a group destination name • the request object that defines the request to be sent. In our example we’re just forwarding the incoming request unchanged to its originally intended destination. However, if we’d wanted, we could have created a completely different request object and sent it to an entirely different destination (including a destination group). Something else we could have done is to have sent our own customised response back to the REST client. ewd.util provides two functions for this: • ewd.util.returnRESTError() • ewd.util.returnRESTResponse() So, for example, you could modify your handler function as follows to simply return your own success response: function onRequest(request, ewd) { console.log('onRequest: request = ' + JSON.stringify(request, null, 2)); var responseObj = { intercepted: 'by me!' }; ewd.util.returnRESTResponse(responseObj, 'application/json'); } The incoming request never got forwarded to its originally intended destination! Alternatively you could return an error response: function onRequest(request, ewd) { console.log('onRequest: request = ' + JSON.stringify(request, null, 2)); var errorObj = { statusCode: 400, restCode: 'BadRequest', message: 'I decided that no requests are allowed' }; ewd.util.returnRESTError(errorObj, 'application/json'); } This time you’ll get an HTTP 400 error returned with an associated JSON error object, and the incoming request will not be forwarded to its originally intended destination. You can therefore use the request event to write your own filters for what incoming requests you want to allow, and send error responses for requests whose object contents indicate that you don’t want them handled. You could also create a log in the database for each incoming request, eg: ewd-federator Reference Guide (Build 0.13) 22 Copyright ©2013-15, M/Gateway Developments Ltd. All Rights Reserved function onRequest(request, ewd) { console.log('onRequest: request = ' + JSON.stringify(request, null, 2)); var logGlo = new ewd.mumps.GlobalNode('log', []); var recNo = logGlo.$('recNo')._increment(); logGlo.$('request').$(recNo)._setDocument(request); } ewd.util.sendRequest(request.destination, request); If you’re familiar with how you access the database from within EWD.js, you’ll recognise that ewd-federator behaves identically. If you’re new to handling GT.M, Cache or GlobalsDB via the globals.js abstraction, read the chapter “JavaScript Access to Mumps Data” in the EWD.js Reference Guide. Manually Sending a Request to a Destination A key part of customising ewd-federator’s behaviour is understanding how to send your own manually-constructed requests to remote servers (or groups of servers). We’ve already seen the basic mechanism in the previous section, ie using the ewd.util.sendRequest() function As previously noted, this function takes two arguments: • the destination name of the site to which you want the request to be sent. The destination name can be a group destination name • the request object that defines the request to be sent. The key part is understanding how to construct a valid request object. The properties of the request object are: • type: This is for you to define. It is simply a string value that you assign to this request (or type of request), so that you can later recognise its corresponding response when it comes back • path: the path part of the URL that is to be sent to the remote server • method: the HTTP method for the request you’re sending • contentType: the MIME type for the response that will be returned by the remote site. application/json is recommended • nvps: an object containing any query string name/value pairs that should be added to the outgoing URL • post_data: if the method is POST or PUT, this object defines the payload of the request See the section above for the example we saw in the log when we intercepted the incoming REST request. If you’re manually sending a request to a group destination, then you can optionally define different name/value pair values to be added to the URLs sent to each site in the group. To do this, instead of using the nvps property, you can define: • nvpsBySite: an object containing any query string name/value pairs that should be added to the outgoing URL, specified by site name, eg: ewd-federator Reference Guide (Build 0.13) 23 Copyright ©2013-15, M/Gateway Developments Ltd. All Rights Reserved nvpsBySite: { reigate: { a: 111, b: 222 }, guildford: { a: 222, b: 3333 }, ...etc } Note: if the destination is an EWD.js server: The path must define the EWD.js module and function name, eg scheduler/parse ewd-federator will automatically digitally sign requests Defining Custom Response Events Custom response event handling hinges around the types that you assign to any requests that you manually send out from within your extension module. For each request that you define and send using the ewd.util.sendRequest() function, you expose the type name that you assigned to it through the events array. For example, let’s go back to that original demo we used at the start of this chapter where we displayed the request in the console.log. It looked like this: onRequest: request = { "type": "posts/1", "path": "posts/1", "method": "GET", "contentType": "application/json", "nvps": { "rest_url": "http://99.9.9.9:8081/placeholder/posts/1", "rest_path": "posts/1", "rest_method": "GET" }, "post_data": {}, "destination": "placeholder" } ewd-federator automatically assigns a type to the incoming REST request - as you can see it makes it the same as the path. We’ll revert the onRequest function logic to forward this request to its intended destination, ie: function onRequest(request, ewd) { console.log('onRequest: request = ' + JSON.stringify(request, null, 2)); ewd.util.sendRequest(request.destination, request); } Now we can add a response event handler to intercept the response for the request type ‘posts/1’. First add the type ‘posts/1’ to the module.exports events list in your extension module: ewd-federator Reference Guide (Build 0.13) 24 Copyright ©2013-15, M/Gateway Developments Ltd. All Rights Reserved module.exports = { event: federator, events: [ 'request', 'posts/1' ] }; Now add the event handler to your workflow at the bottom of your extension module: federator.on('request', onRequest); federator.on('posts/1', onPosts1); And finally add the handler function. I’ve named it onPosts1() in the example above, but of course you can name it anything you like: function onPosts1(responseObj, ewd) { console.log('Got the response: ' + JSON.stringify(responseObj, null, 2)); ewd.util.returnRESTResponse(responseObj.response, 'application/json'); }; Notice that a response handler function takes two arguments: • an object containing the properties that make up the received response • the ewd object that contains the various helper functions you’ll need to use in your handler If you save your changes (your extension module should automatically be reloaded by ewd-federator into any child process that is already using it) and send that original request, you’ll now see the response being intercepted in the console.log, eg: Got the response: { "type": "posts/1", "site": "placeholder", "error": false, "response": { "userId": 1, "id": 1, "title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit", "body": "quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto" }, "contentType": "application/json" } The actual response payload is in response.response. That’s usually all you’ll need, but the other properties can be useful in certain situations. Note that if you intercept a response, it now becomes your responsibility to decide what to do with it. If you don’t do anything, the ewd-federator child process that is handling the response will be left hanging forever. At this point you have three options: ewd-federator Reference Guide (Build 0.13) 25 Copyright ©2013-15, M/Gateway Developments Ltd. All Rights Reserved • return a response to the awaiting REST client by using the ewd.util.returnRESTResponse() function (see earlier). You can use the response object that was passed to your handler function, or you could modify it and/or use a completely different response object entirely • return an error response to the awaiting REST client by using the ewd.util.returnRESTError() function (see earlier) • send one or more new requests to remote servers by using the ewd.util.sendRequest() function (see earlier). You can send requests to any destination or destination group. Make sure you give any message that you send out a type property so you can handle its response with another custom response event handler. By now you’ve probably realised that these simple mechanisms and functions provide you with everything you need to create a “dance” of requests and responses of any complexity across your federation of back-end servers. You simply intercept each response by type and send out new requests with new types according to what you received back. You build out the list of event names that you expose through your extension module’s module.exports, and build out the workflow and associated handler function for each event. At some point(s) in your workflow you’ll reach a point where you’ve accumulated all the information you need for the final response, in which case you’ll invoke the ewd.util.returnRESTResponse() function and send back the final federated response object. Using a ScratchPad Object You may need to accumulate a set of information during a complex “dance” between servers in your federation. The easiest way to do this is instantiate, at the start of your extension module, a globally-scoped empty scratchPad object, eg: var scratchPad = {}; You should clear this down in the first event handler function in your workflow (eg in your onRequest() function), so that every time the child process handles a new request, it starts from a clean slate. You can now access, add to and modify content within this scratchPad object in any or all of your custom event handlers in your workflow. When you reach the end of your workflow in a complex “dance” between federated servers, you can use the contents of your scratchPad object to determine the contents of the federated response that finally gets returned to the awaiting REST client. Note that because all the processing of any one federated “dance” workflow happens in a single child process, there is no risk of your scratchPad object being over-written by anyone else’s processing. You may, of course, want to save some or all of the contents of your scratchPad object to the database, eg as cached information to save time when processing some subsequent request. Response Event Handlers for Destination Groups One important thing to note. If you manually send out a request using ewd.util.sendRequest() to a group destination name, ewd-federator will automatically aggregate the responses as described earlier, and will trigger your custom response event only when the aggregated response has been completely assembled - ie not until the last response has been received from the group of servers. The response object passed into your handler function will be the aggregated response object. ewd-federator Reference Guide (Build 0.13) 26
© Copyright 2024