cloudcmd/lib/server/main.js
2014-06-24 06:36:08 -04:00

473 lines
16 KiB
JavaScript

(function() {
'use strict';
/* Global var accessible from any loaded module */
global.cloudcmd = {};
var DIR, LIBDIR, SRVDIR, JSONDIR, HTMLDIR,
Util,
UTIL = 'util',
SLASH,
ISWIN32,
ext,
path, fs, zlib, url, pipe, CloudFunc, diffPatch, querystring,
OK, RANGE, FILE_NOT_FOUND, MOVED_PERMANENTLY,
REQUEST, RESPONSE,
Config = {
server : true,
socket : true,
port : 80
};
/* Consts */
exports.OK = OK = 200;
RANGE = 206;
MOVED_PERMANENTLY = 301;
exports.FILE_NOT_FOUND = FILE_NOT_FOUND = 404;
exports.REQUEST = REQUEST = 'request';
exports.RESPONSE = RESPONSE = 'response';
/* Native Modules*/
exports.crypto = require('crypto'),
exports.child_process = require('child_process'),
exports.fs = fs = require('fs'),
exports.http = require('http'),
exports.https = require('https'),
exports.path = path = require('path'),
exports.url = url = require('url'),
exports.querystring = querystring = require('querystring'),
/* Constants */
/* current dir + 2 levels up */
exports.WIN32 = ISWIN32 = isWin32();
exports.SLASH = SLASH = '/',
exports.SRVDIR = SRVDIR = __dirname + SLASH,
exports.LIBDIR = LIBDIR = path.normalize(SRVDIR + '../'),
exports.DIR = DIR = path.normalize(LIBDIR + '../'),
exports.HTMLDIR = HTMLDIR = DIR + 'html' + SLASH,
exports.JSONDIR = JSONDIR = DIR + 'json' + SLASH,
/* Functions */
exports.require = mrequire,
exports.librequire = librequire,
exports.srvrequire = srvrequire,
exports.rootrequire = rootrequire,
exports.quietrequire = quietrequire,
exports.generateHeaders = generateHeaders,
exports.getQuery = getQuery,
exports.getPathName = getPathName,
exports.isGZIP = isGZIP,
exports.mainSetHeader = mainSetHeader,
exports.sendFile = sendFile,
exports.sendResponse = sendResponse,
exports.sendError = sendError,
exports.redirect = redirect,
exports.checkParams = checkParams,
exports.checkCallBackParams = checkCallBackParams,
/* compitability with old versions of node */
exports.fs.exists = exports.fs.exists || exports.path.exists,
/* Needed Modules */
/* we can not use librequare here */
exports.util = Util = require(LIBDIR + UTIL),
exports.zlib = zlib = mrequire('zlib'),
/* Main Information */
exports.modules = jsonrequire('modules');
exports.ext = ext = jsonrequire('ext');
exports.mainpackage = rootrequire('package');
/* base configuration */
exports.config = Config,
/*
* Any of loaded below modules could work with global var so
* it should be initialized first. Becouse of almost any of
* moudles do not depends on each other all needed information
* for all modules is initialized hear.
*/
global.cloudcmd.main = exports;
/* Additional Modules */
exports.cloudfunc = CloudFunc = librequire('cloudfunc'),
exports.pipe = pipe = srvrequire('pipe'),
exports.socket = srvrequire('socket'),
exports.console = srvrequire('console'),
exports.terminal = !ISWIN32 ? srvrequire('terminal') : {
init: function() {}
},
exports.express = srvrequire('express'),
exports.auth = srvrequire('auth').auth,
exports.appcache = srvrequire('appcache'),
diffPatch = librequire('diff/diff-match-patch').diff_match_patch,
exports.diff = new (librequire('diff').DiffProto)(diffPatch),
exports.rest = srvrequire('rest').api,
exports.update = srvrequire('update'),
exports.ischanged = srvrequire('ischanged');
exports.files = srvrequire('files');
exports.minify = srvrequire('minify').Minify;
/* second initializing after all modules load, so global var is *
* totally filled of all information that should know all modules */
global.cloudcmd.main = exports;
/**
* function do safe require of needed module
* @param {Strin} src
*/
function mrequire(src) {
var module, msg,
error = Util.exec.try(function() {
module = require(src);
});
if (error)
if (error.code === 'MODULE_NOT_FOUND')
msg = CloudFunc.formatMsg('require', src, 'no');
else
Util.log(error);
Util.log(msg);
return module;
}
function quietrequire(src) {
var module;
Util.exec.try(function() {
module = require(src);
});
return module;
}
function rootrequire(src) { return mrequire(DIR + src); }
function librequire(src) { return mrequire(LIBDIR + src); }
function srvrequire(src) { return mrequire(SRVDIR + src); }
function jsonrequire(src) { return mrequire(JSONDIR + src);}
/**
* function check is current platform is win32
*/
function isWin32() { return process.platform === 'win32'; }
/**
* Функция создаёт заголовки файлов
* в зависимости от расширения файла
* перед отправкой их клиенту
* @param pParams
* name - имя файла
* gzip - данные сжаты gzip'ом
* query
* https://developers.google.com/speed/docs/best-practices/caching?hl=ru#LeverageProxyCaching
*/
function generateHeaders(params) {
var header, p, extension, type, encoding, isContain, cmp,
maxAge = 31337 * 21;
if (params.name) {
p = params,
extension = Util.getExt(p.name),
type = ext[extension] || 'text/plain',
encoding = '';
/* if type of file any, but img - then we shoud specify charset */
isContain = Util.isContainStr(type, ['img', 'image', 'audio']);
if (!isContain)
encoding = '; charset=UTF-8';
isContain = Util.isContainStr(p.query, 'download');
if (isContain)
type = 'application/octet-stream';
header = {
'Access-Control-Allow-Origin' : '*',
'Content-Type' : type + encoding,
'Vary' : 'Accept-Encoding',
'Accept-Ranges' : 'bytes'
};
if (p.time)
Util.copyObj(header, {
'Last-Modified' : p.time
});
if (p.range)
Util.copyObj(header, {
'Content-Range' : 'bytes ' + p.range.start +
'-' + p.range.end +
'/' + p.range.sizeTotal,
'Content-Length': p.range.size
});
cmp = Util.strCmp(ext, '.appcache');
if (!p.cache || cmp)
maxAge = 0;
header['Cache-Control'] = 'max-age=' + maxAge;
if (p.gzip)
header['Content-Encoding'] = 'gzip';
}
return header;
}
function mainSetHeader(pParams) {
var p, header, lGzip,
lRet = checkParams(pParams);
if (lRet) {
p = pParams;
lGzip = p.isGzip || isGZIP(p.request) && p.gzip;
header = generateHeaders({
name : p.name,
time : p.time,
range : p.range,
length : p.length,
cache : p.cache,
gzip : lGzip,
query : getQuery(p.request)
});
setHeader(header, p.response);
p.response.statusCode = p.status || OK;
}
}
/**
* send file to client thru pipe
* and gzip it if client support
*
*/
function sendFile(params) {
var p = params,
ret = checkParams(params);
if (ret)
fs.lstat(p.name, function(error, stat) {
var time, length, range, isGzip,
options = {};
if (error) {
sendError(params, error);
} else {
isGzip = isGZIP(p.request) && p.gzip;
time = stat.mtime,
length = stat.size,
range = getRange(p.request, length);
if (range)
Util.copyObj(p, {
range : range,
status : RANGE
});
Util.copyObj(p, {
time : time
});
mainSetHeader(params);
options = {
gzip : isGzip,
range : range
};
pipe.create(p.name, p.response, options, function(error) {
if (error)
sendError(params, error);
});
}
});
}
/**
* Функция высылает ответ серверу
* @param pHead - заголовок
* @param Data - данные
* @param pName - имя отсылаемого файла
*/
function sendResponse(params, data, notLog) {
var p, isGzip, head,
ret = checkParams(params);
if (ret) {
p = params;
data = p.data || data;
isGzip = isGZIP(p.request);
head = generateHeaders({
name : p.name,
cache : p.cache,
gzip : isGzip,
query : p.query
});
setHeader(head, p.response);
if (!notLog)
Util.log(data);
/* если браузер поддерживает gzip-сжатие - сжимаем данные*/
Util.exec.if(!isGzip,
function() {
p.status = params.status || p.status;
if (!p.data)
p.data = data;
p.response.statusCode = p.status || OK;
p.response.end(p.data);
},
function(callback) {
zlib.gzip (data, function(error, data) {
if (!error)
p.data = data;
else {
p.status = FILE_NOT_FOUND;
p.data = error.toString();
}
Util.exec(callback, p);
});
});
}
}
/**
* redirect to another URL
*/
function redirect(pParams) {
var p, header,
lRet = Util.checkObjTrue(pParams, [RESPONSE]);
if (lRet) {
p = pParams;
header = {
'Location': p.url
};
setHeader(header, p.response);
p.response.statusCode = MOVED_PERMANENTLY;
p.response.end();
}
}
/**
* send error response
*/
function sendError(params, error) {
var p, ret = checkParams(params);
if (ret) {
p = params;
p.status = FILE_NOT_FOUND;
if (!p.data && error)
p.data = error.toString();
sendResponse(p);
}
}
function checkCallBackParams(pParams) {
return Util.checkObj(pParams, ['error', 'data', 'params']);
}
function checkParams(params, additional) {
var ret = Util.checkObjTrue(params, ['name', REQUEST, RESPONSE]);
if (ret && additional)
ret = Util.checkObjTrue(params, additional);
return ret;
}
function getQuery(req) {
var query, parsed;
if (req) {
parsed = url.parse(req.url);
query = parsed.query;
}
return query;
}
function getPathName(req) {
var pathname, parsed;
if (req) {
parsed = url.parse(req.url);
pathname = parsed.pathname;
/* supporting of Russian language in directory names */
pathname = querystring.unescape(pathname);
}
return pathname;
}
function isGZIP(pReq) {
var lEnc, lGZIP;
if (pReq) {
lEnc = pReq.headers['accept-encoding'] || '';
lGZIP = lEnc.match(/\bgzip\b/);
}
return lGZIP;
}
function setHeader(header, response) {
var name;
if (!response.headersSent && Util.isObject(header))
for (name in header)
response.setHeader(name, header[name]);
}
function getRange(req, sizeTotal) {
var range, start, end, size, parts,
rangeStr = req.headers.range;
if (rangeStr) {
parts = rangeStr.replace(/bytes=/, '').split('-');
start = parts[0];
end = parts[1] || sizeTotal - 1;
size = (end - start) + 1;
range = {
start : start - 0,
end : end - 0,
size : size,
sizeTotal : sizeTotal
};
}
return range;
}
})();