reconstruct rule file

This commit is contained in:
加里 2014-08-29 18:08:54 +08:00
parent ef9f00630d
commit 90b6d3dc6f
6 changed files with 125 additions and 114 deletions

View File

@ -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

View File

@ -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

View File

@ -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;

View File

@ -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": {

View File

@ -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
} }
} }

View File

@ -52,7 +52,7 @@ module.exports.testHttpsOverHttp = function(test){
}); });
var req = https.request({ var req = https.request({
host: 'www.alipay.com', host: 'www.gotofail.com',
port: 443, port: 443,
agent: tunnelingAgent agent: tunnelingAgent
},function(res){ },function(res){