require-node-example
Version:
An example for how to use npm package: require-node & require-node-loader
418 lines (385 loc) • 14.7 kB
JavaScript
;
//防止require-node被意外require到了浏览器环境
if (typeof global == 'undefined' && typeof window != 'undefined' && typeof window.alert === 'function') {
var err = new Error('[FATAL ERROR]require-node should not run in browser, because it has code: require("../.." + modulePath)!');
alert(err.message);
throw err;
}
var config = {
alias: {},
resolve: null,
_inject: function (req, res, callback) {
return {
$res: res,
$req: req,
$session: req && req.session,
$body: req.body,
callback: callback,
$origin: req.headers && (req.headers.origin || 'http://' + (req.headers.host || req.hostname)),
$hostname: req.hostname,
$query: req.query
}
}
};
var aliasPathDict = {};
function call(req, res, next) {
return _formatReqRes(req, res).then(function () {
const originalUrlPath = req.originalUrl.split('?', 1)[0];
if (aliasPathDict[originalUrlPath]) {
//如下代码for seajs or requirejs loader
var _browserify = require('./_browserify');
try {
res.status(200).send(_browserify.toCMD('../..' + originalUrlPath, aliasPathDict[originalUrlPath], config));
}
catch (err) {
console.log(err && err.stack || err);
console.log('_browserify err:', originalUrlPath, aliasPathDict[originalUrlPath]);
res.status(404).send(err && err.stack);
}
return;
}
else if (!req.baseUrl && !originalUrlPath.startsWith(config.path || '/require-node')) {
//throw new Error("__promise_break");
throw { message: "__promise_break" };
}
var params = getParams(req);
//console.log('call params:', params);
var moduleName = params[0];
var functionName = params[1];
var moduleInstance = getModuleInstance(moduleName);
if (!moduleInstance) {
var err = new Error('没有此模块:' + moduleName);
err.statusCode = 404;
throw err;
}
var moduleFunction = getModuleFunction(moduleInstance, functionName);
if (!moduleFunction) {
var err = new Error(moduleName + '没有此方法:' + functionName);
err.statusCode = 404;
throw err;
}
var actualParams = params[2];
var formalParams = getModuleFormalParams(moduleFunction);
return new Promise(function (resolve, reject) {
if (config.resolve) {
try {
var ret = config.resolve(req, moduleName, functionName, formalParams, actualParams);
if (_isObject(ret) && ret.then) {
ret.then(resolve, reject);
} else {
resolve(ret);
}
}
catch (err) {
reject(err);
}
} else {
resolve(true);
}
}).then(function (canCall) {
if (!canCall) {
var err = new Error('没有权限调用此方法:' + moduleName + '.' + functionName);
err.statusCode = 403;
throw err;
}
var isCallback = moduleFunctionIsCallback(formalParams);
if (isCallback) {
var callbackResolve, callbackReject;
var callbackPromise = new Promise(function (resolve, reject) { callbackResolve = resolve; callbackReject = reject; });
var callback = function (err, result) {
if (err) {
callbackReject(err);
}
else {
if (req.headers['x-require-node']) {
callbackResolve(arguments.length <= 2 ? result : Array.prototype.slice.call(arguments, 1));
}
else {
callbackResolve(result);
}
}
}
}
parseActualParams(actualParams, actualParams, req, res);
parseActualParams(actualParams, formalParams, req, res, callback);
var result = moduleFunction.apply(moduleInstance, actualParams);
return isCallback ? callbackPromise : result;
});
}).then(function (result) {
if (res.finished) {
return;
}
result = formatResult(result, _resultReplacer);
if (req.headers['x-require-node']) {
res.status(200).send([null, result]);
}
else {
if (result && result.$view) {
res.render(result.$view, result);
}
else {
res.status(200).send(typeof result === 'number' ? (result + '') : result);
}
}
}).catch(function (err) {
config.isDebug && console.log('call err:', err);
//err的stack属性默认是非枚举类型,改成可枚举,让res.status.send时序列化此属性
if (err && err.stack) {
Object.defineProperty(err, 'message', { value: err.message, enumerable: true });
config.isDebug && Object.defineProperty(err, 'stack', { value: err.stack, enumerable: true });
config.isDebug && console.log('call err enumerable stack:', err.stack);
}
if (res.finished) {//如果用户已经执行了res.end
return;
}
if (req.headers['x-require-node']) {
res.status(200).send([err]);
}
else {
if (err && err.$view && res.render) {
res.render(err.$view, err);
}
else {
if (next && err && err.message === '__promise_break') {
return next();//next()以及_formatReqRes()前加return的目的是为了兼容koa
}
else {
res.status(err.statusCode || 500).send(err);
}
}
}
})
}
var pathPattern = /^\/([^\.\/]+)[\.\/]([^\(\[%]+)(?:(?:\(|\[|%5B)(.+)(?:\)|\]|%5D))?/i; //%5B %5D分别表示[ ]这两个字符
function getParams(req) {
if (req.headers['x-require-node']) {
config.isDebug && console.log('x-require-node:', req.method, req.url);
if (req.body instanceof Array && req.body.length === 3 && req.body[2] instanceof Array) {
return req.body;
}
}
else {
var urlPath = req.originalUrl.split('?', 1)[0].slice((req.baseUrl || config.path || '/require-node').length)
var match = urlPath.match(pathPattern);
config.isDebug && console.log('call path match', match);
if (match) {
if (req.method === 'POST') {
var params = req.body instanceof Array ? req.body : [];
}
else {
var params = match[3] ? JSON.parse('[' + decodeURIComponent(match[3]) + ']') : [];
}
//console.log('params',params);
return [decodeURIComponent(match[1]), decodeURIComponent(match[2]), params];
}
}
var err = new Error('提取参数错误');
err.statusCode = 400;
throw err;
}
function getModuleInstance(moduleName) {
if (config.baseLastDir && moduleName.startsWith(config.baseLastDir)) {
return require(config.base + moduleName);
}
var modulePath = getModulePath(moduleName);
if (!moduleName || !modulePath) {
console.error('moduleName:', moduleName, ' ,modulePath:', modulePath);
return null;
}
return require('../..' + modulePath);
}
function getModuleFunction(moduleInstance, functionName) {
let ret;
if (functionName instanceof Array) {
ret = moduleInstance;
for (var i = 0; i < functionName.length; i++) {
ret = ret[functionName[i]];
}
} else {
ret = moduleInstance[functionName];
}
return ret;
}
function getModulePath(moduleName) {
if (!moduleName || typeof moduleName !== 'string') {
return null;
}
//为了安全,最后始终从alias里取出modulePath。如果仅根据moduleName以'/'开始就返回,那么任意后台js都可能被前端调用
if (moduleName[0] === '/') {
moduleName = aliasPathDict[moduleName];
}
return config.alias[moduleName];
}
function getModuleFormalParams(moduleFunction) {
//提取形参列表
if (moduleFunction.$formalParams) {
return moduleFunction.$formalParams;
}
else {
var formalParamsStr = moduleFunction.toString().split(')')[0].split('(')[1];
var ret = formalParamsStr ? formalParamsStr.split(',').map(function (i) { return i.trim(); }) : [];
moduleFunction.$formalParams = ret;//缓存把结果缓存起来
return ret;
}
}
function parseActualParams(params, refParams, req, res, callback) {
var _$inject = config._inject(req, res, callback);
var $inject = config.inject ? config.inject(req, res, callback) : {};
refParams.forEach(function (refParam, index) {
params[index] = $inject[refParam] || _$inject[refParam] || params[index];
});
}
var callbackFunctionNames;
function moduleFunctionIsCallback(formalParams) {
if (!callbackFunctionNames) {
var callback = {};
var _$inject = config._inject({}, {}, callback);
var $inject = config.inject ? config.inject({}, {}, callback) : {};
callbackFunctionNames = [];
for (let key in _$inject) {
if (_$inject[key] === callback) {
callbackFunctionNames.push(key)
}
}
for (let key in $inject) {
if ($inject[key] === callback) {
callbackFunctionNames.push(key)
}
}
//console.log('callbackFunctionNames:', callbackFunctionNames);
}
return formalParams.length > 0 && callbackFunctionNames.indexOf(formalParams[formalParams.length - 1]) > -1;
}
function _formatReqRes(req, res) {
//console.log('req', req.url, req.originalUrl, req.body);
req.originalUrl = req.originalUrl || req.url;
if (!res.status) res.status = status => {
res.statusCode = status;
return res;
}
if (!res.send) res.send = data => {
res.setHeader('Content-Type', 'application/json');
res.end(typeof data === 'string' ? data : JSON.stringify(data));
}
return new Promise(function (resolve, reject) {
if (req.hasOwnProperty('body')) {
resolve();
}
else {
require('body-parser').json({ limit: '800mb' })(req, res, resolve)
}
}).then(() => _parseDate(req.body));
}
function formatResult(json, replacer) {
var ret = { ret: json };
_formatResult(ret, replacer);
return ret.ret;
}
function _formatResult(json, replacer) {
if (!_isObject(json)) {
return json;
}
for (var key in json) {
if (json instanceof Object && !json.hasOwnProperty(key)) {//Object.create(null)对象没有hasOwnProperty方法
continue;
}
var newJson = replacer(key, json[key]);
if (json[key] !== newJson) {
json[key] = newJson;
} else {
_formatResult(json[key], replacer)
}
}
}
function _resultReplacer(key, value) {
if (!value) {
return value;
}
if (key === 'headers') {
console.log(value, value instanceof Map, value instanceof Array)
}
switch (typeof value) {
case 'string': //为了提速,优先判断是否string,number
case 'number':
return value;
case 'object':
if (value instanceof Array) { //为了提速,优先判断是否Array
return value;
} else if (value instanceof Date) {
return {
__$type$__: 'Date',
__$value$__: +value
}
} else if (value instanceof Map) {
return {
__$type$__: 'Map',
__$value$__: [...value]
}
} else if (value instanceof Set) {
return {
__$type$__: 'Set',
__$value$__: [...value]
}
} else {
return value
}
case 'function':
return {
__$type$__: 'Function',
__$value$__: '' + value,
__$this$__: value.this
}
default:
return value;
}
return value;
}
function _parseDate(obj) {
if (!_isObject(obj)) {
return;
}
for (var key in obj) {
if (_isObject(obj[key])) {
_parseDate(obj[key]);
}
else if (_isDateTimeStr(obj[key])) {
obj[key] = new Date(obj[key]);
}
}
}
function _isObject(obj) {
return obj !== null && typeof (obj) === 'object';
}
function _isDateTimeStr(str) {
return typeof str === "string" && str[10] === "T" && str[str.length - 1] === "Z" && (str.length === 24 || str.length === 20);
}
module.exports = function (options) {
for (var key in options) {
config[key] = options[key];
}
if (config.alias) {
for (var moduleName in config.alias) {
var modulePath = config.alias[moduleName];
aliasPathDict[modulePath] = moduleName;
if (modulePath.slice(-3) !== '.js') {
aliasPathDict[modulePath + '.js'] = moduleName;
}
}
}
else {
config.alias = {};
aliasPathDict = {};
}
//format config
if (config.base) {
var subBase = config.base.split(/[\\/]/);
if (!subBase[subBase.length - 1]) {
subBase = subBase.slice(0, -1);
}
config.base = subBase.slice(0, -1).join('/');
config.baseLastDir = '/' + subBase[subBase.length - 1] + '/';
}
config.isDebug && console.log('require-node alias:', config.alias);
//console.log('aliasPathDict', aliasPathDict)
return call;
};