msful
Version:
Web API server for micro service.
927 lines (819 loc) • 26.1 kB
JavaScript
// API実行処理.
//
//
module.exports.create = function (_g, core) {
'use strict';
var _u = undefined
var fs = require('fs');
var vm = require('vm');
var path = require('path');
var constants = require("../constants");
var u = require("../../lib/u");
var caches = require("../../lib/subs/caches");
var sysParams = core.getSysParams();
var notCache = sysParams.isNotCache();
var closeFlag = sysParams.isCloseFlag();
var debugMode = sysParams.getDebugMode();
// システムロガー.
var log = msfulLogger().get("system");
// httpCore処理.
var httpCore = require('../http_core');
// エラーハンドリング.
var error = require("../error");
// TOPメモリ(global).
var _TOP_MEMORY = Object.freeze("_$t06_$$m3M0_3$_$");
// スクリプト送信終了フラグ(success).
var _EXIT_SEND_SCRIPT_FLG = Object.freeze("_$eXit_$sEnd_$fLag_$");
// スクリプト送信エラーフラグ(error).
var _EXIT_SEND_ERROR_SCRIPT_FLG = Object.freeze("_$eR08r_$sEnd_$fLag_$");
// データなし(return),
var NONE = Object.freeze(new Object());
// Array.prototype.slice.
var _Array_prototype_slice_object = Array.prototype.slice;
// URLアクセスパス.
var baseApiPath = path.resolve(constants.API_DIR);
//
// キャッシュ情報.
//
// APIキャッシュ時間系.
var _CACHE_API_CHECK_TIME = 60000; // 60秒.
var _CACHE_API_REMOVE_TIME = 300000; // 300秒.
// requireCache時間系.
var CHECK_TIME = 15000; // 15秒.
var REMOVE_TIME = 60000; // 60秒.
// キャッシュ情報.
var cacheApi = caches.create(_CACHE_API_CHECK_TIME, _CACHE_API_REMOVE_TIME);
var rtxScriptCache = caches.create(_CACHE_API_CHECK_TIME, _CACHE_API_REMOVE_TIME);
var cacheRequire = caches.create(CHECK_TIME, REMOVE_TIME);
var backupRequire = caches.create(CHECK_TIME, REMOVE_TIME);
// パラメータ取得.
var _$getParams = function (request, data) {
if (request.method.toLowerCase() == "post") {
return _$post(request, data);
} else {
return _$get(request.url);
}
}
// POSTパラメータ処理.
var _$post = function (req, data) {
// バッファデータの場合.
if(data instanceof Buffer) {
return data;
// Uint8Arrayデータの場合.
} else if(data instanceof Uint8Array) {
return Buffer.from(data);
// json系の場合.
} else if (req.headers["content-type"].indexOf("application/json") == 0) {
// /jsonが含まれる場合はjson変換.
return JSON.parse(data);
} else if (typeof(data) == "string") {
// 文字列の場合はQueryパラメータ変換.
return _analysisParams(data);
} else {
// それ以外はそのまま返却.
return data;
}
}
// GETパラメータを処理.
var _$get = function (url) {
var p = url.indexOf("?");
if (p == -1) {
return {};
}
return _analysisParams(url.substring(p + 1));
}
// パラメータ解析.
var _analysisParams = function (n) {
var list = n.split("&");
var len = list.length;
var ret = {};
for (var i = 0; i < len; i++) {
n = list[i].split("=");
if (n.length == 1) {
ret[n[0]] = '';
} else {
ret[n[0]] = decodeURIComponent(n[1]);
}
}
return ret;
}
// スクリプト送信終了チェック.
var _endSendScript = function(m) {
var ret = true;
try {
ret = !("response" in m) ||
(!(_EXIT_SEND_SCRIPT_FLG in m) &&
m[_EXIT_SEND_SCRIPT_FLG]) ||
(!(_EXIT_SEND_ERROR_SCRIPT_FLG in m) &&
m[_EXIT_SEND_ERROR_SCRIPT_FLG]);
} catch(e) {
log.warn("exception", e);
}
return ret;
}
// スクリプト送信エラーかチェック.
var _errorSendScript = function(m) {
var ret = true;
try {
ret = m[_EXIT_SEND_ERROR_SCRIPT_FLG];
} catch(e) {}
return ret;
}
// [スクリプト結果]書き込み処理.
var _send = function(m, status, body) {
if (m[_EXIT_SEND_SCRIPT_FLG] || m[_EXIT_SEND_ERROR_SCRIPT_FLG]) {
return false;
}
try {
// 書き込み処理.
m.response.writeHead(status, m.headers);
m.response.end(body);
// スクリプト終了.
m[_EXIT_SEND_SCRIPT_FLG] = true;
return true;
} catch (e) {
// エラーをデバッグ出力.
log.debug("exception", e);
// レスポンスソケットクローズ.
try {
m.response.socket.destroy();
} catch (ee) {}
// エラー終了フラグをセット.
try {
m[_EXIT_SEND_ERROR_SCRIPT_FLG] = true;
} catch (ee) {}
return false;
}
}
// [binary]正常戻り値処理.
var _successBinary = function(m, status, body, charset) {
if (_endSendScript(m)) {
return false;
}
// bodyが文字列の場合はBufferに変換.
if (body instanceof String) {
if(!charset || charset == "") {
charset = "uft-8";
}
body = Buffer.from(body, charset);
// Uint8Arrayの場合は、バッファ変換.
} else if(body instanceof Uint8Array) {
body = Buffer.from(body);
}
// bodyがBufferでない場合は、エラー.
if (!(body instanceof Buffer)) {
throw new Error("The body data is not in binary format.");
}
// cros対応ヘッダを設定.
var headers = m.headers;
httpCore.setCrosHeader(headers, body.length, notCache, closeFlag);
// cros対応ヘッダを設定.
return _send(m, status, body);
}
// [webApi]正常戻り値処理.
var _successApi = function(m, status, body) {
if (_endSendScript(m)) {
return false;
}
var headers = m.headers;
httpCore.setCrosHeader(headers, httpCore.utf8Length(body), notCache, closeFlag);
headers['Content-Type'] = 'application/json; charset=utf-8;';
// 返却処理.
return _send(m, status, body);
}
// [webapi]エラー処理.
var _errorApi = function(m, e, status, trace) {
if (_endSendScript(m)) {
return false;
}
var message = "";
if (e) {
// httpErrorハンドリング.
if (e["status"]) {
status = e["status"]|0;
if(e["message"]) {
message = "" + e["message"];
}
}
// 例外が発生した場合は、エラー返却.
// ただし、ファイルが存在しない場合は、404返却.
else if (e.code && e.code == 'ENOENT') {
status = 404;
} else {
status = 500;
}
// メッセージが存在しない.
if(!message) {
message = httpCore.getMessage(status);
}
if((debugMode || status >= 500) && log.isErrorEnabled()) {
log.error("http_error: status: " + status + " message: " + message,
"[" + httpCore.getIp(m.request) + "]", trace);
}
} else if((debugMode || status >= 500) && log.isErrorEnabled()) {
log.error("http_error: status: " + status + " message: " + message,
"[" + httpCore.getIp(m.request) + "]");
}
// error 404 と 500 の場合は、メッセージを書き換える.
if(status == 404 || status == 500) {
message = httpCore.getMessage(status);
}
var headers = m.headers;
var body = "{\"result\": \"error\", \"error\": " + status + ", \"message\": \"" + message + "\"}";
httpCore.setCrosHeader(headers, httpCore.utf8Length(body), notCache, closeFlag);
headers['Content-Type'] = 'application/json; charset=utf-8;';
// 返却処理.
return _send(m, status, body);
}
// エラー処理(readApi外でエラー処理を呼び出す場合)
var _outError = function(req, res, e, status, trace, headers) {
return _errorApi({
request: req,
response: res,
headers: (!headers) ? {} : headers
}, e, status, trace);
}
// Respオブジェクト.
var ResponseContext = function(mm) {
this._m = mm;
this._promise = false;
this._sendBuffer = null;
}
// 次の実行処理をセット.
ResponseContext.prototype.push = function(call) {
// 処理対象がfnctionコールでない場合.
var t = typeof(call);
if(t != "function") {
// 代わりにファイル名で設定されている場合.
if(t == "string") {
try {
// キャッシュに情報が存在するかチェック.
var cache = rtxScriptCache.getCache(call);
var mtime = fs.statSync(call).mtime;
if(cache != null && cache,lastModified === mtime) {
// キャッシュに存在する場合はそれを利用する.
call = cache.call;
} else {
// 対象ファイルをオープン.
var value = fs.readFileSync(call, "utf-8");
// ファイル内容をFunction変換.
// フィルタセット.
value = "function(rtx) {\n" + value + "\n})";
var name = call;
call = new Function(value);
value = null;
// キャッシュにセット.
rtxScriptCache.put(name, mtime, call)
}
} catch(e) {
_errorApi(this._m, e, 500, e);
rtxScriptCache.remove(call);
}
}
}
if(this._nstack == _u) {
this._nstack = u.stack();
}
this._nstack.push(call);
}
// 次の実行処理が存在するかチェック.
ResponseContext.prototype.size = function() {
if(this._nstack == _u) {
return 0;
}
return this._nstack.size();
}
// 次に実行するメソッドを実行.
// 次に実行するメソッドが存在しない場合はエラーを出力.
ResponseContext.prototype.next = function() {
if(this._nstack == _u) {
this.error(500, "Next method does not exist.");
}
var call = this._nstack.pop();
if(call == _u) {
this.error(500, "Next method does not exist.");
}
return call();
}
// 正常時のWebAPI返却.
ResponseContext.prototype.send = function(body, status) {
if(body == _u || body == null || body == NONE) {
throw new Error("It is not valid Body data.")
}
var m = this._m;
if((status|0) <= 0) {
status = m.response.statusCode;
if((status|0) <= 0) {
status = 200;
}
}
if(this._promise) {
this._sendBuffer = {
type: 1,
status: status,
body: body
}
return "OK";
}
try {
m.response.statusCode = status;
_successApi(m, status, JSON.stringify(body));
} catch(e) {
log.debug("exception", e);
} finally {
_closeable(m);
}
};
// 正常時のデータ返却.
ResponseContext.prototype.binary = function(body, status, charset) {
if(body == _u || body == null || body == NONE) {
throw new Error("It is not valid Body data.")
}
var m = this._m;
if((status|0) <= 0) {
status = m.response.statusCode;
if((status|0) <= 0) {
status = 200;
}
}
if(this._promise) {
this._sendBuffer = {
type: 2,
status: status,
body: body,
charset: charset
}
return "OK";
}
try {
m.response.statusCode = status;
_successBinary(m, status, body, charset);
} catch(e) {
log.debug("exception", e);
} finally {
_closeable(m);
}
};
// リダイレクトを行う場合に呼び出す.
ResponseContext.prototype.redirect = function(url, status) {
status = status|0
if(status <= 0) {
status = 303
}
if(this._promise) {
this._sendBuffer = {
type: 3,
status: status,
location: url
}
return "OK";
}
var m = this._m;
try {
m.response.statusCode = status;
m.headers['Location'] = url;
m.rtx.success("", status);
} catch(e) {
log.debug("exception", e);
} finally {
_closeable(m);
}
};
// エラー出力時に呼び出す.
ResponseContext.prototype.error = function(status, message, e) {
var m = this._m;
status = status|0;
if(status <= 0) {
status = 500;
}
try {
if(message == _u) {
_errorApi(m, e, status, e);
} else {
_errorApi(m, {status: status, message: message}, status, e);
}
} catch(ee) {
log.debug("exception", ee);
} finally {
this._sendBuffer = null;
_closeable(m);
}
};
// 例外出力時に呼び出す.
ResponseContext.prototype.exception = function(e, status) {
var m = this._m;
status = status|0;
if(status <= 0) {
status = 500;
}
try {
_errorApi(m, e, status, e);
} catch(ee) {
log.debug("exception", ee);
} finally {
this._sendBuffer = null;
_closeable(m);
}
};
// ステータスセット、現在のステータス情報を取得.
ResponseContext.prototype.status = function(status) {
var m = this._m;
status = status | 0;
if (status != 0) {
m.response.statusCode = status;
}
return m.response.statusCode;
};
// スクリプト送信済みかチェック.
ResponseContext.prototype.isSendScript = function() {
return _endSendScript(this._m);
}
// スクリプト送信エラーかチェック.
ResponseContext.prototype.isErrorSendScript = function() {
return _errorSendScript(this._m);
}
// レスポンス終了処理.
var _endRtx = function(m) {
if(m.rtx._promise) {
var buf = m.rtx._sendBuffer;
m.rtx._sendBuffer = null;
if(buf == null) {
// 送信条件が存在しない場合は、空データを送信.
buf = { type: -1 };
}
try {
switch(buf.type) {
case 1:
m.response.statusCode = buf.status;
_successApi(m, buf.status, JSON.stringify(buf.body));
return "OK";
case 2:
m.response.statusCode = buf.status;
_successBinary(m, buf.status, buf.body, buf.charset);
return "OK";
case 3:
m.response.statusCode = buf.status;
m.headers['Location'] = buf.url;
m.rtx.success("", buf.status);
return "OK";
default:
// 何も返す情報がない場合は空を返す
m.response.statusCode = 200
_successApi(m, 200, '{}');
return "OK";
}
} catch(e) {
log.debug("exception", e);
} finally {
_closeable(m);
}
} else {
return "NONE";
}
}
// エラー.
var _error = function(mm) {
var _m = mm;
return function(e, status, err) {
try {
return _errorApi(_m, e, status, (err == _u) ? e: err);
} catch(ee) {}
return false;
};
}
// サーバエラー.
var _serverError = function(mm) {
var _m = mm;
return function(status, message, e) {
try {
if(message == _u) {
return _errorApi(_m, e, status, e);
} else {
return _errorApi(_m, {status: status, message: message}, status, e);
}
} catch(ee) {
log.debug("exception", ee);
} finally {
this._sendBuffer = null;
_closeable(_m);
}
return false;
};
}
// クローズ後の実行処理.
var _closeable = function(_m) {
if(_m.closeable != _u) {
try {
_m.closeable.close();
} catch(e){}
}
core.clearModules();
}
// 非同期用キャッチ処理.
var _catchsByAsync = function(e, _m) {
try {
_m._serverError(500, "internal server error", e);
} catch(ee) {}
}
// setImmediate拡張.
var __setImmediate = global.setImmediate;
var _setImmediate = function(mm) {
return function() {
var m = mm;
var call = arguments[0];
var args = [];
if(arguments.length > 1) {
args = _Array_prototype_slice_object.call(arguments, 1);
}
return __setImmediate(function() {
var im = m; m = null;
var icall = call;
var iargs = args;
try {
icall.apply(null, iargs);
} catch(e) {
_catchsByAsync(e, im);
}
});
}
}
// setInterval拡張.
var __setInterval = global.setInterval;
var _setInterval = function(mm) {
return function() {
var m = mm;
var call = arguments[0];
var time = arguments[1];
var args = [];
if(arguments.length > 2) {
args = _Array_prototype_slice_object.call(arguments, 2);
}
return __setInterval(function() {
var im = m;
var icall = call;
var iargs = args;
try {
icall.apply(null, iargs);
} catch(e) {
_catchsByAsync(e, im);
}
},time);
}
}
// setTimeout拡張.
var __setTimeout = global.setTimeout;
var _setTimeout = function(mm) {
return function() {
var m = mm;
var call = arguments[0];
var time = arguments[1];
var args = [];
if(arguments.length > 2) {
args = _Array_prototype_slice_object.call(arguments, 2);
}
return __setTimeout(function() {
var im = m;
var icall = call;
var iargs = args;
try {
icall.apply(null, iargs);
} catch(e) {
_catchsByAsync(e, im);
}
},time);
}
}
// コンソール出力.
var _print = function(n) {
process.stdout.write(n + "\n");
}
// モジュールをセット.
var setModules = function(out) {
core.setDefaultModules(out);
var modules = core.getModules();
for(var k in modules) {
out[k] = modules[k];
}
// グローバルメモリにmsful固有条件をセット.
core.setMsfulGlobals(out);
}
// メモリ情報を取得.
var createMemory = function(req, res, data) {
var memory = {}
setModules(memory);
memory[_EXIT_SEND_SCRIPT_FLG] = false;
memory[_EXIT_SEND_ERROR_SCRIPT_FLG] = false;
memory.NONE = NONE;
memory.request = req;
memory.response = res;
memory.headers = {};
memory.params = _$getParams(req, data);
// キャッシュApiオブジェクト.
memory.cacheApi = cacheApi;
// レスポンスコンテキスト終了処理.
memory._endRtx = _endRtx;
// エラーハンドリング.
memory._error = _error(memory);
// サーバエラー処理.
memory._serverError = _serverError(memory);
// コンソール出力.
memory.print = _print;
// HttpErrorハンドラ.
memory.HttpError = error.HttpError;
// ResponseContext処理.
memory.rtx = new ResponseContext(memory);
// promise.
memory.rtx.$ = function() {
memory.rtx._promise = true;
return new Promise(function(resolve) {
resolve(memory.rtx)
})
}
// 拡張require.
memory.require = Object.freeze(
require("../require")(cacheRequire, backupRequire, core)
);
// タイマー系.
// try catch を入れて、エラーハンドリング時にHTTPレスポンスを返却.
memory.setImmediate = _setImmediate(memory);
memory.setInterval = _setInterval(memory);
memory.setTimeout = _setTimeout(memory);
// 初期化が必要な処理を実行.
core.createModules(req, res, memory.params);
return memory;
}
// キャッシュ実行.
var executeCacheApi = function(name, req, res, data, cache) {
try {
// 初期化.
var memory = createMemory(req, res, data);
var status = 200;
// 実行処理.
cache.call(memory);
} catch(e) {
_errorApi(memory, e, status, e);
}
}
// api読み込み.
var readApi = function(req, res, data, url) {
// キャッシュチェック.
cacheApi.cacheControll();
rtxScriptCache.cacheControll();
// フィルタパスを生成.
var dir = null;
var p = url.lastIndexOf("/");
if(p == -1) {
dir = "/";
} else {
dir = url.substring(0, p+1);
}
var filterName = constants.API_PATH + dir + constants.FILTER_FILE;
dir = null;
// apiFileパスを生成.
var apiFile = constants.API_PATH + url;
if (apiFile.lastIndexOf("/") == apiFile.length - 1) {
apiFile += "index.js";
} else if(apiFile.lastIndexOf(".js") != apiFile.length - 3) {
apiFile += ".js"
}
apiFile = path.resolve(apiFile);
// それぞれの存在確認処理.
fs.stat(filterName, function (ferr, fstat) {
fs.stat(apiFile, function(err, stat) {
var filterTime, apiTime;
try {
// 初期化.
var memory = createMemory(req, res, data);
var status = 200;
// エラー処理.
if (err) throw err;
// APIファイル時間を取得.
apiTime = stat.mtime.getTime();
// フィルタなしの実行処理.
var filterSrc = "rtx.next();\n";
// フィルタが存在する場合.
if (!ferr) {
filterTime = fstat.mtime.getTime();
// キャッシュデータを取得.
var cache = cacheApi.getCache(apiFile);
// [フィルタあり]キャッシュ実行可能かチェック.
if(cache != _u && cache.filterTime == filterTime && cache.apiTime == apiTime) {
cache.update = Date.now();
executeCacheApi(apiFile, req, res, data, cache);
return;
}
cache = null;
// フィルタセット.
filterSrc = "(function() {\n" +
fs.readFileSync(filterName, "utf-8") +
"\n})();\n";
// フィルタが存在しない場合.
} else {
// キャッシュデータを取得.
var cache = cacheApi.getCache(apiFile);
// [フィルタなし]キャッシュ実行可能かチェック.
if(cache != _u && cache.filterTime == -1 && cache.apiTime == apiTime) {
cache.update = Date.now();
executeCacheApi(apiFile, req, res, data, cache);
return;
}
cache = null;
filterTime = -1;
}
// 実行スクリプト生成.
var scriptSrc =
"(function() {\n" +
"var _api_name=Object.freeze(\""+apiFile+"\");\n" +
"return function(_mm){\n" +
// topメモリの張替え.
"(function(mm){\n" +
//"var m="+_TOP_MEMORY+";for(var k in m){if(m[k]!=m){delete m[k];}}\n" +
"var m="+_TOP_MEMORY+";for(var k in m){if(m[k]!=m){m[k] = undefined;}}\n" +
"for(var k in mm){m[k]=mm[k];};mm=undefined;m=undefined;\n" +
"})(_mm);\n" +
"_mm=undefined;\n" +
// システムログを取得.
"var systemLog = logger.get('system');\n" +
"try {\n" +
// api実行用メソッドをResp.next実行用にセット.
"rtx.push(function() {\n" +
"return (function() {\n" +
fs.readFileSync(apiFile, "utf-8") +
"\n})();" +
"\n});\n" +
// フィルタ実行.
"var result = " + filterSrc + "\n" +
// 処理結果がPromiseの場合.
"if (result && typeof(result['then']) === 'function') {\n" +
"result.then(function() { return _endRtx(rtx._m) });" +
"result.catch(function(e) {\n" +
"_error(e);\n" +
"});\n" +
"\n}" +
"\n} catch(e) {\n" +
// ここで落ちる場合は、シンタックスエラー(文法系エラー)なので、
// キャッシュ削除と、closeableが動かないエラー返却を行う.
"cacheApi.remove(_api_name);\n" +
"_error(e);\n" +
"} finally {\n" +
"}\n" +
"}\n" +
"})();";
filterSrc = null;
// 実行処理.
var script = new vm.Script(scriptSrc, {filename:apiFile});
scriptSrc = null;
// 操作可能なtopMemoryをセット.
var _g = {};_g[_TOP_MEMORY] = _g;
// 上書き禁止.
Object.defineProperty(_g, _TOP_MEMORY, {writable : false});
var context = vm.createContext(_g);
var scriptCall = script.runInContext(context, {filename:apiFile});
// スクリプト実行.
scriptCall(memory);
// 実行成功の場合キャッシュセット.
cacheApi.put(apiFile, 0, scriptCall, {
filterTime: filterTime,
apiTime: apiTime
});
} catch(e) {
cacheApi.remove(apiFile);
_errorApi(memory, e, status, e);
}
});
});
}
var o = {};
// 実行処理.
var _exec = function(req, res, url, data) {
setImmediate(function() {
var rq = req;
var rs = res;
try {
var d = data;
var u = url;
readApi(rq, rs, d, u);
} catch(e) {
_outError(rq, rs, e);
}
})
}
// API実行.
o.execute = function(req, res, url, data) {
// アクセス禁止URL.
if (url.indexOf(constants.FORBIDDEN_URL) != -1) {
// アクセス禁止.
_outError(req, res, {status: 403, message: "error: 403"}, 403);
}
// WebAPIのパスチェック.
else if(!httpCore.checkPath(constants.API_PATH, baseApiPath, url, res)) {
_outError(req, res, {status: 403, message: "It is an illegal URL."}, 403);
}
// 実行処理
else {
// WebApi返却(非同期).
_exec(req, res, url, data);
}
}
return o;
};