UNPKG

4.73 kBJavaScriptView Raw
1'use strict';
2
3// Setup of Express in a Node cluster, a convenient starting point for Webapps.
4// Also sets up some call perf metrics collection across the cluster.
5
6const _ = require('underscore');
7const wrap = _.wrap;
8
9const express = require('abacus-express');
10const cluster = require('abacus-cluster');
11const vcapenv = require('abacus-vcapenv');
12const perf = require('abacus-perf');
13const hystrix = require('abacus-hystrix');
14const eureka = require('abacus-eureka');
15const oauth = require('abacus-oauth');
16const cp = require('child_process');
17const commander = require('commander');
18const rc = require('abacus-rc');
19
20// Setup debug log
21const debug = require('abacus-debug')('abacus-webapp');
22const edebug = require('abacus-debug')('e-abacus-webapp');
23
24const defaultSecurity = () => process.env.SECURED === 'true';
25const secured = () => process.env.HEALTHCHECK_SECURED === 'false' ? false : defaultSecurity();
26
27const healthCheckScopes = 'abacus.system.read';
28
29const defaultHealthCheck = (req, res) => {
30 debug(`Entering ${secured ? 'secured' : ''} healthcheck`);
31 const h = perf.healthy();
32 debug('Returning app health %s', h);
33 res.status(h ? 200 : 500).send({
34 healthy: h
35 });
36};
37
38// Configure a clustered Express-based Web app
39//
40// Use like this:
41// const app = webapp();
42// app.listen();
43const webapp = () => {
44 // Determine the app name
45 const appname = vcapenv.appname();
46 debug('Creating app %s', appname);
47
48 // Create the app
49 const app = cluster(express());
50
51 // Configure the app to report its app instance id and instance index
52 app.use(vcapenv.headers());
53
54 let healthCheckMiddlware = defaultHealthCheck;
55
56 /**
57 * Specifies the healthcheck middleware to use for the
58 * healthcheck endpoint.
59 * If no middleware is specified or this method is not called, a
60 * default one will be used.
61 */
62 app.useHealthCheck = (middlware) => {
63 if (middlware) healthCheckMiddlware = middlware;
64 else healthCheckMiddlware = defaultHealthCheck;
65 };
66
67 // Monkey patch the app listen function to register some of our middleware
68 // after the app's middleware
69 app.listen = wrap(app.listen, (listen, opt, cb) => {
70 if (secured())
71 app.use(
72 '/hystrix.stream',
73 oauth.basicStrategy(process.env.API, hystrix.scopes, process.env.JWTKEY, process.env.JWTALGO)
74 );
75 app.use('/hystrix.stream', hystrix.stream());
76
77 if (secured())
78 app.use(
79 '/healthcheck',
80 oauth.basicStrategy(process.env.API, healthCheckScopes, process.env.JWTKEY, process.env.JWTALGO)
81 );
82 app.get('/healthcheck', healthCheckMiddlware);
83
84 // Call the original app listen function
85 debug('Listening');
86 const server = listen.call(app, opt, cb);
87
88 // Optionally register the app instance in a Eureka registry
89 if (cluster.isMaster() && eureka())
90 eureka.register(eureka(), appname, vcapenv.iport() || server.address().port, vcapenv.iaddress(), (err, val) => {
91 if (err) debug('Couldn\'t register app %s in Eureka registry, %o', appname, err);
92 else debug('Registered app %s in Eureka registry', appname);
93 });
94
95 return server;
96 });
97
98 return app;
99};
100
101// Return a clustered basic Express app
102const basic = () => {
103 return cluster(express.basic());
104};
105
106// Let the Express module send messages to the cluster master
107express.on('message', cluster.onMessage);
108
109// Broadcast perf stat messages across the cluster. Only need to do that
110// if we have more than one worker in the cluster.
111if (cluster.size() > 1) {
112 debug('Broadcasting perf stat messages across the cluster');
113 cluster.on('message', perf.onMessage);
114 perf.on('message', cluster.onMessage);
115}
116
117// Set default port and host name, command line has higher priority then
118// the existing env, then rc files
119const conf = () => {
120 process.env.PORT = commander.port || process.env.PORT || 9080;
121 if (commander.host) process.env.HOST = commander.host;
122};
123
124// Command line interface
125const runCLI = () => {
126 // Parse command line options
127 commander
128 .option('-p, --port <port>', 'port number [9080]')
129 .option('-h, --host <hostname>', 'host name [*]')
130 .option('start', 'start the server')
131 .option('stop', 'stop the server')
132 .parse(process.argv);
133
134 // Load env from rc file
135 rc();
136
137 // Start a Webapp
138 if (commander.start) {
139 conf();
140
141 // Run the app CLI
142 const app = require(process.cwd());
143 if (app && app.runCLI) app.runCLI();
144 } else if (commander.stop)
145 // Stop the Webapp
146 cp.exec('pkill -f "node ' + vcapenv.appname() + '.* master"', (err, stdout, stderr) => {
147 if (err) edebug('Stop error %o', err);
148 });
149};
150
151// Export our public functions
152module.exports = webapp;
153module.exports.basic = basic;
154module.exports.runCLI = runCLI;