UNPKG

hiproxy

Version:

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

384 lines (327 loc) 10 kB
/** * @file Proxy Server * @author zdying */ require('colors'); var url = require('url'); var path = require('path'); var EventEmitter = require('events'); var openBrowser = require('op-browser'); var Hosts = require('./hosts'); var Rewrite = require('./rewrite'); var getLocalIP = require('./helpers/getLocalIP'); var Logger = require('./logger'); var createPacFile = require('./helpers/createPacFile'); var showImage = require('./helpers/showImage'); var dirtool = require('./helpers/dirTool'); var initFlow = require('./flows/initialize'); var CALLBACK_NAMES = ['onBeforeRequest', 'onBeforeResponse', 'onData', 'onError']; // global.log = log; /** * hiproxy代理服务器 * @param {Object} options 配置参数 * @param {Number} options.httpPort http代理服务端口号 * @param {Number} options.httpsPort https代理服务器端口号 * @param {String} options.dir 指定的工作路径 * @extends EventEmitter * @constructor */ function ProxyServer (options) { EventEmitter.call(this); var httpPort = 0; var httpsPort = 0; var dir = process.cwd(); // TODO 更新文档,说明参数 if (options && typeof options === 'object') { httpPort = options.httpPort; httpsPort = options.httpsPort; dir = options.dir || dir; this.options = options; } else { httpPort = arguments[0]; httpsPort = arguments[1]; dir = arguments[2] || dir; this.options = { httpPort: httpPort, httpsPort: httpsPort, dir: dir }; } this.hosts = new Hosts(); this.rewrite = new Rewrite(); this.logger = new Logger(/* process.stdout, process.stderr */); // 如果没有指定httpPort, 默认随机分配 this.httpPort = httpPort || 0; this.httpServer = null; // 如果没有指定httpsPort,默认不随机分配 this.httpsPort = httpsPort; this.httpsServer = null; this.dir = dir; this._initCallbacks(); } ProxyServer.prototype = { constructor: ProxyServer, // extends from EventEmitter __proto__: EventEmitter.prototype, _initCallbacks: function () { var callbackNames = CALLBACK_NAMES; var options = this.options; callbackNames.forEach(function (cbk) { var cbkFn = options[cbk] || (options[cbk] = []); if (!Array.isArray(cbkFn)) { options[cbk] = [cbkFn]; } }); }, /** * 启动代理服务 * * @param {Object} config 配置字段 * @return {Promise} * @public */ start: function (config) { var hiproxy = this; var ip = getLocalIP(); hiproxy.localIP = ip; hiproxy.lastConfig = config || {}; return new Promise(function (resolve, reject) { initFlow.use(function (ctx, next) { resolve([hiproxy.httpServer, hiproxy.httpsServer]); next(); }); initFlow.run({ localIP: ip, args: hiproxy.lastConfig }, null, hiproxy); }); }, /** * 停止代理服务 * @return {ProxyServer} * @public */ stop: function () { this.httpServer.close(); if (this.httpsServer) { this.httpsServer.close(); } /** * Emitted when the hiproxy server(s) stop. * @event ProxyServer#stop */ this.emit('stop'); return this; }, /** * 重启代理服务 * @return {ProxyServer} * @public */ restart: function (config) { return this.stop().start(config || this.lastConfig || {}); }, /** * 添加Hosts文件 * * @param {String|Array} filePath `hosts`文件路径(绝对路径) * @return {ProxyServer} * @public */ addHostsFile: function (filePath) { /** * Emitted when add hosts file. * @event ProxyServer#addHostsFile * @property {Array|String} filePath rewrite file path(s) */ this.emit('addHostsFile', filePath); this.logger.debug('add hosts file: ' + filePath); this.hosts.addFile(filePath); this.createPacFile(); return this; }, /** * 添加rewrite文件 * * @param {String|Array} filePath `rewrite`文件路径(绝对路径) * @return {ProxyServer} * @public */ addRewriteFile: function (filePath) { /** * Emitted when add rewrite file. * @event ProxyServer#addRewriteFile * @property {Array|String} filePath rewrite file path(s) */ this.emit('addRewriteFile', filePath); this.logger.debug('add rewrite file: ' + filePath); this.rewrite.addFile(filePath); this.createPacFile(); return this; }, /** * 添加rewrite规则 * * @param {String|Array} source `rewrite`规则代码片段 * @return {ProxyServer} * @public */ // addRewriteRule: function (source) { // /** // * Emitted when add rewrite file. // * @event ProxyServer#addRewriteRule // * @property {Array|String} filePath rewrite file path(s) // */ // this.emit('addRewriteRule', source); // this.logger.debug('add rewrite rule: ' + source); // this.rewrite.addRule(source); // this.createPacFile(); // return this; // }, /** * 打开浏览器窗口 * * @param {String} browserName 浏览器名称 * @param {String} url 要打开的url * @param {Boolean} [usePacProxy=false] 是否使用自动代理 * @return {ProxyServer} * @public */ openBrowser: /* istanbul ignore next */ function (browserName, url, usePacProxy) { var self = this; if (usePacProxy) { this.createPacFile().then(function (success) { self._open(browserName, url, true); }); } else { this._open(browserName, url, false); } return this; }, _open: /* istanbul ignore next */ function (browserName, url, usePacProxy) { var proxyURL = 'http://127.0.0.1:' + this.httpPort; var dataDir = path.join(dirtool.getHiproxyDir(), 'data-dir'); if (usePacProxy) { openBrowser.open(browserName, url, '', proxyURL + '/proxy.pac', dataDir); } else { openBrowser.open(browserName, url, proxyURL, '', dataDir); } return this; }, /** * 创建自动配置代理文件 * @private */ createPacFile: function () { var hosts = this.hosts.getHost(); var rewrite = this.rewrite.getRule(); var logger = this.logger; var allDomains = Object.keys(hosts).concat(Object.keys(rewrite)); var domains = {}; allDomains.forEach(function (domain) { domains[domain] = 1; }); /** * Emitter when the `pac` proxy file is created or updated. * @event ProxyServer#createPacFile * @property {Object} domains domain list */ this.emit('createPacFile', domains); return createPacFile(this.httpPort, this.localIP, domains) .then(function () { return true; }) .catch(function (err) { logger.debug(err); return false; }); }, enableConfFile: function (type, filePath) { this[type] && this[type].enableFile(filePath); }, disableConfFile: function (type, filePath) { this[type] && this[type].disableFile(filePath); }, /** * 添加配置规则,包括hosts和rewrite。 * * @param {String} type 规则类型,可选值为`rewrite`和`hosts` * @param {String} content 规则内容配置代码,比如`127.0.0.1 hiproxy.org` * @param {String} [fileName] 文件名称,主要用于在插件中显示,默认会自动生成一个随机的名称 * @returns this */ addRule: function (type, content, fileName) { if (type === 'hosts' || type === 'rewrite') { this[type].addRule(content, fileName); this.createPacFile(); } return this; }, /** * 服务启动后,显示服务信息 * * @param {Array} servers 启动的服务 */ showStartedMessage: function () { var proxyAddr = this.httpServer.address(); var httpsAddr = this.httpsServer && this.httpsServer.address(); var workspace = global.args.workspace || process.cwd(); var ip = getLocalIP(); var hostFilePath = Object.keys(this.hosts._files); var rewriteFilePath = Object.keys(this.rewrite._files); var images = [ '', ' Proxy address: '.bold.green + ('http://' + ip + ':' + proxyAddr.port).underline, ' Https address: '.bold.magenta + (httpsAddr ? ('https://' + ip + ':' + httpsAddr.port).underline : 'disabled'), ' Proxy file at: '.bold.yellow + ('http://' + ip + ':' + proxyAddr.port + '/proxy.pac').underline, ' SSL/TLS cert : '.bold.magenta + ('http://' + ip + ':' + proxyAddr.port + '/ssl-certificate').underline, ' Workspace at : '.bold.cyan + workspace.underline ]; if (hostFilePath.length) { images.push('hosts files : \n '.bold.green + hostFilePath.join('\n ')); } if (rewriteFilePath.length) { images.push('rewrite files : \n '.bold.green + rewriteFilePath.join('\n ')); } return showImage(images); }, /** * Add callbacks for hiproxy * * @param {String} type callback types, valid values: 'onBeforeRequest', 'onBeforeResponse', 'onData', 'onError'. * @param {Function} fn callback function. */ addCallback: function (type /* , fn1, fn2, ... */) { var validTypes = CALLBACK_NAMES; var fns = [].slice.call(arguments, 1); var cbks = this.options[type]; if (type && validTypes.indexOf(type) !== -1) { fns.forEach(function (fn) { if (typeof fn === 'function') { cbks.push(fn); } }); } else { this.logger.warn('Invalid callback type `' + type + '` for `addCallback()`, valid values:', CALLBACK_NAMES.join(', ') + '.'); } }, removeCallback: function (type /* , fn1, fn2, ... */) { // TODO }, /** * for web api test */ testWebAPI: function () { if (process.env.NPM_TEST) { this.testWebAPICalled = true; } }, isInternalRequest: function (req) { var originalUrl = req.url; var urlInfo = url.parse(originalUrl); var hostname = urlInfo.hostname; var internalHosts = ['127.0.0.1', 'hi.proxy']; return internalHosts.indexOf(hostname) !== -1; } }; module.exports = ProxyServer;