catproxy
Version:
a node proxy or host change tools
453 lines (371 loc) • 15.4 kB
JavaScript
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.pipeRequest = exports.beforeRes = exports.afterRes = exports.beforeReq = undefined;
var _regenerator = require('babel-runtime/regenerator');
var _regenerator2 = _interopRequireDefault(_regenerator);
var _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator');
var _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2);
var _log = require('./log');
var _log2 = _interopRequireDefault(_log);
var _rule = require('./config/rule');
var _config = require('./config/config');
var config = _interopRequireWildcard(_config);
var _iconvLite = require('iconv-lite');
var _iconvLite2 = _interopRequireDefault(_iconvLite);
var _buffer = require('buffer');
var _promise = require('promise');
var _promise2 = _interopRequireDefault(_promise);
var _path = require('path');
var _path2 = _interopRequireDefault(_path);
var _tools = require('./tools');
var _dataHelper = require('./dataHelper');
var _mime = require('mime');
var _mime2 = _interopRequireDefault(_mime);
var _requestIp = require('request-ip');
var _requestIp2 = _interopRequireDefault(_requestIp);
var _getLocalIps = require('./getLocalIps');
var _ip = require('ip');
var _ip2 = _interopRequireDefault(_ip);
var _url = require('url');
var _url2 = _interopRequireDefault(_url);
var _merge = require('merge');
var _merge2 = _interopRequireDefault(_merge);
var _defCfg = require('./config/defCfg');
var _weinreServer = require('./weinreServer');
function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } }
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
// 自动解析类型,其他类型一律保存的是 Buffer
var autoDecodeRegs = /text\/.+|(?:application\/(?:json.*|.*javascript))/i; // 事件触发中心
var detailBeforeReq = function () {
var _ref = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee(reqInfo) {
var catProxy, com, port, _result, _reqInfo, req, headers, clientIp, result;
return _regenerator2.default.wrap(function _callee$(_context) {
while (1) {
switch (_context.prev = _context.next) {
case 0:
catProxy = this.catProxy;
com = this;
// 等待解析url
// weinre解析直接转走
// 6d902c89-aee6-4428-9357-b71c7242359f/ws/target|/b97a96cd-cd88-48dd-9d4f-7d41401aa4d8/target/target-script-min.js
if (!(reqInfo.originalUrl.toLowerCase().indexOf(_defCfg.WEINRE_PATH + '/' + _tools.weinreId) >= 0)) {
_context.next = 10;
break;
}
port = config.get('weinrePort');
reqInfo.host = '127.0.0.1';
reqInfo.port = port;
reqInfo.protocol = 'http';
reqInfo.path = (reqInfo.path || '').replace(_defCfg.WEINRE_PATH + '/' + _tools.weinreId, '');
_context.next = 14;
break;
case 10:
_context.next = 12;
return (0, _rule.parseRule)(reqInfo);
case 12:
_result = _context.sent;
reqInfo = _result || reqInfo;
case 14:
// 添加 clientIp,目前还有bug-- 这段 clientIp总是获取不对
_reqInfo = reqInfo, req = _reqInfo.req, headers = _reqInfo.headers;
clientIp = _requestIp2.default.getClientIp(req);
// clientIp获取不对,就设置成 机器ip???
// let xForwardedFor = headers['x-forwarded-for'];
// if (!xForwardedFor) {
// headers['x-forwarded-for'] = clientIp + "," + localIps[0];
// } else {
// headers['x-forwarded-for'] = "," + localIps[0];
// }
reqInfo.clientIp = clientIp;
// 触发事件
_context.next = 19;
return catProxy.triggerBeforeReq(reqInfo, this);
case 19:
result = _context.sent;
// 修改了引用
if (reqInfo !== result) {
reqInfo = (0, _merge2.default)(reqInfo, result);
}
return _context.abrupt('return', reqInfo);
case 22:
case 'end':
return _context.stop();
}
}
}, _callee, this);
}));
return function detailBeforeReq(_x) {
return _ref.apply(this, arguments);
};
}();
/**
* 代理请求发出前
* 该方法主要是处理在响应前的所有事情,可以用来替换header,替换头部数据等操作
* 可以直接像res中写数据结束请求
* 如果是异步请返回promise对象
* @param reqInfo 请求信息 可以修改请求代理的form数据和 请求代理的头部数据
* @param {resInfo} 响应投信息可以修改响应投的header
* @param res 响应对象
* @returns {*}
* reqInfo 包含的信息
* {
* headers: "请求头"
* host: "请求地址,包括端口,默认端口省略"
* method: "请求方法"
* protocol: "请求协议"
* originalFullUrl: "原始请求地址,包括参数"
* req: "请求对象,请勿删除"
* port: "请求端口"
* startTime: "请求开始时间"
* path: "请求路径,包括参数"
* originalUrl: "原始的请求地址,不包括参数,请不要修改",
* bodyData: "buffer 数据,body参数,可以修改"
* reqInfo.sendToFile
* //重定向到某个url,--有这个,就会忽略远程的调用即host设置之类的都无效
* reqInfo.redirect
* }
*
* 举例说明,可以请求的修改的地方
* 修改请求头,注意只有请求远程服务器的时候管用
* reqInfo.headers['test-cjx'] = 111;
* //修改请求域名
* reqInfo.host = '114.113.198.187';
* //修改请求协议
* reqInfo.protocol = '114.113.198.187';
* //修改请求端口
* reqInfo.port="8080"
* //修改请求路径--包括get参数
* reqInfo.path= "/ccc?aaa"
* //修改方法
* reqInfo.method= "post" //注意post方法要有对应的content-type数据才能过去
* //修改请求数据
* reqInfo.bodyData = "请求数据",
* //直接定位到某个文件 --如果返回某个文件,有这个,就会忽略远程的调用即host设置之类的都无效
* reqInfo.sendToFile
* //重定向到某个url,--有这个,就会忽略远程的调用即host设置之类的都无效
* reqInfo.redirect
*/
/**
* test code
* reqInfo.headers['test-cjx'] = 111;
* reqInfo.path = '/hyg/mobile/common/base/base.34b37a3c0b.js';
* reqInfo.port = 9090;
* reqInfo.protocol = "https";
* reqInfo.path = "/a/b?c=d";
* reqInfo.method = "post";
* reqInfo.headers['Content-Type'] = 'application/x-www-form-urlencoded; charset=UTF-8';
* reqInfo.bodyData = new Buffer('a=b&c=d');
* reqInfo.sendToFile = "D:/project/gitWork/catproxy/bin.js";
* reqInfo.serverIp ="127.0.1" * 真实地服务器ip地址,请不要修改
* log.debug(reqInfo.headers);
* log.debug(reqInfo.bodyData.toString());
* if (reqInfo.host.indexOf('pimg1.126.net') > -1) {
* reqInfo.host = '114.113.198.187';
* }
*/
var beforeReq = function beforeReq(reqInfo) {
return detailBeforeReq.call(this, reqInfo).then(null, function (err) {
// 如果出错忽略所有数据
// 如果改了reqInfo引用上的数据就没救了
_log2.default.error(err);
return reqInfo;
});
};
/**
*禁止缓存
* */
var disCache = function disCache(resInfo) {
// 禁用缓存则删掉缓存相关的header
var disCache = config.get('disCache');
if (disCache) {
// http 1.1引入
resInfo.headers['cache-control'] = 'no-store';
// 时间点表示什么时候文件过期,缺点,服务器和客户端必须有严格的时间同步
// 旧浏览器兼容 expires -1 表示不缓存
resInfo.headers.expires = '-1';
// 删除 etag ,让浏览器下次请求不能带 If-None-Match 头部,这样服务器无法返回304
/**
* etag是服务器首次相应带etag,给文件打标机,下次在请求的时候浏览器
* 请求头会带 If-None-Match , 服务器根据该字段判断文件是否改变,如果没改变就返回304,否则返回新文件
*/
delete resInfo.headers.etag;
/**
* last-Modified 是 浏览器首次返回的res中带Last-Modified头,标记一个时间,服务器下次接受到请求会带头If-Modified-Since
* 服务器根据该头部判断是否是缓存,如果是返回304,不是则返回新文件
* 缺点,如果服务器文件并没有什么改变,只是改变了时间,也会跟新文件
*/
// 删除 last-modified,让浏览器下次请求不能带 If-Modified-Since 头部,这样服务器无法返回304
delete resInfo.headers['last-modified'];
}
return resInfo;
};
/**
* 准备响应请求前
* @param {[type]} resInfo [响应信息]
* * resInfo包含的信息
* {
* headers: "响应头 --- 代理请求已经发出并受到,无法修改"
* host: "请求地址,包括端口,默认端口省略 --- 代理请求已经发出并受到,无法修改"
* method: "请求方法 --- 代理请求已经发出并受到,无法修改"
* protocol: "请求协议 --- 代理请求已经发出并受到,无法修改"
* originalFullUrl: "原始请求地址,包括参数 --- 代理请求已经发出并受到,无法修改"
* res: "请求对象,请勿删除 --- 代理请求已经发出并受到,无法修改"
* port: "请求端口 --- 代理请求已经发出并受到,无法修改"
* startTime: "请求开始时间 --- 代理请求已经发出并受到,无法修改"
* path: "请求路径,包括参数 --- 代理请求已经发出并受到,无法修改"
* originalUrl: "原始的请求地址,不包括参数,请不要修改 --- 代理请求已经发出并受到,无法修改",
* statusCode: "响应状态码, 可以修改"
* headers: "响应头,可修改"
* ---注意如果有bodyData则会直接用bodyData的数据返回
* bodyData: "buffer 数据",
* bodyDataErr: "请求出错,目前如果是大文件会触发这个,这个时候bodyData为空,且不可以设置"
* charset: "", 编码,如果是 文本文件设置后,如果支持该编码将用该编码解码
* //这个时候 resInfo,bodyData无效
*
* }
*
* 举例说明可以修改响应的地方/
* resInfo.headers['test-cjx'] = 111;
* @return {[type]} [description]
*/
var beforeRes = function () {
var _ref2 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee2(resInfo) {
var catProxy, com, contentEncoding, contentType, bodyData, result, meta;
return _regenerator2.default.wrap(function _callee2$(_context2) {
while (1) {
switch (_context2.prev = _context2.next) {
case 0:
catProxy = this.catProxy;
com = this;
contentEncoding = resInfo.headers['content-encoding'];
contentType = resInfo.headers['content-type'];
resInfo.ext = _mime2.default.extension(contentType || '') || (_path2.default.extname(_url2.default.parse(resInfo.originalUrl || '').pathname || '') || '').slice(1);
// 禁止缓存
_context2.next = 7;
return disCache(resInfo);
case 7:
resInfo = _context2.sent;
_context2.prev = 8;
if (!(contentEncoding && resInfo.bodyData.length)) {
_context2.next = 16;
break;
}
_context2.next = 12;
return (0, _dataHelper.decodeCompress)(resInfo.bodyData, contentEncoding);
case 12:
bodyData = _context2.sent;
resInfo.bodyData = bodyData;
delete resInfo.headers['content-encoding'];
// 更新content-length
if (resInfo.headers['content-length']) {
resInfo.headers['content-length'] = bodyData.length;
}
case 16:
_context2.next = 21;
break;
case 18:
_context2.prev = 18;
_context2.t0 = _context2['catch'](8);
_log2.default.error(_context2.t0);
case 21:
_context2.next = 23;
return catProxy.triggerBeforeRes(resInfo, this);
case 23:
result = _context2.sent;
// 修改了引用
if (resInfo !== result) {
resInfo = (0, _merge2.default)(resInfo, result);
}
resInfo.isBinary = (0, _dataHelper.isBinary)(resInfo.bodyData);
// 文本文件 -- 需要检测编码是不是不是 utf-8
// 二进制文件是没有charset的
if (resInfo.isBinary) {
_context2.next = 40;
break;
}
// 设置默认编码
resInfo.charset = (0, _dataHelper.getCharset)(resInfo);
if (!resInfo.weinre) {
_context2.next = 39;
break;
}
_context2.prev = 29;
_context2.next = 32;
return (0, _weinreServer.insertWeinreScript)(resInfo.bodyData, resInfo.charset, _defCfg.WEINRE_PATH);
case 32:
resInfo.bodyData = _context2.sent;
if (resInfo.headers['content-length']) {
resInfo.headers['content-length'] = resInfo.bodyData.length;
}
_context2.next = 39;
break;
case 36:
_context2.prev = 36;
_context2.t1 = _context2['catch'](29);
_log2.default.error(_context2.t1);
case 39:
// 是个文件
if (contentType && contentType.indexOf('text/html') > -1 && config.get('cacheFlush') && resInfo.bodyData && resInfo.bodyData.length) {
meta = '\n\t\t\t<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />\n\t\t\t<meta http-equiv="Pragma" content="no-cache" />\n\t\t\t<meta http-equiv="Expires" content="0" />\n\t\t\t';
resInfo.bodyData = resInfo.bodyData.toString().replace('<head>', '<head>' + meta);
resInfo.bodyData = _iconvLite2.default.encode(resInfo.bodyData, resInfo.charset || 'UTF-8');
// 重新设置body的长度
if (resInfo.headers['content-length']) {
resInfo.headers['content-length'] = resInfo.bodyData.length;
}
}
case 40:
return _context2.abrupt('return', resInfo);
case 41:
case 'end':
return _context2.stop();
}
}
}, _callee2, this, [[8, 18], [29, 36]]);
}));
return function beforeRes(_x2) {
return _ref2.apply(this, arguments);
};
}();
/**
* 请求响应后
* 该方法主要是请求响应后的处理操作,主要是可以查看请求数据,
* 注意这时候请求已经结束了,无法在做其他的处理
* @param result
* 所有字段不可以修改,只可以查看
* * result包含的信息
* {
* statusCode: "响应状态码"
* headers: "请求头"
* host: "请求地址"
* method: "请求方法"
* protocol: "请求协议"
* originalFullUrl: "原始请求地址,包括参数"
* port: "请求端口"
* endTime: "请求开始时间"
* path: "请求路径,包括参数"
* originalUrl: "原始的请求地址,不包括参数",
* bodyData: "buffer 数据,body参数",
* bodyDataErr: null,
* bodyDataFile: null //如果资源定位到本地就显示这个字段
* }
* @returns {*}
*/
var afterRes = function afterRes(result) {
var catProxy = this.catProxy;
catProxy.triggerAfterRes(result, this);
return result;
};
// 中转请求
var pipeRequest = function pipeRequest(result) {
result.id = (0, _tools.getMonitorId)();
var catProxy = this.catProxy;
catProxy.triggerPipeReq(result, this);
return result;
};
exports.beforeReq = beforeReq;
exports.afterRes = afterRes;
exports.beforeRes = beforeRes;
exports.pipeRequest = pipeRequest;