UNPKG

catproxy

Version:

a node proxy or host change tools

416 lines (415 loc) 11.3 kB
'use strict'; import http from 'http'; import https from 'https'; import extendMime from './extendMime'; import defCfg from './config/defCfg'; import configInit, * as config from './config/config'; import merge from 'merge'; import Promise from 'promise'; import log from './log'; import { requestHandler, requestConnectHandler, requestUpgradeHandler } from './requestSerives'; import EventEmitter from 'events'; import { getCert, getCertDir } from './cert/cert.js'; import { SNICallback } from './httpsProxySer'; import ui from './web/app'; import { localIps } from './getLocalIps'; import { error as errFun, getPort, openCmd } from './tools'; import * as requestMiddleware from './requestMiddleware'; import configProps from './config/configProps'; import util from 'util'; import * as rule from './config/rule'; import express from 'express'; import webCfg from './config/webCfg'; import path from 'path'; import ws from './ws/ws'; // process.env.NODE_ENV const getLocalUiReg = port => { let ips = localIps.slice(0); ips.push('localhost'); let l = ips.length; return ips.reduce((result, cur, index) => { if (index > 0) { result += '|'; } result += `(?:${cur}`; if (port !== 80 || port !== 443) { result += `:${port})`; } else { result += ')'; } // 最后一次 if (index === l - 1) { return new RegExp(result, 'i'); } return result; }, '^(?:http|ws)(?:s?)://'); }; /** * 按顺序调用数组,每个步骤返回promise * @arr 表示要执行的数据 * @result表示执行的结果,结果会进行合并,结果必须是一个object * @context 表示执行的上下文 */ const execArrByStep = async function(arr, result, context) { result = result || {}; if (!arr || !arr.length) { return result; } for (let cur of arr) { if (cur) { // 这里调用如果出错,最后直接抛出 -- 也可以考虑哪一步出错,哪一步单独抛出 // 检测cur是够是一个函数?? let newRes = await cur.call(context, result); // 修改了引用 if (newRes !== result) { result = merge(result, newRes); } } } return result; }; class CatProxy { /** * * @param {[type]} option * { * type: "当前服务器类型" * port: "当前http端口", * httpsPort: "当前https服务器端口", * certHost: "https证书生成默认host代理", * crackHttps 是否解开 https请求,在 http代理模式下, * log: '日志级别', * uiPort 端口 * } * @param servers 自定义服务器,最多同时开启2个服务器,一个http一个https, 2个服务器的时候顺序是http,https 如果只有一个则没有顺序问题 * * @param saveProps 要同步到文件的字段 为空则全部同步 * */ constructor(opt, saveProps) { this.option = {}; // 初始化配置文件 configInit(); let certDir = getCertDir(); log.info(`当前证书目录: ${certDir}`); // 读取缓存配置文件 let fileCfg = {}; configProps.forEach(current => { let val = config.get(current); if (val !== undefined && val !== null) { fileCfg[current] = val; } }); // 混合三种配置 let cfg = merge.recursive({}, defCfg, fileCfg, opt); if (saveProps && saveProps.length) { this.option.saveProps = saveProps; // 配置了保存字段,则只保存这些字段 config.setSaveProp(...saveProps); } // 将用户当前设置保存到缓存配置文件 configProps.forEach(current => { if (cfg[current] !== null && cfg[current] !== undefined) { // 为‘’表示要删除这个字段 if (cfg[current] === '' && config.get(current)) { config.del(current); } else { config.set(current, cfg[current]); } } }); config.save(); this._beforeReqEvt = []; this._beforeResEvt = []; this._afterResEvt = []; this._pipeRequestEvt = []; } init() { let com = this; // 'hosts', "log", 'breakHttps', 'excludeHttps', 'sni' 可以通过 进程修改的字段 // 别的进程发送的消息 process.on('message', function(message) { if (!message.result || !typeof message.result === 'object') { return; } log.debug('receive message'); if (message.type) { switch (message.type) { case 'config': let data = {}; config.set(message.result); // 所有配置均不保存 config.save([]); com.setLogLevel(); break; default: log.error('收到未知的消息', message); } } }); this.setLogLevel(); // dangerous options process.env.NODE_TLS_REJECT_UNAUTHORIZED = 0; return Promise.resolve() .then(this.createCache.bind(this)) .then(this.checkParam.bind(this)) .then(this.checkEnv.bind(this)) .then(this.createServer.bind(this)) .then(this.uiInit.bind(this)) .then(null, err => { this.errorHandle(err); process.exit(1); }); } // 创建缓存,创建请求保存 createCache() {} checkParam() {} // 设置 日志级别 setLogLevel(logLevel) { if (logLevel) { config.set('log', logLevel); log.transports.console.level = config.get('log'); config.save('log'); } else { log.transports.console.level = config.get('log'); } } // 设置服务器类别 setServerType(type) { config.set('type', type); config.save('type'); } // 设置服务器端口 setHttpPort(port) { port = +port; config.set('port', port); config.save('port'); } setHttpsPort(port) { port = +port; config.set('httpsPort', port); config.save('httpsPort'); } // 设置ui端口 setUiPort(port) { port = +port; config.set('uiPort', port); config.save('uiPort'); } // 设置sni类型 setSniType(type) { config.set('sni', type); config.save('sni'); } // 设置破解https setBreakHttps(list) { config.set('breakHttps', list); config.save('breakHttps'); } // 设置排除https列表 setExcludeHttps(list) { config.set('excludeHttps', list); config.save('excludeHttps'); } setRules(rules) { rule.saveRules(rules); } // 获取配置 getConfig(key) { if (typeof key === 'string') { return config.get(key); } return config.get(); } setConfig(...arg) { config.set(...arg); config.save(); } // 环境检测 checkEnv() {} uiInit() { let port = config.get('uiPort'); let isAutoOpen = config.get('autoOpen'); let p = port; // 如果port是0 则只提供下载链接的server return Promise.resolve(p || getPort()) .then(p => { // 内置服务器初始化 let host = `http://${localIps[0]}:${p}`; let uiOption = { port: p, hostname: localIps[0], host: host, wsServerUrl: host + webCfg.wsPath, cdnBasePath: path.join('/c', webCfg.cdnBasePath), env: webCfg.env, }; // 写成正则,判断是否是ui的一个访问地址 this.localUiReg = getLocalUiReg(p); let uiApp = ui(!!port); let app = express(); let uiServer = app.listen(p, function() { log.info('catproxy 规则配置地址:' + host + '/c/index'); log.info('catproxy 监控界面地址:' + host + '/c/m'); if (port && isAutoOpen) { openCmd(host + '/c/index'); } }); uiApp.locals.uiOption = uiOption; uiServer.on('error', err => { errFun(err); process.exit(1); }); // 字app app.use('/c', uiApp); this.ui = { app, uiServer, }; }) .then(() => { if (port) { return ws(this.ui.uiServer, this); } }) .then(wsServer => { if (wsServer) { this.ui.wsServer = wsServer; } }); } // 出错处理 errorHandle(err) { if (err) { log.error(err); } return Promise.reject(err); } // 根据配置创建服务器 createServer() { let opt = config.get(); let servers = this.servers || []; let com = this; // 可以自定义server或者用系统内置的server if (opt.type === 'http' && !servers[0]) { servers[0] = http.createServer(); } else if (opt.type === 'https' && !servers[0]) { // 找到证书,创建https的服务器 let { privateKey: key, cert } = getCert(opt.certHost); servers[0] = https.createServer({ key, cert, rejectUnauthorized: false, SNICallback, }); } else if (opt.type === 'all' && !servers[0] && !servers[1]) { servers[0] = http.createServer(); let { privateKey: key, cert } = getCert(opt.certHost); servers[1] = https.createServer({ key, cert, rejectUnauthorized: false, SNICallback, }); } let requestFun = requestMiddleware.middleWare(requestHandler); servers.forEach(server => { server.catProxy = com; // 如果在http下代理https,则需要过度下请求 if (server instanceof http.Server) { server.on('connect', requestConnectHandler); } server.on('upgrade', requestUpgradeHandler); server.on('request', function(req, res) { if (req.headers.upgrade) { return; } requestFun.call(this, req, res); }); server.on('clientError', function(err, con) { log.error('ser-clientError' + err); }); let serverType = server instanceof http.Server ? 'http' : 'https'; let port = serverType === 'http' ? opt.port : opt.httpsPort; // 如果server没有被监听,则调用默认端口监听 if (!server.listening) { // 根据server的类型调用不同的端口 server.listen(port, function() { log.info('代理服务器启动于:' + `${serverType}://${localIps[0]}:${port}`); }); } server.on('error', function(err) { errFun(err); process.exit(1); }); }); this.servers = servers; } // 想服务器添加request事件 use(fun) { requestMiddleware.use(fun); return this; } // 在中转请求前,可以用于修改reqInfo onBeforeReq(...fun) { fun.forEach(f => util.isFunction(f) && this._beforeReqEvt.push(f)); } // 请求结束,可以用于产看请求结果 onAfterRes(...fun) { fun.forEach(f => util.isFunction(f) && this._afterResEvt.push(f)); } // 获得中转请求前,可以用于修改resInfo onBeforeRes(...fun) { fun.forEach(f => util.isFunction(f) && this._beforeResEvt.push(f)); } onPipeRequest(...fun) { fun.forEach(f => util.isFunction(f) && this._pipeRequestEvt.push(f)); } /** * 触发req事件,result表示参数,context表示上下文 * result 格式看evt中的格式 */ triggerBeforeReq(result, context) { return execArrByStep(this._beforeReqEvt.concat([this.__monitorBeforeReq]), result, context); } /** * 触发 请求前事件 * result 格式看evt中的格式 * context为上下文 */ triggerBeforeRes(result, context) { return execArrByStep(this._beforeResEvt.concat([this.__monitorBeforeRes]), result, context); } /** * 触发请求后事件 * result 格式看evt中的格式 * context为上下文 */ triggerAfterRes(result, context) { (this._afterResEvt || []).concat([this.__monitorAfterRes]).forEach(current => { try { current && current.call(context, result); } catch (e) { log.error(e); } }); } /** * 触发穿过请求 * result 格式看evt中的格式 * context为上下文 */ triggerPipeReq(result, context) { if (this._pipeRequestEvt.length) { this._pipeRequestEvt.forEach(current => { try { current.call(context, result); } catch (e) { log.error(e); } }); } } } export default CatProxy; export { CatProxy };