cloudcmd/lib/server/ponse.js

324 lines
9.6 KiB
JavaScript

(function() {
'use strict';
var fs = require('fs'),
zlib = require('zlib'),
url = require('url'),
querystring = require('querystring'),
DIR_JSON = __dirname + '/../../json/',
Util = require('../util'),
pipe = require('./pipe'),
ext = require(DIR_JSON + 'ext'),
OK = 200,
RANGE = 206,
MOVED_PERMANENTLY = 301,
FILE_NOT_FOUND = 404,
REQUEST = 'request',
RESPONSE = 'response';
exports.redirect = redirect;
exports.send = send;
exports.sendError = sendError;
exports.sendFile = sendFile;
exports.isGZIP = isGZIP;
exports.getPathName = getPathName;
exports.getQuery = getQuery;
exports.setHeader = setHeader;
/* Функция высылает ответ серверу
* @param data
* @param params
* @param notLog
*/
function send(data, params, notLog) {
var p, isGzip, head,
ret = checkParams(params);
if (ret) {
p = params;
data = data;
isGzip = p.gzip && isGZIP(p.request);
head = generateHeaders({
name : p.name,
cache : p.cache,
gzip : isGzip,
query : p.query
});
fillHeader(head, p.response);
if (!notLog)
Util.log(data);
/* если браузер поддерживает gzip-сжатие - сжимаем данные*/
Util.exec.if(!isGzip,
function() {
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.message;
}
callback();
});
});
}
}
/**
* Функция создаёт заголовки файлов
* в зависимости от расширения файла
* перед отправкой их клиенту
* @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(extension, '.appcache');
if (!p.cache || cmp)
maxAge = 0;
header['Cache-Control'] = 'max-age=' + maxAge;
if (p.gzip)
header['Content-Encoding'] = 'gzip';
}
return header;
}
function setHeader(pParams) {
var p, header, gzip,
lRet = checkParams(pParams);
if (lRet) {
p = pParams;
gzip = 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 : gzip,
query : getQuery(p.request)
});
fillHeader(header, p.response);
p.response.statusCode = p.status || OK;
}
}
function fillHeader(header, response) {
var isObject = Util.isObject(header),
isSent = response.headersSent;
if (!isSent && isObject)
Object.keys(header).forEach(function(name) {
response.setHeader(name, header[name]);
});
}
/**
* 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(error, params);
} 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
});
setHeader(params);
options = {
gzip : isGzip,
range : range
};
pipe.create(p.name, p.response, options, function(error) {
if (error)
sendError(error, params);
});
}
});
}
/**
* send error response
*/
function sendError(error, params) {
var data, ret = checkParams(params);
if (ret) {
params.status = FILE_NOT_FOUND;
data = error.message || '' + error;
send(data, params);
}
}
function checkParams(params) {
var ret = true;
Util.checkArgs(arguments, ['params']);
Util.checkArgs([params.name, params.request, params.response], ['name', 'requst', 'response']);
return ret;
}
function getQuery(req) {
var query, parsed;
Util.checkArgs(arguments, ['req']);
parsed = url.parse(req.url);
query = parsed.query;
return query;
}
function getPathName(req) {
var pathname, parsed;
Util.checkArgs(arguments, ['req']);
parsed = url.parse(req.url);
pathname = parsed.pathname;
/* supporting of Russian language in directory names */
pathname = querystring.unescape(pathname);
return pathname;
}
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;
}
function isGZIP(req) {
var enc, is;
if (req) {
enc = req.headers['accept-encoding'] || '';
is = enc.match(/\bgzip\b/);
}
return is;
}
/**
* redirect to another URL
*/
function redirect(url, response) {
var header = {
'Location': url
};
Util.checkArgs(arguments, ['url', 'response']);
setHeader(header, response);
response.statusCode = MOVED_PERMANENTLY;
response.end();
}
})();