UNPKG

hiproxy

Version:

hiproxy - lightweight and powerful proxy tool for front-end developer based on Node.js.

338 lines (287 loc) 9.9 kB
/** * @file rewrite指令 * @author zdying */ var fs = require('fs'); var path = require('path'); var querystring = require('querystring'); var setHeader = require('./setHeader'); var getMimeType = require('simple-mime')('application/octet-stream'); // https://nodejs.org/dist/latest-v6.x/docs/api/http.html#http_message_headers var FIELDS_SHOULD_BE_OVERWRITTEN = [ 'age', 'authorization', 'content-length', 'content-type', 'etag', 'expires', 'from', 'host', 'if-modified-since', 'if-unmodified-since', 'last-modified', 'location', 'max-forwards', 'proxy-authorization', 'referer', 'retry-after', 'user-agent' ]; module.exports = { // proxy request config 'proxy_set_header': function (key, value) { log.debug('proxy_set_header -', key, value); var headers = this.req.headers; var keyLower = key.toLowerCase(); var oldValue = headers[keyLower]; if (keyLower in headers) { if (FIELDS_SHOULD_BE_OVERWRITTEN.indexOf(keyLower) !== -1) { // re-set the value headers[keyLower] = value; } else if (keyLower === 'set-cookie') { oldValue.push(value); } else { headers[keyLower] = oldValue + ', ' + value; } } else { if (keyLower === 'set-cookie') { headers[keyLower] = [value]; } else { headers[keyLower] = value; } } }, 'proxy_hide_header': function (key) { var keys = [].slice.call(arguments, 0); var headers = this.req.headers; log.debug('proxy_hide_header -', keys.join(',')); keys.forEach(function (key) { delete headers[key.toLowerCase()]; }); }, 'proxy_set_cookie': function (key, value) { log.debug('proxy_set_cookie -', key, value); var str = key + '=' + (value || ''); var headers = this.req.headers; var cookie = headers.cookie || ''; headers.cookie = (cookie ? cookie + '; ' : '') + str; }, 'proxy_hide_cookie': function (key) { var headers = this.req.headers; var cookie = headers.cookie; var keys = [].slice.call(arguments, 0); var cookies = []; var newCookie = []; log.debug('proxy_hide_cookie -', keys.join(',')); if (cookie) { cookies = cookie.split('; '); } if (arguments.length === 0) { headers.cookie = ''; } else { cookies.forEach(function (_cookie) { if (keys.indexOf(_cookie.split('=')[0]) === -1) { newCookie.push(_cookie); } }); headers.cookie = newCookie.join('; '); } }, 'proxy_method': function (key) { log.debug('proxy_method -', key); this.proxy.method = key.toUpperCase(); }, 'proxy_set_body': function (body) { log.debug('proxy_set_body -', body); this.req.body = body; }, 'proxy_replace_body': function (oldValue, newValue, flag) { log.debug('proxy_replace_body -', oldValue, newValue); var body = this.req.body || ''; var reg = /^[igm]+$/; if (flag && !reg.test(flag)) { log.warn('Invalid `proxy_replace_body` replace flag : ' + flag + ', the valid flags is `i`(ignore case) and `g`(global).'); flag = ''; } oldValue = new RegExp(oldValue, flag); this.req.body = String(body).replace(oldValue, newValue); }, 'proxy_append_body': function (key, value) { log.debug('proxy_append_body -', key, value); var headers = this.req.headers || {}; var contentType = headers['content-type']; var body = this.req.body; var formURL = 'application/x-www-form-urlencoded'; var formData = 'multipart/form-data'; var json = 'application/json'; if (contentType.indexOf(json) !== -1) { // TODO support key path (`a.b.c`) body = JSON.parse(body.toString() || '{}'); body[key] = value; body = JSON.stringify(body); } else if (contentType.indexOf(formURL) !== -1 || contentType.indexOf(formData) !== -1) { body = querystring.parse(body.toString() || ''); body[key] = value; body = querystring.stringify(body); } this.req.body = body; }, 'proxy_timeout': function (value) { var timeout = Number(value); if (Number.isNaN(timeout)) { log.warn('Invalid `proxy_timeout` directive value: ' + value + ', the value should be a number.'); } else { this.proxy.timeout = timeout; } }, // response config 'hide_cookie': function () { var self = this; var keys = [].slice.call(arguments, 0); log.debug('hide_cookie -', keys.join(',')); // TODO hide all cookie when key in empty keys.forEach(function (key) { setHeader(self.res, 'Set-Cookie', key + '=; Expires=' + new Date(1).toGMTString()); }); }, 'hide_header': function (key) { var keys = [].slice.call(arguments, 0); var ctx = this; log.debug('hide_header -', keys.join(',')); keys.forEach(function (key) { ctx.res.removeHeader(key); }); }, 'set_header': function (key, value) { log.debug('set_header -', key, value); setHeader(this.res, key, value); }, 'set_cookie': function (key, value) { log.debug('set_cookie -', key, value); // TODO expires support setHeader(this.res, 'Set-Cookie', key + '=' + value); }, 'echo': function () { this.res.write([].join.call(arguments, ' ')); }, 'send_file': function (value) { var filePath = ''; var res = this.res; if (path.isAbsolute(value)) { // absolute path filePath = value; } else { // relative path var currentFilePath = this.rewriteRule.extends.filePath; var dirname = path.dirname(currentFilePath); filePath = path.join(dirname, value); } return new Promise(function (resolve, reject) { fs.readFile(filePath, 'utf-8', function (err, data) { if (err) { log.error(err); data = 'File send error: <br/>' + err.stack; res.setHeader('Content-Type', 'text/html'); res.statusCode = err.code === 'ENOENT' ? 404 : 500; // res.writeHead(err.code === 'ENOENT' ? 404 : 500, { // 'Content-Type': 'text/html' // }); } else { if (!res.getHeader('content-type')) { res.setHeader('Content-Type', getMimeType(filePath)); } } // TODO fix bug:调用两次的后果 res.end(data); // self.res.write(data); resolve(data); }); }); }, 'sub_filter': function (oldValue, newValue) { // TODO 支持第三个参数,flag => igm var body = this.res.body; var source = oldValue; var variables = this.rewriteRule.variables; var res = this.res; var contentType = (res.getHeader('Content-Type') || '').split(/\s*;\s*/)[0]; var subFilterTypes = variables.sub_filter_types || '*'; var subFilterOnce = variables.sub_filter_once; var subFilterLastModified = variables.sub_filter_last_modified; var needReplace = subFilterTypes === '*' || subFilterTypes.indexOf(contentType) !== -1; if (body && needReplace) { // if `sub_filter_once` is `off`, replace all the `oldValue`; if (subFilterOnce && subFilterOnce === 'off') { source = new RegExp('(' + source + ')', 'g'); } this.res.body = body.toString().replace(source, newValue); // if `sub_filter_last_modified` is NOT `on`, remove the `Last-Modified` header. if (subFilterLastModified !== 'on') { res.removeHeader('last-modified'); } } }, 'sub_filter_once': function (value) { if (/^(on|off)$/.test(value)) { this.rewriteRule.variables.sub_filter_once = value; } else { log.warn('Invalid `sub_filter_once` directive value: ' + value + ', the value should be `on` or `off`.'); } }, 'sub_filter_last_modified': function (value) { if (/^(on|off)$/.test(value)) { this.rewriteRule.variables.sub_filter_last_modified = value; } else { log.warn('Invalid `sub_filter_last_modified` directive value: ' + value + ', the value should be `on` or `off`.'); } }, 'sub_filter_types': function () { var types = [].slice.call(arguments, 0); var isAllType = types.indexOf('*') !== -1; this.rewriteRule.variables.sub_filter_types = isAllType ? '*' : types; }, /** * Set the response status code and status message. * * @param {Number} code The status code. * @param {String} [message] A optional human-readable status message. */ 'status': function (code, message) { this.res.statusCode = code; this.res.statusMessage = message; }, // location commands 'proxy_pass': function (value) { if (this.variables.$alias !== true) { this.variables.$proxy_pass = value; } }, 'alias': function (value) { this.variables.$alias = true; if (path.isAbsolute(value)) { // absolute path this.variables.$proxy_pass = value; } else { // relative path var currentFilePath = this.extends.filePath; var dirname = path.dirname(currentFilePath); this.variables.$proxy_pass = path.join(dirname, value); } }, 'root': function (value) { this.variables.$default = value; }, // domain commands 'ssl_certificate': function (value) { var filePath = ''; if (path.isAbsolute(value)) { // absolute path filePath = value; } else { // relative path var rewriteFilePath = this.extends.filePath; var dirname = path.dirname(rewriteFilePath); filePath = path.join(dirname, value); } this.variables.$ssl_certificate = filePath; }, 'ssl_certificate_key': function (value) { var filePath = ''; if (path.isAbsolute(value)) { // absolute path filePath = value; } else { // relative path var rewriteFilePath = this.extends.filePath; var dirname = path.dirname(rewriteFilePath); filePath = path.join(dirname, value); } this.variables.$ssl_certificate_key = filePath; } };