UNPKG

thinknode

Version:

A fast, flexible and all-in-one web framework for node.js.

994 lines (847 loc) 27.8 kB
'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;