anyproxy/proxy.js

376 lines
14 KiB
JavaScript
Raw Normal View History

2014-09-03 01:10:26 +08:00
try{
GLOBAL.util = require('./lib/util');
2014-09-03 01:10:26 +08:00
}catch(e){}
2014-08-11 16:43:14 +08:00
var http = require('http'),
2015-04-20 09:39:27 +08:00
https = require('https'),
fs = require('fs'),
async = require("async"),
url = require('url'),
program = require('commander'),
color = require('colorful'),
certMgr = require("./lib/certMgr"),
getPort = require("./lib/getPort"),
requestHandler = require("./lib/requestHandler"),
Recorder = require("./lib/recorder"),
logUtil = require("./lib/log"),
inherits = require("util").inherits,
util = require("./lib/util"),
path = require("path"),
juicer = require('juicer'),
events = require("events"),
express = require("express"),
ip = require("ip"),
fork = require("child_process").fork,
ThrottleGroup = require("stream-throttle").ThrottleGroup,
iconv = require('iconv-lite'),
Buffer = require('buffer').Buffer,
WebSocketServer = require('ws').Server;
2014-08-27 17:42:42 +08:00
2014-09-04 11:22:35 +08:00
var T_TYPE_HTTP = 0,
2014-08-27 17:42:42 +08:00
T_TYPE_HTTPS = 1,
DEFAULT_PORT = 8001,
2015-02-24 15:57:27 +08:00
DEFAULT_WEB_PORT = 8002, // port for web interface
DEFAULT_WEBSOCKET_PORT = 8003, // internal web socket for web interface, not for end users
2014-09-18 17:42:01 +08:00
DEFAULT_CONFIG_PORT = 8088,
2014-08-27 17:42:42 +08:00
DEFAULT_HOST = "localhost",
DEFAULT_TYPE = T_TYPE_HTTP;
2014-08-11 16:43:14 +08:00
2014-09-06 09:50:49 +08:00
var default_rule = require('./lib/rule_default');
2014-12-08 17:13:41 +08:00
2014-12-08 15:35:46 +08:00
//may be unreliable in windows
try{
2014-12-08 17:13:41 +08:00
var anyproxyHome = path.join(util.getUserHome(),"/.anyproxy/");
2014-12-08 15:35:46 +08:00
if(!fs.existsSync(anyproxyHome)){
fs.mkdirSync(anyproxyHome);
}
if(fs.existsSync(path.join(anyproxyHome,"rule_default.js"))){
default_rule = require(path.join(anyproxyHome,"rule_default"));
}
if(fs.existsSync(path.join(process.cwd(),'rule.js'))){
default_rule = require(path.join(process.cwd(),'rule'));
}
}catch(e){
if(e){
logUtil.printLog("error" + e, logUtil.T_ERR);
throw e;
}
}
2014-09-06 09:50:49 +08:00
2014-09-04 11:22:35 +08:00
//option
//option.type : 'http'(default) or 'https'
//option.port : 8001(default)
//option.hostname : localhost(default)
2014-09-18 17:42:01 +08:00
//option.rule : ruleModule
2014-09-18 13:36:27 +08:00
//option.webPort : 8002(default)
//option.socketPort : 8003(default)
2014-09-18 17:42:01 +08:00
//option.webConfigPort : 8088(default)
2014-10-23 11:07:02 +08:00
//option.dbFile : null(default)
//option.throttle : null(default)
2014-11-04 17:23:52 +08:00
//option.disableWebInterface
//option.silent : false(default)
2015-01-22 10:16:19 +08:00
//option.interceptHttps ,internal param for https
2014-09-04 11:22:35 +08:00
function proxyServer(option){
option = option || {};
var self = this,
2014-11-04 17:23:52 +08:00
proxyType = /https/i.test(option.type || DEFAULT_TYPE) ? T_TYPE_HTTPS : T_TYPE_HTTP ,
proxyPort = option.port || DEFAULT_PORT,
proxyHost = option.hostname || DEFAULT_HOST,
proxyRules = option.rule || default_rule,
proxyWebPort = option.webPort || DEFAULT_WEB_PORT, //port for web interface
socketPort = option.socketPort || DEFAULT_WEBSOCKET_PORT, //port for websocket
proxyConfigPort = option.webConfigPort || DEFAULT_CONFIG_PORT, //port to ui config server
disableWebInterface = !!option.disableWebInterface,
2015-04-20 09:39:27 +08:00
ifSilent = !!option.silent,
wss,
child_webServer;
if(ifSilent){
logUtil.setPrintStatus(false);
}
2014-10-23 11:07:02 +08:00
if(option.dbFile){
GLOBAL.recorder = new Recorder({filename: option.dbFile});
}else{
GLOBAL.recorder = new Recorder();
}
2015-01-22 10:16:19 +08:00
if(!!option.interceptHttps){
default_rule.setInterceptFlag(true);
}
2014-10-29 16:20:18 +08:00
if(option.throttle){
logUtil.printLog("throttle :" + option.throttle + "kb/s");
2014-10-29 16:20:18 +08:00
GLOBAL._throttle = new ThrottleGroup({rate: 1024 * parseInt(option.throttle) }); // rate - byte/sec
}
2014-09-05 13:53:12 +08:00
requestHandler.setRules(proxyRules); //TODO : optimize calling for set rule
2014-08-14 22:58:30 +08:00
self.httpProxyServer = null;
2014-08-13 11:51:39 +08:00
2014-08-14 22:58:30 +08:00
async.series(
[
2014-08-13 11:51:39 +08:00
//creat proxy server
2014-08-11 16:43:14 +08:00
function(callback){
if(proxyType == T_TYPE_HTTPS){
2014-08-11 17:48:32 +08:00
certMgr.getCertificate(proxyHost,function(err,keyContent,crtContent){
2014-08-11 16:43:14 +08:00
if(err){
callback(err);
}else{
2014-08-14 22:58:30 +08:00
self.httpProxyServer = https.createServer({
2014-08-11 16:43:14 +08:00
key : keyContent,
cert: crtContent
2014-08-13 11:51:39 +08:00
},requestHandler.userRequestHandler);
2014-08-11 16:43:14 +08:00
callback(null);
}
});
}else{
2014-08-14 22:58:30 +08:00
self.httpProxyServer = http.createServer(requestHandler.userRequestHandler);
2014-08-11 16:43:14 +08:00
callback(null);
}
2014-08-11 16:43:14 +08:00
},
function(callback){
2015-04-20 09:39:27 +08:00
//listen CONNECT request for https over http
2014-08-14 22:58:30 +08:00
self.httpProxyServer.on('connect',requestHandler.connectReqHandler);
2015-04-20 09:39:27 +08:00
//start proxy server
2014-08-14 22:58:30 +08:00
self.httpProxyServer.listen(proxyPort);
2014-08-11 16:43:14 +08:00
callback(null);
2014-09-04 11:22:35 +08:00
},
//start web interface
function(callback){
2014-11-04 17:23:52 +08:00
if(disableWebInterface){
logUtil.printLog('web interface is disabled');
2014-11-04 17:23:52 +08:00
}else{
2015-04-20 09:39:27 +08:00
//[beta] customMenu
2015-01-05 17:12:50 +08:00
var customMenuConfig = proxyRules.customMenu,
menuList = [],
menuListStr;
if(customMenuConfig && customMenuConfig.length){
for(var i = 0 ; i < customMenuConfig.length ; i++){
menuList.push(customMenuConfig[i].name);
}
}
menuListStr = menuList.join("@@@");
2014-11-04 17:23:52 +08:00
//web interface
2015-01-05 17:12:50 +08:00
var args = [proxyWebPort, socketPort, proxyConfigPort, requestHandler.getRuleSummary(), ip.address(),menuListStr];
2015-04-20 09:39:27 +08:00
child_webServer = fork(path.join(__dirname,"./webServer.js"),args);
//deal websocket data
var wsDataDealer = function(){};
inherits(wsDataDealer,events.EventEmitter);
var dealer = new wsDataDealer();
GLOBAL.recorder.on("update",function(data){
wss && wss.broadcast({
type : "update",
content: data
});
});
dealer.on("message",function(ws,jsonData){
if(jsonData.type == "reqBody" && jsonData.id){
GLOBAL.recorder.getBodyUTF8(jsonData.id, function(err, data){
var result = {};
if(err){
result = {
2015-01-28 15:33:00 +08:00
type : "body",
id : null,
body : null,
2015-04-20 09:39:27 +08:00
error: err.toString()
};
}else{
result = {
type : "body",
id : data.id,
body : data
};
2015-01-28 15:33:00 +08:00
}
2015-04-20 09:39:27 +08:00
ws.send(JSON.stringify(result));
2014-11-04 17:23:52 +08:00
});
}
});
2014-09-18 17:42:01 +08:00
2015-04-20 09:39:27 +08:00
dealer.on("message",function(ws,jsonData){
// another dealer here...
});
2015-04-20 09:39:27 +08:00
//web socket interface
wss = new WebSocketServer({port: socketPort});
wss.on("connection",function(ws){
ws.on("message",function(msg){
try{
var msgObj = JSON.parse(msg);
dealer && dealer.emit("message",ws,msgObj);
}catch(e){
var result = {
type : "error",
error: "failed to parse your request : " + e.toString()
};
ws.send(JSON.stringify(result));
}
});
2014-11-04 17:23:52 +08:00
});
2015-04-20 09:39:27 +08:00
wss.broadcast = function(data) {
if(typeof data == "object"){
data = JSON.stringify(data);
}
2014-11-04 17:23:52 +08:00
2015-04-20 09:39:27 +08:00
for(var i in this.clients){
try{
this.clients[i].send(data);
}catch(e){
logUtil.printLog("websocket failed to send data, " + e, logUtil.T_ERR);
}
}
};
//watch dog
setInterval(function(argument) {
2014-09-18 17:42:01 +08:00
child_webServer.send({
2015-04-20 09:39:27 +08:00
type:"watch"
2014-10-10 10:55:46 +08:00
});
2015-04-20 09:39:27 +08:00
},5000);
}
callback(null);
},
2014-09-11 10:22:08 +08:00
2015-04-20 09:39:27 +08:00
//server status manager
function(callback){
2014-09-11 10:22:08 +08:00
2015-04-20 09:39:27 +08:00
//kill web server when father process exits
process.on("exit",function(code){
child_webServer.kill();
logUtil.printLog('AnyProxy is about to exit with code: ' + code, logUtil.T_ERR);
process.exit();
});
process.on("uncaughtException",function(err){
child_webServer.kill();
logUtil.printLog('Caught exception: ' + err, logUtil.T_ERR);
process.exit();
});
var tipText,webUrl;
webUrl = "http://" + ip.address() + ":" + proxyWebPort +"/";
tipText = "GUI interface started at : " + webUrl;
logUtil.printLog(color.green(tipText));
2014-09-22 13:59:23 +08:00
2015-04-20 09:39:27 +08:00
// tipText = "[alpha]qr code to for iOS client: " + webUrl + "qr";
// logUtil.printLog(color.green(tipText));
callback(null);
2014-08-11 16:43:14 +08:00
}
],
//final callback
function(err,result){
if(!err){
2014-12-08 17:13:41 +08:00
var tipText = (proxyType == T_TYPE_HTTP ? "Http" : "Https") + " proxy started at " + color.bold(ip.address() + ":" + proxyPort);
logUtil.printLog(color.green(tipText));
2014-08-11 16:43:14 +08:00
}else{
2014-08-13 11:51:39 +08:00
var tipText = "err when start proxy server :(";
logUtil.printLog(color.red(tipText), logUtil.T_ERR);
logUtil.printLog(err, logUtil.T_ERR);
2014-08-11 16:43:14 +08:00
}
}
);
2014-08-14 22:58:30 +08:00
2014-09-04 11:22:35 +08:00
self.close = function(){
self.httpProxyServer && self.httpProxyServer.close();
logUtil.printLog(color.green("server closed :" + proxyHost + ":" + proxyPort));
2014-09-04 11:22:35 +08:00
}
2014-08-11 16:43:14 +08:00
}
2014-09-18 13:36:27 +08:00
// BETA : UIConfigServer
2014-09-11 10:22:08 +08:00
function UIConfigServer(port){
var self = this;
var app = express(),
customerRule = {
summary: function(){
logUtil.printLog("replace some response with local response");
2014-09-11 10:22:08 +08:00
return "replace some response with local response";
}
},
userKey;
2014-09-18 13:36:27 +08:00
port = port || DEFAULT_CONFIG_PORT;
2014-09-11 10:22:08 +08:00
customerRule.shouldUseLocalResponse = function(req,reqBody){
var url = req.url;
if(userKey){
var ifMatch = false;
userKey.map(function(item){
if(ifMatch) return;
var matchCount = 0;
if( !item.urlKey && !item.reqBodyKey){
ifMatch = false;
return;
}else{
if(!item.urlKey || (item.urlKey && url.indexOf(item.urlKey) >= 0 ) ){
++matchCount;
}
if(!item.reqBodyKey || (item.reqBodyKey && reqBody.toString().indexOf(item.reqBodyKey) >= 0) ){
++matchCount;
}
ifMatch = (matchCount==2);
if(ifMatch){
req.willResponse = item.localResponse;
}
}
});
return ifMatch;
}else{
return false;
}
};
customerRule.dealLocalResponse = function(req,reqBody,callback){
2015-01-05 17:12:50 +08:00
callback(200,{"content-type":"text/html"},req.willResponse);
2014-09-11 10:22:08 +08:00
return req.willResponse;
};
app.post("/update",function(req,res){
var data = "";
req.on("data",function(chunk){
data += chunk;
});
req.on("end",function(){
userKey = JSON.parse(data);
res.statusCode = 200;
res.setHeader("Content-Type", "application/json;charset=UTF-8");
res.end(JSON.stringify({success : true}));
requestHandler.setRules(customerRule);
2014-09-11 10:22:08 +08:00
self.emit("rule_changed");
});
});
app.use(express.static(__dirname + "/web_uiconfig"));
app.listen(port);
self.app = app;
}
2015-04-20 09:39:27 +08:00
// var configServer = new UIConfigServer(proxyConfigPort);
// configServer.on("rule_changed",function() {});
// inherits(UIConfigServer, events.EventEmitter);
2014-09-11 10:22:08 +08:00
2014-08-14 22:58:30 +08:00
module.exports.proxyServer = proxyServer;
2014-08-14 10:59:49 +08:00
module.exports.generateRootCA = certMgr.generateRootCA;
module.exports.isRootCAFileExists = certMgr.isRootCAFileExists;