add bin.js

This commit is contained in:
加里 2014-08-13 11:51:39 +08:00
parent cdb2dbd744
commit a9d56ef859
11 changed files with 361 additions and 100 deletions

22
bin.js
View File

@ -1,22 +1,36 @@
#!/usr/bin/env node #!/usr/bin/env node
var program = require('commander'), var program = require('commander'),
mainProxy = require("./proxy.js"); mainProxy = require("./proxy.js"),
color = require('colorful'),
fs = require("fs");
program program
.option('-u, --host [value]', 'hostname for https proxy, localhost for default') .option('-u, --host [value]', 'hostname for https proxy, localhost for default')
.option('-t, --type [value]', 'http|https,http for default') .option('-t, --type [value]', 'http|https,http for default')
.option('-p, --port [value]', 'proxy port, 8001 for default') .option('-p, --port [value]', 'proxy port, 8001 for default')
.option('-r, --rule [value]', 'rule file to map localfiles')
.option('-g, --root [value]', 'generate root CA')
.option('-c, --clear', 'clear all the tmp certificates') .option('-c, --clear', 'clear all the tmp certificates')
.parse(process.argv); .parse(process.argv);
if(program.clear){ if(program.clear){
require("./lib/certMgr").clearCerts(function(){ require("./lib/certMgr").clearCerts(function(){
console.log("all certs cleared"); console.log( color.green("all certs cleared") );
process.exit(0); process.exit(0);
}); });
}else if(program.root){
require("./lib/certMgr").generateRootCA(function(){
process.exit(0);
});
}else{ }else{
mainProxy.startServer(program.type,program.port, program.host); var handleRule;
if(program.rule){
console.log("parsing rule file..");
var handleRule = require(program.rule);
}
mainProxy.startServer(program.type,program.port, program.host ,handleRule);
} }

View File

@ -16,10 +16,10 @@ email=a@b.com
#Optional #Optional
password=a password=a
if [ -z "$outputPath$domain" ] if [ -z "$domain" ]
then then
echo "Argument not present." echo "Argument not present."
echo "Useage $0 [common name]" echo "Useage $0 [domain] [outputPath]"
exit 99 exit 99
fi fi

View File

@ -3,4 +3,11 @@
outputPath=$1 outputPath=$1
cd $outputPath cd $outputPath
openssl genrsa -out rootCA.key 2048 openssl genrsa -out rootCA.key 2048
openssl req -x509 -new -nodes -key rootCA.key -days 36500 -out rootCA.crt openssl req -x509 -new -nodes -key rootCA.key -days 36500 -out rootCA.crt
echo "============="
echo "rootCA generated at :"
pwd
echo "please trust them and add to your system keychain"
echo "============="
exit 0

View File

