UNPKG

catproxy

Version:

a node proxy or host change tools

463 lines (459 loc) 12.8 kB
// 处理请求来后返回的数据 import http from 'http'; import https from 'https'; import log from './log'; import {Buffer} from 'buffer'; import fs from 'fs'; import merge from 'merge'; import {STATUS, LIMIT_SIZE} from './config/defCfg'; import Promise from 'promise'; import changeHost from './changeHost'; import ip from 'ip'; import {localIps} from './getLocalIps'; import {getUrl} from './tools'; import net from 'net'; import {getCert} from './cert/cert.js'; import {writeErr, sendErr, getMonitorId} from './tools'; import mime from 'mime'; import path from 'path'; import querystring from 'querystring'; import * as config from './config/config'; let isStartHttps = /https/; import {beforeReq, afterRes, beforeRes} from './evt'; // 发送代理请求钱触发 let triggerBeforeRes = function(resInfo) { return beforeRes.call(this, resInfo).then(result => { return merge(resInfo, result); }); }; // 处理本地数据 let local = function(reqInfo, resInfo, fileAbsPath) { var com = this; resInfo.headers = resInfo.headers || {}; return new Promise(resolve => { fs.readFile(fileAbsPath, function(err, buffer) { if (err) { resInfo.bodyData = new Buffer("local file error" + err); // 如果用户没有设置statusCode就设置默认的 resInfo.statusCode = 404; resolve(resInfo); } else { // 如果用户没有设置statusCode就设置默认的 resInfo.statusCode = 200; resInfo.bodyData = buffer; resolve(resInfo); } }); }) .then(resInfo => { return triggerBeforeRes.call(com, resInfo); }) .then(resInfo => { let {bodyData, headers = {}, statusCode, res} = resInfo; headers['loacl-file'] = querystring.escape(fileAbsPath); delete headers['content-length']; delete headers['content-encoding']; if (!headers['content-type']) { let extName = path.extname(fileAbsPath) || ""; let mimeType = extName ? mime.lookup((extName).slice(1)) : 'text/html'; headers['content-type'] = mimeType; } res.writeHead(statusCode, headers || {}); if (!res.headers) { res.headers = headers || {}; } res.end(bodyData); res.emit('resBodyDataReady', null, bodyData); }, function(err) { let {headers = {}, res} = resInfo; // 由错误方法删除的header return Promise.reject(err); }); }; // 处理将 域名转换成ip let detailHost = function(result, reqInfo, resInfo) { // 取当前启动的port let com = this; let {port, httpsPort} = config.get(); let isServerPort = +port === +result.port; if (isStartHttps.test(reqInfo.protocol)) { isServerPort = +httpsPort === +result.port; } // 这里自己将死循环嗯哼获取ip错误的情况已经处理了 return changeHost(result.hostname, isServerPort) .then(address => { // 如果还是死循环,则跳出 if (isServerPort && localIps.some(current => ip.isEqual(current, address))) { if (reqInfo.headers && reqInfo.headers['self-server']) { return Promise.reject('Dead circulation'); } else { reqInfo.headers = reqInfo.headers || {}; reqInfo.headers['self-server'] = 1; } } return merge(result, { hostname: address }); }, err => { let {res} = resInfo; return triggerBeforeRes.call(com, merge(resInfo, {statusCode: 504}, {bodyDataErr: err, headers: {}})) .then(() => { return Promise.reject(err); }); }); }; // 真正代理请求 let proxyReq = function(options, reqInfo, resInfo, req) { var com = this; return new Promise((resolve, reject) => { // 在这里hostname已经全部被转换成 ip了,将ip传递到前端 resInfo.serverIp = options.hostname; // 发出请求 log.verbose('send proxy request originalFullUrl: ' + reqInfo.originalFullUrl); let proxyReq = (isStartHttps.test(reqInfo.protocol) ? https : http) .request(options, proxyRes => { let remoteUrl = getUrl(merge({}, options, {protocol: reqInfo.protocol})); log.verbose(`received request from : ${remoteUrl}, statusCode ${proxyRes.statusCode}`); resInfo = merge(resInfo, { headers: proxyRes.headers || {}, statusCode: proxyRes.statusCode }); resolve({proxyRes, remoteUrl, reqInfo, resInfo}); }); // 向 直接请求写入数据 if (reqInfo.bodyData && reqInfo.bodyData.length) { if (!reqInfo.bodyDataErr) { proxyReq.write(reqInfo.bodyData); proxyReq.end(); } else { proxyReq.write(reqInfo.bodyData); req.on('data', function(buffer) { proxyReq.write(buffer); }) .on('end', () => { proxyReq.end(); }); req.resume(); } } else {// 没有数据就直接end否则读取数据 proxyReq.end(); } // 出错直接结束请求 proxyReq.on("error", (err) => { reject(err); }); }) .then(({proxyRes, remoteUrl, reqInfo, resInfo}) => { let {res} = resInfo; // 数据太大的时候触发 let err = { message: '响应数据过大,无法显示', status: STATUS.LIMIT_ERROR }; let resBodyData = [], l = 0, isError = false, isFired = false; proxyRes // 过滤大文件,只有小文件才返回 // 文件过大的将无法拦截,没有事件通知 .on('data', chunk => { if (l > LIMIT_SIZE) { isError = true; if (!isFired) { isFired = true; let {statusCode, headers} = resInfo; headers['remote-url'] = querystring.escape(remoteUrl); res.writeHead(statusCode || 200, headers); res.write(Buffer.concat(resBodyData)); res.write(chunk); resBodyData = []; let bodyData = null; let bodyDataErr = err.message; // 提前触发事件 return triggerBeforeRes.call(com, merge(resInfo, {bodyData, bodyDataErr})); } else { res.write(chunk); } } else { resBodyData.push(chunk); l += chunk.length; } }) .on('end', ()=> { let bodyData = Buffer.concat(resBodyData); return Promise.resolve(bodyData) .then((bodyData) => { // 文件大小没有出错的情况下 if (!isError) { return triggerBeforeRes.call(com, merge(resInfo, {bodyData})) .then((resInfo) => { let {statusCode, headers, bodyData} = resInfo; headers['remote-url'] = querystring.escape(remoteUrl); res.writeHead(statusCode || 200, headers); res.write(bodyData); return resInfo; }, (err) => { let headers = resInfo.headers; headers['remote-url'] = querystring.escape(remoteUrl); res.writeHead(500, headers); err = writeErr(err); res.write(err); log.error(err); resInfo.statusCode = 500; resInfo.bodyData = err; return resInfo; }); } else { resInfo.bodyData = new Buffer(""); resInfo.bodyDataErr = err.message; return resInfo; } }) .then(({headers, bodyData}) => { // 转换head大小写问题 if (!res.headers) { res.headers = headers; } res.end(); res.emit('resBodyDataReady', isError ? err : null, bodyData || new Buffer("")); }, function(err) { log.error(err); }); }); }) .then(null, err => { let statusCode = "500"; if (err && err.message.indexOf('ENOTFOUND') > -1){ statusCode = '504'; } // 出错也要处理下 return triggerBeforeRes.call(com, merge({statusCode}, resInfo, {bodyDataErr: err, headers: {}})) .then(() => { return Promise.reject(err); }); }); }; let remote = function(reqInfo, resInfo) { let {req} = reqInfo; let {res} = resInfo; let com = this; let isSecure = req.connection.encrypted || req.connection.pai; let oldProtocol = !!isSecure ? "https" : 'http'; return Promise.resolve() .then(() => { let t = /^\/.*/; let hostname = reqInfo.host.split(':')[0]; if (!net.isIP(hostname)) { reqInfo.headers.host = reqInfo.host; } // 请求选项 let options = { hostname, port: reqInfo.port || (reqInfo.protocol === 'http' ? 80 : 443), path: t.test(reqInfo.path) ? reqInfo.path : "/" + reqInfo.path, method: reqInfo.method, headers: reqInfo.headers }; if (hostname === 1) if (reqInfo.protocol === 'https') { options.rejectUnauthorized = false; // 旧的协议是http-即http跳转向https--从新生成证书 if (oldProtocol === 'http') { let {privateKey: key, cert} = getCert(hostname); options.key = key; options.cert = cert; } } return options; }) .then(options => { return detailHost.call(com, options, reqInfo, resInfo); }) .then((options) => { return proxyReq.call(com, options, reqInfo, resInfo, req); }); }; export default function(reqInfo, resInfo){ // /******不要尝试取修改 resInfo的引用,会导致 isBinary取不到***************/ let self = this; let res = resInfo.res; let req = reqInfo.req; let id = getMonitorId(); Object.defineProperty(reqInfo, "id", { value: id, enumerable: true }); Object.defineProperty(resInfo, "id", { value: id, enumerable: true }); // 当bodyData缓存处理完毕就触发事件告诉用户数据 res.on("resBodyDataReady", (err, bodyData) => { let headers = res.headers || {}; let result = {}; if (reqInfo.sendToFile) { Object.defineProperty(result, 'sendToFile', { writable: false, value: reqInfo.sendToFile, enumerable: true }); } // 响应回来后所有的字段都是只读的 Object.defineProperties(result, { headers: { writable: false, value: headers, enumerable: true }, statusCode: { writable: false, value: res.statusCode, enumerable: true }, host: { writable: false, value: reqInfo.host, enumerable: true }, method: { writable: false, value: reqInfo.method, enumerable: true }, protocol: { writable: false, value: reqInfo.protocol, enumerable: true }, originalFullUrl: { writable: false, value: reqInfo.originalFullUrl, enumerable: true }, port: { writable: false, value: reqInfo.port, enumerable: true }, path: { writable: false, value: reqInfo.path, enumerable: true }, originalUrl: { writable: false, value: reqInfo.originalUrl, enumerable: true }, endTime: { writable: false, value: new Date().getTime(), enumerable: true }, bodyData: { writable: false, value: bodyData, enumerable: true }, bodyDataErr: { writable: false, value: err && err.message ? err.message : err, enumerable: true }, id: { writable: false, value: id, enumerable: true }, isBinary: { writable: false, value: resInfo.isBinary, enumerable: true } }); if (resInfo.charset) { Object.defineProperty(result, "charset", { writable: false, value: resInfo.charset }); } return afterRes.call(self, result); }); req.on('reqBodyDataReady', (err, reqBodyData) => { reqInfo.bodyData = reqBodyData || []; reqInfo.bodyDataErr = err; // 请求前拦截一次--所有的拦截都在evt.js中处理 Promise.resolve(beforeReq.call(self, reqInfo)) .then((result) => { // 引用发生变化 if (result !== reqInfo) { reqInfo = merge(result, reqInfo); } Object.defineProperties(resInfo, { host: { writable: false, value: reqInfo.host, enumerable: true }, method: { writable: false, value: reqInfo.method, enumerable: true }, protocol: { writable: false, value: reqInfo.protocol, enumerable: true }, port: { writable: false, value: reqInfo.port, enumerable: true }, path: { writable: false, value: reqInfo.path, enumerable: true } }); // 将是否设置weinre传递到 resInfo if (typeof reqInfo.weinre === 'boolean') { resInfo.weinre = reqInfo.weinre; delete reqInfo.weinre; } return {reqInfo, resInfo}; }) .then(({reqInfo, resInfo}) => { // 如果在事件里面已经结束了请求,那就结束了 if (res.finished) { // 用户做了转发处理,这个时候不知道内容是什么 res.emit('resBodyDataReady', null, null); } else if (reqInfo.redirect) { resInfo.headers = { 'Location':reqInfo.redirect }; resInfo.statusCode = 302; resInfo.bodyData = ""; triggerBeforeRes.call(self, resInfo) .then(function() { res.writeHead(resInfo.statusCode, resInfo.headers); res.end(); res.emit('resBodyDataReady', null, null); }, function() { let err = "调用内部出现错误"; sendErr(res, err, req.url); res.emit('resBodyDataReady', err, null); }); } else { if (reqInfo.sendToFile) { return local.call(self, reqInfo, resInfo, reqInfo.sendToFile); } else { return remote.call(self, reqInfo, resInfo); } } }) .then(null, function(err) { // 日志在 sendErr中打印和处理 sendErr(res, err, req.url); res.emit('resBodyDataReady', err, null); }); }); }