add local plugins with check folder update

This commit is contained in:
Alexander.Trofimov
2017-01-24 18:28:35 +03:00
parent fe53b4665f
commit f649328ad0
7 changed files with 273 additions and 212 deletions

View File

@ -138,6 +138,10 @@
"algorithm": "HS256",
"expires": "5m"
}
},
"plugins": {
"path": "../../../sdkjs-plugins",
"url": "../../../../sdkjs-plugins"
}
}
},

View File

@ -11,6 +11,7 @@
"config": "^1.21.0",
"dnscache": "0.0.4",
"escape-string-regexp": "^1.0.5",
"forwarded": "^0.1.0",
"ipaddr.js": "^1.2.0",
"jsonwebtoken": "^7.1.9",
"log4js": "^0.6.38",

File diff suppressed because one or more lines are too long

View File

@ -50,11 +50,13 @@ const dnscache = require('dnscache')({
const jwt = require('jsonwebtoken');
const NodeCache = require( "node-cache" );
const ms = require('ms');
var constants = require('./constants');
const constants = require('./constants');
const forwarded = require('forwarded');
var configIpFilter = config.get('services.CoAuthoring.ipfilter');
var cfgIpFilterRules = configIpFilter.get('rules');
var cfgIpFilterErrorCode = configIpFilter.get('errorcode');
const cfgIpFilterEseForRequest = configIpFilter.get('useforrequest');
var cfgExpPemStdTtl = config.get('services.CoAuthoring.expire.pemStdTTL');
var cfgExpPemCheckPeriod = config.get('services.CoAuthoring.expire.pemCheckPeriod');
var cfgTokenOutboxHeader = config.get('services.CoAuthoring.token.outbox.header');
@ -103,6 +105,7 @@ function fsStat(fsPath) {
});
});
}
exports.fsStat = fsStat;
function fsReadDir(fsPath) {
return new Promise(function(resolve, reject) {
fs.readdir(fsPath, function(err, list) {
@ -114,27 +117,40 @@ function fsReadDir(fsPath) {
});
});
}
function* walkDir(fsPath, results, optNoSubDir) {
var list = yield fsReadDir(fsPath);
for (var i = 0; i < list.length; ++i) {
var fileName = list[i];
var file = path.join(fsPath, fileName);
var stats = yield fsStat(file);
function* walkDir(fsPath, results, optNoSubDir, optOnlyFolders) {
const list = yield fsReadDir(fsPath);
for (let i = 0; i < list.length; ++i) {
const file = path.join(fsPath, list[i]);
const stats = yield fsStat(file);
if (stats.isDirectory()) {
if (optNoSubDir) {
continue;
optOnlyFolders && results.push(file);
} else {
yield* walkDir(file, results);
yield* walkDir(file, results, optNoSubDir, optOnlyFolders);
}
} else {
results.push(file);
!optOnlyFolders && results.push(file);
}
}
}
exports.listFolders = function(fsPath, optNoSubDir) {
return co(function* () {
let stats, list = [];
try {
stats = yield fsStat(fsPath);
} catch (e) {
//exception if fsPath not exist
stats = null;
}
if (stats && stats.isDirectory()) {
yield* walkDir(fsPath, list, optNoSubDir, true);
}
return list;
});
};
exports.listObjects = function(fsPath, optNoSubDir) {
return co(function* () {
var list;
var stats;
let stats, list = [];
try {
stats = yield fsStat(fsPath);
} catch (e) {
@ -143,13 +159,10 @@ exports.listObjects = function(fsPath, optNoSubDir) {
}
if (stats) {
if (stats.isDirectory()) {
list = [];
yield* walkDir(fsPath, list, optNoSubDir);
yield* walkDir(fsPath, list, optNoSubDir, false);
} else {
list = [fsPath];
list.push(fsPath);
}
} else {
list = [];
}
return list;
});
@ -170,17 +183,6 @@ exports.readFile = function(file) {
});
});
};
exports.fsStat = function(file) {
return new Promise(function(resolve, reject) {
fs.stat(file, function(err, data) {
if (err) {
reject(err);
} else {
resolve(data);
}
});
});
};
function makeAndroidSafeFileName(str) {
for (var i = 0; i < str.length; i++) {
if (-1 == ANDROID_SAFE_FILENAME.indexOf(str[i])) {
@ -589,6 +591,20 @@ function checkIpFilter(ipString, opt_hostname) {
return status;
}
exports.checkIpFilter = checkIpFilter;
function checkClientIp(req, res, next) {
let status = 0;
if (cfgIpFilterEseForRequest) {
const addresses = forwarded(req);
const ipString = addresses[addresses.length - 1];
status = checkIpFilter(ipString);
}
if (status > 0) {
res.sendStatus(status);
} else {
next();
}
}
exports.checkClientIp = checkClientIp;
function dnsLookup(hostname, options) {
return new Promise(function(resolve, reject) {
dnscache.lookup(hostname, options, function(err, addresses){
@ -655,3 +671,4 @@ function fillJwtForRequest(opt_payload) {
return jwt.sign(data, secret, options);
}
exports.fillJwtForRequest = fillJwtForRequest;
exports.forwarded = forwarded;

View File

@ -11,7 +11,6 @@
"cron": "^1.1.0",
"express": "^4.14.0",
"fakeredis": "^1.0.3",
"forwarded": "^0.1.0",
"jsonwebtoken": "^7.1.9",
"jwa": "^1.1.4",
"mime": "^1.3.4",

View File

@ -283,7 +283,7 @@ function convertRequest(req, res) {
var status = yield* convertByCmd(cmd, async, utils.getBaseUrlByRequest(req));
utils.fillResponse(req, res, status.url, status.err);
} else {
var addresses = forwarded(req);
var addresses = utils.forwarded(req);
logger.error('Error convert unknown outputtype: query = %j from = %s docId = %s', params, addresses, docId);
utils.fillResponse(req, res, undefined, constants.UNKNOWN);
}

View File

@ -30,39 +30,39 @@
*
*/
'use strict';
const cluster = require('cluster');
const configCommon = require('config');
const config = configCommon.get('services.CoAuthoring');
//process.env.NODE_ENV = config.get('server.mode');
const logger = require('./../../Common/sources/logger');
const co = require('co');
if (cluster.isMaster) {
const fs = require('fs');
const co = require('co');
//const numCPUs = require('os').cpus().length;
const license = require('./../../Common/sources/license');
//const cfgWorkerPerCpu = config.get('server.workerpercpu');
var licenseInfo, workersCount = 0;
let licenseInfo, workersCount = 0, updateTime;
const readLicense = function*() {
licenseInfo = yield* license.readLicense();
workersCount = Math.min(1, licenseInfo.count/*, Math.ceil(numCPUs * cfgWorkerPerCpu)*/);
};
const updateLicenseWorker = (worker) => {
worker.send({data: licenseInfo});
worker.send({type: 1, data: licenseInfo});
};
const updateWorkers = () => {
var i;
const arrKeyWorkers = Object.keys(cluster.workers);
if (arrKeyWorkers.length < workersCount) {
for (i = arrKeyWorkers.length; i < workersCount; ++i) {
for (let i = arrKeyWorkers.length; i < workersCount; ++i) {
const newWorker = cluster.fork();
logger.warn('worker %s started.', newWorker.process.pid);
}
} else {
for (i = workersCount; i < arrKeyWorkers.length; ++i) {
for (let i = workersCount; i < arrKeyWorkers.length; ++i) {
const killWorker = cluster.workers[arrKeyWorkers[i]];
if (killWorker) {
killWorker.kill();
@ -70,12 +70,23 @@ if (cluster.isMaster) {
}
}
};
const updatePlugins = (eventType, filename) => {
console.log('update Folder: %s ; %s', eventType, filename);
if (updateTime && 1000 >= (new Date() - updateTime)) {
return;
}
console.log('update Folder true: %s ; %s', eventType, filename);
updateTime = new Date();
for (let i in cluster.workers) {
cluster.workers[i].send({type: 2});
}
};
const updateLicense = () => {
return co(function*() {
try {
yield* readLicense();
logger.warn('update cluster with %s workers', workersCount);
for (var i in cluster.workers) {
for (let i in cluster.workers) {
updateLicenseWorker(cluster.workers[i]);
}
updateWorkers();
@ -95,16 +106,22 @@ if (cluster.isMaster) {
updateLicense();
try {
fs.watch(config.get('plugins.path'), updatePlugins);
} catch (e) {
logger.warn('Plugins watch exception (https://nodejs.org/docs/latest/api/fs.html#fs_availability).');
}
fs.watchFile(configCommon.get('license').get('license_file'), updateLicense);
setInterval(updateLicense, 86400000);
} else {
logger.warn('Express server starting...');
const express = require('express');
const http = require('http');
const urlModule = require('url');
const path = require('path');
const bodyParser = require("body-parser");
const mime = require('mime');
const forwarded = require('forwarded');
const docsCoServer = require('./DocsCoServer');
const canvasService = require('./canvasservice');
const converterService = require('./converterservice');
@ -113,43 +130,37 @@ if (cluster.isMaster) {
const constants = require('./../../Common/sources/constants');
const utils = require('./../../Common/sources/utils');
const configStorage = configCommon.get('storage');
var configIpFilter = configCommon.get('services.CoAuthoring.ipfilter');
var cfgIpFilterEseForRequest = configIpFilter.get('useforrequest');
const app = express();
var server = null;
const server = http.createServer(app);
logger.warn('Express server starting...');
server = http.createServer(app);
let userPlugins = null, updatePlugins = true;
if (config.has('server.static_content')) {
var staticContent = config.get('server.static_content');
for (var i = 0; i < staticContent.length; ++i) {
var staticContentElem = staticContent[i];
const staticContent = config.get('server.static_content');
for (let i = 0; i < staticContent.length; ++i) {
const staticContentElem = staticContent[i];
app.use(staticContentElem['name'], express.static(staticContentElem['path'], staticContentElem['options']));
}
}
if (configStorage.has('fs.folderPath')) {
var cfgBucketName = configStorage.get('bucketName');
var cfgStorageFolderName = configStorage.get('storageFolderName');
const cfgBucketName = configStorage.get('bucketName');
const cfgStorageFolderName = configStorage.get('storageFolderName');
app.use('/' + cfgBucketName + '/' + cfgStorageFolderName, (req, res, next) => {
var index = req.url.lastIndexOf('/');
const index = req.url.lastIndexOf('/');
if ('GET' === req.method && -1 != index) {
var contentDisposition = req.query['disposition'] || 'attachment';
var sendFileOptions = {
root: configStorage.get('fs.folderPath'),
dotfiles: 'deny',
headers: {
const contentDisposition = req.query['disposition'] || 'attachment';
let sendFileOptions = {
root: configStorage.get('fs.folderPath'), dotfiles: 'deny', headers: {
'Content-Disposition': contentDisposition
}
};
var urlParsed = urlModule.parse(req.url);
const urlParsed = urlModule.parse(req.url);
if (urlParsed && urlParsed.pathname) {
var filename = decodeURIComponent(path.basename(urlParsed.pathname));
const filename = decodeURIComponent(path.basename(urlParsed.pathname));
sendFileOptions.headers['Content-Type'] = mime.lookup(filename);
}
var realUrl = req.url.substring(0, index);
const realUrl = req.url.substring(0, index);
res.sendFile(realUrl, sendFileOptions, (err) => {
if (err) {
logger.error(err);
@ -161,19 +172,7 @@ if (cluster.isMaster) {
}
});
}
function checkClientIp(req, res, next) {
var status = 0;
if (cfgIpFilterEseForRequest) {
var addresses = forwarded(req);
var ipString = addresses[addresses.length - 1];
status = utils.checkIpFilter(ipString);
}
if (status > 0) {
res.sendStatus(status);
} else {
next();
}
}
// Если захочется использовать 'development' и 'production',
// то с помощью app.settings.env (https://github.com/strongloop/express/issues/936)
// Если нужна обработка ошибок, то теперь она такая https://github.com/expressjs/errorhandler
@ -185,26 +184,28 @@ if (cluster.isMaster) {
app.get('/index.html', (req, res) => {
res.send('Server is functioning normally. Version: ' + docsCoServer.version);
});
var rawFileParser = bodyParser.raw({ inflate: true, limit: config.get('server.limits_tempfile_upload'), type: '*/*' });
const rawFileParser = bodyParser.raw(
{inflate: true, limit: config.get('server.limits_tempfile_upload'), type: '*/*'});
app.get('/coauthoring/CommandService.ashx', checkClientIp, rawFileParser, docsCoServer.commandFromServer);
app.post('/coauthoring/CommandService.ashx', checkClientIp, rawFileParser, docsCoServer.commandFromServer);
app.get('/coauthoring/CommandService.ashx', utils.checkClientIp, rawFileParser, docsCoServer.commandFromServer);
app.post('/coauthoring/CommandService.ashx', utils.checkClientIp, rawFileParser,
docsCoServer.commandFromServer);
if (config.has('server.fonts_route')) {
var fontsRoute = config.get('server.fonts_route');
const fontsRoute = config.get('server.fonts_route');
app.get('/' + fontsRoute + 'native/:fontname', fontService.getFont);
app.get('/' + fontsRoute + 'js/:fontname', fontService.getFont);
app.get('/' + fontsRoute + 'odttf/:fontname', fontService.getFont);
}
app.get('/ConvertService.ashx', checkClientIp, rawFileParser, converterService.convert);
app.post('/ConvertService.ashx', checkClientIp, rawFileParser, converterService.convert);
app.get('/ConvertService.ashx', utils.checkClientIp, rawFileParser, converterService.convert);
app.post('/ConvertService.ashx', utils.checkClientIp, rawFileParser, converterService.convert);
app.get('/FileUploader.ashx', checkClientIp, rawFileParser, fileUploaderService.uploadTempFile);
app.post('/FileUploader.ashx', checkClientIp, rawFileParser, fileUploaderService.uploadTempFile);
app.get('/FileUploader.ashx', utils.checkClientIp, rawFileParser, fileUploaderService.uploadTempFile);
app.post('/FileUploader.ashx', utils.checkClientIp, rawFileParser, fileUploaderService.uploadTempFile);
var docIdRegExp = new RegExp("^[" + constants.DOC_ID_PATTERN + "]*$", 'i');
const docIdRegExp = new RegExp("^[" + constants.DOC_ID_PATTERN + "]*$", 'i');
app.param('docid', (req, res, next, val) => {
if (docIdRegExp.test(val)) {
next();
@ -223,14 +224,53 @@ if (cluster.isMaster) {
app.post('/upload/:docid/:userid/:index/:jwt?', rawFileParser, fileUploaderService.uploadImageFile);
app.post('/downloadas/:docid', rawFileParser, canvasService.downloadAs);
app.get('/healthcheck', checkClientIp, converterService.convertHealthCheck);
app.get('/healthcheck', utils.checkClientIp, converterService.convertHealthCheck);
const sendUserPlugins = (res, data) => {
res.setHeader('Content-Type', 'application/json');
res.send(JSON.stringify(data));
};
app.get('/sdkjs-plugins/config.json', utils.checkClientIp, (req, res) => {
if (userPlugins && !updatePlugins) {
sendUserPlugins(res, userPlugins);
return;
}
let pluginsPath = config.get('plugins.path');
utils.listFolders(pluginsPath, true).then((values) => {
return co(function*() {
let stats = null;
let result = [];
for (let i = 0; i < values.length; ++i) {
try {
stats = yield utils.fsStat(path.join(values[i], 'config.json'));
} catch (err) {
stats = null;
}
if (stats && stats.isFile) {
result.push(path.basename(values[i]) + '/config.json');
}
}
userPlugins = {'url': config.get('plugins.url'), 'pluginsData': result};
sendUserPlugins(res, userPlugins);
});
});
});
});
process.on('message', (msg) => {
if (!docsCoServer) {
return;
}
switch (msg.type) {
case 1:
docsCoServer.setLicenseInfo(msg.data);
break;
case 2:
updatePlugins = true;
break;
}
});
}