@ -1,11 +1,14 @@
var exec = require('child_process').exec, var exec = require('child_process').exec,
spawn = require('child_process').spawn,
path = require("path"), path = require("path"),
fs = require("fs"), fs = require("fs"),
os = require("os"), os = require("os"),
asyncTaskMgr = require("./asyncTaskMgr"); color = require('colorful'),
asyncTask = require("./asyncTaskMgr");
var certDir = path.join(getUserHome(),"/.anyproxy_certs/"), var certDir = path.join(getUserHome(),"/.anyproxy_certs/"),
asyncTaskMgr = new asyncTaskMgr(); cmdDir = "./cert/",
asyncTaskMgr = new asyncTask();
if(!fs.existsSync(certDir)){ if(!fs.existsSync(certDir)){
fs.mkdirSync(certDir); fs.mkdirSync(certDir);
@ -38,14 +41,16 @@ function getUserHome() {
} }
function createCert(hostname,callback){ function createCert(hostname,callback){
console.log("creating cert for :" + hostname); console.log(hostname);
checkRootCA();
var cmd = "./gen-cer __host __path".replace(/__host/,hostname).replace(/__path/,certDir); var cmd = "./gen-cer __host __path".replace(/__host/,hostname).replace(/__path/,certDir);
exec(cmd,{cwd:"./cert/"},function(err,stdout,stderr){ exec(cmd,{ cwd : cmdDir },function(err,stdout,stderr){
if(err){ if(err){
callback && callback(new Error("error when generating certificate"),null); callback && callback(new Error("error when generating certificate"),null);
}else{ }else{
console.log("certificate created for __HOST".replace(/__HOST/,hostname)); var tipText = "certificate created for __HOST".replace(/__HOST/,hostname);
console.log(color.yellow(color.bold("[internal https]")) + color.yellow(tipText));
callback(null); callback(null);
} }
}); });
@ -55,6 +60,34 @@ function clearCerts(cb){
exec("rm *.key *.csr *.crt",{cwd : certDir},cb); exec("rm *.key *.csr *.crt",{cwd : certDir},cb);
} }
function checkRootCA(){
var crtFile = path.join(cmdDir,"rootCA.crt"),
keyFile = path.join(cmdDir,"rootCA.key");
if(!fs.existsSync(crtFile) || !fs.existsSync(keyFile)){
console.log(color.red("can not find rootCA.crt or rootCA.key"));
process.exit(0);
}
}
function generateRootCA(){
var spawnSteam = spawn("./gen-rootCA",['.'],{cwd:cmdDir,stdio: 'inherit'});
spawnSteam.on('close', function (code) {
if(code == 0){
console.log(color.green("rootCA generated"));
console.log("now clearing temp certs...");
clearCerts(function(){
console.log(color.green("done"));
process.exit(0);
});
}else{
}
});
}
module.exports.generateRootCA = generateRootCA;
module.exports.getCertificate = getCertificate; module.exports.getCertificate = getCertificate;
module.exports.createCert = createCert; module.exports.createCert = createCert;
module.exports.clearCerts = clearCerts; module.exports.clearCerts = clearCerts;

View File

@ -6,11 +6,16 @@ var getPort = require('./getPort'),
fs = require('fs'), fs = require('fs'),
net = require('net'), net = require('net'),
url = require('url'), url = require('url'),
color = require('colorful'),
certMgr = require("./certMgr"), certMgr = require("./certMgr"),
requestHandler = require("./requestHandler"); requestHandler = require("./requestHandler"),
asyncTask = require("./asyncTaskMgr");
var DEFAULT_RELEASE_TIME = 120*1000; var DEFAULT_RELEASE_TIME = 120*1000;
var asyncTaskMgr = new asyncTask();
module.exports =function(){ module.exports =function(){
var self = this; var self = this;
self.serverList = { self.serverList = {
@ -30,40 +35,49 @@ module.exports =function(){
//server exists //server exists
if(serverInfo){ if(serverInfo){
console.log("exists :" + hostname );
serverInfo.lastestUse = new Date().getTime(); serverInfo.lastestUse = new Date().getTime();
port = serverInfo.port; port = serverInfo.port;
userCB && userCB(null,port); userCB && userCB(null,port);
//create server with corresponding CA //create server with corresponding CA
}else{ }else{
console.log("creating :" + hostname );
async.series([ asyncTaskMgr.addTask(hostname ,userCB,function(cb){
//find a clean port createServer(cb);
function(callback){
getPort(function(cleanPort){
port = cleanPort;
callback(null,port);
});
}
//create server
,function(callback){
certMgr.getCertificate(hostname,function(err,keyContent,crtContent){
var server = createHttpsServer(port,keyContent,crtContent);
self.serverList[hostname] = {
port : port,
server : server,
lastestUse : new Date().getTime()
};
console.log("https server @port __portNum for __HOST established".replace(/__portNum/,port).replace(/__HOST/,hostname));
callback && callback(null,port);
});
}
],function(err,result){
userCB && userCB(err,result[result.length - 1]);
}); });
function createServer(cb){
async.series([
//find a clean port
function(callback){
getPort(function(cleanPort){
port = cleanPort;
callback(null,port);
});
}
//create server
,function(callback){
certMgr.getCertificate(hostname,function(err,keyContent,crtContent){
var server = createHttpsServer(port,keyContent,crtContent);
self.serverList[hostname] = {
port : port,
server : server,
lastestUse : new Date().getTime()
};
var tipText = "https server @port __portNum for __HOST established".replace(/__portNum/,port).replace(/__HOST/,hostname);
console.log(color.yellow(color.bold("[internal https]")) + color.yellow(tipText));
callback && callback(null,port);
});
}
],function(err,result){
cb && cb(err,result[result.length - 1]);
});
}
} }
}; };
@ -75,7 +89,7 @@ module.exports =function(){
if( (timeNow - item.lastestUse) > DEFAULT_RELEASE_TIME){ if( (timeNow - item.lastestUse) > DEFAULT_RELEASE_TIME){
item.server.close(); item.server.close();
delete self.serverList[serverName]; delete self.serverList[serverName];
console.log("https server released : " + serverName); console.log(color.yellow(color.bold("[internal https]")) + color.yellow("https server released : " + serverName));
} }
} }
@ -86,6 +100,7 @@ function createHttpsServer(port,keyContent,crtContent){
return https.createServer({ return https.createServer({
key : keyContent, key : keyContent,
cert: crtContent cert: crtContent
},requestHandler).listen(port); },requestHandler.userRequestHandler).listen(port);
} }

View File

@ -0,0 +1,8 @@
var httpsServerMgr = require("./httpsServerMgr");
var instance = new httpsServerMgr();
instance.fetchPort("localhost",function(err,port){
console.log(port);
});

View File

@ -1,29 +1,181 @@
var http = require("http"), var http = require("http"),
https = require("https"); https = require("https"),
net = require("net"),
fs = require("fs"),
url = require("url"),
pathUtil = require("path"),
color = require("colorful"),
httpsServerMgr = require("./httpsServerMgr");
function handler(req,userRes){ var httpsServerMgrInstance = new httpsServerMgr();
console.log(req);
var ifHttps = !!req.connection.encrypted && !/http:/.test(req.url); //default rule
var handleRule = {
map :[
// {
// host :".",
// path :"/path/test",
// localFile :"",
// localDir :"~/"
// }
]
,httpsConfig:{
bypassAll : false,
interceptDomains:["^.*alibaba-inc\.com$"]
}
};
var options = { function userRequestHandler(req,userRes){
hostname : req.headers.host, var host = req.headers.host,
port : req.port || (ifHttps ? 443 : 80), path = url.parse(req.url).path,
path : req.url, ifLocalruleMatched = false;
method : req.method,
headers : req.headers
};
var proxyReq = (ifHttps ? https : http).request(options, function(res) { console.log(color.green("\nreceived request to : " + host + path));
userRes.writeHead(res.statusCode,res.headers); /*
res.pipe(userRes); req.url is wired
}); in http server : http://www.baidu.com/a/b/c
in https server : /work/alibaba
*/
proxyReq.on("error",function(e){ for(var index in handleRule.map){
console.log("err with request :" + req.url); var rule = handleRule.map[index];
userRes.end();
}); var hostTest = new RegExp(rule.host).test(host),
proxyReq.end(); 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;
if(!targetLocalfile){ //find file in dir //TODO : /a/b/c -> b/c
var basename = pathUtil.basename(path);
basename = basename.slice(0,basename.indexOf("?")); //remove chars after question mark
targetLocalfile = pathUtil.join(rule.localDir,basename);
}
console.log("==>local file: " + targetLocalfile);
if(fs.existsSync(targetLocalfile)){
try{
var fsStream = fs.createReadStream(targetLocalfile);
userRes.writeHead(200,{"cache-control":"max-age=0"}); //remove any cache related header
fsStream.pipe(userRes);
ifLocalruleMatched = true;
break;
}catch(e){
console.log(e.message);
}
}else{
console.log("file not exist : " + targetLocalfile);
}
}
}
if(ifLocalruleMatched){
return;
}else{
console.log("==>will forward to real server by proxy");
var ifHttps = !!req.connection.encrypted && !/http:/.test(req.url);
var options = {
hostname : req.headers.host,
port : req.port || (ifHttps ? 443 : 80),
path : path,
method : req.method,
headers : req.headers
};
var proxyReq = (ifHttps ? https : http).request(options, function(res) {
userRes.writeHead(res.statusCode,res.headers);
res.pipe(userRes);
});
proxyReq.on("error",function(e){
console.log("err with request :" + req.url);
userRes.end();
});
proxyReq.end();
}
} }
module.exports = handler; function connectReqHandler(req, socket, head){
var hostname = req.url.split(":")[0],
targetPort= req.url.split(":")[1],
httpsRule = handleRule.httpsConfig;
var shouldBypass = httpsRule.bypassAll;
if(!shouldBypass){ //read rules
for(var index in httpsRule.interceptDomains){
var reg = new RegExp(httpsRule.interceptDomains[index]);
if( reg.test(hostname) ){
shouldBypass = true;
break;
}
}
}
console.log(color.green("\nreceived https CONNECT request " + hostname));
if(shouldBypass){
console.log("==>meet the rules, will bypass the man-in-the-middle proxy");
try{
var conn = net.connect(targetPort, hostname, function(){
socket.write('HTTP/' + req.httpVersion + ' 200 OK\r\n\r\n', 'UTF-8', function() {
conn.pipe(socket);
socket.pipe(conn);
});
});
conn.on("error",function(e){
console.log("err when connect to __host".replace(/__host/,hostname));
});
}catch(e){
console.log("err when connect to remote https server (__hostname)".replace(/__hostname/,hostname));//TODO
}
}else{
//TODO : remote port other than 433
console.log("==>will forward to local https server");
//forward the https-request to local https server
httpsServerMgrInstance.fetchPort(hostname,function(err,port){
if(!err && port){
try{
var conn = net.connect(port, 'localhost', function(){ //TODO : localhost -> server
socket.write('HTTP/' + req.httpVersion + ' 200 OK\r\n\r\n', 'UTF-8', function() {
conn.pipe(socket);
socket.pipe(conn);
});
});
conn.on("error",function(e){
console.log("err when connect to __host".replace(/__host/,hostname));
});
}catch(e){
console.log("err when connect to local https server (__hostname)".replace(/__hostname/,hostname));//TODO
}
}else{
console.log("err fetch HTTPS server for host:" + hostname);
}
});
}
}
function setRules(newRule){
if(!newRule){
return;
}
if(!newRule.map || !newRule.httpsConfig){
throw(new Error("invalid rule schema"));
}else{
handleRule = newRule;
}
}
module.exports.userRequestHandler = userRequestHandler;
module.exports.connectReqHandler = connectReqHandler;
module.exports.setRules = setRules;

View File

@ -4,10 +4,11 @@
"description": "https proxy over http", "description": "https proxy over http",
"main": "proxy.js", "main": "proxy.js",
"bin": { "bin": {
"anyproxy":"bin.js" "anyproxy": "bin.js"
}, },
"dependencies": { "dependencies": {
"async": "~0.9.0", "async": "~0.9.0",
"colorful": "^2.1.0",
"commander": "~2.3.0" "commander": "~2.3.0"
}, },
"devDependencies": {}, "devDependencies": {},

View File

@ -5,9 +5,9 @@ var http = require('http'),
async = require("async"), async = require("async"),
url = require('url'), url = require('url'),
exec = require('child_process').exec, exec = require('child_process').exec,
httpsServerMgr = require("./lib/httpsServerMgr"),
certMgr = require("./lib/certMgr"),
program = require('commander'), program = require('commander'),
color = require('colorful'),
certMgr = require("./lib/certMgr"),
requestHandler = require("./lib/requestHandler"); requestHandler = require("./lib/requestHandler");
var T_TYPE_HTTP = 0, var T_TYPE_HTTP = 0,
@ -16,17 +16,18 @@ var T_TYPE_HTTP = 0,
DEFAULT_HOST = "localhost", DEFAULT_HOST = "localhost",
DEFAULT_TYPE = T_TYPE_HTTP; DEFAULT_TYPE = T_TYPE_HTTP;
var httpsServerMgrInstance = new httpsServerMgr(), var httpProxyServer;
httpProxyServer;
function startServer(type, port, hostname){ function startServer(type, port, hostname,rule){
var proxyType = /https/i.test(type || DEFAULT_TYPE) ? T_TYPE_HTTPS : T_TYPE_HTTP , var proxyType = /https/i.test(type || DEFAULT_TYPE) ? T_TYPE_HTTPS : T_TYPE_HTTP ,
proxyPort = port || DEFAULT_PORT, proxyPort = port || DEFAULT_PORT,
proxyHost = hostname || DEFAULT_HOST; proxyHost = hostname || DEFAULT_HOST;
requestHandler.setRules(rule);
async.series([ async.series([
//creat server //creat proxy server
function(callback){ function(callback){
if(proxyType == T_TYPE_HTTPS){ if(proxyType == T_TYPE_HTTPS){
certMgr.getCertificate(proxyHost,function(err,keyContent,crtContent){ certMgr.getCertificate(proxyHost,function(err,keyContent,crtContent){
@ -36,21 +37,20 @@ function startServer(type, port, hostname){
httpProxyServer = https.createServer({ httpProxyServer = https.createServer({
key : keyContent, key : keyContent,
cert: crtContent cert: crtContent
},requestHandler); },requestHandler.userRequestHandler);
callback(null); callback(null);
} }
}); });
}else{ }else{
httpProxyServer = http.createServer(requestHandler); httpProxyServer = http.createServer(requestHandler.userRequestHandler);
callback(null); callback(null);
} }
}, },
//listen CONNECT method for https over http //listen CONNECT method for https over http
function(callback){ function(callback){
httpProxyServer.on('connect',dealProxyConnectReq); httpProxyServer.on('connect',requestHandler.connectReqHandler);
httpProxyServer.listen(proxyPort); httpProxyServer.listen(proxyPort);
callback(null); callback(null);
} }
@ -60,36 +60,15 @@ function startServer(type, port, hostname){
//final callback //final callback
function(err,result){ function(err,result){
if(!err){ if(!err){
console.log( (proxyType == T_TYPE_HTTP ? "Http" : "Https") + " proxy started at port " + proxyPort); var tipText = (proxyType == T_TYPE_HTTP ? "Http" : "Https") + " proxy started at port " + proxyPort;
console.log(color.green(tipText));
}else{ }else{
console.log("err when start proxy server :("); var tipText = "err when start proxy server :(";
console.log(color.red(tipText));
console.log(err); console.log(err);
} }
} }
); );
} }
function dealProxyConnectReq(req, socket, head){ module.exports.startServer = startServer;
var hostname = req.url.split(":")[0];
//forward the https-request to local https server
httpsServerMgrInstance.fetchPort(hostname,function(err,port){
if(!err && port){
try{
var conn = net.connect(port, 'localhost', function(){
socket.write('HTTP/' + req.httpVersion + ' 200 OK\r\n\r\n', 'UTF-8', function() {
conn.pipe(socket);
socket.pipe(conn);
});
});
}catch(e){
console.log("err when connect to local https server (__hostname)".replace(/__hostname/,hostname));//TODO
}
}else{
console.log("err fetch HTTPS server for host:" + hostname);
}
});
}
module.exports.startServer = startServer;

36
rule_sample.js Normal file
View File

@ -0,0 +1,36 @@
var rules = {
"map" :[
{
"host" :/./,
"path" :/\/path\/test/,
"localFile" :"",
"localDir" :"~/"
}
,{
"host" :/./,
"path" :/png/,
"localFile" :"/Users/Stella/tmp/test.png",
"localDir" :"~/"
}
,{
"host" :/./,
"path" :/jpg/,
"localFile" :"/Users/Stella/tmp/test.png",
"localDir" :"~/"
}
,{
"host" :/./,
"path" :/gif/,
"localFile" :"/Users/Stella/tmp/test.png",
"localDir" :"~/"
}
]
,"httpsConfig":{
// "bypassAll" : true,
// "interceptDomains":[/^.*alibaba-inc\.com$/]
"bypassAll" : false,
"interceptDomains":[]
}
}
module.exports = rules;

16
test.js Normal file
View File

@ -0,0 +1,16 @@
var https = require("https");
process.env['NODE_TLS_REJECT_UNAUTHORIZED'] = '0';
var options = {
host: "localhost",
port: 8001,
path: "/",
headers: {
Host: "www.alipay.com"
}
};
https.get(options, function(res) {
console.log(res);
res.pipe(process.stdout);
});