options => { const configs = getConfigurations(options); const app = createServer(options); return registerServices(app, options, configs); }
...
## HTTPS
If you want to run dyson over https:// you have to provide a self-signed (or authority-signed) certificate into the `options.https
` the same way it's required for NodeJS HTTPS to work:
``` javascript
const fs = require('fs');
dyson.bootstrap({
configDir: `${__dirname}/dummy`,
port: 3001,
https: {
key: fs.readFileSync(`${__dirname}'/certs/sample.key`),
crt: fs.readFileSync(`${__dirname}/certs/sample.crt`)
}
});
...
options => { const app = express(); let server; if(options.https) { server = https.createServer(options.https, app).listen(options.port); } else { server = app.listen(options.port); } app.set('dyson_server', server); return app; }
...
const options = {
configDir: path.join(__dirname, 'services'),
port: 8765
};
const configs = dyson.getConfigurations(options),
appBefore = dyson.createServer(options),
appAfter = dyson.registerServices(appBefore, options, configs);
console.log(`Dyson listening at port ${options.port}`);
```
Dyson configuration can also be installed into any Express server:
...
options => { const rawConfigs = loader.load(options.configDir); return defaults.assignToAll(rawConfigs); }
...
path = require('path');
const options = {
configDir: path.join(__dirname, 'services'),
port: 8765
};
const configs = dyson.getConfigurations(options),
appBefore = dyson.createServer(options),
appAfter = dyson.registerServices(appBefore, options, configs);
console.log(`Dyson listening at port ${options.port}`);
```
Dyson configuration can also be installed into any Express server:
...
(app, options, configs) => { app.set('dyson_options', options); util.setQuiet(options.quiet); app.use(cors({ origin: true, credentials: true })); app.use(rawBody()); app.use(cookieParser()); app.use(favicon(`${__dirname}/favicon.ico`)); const bodyParserOptions = options.bodyParserJsonLimit ? { limit: options.bodyParserJsonLimit } : {}; app.use(bodyParser.json(bodyParserOptions)); const bodyParserUrlOptions = { extended: true }; bodyParserUrlOptions.limit = options.bodyParserUrlencodedLimit ? options.bodyParserUrlencodedLimit : null; app.use(bodyParser.urlencoded(bodyParserUrlOptions)); Object.keys(configs).forEach(method => { configs[method].forEach(config => { if(options.proxy === true && config.proxy !== false) { // Allows each config file to be bypassed without removing it util.log('Proxying', method.toUpperCase(), 'service at', config.path); } else { const middlewares = [ setConfig(config), requireParameter, config.status, config.callback, delay(config.delay), config.render ]; util.log('Registering', method.toUpperCase(), 'service at', config.path); app[method].apply(app, [config.path].concat(middlewares)); if(method !== 'options') { app.options(config.path, cors({ origin: true, credentials: true })); } } }); }); if(options.proxy) { app.all('*', delay(options.proxyDelay), proxy.middleware); } else { !util.isTest() && app.all('*', (req, res) => { console.error(`404 NOT FOUND: ${req.url}`); res.writeHead(404); res.end(); }); } return app; }
...
const options = {
configDir: path.join(__dirname, 'services'),
port: 8765
};
const configs = dyson.getConfigurations(options),
appBefore = dyson.createServer(options),
appAfter = dyson.registerServices(appBefore, options, configs);
console.log(`Dyson listening at port ${options.port}`);
```
Dyson configuration can also be installed into any Express server:
``` javascript
...
options => { if(options.length) { const [ dir, port ] = options; const opts = _.defaults(readOptions(dir), { port: port || 3000, configDir: dir, proxy: false, multiRequest: ',', quiet: false }); opts.configDir = path.resolve(opts.configDir); fs.stat(opts.configDir, (error, stats) => { if(!error && stats.isDirectory()) { dyson.bootstrap(opts); console.info(`Dyson listening at port: ${opts.port}`); } else { console.error(`Directory does not exist: ${opts.configDir}`); } }); } else { showHelp(); } }
...
#!/usr/bin/env node
const cli = require('../lib/cli');
cli.execute(process.argv.slice(2));
...
(configs, method) => { configs = _.isArray(configs) ? configs : [configs]; return _.compact(configs.map(config => { if(!config || !config.path) { return; } // Assign method specific defaults config = _.defaults(config, defaults[method]); // Bind each method to the config itself return _.bindAll(config); })); }
...
it('should apply defaults (and not overwrite existing values)', () => {
const config = {
path: '/test',
template: {}
};
defaults.assign(config, 'get');
config.should.have.property('cache').and.be.type('boolean');
config.should.have.property('size');
config.should.have.property('collection').and.be.type('boolean');
config.should.have.property('callback').and.be.type('function');
config.should.have.property('path').and.equal(config.path);
...
rawConfigs => { const configs = {}; methods.forEach(method => { configs[method] = assign(rawConfigs[method], method); }); return configs; }
...
const app = createServer(options);
return registerServices(app, options, configs);
};
const getConfigurations = options => {
const rawConfigs = loader.load(options.configDir);
return defaults.assignToAll(rawConfigs);
};
const createServer = options => {
const app = express();
let server;
if(options.https) {
...
callback = function (req, res, next) { res.body = req.body; next(); }
...
this._counter++;
},
_counter: 0
};
defaults.assign(config, 'get');
config.callback().render();
config._counter.should.equal(2);
});
});
});
...
configDir => { const rawConfigs = requireDir(module, path.resolve(configDir)), configs = {}; methods.forEach(method => { configs[method] = _.flattenDeep(findRecursive(rawConfigs, method)); }); return configs; }
...
const bootstrap = options => {
const configs = getConfigurations(options);
const app = createServer(options);
return registerServices(app, options, configs);
};
const getConfigurations = options => {
const rawConfigs = loader.load(options.configDir);
return defaults.assignToAll(rawConfigs);
};
const createServer = options => {
const app = express();
let server;
...
(req, path) => {
const options = req.app.get('dyson_options'),
host = req.headers.host.split(':'),
hostname = host[0],
port = host[1],
delimiter = options.multiRequest,
promises = [],
range = isMultiRequest(path, options);
range.split(delimiter).forEach((id, index, list) => {
const d = when.defer(),
url = path.replace(list, id);
let data = '';
promises.push(d.promise);
http.get({ hostname, port, path: url }, res => {
res.on('data', chunk => {
data += chunk;
});
res.on('end', () => {
d.resolve(JSON.parse(data));
});
}).on('error', error => {
/* eslint-disable no-console */
console.error(error.message);
});
});
return promises;
}
...
}).then(data => {
res.body = cache[path] = data;
util.log('Resolving response for', req.method, path);
next();
});
} else {
when.all(multiRequest.doMultiRequest(req, path)).then(data => {
res.body = cache[path] = data;
util.log('Resolving response for:', req.method, path, '(multiRequest)');
next();
});
}
} else {
...
(path, options) => { const delimiter = options.multiRequest; if(!delimiter) { return false; } return path.split('/').find(fragment => fragment.includes(delimiter)); }
...
path = req.url,
exposeRequest = service.exposeRequest || options.exposeRequest && service.exposeRequest !== false;
const templateArgs = exposeRequest ? [req] : [req.params, req.query, req.body, req.cookies, req.headers],
containerArgs = exposeRequest ? [req] : [req.params, req.query];
if(!(service.cache && cache[path])) {
if(!multiRequest.isMultiRequest(path, options)) {
const isCollection = _.isFunction(service.collection) ? service.collection.apply(service, templateArgs) : service.collection,
template = _.isFunction(service.template) ? service.template.apply(service, templateArgs) : service.template;
let promise;
if(!isCollection) {
promise = setValues(template, templateArgs);
} else {
...
(req, res) => { const options = req.app.get('dyson_options'), proxyHost = options.proxyHost, proxyPort = options.proxyPort, proxyURI = `${proxyHost}${proxyPort ? `:${proxyPort}` : ''}${req.url}`; let readStream; util.log('Proxying', req.url, 'to', proxyURI); if(req._body) { readStream = new Stream.Readable(); readStream._read = function() { this.push(req.rawBody); this.push(null); }; } else { readStream = req; } readStream.pipe(request({ method: req.method, url: proxyURI, headers: _.omit(req.headers, ['host']) }, error => { if(error) { console.error(`500 INTERNAL SERVER ERROR: ${proxyURI}`); console.error(error); res.writeHead(500); res.end(); } })).pipe(res); }
n/a
(req, res, next) => { const service = res.locals.config, options = res.app.get('dyson_options'), path = req.url, exposeRequest = service.exposeRequest || options.exposeRequest && service.exposeRequest !== false; const templateArgs = exposeRequest ? [req] : [req.params, req.query, req.body, req.cookies, req.headers], containerArgs = exposeRequest ? [req] : [req.params, req.query]; if(!(service.cache && cache[path])) { if(!multiRequest.isMultiRequest(path, options)) { const isCollection = _.isFunction(service.collection) ? service.collection.apply(service, templateArgs) : service.collection , template = _.isFunction(service.template) ? service.template.apply(service, templateArgs) : service.template; let promise; if(!isCollection) { promise = setValues(template, templateArgs); } else { const size = _.isFunction(service.size) ? service.size.apply(service, templateArgs) : service.size; promise = when.map(_.times(parseInt(size, 10)), () => setValues(template, templateArgs)); } promise.then(data => { return !service.container ? data : setValues(_.result(service, 'container'), containerArgs.concat([data]), service); }).then(data => { res.body = cache[path] = data; util.log('Resolving response for', req.method, path); next(); }); } else { when.all(multiRequest.doMultiRequest(req, path)).then(data => { res.body = cache[path] = data; util.log('Resolving response for:', req.method, path, '(multiRequest)'); next(); }); } } else { util.log('Resolving response for', req.method, path, '(cached)'); res.body = cache[path]; next(); } }
...
}
};
});
it('should expose request to template', () => {
configDefaults.generate(req, res, next);
sinon.assert.calledWithExactly(res.locals.config.template, req);
});
it('should expose request to container', () => {
...
(req, res) => { res.send(res.body); }
...
this._counter++;
},
_counter: 0
};
defaults.assign(config, 'get');
config.callback().render();
config._counter.should.equal(2);
});
});
});
...
(template = null, params, scope) => { return when.promise(resolve => { if(!template) return resolve(null); if(typeof template === 'string') return resolve(template); const promises = [], obj = _.isArray(template) ? [] : Object.create(template); _.forEach(template, (value, key) => { if(template.hasOwnProperty(key)) { obj[key] = _.isFunction(value) ? value.apply(scope || obj, params) : _.isPlainObject(value) ? setValues(value, params, obj ) : value; if(when.isPromiseLike(obj[key])) { promises.push(obj[key]); obj[key].then(function(key, value) { obj[key] = value; }.bind(obj, key)); } } }); when.all(promises).then(() => resolve(obj)); }); }
...
describe('dyson.response', () => {
describe('.setValues', () => {
it('should return a promise', () => {
const actual = configDefaults.setValues({});
actual.should.have.property('then');
actual.then.should.be.a.Function;
});
it('should render data based on template', done => {
...
() => { return process.env.NODE_ENV === 'test'; }
...
if(options.proxy) {
app.all('*', delay(options.proxyDelay), proxy.middleware);
} else {
!util.isTest() && app.all('*', (req, res) => {
console.error(`404 NOT FOUND: ${req.url}`);
res.writeHead(404);
res.end();
});
}
return app;
...
log = function () { if(!isTest() && !quiet) { console.log.apply(console, arguments); } }
...
port: 8765
};
const configs = dyson.getConfigurations(options),
appBefore = dyson.createServer(options),
appAfter = dyson.registerServices(appBefore, options, configs);
console.log(`Dyson listening at port ${options.port}`);
```
Dyson configuration can also be installed into any Express server:
``` javascript
const express = require('express'),
dyson = require('./lib/dyson'),
...
isQuiet => { quiet = typeof isQuiet === 'boolean' ? isQuiet : true; }
...
next();
};
// Register middleware to Express as service for each config (as in: `app.get(config.path, config.callback);`)
const registerServices = (app, options, configs) => {
app.set('dyson_options', options);
util.setQuiet(options.quiet);
app.use(cors({ origin: true, credentials: true }));
app.use(rawBody());
app.use(cookieParser());
app.use(favicon(`${__dirname}/favicon.ico`));
const bodyParserOptions = options.bodyParserJsonLimit ? { limit: options.bodyParserJsonLimit } : {};
...