mirror of
https://github.com/zhigang1992/deployd.git
synced 2026-05-12 19:59:03 +08:00
277 lines
6.4 KiB
JavaScript
277 lines
6.4 KiB
JavaScript
var parse = require('url').parse
|
|
, EventEmitter = require('events').EventEmitter
|
|
, util = require('util')
|
|
, path = require('path')
|
|
, fs = require('fs')
|
|
, Script = require('./script')
|
|
, makeExtendable = require('./util/extendable');
|
|
|
|
/**
|
|
* A `Resource` handles incoming requests at a matched url. The base class is designed
|
|
* to be extended by overriding methods that will be called by a `Router`.
|
|
*
|
|
* A `Resource` is also an `EventEmitter`. The following events are available.
|
|
*
|
|
* - `changed` after a resource config has changed
|
|
* - `deleted` after a resource config has been deleted
|
|
*
|
|
* Options:
|
|
*
|
|
* - `path` the base path a resource should handle
|
|
* - `db` the database a resource will use for persistence
|
|
*
|
|
* Example:
|
|
*
|
|
* The following resource would respond with a file at the url `/my-file.html`.
|
|
*
|
|
* function MyFileResource(name, options) {
|
|
* Resource.apply(this, arguments);
|
|
*
|
|
* this.on('changed', function(config) {
|
|
* console.log('MyFileResource changed', config);
|
|
* });
|
|
* }
|
|
* util.inherits(MyFileResource, Resource);
|
|
*
|
|
* FileResource.prototype.handle = function (ctx, next) {
|
|
* if (ctx.url === '/my-file.html') {
|
|
* fs.createReadStream('my-file.html').pipe(ctx.res);
|
|
* } else {
|
|
* next();
|
|
* }
|
|
* }
|
|
*
|
|
* @param {Object} options
|
|
* @api private
|
|
*/
|
|
|
|
function Resource(name, options) {
|
|
EventEmitter.call(this);
|
|
this.name = name;
|
|
this.path = '/' + name;
|
|
options = this.options = options || {};
|
|
this.config = options.config || {};
|
|
this.server = options.server;
|
|
this.events = {};
|
|
var instance = this;
|
|
|
|
// Bind external functions
|
|
if (this.external) {
|
|
var external = {};
|
|
Object.keys(this.external).forEach(function(key) {
|
|
if (typeof instance.external[key] == 'function') {
|
|
external[key] = instance.external[key].bind(instance);
|
|
}
|
|
});
|
|
this.external = external;
|
|
}
|
|
|
|
if (typeof this.init === 'function') this.init(options);
|
|
}
|
|
|
|
/**
|
|
* The external prototype for exposing methods over http and to dpd.js
|
|
*/
|
|
util.inherits(Resource, EventEmitter);
|
|
|
|
makeExtendable(Resource, {
|
|
constructorProperties: ['label', 'defaultPath']
|
|
});
|
|
|
|
Resource.prototype.external = {};
|
|
|
|
/**
|
|
* Parse the `url` into a basepath, query, and parts.
|
|
*
|
|
* @param {String} url
|
|
* @return {Object}
|
|
* @api private
|
|
*/
|
|
|
|
Resource.prototype.parse = function (url) {
|
|
var parsed = parse(url, true)
|
|
, pathname = parsed.pathname
|
|
, parts = parsed.parts = pathname.split('/');
|
|
|
|
// remove empty
|
|
parts.shift();
|
|
parsed.basepath = parts[0];
|
|
|
|
// remove empty trailing slash part
|
|
if(parts[parts.length - 1] === '') parts.pop();
|
|
|
|
// the last part is always the identifier
|
|
if(parts.length > 1) parsed.id = parts[parts.length - 1];
|
|
|
|
if(parsed.query.q && parsed.query.q[0] === '{' && parsed.query.q[parsed.query.q.length - 1] === '}') {
|
|
parsed.query.q = JSON.parse(parsed.query.q);
|
|
}
|
|
|
|
return parsed;
|
|
};
|
|
|
|
Resource.prototype.load = function (fn) {
|
|
var resource = this
|
|
, configPath = this.options && this.options.configPath
|
|
, events = this.events = {};
|
|
|
|
if(configPath) {
|
|
Script.loaddir(configPath, events, fn);
|
|
} else {
|
|
fn();
|
|
}
|
|
};
|
|
|
|
Resource.prototype.run = function (event, script, ctx) {
|
|
var domain = this.createDomain(event, ctx)
|
|
, result;
|
|
|
|
domain.respond = function (data) {
|
|
result = data;
|
|
}
|
|
|
|
script.run(ctx, domain, function (err, resultingDomain) {
|
|
if(err) return ctx.done(err);
|
|
|
|
ctx.done(null, result || resultingDomain._this);
|
|
});
|
|
}
|
|
|
|
Resource.prototype.createDomain = function (event, ctx) {
|
|
var parts = ctx.url.split('/').filter(function(p) { return p; });
|
|
var domain = {
|
|
url: ctx.url
|
|
, parts: parts
|
|
, query: ctx.query
|
|
, body: ctx.body
|
|
, data: ctx.body
|
|
};
|
|
return domain;
|
|
}
|
|
|
|
Resource.prototype.getEventScript = function (ctx) {
|
|
var url = ctx.url || '/';
|
|
return this.events[url.split('/')[1]];
|
|
}
|
|
|
|
Resource.prototype.parseEvent = function (ctx) {
|
|
if(ctx.url) return ctx.url.split('/')[1];
|
|
}
|
|
|
|
/**
|
|
* Handle an incoming request. This gets called by the router.
|
|
* Call `next()` if the resource cannot handle the request.
|
|
* Otherwise call `cxt.done(err, res)` when the resource
|
|
* is ready to respond.
|
|
*
|
|
* Example:
|
|
*
|
|
* Override the handle method to return a string:
|
|
*
|
|
* function MyResource(settings) {
|
|
* Resource.apply(this, arguments);
|
|
* }
|
|
* util.inherits(MyResource, Resource);
|
|
*
|
|
* MyResource.prototype.handle = function (ctx, next) {
|
|
* // respond with the file contents (or an error if one occurs)
|
|
* fs.readFile('myfile.txt', ctx.done);
|
|
* }
|
|
*
|
|
* @param {Context} ctx
|
|
* @param {function} next
|
|
*/
|
|
|
|
Resource.prototype.handle = function (ctx, next) {
|
|
switch (ctx.method) {
|
|
case 'GET':
|
|
this.get(ctx, next);
|
|
break;
|
|
case 'POST':
|
|
this.post(ctx, next);
|
|
break;
|
|
case 'PUT':
|
|
this.put(ctx, next);
|
|
break;
|
|
case 'DELETE':
|
|
this.del(ctx, next);
|
|
break;
|
|
default:
|
|
next();
|
|
break;
|
|
}
|
|
};
|
|
|
|
Resource.prototype.getDefaultPermissions = function (ctx) {
|
|
// a resource should return an object defining
|
|
// default permissions for the given context
|
|
return {};
|
|
}
|
|
|
|
Resource.prototype.getRequiredPermissions = function (ctx) {
|
|
// a resource should return an object defining
|
|
// required permissions for the given context
|
|
return {};
|
|
}
|
|
|
|
Resource.prototype.beforeHandle = function (ctx) {
|
|
// hook
|
|
}
|
|
|
|
Resource.prototype.afterHandle = function (ctx) {
|
|
// hook
|
|
}
|
|
Resource.prototype.get = function(ctx, next) {
|
|
next();
|
|
};
|
|
|
|
Resource.prototype.post = function(ctx, next) {
|
|
next();
|
|
};
|
|
|
|
Resource.prototype.put = function(ctx, next) {
|
|
next();
|
|
};
|
|
|
|
Resource.prototype.del = function(ctx, next) {
|
|
next();
|
|
};
|
|
|
|
/**
|
|
* Turn a resource constructor into an object ready
|
|
* for JSON. It should atleast include the `type`
|
|
* and `defaultPath`.
|
|
*/
|
|
|
|
Resource.prototype.toJSON = function() {
|
|
return {
|
|
type: this.name,
|
|
defaultPath: '/my-resource'
|
|
};
|
|
};
|
|
|
|
/*!
|
|
* If true, generates utility functions for this resource in dpd.js
|
|
*/
|
|
|
|
Resource.prototype.clientGeneration = false;
|
|
|
|
/*!
|
|
* If clientGeneration is true, generates utility functions that alias to get(path)
|
|
*/
|
|
|
|
Resource.prototype.clientGenerationGet = [];
|
|
|
|
/*!
|
|
* If clientGeneration is true, generates utility functions that alias to exec(path)
|
|
*/
|
|
|
|
Resource.prototype.clientGenerationExec = [];
|
|
|
|
/*!
|
|
* Resource tag, for duck typing
|
|
*/
|
|
|
|
Resource.prototype.__resource__ = true;
|
|
|
|
module.exports = Resource; |