This commit is contained in:
加里 2014-08-11 16:43:14 +08:00
parent 5e269eac91
commit 7eea58952b
14 changed files with 367 additions and 254 deletions

View File

@ -1,4 +1,4 @@
http-proxy anyproxy
========== ==========
## Intro ## Intro

7
bin.js
View File

@ -1,5 +1,5 @@
var program = require('commander'), var program = require('commander'),
mainProxy = require("./index.js"); mainProxy = require("./proxy.js");
program program
.option('-u, --host [value]', 'hostname for https proxy, localhost for default') .option('-u, --host [value]', 'hostname for https proxy, localhost for default')
@ -9,12 +9,11 @@ program
.parse(process.argv); .parse(process.argv);
if(program.clear){ if(program.clear){
exec("rm -rf ./cert/tmpCert",function(){ require("./lib/certMgr").clearCerts(function(){
console.log("certificates cleared"); console.log("all certs cleared");
process.exit(0); process.exit(0);
}); });
}else{ }else{
mainProxy.startServer(program.type,program.port, program.host); mainProxy.startServer(program.type,program.port, program.host);
} }

View File

@ -2,6 +2,7 @@
#Required #Required
domain=$1 domain=$1
outputPath=$2
commonname=$domain commonname=$domain
#Change to your company details #Change to your company details
@ -15,7 +16,7 @@ email=a@b.com
#Optional #Optional
password=a password=a
if [ -z "$domain" ] if [ -z "$outputPath$domain" ]
then then
echo "Argument not present." echo "Argument not present."
echo "Useage $0 [common name]" echo "Useage $0 [common name]"
@ -23,25 +24,25 @@ then
exit 99 exit 99
fi fi
echo "Generating key request for $domain" echo "Generating key request for $outputPath$domain"
#Generate a key #Generate a key
# openssl genrsa -out host.key 2048 # openssl genrsa -out host.key 2048
# openssl genrsa -des3 -out $domain.key 2048 -noout # openssl genrsa -des3 -out $outputPath$domain.key 2048 -noout
openssl genrsa -passout pass:$password -out ./tmpCert/$domain.key 2048 openssl genrsa -passout pass:$password -out $outputPath$domain.key 2048
#Remove passphrase from the key. Comment the line out to keep the passphrase #Remove passphrase from the key. Comment the line out to keep the passphrase
echo "Removing passphrase from key" 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 #Create the request
echo "Creating CSR" 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" -subj "/C=$country/ST=$state/L=$locality/O=$organization/OU=$organizationalunit/CN=$commonname/emailAddress=$email"
#Generating a Self-Signed Certificate #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 openssl x509 -req -days 365 -in $outputPath$domain.csr -CA rootCA.crt -CAkey rootCA.key -CAcreateserial -out $outputPath$domain.crt
#-signkey ./tmpCert/$domain.key #-signkey $outputPath$domain.key
#openssl x509 -req -in host.csr -CA rootCA.crt -CAkey rootCA.key -CAcreateserial -out host.crt -days 365 #openssl x509 -req -in host.csr -CA rootCA.crt -CAkey rootCA.key -CAcreateserial -out host.crt -days 365
echo "Finished" echo "Finished"

View File

@ -1,4 +1,6 @@
#!/bin/bash #!/bin/bash
outputPath=$1
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

123
index.js
View File

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

51
lib/asyncTaskMgr.js Normal file
View File

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

60
lib/certMgr.js Normal file
View File

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

View File

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

95
lib/httpsServerMgr.js Normal file
View File

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

28
lib/requestHandler.js Normal file
View File

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

View File

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

View File

@ -1,8 +1,9 @@
{ {
"name": "http-proxy", "name": "anyproxy",
"version": "0.1.0", "version": "0.1.0",
"description": "https proxy over http", "description": "https proxy over http",
"main": "index.js", "main": "proxy.js",
"bin": "bin.js",
"dependencies": { "dependencies": {
"async": "~0.9.0", "async": "~0.9.0",
"commander": "~2.3.0" "commander": "~2.3.0"

94
proxy.js Normal file
View File

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

21
testCert.js Normal file
View File

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