thinknode
Version:
A fast, flexible and all-in-one web framework for node.js.
994 lines (847 loc) • 27.8 kB
JavaScript
'use strict';
exports.__esModule = true;
var _stringify = require('babel-runtime/core-js/json/stringify');
var _stringify2 = _interopRequireDefault(_stringify);
var _values = require('babel-runtime/core-js/object/values');
var _values2 = _interopRequireDefault(_values);
var _promise = require('babel-runtime/core-js/promise');
var _promise2 = _interopRequireDefault(_promise);
var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck');
var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);
var _possibleConstructorReturn2 = require('babel-runtime/helpers/possibleConstructorReturn');
var _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2);
var _inherits2 = require('babel-runtime/helpers/inherits');
var _inherits3 = _interopRequireDefault(_inherits2);
var _fs = require('fs');
var _fs2 = _interopRequireDefault(_fs);
var _url = require('url');
var _url2 = _interopRequireDefault(_url);
var _crypto = require('crypto');
var _crypto2 = _interopRequireDefault(_crypto);
var _mime = require('mime');
var _mime2 = _interopRequireDefault(_mime);
var _Base = require('../Base');
var _Base2 = _interopRequireDefault(_Base);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
/**
* normalize pathname, remove hack chars
* @param {String} pathname []
* @return {String} []
*/
var normalizePathname = function normalizePathname(pathname) {
var length = pathname.length;
var i = 0,
chr = void 0,
result = [],
value = '';
while (i < length) {
chr = pathname[i++];
if (chr === '/' || chr === '\\') {
if (value && value[0] !== '.') {
result.push(value);
}
value = '';
} else {
value += chr;
}
}
if (value && value[0] !== '.') {
result.push(value);
}
return result.join('/');
};
/**
* 分割pathname
* @param {[type]} pathname [description]
* @return {[type]} [description]
*/
/**
*
* @author richen
* @copyright Copyright (c) 2015 - <richenlin(at)gmail.com>
* @license MIT
* @version 15/11/26
*/
var splitPathName = function splitPathName(pathname) {
var ret = [];
var j = 0;
pathname = pathname.split('/');
for (var i = 0, length = pathname.length, item; i < length; i++) {
item = pathname[i].trim();
if (item) {
ret[j++] = item;
}
}
return ret;
};
/**
* 解析cookie
* @param {[type]} str [description]
* @return {[type]} [description]
*/
var cookieParse = function cookieParse() {
var str = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
var data = {};
str.split(/; */).forEach(function (item) {
var pos = item.indexOf('=');
if (pos === -1) {
return;
}
var key = item.slice(0, pos).trim();
var val = item.slice(pos + 1).trim();
if ('"' === val[0]) {
val = val.slice(1, -1);
}
// only assign once
if (undefined === data[key]) {
try {
data[key] = decodeURIComponent(val);
} catch (e) {
data[key] = val;
}
}
});
return data;
};
/**
* 格式化cookie
* @param {[type]} name [description]
* @param {[type]} value [description]
* @param {[type]} options [description]
* @return {[type]} [description]
*/
var cookieStringify = function cookieStringify(name, value, options) {
options = options || {};
var item = [name + '=' + encodeURIComponent(value)];
if (options.maxage) {
item.push('Max-Age=' + options.maxage);
}
if (options.domain) {
item.push('Domain=' + options.domain);
}
if (options.path) {
item.push('Path=' + options.path);
}
var expires = options.expires;
if (expires) {
if (!THINK.isDate(expires)) {
expires = new Date(expires);
}
item.push('Expires=' + expires.toUTCString());
}
if (options.httponly) {
item.push('HttpOnly');
}
if (options.secure) {
item.push('Secure');
}
return item.join('; ');
};
/**
* 生成uid
* @param length
* @returns {string}
*/
var cookieUid = function cookieUid(length) {
var str = _crypto2.default.randomBytes(Math.ceil(length * 0.75)).toString('base64').slice(0, length);
return str.replace(/[\+\/]/g, '_');
};
/**
* 生成cookie签名
* @param val
* @param secret
* @returns {string}
*/
var cookieSign = function cookieSign(val) {
var secret = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : '';
secret = _crypto2.default.createHmac('sha256', secret).update(val).digest('base64');
secret = secret.replace(/\=+$/, '');
return val + '.' + secret;
};
/**
* 解析cookie签名
* @param {[type]} val
* @param {[type]} secret
* @return {[type]}
*/
var cookieUnsign = function cookieUnsign(val, secret) {
var str = val.slice(0, val.lastIndexOf('.'));
return cookieSign(str, secret) === val ? str : '';
};
var _class = function (_base) {
(0, _inherits3.default)(_class, _base);
function _class() {
(0, _classCallCheck3.default)(this, _class);
return (0, _possibleConstructorReturn3.default)(this, _base.apply(this, arguments));
}
_class.prototype.init = function init(req, res) {
//bind request and response
this.req = req;
this.res = res;
//set http end flag
this.isend = false;
//content type is send
this.typesend = false;
this._get = {};
this._post = {};
this._file = {};
this._payload = null; //request payload, Buffer
this._cookie = {}; //request cookie
this._tplfile = null; //输出模板定位的模板文件
this._status = null; //http输出结束时的http状态
this._endError = null; //http输出结束时出错信息
this._sendCookie = {}; //需要发送的cookie
this._sendType = ''; //输出的content_type
this.isRestful = false;
this.isWebSocket = false;
this.runType = null;
this.group = '';
this.controller = '';
this.action = '';
this.splitPathName = splitPathName;
this.cookieStringify = cookieStringify;
this.cookieParse = cookieParse;
this.cookieUid = cookieUid;
this.cookieSign = cookieSign;
this.cookieUnsign = cookieUnsign;
};
/**
* 执行
* @param req
* @param res
* @param type
* @returns {*|{path, filename}}
*/
_class.run = function run(req, res) {
var type = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 'HTTP';
//instance of http
var http = new this(req, res);
try {
//http runtype
http.runType = type;
//set http start time
http.startTime = Date.now();
//set timeout
var timeout = THINK.config('http_timeout');
http.timeoutTimer = 0;
if (timeout) {
http.timeoutTimer = res.setTimeout(timeout * 1000, function () {
return THINK.statusAction(http, 504);
});
}
//url
http.url = req.url;
//http version
http.version = req.httpVersion;
//http method
http.method = req.method;
//http header
http.headers = req.headers;
var urlInfo = _url2.default.parse('//' + req.headers.host + req.url, true, true);
http.pathname = normalizePathname(urlInfo.pathname);
//querystring
http.query = urlInfo.query;
//hostname,contains port number
http.host = urlInfo.host;
//hostname, does not include port
http.hostname = urlInfo.hostname;
http._get = THINK.isEmpty(urlInfo.query) ? {} : urlInfo.query;
http._sendType = THINK.config('tpl_content_type');
//auto send header
if (!res.headersSent) {
res.setHeader('X-Powered-By', 'ThinkNode');
//Security
res.setHeader('X-Content-Type-Options', 'nosniff');
res.setHeader('X-XSS-Protection', '1;mode=block');
}
} catch (err) {
return THINK.statusAction(http, 500, err);
}
//invoke middleware
return THINK.run('request_begin', http).then(function () {
if (http.hasPayload()) {
//payload parse
return THINK.run('payload_parse', http).then(function () {
//payload check
return THINK.run('payload_check', http);
});
}
return _promise2.default.resolve();
}).then(function () {
//route parse
return THINK.run('route_parse', http);
}).then(function () {
return http;
}).catch(function (err) {
return THINK.statusAction(http, 500, err);
});
};
/**
* check http method is get
* @return {Boolean} []
*/
_class.prototype.isGet = function isGet() {
return this.method === 'GET';
};
/**
* check http method is post
* @return {Boolean} []
*/
_class.prototype.isPost = function isPost() {
return this.method === 'POST';
};
/**
* is ajax request
* @param {String} method []
* @return {Boolean} []
*/
_class.prototype.isAjax = function isAjax(method) {
if (method && this.method !== method.toUpperCase()) {
return false;
}
return this.headers['x-requested-with'] === 'XMLHttpRequest';
};
/**
* is pjax request
* @param {String} method []
* @return {Boolean} []
*/
_class.prototype.isPjax = function isPjax(method) {
return this.headers['x-pjax'] || false;
};
/**
* is jsonp request
* @param {String} name [callback name]
* @return {Boolean} []
*/
_class.prototype.isJsonp = function isJsonp(name) {
name = name || THINK.config('url_callback_name');
return !!this.get(name);
};
/**
* get user agent
* @return {String} []
*/
_class.prototype.userAgent = function userAgent() {
return this.headers['user-agent'] || '';
};
/**
* get page request referrer
* @param {String} host [only get referrer host]
* @return {String} []
*/
_class.prototype.referrer = function referrer(host) {
var ref = this.headers.referer || this.headers.referrer || '';
if (!ref || !host) {
return ref;
}
var info = _url2.default.parse(ref);
return info.hostname;
};
/**
* get or set get params
* @param name
* @param value
* @returns {*}
*/
_class.prototype.get = function get(name, value) {
if (!this._nGet) {
this._nGet = THINK.walkFilter(THINK.extend({}, this._get));
}
if (value === undefined) {
if (name === undefined) {
return this._nGet;
}
if (THINK.isString(name)) {
return THINK.isTrueEmpty(this._nGet[name]) ? '' : this._nGet[name];
}
// this._nGet = name;
} else {
this._nGet[name] = value;
}
return null;
};
/**
* get or set post params
* @param name
* @param value
* @returns {*}
*/
_class.prototype.post = function post(name, value) {
if (!this._nPost) {
this._nPost = THINK.walkFilter(THINK.extend({}, this._post));
}
if (value === undefined) {
if (name === undefined) {
return this._nPost;
}
if (THINK.isString(name)) {
return THINK.isTrueEmpty(this._nPost[name]) ? '' : this._nPost[name];
}
// this._nPost = name;
} else {
this._nPost[name] = value;
}
return null;
};
/**
* get post or get params
* @param {String} name []
* @return {Object | String} []
*/
_class.prototype.param = function param(name) {
if (name === undefined) {
return THINK.extend(false, this.get(), this.post());
} else {
var result = this.post(name);
return THINK.isTrueEmpty(result) ? this.get(name) : result;
}
};
/**
* get or set file data
* @param name
* @param value
* @returns {*}
*/
_class.prototype.file = function file(name, value) {
if (value === undefined) {
if (name === undefined) {
return this._file;
}
return this._file[name] !== undefined ? this._file[name] : {};
}
this._file[name] = value;
return null;
};
/**
* get or set header
* @param name
* @param value
* @returns {*}
*/
_class.prototype.header = function header(name, value) {
if (name === undefined) {
return this.headers;
} else if (value === undefined) {
return this.headers[name.toLowerCase()] || '';
}
//check content type is send
if (name.toLowerCase() === 'content-type') {
if (this.typesend) {
return null;
}
this.typesend = true;
}
//set header
if (!this.res.headersSent) {
this.res.setHeader(name, value);
}
return null;
};
/**
* get or set content type
* @param contentType
* @param encoding
* @returns {string|*|string}
*/
_class.prototype.type = function type(contentType, encoding) {
if (!contentType) {
return (this.headers['content-type'] || '').split(';')[0].trim();
}
if (this.typesend) {
return null;
}
if (contentType.indexOf('/') === -1) {
contentType = _mime2.default.lookup(contentType);
}
this._sendType = contentType;
if (encoding !== false && contentType.toLowerCase().indexOf('charset=') === -1) {
contentType += '; charset=' + (encoding || THINK.config('encoding'));
}
this.header('Content-Type', contentType);
return null;
};
/**
* set http status
* @param status
*/
_class.prototype.status = function status() {
var _status = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 200;
var res = this.res;
if (!res.headersSent) {
this._status = _status;
res.statusCode = _status;
}
return null;
};
/**
* get or set cookie
* @param name
* @param value
* @param options
* @returns {*}
*/
_class.prototype.cookie = function cookie(name, value, options) {
//send cookies
if (name === true) {
if (THINK.isEmpty(this._sendCookie)) {
return null;
}
var cookies = (0, _values2.default)(this._sendCookie).map(function (item) {
return cookieStringify(item.name, item.value, item);
});
this.header('Set-Cookie', cookies);
this._sendCookie = {};
return null;
}
//parse cookie
if (THINK.isEmpty(this._cookie) && this.headers.cookie) {
this._cookie = this.cookieParse(this.headers.cookie);
}
if (name === undefined) {
return this._cookie;
} else if (value === undefined) {
return this._cookie[name] !== undefined ? this._cookie[name] : '';
}
//set cookie
if (typeof options === 'number') {
options = { timeout: options };
}
options = THINK.extend(false, {
domain: THINK.config('cookie_domain'), //cookie有效域名
path: THINK.config('cookie_path'), //cookie路径
timeout: THINK.config('cookie_timeout') }, options);
if (value === null) {
options.timeout = -1000;
}
if (options.timeout !== 0) {
options.expires = new Date(Date.now() + options.timeout * 1000);
}
if (options.timeout > 0) {
options.maxage = options.timeout;
}
options.name = name;
options.value = value;
this._sendCookie[name] = options;
return null;
};
/**
* redirect
* @param urls
* @param code
* @returns {*|{path, filename}}
*/
_class.prototype.redirect = function redirect(urls, code) {
this.header('Location', urls || '/');
return THINK.statusAction(this, code || 302);
};
/**
* send execute time
* @param name
*/
_class.prototype.sendTime = function sendTime(name) {
var time = Date.now() - this.startTime;
this.header('X-' + (name || 'EXEC-TIME'), time + 'ms');
return null;
};
/**
* get uesr ip
* @param forward
* @returns {*}
*/
_class.prototype.ip = function ip(forward) {
var proxy = THINK.config('use_proxy') || this.host === this.hostname;
var _ip = void 0;
if (proxy) {
if (forward) {
return (this.req.headers['x-forwarded-for'] || '').split(',').filter(function (item) {
item = item.trim();
if (THINK.isIP(item)) {
return item;
}
return '';
});
}
_ip = this.req.headers['x-real-ip'];
} else {
var connection = this.req.connection;
var socket = this.req.socket;
if (connection && connection.remoteAddress !== '127.0.0.1') {
_ip = connection.remoteAddress;
} else if (socket && socket.remoteAddress !== '127.0.0.1') {
_ip = socket.remoteAddress;
}
}
if (!_ip) {
return '127.0.0.1';
}
if (_ip.indexOf(':') > -1) {
_ip = _ip.split(':').slice(-1)[0];
}
if (!THINK.isIP(_ip)) {
return '127.0.0.1';
}
return _ip;
};
/**
* set cache-control and expires header
* @param time
*/
_class.prototype.expires = function expires(time) {
time = time * 1000;
var date = new Date(Date.now() + time);
this.header('Cache-Control', 'max-age=' + time);
this.header('Expires', date.toUTCString());
return null;
};
/**
* get or check token
* @param check
* @returns {*}
*/
_class.prototype.token = function token() {
var _this2 = this;
var check = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
var tokenOn = THINK.config('token_on');
if (check) {
if (tokenOn) {
var tokenName = THINK.config('token_name');
return this.session(tokenName).catch(function () {
return null;
}).then(function (value) {
var formValue = _this2.param(tokenName);
if (value === formValue) {
//token匹配方式 http每次请求token不同, session在session有效期内token相同
if (THINK.config('token_type') === 'http') {
//匹配完成后清除token
_this2.session(tokenName, null);
}
return true;
}
return false;
});
}
return true;
} else {
if (tokenOn) {
return this.session(THINK.config('token_name')).catch(function () {
return null;
}).then(function (value) {
if (!value) {
value = _this2.cookieUid(32);
_this2.session(THINK.config('token_name'), value);
}
return value;
});
}
return '';
}
};
/**
* get or set session
* @param name
* @param value
* @param timeout
* @returns {*}
*/
_class.prototype.session = function session(name, value, timeout) {
this.sessionStore(this);
if (!this._session) {
return null;
}
if (name === undefined) {
return this._session.rm();
}
try {
if (value !== undefined) {
timeout = THINK.isNumber(timeout) ? timeout : THINK.config('session_timeout');
return this._session.set(name, value, timeout);
} else {
return this._session.get(name);
}
} catch (e) {
return null;
}
};
/**
* check request has post data
* @returns {boolean}
*/
_class.prototype.hasPayload = function hasPayload() {
if ('transfer-encoding' in this.req.headers) {
return true;
}
return (this.req.headers['content-length'] | 0) > 0;
};
/**
* get payload data
* @param encoding
* @returns {Promise.<TResult>}
*/
_class.prototype.getPayload = function getPayload() {
var _this3 = this;
var encoding = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'utf8';
var _getPayload = function _getPayload() {
if (_this3.payload) {
return _promise2.default.resolve(_this3.payload);
}
if (!_this3.req.readable) {
return _promise2.default.resolve(new Buffer(0));
}
var buffers = [];
var deferred = THINK.getDefer();
_this3.req.on('data', function (chunk) {
buffers.push(chunk);
});
_this3.req.on('end', function () {
_this3.payload = Buffer.concat(buffers);
deferred.resolve(_this3.payload);
});
_this3.req.on('error', function () {
return THINK.statusAction(_this3, 400);
});
return deferred.promise;
};
return _getPayload().then(function (buffer) {
return encoding === true ? buffer : buffer.toString(encoding);
});
};
/**
*
* @param obj
* @param encoding
* @returns {type[]}
* @private
*/
_class.prototype.write = function write(obj, encoding) {
if (!this.res.connection || this.isend) {
return null;
}
try {
//send cookie
this.cookie(true);
//send header
this.type(this._sendType);
//write content
if (obj === undefined || obj === null || THINK.isPromise(obj)) {
return null;
}
if (THINK.isArray(obj) || THINK.isObject(obj)) {
obj = (0, _stringify2.default)(obj);
}
if (!THINK.isString(obj) && !THINK.isBuffer(obj)) {
obj += '';
}
if (THINK.isBuffer(obj)) {
return this.res.write(obj);
} else {
return this.res.write(obj, encoding || THINK.config('encoding'));
}
} catch (err) {
THINK.log(err);
return null;
}
};
/**
* http response end
* @param obj
* @param encoding
* @return {*|{path, filename}}
*/
_class.prototype.end = function end(obj, encoding) {
//write content
this.write(obj, encoding);
if (this.timeoutTimer) {
clearTimeout(this.timeoutTimer);
this.timeoutTimer = 0;
}
this.isend = true;
!this.isWebSocket && this.res && this.res.end();
return this._endError ? this.afterEnd(this._status || 500, this._endError) : this.afterEnd();
};
/**
* http info print
* @param status
* @param msg
* @param type
*/
_class.prototype.afterEnd = function afterEnd() {
var status = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 200;
var _this4 = this;
var msg = arguments[1];
var type = arguments[2];
if (!this.isend) {
this.isend = true;
!this.isWebSocket && this.res && this.res.end();
}
process.nextTick(function () {
try {
//错误输出
msg && THINK.error(msg, false);
//控制台输出
type = type || _this4.runType;
THINK.log(_this4.req.method.toUpperCase() + ' ' + status + ' ' + (_this4.url || '/'), type, _this4.startTime);
//remove temp upload file
if (THINK.config('post_file_autoremove') && !THINK.isEmpty(_this4.file)) {
var key = void 0,
path = void 0,
fn = function fn() {};
for (key in _this4.file) {
path = _this4.file[key].path;
if (THINK.isFile(path)) {
_fs2.default.unlink(path, fn);
}
}
}
} catch (e) {
THINK.log(e);
}
});
return THINK.getDefer().promise;
};
/**
* session驱动
* @param http
* @returns {cls|*}
*/
_class.prototype.sessionStore = function sessionStore(http) {
//if session is init, return
if (http._session) {
return http._session;
}
var sessionName = THINK.config('session_name');
var sessionSign = THINK.config('session_sign');
//validate cookie sign
var cookie = http.cookie(sessionName);
if (cookie && sessionSign) {
cookie = http.cookieUnsign(cookie, sessionSign);
//set cookie to http._cookie
if (cookie) {
http._cookie[sessionName] = cookie;
}
}
var sessionCookie = cookie;
//generate session cookie when cookie is not set
if (!cookie) {
cookie = http.cookieUid(32);
sessionCookie = cookie;
//sign cookie
if (sessionSign) {
cookie = http.cookieSign(cookie, sessionSign);
}
//将生成的sessionCookie放在http._cookie对象上,方便程序内读取
http._cookie[sessionName] = sessionCookie;
http.cookie(sessionName, cookie, { length: 32, httponly: true });
}
//sessionStore
var driver = THINK.ucFirst(THINK.config('session_type'));
http._session = THINK.adapter(driver + 'Session', {
cache_path: THINK.isEmpty(THINK.config('session_path')) ? THINK.CACHE_PATH : THINK.config('session_path'),
cache_key_prefix: sessionCookie,
cache_timeout: THINK.config('session_timeout')
});
return http._session;
};
return _class;
}(_Base2.default);
exports.default = _class;