diff --git a/lib/recorder.js b/lib/recorder.js
new file mode 100644
index 0000000..504bc60
--- /dev/null
+++ b/lib/recorder.js
@@ -0,0 +1,120 @@
+//start recording and share a list when required
+var zlib = require('zlib'),
+ Datastore = require('nedb'),
+ util = require("util"),
+ events = require('events'),
+ db = new Datastore(); //in-memory store
+
+function Recorder(){
+ var self = this,
+ id = 1;
+
+ self.recordBodyMap = []; // id - body
+
+ self.updateRecord = function(id,info){
+ if(id < 0 ) return;
+
+ var finalInfo = normalizeInfo(id,info);
+
+ db.update({_id:id},finalInfo);
+ self.updateRecordBody(id,info);
+
+ self.emit("update",finalInfo);
+ };
+
+
+ self.appendRecord = function(info){
+ if(info.req.headers.anyproxy_web_req){ //request from web interface
+ return -1;
+ }
+
+ var thisId = id++,
+ finalInfo = normalizeInfo(thisId,info);
+ db.insert(finalInfo);
+ self.updateRecordBody(id,info);
+
+ self.emit("update",finalInfo);
+ return thisId;
+ };
+
+
+ //update recordBody if exits
+ self.updateRecordBody =function(id,info){
+ if(id == -1) return;
+
+ if(!id || !info.resBody) return;
+ //add to body map
+ //do not save image data
+ if(/image/.test(info.res.headers['content-type'])){
+ self.recordBodyMap[id] = "(image)";
+ }else if(/gzip/.test(info.res.headers['content-encoding'])){
+ zlib.unzip(info.resBody,function(err,buffer){
+ if(err){
+ self.recordBodyMap[id] = "(err when unzip response buffer)";
+ }else{
+ self.recordBodyMap[id] = buffer.toString();
+ }
+ });
+ }else{
+ self.recordBodyMap[id] = info.resBody.toString();
+ }
+ };
+
+
+ self.getBody = function(id){
+ if(id < 0){
+ return "";
+ }
+
+ return self.recordBodyMap[id] || "";
+ };
+
+ self.getSummaryList = function(cb){
+ db.find({},cb);
+ };
+}
+
+util.inherits(Recorder, events.EventEmitter);
+
+function normalizeInfo(id,info){
+ var singleRecord = {};
+
+ //general
+ singleRecord._id = id;
+ singleRecord.id = id;
+ singleRecord.url = info.url;
+ singleRecord.host = info.host;
+ singleRecord.path = info.path;
+ singleRecord.method = info.method;
+
+ //req
+ singleRecord.reqHeader = info.req.headers;
+ singleRecord.startTime = info.startTime;
+
+ //res
+ if(info.res){
+ singleRecord.statusCode= info.res.statusCode;
+ singleRecord.endTime = info.endTime;
+ singleRecord.resHeader = info.res.headers;
+ singleRecord.length = info.length;
+ if(info.res.headers['content-type']){
+ singleRecord.mime = info.res.headers['content-type'].split(";")[0];
+ }else{
+ singleRecord.mime = "";
+ }
+
+ singleRecord.duration = info.endTime - info.startTime;
+ }else{
+ singleRecord.statusCode= "";
+ singleRecord.endTime = "";
+ singleRecord.resHeader = "";
+ singleRecord.length = "";
+ singleRecord.mime = "";
+ singleRecord.duration = "";
+ }
+
+
+ return singleRecord;
+}
+
+module.exports = Recorder;
\ No newline at end of file
diff --git a/lib/requestHandler.js b/lib/requestHandler.js
index 30c37b3..3dc58cb 100644
--- a/lib/requestHandler.js
+++ b/lib/requestHandler.js
@@ -29,10 +29,24 @@ var handleRule = {
function userRequestHandler(req,userRes){
var host = req.headers.host,
- urlPattern = url.parse(req.url),
- path = urlPattern.path,
+ urlPattern = url.parse(req.url),
+ path = urlPattern.path,
ifLocalruleMatched = false,
- callback = null;
+ callback = null,
+ ifHttps = !!req.connection.encrypted && !/http:/.test(req.url),
+ resourceInfo = {},
+ resourceInfoId = -1;
+
+ resourceInfo.host = host;
+ resourceInfo.method = req.method;
+ resourceInfo.path = path;
+ resourceInfo.url = (ifHttps ? "https://" :"http://") + host + path;
+ resourceInfo.req = req;
+ resourceInfo.startTime = new Date().getTime();
+
+ try{
+ resourceInfoId = GLOBAL.recorder.appendRecord(resourceInfo);
+ }catch(e){}
console.log(color.green("\nreceived request to : " + host + path));
/*
@@ -41,6 +55,7 @@ function userRequestHandler(req,userRes){
in https server : /work/alibaba
*/
+ //handle OPTIONS request
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
@@ -83,8 +98,6 @@ function userRequestHandler(req,userRes){
}
}
-
-
//sleep for seconds if configed in the rule file
//see rule_sample.js
if(hostTest && pathTest && !!rule.sleep){
@@ -102,7 +115,6 @@ function userRequestHandler(req,userRes){
}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,
@@ -115,10 +127,28 @@ function userRequestHandler(req,userRes){
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);
+ var resData = [],
+ length = 0;
+ res.on("data",function(chunk){
+ resData.push(chunk);
+ length += chunk.length;
+ userRes.write(chunk);
+ });
+
+ res.on("end",function(){
+ callback && callback.call(null,userRes);
userRes.end();
+
+ //update record info
+ resourceInfo.endTime = new Date().getTime();
+ resourceInfo.res = res;
+ resourceInfo.resBody = Buffer.concat(resData);
+ resourceInfo.length = length;
+
+ try{
+ GLOBAL.recorder.updateRecord(resourceInfoId,resourceInfo);
+ }catch(e){}
+
});
res.on('error',function(error){
console.log('error',error);
diff --git a/package.json b/package.json
index 32610c7..91a63f2 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "anyproxy",
- "version": "1.2.11",
+ "version": "1.3.0",
"description": "a charles/fiddler like proxy written in NodeJs, which can handle HTTPS requests and CROS perfectly.",
"main": "proxy.js",
"bin": {
@@ -11,7 +11,11 @@
"async-task-mgr": "^1.0.1",
"colorful": "^2.1.0",
"commander": "~2.3.0",
- "sleep": "~1.1.8"
+ "entities": "^1.1.1",
+ "express": "^4.8.5",
+ "nedb": "^0.11.0",
+ "sleep": "~1.1.8",
+ "ws": "^0.4.32"
},
"devDependencies": {
"tunnel": "0.0.3"
diff --git a/proxy.js b/proxy.js
index 9e74f4a..8775137 100644
--- a/proxy.js
+++ b/proxy.js
@@ -1,21 +1,27 @@
var http = require('http'),
https = require('https'),
fs = require('fs'),
- net = require('net'),
async = require("async"),
url = require('url'),
- exec = require('child_process').exec,
program = require('commander'),
color = require('colorful'),
certMgr = require("./lib/certMgr"),
getPort = require("./lib/getPort"),
- requestHandler = require("./lib/requestHandler");
+ requestHandler = require("./lib/requestHandler"),
+ Recorder = require("./lib/Recorder"),
+ entities = require("entities"),
+ express = require("express"),
+ WebSocketServer= require('ws').Server;
+
+GLOBAL.recorder = new Recorder();
var T_TYPE_HTTP = 0,
- T_TYPE_HTTPS = 1,
- DEFAULT_PORT = 8001,
- DEFAULT_HOST = "localhost",
- DEFAULT_TYPE = T_TYPE_HTTP;
+ T_TYPE_HTTPS = 1,
+ DEFAULT_PORT = 8001,
+ DEFAULT_WEB_PORT = 8002,
+ DEFAULT_WEBSOCKET_PORT = 8003,
+ DEFAULT_HOST = "localhost",
+ DEFAULT_TYPE = T_TYPE_HTTP;
function proxyServer(type, port, hostname,ruleFile){
var self = this,
@@ -29,6 +35,8 @@ function proxyServer(type, port, hostname,ruleFile){
console.log(color.green("server closed :" + proxyHost + ":" + proxyPort));
}
+ startWebServer();
+
if(ruleFile){
if(fs.existsSync(ruleFile)){
try{ //for abs path
@@ -88,6 +96,59 @@ function proxyServer(type, port, hostname,ruleFile){
}
+function startWebServer(port){
+ port = port || DEFAULT_WEB_PORT;
+
+ //web interface
+ var app = express();
+ app.use(function(req, res, next) {
+ res.setHeader("note", "THIS IS A REQUEST FROM ANYPROXY WEB INTERFACE");
+ return next();
+ });
+
+ app.get("/summary",function(req,res){
+ GLOBAL.recorder.getSummaryList(function(err,docs){
+ if(err){
+ res.end(err.toString());
+ }else{
+ res.json(docs.slice(docs.length -500));
+ }
+ });
+ });
+
+ app.get("/body",function(req,res){
+ var reqQuery = url.parse(req.url,true);
+ var id = reqQuery.query.id;
+
+ res.setHeader("Content-Type","text/html");
+ res.writeHead(200);
+
+ var body = GLOBAL.recorder.getBody(id);
+ res.end(entities.encodeHTML(body));
+ });
+
+ app.use(express.static(__dirname + '/web'));
+
+ app.listen(port);
+
+ var tipText = "web interface started at port " + port;
+ console.log(color.green(tipText));
+
+
+
+ //web socket interface
+ var wss = new WebSocketServer({port: DEFAULT_WEBSOCKET_PORT});
+ wss.broadcast = function(data) {
+ for(var i in this.clients){
+ this.clients[i].send(data);
+ }
+ };
+ GLOBAL.recorder.on("update",function(data){
+ wss.broadcast( JSON.stringify(data) );
+ });
+}
+
+
module.exports.proxyServer = proxyServer;
module.exports.generateRootCA = certMgr.generateRootCA;
module.exports.isRootCAFileExists = certMgr.isRootCAFileExists;
\ No newline at end of file
diff --git a/rule_sample.js b/rule_sample.js
index 9bb2221..7470d01 100644
--- a/rule_sample.js
+++ b/rule_sample.js
@@ -23,7 +23,7 @@ var rules = {
"sleep" :5//seconds
},{
"host" :/./,
- "path" :/(.*)\.html/,
+ "path" :/html/,
"callback" :function(res){
//remoty.js will be inject into response via callback
res.write("
+
+
+
+