mirror of
https://github.com/alibaba/anyproxy.git
synced 2025-05-10 14:58:27 +00:00
reconstruct rule file
This commit is contained in:
parent
ef9f00630d
commit
90b6d3dc6f
@ -1,6 +1,6 @@
|
|||||||
anyproxy
|
anyproxy
|
||||||
==========
|
==========
|
||||||
another proxy written in NodeJS, which can handle HTTPS requests and CORS perfectly. Produced by Alipay-ct-wd.
|
another proxy written in NodeJS, which can handle HTTPS requests and CORS perfectly.
|
||||||
|
|
||||||
Feature
|
Feature
|
||||||
------------
|
------------
|
||||||
@ -95,7 +95,7 @@ var rules = {
|
|||||||
,{
|
,{
|
||||||
"host" :/./,
|
"host" :/./,
|
||||||
"path" :/\.(png|gif|jpg|jpeg)/,
|
"path" :/\.(png|gif|jpg|jpeg)/,
|
||||||
"localFile" :"/Users/Stella/tmp/test.png",
|
"localFile" :"/Users/Username/tmp/test.png",
|
||||||
"localDir" :"~/"
|
"localDir" :"~/"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@ -111,4 +111,3 @@ module.exports = rules;
|
|||||||
|
|
||||||
## Contact
|
## Contact
|
||||||
* Please feel free to raise any issue about this project, or give us some advice on this doc. :)
|
* Please feel free to raise any issue about this project, or give us some advice on this doc. :)
|
||||||
* Email: alipay-sh-wd@list.alibaba-inc.com
|
|
||||||
|
@ -18,7 +18,7 @@ module.exports =function(){
|
|||||||
var self = this;
|
var self = this;
|
||||||
self.serverList = {
|
self.serverList = {
|
||||||
/* schema sample
|
/* schema sample
|
||||||
"www.alipay.com":{
|
"www.example.com":{
|
||||||
port:123,
|
port:123,
|
||||||
server : serverInstance,
|
server : serverInstance,
|
||||||
lastestUse: 99999 //unix time stamp
|
lastestUse: 99999 //unix time stamp
|
||||||
|
@ -1,13 +1,15 @@
|
|||||||
var http = require("http"),
|
var http = require("http"),
|
||||||
https = require("https"),
|
https = require("https"),
|
||||||
net = require("net"),
|
net = require("net"),
|
||||||
fs = require("fs"),
|
fs = require("fs"),
|
||||||
url = require("url"),
|
url = require("url"),
|
||||||
pathUtil = require("path"),
|
pathUtil = require("path"),
|
||||||
|
zlib = require('zlib'),
|
||||||
|
async = require('async'),
|
||||||
color = require("colorful"),
|
color = require("colorful"),
|
||||||
sleep = require("sleep"),
|
|
||||||
Buffer = require('buffer').Buffer,
|
Buffer = require('buffer').Buffer,
|
||||||
httpsServerMgr = require("./httpsServerMgr");
|
httpsServerMgr = require("./httpsServerMgr"),
|
||||||
|
userRule = require("./rule.js"); //TODO - to be configurable
|
||||||
|
|
||||||
var httpsServerMgrInstance = new httpsServerMgr();
|
var httpsServerMgrInstance = new httpsServerMgr();
|
||||||
|
|
||||||
@ -31,16 +33,16 @@ function userRequestHandler(req,userRes){
|
|||||||
var host = req.headers.host,
|
var host = req.headers.host,
|
||||||
urlPattern = url.parse(req.url),
|
urlPattern = url.parse(req.url),
|
||||||
path = urlPattern.path,
|
path = urlPattern.path,
|
||||||
ifLocalruleMatched = false,
|
callback = null, //TODO : remove callback
|
||||||
callback = null,
|
protocol = (!!req.connection.encrypted && !/http:/.test(req.url)) ? "https" : "http",
|
||||||
ifHttps = !!req.connection.encrypted && !/http:/.test(req.url),
|
|
||||||
resourceInfo = {},
|
resourceInfo = {},
|
||||||
resourceInfoId = -1;
|
resourceInfoId = -1;
|
||||||
|
|
||||||
|
//record
|
||||||
resourceInfo.host = host;
|
resourceInfo.host = host;
|
||||||
resourceInfo.method = req.method;
|
resourceInfo.method = req.method;
|
||||||
resourceInfo.path = path;
|
resourceInfo.path = path;
|
||||||
resourceInfo.url = (ifHttps ? "https://" :"http://") + host + path;
|
resourceInfo.url = protocol + "://" + host + path;
|
||||||
resourceInfo.req = req;
|
resourceInfo.req = req;
|
||||||
resourceInfo.startTime = new Date().getTime();
|
resourceInfo.startTime = new Date().getTime();
|
||||||
|
|
||||||
@ -51,104 +53,144 @@ function userRequestHandler(req,userRes){
|
|||||||
console.log(color.green("\nreceived request to : " + host + path));
|
console.log(color.green("\nreceived request to : " + host + path));
|
||||||
/*
|
/*
|
||||||
req.url is wired
|
req.url is wired
|
||||||
in http server : http://www.baidu.com/a/b/c
|
in http server : http://www.example.com/a/b/c
|
||||||
in https server : /work/alibaba
|
in https server : /work/alibaba
|
||||||
*/
|
*/
|
||||||
|
|
||||||
//handle OPTIONS request
|
if(userRule.shouldUseLocalResponse(req)){
|
||||||
if(req.method == "OPTIONS"){
|
console.log("==>use local rules");
|
||||||
console.log("==>OPTIONS req for CROS, will allow all");
|
userRule.dealLocalResponse(req,function(statusCode,resHeader,resBody){
|
||||||
userRes.writeHead(200,mergeCORSHeader(req.headers)); //remove any cache related header, add crossdomain headers
|
|
||||||
userRes.end();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
//try to mactch rule file
|
//update record info
|
||||||
for(var index in handleRule.map){
|
resourceInfo.endTime = new Date().getTime();
|
||||||
var rule = handleRule.map[index];
|
resourceInfo.res = { //construct a self-defined res object
|
||||||
|
statusCode : statusCode || "",
|
||||||
|
headers : resHeader || {}
|
||||||
var hostTest = new RegExp(rule.host).test(host),
|
|
||||||
pathTest = new RegExp(rule.path).test(path);
|
|
||||||
|
|
||||||
if(hostTest && pathTest && (rule.localFile || rule.localDir) ){
|
|
||||||
console.log("==>meet the rules, will map to local file");
|
|
||||||
|
|
||||||
var targetLocalfile = rule.localFile;
|
|
||||||
|
|
||||||
//localfile not set, map to dir
|
|
||||||
if(!targetLocalfile){ //find file in dir, /a/b/file.html -> dir + b/file.html
|
|
||||||
var remotePathWithoutPrefix = path.replace(new RegExp(rule.path),""); //remove prefix
|
|
||||||
targetLocalfile = pathUtil.join(rule.localDir,remotePathWithoutPrefix);
|
|
||||||
}
|
}
|
||||||
|
resourceInfo.resBody = resBody;
|
||||||
|
resourceInfo.length = resBody.length;
|
||||||
|
|
||||||
console.log("==>local file: " + targetLocalfile);
|
try{
|
||||||
if(fs.existsSync(targetLocalfile)){
|
GLOBAL.recorder.updateRecord(resourceInfoId,resourceInfo);
|
||||||
try{
|
}catch(e){}
|
||||||
var fsStream = fs.createReadStream(targetLocalfile);
|
|
||||||
userRes.writeHead(200,mergeCORSHeader( req.headers,{}) ); //CORS for localfiles
|
|
||||||
fsStream.pipe(userRes);
|
|
||||||
ifLocalruleMatched = true;
|
|
||||||
break;
|
|
||||||
}catch(e){
|
|
||||||
console.log(e.message);
|
|
||||||
}
|
|
||||||
}else{
|
|
||||||
console.log("file not exist : " + targetLocalfile);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//sleep for seconds if configed in the rule file
|
userRes.writeHead(statusCode,resHeader);
|
||||||
//see rule_sample.js
|
userRes.end(resBody);
|
||||||
if(hostTest && pathTest && !!rule.sleep){
|
});
|
||||||
console.log(color.yellow('[' + req.url + '] will sleep for ' + rule.sleep + ' seconds.'));
|
|
||||||
sleep.sleep(rule.sleep);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(hostTest && pathTest && !!rule.callback){
|
|
||||||
callback = rule.callback;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(ifLocalruleMatched){
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
}else{
|
}else{
|
||||||
console.log("==>will forward to real server by proxy");
|
console.log("==>will forward to real server by proxy");
|
||||||
|
|
||||||
|
//modify protocol if needed
|
||||||
|
protocol = userRule.replaceRequestProtocol(req,protocol) || protocol;
|
||||||
|
|
||||||
var options = {
|
var options = {
|
||||||
hostname : urlPattern.hostname || req.headers.host,
|
hostname : urlPattern.hostname || req.headers.host,
|
||||||
port : urlPattern.port || req.port || (ifHttps ? 443 : 80),
|
port : urlPattern.port || req.port || (/https/.test(protocol) ? 443 : 80),
|
||||||
path : path,
|
path : path,
|
||||||
method : req.method,
|
method : req.method,
|
||||||
headers : req.headers
|
headers : req.headers
|
||||||
};
|
};
|
||||||
|
|
||||||
var proxyReq = (ifHttps ? https : http).request(options, function(res) {
|
//modify request options
|
||||||
userRes.writeHead(res.statusCode,mergeCORSHeader(req.headers,res.headers));
|
options = userRule.replaceRequestOption(req,options) || options;
|
||||||
|
|
||||||
|
var proxyReq = ( /https/.test(protocol) ? https : http).request(options, function(res) {
|
||||||
|
var statusCode = res.statusCode;
|
||||||
|
statusCode = userRule.replaceResponseStatusCode(req,res,statusCode) || statusCode;
|
||||||
|
|
||||||
|
var resHeader = res.headers;
|
||||||
|
resHeader = userRule.replaceResponseHeader(req,res,resHeader) || resHeader;
|
||||||
|
|
||||||
|
//remove content-encoding
|
||||||
|
// delete resHeader['content-encoding'];
|
||||||
|
|
||||||
|
userRes.writeHead(statusCode, resHeader);
|
||||||
|
|
||||||
var resData = [],
|
var resData = [],
|
||||||
length = 0;
|
length;
|
||||||
|
|
||||||
res.on("data",function(chunk){
|
res.on("data",function(chunk){
|
||||||
resData.push(chunk);
|
resData.push(chunk);
|
||||||
length += chunk.length;
|
|
||||||
userRes.write(chunk);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
res.on("end",function(){
|
res.on("end",function(){
|
||||||
callback && callback.call(null,userRes);
|
|
||||||
userRes.end();
|
|
||||||
|
|
||||||
//update record info
|
var serverResData,
|
||||||
resourceInfo.endTime = new Date().getTime();
|
userCustomResData;
|
||||||
resourceInfo.res = res;
|
|
||||||
resourceInfo.resBody = Buffer.concat(resData);
|
async.series([
|
||||||
resourceInfo.length = length;
|
//TODO : manage gzip
|
||||||
|
|
||||||
|
//unzip server res
|
||||||
|
function(callback){
|
||||||
|
serverResData = Buffer.concat(resData);
|
||||||
|
if(/gzip/i.test(res.headers['content-encoding'])){
|
||||||
|
zlib.gunzip(serverResData,function(err,buff){
|
||||||
|
serverResData = buff;
|
||||||
|
callback();
|
||||||
|
});
|
||||||
|
}else{
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
|
||||||
|
//get custom response
|
||||||
|
},function(callback){
|
||||||
|
|
||||||
|
userCustomResData = userRule.replaceServerResData(req,res,serverResData);
|
||||||
|
|
||||||
|
//gzip users' string if necessary
|
||||||
|
if(typeof userCustomResData == "string" && /gzip/i.test(res.headers['content-encoding']) ){
|
||||||
|
zlib.gzip(userCustomResData,function(err,data){
|
||||||
|
userCustomResData = data;
|
||||||
|
console.log(data);
|
||||||
|
callback();
|
||||||
|
});
|
||||||
|
}else{
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
|
||||||
|
//generate response data
|
||||||
|
},function(callback){
|
||||||
|
serverResData = userCustomResData || serverResData;
|
||||||
|
callback();
|
||||||
|
|
||||||
|
//delay
|
||||||
|
},function(callback){
|
||||||
|
var pauseTimeInMS = userRule.pauseBeforeSendingResponse(req,res);
|
||||||
|
if(pauseTimeInMS){
|
||||||
|
setTimeout(callback,pauseTimeInMS);
|
||||||
|
}else{
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
|
||||||
|
//send response
|
||||||
|
},function(callback){
|
||||||
|
userRes.write(serverResData);
|
||||||
|
userRes.end();
|
||||||
|
|
||||||
|
callback();
|
||||||
|
|
||||||
|
//udpate record info
|
||||||
|
},function(callback){
|
||||||
|
resourceInfo.endTime = new Date().getTime();
|
||||||
|
resourceInfo.res = res; //TODO : replace res header / statusCode ?
|
||||||
|
resourceInfo.resBody = serverResData;
|
||||||
|
resourceInfo.length = serverResData.length;
|
||||||
|
|
||||||
|
try{
|
||||||
|
GLOBAL.recorder.updateRecord(resourceInfoId,resourceInfo);
|
||||||
|
}catch(e){}
|
||||||
|
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
|
||||||
|
],function(err,result){
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
try{
|
|
||||||
GLOBAL.recorder.updateRecord(resourceInfoId,resourceInfo);
|
|
||||||
}catch(e){}
|
|
||||||
|
|
||||||
});
|
});
|
||||||
res.on('error',function(error){
|
res.on('error',function(error){
|
||||||
console.log('error',error);
|
console.log('error',error);
|
||||||
@ -230,6 +272,7 @@ function connectReqHandler(req, socket, head){
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//TODO : reactive this function
|
||||||
function setRules(newRule){
|
function setRules(newRule){
|
||||||
if(!newRule){
|
if(!newRule){
|
||||||
return;
|
return;
|
||||||
@ -242,35 +285,6 @@ function setRules(newRule){
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS
|
|
||||||
function mergeCORSHeader(reqHeader,originHeader){
|
|
||||||
var targetObj = originHeader || {};
|
|
||||||
|
|
||||||
delete targetObj["Access-Control-Allow-Credentials"];
|
|
||||||
delete targetObj["Access-Control-Allow-Origin"];
|
|
||||||
delete targetObj["Access-Control-Allow-Methods"];
|
|
||||||
delete targetObj["Access-Control-Allow-Headers"];
|
|
||||||
|
|
||||||
targetObj["access-control-allow-credentials"] = "true";
|
|
||||||
targetObj["access-control-allow-origin"] = reqHeader['origin'] || "-___-||";
|
|
||||||
targetObj["access-control-allow-methods"] = "GET, POST, PUT";
|
|
||||||
targetObj["access-control-allow-headers"] = reqHeader['access-control-request-headers'] || "-___-||";
|
|
||||||
|
|
||||||
//
|
|
||||||
// targetObj["Transfer-Encoding"] = "chunked";
|
|
||||||
|
|
||||||
// // Disable caching
|
|
||||||
// // If the response status is 304 not modified, the data event of response will not emmit
|
|
||||||
// targetObj["Cache-Control"] = "no-cache, no-store, must-revalidate";
|
|
||||||
// targetObj["Pragma"] = "no-cache";
|
|
||||||
// targetObj["Expires"] = 0;
|
|
||||||
// //
|
|
||||||
// targetObj["server"] = "anyproxy server";
|
|
||||||
// targetObj["x-powered-by"] = "Alipay-ct-wd";
|
|
||||||
|
|
||||||
return targetObj;
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports.userRequestHandler = userRequestHandler;
|
module.exports.userRequestHandler = userRequestHandler;
|
||||||
module.exports.connectReqHandler = connectReqHandler;
|
module.exports.connectReqHandler = connectReqHandler;
|
||||||
module.exports.setRules = setRules;
|
module.exports.setRules = setRules;
|
||||||
|
@ -14,7 +14,6 @@
|
|||||||
"entities": "^1.1.1",
|
"entities": "^1.1.1",
|
||||||
"express": "^4.8.5",
|
"express": "^4.8.5",
|
||||||
"nedb": "^0.11.0",
|
"nedb": "^0.11.0",
|
||||||
"sleep": "~1.1.8",
|
|
||||||
"ws": "^0.4.32"
|
"ws": "^0.4.32"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
@ -19,8 +19,7 @@ var rules = {
|
|||||||
"localDir" :"/Users/Stella/tmp/"
|
"localDir" :"/Users/Stella/tmp/"
|
||||||
},{
|
},{
|
||||||
"host" :/./,
|
"host" :/./,
|
||||||
"path" :/response\.(json)/,
|
"path" :/response\.(json)/
|
||||||
"sleep" :5//seconds
|
|
||||||
},{
|
},{
|
||||||
"host" :/./,
|
"host" :/./,
|
||||||
"path" :/html/,
|
"path" :/html/,
|
||||||
@ -33,7 +32,7 @@ var rules = {
|
|||||||
]
|
]
|
||||||
,"httpsConfig":{
|
,"httpsConfig":{
|
||||||
"bypassAll" : false, //by setting this to true, anyproxy will not intercept any https request
|
"bypassAll" : false, //by setting this to true, anyproxy will not intercept any https request
|
||||||
"interceptDomains":[/www\.alipay\.com/,/www\.b\.com/] //by setting bypassAll:false, requests towards these domains will be intercepted, and try to meet the map rules above
|
"interceptDomains":[/www\.example\.com/] //by setting bypassAll:false, requests towards these domains will be intercepted, and try to meet the map rules above
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user