-
Notifications
You must be signed in to change notification settings - Fork 2
/
install.js
193 lines (171 loc) · 6.72 KB
/
install.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
'use strict';
var path = require('path');
var _ = require('lodash');
var assert = require('assert-plus');
var vasync = require('vasync');
var verror = require('verror');
var compose = require('restify').helpers.compose;
var bunyan = require('bunyan');
// local globals
var LOG;
/**
* Install the enroute routes into the restify server.
*
* @param {object} opts Options object.
* @param {object} opts.enroute The parsed enroute configuration.
* @param {object} opts.log an optional logger
* @param {boolean} [opts.hotReload] Whether to hot reload the routes
* @param {object} opts.server The restify server.
* @param {string} opts.basePath The basepath to resolve source files.
* @param {function} cb The callback.
* @returns {undefined}
*/
function install(opts, cb) {
assert.object(opts, 'opts');
assert.optionalObject(opts.log, 'opts.log');
assert.object(opts.enroute, 'opts.enroute');
assert.object(opts.server, 'opts.server');
assert.string(opts.basePath, 'opts.basePath');
assert.optionalBool(opts.hotReload, 'opts.hotReload');
var log;
if (opts.log) {
log = opts.log.child({ component : 'enroute' });
} else {
// only create default logger if one wasn't passed in.
if (!LOG) {
LOG = bunyan.createLogger({ name: 'enroute' });
}
log = LOG;
}
vasync.pipeline({arg: {}, funcs: [
// Read the routes from disk and parse them as functions
function getRoutes(ctx, cb1) {
ctx.routes = [];
var barrier = vasync.barrier();
barrier.on('drain', function () {
return cb1();
});
if (opts.hotReload) {
log.info({ basePath: opts.basePath },
'hot reloading of routes is enabled for base dir');
}
// go through each of the route names
_.forEach(opts.enroute.routes, function (methods, routeName) {
// go through each of the HTTP methods
_.forEach(methods, function (src, method) {
barrier.start(routeName + method);
// resolve to the correct file
var sourceFile = path.resolve(opts.basePath, src.source);
var route;
try {
var func = (opts.hotReload) ?
// restify middleware wrapper for hot reload
function enrouteHotReloadProxy(req, res, next) {
return reloadProxy({
basePath: opts.basePath,
method: method,
req: req,
res: res,
routeName: routeName,
sourceFile: sourceFile
}, next);
} : require(sourceFile);
route = {
name: routeName,
method: method,
func: func
};
} catch (e) {
return cb1(new verror.VError({
name: 'EnrouteRequireError',
cause: e,
info: {
file: sourceFile,
route: routeName,
method: method
}
}, 'failed to require file, possible syntax error'));
}
// if HTTP method is 'delete', since restify uses 'del'
// instead of 'delete', change it to 'del'
if (route.method === 'delete') {
route.method = 'del';
}
// if HTTP method is 'options', since restify uses
// 'opts' instead of 'options', change it to 'opts'
if (route.method === 'options') {
route.method = 'opts';
}
ctx.routes.push(route);
barrier.done(routeName + method);
return null;
});
});
},
function installRoutes(ctx, cb1) {
_.forEach(ctx.routes, function (route) {
var routePath = typeof route.name === 'string' &&
route.name[0] === '/'
? route.name
: '/' + route.name;
opts.server[route.method](routePath, route.func);
});
return cb1();
}
]}, function (err, res) {
return cb(err);
});
}
/**
* using the restify handler composer, create a middleware on the fly that
* re-requires the file on every load executes it.
* @private
* @function reloadProxy
* @param {Object} opts an options object
* @param {String} opts.basePath The basepath to resolve source files.
* @param {String} opts.method http verb
* @param {Object} opts.log the enroute logger
* @param {Object} opts.req the request object
* @param {Object} opts.res the response object
* @param {String} opts.routeName the name of the restify route
* @param {String} opts.sourceFile the response object
* @param {Function} cb callback fn
* @returns {undefined}
*/
function reloadProxy(opts, cb) {
assert.object(opts, 'opts');
assert.string(opts.basePath, 'opts.basePath');
assert.string(opts.method, 'opts.method');
assert.object(opts.req, 'opts.req');
assert.object(opts.res, 'opts.res');
assert.string(opts.routeName, 'opts.routeName');
assert.string(opts.sourceFile, 'opts.sourceFile');
assert.func(cb, 'cb');
// clear require cache for code loaded from a specific base dir
Object.keys(require.cache).forEach(function (cacheKey) {
if (cacheKey.indexOf(opts.basePath) !== -1) {
delete require.cache[cacheKey];
}
});
var handlers;
try {
handlers = require(opts.sourceFile);
} catch (e) {
var err = new verror.VError({
name: 'EnrouteRequireError',
cause: e,
info: {
file: opts.sourceFile,
route: opts.routeName,
method: opts.method
}
}, 'failed to require file, possible syntax error');
// now that the chain has failed, send back the require error.
return cb(err);
}
// if no require error, execute the handler chain. any errors that occur at
// runtime should be a runtime exception.
var handlerChain = compose(handlers);
return handlerChain(opts.req, opts.res, cb);
}
module.exports = install;