mirror of
https://github.com/alibaba/anyproxy.git
synced 2025-07-29 00:59:10 +00:00
Add test cases for the proxy, and do some tiny fixes.
the fixes are: 1. add "content-type" in headers for when dealing with localresponse 2. make a more accurate tip for throttle rate when lower than 1 3. make the crtMgr funcionality a more independent one 4. uppercase the request header before sending it out update the tip
This commit is contained in:
217
lib/certMgr.js
217
lib/certMgr.js
@@ -1,174 +1,81 @@
|
||||
var exec = require('child_process').exec,
|
||||
spawn = require('child_process').spawn,
|
||||
path = require("path"),
|
||||
fs = require("fs"),
|
||||
os = require("os"),
|
||||
color = require('colorful'),
|
||||
readline = require('readline'),
|
||||
util = require('./util'),
|
||||
logUtil = require("./log"),
|
||||
certGenerator = require("./certGenerator"),
|
||||
asyncTask = require("async-task-mgr");
|
||||
var logUtil = require('./log');
|
||||
var util = require('./util');
|
||||
var color = require('colorful');
|
||||
var EasyCert = require('node-easy-cert');
|
||||
var exec = require('child_process').exec;
|
||||
var path = require('path');
|
||||
var readline = require('readline');
|
||||
|
||||
var isWin = /^win/.test(process.platform),
|
||||
certDir = path.join(util.getUserHome(),"/.anyproxy_certs/"),
|
||||
rootCAcrtFilePath = path.join(certDir,"rootCA.crt"),
|
||||
rootCAkeyFilePath = path.join(certDir,"rootCA.key"),
|
||||
createCertTaskMgr = new asyncTask();
|
||||
var isWin = /^win/.test(process.platform);
|
||||
var options = {
|
||||
rootDirPath: util.getUserHome() + '/.anyproxy_certs',
|
||||
defaultCertAttrs: [
|
||||
{ name: 'countryName', value: 'CN' },
|
||||
{ name: 'organizationName', value: 'AnyProxy' },
|
||||
{ shortName: 'ST', value: 'SH' },
|
||||
{ shortName: 'OU', value: 'AnyProxy SSL Proxy' }
|
||||
]
|
||||
};
|
||||
|
||||
if(!fs.existsSync(certDir)){
|
||||
try{
|
||||
fs.mkdirSync(certDir,0777);
|
||||
}catch(e){
|
||||
logUtil.printLog("===========", logUtil.T_ERR);
|
||||
logUtil.printLog("failed to create cert dir ,please create one by yourself - " + certDir, logUtil.T_ERR);
|
||||
logUtil.printLog("this error will not block main thread unless you use https-related features in anyproxy", logUtil.T_ERR);
|
||||
logUtil.printLog("===========", logUtil.T_ERR);
|
||||
}
|
||||
}
|
||||
var easyCert = new EasyCert(options);
|
||||
var crtMgr = util.merge({}, easyCert);
|
||||
|
||||
var cache_rootCACrtFileContent, cache_rootCAKeyFileContent;
|
||||
function getCertificate(hostname,certCallback){
|
||||
checkRootCA();
|
||||
var keyFile = path.join(certDir , "__hostname.key".replace(/__hostname/,hostname) ),
|
||||
crtFile = path.join(certDir , "__hostname.crt".replace(/__hostname/,hostname) );
|
||||
|
||||
if(!cache_rootCACrtFileContent || !cache_rootCAKeyFileContent){
|
||||
cache_rootCACrtFileContent = fs.readFileSync(rootCAcrtFilePath, {encoding: 'utf8'});
|
||||
cache_rootCAKeyFileContent = fs.readFileSync(rootCAkeyFilePath, {encoding: 'utf8'});
|
||||
}
|
||||
|
||||
createCertTaskMgr.addTask(hostname,function(callback){
|
||||
if(!fs.existsSync(keyFile) || !fs.existsSync(crtFile)){
|
||||
try{
|
||||
var result = certGenerator.generateCertsForHostname(hostname, {
|
||||
cert: cache_rootCACrtFileContent,
|
||||
key: cache_rootCAKeyFileContent
|
||||
});
|
||||
fs.writeFileSync(keyFile, result.privateKey);
|
||||
fs.writeFileSync(crtFile, result.certificate);
|
||||
callback(null, result.privateKey, result.certificate);
|
||||
|
||||
}catch(e){
|
||||
callback(e);
|
||||
}
|
||||
}else{
|
||||
callback(null , fs.readFileSync(keyFile) , fs.readFileSync(crtFile));
|
||||
// catch specified error, such as ROOT_CA_NOT_EXISTS
|
||||
crtMgr.getCertificate = function (host, cb) {
|
||||
easyCert.getCertificate(host, (error, keyContent, crtContent) => {
|
||||
if (error === 'ROOT_CA_NOT_EXISTS') {
|
||||
util.showRootInstallTip();
|
||||
process.exit(0);
|
||||
return;
|
||||
}
|
||||
|
||||
},function(err,keyContent,crtContent){
|
||||
if(!err){
|
||||
certCallback(null ,keyContent,crtContent);
|
||||
}else{
|
||||
certCallback(err);
|
||||
}
|
||||
cb(error, keyContent, crtContent);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
function createCert(hostname,callback){
|
||||
checkRootCA();
|
||||
// set default common name of the cert
|
||||
crtMgr.generateRootCA = function (cb) {
|
||||
doGenerate(false);
|
||||
|
||||
var cmd = cmd_genCert + " __host __path".replace(/__host/,hostname).replace(/__path/,certDir);
|
||||
exec(cmd,{ cwd : certDir },function(err,stdout,stderr){
|
||||
if(err){
|
||||
callback && callback(new Error("error when generating certificate"),null);
|
||||
}else{
|
||||
var tipText = "certificate created for __HOST".replace(/__HOST/,hostname);
|
||||
logUtil.printLog(color.yellow(color.bold("[internal https]")) + color.yellow(tipText)) ;
|
||||
callback(null);
|
||||
}
|
||||
});
|
||||
}
|
||||
function doGenerate(overwrite) {
|
||||
const rootOptions = {
|
||||
commonName: 'AnyProxy',
|
||||
overwrite: !!overwrite
|
||||
};
|
||||
|
||||
function clearCerts(cb){
|
||||
if(isWin){
|
||||
exec("del * /q",{cwd : certDir},cb);
|
||||
}else{
|
||||
exec("rm *.key *.csr *.crt *.srl",{cwd : certDir},cb);
|
||||
}
|
||||
}
|
||||
|
||||
function isRootCAFileExists(){
|
||||
return (fs.existsSync(rootCAcrtFilePath) && fs.existsSync(rootCAkeyFilePath));
|
||||
}
|
||||
|
||||
var rootCAExists = false;
|
||||
function checkRootCA(){
|
||||
if(rootCAExists) return;
|
||||
if(!isRootCAFileExists()){
|
||||
logUtil.printLog(color.red("can not find rootCA.crt or rootCA.key"), logUtil.T_ERR);
|
||||
logUtil.printLog(color.red("you may generate one by the following methods"), logUtil.T_ERR);
|
||||
logUtil.printLog(color.red("\twhen using globally : anyproxy --root"), logUtil.T_ERR);
|
||||
logUtil.printLog(color.red("\twhen using as a module : require(\"anyproxy\").generateRootCA();"), logUtil.T_ERR);
|
||||
logUtil.printLog(color.red("\tmore info : https://github.com/alibaba/anyproxy/wiki/How-to-config-https-proxy"), logUtil.T_ERR);
|
||||
process.exit(0);
|
||||
} else{
|
||||
rootCAExists = true;
|
||||
}
|
||||
}
|
||||
|
||||
function generateRootCA(){
|
||||
|
||||
if(isRootCAFileExists()){
|
||||
logUtil.printLog(color.yellow("rootCA exists at " + certDir));
|
||||
var rl = readline.createInterface({
|
||||
input : process.stdin,
|
||||
output: process.stdout
|
||||
});
|
||||
|
||||
rl.question("do you really want to generate a new one ?)(yes/NO)", function(answer) {
|
||||
if(/yes/i.test(answer)){
|
||||
startGenerating();
|
||||
}else{
|
||||
logUtil.printLog("will not generate a new one");
|
||||
process.exit(0);
|
||||
easyCert.generateRootCA(rootOptions, (error, keyPath, crtPath) => {
|
||||
if (!error) {
|
||||
const certDir = path.dirname(keyPath);
|
||||
logUtil.printLog(color.cyan('The cert is generated at "' + certDir + '"'));
|
||||
if(isWin){
|
||||
exec("start .",{ cwd : certDir });
|
||||
}else{
|
||||
exec("open .",{ cwd : certDir });
|
||||
}
|
||||
}
|
||||
|
||||
rl.close();
|
||||
});
|
||||
}else{
|
||||
startGenerating();
|
||||
}
|
||||
if (error === 'ROOT_CA_EXISTED') {
|
||||
var rl = readline.createInterface({
|
||||
input : process.stdin,
|
||||
output: process.stdout
|
||||
});
|
||||
|
||||
function startGenerating(){
|
||||
//clear old certs
|
||||
clearCerts(function(){
|
||||
logUtil.printLog(color.green("temp certs cleared"));
|
||||
try{
|
||||
var result = certGenerator.generateRootCA();
|
||||
fs.writeFileSync(rootCAkeyFilePath, result.privateKey);
|
||||
fs.writeFileSync(rootCAcrtFilePath, result.certificate);
|
||||
rl.question("do you really want to generate a new one ?)(yes/NO)", function(answer) {
|
||||
if(/yes/i.test(answer)){
|
||||
doGenerate(true);
|
||||
}else{
|
||||
console.log("will not generate a new one");
|
||||
|
||||
logUtil.printLog(color.green("rootCA generated"));
|
||||
logUtil.printLog(color.green(color.bold("please trust the rootCA.crt in " + certDir)));
|
||||
logUtil.printLog(color.green(color.bold("or you may get it via anyproxy webinterface")));
|
||||
}
|
||||
rl.close();
|
||||
});
|
||||
|
||||
if(isWin){
|
||||
exec("start .",{cwd : certDir});
|
||||
}else{
|
||||
exec("open .",{cwd : certDir});
|
||||
}
|
||||
|
||||
}catch(e){
|
||||
logUtil.printLog(color.red(e));
|
||||
logUtil.printLog(color.red(e.stack));
|
||||
logUtil.printLog(color.red("fail to generate root CA"),logUtil.T_ERR);
|
||||
return;
|
||||
}
|
||||
cb(error, keyPath, crtPath);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function getRootCAFilePath(){
|
||||
if(isRootCAFileExists()){
|
||||
return rootCAcrtFilePath;
|
||||
}else{
|
||||
return "";
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
module.exports.getRootCAFilePath = getRootCAFilePath;
|
||||
module.exports.generateRootCA = generateRootCA;
|
||||
module.exports.getCertificate = getCertificate;
|
||||
module.exports.createCert = createCert;
|
||||
module.exports.clearCerts = clearCerts;
|
||||
module.exports.isRootCAFileExists = isRootCAFileExists;
|
||||
module.exports = crtMgr;
|
@@ -14,8 +14,7 @@ var http = require("http"),
|
||||
logUtil = require("./log"),
|
||||
httpsServerMgr = require("./httpsServerMgr");
|
||||
|
||||
var defaultRule = require("./rule_default.js"),
|
||||
userRule = defaultRule; //init
|
||||
var userRule = util.freshRequire('./rule_default');
|
||||
|
||||
function userRequestHandler(req,userRes){
|
||||
/*
|
||||
@@ -103,7 +102,7 @@ function userRequestHandler(req,userRes){
|
||||
resourceInfo.resBody = resBody;
|
||||
resourceInfo.length = resBody ? resBody.length : 0;
|
||||
resourceInfo.statusCode = statusCode;
|
||||
|
||||
|
||||
GLOBAL.recorder && GLOBAL.recorder.updateRecord(resourceInfoId,resourceInfo);
|
||||
|
||||
userRes.writeHead(statusCode,resHeader);
|
||||
@@ -140,6 +139,8 @@ function userRequestHandler(req,userRes){
|
||||
options.headers = util.lower_keys(options.headers);
|
||||
options.headers["content-length"] = reqData.length; //rewrite content length info
|
||||
|
||||
options.headers = util.upper_keys(options.headers);
|
||||
|
||||
//send request
|
||||
var proxyReq = ( /https/.test(protocol) ? https : http).request(options, function(res) {
|
||||
|
||||
@@ -202,7 +203,7 @@ function userRequestHandler(req,userRes){
|
||||
|
||||
//delay
|
||||
},function(callback){
|
||||
var pauseTimeInMS = userRule.pauseBeforeSendingResponse(req,res);
|
||||
var pauseTimeInMS = userRule.pauseBeforeSendingResponse(req,res);
|
||||
if(pauseTimeInMS){
|
||||
setTimeout(callback,pauseTimeInMS);
|
||||
}else{
|
||||
@@ -232,7 +233,7 @@ function userRequestHandler(req,userRes){
|
||||
resourceInfo.resHeader = resHeader;
|
||||
resourceInfo.resBody = serverResData;
|
||||
resourceInfo.length = serverResData ? serverResData.length : 0;
|
||||
|
||||
|
||||
GLOBAL.recorder && GLOBAL.recorder.updateRecord(resourceInfoId,resourceInfo);
|
||||
|
||||
callback();
|
||||
@@ -260,7 +261,7 @@ function userRequestHandler(req,userRes){
|
||||
userRes.end();
|
||||
});
|
||||
|
||||
proxyReq.end(reqData);
|
||||
proxyReq.end(reqData);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -272,14 +273,14 @@ function connectReqHandler(req, socket, head){
|
||||
|
||||
var shouldIntercept = userRule.shouldInterceptHttpsReq(req);
|
||||
|
||||
//bypass webSocket on webinterface
|
||||
//bypass webSocket on webinterface
|
||||
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");
|
||||
logUtil.printLog("==>will forward to local https server");
|
||||
}else{
|
||||
logUtil.printLog("==>will bypass the man-in-the-middle proxy");
|
||||
}
|
||||
@@ -325,7 +326,7 @@ function connectReqHandler(req, socket, head){
|
||||
|
||||
//determine the target server
|
||||
function(callback){
|
||||
|
||||
|
||||
if(shouldIntercept){
|
||||
proxyPort = internalHttpsPort;
|
||||
proxyHost = "127.0.0.1";
|
||||
@@ -356,11 +357,11 @@ function connectReqHandler(req, socket, head){
|
||||
|
||||
callback();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
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);
|
||||
}
|
||||
@@ -372,7 +373,7 @@ function connectReqHandler(req, socket, head){
|
||||
resourceInfo.resHeader = {};
|
||||
resourceInfo.resBody = "";
|
||||
resourceInfo.length = 0;
|
||||
|
||||
|
||||
GLOBAL.recorder && GLOBAL.recorder.updateRecord(resourceInfoId,resourceInfo);
|
||||
|
||||
callback();
|
||||
@@ -385,9 +386,13 @@ function connectReqHandler(req, socket, head){
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @return return the merged rule for reference
|
||||
*/
|
||||
function setRules(newRule){
|
||||
|
||||
if(!newRule){
|
||||
return;
|
||||
return userRule;
|
||||
}else{
|
||||
|
||||
if(!newRule.summary){
|
||||
@@ -396,8 +401,8 @@ function setRules(newRule){
|
||||
};
|
||||
}
|
||||
|
||||
userRule = util.merge(defaultRule,newRule);
|
||||
|
||||
userRule = util.merge(userRule,newRule);
|
||||
|
||||
var functions = [];
|
||||
if('function' == typeof(userRule.init)){
|
||||
functions.push(function(cb){
|
||||
@@ -416,6 +421,7 @@ function setRules(newRule){
|
||||
}
|
||||
});
|
||||
|
||||
return userRule;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -427,3 +433,4 @@ module.exports.userRequestHandler = userRequestHandler;
|
||||
module.exports.connectReqHandler = connectReqHandler;
|
||||
module.exports.setRules = setRules;
|
||||
module.exports.getRuleSummary = getRuleSummary;
|
||||
module.exports.token = Date.now();
|
||||
|
@@ -73,6 +73,7 @@ setTimeout(function(){
|
||||
|
||||
|
||||
module.exports = {
|
||||
token: Date.now(),
|
||||
summary:function(){
|
||||
var tip = "the default rule for AnyProxy.";
|
||||
if(!isRootCAFileExists){
|
||||
@@ -102,7 +103,10 @@ module.exports = {
|
||||
if(err){
|
||||
callback(200, {}, "[AnyProxy failed to load local file] " + err);
|
||||
}else{
|
||||
callback(200, {}, buffer);
|
||||
var header = {
|
||||
'Content-Type': utils.contentType(req.anyproxy_map_local)
|
||||
};
|
||||
callback(200, header, buffer);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
62
lib/util.js
62
lib/util.js
@@ -1,8 +1,11 @@
|
||||
var fs = require("fs"),
|
||||
path = require("path"),
|
||||
mime = require('mime-types'),
|
||||
color = require('colorful'),
|
||||
logUtil = require("./log"),
|
||||
exec = require('child_process').exec;
|
||||
|
||||
|
||||
const changeCase = require('change-case');
|
||||
// {"Content-Encoding":"gzip"} --> {"content-encoding":"gzip"}
|
||||
module.exports.lower_keys = function(obj){
|
||||
for(var key in obj){
|
||||
@@ -34,7 +37,7 @@ module.exports.getAnyProxyHome = function(){
|
||||
|
||||
if(!fs.existsSync(home)){
|
||||
try{
|
||||
fs.mkdirSync(home,0777);
|
||||
fs.mkdirSync(home, '0777');
|
||||
}catch(e){
|
||||
return null;
|
||||
}
|
||||
@@ -48,7 +51,7 @@ module.exports.generateCacheDir = function(){
|
||||
var rand = Math.floor(Math.random() * 1000000),
|
||||
cachePath = path.join(util.getAnyProxyHome(),"./" + CACHE_DIR_PREFIX + rand);
|
||||
|
||||
fs.mkdirSync(cachePath,0777);
|
||||
fs.mkdirSync(cachePath, '0777');
|
||||
return cachePath;
|
||||
}
|
||||
|
||||
@@ -60,7 +63,7 @@ module.exports.clearCacheDir = function(cb){
|
||||
if(isWin){
|
||||
exec("for /D %f in (" + dirNameWildCard + ") do rmdir %f /s /q",{cwd : home},cb);
|
||||
}else{
|
||||
exec("rm -rf " + dirNameWildCard + "",{cwd : home},cb);
|
||||
exec("rm -rf " + dirNameWildCard + "",{cwd : home},cb);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,4 +105,53 @@ module.exports.filewalker = function(root,cb){
|
||||
|
||||
cb && cb.apply(null,[null,ret]);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* 获取文件所对应的content-type以及content-length等信息
|
||||
* 比如在useLocalResponse的时候会使用到
|
||||
*/
|
||||
module.exports.contentType = function (filepath) {
|
||||
return mime.contentType(path.extname(filepath));
|
||||
};
|
||||
|
||||
/*
|
||||
* 读取file的大小,以byte为单位
|
||||
*/
|
||||
module.exports.contentLength = function (filepath) {
|
||||
try {
|
||||
var stat = fs.statSync(filepath);
|
||||
return stat.size;
|
||||
} catch (e) {
|
||||
logUtil.printLog(color.red("\nfailed to ready local file : " + filepath));
|
||||
logUtil.printLog(color.red(e));
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
module.exports.showRootInstallTip = function () {
|
||||
logUtil.printLog(color.red("can not find rootCA.crt or rootCA.key"), logUtil.T_ERR);
|
||||
logUtil.printLog(color.red("you may generate one by the following methods"), logUtil.T_ERR);
|
||||
logUtil.printLog(color.red("\twhen using globally : anyproxy --root"), logUtil.T_ERR);
|
||||
logUtil.printLog(color.red("\twhen using as a module : require(\"anyproxy\").generateRootCA();"), logUtil.T_ERR);
|
||||
logUtil.printLog(color.red("\tmore info : https://github.com/alibaba/anyproxy/wiki/How-to-config-https-proxy"), logUtil.T_ERR);
|
||||
};
|
||||
|
||||
/*
|
||||
* remove the cache before requering, the path SHOULD BE RELATIVE TO UTIL.JS
|
||||
*/
|
||||
module.exports.freshRequire = function (path) {
|
||||
delete require.cache[require.resolve(path)];
|
||||
return require(path);
|
||||
};
|
||||
|
||||
module.exports.upper_keys = function (obj) {
|
||||
var upperObject = {};
|
||||
for(var key in obj) {
|
||||
var upperKey = changeCase.headerCase(key);
|
||||
upperObject[upperKey] = obj[key];
|
||||
}
|
||||
|
||||
return upperObject;
|
||||
};
|
||||
|
||||
|
Reference in New Issue
Block a user