From 7eea58952b56c7c73b412b9503e931d9c17dff69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8A=A0=E9=87=8C?= Date: Mon, 11 Aug 2014 16:43:14 +0800 Subject: [PATCH] update --- README.md | 2 +- bin.js | 7 ++- cert/gen-cer | 17 +++--- cert/gen-rootCA | 2 + index.js | 123 ------------------------------------------ lib/asyncTaskMgr.js | 51 ++++++++++++++++++ lib/certMgr.js | 60 +++++++++++++++++++++ lib/createCert.js | 16 ------ lib/httpsServerMgr.js | 95 ++++++++++++++++++++++++++++++++ lib/requestHandler.js | 28 ++++++++++ lib/serverMgr.js | 100 ---------------------------------- package.json | 5 +- proxy.js | 94 ++++++++++++++++++++++++++++++++ testCert.js | 21 ++++++++ 14 files changed, 367 insertions(+), 254 deletions(-) delete mode 100644 index.js create mode 100644 lib/asyncTaskMgr.js create mode 100644 lib/certMgr.js delete mode 100644 lib/createCert.js create mode 100644 lib/httpsServerMgr.js create mode 100644 lib/requestHandler.js delete mode 100644 lib/serverMgr.js create mode 100644 proxy.js create mode 100644 testCert.js diff --git a/README.md b/README.md index a5e09b9..d14fa17 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -http-proxy +anyproxy ========== ## Intro diff --git a/bin.js b/bin.js index d79c8ac..a334744 100644 --- a/bin.js +++ b/bin.js @@ -1,5 +1,5 @@ var program = require('commander'), - mainProxy = require("./index.js"); + mainProxy = require("./proxy.js"); program .option('-u, --host [value]', 'hostname for https proxy, localhost for default') @@ -9,12 +9,11 @@ program .parse(process.argv); if(program.clear){ - exec("rm -rf ./cert/tmpCert",function(){ - console.log("certificates cleared"); + require("./lib/certMgr").clearCerts(function(){ + console.log("all certs cleared"); process.exit(0); }); }else{ mainProxy.startServer(program.type,program.port, program.host); - } \ No newline at end of file diff --git a/cert/gen-cer b/cert/gen-cer index 3bd7fe6..141e699 100755 --- a/cert/gen-cer +++ b/cert/gen-cer @@ -2,6 +2,7 @@ #Required domain=$1 +outputPath=$2 commonname=$domain #Change to your company details @@ -15,7 +16,7 @@ email=a@b.com #Optional password=a -if [ -z "$domain" ] +if [ -z "$outputPath$domain" ] then echo "Argument not present." echo "Useage $0 [common name]" @@ -23,25 +24,25 @@ then exit 99 fi -echo "Generating key request for $domain" +echo "Generating key request for $outputPath$domain" #Generate a key # openssl genrsa -out host.key 2048 -# openssl genrsa -des3 -out $domain.key 2048 -noout -openssl genrsa -passout pass:$password -out ./tmpCert/$domain.key 2048 +# openssl genrsa -des3 -out $outputPath$domain.key 2048 -noout +openssl genrsa -passout pass:$password -out $outputPath$domain.key 2048 #Remove passphrase from the key. Comment the line out to keep the passphrase echo "Removing passphrase from key" -openssl rsa -in ./tmpCert/$domain.key -passin pass:$password -out ./tmpCert/$domain.key +openssl rsa -in $outputPath$domain.key -passin pass:$password -out $outputPath$domain.key #Create the request echo "Creating CSR" -openssl req -new -key ./tmpCert/$domain.key -out ./tmpCert/$domain.csr -passin pass:$password \ +openssl req -new -key $outputPath$domain.key -out $outputPath$domain.csr -passin pass:$password \ -subj "/C=$country/ST=$state/L=$locality/O=$organization/OU=$organizationalunit/CN=$commonname/emailAddress=$email" #Generating a Self-Signed Certificate -openssl x509 -req -days 365 -in ./tmpCert/$domain.csr -CA rootCA.crt -CAkey rootCA.key -CAcreateserial -out ./tmpCert/$domain.crt -#-signkey ./tmpCert/$domain.key +openssl x509 -req -days 365 -in $outputPath$domain.csr -CA rootCA.crt -CAkey rootCA.key -CAcreateserial -out $outputPath$domain.crt +#-signkey $outputPath$domain.key #openssl x509 -req -in host.csr -CA rootCA.crt -CAkey rootCA.key -CAcreateserial -out host.crt -days 365 echo "Finished" diff --git a/cert/gen-rootCA b/cert/gen-rootCA index f784592..5e89008 100755 --- a/cert/gen-rootCA +++ b/cert/gen-rootCA @@ -1,4 +1,6 @@ #!/bin/bash +outputPath=$1 +cd $outputPath openssl genrsa -out rootCA.key 2048 openssl req -x509 -new -nodes -key rootCA.key -days 36500 -out rootCA.crt \ No newline at end of file diff --git a/index.js b/index.js deleted file mode 100644 index 9002aac..0000000 --- a/index.js +++ /dev/null @@ -1,123 +0,0 @@ -var http = require('http'), - https = require('https'), - fs = require('fs'), - net = require('net'), - async = require("async"), - url = require('url'), - exec = require('child_process').exec, - serverMgr = require("./lib/serverMgr"), - createCert= require("./lib/createCert"), - program = require('commander'); - -var T_TYPE_HTTP = 0, - T_TYPE_HTTPS = 1, - DEFAULT_PORT = 8001, - DEFAULT_HOST = "localhost", - DEFAULT_TYPE = T_TYPE_HTTP; - -var serverMgrInstance = new serverMgr(), - httpProxyServer; - -function startServer(type, port, hostname){ - var proxyType = /https/i.test(type || DEFAULT_TYPE) ? T_TYPE_HTTPS : T_TYPE_HTTP , - proxyPort = port || DEFAULT_PORT, - proxyHost = hostname || DEFAULT_HOST; - - async.series([ - //creat server - function(callback){ - if(proxyType == T_TYPE_HTTPS){ - var keyFile = "./cert/tmpCert/__hostname.key".replace(/__hostname/,proxyHost), - crtFile = "./cert/tmpCert/__hostname.crt".replace(/__hostname/,proxyHost); - - if(!fs.existsSync(keyFile) || !fs.existsSync(crtFile)){ - createCert(proxyHost,function(){ - httpProxyServer = https.createServer({ - key : fs.readFileSync(keyFile), - cert: fs.readFileSync(crtFile) - },dealProxyUserHttpReq); - callback(null); - }); - }else{ - httpProxyServer = https.createServer({ - key : fs.readFileSync(keyFile), - cert: fs.readFileSync(crtFile) - },dealProxyUserHttpReq); - callback(null); - } - - }else{ - httpProxyServer = http.createServer(dealProxyUserHttpReq); - callback(null); - - } - }, - - function(callback){ - //listen CONNECT method for https over http - httpProxyServer.on('connect',dealProxyConnectReq); - httpProxyServer.listen(proxyPort); - callback(null); - - }], - - //final callback - function(err,result){ - if(!err){ - console.log( (proxyType == T_TYPE_HTTP ? "Http" : "Https") + " proxy started at port " + proxyPort); - }else{ - console.log("err when start proxy server :("); - console.log(err); - } - } - ); -} - -function dealProxyUserHttpReq(req,res){ - var urlPattern = url.parse(req.url); - var options = { - hostname : urlPattern.host, - port : urlPattern.port || 80, - path : urlPattern.path, - method : req.method, - headers : req.headers - }; - - //forward to real server - var directReq = http.request(options,function(directRes){ - res.writeHead(directRes.statusCode , directRes.headers); - directRes.pipe(res); - }); - - directReq.on("error",function(e){ - console.log("err with request :" + req.url); - res.end(); - }); - - directReq.end(); -} - -function dealProxyConnectReq(req, socket, head){ - var hostname = req.url.split(":")[0]; - - //forward the https-request to local https server - serverMgrInstance.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; diff --git a/lib/asyncTaskMgr.js b/lib/asyncTaskMgr.js new file mode 100644 index 0000000..470ef68 --- /dev/null +++ b/lib/asyncTaskMgr.js @@ -0,0 +1,51 @@ +function asyncTaskMgr(){ + var self = this; + + self.callbackList = { + sampleName:{ + status:0, /* 0,stopped,will not callback / 1,loading / 2,loaded */ + result:null, + callbackList:[] + } + } + + self.addTask = function(name,cb,action){ + if(self.callbackList[name]){ + var task = self.callbackList[name]; + + if(task.status == 2){ //done + cb && cb.apply(null,task.result); + + }else if(task.status == 1){ //pending + task.callbackList.push(cb); + + }else if(task.status == 0){ //stopped + return; //do nothing + } + }else{ + var task; + task = self.callbackList[name] = { + status : 1, + result : null, + callbackList : [cb] + }; + + action && action.call(null,function(){ //action应该带一个回调 + if(arguments && arguments[0] === -1){ //返回第一个参数为-1,为停止任务 + task.status = 0; + task.callbackList = []; + }else{ + task.result = arguments; + task.status = 2; + var tmpCb; + while(tmpCb = task.callbackList.shift()){ + tmpCb && tmpCb.apply(null,task.result); + } + + } + }); + } + } +}; + +module.exports = asyncTaskMgr; diff --git a/lib/certMgr.js b/lib/certMgr.js new file mode 100644 index 0000000..262df15 --- /dev/null +++ b/lib/certMgr.js @@ -0,0 +1,60 @@ +var exec = require('child_process').exec, + path = require("path"), + fs = require("fs"), + os = require("os"), + asyncTaskMgr = require("./asyncTaskMgr"); + +var certDir = path.join(getUserHome(),"/.anyproxy_certs/"), + asyncTaskMgr = new asyncTaskMgr(); + +if(!fs.existsSync(certDir)){ + fs.mkdirSync(certDir); +} + +function getCertificate(hostname,cb){ + var keyFile = path.join(certDir , "__hostname.key".replace(/__hostname/,hostname) ), + crtFile = path.join(certDir , "__hostname.crt".replace(/__hostname/,hostname) ); + + if(!fs.existsSync(keyFile) || !fs.existsSync(crtFile)){ + asyncTaskMgr.addTask(hostname,function(err){ + if(!err){ + cb(null , fs.readFileSync(keyFile) , fs.readFileSync(crtFile) ); + }else{ + cb(err); + } + },function(cb){ + createCert(hostname,function(err){ + cb(err ? -1 : null); + }); + }); + + }else{ + cb(null , fs.readFileSync(keyFile) , fs.readFileSync(crtFile) ); + } +} + +function getUserHome() { + return process.env.HOME || process.env.HOMEPATH || process.env.USERPROFILE; +} + +function createCert(hostname,callback){ + console.log("creating cert for :" + hostname); + + var cmd = "./gen-cer __host __path".replace(/__host/,hostname).replace(/__path/,certDir); + exec(cmd,{cwd:"./cert/"},function(err,stdout,stderr){ + if(err){ + callback && callback(new Error("error when generating certificate"),null); + }else{ + console.log("certificate created for __HOST".replace(/__HOST/,hostname)); + callback(null); + } + }); +} + +function clearCerts(cb){ + exec("rm *.key *.csr *.crt",{cwd : certDir},cb); +} + +module.exports.getCertificate = getCertificate; +module.exports.createCert = createCert; +module.exports.clearCerts = clearCerts; \ No newline at end of file diff --git a/lib/createCert.js b/lib/createCert.js deleted file mode 100644 index 5b7fa66..0000000 --- a/lib/createCert.js +++ /dev/null @@ -1,16 +0,0 @@ -//TODO : move to the tmp/ dir -var exec = require('child_process').exec; - -module.exports = function(hostname,callback){ - console.log("creating cert for :" + hostname); - - var cmd = "./gen-cer "+hostname; - exec(cmd,{cwd:"./cert/"},function(err,stdout,stderr){ - if(err){ - callback && callback(new Error("error when generating certificate"),null); - }else{ - console.log("certificate created for __HOST".replace(/__HOST/,hostname)); - callback(null); - } - }); -} \ No newline at end of file diff --git a/lib/httpsServerMgr.js b/lib/httpsServerMgr.js new file mode 100644 index 0000000..18892b6 --- /dev/null +++ b/lib/httpsServerMgr.js @@ -0,0 +1,95 @@ +//manage https servers +var getPort = require('./getPort'), + async = require("async"), + http = require('http'), + https = require('https'), + fs = require('fs'), + net = require('net'), + url = require('url'), + certMgr = require("./certMgr"), + requestHandler = require("./requestHandler"); + +if(!fs.existsSync("cert/tmpCert")){ + fs.mkdirSync("cert/tmpCert"); +} + +var DEFAULT_RELEASE_TIME = 120*1000; + +module.exports =function(){ + var self = this; + self.serverList = { + /* schema sample + "www.alipay.com":{ + port:123, + server : serverInstance, + lastestUse: 99999 //unix time stamp + } + */ + }; + + //fetch a port for https server with hostname + this.fetchPort = function(hostname,userCB){ + var serverInfo = self.serverList[hostname], + port; + + //server exists + if(serverInfo){ + console.log("exists :" + hostname ); + serverInfo.lastestUse = new Date().getTime(); + port = serverInfo.port; + userCB && userCB(null,port); + + //create server with corresponding CA + }else{ + console.log("creating :" + hostname ); + 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() + }; + 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]); + }); + } + }; + + //clear servers which have been idle for some time + setInterval(function(){ + var timeNow = new Date().getTime(); + for(var serverName in self.serverList){ + var item = self.serverList[serverName]; + if( (timeNow - item.lastestUse) > DEFAULT_RELEASE_TIME){ + item.server.close(); + delete self.serverList[serverName]; + console.log("https server released : " + serverName); + } + } + + },DEFAULT_RELEASE_TIME); +} + +function createHttpsServer(port,keyContent,crtContent){ + return https.createServer({ + key : keyContent, + cert: crtContent + },requestHandler).listen(port); +} + diff --git a/lib/requestHandler.js b/lib/requestHandler.js new file mode 100644 index 0000000..2926980 --- /dev/null +++ b/lib/requestHandler.js @@ -0,0 +1,28 @@ +var http = require("http"), + https = require("https"); + +function handler(req,userRes){ + + var ifHttps = !!req.connection.encrypted; + + var options = { + hostname : req.headers.host, + port : req.port || (ifHttps ? 443 : 80), + path : req.url, + 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; \ No newline at end of file diff --git a/lib/serverMgr.js b/lib/serverMgr.js deleted file mode 100644 index b4f8c09..0000000 --- a/lib/serverMgr.js +++ /dev/null @@ -1,100 +0,0 @@ -//manage https servers -var getPort = require('./getPort'), - async = require("async"), - http = require('http'), - https = require('https'), - fs = require('fs'), - net = require('net'), - url = require('url'), - createCert= require("./createCert"); - -if(!fs.existsSync("cert/tmpCert")){ - fs.mkdirSync("cert/tmpCert"); -} - -module.exports =function(){ - var self = this; - self.serverList = { - /* schema sample - "www.alipay.com":{ - port:123, - server : serverInstance - } - */ - }; - - //fetch a port for https server with hostname - this.fetchPort = function(hostname,userCB){ - var serverInfo = self.serverList[hostname], - port; - - //server exists - if(serverInfo){ - port = serverInfo.port; - userCB && userCB(null,port); - - //create server with corresponding CA - }else{ - var keyFile = "./cert/tmpCert/__hostname.key".replace(/__hostname/,hostname), - crtFile = "./cert/tmpCert/__hostname.crt".replace(/__hostname/,hostname); - - async.series([ - //find a clean port - function(callback){ - getPort(function(cleanPort){ - port = cleanPort; - callback(null,port); - }); - }, - - //create a cert for this hostname if not exists - function(callback){ - if(!fs.existsSync(keyFile) || !fs.existsSync(crtFile)){ - createCert(hostname,callback) - }else{ - callback(null); - } - - //create server - },function(callback){ - var server = createHttpsServer(port,keyFile,crtFile); - self.serverList[hostname] = { - port : port, - server : server - }; - 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 createHttpsServer(port,keyFile,crtFile){ - return https.createServer({ - key : fs.readFileSync(keyFile), - cert: fs.readFileSync(crtFile) - },function(req,userRes){ - var options = { - hostname: req.headers.host, - port: req.port || 443, - path: req.url, - method: req.method, - headers:req.headers - }; - - var proxyReq = https.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(); - - }).listen(port); -} \ No newline at end of file diff --git a/package.json b/package.json index 011611c..fd29ecf 100644 --- a/package.json +++ b/package.json @@ -1,8 +1,9 @@ { - "name": "http-proxy", + "name": "anyproxy", "version": "0.1.0", "description": "https proxy over http", - "main": "index.js", + "main": "proxy.js", + "bin": "bin.js", "dependencies": { "async": "~0.9.0", "commander": "~2.3.0" diff --git a/proxy.js b/proxy.js new file mode 100644 index 0000000..e5c550b --- /dev/null +++ b/proxy.js @@ -0,0 +1,94 @@ +var http = require('http'), + https = require('https'), + fs = require('fs'), + net = require('net'), + async = require("async"), + url = require('url'), + exec = require('child_process').exec, + httpsServerMgr = require("./lib/httpsServerMgr"), + certMgr = require("./lib/certMgr"), + program = require('commander'), + requestHandler = require("./lib/requestHandler"); + +var T_TYPE_HTTP = 0, + T_TYPE_HTTPS = 1, + DEFAULT_PORT = 8001, + DEFAULT_HOST = "localhost", + DEFAULT_TYPE = T_TYPE_HTTP; + +var httpsServerMgrInstance = new httpsServerMgr(), + httpProxyServer; + +function startServer(type, port, hostname){ + var proxyType = /https/i.test(type || DEFAULT_TYPE) ? T_TYPE_HTTPS : T_TYPE_HTTP , + proxyPort = port || DEFAULT_PORT, + proxyHost = hostname || DEFAULT_HOST; + + async.series([ + + //creat server + function(callback){ + if(proxyType == T_TYPE_HTTPS){ + httpsServerMgr.getCertificate(proxyHost,function(err,keyContent,crtContent){ + if(err){ + callback(err); + }else{ + httpProxyServer = https.createServer({ + key : keyContent, + cert: crtContent + },requestHandler); + callback(null); + } + }); + + }else{ + httpProxyServer = http.createServer(requestHandler); + callback(null); + } + }, + + //listen CONNECT method for https over http + function(callback){ + httpProxyServer.on('connect',dealProxyConnectReq); + httpProxyServer.listen(proxyPort); + callback(null); + } + + ], + + //final callback + function(err,result){ + if(!err){ + console.log( (proxyType == T_TYPE_HTTP ? "Http" : "Https") + " proxy started at port " + proxyPort); + }else{ + console.log("err when start proxy server :("); + console.log(err); + } + } + ); +} + +function dealProxyConnectReq(req, socket, head){ + 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; diff --git a/testCert.js b/testCert.js new file mode 100644 index 0000000..683ac33 --- /dev/null +++ b/testCert.js @@ -0,0 +1,21 @@ +var certMgr = require("./lib/certMgr"); + +certMgr.clearCerts(function(){ + console.log(arguments); +}); + +certMgr.getCertificate("www.test3.com",function(){ + console.log(arguments); +}); + +certMgr.getCertificate("www.test2.com",function(){ + console.log(arguments); +}); + +certMgr.getCertificate("www.test3.com",function(){ + console.log(arguments); +}); + +certMgr.getCertificate("www.test3.com",function(){ + console.log(arguments); +}); \ No newline at end of file