anyproxy/lib/requestHandler.js

437 lines
14 KiB
JavaScript
Raw Normal View History

2014-08-29 18:08:54 +08:00
var http = require("http"),
2014-08-13 11:51:39 +08:00
https = require("https"),
net = require("net"),
fs = require("fs"),
url = require("url"),
pathUtil = require("path"),
2014-08-29 18:08:54 +08:00
zlib = require('zlib'),
async = require('async'),
2014-08-13 11:51:39 +08:00
color = require("colorful"),
Buffer = require('buffer').Buffer,
2014-09-02 14:54:45 +08:00
util = require("./util"),
getPort = require("./getPort"),
2014-10-29 16:20:18 +08:00
Stream = require("stream"),
logUtil = require("./log"),
2014-11-10 15:08:50 +08:00
httpsServerMgr = require("./httpsServerMgr");
var userRule = util.freshRequire('./rule_default');
2014-08-13 11:51:39 +08:00
function userRequestHandler(req,userRes){
2015-09-03 10:51:17 +08:00
/*
note
req.url is wired
in http server : http://www.example.com/a/b/c
in https server : /a/b/c
*/
var host = req.headers.host,
2015-08-19 11:03:33 +08:00
protocol = (!!req.connection.encrypted && !/^http:/.test(req.url)) ? "https" : "http",
fullUrl = protocol === "http" ? req.url : (protocol + '://' + host + req.url),
urlPattern = url.parse(fullUrl),
path = urlPattern.path,
resourceInfo,
2014-09-02 14:54:45 +08:00
resourceInfoId = -1,
reqData;
2014-08-27 17:42:42 +08:00
// console.log(req.url);
// console.log(path);
2014-08-29 18:08:54 +08:00
//record
resourceInfo = {
host : host,
method : req.method,
path : path,
2014-11-05 16:15:17 +08:00
protocol : protocol,
url : protocol + "://" + host + path,
req : req,
startTime : new Date().getTime()
};
2014-09-02 14:54:45 +08:00
if(GLOBAL.recorder){
2014-08-27 17:42:42 +08:00
resourceInfoId = GLOBAL.recorder.appendRecord(resourceInfo);
2014-09-02 14:54:45 +08:00
}
2014-08-13 11:51:39 +08:00
logUtil.printLog(color.green("\nreceived request to : " + host + path));
2014-08-13 11:51:39 +08:00
2014-09-02 14:54:45 +08:00
//get request body and route to local or remote
2015-07-08 17:55:14 +08:00
async.series([
fetchReqData,
routeReq
],function(){
//mark some ext info
if(req.anyproxy_map_local){
GLOBAL.recorder.updateExtInfo(resourceInfoId, {map : req.anyproxy_map_local});
}
});
2014-09-02 14:54:45 +08:00
//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)){
logUtil.printLog("==>use local rules");
2014-09-02 14:54:45 +08:00
dealWithLocalResponse(callback);
}else{
logUtil.printLog("==>will forward to real server by proxy");
2014-09-02 14:54:45 +08:00
dealWithRemoteResonse(callback);
}
}
function dealWithLocalResponse(callback){
userRule.dealLocalResponse(req,reqData,function(statusCode,resHeader,resBody){
2014-08-29 18:08:54 +08:00
//update record info
resourceInfo.endTime = new Date().getTime();
resourceInfo.res = { //construct a self-defined res object
statusCode : statusCode || "",
2014-09-02 14:54:45 +08:00
headers : resHeader || {}
2014-08-13 11:51:39 +08:00
}
2014-09-02 14:54:45 +08:00
resourceInfo.resHeader = resHeader || {};
resourceInfo.resBody = resBody;
2014-09-11 10:22:08 +08:00
resourceInfo.length = resBody ? resBody.length : 0;
2014-09-02 14:54:45 +08:00
resourceInfo.statusCode = statusCode;
2014-09-02 14:54:45 +08:00
GLOBAL.recorder && GLOBAL.recorder.updateRecord(resourceInfoId,resourceInfo);
2014-08-29 18:08:54 +08:00
userRes.writeHead(statusCode,resHeader);
userRes.end(resBody);
2014-09-02 14:54:45 +08:00
callback && callback();
2014-08-29 18:08:54 +08:00
});
2014-08-13 11:51:39 +08:00
return;
2014-09-02 14:54:45 +08:00
}
2014-08-13 11:51:39 +08:00
2014-09-02 14:54:45 +08:00
function dealWithRemoteResonse(callback){
var options;
2014-08-13 11:51:39 +08:00
2014-09-02 14:54:45 +08:00
//modify request protocol
2014-08-29 18:08:54 +08:00
protocol = userRule.replaceRequestProtocol(req,protocol) || protocol;
2014-09-02 14:54:45 +08:00
//modify request options
options = {
2014-08-21 17:40:02 +08:00
hostname : urlPattern.hostname || req.headers.host,
2014-08-29 18:08:54 +08:00
port : urlPattern.port || req.port || (/https/.test(protocol) ? 443 : 80),
2014-08-13 11:51:39 +08:00
path : path,
method : req.method,
headers : req.headers
};
2014-11-10 15:08:50 +08:00
2014-08-29 18:08:54 +08:00
options = userRule.replaceRequestOption(req,options) || options;
2015-01-09 15:37:13 +08:00
options.rejectUnauthorized = false;
2015-07-28 21:53:42 +08:00
try{
delete options.headers['accept-encoding']; //avoid gzipped response
}catch(e){}
2014-08-29 18:08:54 +08:00
2015-08-12 19:26:29 +08:00
//update request data
2014-09-02 14:54:45 +08:00
reqData = userRule.replaceRequestData(req,reqData) || reqData;
options.headers = util.lower_keys(options.headers);
options.headers["content-length"] = reqData.length; //rewrite content length info
options.headers = util.upper_keys(options.headers);
2014-09-02 14:54:45 +08:00
//send request
2014-08-29 18:08:54 +08:00
var proxyReq = ( /https/.test(protocol) ? https : http).request(options, function(res) {
2014-09-02 14:54:45 +08:00
//deal response header
2014-08-29 18:08:54 +08:00
var statusCode = res.statusCode;
statusCode = userRule.replaceResponseStatusCode(req,res,statusCode) || statusCode;
var resHeader = userRule.replaceResponseHeader(req,res,res.headers) || res.headers;
2014-09-02 14:54:45 +08:00
resHeader = util.lower_keys(resHeader);
2014-08-29 18:08:54 +08:00
2014-09-02 14:54:45 +08:00
// remove gzip related header, and ungzip the content
2015-07-28 21:53:42 +08:00
// note there are other compression types like deflate
2015-01-12 10:43:20 +08:00
var ifServerGzipped = /gzip/i.test(resHeader['content-encoding']);
2015-07-28 21:53:42 +08:00
if(ifServerGzipped){
delete resHeader['content-encoding'];
}
delete resHeader['content-length'];
2014-08-29 18:08:54 +08:00
userRes.writeHead(statusCode, resHeader);
2014-08-27 09:02:35 +08:00
2014-09-02 14:54:45 +08:00
//deal response data
var length,
resData = [];
2014-08-29 18:08:54 +08:00
2014-08-27 17:42:42 +08:00
res.on("data",function(chunk){
resData.push(chunk);
});
res.on("end",function(){
2014-09-02 14:54:45 +08:00
var serverResData;
2014-08-29 18:08:54 +08:00
async.series([
//ungzip server res
2014-08-29 18:08:54 +08:00
function(callback){
serverResData = Buffer.concat(resData);
if(ifServerGzipped ){
2014-08-29 18:08:54 +08:00
zlib.gunzip(serverResData,function(err,buff){
serverResData = buff;
callback();
});
}else{
callback();
}
//get custom response
},function(callback){
2014-10-21 11:18:55 +08:00
if(userRule.replaceServerResData){
logUtil.printLog(color.red("replaceServerResData is deprecated, and will be unavilable soon. Use replaceServerResDataAsync instead."), logUtil.T_ERR);
2014-10-21 11:18:55 +08:00
serverResData = userRule.replaceServerResData(req,res,serverResData) || serverResData;
callback();
}else if(userRule.replaceServerResDataAsync){
userRule.replaceServerResDataAsync(req,res,serverResData,function(newRes){
serverResData = newRes;
callback();
});
}else{
callback();
}
2014-08-29 18:08:54 +08:00
//delay
},function(callback){
var pauseTimeInMS = userRule.pauseBeforeSendingResponse(req,res);
2014-08-29 18:08:54 +08:00
if(pauseTimeInMS){
setTimeout(callback,pauseTimeInMS);
}else{
callback();
}
//send response
},function(callback){
2014-10-29 16:20:18 +08:00
if(GLOBAL._throttle){
var thrStream = new Stream();
var readable = thrStream.pipe(GLOBAL._throttle.throttle());
readable.pipe(userRes);
thrStream.emit("data",serverResData);
thrStream.emit("end");
callback();
}else{
2014-09-05 18:12:50 +08:00
userRes.end(serverResData);
2014-10-29 16:20:18 +08:00
callback();
}
2014-08-29 18:08:54 +08:00
//udpate record info
},function(callback){
resourceInfo.endTime = new Date().getTime();
resourceInfo.statusCode = statusCode;
resourceInfo.resHeader = resHeader;
resourceInfo.resBody = serverResData;
2014-09-11 10:33:03 +08:00
resourceInfo.length = serverResData ? serverResData.length : 0;
2014-09-02 14:54:45 +08:00
GLOBAL.recorder && GLOBAL.recorder.updateRecord(resourceInfoId,resourceInfo);
2014-08-29 18:08:54 +08:00
callback();
//push trafic data to rule file
},function(callback){
userRule.fetchTrafficData && userRule.fetchTrafficData(resourceInfoId,resourceInfo);
callback();
2014-08-29 18:08:54 +08:00
}
],function(err,result){
2014-09-02 14:54:45 +08:00
callback && callback();
2014-08-29 18:08:54 +08:00
});
2014-08-27 17:42:42 +08:00
2014-08-27 09:02:35 +08:00
});
2014-08-27 15:24:30 +08:00
res.on('error',function(error){
logUtil.printLog('error' + error, logUtil.T_ERR);
2014-08-27 15:24:30 +08:00
});
2014-08-27 09:02:35 +08:00
2014-08-13 11:51:39 +08:00
});
proxyReq.on("error",function(e){
logUtil.printLog("err with request :" + e + " " + req.url, logUtil.T_ERR);
2014-08-13 11:51:39 +08:00
userRes.end();
});
2014-08-21 17:40:02 +08:00
proxyReq.end(reqData);
2014-08-13 11:51:39 +08:00
}
}
function connectReqHandler(req, socket, head){
var host = req.url.split(":")[0],
2014-08-13 11:51:39 +08:00
targetPort= req.url.split(":")[1],
resourceInfo,
resourceInfoId;
2014-08-13 11:51:39 +08:00
var shouldIntercept = userRule.shouldInterceptHttpsReq(req);
2014-08-13 11:51:39 +08:00
//bypass webSocket on webinterface
2014-09-11 14:17:48 +08:00
if(targetPort == 8003){
shouldIntercept = false; // TODO : a more general solution?
}
logUtil.printLog(color.green("\nreceived https CONNECT request " + host));
if(shouldIntercept){
logUtil.printLog("==>will forward to local https server");
}else{
logUtil.printLog("==>will bypass the man-in-the-middle proxy");
}
//record
resourceInfo = {
host : host,
method : req.method,
path : "",
url : "https://" + host,
req : req,
startTime : new Date().getTime()
};
resourceInfoId = GLOBAL.recorder.appendRecord(resourceInfo);
var proxyPort,
proxyHost,
internalHttpsPort,
httpsServerMgrInstance;
async.series([
//check if internal https server exists
function(callback){
2015-03-20 11:42:54 +08:00
if(!shouldIntercept){
callback();
2015-03-20 11:42:54 +08:00
return;
}else{
2015-03-20 11:42:54 +08:00
if(internalHttpsPort){
callback();
2015-03-20 11:42:54 +08:00
}else{
getPort(function(port){
internalHttpsPort = port;
httpsServerMgrInstance = new httpsServerMgr({
port :port,
handler :userRequestHandler
});
callback();
});
}
}
},
//determine the target server
function(callback){
if(shouldIntercept){
proxyPort = internalHttpsPort;
proxyHost = "127.0.0.1";
callback();
2014-08-13 11:51:39 +08:00
}else{
2015-02-12 14:37:44 +08:00
proxyPort = (targetPort == 80)? 443 : targetPort;
proxyHost = host;
2014-08-13 11:51:39 +08:00
callback();
}
//connect
},function(callback){
try{
var conn = net.connect(proxyPort, proxyHost, function(){
2015-01-12 10:43:20 +08:00
socket.write('HTTP/' + req.httpVersion + ' 200 OK\r\n\r\n', 'UTF-8', function(){
//throttle for direct-foward https
if(GLOBAL._throttle && !shouldIntercept ){
var readable = conn.pipe(GLOBAL._throttle.throttle());
readable.pipe(socket);
socket.pipe(conn);
}else{
conn.pipe(socket);
socket.pipe(conn);
}
2014-11-17 11:02:24 +08:00
callback();
2014-08-13 11:51:39 +08:00
});
});
conn.on("error",function(e){
logUtil.printLog("err when connect to + " + host + " , " + e, logUtil.T_ERR);
});
}catch(e){
logUtil.printLog("err when connect to remote https server (__host)".replace(/__host/,host), logUtil.T_ERR);
2014-08-13 11:51:39 +08:00
}
//update record
},function(callback){
resourceInfo.endTime = new Date().getTime();
resourceInfo.statusCode = "200";
resourceInfo.resHeader = {};
resourceInfo.resBody = "";
resourceInfo.length = 0;
2014-09-02 14:54:45 +08:00
GLOBAL.recorder && GLOBAL.recorder.updateRecord(resourceInfoId,resourceInfo);
callback();
}
],function(err,result){
if(err){
logUtil.printLog("err " + err, logUtil.T_ERR);
throw err;
}
});
}
/**
* @return return the merged rule for reference
*/
2014-08-13 11:51:39 +08:00
function setRules(newRule){
2014-08-13 11:51:39 +08:00
if(!newRule){
return userRule;
2014-08-13 11:51:39 +08:00
}else{
2014-09-11 10:22:08 +08:00
if(!newRule.summary){
newRule.summary = function(){
return "this rule file does not have a summary";
};
}
userRule = util.merge(userRule,newRule);
2014-11-13 13:12:13 +08:00
var functions = [];
if('function' == typeof(userRule.init)){
functions.push(function(cb){
userRule.init(cb);
});
}
if('function' == typeof(userRule.summary)){
functions.push(function(cb){
logUtil.printLog(userRule.summary());
2014-11-13 13:12:13 +08:00
cb(null);
});
}
async.series(functions,function(errors,result){
if(!errors){
logUtil.printLog(color.green('Anyproxy rules initialize finished, have fun!'));
2014-11-13 13:12:13 +08:00
}
});
return userRule;
2014-08-13 11:51:39 +08:00
}
2014-08-11 16:43:14 +08:00
}
2014-09-11 10:22:08 +08:00
function getRuleSummary(){
return userRule.summary();
}
2014-08-13 11:51:39 +08:00
module.exports.userRequestHandler = userRequestHandler;
module.exports.connectReqHandler = connectReqHandler;
module.exports.setRules = setRules;
2014-09-11 10:22:08 +08:00
module.exports.getRuleSummary = getRuleSummary;
module.exports.token = Date.now();