813 lines
22 KiB
JavaScript
813 lines
22 KiB
JavaScript
/*!
|
|
* Module dependencies.
|
|
*/
|
|
|
|
var DataSource = require('loopback-datasource-juggler').DataSource
|
|
, ModelBuilder = require('loopback-datasource-juggler').ModelBuilder
|
|
, assert = require('assert')
|
|
, fs = require('fs')
|
|
, RemoteObjects = require('strong-remoting')
|
|
, swagger = require('strong-remoting/ext/swagger')
|
|
, stringUtils = require('underscore.string')
|
|
, path = require('path');
|
|
|
|
/**
|
|
* The `App` object represents a Loopback application.
|
|
*
|
|
* The App object extends [Express](http://expressjs.com/api.html#express) and
|
|
* supports
|
|
* [Express / Connect middleware](http://expressjs.com/api.html#middleware). See
|
|
* [Express documentation](http://expressjs.com/api.html) for details.
|
|
*
|
|
* ```js
|
|
* var loopback = require('loopback');
|
|
* var app = loopback();
|
|
*
|
|
* app.get('/', function(req, res){
|
|
* res.send('hello world');
|
|
* });
|
|
*
|
|
* app.listen(3000);
|
|
* ```
|
|
*
|
|
* @class LoopBackApplication
|
|
* @header var app = loopback()
|
|
*/
|
|
function App() {
|
|
// this is a dummy placeholder for jsdox
|
|
}
|
|
|
|
/*!
|
|
* Export the app prototype.
|
|
*/
|
|
|
|
var app = exports = module.exports = {};
|
|
|
|
/**
|
|
* Lazily load a set of [remote objects](http://apidocs.strongloop.com/strong-remoting/#remoteobjectsoptions).
|
|
*
|
|
* **NOTE:** Calling `app.remotes()` multiple times will only ever return a
|
|
* single set of remote objects.
|
|
* @returns {RemoteObjects}
|
|
*/
|
|
|
|
app.remotes = function () {
|
|
if(this._remotes) {
|
|
return this._remotes;
|
|
} else {
|
|
return (this._remotes = RemoteObjects.create());
|
|
}
|
|
}
|
|
|
|
/*!
|
|
* Remove a route by reference.
|
|
*/
|
|
|
|
app.disuse = function (route) {
|
|
if(this.stack) {
|
|
for (var i = 0; i < this.stack.length; i++) {
|
|
if(this.stack[i].route === route) {
|
|
this.stack.splice(i, 1);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Define and attach a model to the app. The `Model` will be available on the
|
|
* `app.models` object.
|
|
*
|
|
* ```js
|
|
* var Widget = app.model('Widget', {dataSource: 'db'});
|
|
* Widget.create({name: 'pencil'});
|
|
* app.models.Widget.find(function(err, widgets) {
|
|
* console.log(widgets[0]); // => {name: 'pencil'}
|
|
* });
|
|
* ```
|
|
*
|
|
* @param {String} modelName The name of the model to define
|
|
* @options {Object} config The model's configuration
|
|
* @property {String} dataSource The `DataSource` to attach the model to
|
|
* @property {Object} [options] an object containing `Model` options
|
|
* @property {Object} [properties] object defining the `Model` properties in [LoopBack Definition Language](http://docs.strongloop.com/loopback-datasource-juggler/#loopback-definition-language)
|
|
* @end
|
|
* @returns {ModelConstructor} the model class
|
|
*/
|
|
|
|
app.model = function (Model, config) {
|
|
if(arguments.length === 1) {
|
|
assert(typeof Model === 'function', 'app.model(Model) => Model must be a function / constructor');
|
|
assert(Model.pluralModelName, 'Model must have a "pluralModelName" property');
|
|
this.remotes().exports[Model.pluralModelName] = Model;
|
|
this.models().push(Model);
|
|
Model.shared = true;
|
|
Model.app = this;
|
|
Model.emit('attached', this);
|
|
return;
|
|
}
|
|
var modelName = Model;
|
|
config = config || {};
|
|
assert(typeof modelName === 'string', 'app.model(name, config) => "name" name must be a string');
|
|
|
|
Model =
|
|
this.models[modelName] =
|
|
this.models[classify(modelName)] =
|
|
this.models[camelize(modelName)] = modelFromConfig(modelName, config, this);
|
|
|
|
if(config.public !== false) {
|
|
this.model(Model);
|
|
}
|
|
|
|
return Model;
|
|
}
|
|
|
|
/**
|
|
* Get the models exported by the app. Only models defined using `app.model()`
|
|
* will show up in this list.
|
|
*
|
|
* There are two ways how to access models.
|
|
*
|
|
* **1. A list of all models**
|
|
*
|
|
* Call `app.models()` to get a list of all models.
|
|
*
|
|
* ```js
|
|
* var models = app.models();
|
|
*
|
|
* models.forEach(function (Model) {
|
|
* console.log(Model.modelName); // color
|
|
* });
|
|
* ```
|
|
*
|
|
* **2. By model name**
|
|
*
|
|
* `app.model` has properties for all defined models.
|
|
*
|
|
* In the following example the `Product` and `CustomerReceipt` models are
|
|
* accessed using the `models` object.
|
|
*
|
|
* ```js
|
|
* var loopback = require('loopback');
|
|
* var app = loopback();
|
|
* app.boot({
|
|
* dataSources: {
|
|
* db: {connector: 'memory'}
|
|
* }
|
|
* });
|
|
*
|
|
* app.model('product', {dataSource: 'db'});
|
|
* app.model('customer-receipt', {dataSource: 'db'});
|
|
*
|
|
* // available based on the given name
|
|
* var Product = app.models.Product;
|
|
*
|
|
* // also available as camelCase
|
|
* var product = app.models.product;
|
|
*
|
|
* // multi-word models are avaiable as pascal cased
|
|
* var CustomerReceipt = app.models.CustomerReceipt;
|
|
*
|
|
* // also available as camelCase
|
|
* var customerReceipt = app.models.customerReceipt;
|
|
* ```
|
|
*
|
|
* @returns {Array} a list of model classes
|
|
*/
|
|
|
|
app.models = function () {
|
|
return this._models || (this._models = []);
|
|
}
|
|
|
|
/**
|
|
* Define a DataSource.
|
|
*
|
|
* @param {String} name The data source name
|
|
* @param {Object} config The data source config
|
|
*/
|
|
|
|
app.dataSource = function (name, config) {
|
|
this.dataSources[name] =
|
|
this.dataSources[classify(name)] =
|
|
this.dataSources[camelize(name)] = dataSourcesFromConfig(config);
|
|
}
|
|
|
|
/**
|
|
* Get all remote objects.
|
|
*/
|
|
|
|
app.remoteObjects = function () {
|
|
var result = {};
|
|
var models = this.models();
|
|
|
|
// add in models
|
|
models.forEach(function (ModelCtor) {
|
|
// only add shared models
|
|
if(ModelCtor.shared && typeof ModelCtor.sharedCtor === 'function') {
|
|
result[ModelCtor.pluralModelName] = ModelCtor;
|
|
}
|
|
});
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Get the apps set of remote objects.
|
|
*/
|
|
|
|
app.remotes = function () {
|
|
return this._remotes || (this._remotes = RemoteObjects.create());
|
|
}
|
|
|
|
/**
|
|
* Enable swagger REST API documentation.
|
|
*
|
|
* > Note: This method is deprecated, use the extension
|
|
* [loopback-explorer](http://npmjs.org/package/loopback-explorer) instead.
|
|
*
|
|
* **Options**
|
|
*
|
|
* - `basePath` The basepath for your API - eg. 'http://localhost:3000'.
|
|
*
|
|
* **Example**
|
|
*
|
|
* ```js
|
|
* // enable docs
|
|
* app.docs({basePath: 'http://localhost:3000'});
|
|
* ```
|
|
*
|
|
* Run your app then navigate to
|
|
* [the API explorer](http://petstore.swagger.wordnik.com/).
|
|
* Enter your API basepath to view your generated docs.
|
|
*
|
|
* @deprecated
|
|
*/
|
|
|
|
app.docs = function (options) {
|
|
var remotes = this.remotes();
|
|
swagger(remotes, options);
|
|
}
|
|
|
|
/*!
|
|
* Get a handler of the specified type from the handler cache.
|
|
*/
|
|
|
|
app.handler = function (type) {
|
|
var handlers = this._handlers || (this._handlers = {});
|
|
if(handlers[type]) {
|
|
return handlers[type];
|
|
}
|
|
|
|
var remotes = this.remotes();
|
|
var handler = this._handlers[type] = remotes.handler(type);
|
|
return handler;
|
|
}
|
|
|
|
/**
|
|
* An object to store dataSource instances.
|
|
*/
|
|
|
|
app.dataSources = app.datasources = {};
|
|
|
|
/**
|
|
* Enable app wide authentication.
|
|
*/
|
|
|
|
app.enableAuth = function() {
|
|
var remotes = this.remotes();
|
|
|
|
remotes.before('**', function(ctx, next, method) {
|
|
var req = ctx.req;
|
|
var Model = method.ctor;
|
|
var modelInstance = ctx.instance;
|
|
var modelId = modelInstance && modelInstance.id || req.param('id');
|
|
|
|
if(Model.checkAccess) {
|
|
Model.checkAccess(
|
|
req.accessToken,
|
|
modelId,
|
|
method.name,
|
|
function(err, allowed) {
|
|
if(err) {
|
|
console.log(err);
|
|
next(err);
|
|
} else if(allowed) {
|
|
next();
|
|
} else {
|
|
var e = new Error('Access Denied');
|
|
e.statusCode = 401;
|
|
next(e);
|
|
}
|
|
}
|
|
);
|
|
} else {
|
|
next();
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Initialize an application from an options object or a set of JSON and JavaScript files.
|
|
*
|
|
* **What happens during an app _boot_?**
|
|
*
|
|
* 1. **DataSources** are created from an `options.dataSources` object or `datasources.json` in the current directory
|
|
* 2. **Models** are created from an `options.models` object or `models.json` in the current directory
|
|
* 3. Any JavaScript files in the `./models` directory are loaded with `require()`.
|
|
* 4. Any JavaScript files in the `./boot` directory are loaded with `require()`.
|
|
*
|
|
* **Options**
|
|
*
|
|
* - `cwd` - _optional_ - the directory to use when loading JSON and JavaScript files
|
|
* - `models` - _optional_ - an object containing `Model` definitions
|
|
* - `dataSources` - _optional_ - an object containing `DataSource` definitions
|
|
*
|
|
* > **NOTE:** mixing `app.boot()` and `app.model(name, config)` in multiple
|
|
* > files may result
|
|
* > in models being **undefined** due to race conditions. To avoid this when
|
|
* > using `app.boot()`
|
|
* > make sure all models are passed as part of the `models` definition.
|
|
*
|
|
* <a name="model-definition"></a>
|
|
* **Model Definitions**
|
|
*
|
|
* The following is an example of an object containing two `Model` definitions: "location" and "inventory".
|
|
*
|
|
* ```js
|
|
* {
|
|
* "dealership": {
|
|
* // a reference, by name, to a dataSource definition
|
|
* "dataSource": "my-db",
|
|
* // the options passed to Model.extend(name, properties, options)
|
|
* "options": {
|
|
* "relations": {
|
|
* "cars": {
|
|
* "type": "hasMany",
|
|
* "model": "Car",
|
|
* "foreignKey": "dealerId"
|
|
* }
|
|
* }
|
|
* },
|
|
* // the properties passed to Model.extend(name, properties, options)
|
|
* "properties": {
|
|
* "id": {"id": true},
|
|
* "name": "String",
|
|
* "zip": "Number",
|
|
* "address": "String"
|
|
* }
|
|
* },
|
|
* "car": {
|
|
* "dataSource": "my-db"
|
|
* "properties": {
|
|
* "id": {
|
|
* "type": "String",
|
|
* "required": true,
|
|
* "id": true
|
|
* },
|
|
* "make": {
|
|
* "type": "String",
|
|
* "required": true
|
|
* },
|
|
* "model": {
|
|
* "type": "String",
|
|
* "required": true
|
|
* }
|
|
* }
|
|
* }
|
|
* }
|
|
* ```
|
|
*
|
|
* **Model definition properties**
|
|
*
|
|
* - `dataSource` - **required** - a string containing the name of the data source definition to attach the `Model` to
|
|
* - `options` - _optional_ - an object containing `Model` options
|
|
* - `properties` _optional_ - an object defining the `Model` properties in [LoopBack Definition Language](http://docs.strongloop.com/loopback-datasource-juggler/#loopback-definition-language)
|
|
*
|
|
* **DataSource definition properties**
|
|
*
|
|
* - `connector` - **required** - the name of the [connector](#working-with-data-sources-and-connectors)
|
|
*
|
|
* @header app.boot([options])
|
|
* @throws {Error} If config is not valid
|
|
* @throws {Error} If boot fails
|
|
*/
|
|
|
|
app.boot = function(options) {
|
|
options = options || {};
|
|
|
|
if(typeof options === 'string') {
|
|
options = {appRootDir: options};
|
|
}
|
|
var app = this;
|
|
var appRootDir = options.appRootDir = options.appRootDir || process.cwd();
|
|
var ctx = {};
|
|
var appConfig = options.app;
|
|
var modelConfig = options.models;
|
|
var dataSourceConfig = options.dataSources;
|
|
|
|
if(!appConfig) {
|
|
appConfig = tryReadConfig(appRootDir, 'app') || {};
|
|
}
|
|
if(!modelConfig) {
|
|
modelConfig = tryReadConfig(appRootDir, 'models') || {};
|
|
}
|
|
if(!dataSourceConfig) {
|
|
dataSourceConfig = tryReadConfig(appRootDir, 'datasources') || {};
|
|
}
|
|
|
|
assertIsValidConfig('app', appConfig);
|
|
assertIsValidConfig('model', modelConfig);
|
|
assertIsValidConfig('data source', dataSourceConfig);
|
|
|
|
appConfig.host =
|
|
process.env.npm_config_host ||
|
|
process.env.OPENSHIFT_SLS_IP ||
|
|
process.env.OPENSHIFT_NODEJS_IP ||
|
|
process.env.HOST ||
|
|
appConfig.host ||
|
|
process.env.npm_package_config_host ||
|
|
app.get('host');
|
|
|
|
appConfig.port =
|
|
process.env.npm_config_port ||
|
|
process.env.OPENSHIFT_SLS_PORT ||
|
|
process.env.OPENSHIFT_NODEJS_PORT ||
|
|
process.env.PORT ||
|
|
appConfig.port ||
|
|
process.env.npm_package_config_port ||
|
|
app.get('port') ||
|
|
3000;
|
|
|
|
appConfig.restApiRoot =
|
|
appConfig.restApiRoot ||
|
|
app.get('restApiRoot') ||
|
|
'/api';
|
|
|
|
if(appConfig.host !== undefined) {
|
|
assert(typeof appConfig.host === 'string', 'app.host must be a string');
|
|
app.set('host', appConfig.host);
|
|
}
|
|
|
|
if(appConfig.port !== undefined) {
|
|
var portType = typeof appConfig.port;
|
|
assert(portType === 'string' || portType === 'number', 'app.port must be a string or number');
|
|
app.set('port', appConfig.port);
|
|
}
|
|
|
|
assert(appConfig.restApiRoot !== undefined, 'app.restBasePath is required');
|
|
assert(typeof appConfig.restApiRoot === 'string', 'app.restBasePath must be a string');
|
|
assert(/^\//.test(appConfig.restApiRoot), 'app.restBasePath must start with "/"');
|
|
app.set('restApiRoot', appConfig.restBasePath);
|
|
|
|
for(var configKey in appConfig) {
|
|
var cur = app.get(configKey);
|
|
if(cur === undefined || cur === null) {
|
|
app.set(configKey, appConfig[configKey]);
|
|
}
|
|
}
|
|
|
|
// instantiate data sources
|
|
forEachKeyedObject(dataSourceConfig, function(key, obj) {
|
|
app.dataSource(key, obj);
|
|
});
|
|
|
|
// instantiate models
|
|
forEachKeyedObject(modelConfig, function(key, obj) {
|
|
app.model(key, obj);
|
|
});
|
|
|
|
// try to attach models to dataSources by type
|
|
try {
|
|
require('./loopback').autoAttach();
|
|
} catch(e) {
|
|
if(e.name === 'AssertionError') {
|
|
console.warn(e);
|
|
} else {
|
|
throw e;
|
|
}
|
|
}
|
|
|
|
// disable token requirement for swagger, if available
|
|
var swagger = app.remotes().exports.swagger;
|
|
var requireTokenForSwagger = appConfig.swagger
|
|
&& appConfig.swagger.requireToken;
|
|
if(swagger) {
|
|
swagger.requireToken = requireTokenForSwagger || false;
|
|
}
|
|
|
|
// require directories
|
|
var requiredModels = requireDir(path.join(appRootDir, 'models'));
|
|
var requiredBootScripts = requireDir(path.join(appRootDir, 'boot'));
|
|
}
|
|
|
|
function assertIsValidConfig(name, config) {
|
|
if(config) {
|
|
assert(typeof config === 'object', name + ' config must be a valid JSON object');
|
|
}
|
|
}
|
|
|
|
function forEachKeyedObject(obj, fn) {
|
|
if(typeof obj !== 'object') return;
|
|
|
|
Object.keys(obj).forEach(function(key) {
|
|
fn(key, obj[key]);
|
|
});
|
|
}
|
|
|
|
function classify(str) {
|
|
return stringUtils.classify(str);
|
|
}
|
|
|
|
function camelize(str) {
|
|
return stringUtils.camelize(str);
|
|
}
|
|
|
|
function dataSourcesFromConfig(config) {
|
|
var connectorPath;
|
|
|
|
assert(typeof config === 'object',
|
|
'cannont create data source without config object');
|
|
|
|
if(typeof config.connector === 'string') {
|
|
connectorPath = path.join(__dirname, 'connectors', config.connector+'.js');
|
|
|
|
if(fs.existsSync(connectorPath)) {
|
|
config.connector = require(connectorPath);
|
|
}
|
|
}
|
|
|
|
return require('./loopback').createDataSource(config);
|
|
}
|
|
|
|
function modelFromConfig(name, config, app) {
|
|
var ModelCtor = require('./loopback').createModel(name, config.properties, config.options);
|
|
var dataSource = app.dataSources[config.dataSource];
|
|
|
|
assert(isDataSource(dataSource), name + ' is referencing a dataSource that does not exist: "'+ config.dataSource +'"');
|
|
|
|
ModelCtor.attachTo(dataSource);
|
|
return ModelCtor;
|
|
}
|
|
|
|
function requireDir(dir, basenames) {
|
|
assert(dir, 'cannot require directory contents without directory name');
|
|
|
|
var requires = {};
|
|
|
|
if (arguments.length === 2) {
|
|
// if basenames argument is passed, explicitly include those files
|
|
basenames.forEach(function (basename) {
|
|
var filepath = Path.resolve(Path.join(dir, basename));
|
|
requires[basename] = tryRequire(filepath);
|
|
});
|
|
} else if (arguments.length === 1) {
|
|
// if basenames arguments isn't passed, require all javascript
|
|
// files (except for those prefixed with _) and all directories
|
|
|
|
var files = tryReadDir(dir);
|
|
|
|
// sort files in lowercase alpha for linux
|
|
files.sort(function (a,b) {
|
|
a = a.toLowerCase();
|
|
b = b.toLowerCase();
|
|
|
|
if (a < b) {
|
|
return -1;
|
|
} else if (b < a) {
|
|
return 1;
|
|
} else {
|
|
return 0;
|
|
}
|
|
});
|
|
|
|
files.forEach(function (filename) {
|
|
// ignore index.js and files prefixed with underscore
|
|
if ((filename === 'index.js') || (filename[0] === '_')) { return; }
|
|
|
|
var filepath = path.resolve(path.join(dir, filename));
|
|
var ext = path.extname(filename);
|
|
var stats = fs.statSync(filepath);
|
|
|
|
// only require files supported by require.extensions (.txt .md etc.)
|
|
if (stats.isFile() && !(ext in require.extensions)) { return; }
|
|
|
|
var basename = path.basename(filename, ext);
|
|
|
|
requires[basename] = tryRequire(filepath);
|
|
});
|
|
|
|
}
|
|
|
|
return requires;
|
|
};
|
|
|
|
function tryRequire(modulePath) {
|
|
try {
|
|
return require.apply(this, arguments);
|
|
} catch(e) {
|
|
console.error('failed to require "%s"', modulePath);
|
|
throw e;
|
|
}
|
|
}
|
|
|
|
function tryReadDir() {
|
|
try {
|
|
return fs.readdirSync.apply(fs, arguments);
|
|
} catch(e) {
|
|
return [];
|
|
}
|
|
}
|
|
|
|
function isModelCtor(obj) {
|
|
return typeof obj === 'function' && obj.modelName && obj.name === 'ModelCtor';
|
|
}
|
|
|
|
function isDataSource(obj) {
|
|
return obj instanceof DataSource;
|
|
}
|
|
|
|
function tryReadConfig(cwd, fileName) {
|
|
try {
|
|
return require(path.join(cwd, fileName + '.json'));
|
|
} catch(e) {
|
|
if(e.code !== "MODULE_NOT_FOUND") {
|
|
throw e;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Install all express middleware required by LoopBack.
|
|
*
|
|
* It is possible to inject your own middleware by listening on one of the
|
|
* following events:
|
|
*
|
|
* - `middleware:preprocessors` is emitted after all other
|
|
* request-preprocessing middleware was installed, but before any
|
|
* request-handling middleware is configured.
|
|
*
|
|
* Usage:
|
|
* ```js
|
|
* app.once('middleware:preprocessors', function() {
|
|
* app.use(loopback.limit('5.5mb'))
|
|
* });
|
|
* ```
|
|
* - `middleware:handlers` is emitted when it's time to add your custom
|
|
* request-handling middleware. Note that you should not install any
|
|
* express routes at this point (express routes are discussed later).
|
|
*
|
|
* Usage:
|
|
* ```js
|
|
* app.once('middleware:handlers', function() {
|
|
* app.use('/admin', adminExpressApp);
|
|
* app.use('/custom', function(req, res, next) {
|
|
* res.send(200, { url: req.url });
|
|
* });
|
|
* });
|
|
* ```
|
|
* - `middleware:error-loggers` is emitted at the end, before the loopback
|
|
* error handling middleware is installed. This is the point where you
|
|
* can install your own middleware to log errors.
|
|
*
|
|
* Notes:
|
|
* - The middleware function must take four parameters, otherwise it won't
|
|
* be called by express.
|
|
*
|
|
* - It should also call `next(err)` to let the loopback error handler convert
|
|
* the error to an HTTP error response.
|
|
*
|
|
* Usage:
|
|
* ```js
|
|
* var bunyan = require('bunyan');
|
|
* var log = bunyan.createLogger({name: "myapp"});
|
|
* app.once('middleware:error-loggers', function() {
|
|
* app.use(function(err, req, res, next) {
|
|
* log.error(err);
|
|
* next(err);
|
|
* });
|
|
* });
|
|
* ```
|
|
*
|
|
* Express routes should be added after `installMiddleware` was called.
|
|
* This way the express router middleware is injected at the right place in the
|
|
* middleware chain. If you add an express route before calling this function,
|
|
* bad things will happen: Express will automatically add the router
|
|
* middleware and since we haven't added request-preprocessing middleware like
|
|
* cookie & body parser yet, your route handlers will receive raw unprocessed
|
|
* requests.
|
|
*
|
|
* This is the correct order in which to call `app` methods:
|
|
* ```js
|
|
* app.boot(__dirname); // optional
|
|
*
|
|
* app.installMiddleware();
|
|
*
|
|
* // [register your express routes here]
|
|
*
|
|
* app.listen();
|
|
* ```
|
|
*/
|
|
app.installMiddleware = function() {
|
|
var loopback = require('../');
|
|
|
|
/*
|
|
* Request pre-processing
|
|
*/
|
|
this.use(loopback.favicon());
|
|
// TODO(bajtos) refactor to app.get('loggerFormat')
|
|
var loggerFormat = this.get('env') === 'development' ? 'dev' : 'default';
|
|
this.use(loopback.logger(loggerFormat));
|
|
this.use(loopback.cookieParser(this.get('cookieSecret')));
|
|
this.use(loopback.token({ model: this.models.accessToken }));
|
|
this.use(loopback.bodyParser());
|
|
this.use(loopback.methodOverride());
|
|
|
|
// Allow the app to install custom preprocessing middleware
|
|
this.emit('middleware:preprocessors');
|
|
|
|
/*
|
|
* Request handling
|
|
*/
|
|
|
|
// LoopBack REST transport
|
|
this.use(this.get('restApiRoot') || '/api', loopback.rest());
|
|
|
|
// Allow the app to install custom request handling middleware
|
|
this.emit('middleware:handlers');
|
|
|
|
// Let express routes handle requests that were not handled
|
|
// by any of the middleware registered above.
|
|
// This way LoopBack REST and API Explorer take precedence over
|
|
// express routes.
|
|
this.use(this.router);
|
|
|
|
// The static file server should come after all other routes
|
|
// Every request that goes through the static middleware hits
|
|
// the file system to check if a file exists.
|
|
this.use(loopback.static(path.join(__dirname, 'public')));
|
|
|
|
// Requests that get this far won't be handled
|
|
// by any middleware. Convert them into a 404 error
|
|
// that will be handled later down the chain.
|
|
this.use(loopback.urlNotFound());
|
|
|
|
/*
|
|
* Error handling
|
|
*/
|
|
|
|
// Allow the app to install custom error logging middleware
|
|
this.emit('middleware:error-handlers');
|
|
|
|
// The ultimate error handler.
|
|
this.use(loopback.errorHandler());
|
|
};
|
|
|
|
/**
|
|
* Listen for connections and update the configured port.
|
|
*
|
|
* When there are no parameters or there is only one callback parameter,
|
|
* the server will listen on `app.get('host')` and `app.get('port')`.
|
|
*
|
|
* ```js
|
|
* // listen on host/port configured in app config
|
|
* app.listen();
|
|
* ```
|
|
*
|
|
* Otherwise all arguments are forwarded to `http.Server.listen`.
|
|
*
|
|
* ```js
|
|
* // listen on the specified port and all hosts, ignore app config
|
|
* app.listen(80);
|
|
* ```
|
|
*
|
|
* The function also installs a `listening` callback that calls
|
|
* `app.set('port')` with the value returned by `server.address().port`.
|
|
* This way the port param contains always the real port number, even when
|
|
* listen was called with port number 0.
|
|
*
|
|
* @param {Function=} cb If specified, the callback will be added as a listener
|
|
* for the server's "listening" event.
|
|
* @returns {http.Server} A node `http.Server` with this application configured
|
|
* as the request handler.
|
|
*/
|
|
app.listen = function(cb) {
|
|
var self = this;
|
|
|
|
var server = require('http').createServer(this);
|
|
|
|
server.on('listening', function() {
|
|
self.set('port', this.address().port);
|
|
});
|
|
|
|
var useAppConfig =
|
|
arguments.length == 0 ||
|
|
(arguments.length == 1 && typeof arguments[0] == 'function');
|
|
|
|
if (useAppConfig) {
|
|
server.listen(this.get('port'), this.get('host'), cb);
|
|
} else {
|
|
server.listen.apply(server, arguments);
|
|
}
|
|
|
|
return server;
|
|
}
|