UNPKG

catproxy

Version:

a node proxy or host change tools

411 lines (410 loc) 11.1 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) { // 这里调用如果出错,最后直接抛出 -- 也可以考虑哪一步出错,哪一步单独抛出 // 检测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, result, context); } /** * 触发 请求前事件 * result 格式看evt中的格式 * context为上下文 */ triggerBeforeRes (result, context) { return execArrByStep(this._beforeResEvt, result, context); } /** * 触发请求后事件 * result 格式看evt中的格式 * context为上下文 */ triggerAfterRes (result, context) { if (this._afterResEvt.length) { this._afterResEvt.forEach(current => { try{ 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};