var http = require("http"), https = require("https"), net = require("net"), fs = require("fs"), url = require("url"), pathUtil = require("path"), color = require("colorful"), sleep = require("sleep"), Buffer = require('buffer').Buffer, httpsServerMgr = require("./httpsServerMgr"); var httpsServerMgrInstance = new httpsServerMgr(); //default rule var handleRule = { map :[ // { // host :".", // path :"/path/test", // localFile :"", // localDir :"~/" // } ] ,httpsConfig:{ bypassAll : true, interceptDomains:["^.*alibaba-inc\.com$"] } }; function userRequestHandler(req,userRes){ var host = req.headers.host, urlPattern = url.parse(req.url), path = urlPattern.path, ifLocalruleMatched = false, callback = null; console.log(color.green("\nreceived request to : " + host + path)); /* req.url is wired in http server : http://www.baidu.com/a/b/c in https server : /work/alibaba */ if(req.method == "OPTIONS"){ console.log("==>OPTIONS req for CROS, will allow all"); userRes.writeHead(200,mergeCORSHeader(req.headers)); //remove any cache related header, add crossdomain headers userRes.end(); return; } //try to mactch rule file for(var index in handleRule.map){ var rule = handleRule.map[index]; var hostTest = new RegExp(rule.host).test(host), 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; //localfile not set, map to dir if(!targetLocalfile){ //find file in dir, /a/b/file.html -> dir + b/file.html var remotePathWithoutPrefix = path.replace(new RegExp(rule.path),""); //remove prefix targetLocalfile = pathUtil.join(rule.localDir,remotePathWithoutPrefix); } console.log("==>local file: " + targetLocalfile); if(fs.existsSync(targetLocalfile)){ try{ var fsStream = fs.createReadStream(targetLocalfile); userRes.writeHead(200,mergeCORSHeader( req.headers,{}) ); //CORS for localfiles fsStream.pipe(userRes); ifLocalruleMatched = true; break; }catch(e){ console.log(e.message); } }else{ console.log("file not exist : " + targetLocalfile); } } //sleep for seconds if configed in the rule file //see rule_sample.js if(hostTest && pathTest && !!rule.sleep){ console.log(color.yellow('[' + req.url + '] will sleep for ' + rule.sleep + ' seconds.')); sleep.sleep(rule.sleep); } if(hostTest && pathTest && !!rule.callback){ callback = rule.callback; } } 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 : urlPattern.hostname || req.headers.host, port : urlPattern.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,mergeCORSHeader(req.headers,res.headers)); res.pipe(userRes,{end:false}); res.on('end',function(){ if(callback)callback(userRes); userRes.end(); }); }); proxyReq.on("error",function(e){ console.log("err with request :" + e.code + " " + req.url); userRes.end(); }); req.pipe(proxyReq); } } 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 shouldBypass = true; for(var index in httpsRule.interceptDomains){ var reg = new RegExp(httpsRule.interceptDomains[index]); if( reg.test(hostname) ){ shouldBypass = false; break; } } } console.log(color.green("\nreceived https CONNECT request " + hostname)); if(shouldBypass){ console.log("==>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("==>meet the rules, will forward to local https server"); //forward the https-request to local https server httpsServerMgrInstance.fetchPort(hostname,userRequestHandler,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; } } // https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS function mergeCORSHeader(reqHeader,originHeader){ var targetObj = originHeader || {}; delete targetObj["Access-Control-Allow-Credentials"]; delete targetObj["Access-Control-Allow-Origin"]; delete targetObj["Access-Control-Allow-Methods"]; delete targetObj["Access-Control-Allow-Headers"]; targetObj["access-control-allow-credentials"] = "true"; targetObj["access-control-allow-origin"] = reqHeader['origin'] || "-___-||"; targetObj["access-control-allow-methods"] = "GET, POST, PUT"; targetObj["access-control-allow-headers"] = reqHeader['access-control-request-headers'] || "-___-||"; // Disable caching // If the response status is 304 not modified, the data event of response will not emmit targetObj["Transfer-Encoding"] = "chunked" targetObj["Cache-Control"] = "no-cache, no-store, must-revalidate"; targetObj["Pragma"] = "no-cache"; targetObj["Expires"] = 0; return targetObj; } module.exports.userRequestHandler = userRequestHandler; module.exports.connectReqHandler = connectReqHandler; module.exports.setRules = setRules;