catproxy
Version:
a node proxy or host change tools
293 lines (284 loc) • 11.5 kB
JavaScript
// 事件触发中心
import log from './log';
import { parseRule } from './config/rule';
import * as config from './config/config';
import iconv from 'iconv-lite';
import { Buffer } from 'buffer';
import Promise from 'promise';
import path from 'path';
import { getMonitorId, weinreId, hostReg } from './tools';
import { decodeCompress, isBinary, getCharset } from './dataHelper';
import mime from 'mime';
import requestIp from 'request-ip';
import { localIps } from './getLocalIps';
import ip from 'ip';
import URL from 'url';
import merge from 'merge';
import { WEINRE_PATH } from './config/defCfg';
import { insertWeinreScript } from './weinreServer';
// 自动解析类型,其他类型一律保存的是 Buffer
var autoDecodeRegs = /text\/.+|(?:application\/(?:json.*|.*javascript))/i;
let detailBeforeReq = async function(reqInfo) {
let catProxy = this.catProxy;
let 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(WEINRE_PATH + '/' + weinreId) >= 0) {
let port = config.get('weinrePort');
reqInfo.host = '127.0.0.1';
reqInfo.port = port;
reqInfo.protocol = 'http';
reqInfo.path = (reqInfo.path || '').replace(WEINRE_PATH + '/' + weinreId, '');
} else {
let result = await parseRule(reqInfo);
reqInfo = result || reqInfo;
}
// 添加 clientIp,目前还有bug-- 这段 clientIp总是获取不对
let { req, headers } = reqInfo;
let clientIp = requestIp.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;
// 触发事件
let result = await catProxy.triggerBeforeReq(reqInfo, this);
// 修改了引用
if (reqInfo !== result) {
reqInfo = merge(reqInfo, result);
}
return reqInfo;
};
/**
* 代理请求发出前
* 该方法主要是处理在响应前的所有事情,可以用来替换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(reqInfo) {
return detailBeforeReq.call(this, reqInfo).then(null, function(err) {
// 如果出错忽略所有数据
// 如果改了reqInfo引用上的数据就没救了
log.error(err);
return reqInfo;
});
};
/**
*禁止缓存
* */
var disCache = function(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 = async function(resInfo) {
let catProxy = this.catProxy;
let com = this;
let contentEncoding = resInfo.headers['content-encoding'];
let contentType = resInfo.headers['content-type'];
resInfo.ext = mime.extension(contentType || '') || (path.extname(URL.parse(resInfo.originalUrl || '').pathname || '') || '').slice(1);
// 禁止缓存
resInfo = await disCache(resInfo);
try {
// 解压成功就删除解压头部
if (contentEncoding && resInfo.bodyData.length) {
let bodyData = await decodeCompress(resInfo.bodyData, contentEncoding);
resInfo.bodyData = bodyData;
delete resInfo.headers['content-encoding'];
// 更新content-length
if (resInfo.headers['content-length']) {
resInfo.headers['content-length'] = bodyData.length;
}
}
} catch (e) {
log.error(e);
}
// 触发事件
let result = await catProxy.triggerBeforeRes(resInfo, this);
// 修改了引用
if (resInfo !== result) {
resInfo = merge(resInfo, result);
}
resInfo.isBinary = isBinary(resInfo.bodyData);
// 文本文件 -- 需要检测编码是不是不是 utf-8
// 二进制文件是没有charset的
if (!resInfo.isBinary) {
// 设置默认编码
resInfo.charset = getCharset(resInfo);
if (resInfo.weinre) {
try {
// 传递域名过去
resInfo.bodyData = await insertWeinreScript(resInfo.bodyData, resInfo.charset, WEINRE_PATH);
if (resInfo.headers['content-length']) {
resInfo.headers['content-length'] = resInfo.bodyData.length;
}
} catch (error) {
log.error(error);
}
}
// 是个文件
if (contentType && contentType.indexOf('text/html') > -1 && config.get('cacheFlush') && resInfo.bodyData && resInfo.bodyData.length) {
let meta = `
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
<meta http-equiv="Pragma" content="no-cache" />
<meta http-equiv="Expires" content="0" />
`;
resInfo.bodyData = resInfo.bodyData.toString().replace('<head>', '<head>' + meta);
resInfo.bodyData = iconv.encode(resInfo.bodyData, resInfo.charset || 'UTF-8');
// 重新设置body的长度
if (resInfo.headers['content-length']) {
resInfo.headers['content-length'] = resInfo.bodyData.length;
}
}
}
return resInfo;
};
/**
* 请求响应后
* 该方法主要是请求响应后的处理操作,主要是可以查看请求数据,
* 注意这时候请求已经结束了,无法在做其他的处理
* @param result
* 所有字段不可以修改,只可以查看
* * result包含的信息
* {
* statusCode: "响应状态码"
* headers: "请求头"
* host: "请求地址"
* method: "请求方法"
* protocol: "请求协议"
* originalFullUrl: "原始请求地址,包括参数"
* port: "请求端口"
* endTime: "请求开始时间"
* path: "请求路径,包括参数"
* originalUrl: "原始的请求地址,不包括参数",
* bodyData: "buffer 数据,body参数",
* bodyDataErr: null,
* bodyDataFile: null //如果资源定位到本地就显示这个字段
* }
* @returns {*}
*/
var afterRes = function(result) {
let catProxy = this.catProxy;
catProxy.triggerAfterRes(result, this);
return result;
};
// 中转请求
var pipeRequest = function(result) {
result.id = getMonitorId();
let catProxy = this.catProxy;
catProxy.triggerPipeReq(result, this);
return result;
};
export { beforeReq, afterRes, beforeRes, pipeRequest };