diff --git a/lib/recorder.js b/lib/recorder.js
index 5f65a45..8f204d1 100644
--- a/lib/recorder.js
+++ b/lib/recorder.js
@@ -82,6 +82,7 @@ function normalizeInfo(id,info){
//req
singleRecord.reqHeader = info.req.headers;
singleRecord.startTime = info.startTime;
+ singleRecord.reqBody = info.reqBody || "";
//res
if(info.endTime){
@@ -105,7 +106,6 @@ function normalizeInfo(id,info){
singleRecord.duration = "";
}
-
return singleRecord;
}
diff --git a/lib/requestHandler.js b/lib/requestHandler.js
index 16a2d4d..8c3c118 100644
--- a/lib/requestHandler.js
+++ b/lib/requestHandler.js
@@ -8,10 +8,12 @@ var http = require("http"),
async = require('async'),
color = require("colorful"),
Buffer = require('buffer').Buffer,
+ util = require("./util"),
httpsServerMgr = require("./httpsServerMgr");
var httpsServerMgrInstance = new httpsServerMgr(),
- userRule = require("./rule_default.js"); //default rule file
+ defaultRule = require("./rule_default.js"),
+ userRule = defaultRule; //init
function userRequestHandler(req,userRes){
var host = req.headers.host,
@@ -19,7 +21,8 @@ function userRequestHandler(req,userRes){
path = urlPattern.path,
protocol = (!!req.connection.encrypted && !/http:/.test(req.url)) ? "https" : "http",
resourceInfo,
- resourceInfoId = -1;
+ resourceInfoId = -1,
+ reqData;
//record
resourceInfo = {
@@ -30,84 +33,113 @@ function userRequestHandler(req,userRes){
req : req,
startTime : new Date().getTime()
};
-
- try{
+ if(GLOBAL.recorder){
resourceInfoId = GLOBAL.recorder.appendRecord(resourceInfo);
- }catch(e){}
+ }
console.log(color.green("\nreceived request to : " + host + path));
- /*
- req.url is wired
- in http server : http://www.example.com/a/b/c
- in https server : /work/alibaba
- */
- if(userRule.shouldUseLocalResponse(req)){
- console.log("==>use local rules");
- userRule.dealLocalResponse(req,function(statusCode,resHeader,resBody){
+ //get request body and route to local or remote
+ async.series([fetchReqData,routeReq],function(){});
+
+ //get request body
+ function fetchReqData(callback){
+ var postData = [];
+ req.on("data",function(chunk){
+ postData.push(chunk);
+ });
+ req.on("end",function(){
+ reqData = Buffer.concat(postData);
+ resourceInfo.reqBody = reqData.toString();
+ GLOBAL.recorder && GLOBAL.recorder.updateRecord(resourceInfoId,resourceInfo);
+
+ callback();
+ });
+ }
+
+ //route to dealing function
+ function routeReq(callback){
+ if(userRule.shouldUseLocalResponse(req,reqData)){
+ console.log("==>use local rules");
+ dealWithLocalResponse(callback);
+ }else{
+ console.log("==>will forward to real server by proxy");
+ dealWithRemoteResonse(callback);
+ }
+ }
+
+ function dealWithLocalResponse(callback){
+ userRule.dealLocalResponse(req,reqData,function(statusCode,resHeader,resBody){
//update record info
resourceInfo.endTime = new Date().getTime();
resourceInfo.res = { //construct a self-defined res object
statusCode : statusCode || "",
- headers : resHeader || {}
+ headers : resHeader || {}
}
- resourceInfo.resBody = resBody;
- resourceInfo.length = resBody.length;
-
- try{
- GLOBAL.recorder.updateRecord(resourceInfoId,resourceInfo);
- }catch(e){}
+ resourceInfo.resHeader = resHeader || {};
+ resourceInfo.resBody = resBody;
+ resourceInfo.length = resBody.length;
+ resourceInfo.statusCode = statusCode;
+
+ GLOBAL.recorder && GLOBAL.recorder.updateRecord(resourceInfoId,resourceInfo);
userRes.writeHead(statusCode,resHeader);
userRes.end(resBody);
+ callback && callback();
});
return;
+ }
- }else{
- console.log("==>will forward to real server by proxy");
+ function dealWithRemoteResonse(callback){
+ var options;
- //modify protocol if needed
+ //modify request protocol
protocol = userRule.replaceRequestProtocol(req,protocol) || protocol;
- var options = {
+ //modify request options
+ options = {
hostname : urlPattern.hostname || req.headers.host,
port : urlPattern.port || req.port || (/https/.test(protocol) ? 443 : 80),
path : path,
method : req.method,
headers : req.headers
};
-
- //modify request options
options = userRule.replaceRequestOption(req,options) || options;
+ //update quest data
+ reqData = userRule.replaceRequestData(req,reqData) || reqData;
+ options.headers = util.lower_keys(options.headers);
+ options.headers["content-length"] = reqData.length; //rewrite content length info
+
+ //send request
var proxyReq = ( /https/.test(protocol) ? https : http).request(options, function(res) {
+
+ //deal response header
var statusCode = res.statusCode;
statusCode = userRule.replaceResponseStatusCode(req,res,statusCode) || statusCode;
var resHeader = userRule.replaceResponseHeader(req,res,res.headers) || res.headers;
- resHeader = lower_keys(resHeader);
+ resHeader = util.lower_keys(resHeader);
- /* remove gzip related header, and ungzip the content */
+ // remove gzip related header, and ungzip the content
var ifServerGzipped = /gzip/i.test(resHeader['content-encoding']);
delete resHeader['content-encoding'];
delete resHeader['content-length'];
userRes.writeHead(statusCode, resHeader);
- //waiting for data
- var resData = [],
- length;
+ //deal response data
+ var length,
+ resData = [];
res.on("data",function(chunk){
resData.push(chunk);
});
res.on("end",function(){
-
- var serverResData,
- userCustomResData;
+ var serverResData;
async.series([
@@ -125,9 +157,7 @@ function userRequestHandler(req,userRes){
//get custom response
},function(callback){
-
- userCustomResData = userRule.replaceServerResData(req,res,serverResData);
- serverResData = userCustomResData || serverResData;
+ serverResData = userRule.replaceServerResData(req,res,serverResData) || serverResData;
callback();
//delay
@@ -152,15 +182,13 @@ function userRequestHandler(req,userRes){
resourceInfo.resBody = serverResData;
resourceInfo.length = serverResData.length;
- try{
- GLOBAL.recorder.updateRecord(resourceInfoId,resourceInfo);
- }catch(e){}
+ GLOBAL.recorder && GLOBAL.recorder.updateRecord(resourceInfoId,resourceInfo);
callback();
}
],function(err,result){
-
+ callback && callback();
});
});
@@ -175,7 +203,7 @@ function userRequestHandler(req,userRes){
userRes.end();
});
- req.pipe(proxyReq);
+ proxyReq.end(reqData);
}
}
@@ -255,9 +283,7 @@ function connectReqHandler(req, socket, head){
resourceInfo.resBody = "";
resourceInfo.length = 0;
- try{
- GLOBAL.recorder.updateRecord(resourceInfoId,resourceInfo);
- }catch(e){}
+ GLOBAL.recorder && GLOBAL.recorder.updateRecord(resourceInfoId,resourceInfo);
callback();
}
@@ -269,26 +295,21 @@ function connectReqHandler(req, socket, head){
});
}
-// {"Content-Encoding":"gzip"} --> {"content-encoding":"gzip"}
-function lower_keys(obj){
- for(var key in obj){
- var val = obj[key];
- delete obj[key];
-
- obj[key.toLowerCase()] = val;
- }
-
- return obj;
-}
-
function setRules(newRule){
if(!newRule){
return;
}else{
- userRule = newRule;
+ userRule = util.merge(defaultRule,newRule);
}
}
module.exports.userRequestHandler = userRequestHandler;
module.exports.connectReqHandler = connectReqHandler;
module.exports.setRules = setRules;
+
+/*
+note
+ req.url is wired
+ in http server : http://www.example.com/a/b/c
+ in https server : /work/alibaba
+*/
diff --git a/lib/rule_default.js b/lib/rule_default.js
index b80d8c6..75e63e4 100644
--- a/lib/rule_default.js
+++ b/lib/rule_default.js
@@ -1,14 +1,17 @@
module.exports = {
- shouldUseLocalResponse : function(req){
+ shouldUseLocalResponse : function(req,reqBody){
},
- dealLocalResponse : function(req,callback){
+ dealLocalResponse : function(req,reqBody,callback){
+ },
+
+ replaceRequestProtocol:function(req,protocol){
},
replaceRequestOption : function(req,option){
},
- replaceRequestProtocol:function(req,protocol){
+ replaceRequestData: function(req,data){
},
replaceResponseStatusCode: function(req,res,statusCode){
diff --git a/lib/util.js b/lib/util.js
new file mode 100644
index 0000000..8f58988
--- /dev/null
+++ b/lib/util.js
@@ -0,0 +1,20 @@
+// {"Content-Encoding":"gzip"} --> {"content-encoding":"gzip"}
+module.exports.lower_keys = function(obj){
+ for(var key in obj){
+ var val = obj[key];
+ delete obj[key];
+
+ obj[key.toLowerCase()] = val;
+ }
+
+ return obj;
+}
+
+module.exports.merge = function(baseObj, extendObj){
+ for(var key in extendObj){
+ baseObj[key] = extendObj[key];
+ }
+
+ return baseObj;
+}
+
diff --git a/package.json b/package.json
index 239e1d9..c9c82e3 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "anyproxy",
- "version": "2.0.0",
+ "version": "2.1.0",
"description": "a charles/fiddler like proxy written in NodeJs, which can handle HTTPS requests and CROS perfectly.",
"main": "proxy.js",
"bin": {
@@ -13,6 +13,7 @@
"commander": "~2.3.0",
"entities": "^1.1.1",
"express": "^4.8.5",
+ "iconv-lite": "^0.4.4",
"nedb": "^0.11.0",
"ws": "^0.4.32"
},
diff --git a/proxy.js b/proxy.js
index 7e95430..9072cf1 100644
--- a/proxy.js
+++ b/proxy.js
@@ -15,6 +15,12 @@ var http = require('http'),
GLOBAL.recorder = new Recorder();
+//mix some modules to global.util
+try{
+ GLOBAL.util = {};
+ GLOBAL.util['iconv-lite'] = require("iconv-lite");
+}catch(e){}
+
var T_TYPE_HTTP = 0,
T_TYPE_HTTPS = 1,
DEFAULT_PORT = 8001,
diff --git a/rule_sample/rule__blank.js b/rule_sample/rule__blank.js
index 7886edb..84af8f0 100644
--- a/rule_sample/rule__blank.js
+++ b/rule_sample/rule__blank.js
@@ -1,31 +1,27 @@
module.exports = {
/*
- these functions are required
- you may leave their bodies blank if necessary
+ these functions will overwrite the default ones, write your own when necessary.
*/
//whether to intercept this request by local logic
//if the return value is true, anyproxy will call dealLocalResponse to get response data and will not send request to remote server anymore
- shouldUseLocalResponse : function(req){
- return false;
+ shouldUseLocalResponse : function(req,reqBody){
+ if(/hello/.test(reqBody.toString())){
+ return true;
+ }else{
+ return false;
+ }
},
//you may deal the response locally instead of sending it to server
//this function be called when shouldUseLocalResponse returns true
//callback(statusCode,resHeader,responseData)
//e.g. callback(200,{"content-type":"text/html"},"hello world")
- dealLocalResponse : function(req,callback){
+ dealLocalResponse : function(req,reqBody,callback){
+ callback(200,{"content-type":"text/html"},reqBody);
//callback(statusCode,resHeader,responseData)
},
- //req is user's request sent to the proxy server
- // option is how the proxy server will send request to the real server. i.e. require("http").request(option,function(){...})
- //you may return a customized option to replace the original option
- replaceRequestOption : function(req,option){
- var newOption = option;
- return newOption;
- },
-
//replace the request protocol when sending to the real server
//protocol : "http" or "https"
replaceRequestProtocol:function(req,protocol){
@@ -33,6 +29,24 @@ module.exports = {
return newProtocol;
},
+ //req is user's request sent to the proxy server
+ //option is how the proxy server will send request to the real server. i.e. require("http").request(option,function(){...})
+ //you may return a customized option to replace the original option
+ //you should not write content-length header in options, since anyproxy will handle it for you
+ replaceRequestOption : function(req,option){
+ var newOption = option;
+ return newOption;
+ },
+
+ //replace the request body
+ replaceRequestData: function(req,data){
+ // console.log(data.toString().indexOf("alipay.acquire.order.precreate"));
+ // if(data.toString().indexOf("alipay.acquire.order.precreate") >= 0){
+ // req.needReplaceResponse = true;
+ // }
+ // return text;
+ },
+
//replace the statusCode before it's sent to the user
replaceResponseStatusCode: function(req,res,statusCode){
var newStatusCode = statusCode;
diff --git a/rule_sample/rule_adjust_response_time.js b/rule_sample/rule_adjust_response_time.js
index 6ccb6ab..32ec584 100644
--- a/rule_sample/rule_adjust_response_time.js
+++ b/rule_sample/rule_adjust_response_time.js
@@ -1,34 +1,10 @@
//rule scheme :
module.exports = {
- shouldUseLocalResponse : function(req){
- },
-
- dealLocalResponse : function(req,callback){
- },
-
- replaceRequestOption : function(req,option){
-
- },
-
- replaceRequestProtocol:function(req,protocol){
- },
-
- replaceResponseStatusCode: function(req,res,statusCode){
- },
-
- replaceResponseHeader: function(req,res,header){
- },
-
- replaceServerResData: function(req,res,serverResData){
-
- },
pauseBeforeSendingResponse : function(req,res){
//delay all the response for 1500ms
return 1500;
- },
-
- shouldInterceptHttpsReq :function(req){
}
+
};
\ No newline at end of file
diff --git a/rule_sample/rule_allow_CORS.js b/rule_sample/rule_allow_CORS.js
index e1f468f..182152c 100644
--- a/rule_sample/rule_allow_CORS.js
+++ b/rule_sample/rule_allow_CORS.js
@@ -2,7 +2,7 @@
// Ref: https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS
module.exports = {
- shouldUseLocalResponse : function(req){
+ shouldUseLocalResponse : function(req,reqBody){
//intercept all options request
if(req.method == "OPTIONS"){
return true;
@@ -11,33 +11,16 @@ module.exports = {
}
},
- dealLocalResponse : function(req,callback){
+ dealLocalResponse : function(req,reqBody,callback){
if(req.method == "OPTIONS"){
callback(200,mergeCORSHeader(req.headers),"");
}
},
- replaceRequestOption : function(req,option){
- },
-
- replaceRequestProtocol:function(req,protocol){
- },
-
- replaceResponseStatusCode: function(req,res,statusCode){
- },
-
replaceResponseHeader: function(req,res,header){
return mergeCORSHeader(req.headers, header);
- },
-
- replaceServerResData: function(req,res,serverResData){
- },
-
- pauseBeforeSendingResponse : function(req,res){
- },
-
- shouldInterceptHttpsReq :function(req){
}
+
};
function mergeCORSHeader(reqHeader,originHeader){
diff --git a/rule_sample/rule_intercept_some_https_requests.js b/rule_sample/rule_intercept_some_https_requests.js
index 3a3b94f..5ee36fc 100644
--- a/rule_sample/rule_intercept_some_https_requests.js
+++ b/rule_sample/rule_intercept_some_https_requests.js
@@ -1,24 +1,7 @@
//rule scheme :
module.exports = {
- shouldUseLocalResponse : function(req){
- },
- dealLocalResponse : function(req,callback){
- },
-
- replaceRequestOption : function(req,option){
-
- },
-
- replaceRequestProtocol:function(req,protocol){
- },
-
- replaceResponseStatusCode: function(req,res,statusCode){
- },
-
- replaceResponseHeader: function(req,res,header){
- },
replaceServerResData: function(req,res,serverResData){
//add "hello github" to all github pages
@@ -28,9 +11,6 @@ module.exports = {
return serverResData;
},
- pauseBeforeSendingResponse : function(req,res){
- },
-
shouldInterceptHttpsReq :function(req){
//intercept https://github.com/
//otherwise, all the https traffic will not go through this proxy
diff --git a/rule_sample/rule_remove_cache_header.js b/rule_sample/rule_remove_cache_header.js
index 12bbc34..5c93e7e 100644
--- a/rule_sample/rule_remove_cache_header.js
+++ b/rule_sample/rule_remove_cache_header.js
@@ -1,21 +1,6 @@
//rule scheme :
module.exports = {
- shouldUseLocalResponse : function(req){
- },
-
- dealLocalResponse : function(req,callback){
- },
-
- replaceRequestOption : function(req,option){
- },
-
- replaceRequestProtocol:function(req,protocol){
- },
-
- replaceResponseStatusCode: function(req,res,statusCode){
- },
-
replaceResponseHeader: function(req,res,header){
header = header || {};
header["Cache-Control"] = "no-cache, no-store, must-revalidate";
@@ -23,17 +8,7 @@ module.exports = {
header["Expires"] = 0;
return header;
- },
-
- replaceServerResData: function(req,res,serverResData){
- },
-
- pauseBeforeSendingResponse : function(req,res){
- },
-
- shouldInterceptHttpsReq :function(req){
}
-
};
function disableCacheHeader(header){
diff --git a/rule_sample/rule_replace_request_option.js b/rule_sample/rule_replace_request_option.js
index 268541d..6f3930c 100644
--- a/rule_sample/rule_replace_request_option.js
+++ b/rule_sample/rule_replace_request_option.js
@@ -1,11 +1,6 @@
//rule scheme :
module.exports = {
- shouldUseLocalResponse : function(req){
- },
-
- dealLocalResponse : function(req,callback){
- },
replaceRequestOption : function(req,option){
//replace request towards http://www.taobao.com
@@ -24,25 +19,5 @@ module.exports = {
if(option.hostname == "www.taobao.com" && option.path == "/"){
option.path = "/about/";
}
-
- console.log(option);
- },
-
- replaceRequestProtocol:function(req,protocol){
- },
-
- replaceResponseStatusCode: function(req,res,statusCode){
- },
-
- replaceResponseHeader: function(req,res,header){
- },
-
- replaceServerResData: function(req,res,serverResData){
- },
-
- pauseBeforeSendingResponse : function(req,res){
- },
-
- shouldInterceptHttpsReq :function(req){
}
};
\ No newline at end of file
diff --git a/rule_sample/rule_replace_response_data.js b/rule_sample/rule_replace_response_data.js
index daf4d69..872f5f4 100644
--- a/rule_sample/rule_replace_response_data.js
+++ b/rule_sample/rule_replace_response_data.js
@@ -1,24 +1,6 @@
//rule scheme :
module.exports = {
- shouldUseLocalResponse : function(req){
- },
-
- dealLocalResponse : function(req,callback){
- },
-
- replaceRequestOption : function(req,option){
-
- },
-
- replaceRequestProtocol:function(req,protocol){
- },
-
- replaceResponseStatusCode: function(req,res,statusCode){
- },
-
- replaceResponseHeader: function(req,res,header){
- },
replaceServerResData: function(req,res,serverResData){
@@ -31,11 +13,5 @@ module.exports = {
return serverResData;
}
- },
-
- pauseBeforeSendingResponse : function(req,res){
- },
-
- shouldInterceptHttpsReq :function(req){
}
};
\ No newline at end of file
diff --git a/rule_sample/rule_replace_response_status_code.js b/rule_sample/rule_replace_response_status_code.js
index 763bb1f..24b7777 100644
--- a/rule_sample/rule_replace_response_status_code.js
+++ b/rule_sample/rule_replace_response_status_code.js
@@ -1,18 +1,6 @@
//rule scheme :
module.exports = {
- shouldUseLocalResponse : function(req){
- },
-
- dealLocalResponse : function(req,callback){
- },
-
- replaceRequestOption : function(req,option){
-
- },
-
- replaceRequestProtocol:function(req,protocol){
- },
replaceResponseStatusCode: function(req,res,statusCode){
//redirect requests toward http://www.taobao.com/*
@@ -32,14 +20,5 @@ module.exports = {
}
return header;
- },
-
- replaceServerResData: function(req,res,serverResData){
- },
-
- pauseBeforeSendingResponse : function(req,res){
- },
-
- shouldInterceptHttpsReq :function(req){
}
};
\ No newline at end of file
diff --git a/rule_sample/rule_use_local_data.js b/rule_sample/rule_use_local_data.js
index 28a5520..a9221c8 100644
--- a/rule_sample/rule_use_local_data.js
+++ b/rule_sample/rule_use_local_data.js
@@ -1,9 +1,9 @@
//replace all the images with local one
-var url = require("url"),
- path = require("path"),
- fs = require("fs"),
- buffer = require("buffer");
+var url = require("url"),
+ path = require("path"),
+ fs = require("fs"),
+ buffer = require("buffer");
var map = [
{
@@ -15,7 +15,7 @@ var map = [
];
module.exports = {
- shouldUseLocalResponse : function(req){
+ shouldUseLocalResponse : function(req,reqBody){
var host = req.headers.host,
urlPattern = url.parse(req.url),
path = urlPattern.path;
@@ -45,31 +45,10 @@ module.exports = {
return false;
},
- dealLocalResponse : function(req,callback){
+ dealLocalResponse : function(req,reqBody,callback){
if(req.replaceLocalFile){
callback(200, {"content-type":"image/png"}, fs.readFileSync(req.replaceLocalFile) );
}
- },
-
- replaceRequestOption : function(req,option){
- },
-
- replaceRequestProtocol:function(req,protocol){
- },
-
- replaceResponseStatusCode: function(req,res,statusCode){
- },
-
- replaceResponseHeader: function(req,res,header){
- },
-
- replaceServerResData: function(req,res,serverResData){
- },
-
- pauseBeforeSendingResponse : function(req,res){
- },
-
- shouldInterceptHttpsReq :function(req){
}
};
diff --git a/web/index.html b/web/index.html
index 692549f..5a3c8ce 100644
--- a/web/index.html
+++ b/web/index.html
@@ -58,6 +58,13 @@
+
+
<% if(statusCode) { %>